From c750c347a9ce59274e88ec844c91af5c30655c21 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Mon, 19 Oct 2026 11:13:05 +0800 Subject: [PATCH] fix: inline framework packages declared at the workspace root - vitefu only crawled the package.json at the Vite root. - In a workspace, a project can import packages that only the workspace root declares. - Those packages were left external, so Vitest loaded their .jsx files with Node and failed with `Unknown file extension ".jsx"`. - The plugin now also crawls the workspace root and adds its framework packages to `ssr.noExternal`. Fixes #157 Co-Authored-By: Claude Opus 5.5 (1M context) --- .changeset/workspace-root-solid-pkgs.md | 5 ++ examples/vite-6/package.json | 2 +- examples/vite-6/tests/workspace-root.mjs | 61 ++++++++++++++++++++++++ src/index.ts | 30 +++++++++++- 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 .changeset/workspace-root-solid-pkgs.md create mode 100644 examples/vite-6/tests/workspace-root.mjs diff --git a/.changeset/workspace-root-solid-pkgs.md b/.changeset/workspace-root-solid-pkgs.md new file mode 100644 index 00000000..8801b78d --- /dev/null +++ b/.changeset/workspace-root-solid-pkgs.md @@ -0,0 +1,5 @@ +--- +'vite-plugin-solid': patch +--- + +Packages with a `solid` export condition that are declared only in the workspace root's `package.json` are now inlined for projects in that workspace. Before, Vitest loaded them with Node and failed with `Unknown file extension ".jsx"` (#157). diff --git a/examples/vite-6/package.json b/examples/vite-6/package.json index a233ac35..94a75384 100644 --- a/examples/vite-6/package.json +++ b/examples/vite-6/package.json @@ -6,7 +6,7 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "test": "vitest --browser.headless --run", + "test": "vitest --browser.headless --run && node tests/workspace-root.mjs", "test-dev": "vitest" }, "devDependencies": { diff --git a/examples/vite-6/tests/workspace-root.mjs b/examples/vite-6/tests/workspace-root.mjs new file mode 100644 index 00000000..93ac6a10 --- /dev/null +++ b/examples/vite-6/tests/workspace-root.mjs @@ -0,0 +1,61 @@ +// A package with a `solid` export condition that only the workspace root's +// package.json declares must still be inlined for a project in that +// workspace (#157). The crawl used to read only the project's own +// package.json, so Vitest left the package external and Node failed on its +// `.jsx` files with `Unknown file extension ".jsx"`. +// +// The fixture is a workspace in a temp directory. The root declares +// `fake-solid-lib`, and the project `packages/app` declares nothing. +import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { resolveConfig } from 'vite'; +import solid from 'vite-plugin-solid'; + +const workspaceDir = path.join(os.tmpdir(), `vite-plugin-solid-workspace-${process.pid}`); +const appDir = path.join(workspaceDir, 'packages', 'app'); +const libDir = path.join(workspaceDir, 'node_modules', 'fake-solid-lib'); + +let noExternal; +try { + rmSync(workspaceDir, { recursive: true, force: true }); + mkdirSync(appDir, { recursive: true }); + mkdirSync(libDir, { recursive: true }); + writeFileSync( + path.join(workspaceDir, 'package.json'), + JSON.stringify({ + name: 'workspace', + private: true, + workspaces: ['packages/*'], + dependencies: { 'fake-solid-lib': '1.0.0' }, + }), + ); + writeFileSync(path.join(appDir, 'package.json'), '{ "name": "app", "private": true }\n'); + writeFileSync( + path.join(libDir, 'package.json'), + JSON.stringify({ + name: 'fake-solid-lib', + version: '1.0.0', + type: 'module', + exports: { '.': { solid: './index.jsx', default: './index.js' } }, + }), + ); + writeFileSync(path.join(libDir, 'index.jsx'), 'export const Lib = () =>

lib

;\n'); + writeFileSync(path.join(libDir, 'index.js'), 'export const Lib = () => null;\n'); + + const resolved = await resolveConfig( + { root: appDir, mode: 'test', configFile: false, logLevel: 'silent', plugins: [solid()] }, + 'serve', + ); + noExternal = resolved.environments.ssr.resolve.noExternal; +} finally { + rmSync(workspaceDir, { recursive: true, force: true }); +} + +if (!Array.isArray(noExternal) || !noExternal.includes('fake-solid-lib')) { + console.error( + `FAIL: fake-solid-lib is not in ssr.resolve.noExternal: ${JSON.stringify(noExternal)}`, + ); + process.exit(1); +} +console.log('PASS: framework packages declared only in the workspace root are inlined'); diff --git a/src/index.ts b/src/index.ts index 82770246..84bb924e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,9 +3,10 @@ import solid from 'babel-preset-solid'; import { readFileSync } from 'fs'; import { mergeAndConcat } from 'merge-anything'; import { createRequire } from 'module'; +import path from 'path'; import solidRefresh from 'solid-refresh/babel'; import type { Alias, AliasOptions, FilterPattern, Plugin } from 'vite'; -import { createFilter, version } from 'vite'; +import { createFilter, searchForWorkspaceRoot, version } from 'vite'; import { crawlFrameworkPkgs } from 'vitefu'; const require = createRequire(import.meta.url); @@ -237,6 +238,33 @@ export default function solidPlugin(options: Partial = {}): Plugin { }, }); + // vitefu only reads the dependencies of the package.json at the Vite + // root. In a workspace, the project can also import packages that are + // only declared in the workspace root's package.json (npm and yarn + // hoist them). A framework package found that way was left external, + // so Node loaded its raw `.jsx` source and failed with + // `Unknown file extension ".jsx"` (#157). Crawl the workspace root too + // and inline its framework packages. Only `ssr.noExternal` is merged: + // inlining a name that is never imported does nothing, while + // `optimizeDeps.include` entries must resolve from the project root. + const crawlRoot = path.resolve(projectRoot || process.cwd()); + const workspaceRoot = searchForWorkspaceRoot(crawlRoot); + if (workspaceRoot !== crawlRoot) { + const workspacePkgs = await crawlFrameworkPkgs({ + viteUserConfig: userConfig, + root: workspaceRoot, + isBuild: command === 'build', + isFrameworkPkgByJson(pkgJson) { + return containsSolidField(pkgJson.exports || {}); + }, + }); + for (const name of workspacePkgs.ssr.noExternal) { + if (!solidPkgsConfig.ssr.noExternal.includes(name)) { + solidPkgsConfig.ssr.noExternal.push(name); + } + } + } + // fix for bundling dev in production const nestedDeps = replaceDev ? ['solid-js', 'solid-js/web', 'solid-js/store', 'solid-js/html', 'solid-js/h']