Skip to content

fix(vite): seed esbuild nodePaths so scan-imports resolves bare imports from module pages - #155

Merged
antosubash merged 8 commits into
mainfrom
githubissues
May 17, 2026
Merged

fix(vite): seed esbuild nodePaths so scan-imports resolves bare imports from module pages#155
antosubash merged 8 commits into
mainfrom
githubissues

Conversation

@antosubash

@antosubash antosubash commented May 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fixes GH issue Vite dev-server scan-imports fails to resolve cross-package bare imports from module pages #152: Vite's dep-scan pass failed to resolve cross-package bare imports like maplibre-gl and pmtiles from TSX pages that live outside the host's client_app/ — wheel-installed modules and workspace modules shipping their own JS deps.
  • The scaffold template (which pins Vite ^6.0.0) seeds optimizeDeps.esbuildOptions.nodePaths — esbuild's documented NODE_PATH-style fallback for its dependency scanner.
  • The framework repo's own host/client_app/vite.config.ts (Vite 8) uses the Rolldown equivalent: optimizeDeps.rolldownOptions.resolve.modules with 'node_modules' plus an absolute fallback to the workspace node_modules/. (Vite 8 deprecated esbuildOptions and only forwards a fixed subset of fields — nodePaths is not among them.)
  • Scope is dev-mode pre-bundling only — since Vite 5.1, optimizeDeps does not run during vite build. A build-time fix is a separate concern and is not in this PR.

Test plan

  • New regression test in framework/cli/tests/test_scaffolding_host.py asserts the scaffold wires esbuildOptions.nodePaths to fsRoot/node_modules.
  • make test-py passes locally (1011 passed).
  • make lint passes locally.
  • npx tsc --noEmit -p host/client_app/tsconfig.json has no new errors in vite.config.ts.
  • make dev boot smoke (manual — human verifier).
  • Manual smoke against smpy_gis confirms the original repro is fixed.

Closes #152.

@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: 769510c
Status: ✅  Deploy successful!
Preview URL: https://6129667f.simple-module-python.pages.dev
Branch Preview URL: https://githubissues.simple-module-python.pages.dev

View logs

@antosubash
antosubash merged commit 395f9d4 into main May 17, 2026
12 checks passed
antosubash added a commit that referenced this pull request Aug 9, 2026
…me imports (#253) (#254)

* fix(hosting): emit absolute paths for module CSS imports (#253)

`gen-pages` emitted `@import "#module/<pkg>/styles.css"` into
`modules.generated.css`. That specifier only resolves if the host's
`vite.config.ts` defines a matching `resolve.alias` — but that file is
scaffold output: written into an app once, then owned and edited there.
It is versioned independently of these Python packages, so a Python-only
bump 0.0.26 -> 0.0.27 broke `vite build` in every app scaffolded earlier,
with `Can't resolve '#module/<pkg>/styles.css'` naming a specifier that
appears nowhere in the app's own sources.

Emit absolute paths instead, exactly as the `@source` lines in the same
file already did. The stated objection to filesystem paths (an ugly
`../../../.venv/lib/python3.12/site-packages/...`) only ever applied to
*relative* ones. `modules.generated.css` is now self-contained and needs
no host cooperation at all.

Verified on the scaffold's own stack (vite 6.4.3 + @tailwindcss/vite
4.3.3) that an absolute `@import` resolves with no alias configured, and
that the alias form fails there with the reporter's exact error; and on
the repo's stack (vite 8) for both `vite build` and the dev server,
including a module directory entirely outside the project root.

The `#module/<pkg>` alias stays in the scaffold template — it costs
nothing and removing it would be a second breaking change to a file apps
own — but nothing generated depends on it any more.

Also:
- biome.json: exclude `modules.generated.css` / `modules.assets.json`,
  matching the existing exclusions for the other two generated files.
  Absolute paths are long, so whether a line exceeds biome's 100-char
  lineWidth depended on where the repo happened to live — `make lint`
  passed or failed by checkout path.
- CHANGELOG: move the #152/#155/#156/#173 entries out of [Unreleased]
  into the releases that actually shipped them (verified with
  `git tag --contains`). Leaving them there is what led #253 to conclude
  the #156 fix was unreleased when it has shipped since v0.0.15.

* feat(hosting): let a module import a sibling's TS by npm package name (#253)

Nothing in Node's own resolution made this work. A wheel-installed module
is not an npm workspace member, so it never lands in node_modules at all;
a workspace member *is* symlinked, but onto the source-tree module root —
one level above the Python package — so subpaths landed somewhere that
does not exist. Either way `@simple-module-py/foo/components/x` failed to
resolve.

`gen-pages` now records each module's `npm_name` in modules.assets.json
and the host aliases it onto the module's **Python package directory**.

That anchor is forced, not chosen. A wheel ships `site-packages/<pkg>/**`
and nothing above it — Hatch force-includes the module-root package.json
*into* the package — so the source-tree module root does not survive
installation, and the package dir is the only anchor both layouts share.
Verified by inspecting a real built wheel. The practical consequence is
that the subpath is relative to the package:

    @simple-module-py/foo/components/Widget      ok in both layouts
    @simple-module-py/foo/foo/components/Widget  workspace-only

The second shape is what npm's own symlink gives you for a workspace
member, which is why hand-rolled aliases against the module root appear
to work in a checkout and break once the module is wheel-installed.

npm_name discovery skips a parent package.json unless that directory also
holds a pyproject.toml, so a wheel-installed module cannot pick up a
stray site-packages/package.json and alias itself onto a stranger.
Sibling module names are also excluded from optimizeDeps: they resolve to
source directories, not to pre-bundlable packages.

Verified end-to-end in a real `smpy new` app on the template's vite 6
stack: a workspace module importing a wheel-installed module by package
name — the exact shape from the issue — builds and pulls the sibling's
component into the chunk. Also verified the new template degrades
gracefully against 0.0.27 packages (no npm_name -> no alias, no crash).

Structural changes to stay under the 300-line cap, per CLAUDE.md's
"split by responsibility":
- host/client_app/module-assets.ts — module discovery lifted out of
  vite.config.ts (which was already at 291 lines), mirroring the existing
  compress-assets.ts split.
- framework/cli/tests/test_module_npm_aliases.py — npm identity tests,
  with the importable-module factory moved to a tests/conftest.py fixture
  so both files share it without a cross-file import.

Tests: a real-build guard proving a cross-module import resolves (checked
that it fails when the alias is removed, so it is not vacuous), plus unit
coverage for both install layouts, the pyproject guard, malformed
package.json, and npm-name uniqueness.

* ci: run the module-asset build guards in a job that can actually run them

test_module_css_build.py self-skips without node_modules, and the
`Python tests` job runs `make install-py` only — so both real-build
guards silently skipped in CI (`test_module_css_build.py ss`). They are
the only tests that prove module CSS and cross-module npm-name imports
resolve through a real Tailwind/Vite build; every unit test around them
stays green while the wiring is broken.

`JS build (Vite)` already installs Python deps, JS deps, and runs
gen-pages + build, so it is the one job with everything they need.
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

1 participant