v0.0.13 fixed wheel-installed module pages, but the same crash still hits workspace-member module pages because in an npm-workspaces layout fsRoot IS the workspace root, so workspace-member pages sit under fsRoot and the new plugin's early-return excludes them.
Where the bug is
The plugin added in #154 short-circuits before the workspace-root re-resolution:
// host/client_app/vite.config.ts (v0.0.13)
async resolveId(source, importer) {
…
const importerPath = importer.split('?')[0];
if (importerPath.startsWith(fsRootPrefix)) return null; // ← skips workspace modules
if (!modulePagesPrefixes.some((prefix) => importerPath.startsWith(prefix))) {
return null;
}
…
}
For a wheel-installed module the importer is /repo/.venv/.../site-packages/<pkg>/pages/... which is outside fsRoot (= host/client_app or workspace root), so the early-return is skipped and the workspace-root resolution runs — works.
For a workspace-member module the importer is /repo/modules/<name>/<pkg>/pages/.... In npm-workspaces (workspaces: ["host/client_app", "modules/*"]) deps hoist to /repo/node_modules, so fsRoot resolves to /repo and fsRootPrefix = "/repo/". The importer starts with that prefix → plugin returns null → Vite's default resolver walks up from pages/lib/ looking for node_modules, never finds one before hitting /repo/node_modules (which it can't see through the importer-relative walk), and the scan-imports phase fails:
Error: The following dependencies are imported but could not be resolved:
maplibre-gl (imported by /repo/modules/gis/gis/pages/lib/maplibre.ts)
pmtiles (imported by /repo/modules/gis/gis/pages/lib/maplibre.ts)
Minimal repro
In any smpy new-scaffolded app, add a workspace module that imports a cross-package bare specifier:
smpy create-module gis --dest modules/gis
# Add maplibre-gl + pmtiles to modules/gis/package.json
# Add `import maplibregl from 'maplibre-gl'` to modules/gis/gis/pages/Browse.tsx
npm install # hoists to /repo/node_modules
make dev # Vite fails with "could not be resolved"
Proposed fix
Drop the early-return so the plugin fires for any module-pages importer regardless of fsRoot containment, and only skip when the importer is genuinely outside the module-pages set:
const importerPath = importer.split('?')[0];
if (!modulePagesPrefixes.some((prefix) => importerPath.startsWith(prefix))) {
return null;
}
const resolved = await this.resolve(source, fakeWorkspaceImporter, { skipSelf: true });
return resolved ?? null;
The workspace-root re-resolution is correct in both cases — wheel modules need it because their importer path doesn't transit <repo>/node_modules, and workspace members need it because esbuild's relative node_modules walk doesn't reach the hoisted root.
Extra: dep-scan path
The dev-server resolve pass goes through environment.pluginContainer.resolveId(...) which calls the user's Vite plugins, but the dep-scan pass uses esbuild via optimizeDeps.esbuildOptions and does not (I checked by adding a debug console.log to the plugin: never fires for unresolvable bare specifiers during the scan, even though the same plugin is registered in the config root).
Confirming this: with the if (importerPath.startsWith(fsRootPrefix)) return null line dropped locally, the scan still errors with the same message. The Vite plugin is correct for dev resolves; the scan needs a parallel optimizeDeps.esbuildOptions.plugins esbuild plugin that does the same workspace-root re-resolution. (I tried this locally with require.resolve({ paths: [fsRoot] }) returning { path, external: true } from onResolve; the bare imports still ended up in missing[]. Worth investigating whether the scan plugin order or filter shape is wrong, or whether optimizeDeps.include populated transitively from collectOptimizeIncludes is supposed to suppress the missing check for these specifiers — for us it doesn't.)
Environment
v0.0.13 fixed wheel-installed module pages, but the same crash still hits workspace-member module pages because in an npm-workspaces layout
fsRootIS the workspace root, so workspace-member pages sit underfsRootand the new plugin's early-return excludes them.Where the bug is
The plugin added in #154 short-circuits before the workspace-root re-resolution:
For a wheel-installed module the importer is
/repo/.venv/.../site-packages/<pkg>/pages/...which is outsidefsRoot(= host/client_app or workspace root), so the early-return is skipped and the workspace-root resolution runs — works.For a workspace-member module the importer is
/repo/modules/<name>/<pkg>/pages/.... In npm-workspaces (workspaces: ["host/client_app", "modules/*"]) deps hoist to/repo/node_modules, sofsRootresolves to/repoandfsRootPrefix = "/repo/". The importer starts with that prefix → plugin returnsnull→ Vite's default resolver walks up frompages/lib/looking fornode_modules, never finds one before hitting/repo/node_modules(which it can't see through the importer-relative walk), and the scan-imports phase fails:Minimal repro
In any
smpy new-scaffolded app, add a workspace module that imports a cross-package bare specifier:Proposed fix
Drop the early-return so the plugin fires for any module-pages importer regardless of fsRoot containment, and only skip when the importer is genuinely outside the module-pages set:
The workspace-root re-resolution is correct in both cases — wheel modules need it because their importer path doesn't transit
<repo>/node_modules, and workspace members need it because esbuild's relativenode_moduleswalk doesn't reach the hoisted root.Extra: dep-scan path
The dev-server resolve pass goes through
environment.pluginContainer.resolveId(...)which calls the user's Vite plugins, but the dep-scan pass uses esbuild viaoptimizeDeps.esbuildOptionsand does not (I checked by adding a debugconsole.logto the plugin: never fires for unresolvable bare specifiers during the scan, even though the same plugin is registered in the config root).Confirming this: with the
if (importerPath.startsWith(fsRootPrefix)) return nullline dropped locally, the scan still errors with the same message. The Vite plugin is correct for dev resolves; the scan needs a paralleloptimizeDeps.esbuildOptions.pluginsesbuild plugin that does the same workspace-root re-resolution. (I tried this locally withrequire.resolve({ paths: [fsRoot] })returning{ path, external: true }fromonResolve; the bare imports still ended up inmissing[]. Worth investigating whether the scan plugin order or filter shape is wrong, or whetheroptimizeDeps.includepopulated transitively fromcollectOptimizeIncludesis supposed to suppress the missing check for these specifiers — for us it doesn't.)Environment
workspaces: ["host/client_app", "modules/*"]smpy_gisrepo at https://github.com/antosubash/smpy_gis (PR Refactor layouts into composable components with theme support #2)