diff --git a/apps/desktop/scripts/assert-dist-built.mjs b/apps/desktop/scripts/assert-dist-built.mjs index 3715eba4e2234..093004e319a57 100644 --- a/apps/desktop/scripts/assert-dist-built.mjs +++ b/apps/desktop/scripts/assert-dist-built.mjs @@ -11,10 +11,12 @@ // inherits it. It fails loud and early instead of shipping a broken bundle. // See issues #39484 (renderer blank page) and #41327 / #39472 (dashboard 404). -import { existsSync, statSync, readdirSync } from "fs" +import { existsSync, readFileSync, statSync, readdirSync } from "fs" import { join, resolve } from "path" import { isMain } from "./utils.mjs" +const ROUTER_CONTEXT_ERROR = "may be used only in the context of a" + // Pure check — returns { ok: true } or { ok: false, error: "..." }. // Kept side-effect-free so it can be unit tested without spawning a process. export function checkDistBuilt(distDir) { @@ -41,6 +43,17 @@ export function checkDistBuilt(distDir) { return { ok: false, error: `dist/assets has no built JS bundle (expected vite output under ${assetsDir})` } } + const routerContextAssets = readdirSync(assetsDir) + .filter(name => name.endsWith(".js")) + .filter(name => readFileSync(join(assetsDir, name), "utf8").includes(ROUTER_CONTEXT_ERROR)) + + if (routerContextAssets.length > 1) { + return { + ok: false, + error: `react-router context invariant found in multiple JS assets: ${routerContextAssets.join(", ")}` + } + } + return { ok: true } } diff --git a/apps/desktop/scripts/assert-dist-built.test.mjs b/apps/desktop/scripts/assert-dist-built.test.mjs index 7c99ac76d0d2d..96a2348d52513 100644 --- a/apps/desktop/scripts/assert-dist-built.test.mjs +++ b/apps/desktop/scripts/assert-dist-built.test.mjs @@ -14,6 +14,14 @@ function makeDist(extra) { return { tempRoot, distDir } } +function writeRouterAsset(distDir, name) { + fs.writeFileSync( + path.join(distDir, 'assets', name), + `throw new Error('may be used only in the context of a ')`, + 'utf8', + ) +} + test('checkDistBuilt passes when index.html + an assets JS bundle exist', () => { const { tempRoot, distDir } = makeDist(d => { fs.writeFileSync(path.join(d, 'index.html'), '
', 'utf8') @@ -82,3 +90,35 @@ test('checkDistBuilt fails when assets/ has no JS bundle', () => { fs.rmSync(tempRoot, { recursive: true, force: true }) } }) + +test('checkDistBuilt passes when the Router context invariant is in one JS asset', () => { + const { tempRoot, distDir } = makeDist(d => { + fs.writeFileSync(path.join(d, 'index.html'), '', 'utf8') + fs.mkdirSync(path.join(d, 'assets')) + writeRouterAsset(d, 'vendor-react-abc123.js') + fs.writeFileSync(path.join(d, 'assets', 'command-def456.js'), 'console.log(1)', 'utf8') + }) + try { + assert.deepEqual(checkDistBuilt(distDir), { ok: true }) + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }) + } +}) + +test('checkDistBuilt fails when the Router context invariant is in multiple JS assets', () => { + const { tempRoot, distDir } = makeDist(d => { + fs.writeFileSync(path.join(d, 'index.html'), '', 'utf8') + fs.mkdirSync(path.join(d, 'assets')) + writeRouterAsset(d, 'vendor-react-abc123.js') + writeRouterAsset(d, 'command-def456.js') + }) + try { + const result = checkDistBuilt(distDir) + assert.equal(result.ok, false) + assert.match(result.error, /react-router context invariant found in multiple JS assets/) + assert.match(result.error, /vendor-react-abc123\.js/) + assert.match(result.error, /command-def456\.js/) + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }) + } +}) diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index d6c14e7008e63..971ea8bd60deb 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -112,7 +112,7 @@ export default defineConfig(({ command }) => ({ // the heavy chunk, and the entry then statically imports 19 MB of // shiki just to reach react/hast utils — putting the heavy chunk // right back on the boot path. - { name: 'vendor-react', test: /node_modules[\\/](react|react-dom|scheduler)[\\/]/ }, + { name: 'vendor-react', test: /node_modules[\\/](react|react-dom|scheduler|react-router)[\\/]/ }, { name: 'vendor-md', test: /node_modules[\\/](property-information|hast-util-[^\\/]+|mdast-util-[^\\/]+|micromark[^\\/]*|unist-util-[^\\/]+|vfile[^\\/]*|unified|stringify-entities|space-separated-tokens|comma-separated-tokens|zwitch|html-void-elements|devlop|style-to-js|style-to-object|clsx)[\\/]/ @@ -152,7 +152,7 @@ export default defineConfig(({ command }) => ({ 'react/jsx-dev-runtime': path.resolve(__dirname, '../../node_modules/react/jsx-dev-runtime.js'), 'react/jsx-runtime': path.resolve(__dirname, '../../node_modules/react/jsx-runtime.js') }, - dedupe: ['react', 'react-dom'] + dedupe: ['react', 'react-dom', 'react-router'] }, server: { host: '127.0.0.1',