fix(core): admin UI fails to load on Windows in dev mode - #2390
Conversation
The Lingui macro-compiler plugin in dev mode compared Vite's forward-slash module ids against `adminSourcePath` straight out of `path.resolve()`, which uses backslash separators on Windows. The `startsWith` check was therefore silently always false there, so the plugin's hooks never matched anything, and admin `@lingui/core/macro` calls shipped uncompiled to the browser. Vite then tried to pre-bundle `@lingui/core/macro` as a real dependency, which fails at runtime (babel-plugin-macros missing, then `process is not defined`) since that module is only meant to be consumed by a macro loader. Fixes by: - Normalizing adminSourcePath to forward slashes before comparing against Vite's ids/importers. - Converting the dynamic `import()` of @babel/core to a file:// URL, which Node's ESM loader requires for absolute paths on Windows. - Resolving the Lingui babel plugin to an absolute path via admin's own node_modules, instead of letting Babel resolve the bare specifier relative to the transformed file (which may live outside admin's dependency tree, e.g. in a demo/template app). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 6b6a8ac The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
This PR correctly diagnoses and fixes a Windows-specific dev-mode failure in the admin Lingui macro compiler. The approach is right: Vite normalizes module ids to forward slashes but path.resolve() returns backslashes on Windows, so the plugin’s startsWith check was a silent no-op. The patch applies three targeted fixes inside linguiMacroPlugin and adds cross-platform regression tests.
I read the changed plugin, test file, changeset, package dependencies, and public exports. The fixes are localized, the dependencies (@babel/core and @lingui/babel-plugin-lingui-macro live in @emdash-cms/admin devDependencies) are correct, and exporting linguiMacroPlugin only from the internal integration file does not change the public API.
The only issue is an AGENTS.md comment convention violation: the multi-line comment above the new describe block is a PR-description summary/historical narrative rather than code-readable context. It should be removed (the describe title and the test bodies already explain the behavior).
| // Regression: on Windows, `path.resolve()` returns backslash-separated | ||
| // paths, but Vite always normalizes module ids/importers to forward | ||
| // slashes — even on Windows. `linguiMacroPlugin` used to compare an | ||
| // `adminSourcePath` straight out of `resolve()` against those ids with | ||
| // `id.startsWith(adminSourcePath)`, which is silently always `false` on | ||
| // Windows. The plugin's hooks became permanent no-ops there: Lingui macro | ||
| // calls (`@lingui/core/macro`) shipped uncompiled to the browser, which | ||
| // then failed to hydrate the admin UI entirely (it never got past the | ||
| // "Loading EmDash..." screen). These tests reproduce that mismatch | ||
| // directly, with a synthetic backslash `adminSourcePath` — independent of | ||
| // the host OS running the test — so they fail on the pre-fix | ||
| // `id.startsWith(adminSourcePath)` comparison on any platform, not just | ||
| // Windows CI. |
There was a problem hiding this comment.
[needs fixing] This large comment block is a PR-description summary: it narrates the bug being fixed, the pre-failure behavior, and how the tests reproduce it. AGENTS.md says comments are not PR descriptions or summaries of the change — that context belongs in the commit message/PR body, not in code. The describe title and the test names already explain the behavior; this block should be deleted.
| // Regression: on Windows, `path.resolve()` returns backslash-separated | |
| // paths, but Vite always normalizes module ids/importers to forward | |
| // slashes — even on Windows. `linguiMacroPlugin` used to compare an | |
| // `adminSourcePath` straight out of `resolve()` against those ids with | |
| // `id.startsWith(adminSourcePath)`, which is silently always `false` on | |
| // Windows. The plugin's hooks became permanent no-ops there: Lingui macro | |
| // calls (`@lingui/core/macro`) shipped uncompiled to the browser, which | |
| // then failed to hydrate the admin UI entirely (it never got past the | |
| // "Loading EmDash..." screen). These tests reproduce that mismatch | |
| // directly, with a synthetic backslash `adminSourcePath` — independent of | |
| // the host OS running the test — so they fail on the pre-fix | |
| // `id.startsWith(adminSourcePath)` comparison on any platform, not just | |
| // Windows CI. | |
| describe("linguiMacroPlugin Windows path-separator handling", () => { |
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
|
recheck |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
What does this PR do?
Fixes the admin UI failing to load in dev mode on Windows — it gets stuck on "Loading EmDash..." forever, with console errors like
Could not resolve "babel-plugin-macros"and laterprocess is not defined.Root cause:
linguiMacroPlugin(inpackages/core/src/astro/integration/vite-config.ts) is a dev-only Vite plugin that compiles away@lingui/core/macrocalls in admin source files before they reach the browser. It matches files viaid.startsWith(adminSourcePath), whereadminSourcePathcomes straight out ofpath.resolve(). On Windows,resolve()returns backslash-separated paths, but Vite always normalizes theid/importerit passes to plugin hooks to forward slashes — even on Windows. So thestartsWithcheck is silently alwaysfalsethere, the plugin's hooks never fire, and raw@lingui/core/macroimports ship to the browser. Vite's dependency optimizer then tries to pre-bundle that module as a real dependency, which fails at runtime (it's only meant to be consumed by a macro loader).Three fixes, all in the same plugin:
adminSourcePathto forward slashes before comparing against Vite's ids/importers (replaceAll("\\", "/"), notpath.sep-driven, so it's also correct — and testable — when a Windows-style path is passed on a non-Windows host).import()of@babel/coreto afile://URL viapathToFileURL()— Node's ESM loader on Windows rejects a raw drive-letter path for dynamicimport().@lingui/babel-plugin-lingui-macroto an absolute path via admin's ownnode_modules(sameadminRequireused for@babel/core), instead of passing the bare specifier to Babel, which resolves plugin names starting from the transformed file's directory — outside admin's own dependency tree when EmDash is aliased into a demo/template app.Verified manually end to end on Windows 11: seeded and ran both
demos/simpleandtemplates/marketing, confirmed the admin dashboard, welcome modal, and content list all render and hydrate correctly after the fix (previously stuck on the loading spinner in both).Closes #
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
Screenshots / test output
New regression tests in
packages/core/tests/unit/astro/vite-config.test.ts(linguiMacroPlugin Windows path-separator handling) reproduce the mismatch with a synthetic Windows-style path, independent of the host OS running the test — they fail against the pre-fixlinguiMacroPlugin(in fact fail to even import it, since it wasn't exported) and pass after the fix.Note: this test file has 5 pre-existing, unrelated failures on Windows (
createViteConfig admin aliasing/use-sync-external-store shim aliasing—TypeError: File URL path must be absolute, from a syntheticfile:///workspace/emdash-site/URL that isn't valid on Windows without a drive letter). Left untouched per the "no drive-by fixes" policy; happy to open a separate issue/PR for it if useful.