Skip to content

fix(vite): resolve cross-package bare imports from out-of-workspace modules - #154

Merged
antosubash merged 3 commits into
mainfrom
claude/fix-issue-152-xpaR9
May 15, 2026
Merged

fix(vite): resolve cross-package bare imports from out-of-workspace modules#154
antosubash merged 3 commits into
mainfrom
claude/fix-issue-152-xpaR9

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Closes #152.

Summary

When a module's pages live outside the workspace root — under .venv/.../site-packages/<pkg>/pages/ for wheel installs — Vite's resolver walks up from the importer looking for node_modules and never reaches <repo>/node_modules where npm hoists deps. Bare imports like maplibre-gl, pmtiles, or even host-provided peers (@inertiajs/react, @simple-module-py/ui) fail with:

Failed to resolve import "maplibre-gl" from ".../pages/Map.tsx". Does the file exist?

Fix

Adds a pre resolveId Vite plugin that:

  1. Detects bare imports coming from any registered module-pages directory.
  2. Re-resolves them as if the importer lived at the workspace root, putting <repo>/node_modules back on the resolver's path.
  3. Uses skipSelf: true to avoid an infinite recursion.

Combined with pre-bundling module-declared dependencies + peerDependencies via optimizeDeps.include, dev server, scan-imports, and production build all converge on the host's hoisted copy.

Also accepts the module's package.json in either of the two locations the framework supports (next to the Python package for wheel installs, one level up for editable/workspace installs), matching read_module_package_json in simple_module_hosting.manifest.

