UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

109 lines (99 loc) 4.48 kB
// a browser content blocker (safari's built-in blockers, ublock, adguard, // brave shields) matches filter rules against the request url. in dev the // vite module url mirrors the source path, so an ordinary app file can match // a tracking rule purely by where it lives — easylist, easyprivacy and // adguard all ship the plain substring rule `/analytics/pageview`, which is // enough to refuse `src/features/site/analytics/PageviewTracker.tsx`. // // one blocked module fails the whole dynamic import, and the browser reports // only `TypeError: Importing a module script failed` pointing at one's own // route loader. nothing in that names the file, the app, or the blocker, so // it reads as a framework or vite bug and sends people restarting dev servers // and clearing caches. // // a blocked request also fails `fetch()`, so walking the route's module graph // from the browser finds the exact module the blocker refused. dev only, and // only after a route has already failed to load. const MAX_MODULES = 500 const TIMEOUT_MS = 5000 // vite rewrites bare specifiers to absolute paths, so most of these are // already root-relative; the rest resolve against the importing module. function collectImports(source: string, base: string): string[] { const out: string[] = [] const add = (spec: string) => { if (!/^[./]|^https?:/.test(spec)) return try { out.push(new URL(spec, base).href) } catch {} } for (const m of source.matchAll(/(?:^|[\s;}])(?:import|export)[^'"]{0,300}?from\s*["']([^"']+)["']/g)) add(m[1]) for (const m of source.matchAll(/\bimport\s*\(\s*["']([^"']+)["']\s*\)/g)) add(m[1]) for (const m of source.matchAll(/(?:^|[\s;}])import\s*["']([^"']+)["']/g)) add(m[1]) return out } /** * Walks the module graph under `entryUrl` looking for a module the browser * refuses to fetch. Returns the blocked module's url, or null when every * module in the graph is reachable (which means the import failed for some * other reason, such as a syntax error inside one of them). */ export async function findBlockedModule(entryUrl: string): Promise<string | null> { const deadline = Date.now() + TIMEOUT_MS const seen = new Set<string>() let frontier = [new URL(entryUrl, window.location.href).href] while (frontier.length && seen.size < MAX_MODULES && Date.now() < deadline) { const batch = frontier.filter((url) => !seen.has(url)).slice(0, 32) if (!batch.length) break frontier = frontier.slice(batch.length) const results = await Promise.all( batch.map(async (url) => { seen.add(url) let res: Response try { res = await fetch(url) } catch { // the request never reached the server. in dev the server is // demonstrably up (the page and one's own runtime loaded from it), // so this is the browser refusing to make the request. return { url, blocked: true, imports: [] as string[] } } if (!res.ok) return { url, blocked: false, imports: [] } const type = res.headers.get('content-type') ?? '' if (!type.includes('javascript')) return { url, blocked: false, imports: [] } return { url, blocked: false, imports: collectImports(await res.text(), url) } }) ) for (const result of results) { if (result.blocked) return result.url for (const next of result.imports) { if (!seen.has(next)) frontier.push(next) } } } return null } /** * Turns a failed route import into a message that names the responsible file. * Returns null when the graph is fully reachable, leaving the original error * as the only report. */ export async function diagnoseRouteLoadFailure( routeId: string, routeUrl: string ): Promise<string | null> { const blocked = await findBlockedModule(routeUrl) if (!blocked) return null const path = blocked.replace(window.location.origin, '') return [ `Route "${routeId}" failed to load because this browser refused to fetch ${path}`, ``, `That request never reached the dev server, so a content blocker or privacy`, `extension is blocking it. In dev the module URL is the source path, and a`, `path containing a word like "analytics", "pageview" or "track" matches the`, `tracking rules those blockers ship.`, ``, `Rename the file or the directory so its path no longer matches, or allow`, `${window.location.origin} in the blocker.`, ].join('\n') }