Test plan

  • make test-py — 215 passed
  • npx biome check host/client_app/ — clean
  • npx tsc --noEmit -p host/client_app/tsconfig.json — clean
  • Live repro: copied modules/dashboard to /tmp/fake-wheel-dashboard, pointed modules.manifest.json at it (simulating a wheel-installed module outside the workspace), and added nanoid (not in host's package.json) to its deps + Home.tsx
    • Without the fix: vite dev returns Failed to resolve import "@inertiajs/react" / "@simple-module-py/i18n" / "nanoid" for the out-of-workspace page.
    • With the fix: all bare imports resolve — nanoid/@inertiajs/react/lucide-react go through the pre-bundle cache, workspace packages (@simple-module-py/i18n, @simple-module-py/ui) resolve via @fs/ paths to their workspace sources.
    • vite build also succeeds end-to-end (✓ built in 600ms).
  • New unit tests in framework/hosting/tests/test_manifest.py exercise read_module_package_json for both layouts (wheel + source-tree) and collect_module_js_deps aggregation, locking in the contract the vite.config.ts walks rely on.

Files

  • host/client_app/vite.config.ts — Add moduleBareImportResolver plugin, walk module package.json (wheel + source layouts) for dependencies + peerDependencies.
  • framework/cli/simple_module_cli/templates/host/client_app/vite.config.ts — Same fix for newly scaffolded projects (smpy new / create-host).
  • framework/hosting/tests/test_manifest.py — New tests for read_module_package_json / collect_module_js_deps.

Generated by Claude Code

claude added 2 commits May 15, 2026 11:38
…odules (#152)

When a module's pages live under .venv/.../site-packages/ (wheel install)
or otherwise sit outside the workspace root, Vite's resolver walks up from
the importer looking for node_modules and never reaches <repo>/node_modules
where npm hoists deps. Bare imports like `maplibre-gl`, `pmtiles`, or even
host-provided peers (`@inertiajs/react`, `@simple-module-py/ui`) fail with
"Failed to resolve import … Does the file exist?".

The fix adds a `pre` resolveId Vite plugin that catches bare imports from
any registered module-pages dir and re-resolves them as if the importer
lived at the workspace root, putting <repo>/node_modules back on the
resolver's path. Combined with pre-bundling module-declared deps and
peer deps via `optimizeDeps.include`, dev server, scan-imports, and
production build all converge on the host's hoisted copy.

Tested by pointing the manifest at a copy of the dashboard module
outside the workspace; without the fix `vite dev`/`vite build` fail with
the issue's error, with it both succeed and pages render normally.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 15, 2026

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8bdf314
Status: ✅  Deploy successful!
Preview URL: https://4e3b2133.simple-module-python.pages.dev
Branch Preview URL: https://claude-fix-issue-152-xpar9.simple-module-python.pages.dev

View logs

Three quality fixes from /simplify review:

- moduleBareImportResolver now guards on pagesDir prefixes (modulePagesPrefixes), not pkgDir — so only files actually under `pages/` trigger the workspace re-resolution, not arbitrary files in the module package.
- Precompute the workspace-prefix string + fakeWorkspaceImporter path once at config load, instead of rebuilding them inside the resolveId hot path on every import.
- Drop three inline comments that narrated the guard logic; the block comment above the function already explains the why.

Test side: extend fake_module_factory to write peerDependencies and assert read_module_package_json surfaces them — the TS-side optimizeDeps walk reads both blocks and was previously uncovered.
@antosubash
antosubash marked this pull request as ready for review May 15, 2026 20:12
@antosubash
antosubash merged commit b3d3365 into main May 15, 2026
12 checks passed
antosubash added a commit that referenced this pull request May 18, 2026
…modules (#156)

The moduleBareImportResolver plugin added in #154 short-circuits when the
importer path sits under fsRoot/projectRoot, which was meant to skip
host-internal files but ends up skipping workspace-member module pages
too. In an npm-workspaces scaffold (`workspaces: ["host/client_app",
"modules/*"]`) deps hoist to the workspace root, so fsRoot resolves to
the workspace root — and workspace-member modules at `modules/<name>/...`
sit *under* it. The early-return excluded the very modules that need
workspace-root re-resolution, so bare imports like `maplibre-gl` /
`pmtiles` failed with "could not be resolved" during dev resolveId.

Drop the fsRoot/projectRoot containment check; the `modulePagesPrefixes`
guard already pins the plugin to module-page importers regardless of
whether they live inside or outside the workspace tree. Apply the fix to
both the scaffold template (Vite 6) and the framework's host config
(Vite 8). Add a content-level regression test ensuring the bad early-
return doesn't reappear.
antosubash added a commit that referenced this pull request May 18, 2026
…modules (#156) (#157)

* test(cli): add failing test for vite scan-imports nodePaths fallback (#152)

* test(cli): split assertion into separate fsRoot/node_modules checks (#152)

* fix(scaffold): seed esbuild nodePaths so vite scan-imports resolves cross-package bare imports from module pages (#152)

* fix(host): seed esbuild nodePaths for module-page scan-imports (#152)

* docs(changelog): note fix for vite scan-imports cross-package resolution (#152)

* fix(host): use rolldownOptions.resolve.modules instead of deprecated esbuildOptions.nodePaths for Vite 8 (#152)

* docs(changelog): correct scope — dep pre-bundling is dev-only, note Vite 6 vs 8 paths (#152)

* fix(vite): cross-package bare imports also fail for workspace-member modules (#156)

The moduleBareImportResolver plugin added in #154 short-circuits when the
importer path sits under fsRoot/projectRoot, which was meant to skip
host-internal files but ends up skipping workspace-member module pages
too. In an npm-workspaces scaffold (`workspaces: ["host/client_app",
"modules/*"]`) deps hoist to the workspace root, so fsRoot resolves to
the workspace root — and workspace-member modules at `modules/<name>/...`
sit *under* it. The early-return excluded the very modules that need
workspace-root re-resolution, so bare imports like `maplibre-gl` /
`pmtiles` failed with "could not be resolved" during dev resolveId.

Drop the fsRoot/projectRoot containment check; the `modulePagesPrefixes`
guard already pins the plugin to module-page importers regardless of
whether they live inside or outside the workspace tree. Apply the fix to
both the scaffold template (Vite 6) and the framework's host config
(Vite 8). Add a content-level regression test ensuring the bad early-
return doesn't reappear.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vite dev-server scan-imports fails to resolve cross-package bare imports from module pages

2 participants