Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/workspace-root-solid-pkgs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solidjs/vite-plugin': 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).
74 changes: 74 additions & 0 deletions examples/start-ssr/test/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6672,6 +6672,80 @@ async function runVitestMode() {
`empty root: ${JSON.stringify(emptySetupFiles)}, example root: ${JSON.stringify(exampleSetupFiles)}`,
);

// A framework package (one 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 outside the repo: the root declares
// `fake-solid-lib`, and the project `packages/app` declares nothing.
const workspaceDir = path.join(os.tmpdir(), `solid-vite-plugin-workspace-${process.pid}`);
const appDir = path.join(workspaceDir, 'packages', 'app');
const libDir = path.join(workspaceDir, 'node_modules', 'fake-solid-lib');
const noExternalProbe = `
import { resolveConfig } from 'vite';
import solid from '@solidjs/vite-plugin';
const resolved = await resolveConfig(
{ root: process.argv[1], mode: 'test', configFile: false, logLevel: 'silent', plugins: [solid()] },
'serve',
);
console.log(JSON.stringify(resolved.environments.ssr.resolve.noExternal));
`;
let workspaceNoExternal;
let workspaceError = '';
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 = () => <p>lib</p>;\n');
writeFileSync(path.join(libDir, 'index.js'), 'export const Lib = () => null;\n');
workspaceNoExternal = JSON.parse(
execFileSync(process.execPath, ['--input-type=module', '-e', noExternalProbe, '--', appDir], {
cwd: exampleDir,
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 60000,
})
.toString()
.trim(),
);
} catch (e) {
workspaceError = String(e.stderr || e);
} finally {
rmSync(workspaceDir, { recursive: true, force: true });
}
const workspacePass =
!workspaceError &&
Array.isArray(workspaceNoExternal) &&
workspaceNoExternal.includes('fake-solid-lib');
record(
mode,
'workspace-root-pkgs',
'framework packages declared only in the workspace root are inlined for a workspace project',
workspacePass,
workspacePass
? undefined
: workspaceError || `noExternal: ${JSON.stringify(workspaceNoExternal)}`,
);

// Vitest passes the `ssr` environment's conditions to Node as
// `--conditions` for the packages it leaves external. A jsdom run used to
// put `browser` there, so Node-only packages failed to load: `msw/node`
Expand Down
31 changes: 31 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
defaultClientConditions,
defaultExternalConditions,
defaultServerConditions,
searchForWorkspaceRoot,
transformWithOxc,
} from 'vite';
import { getEnvironmentConsumer, isRunnableEnvironment } from './environment.js';
Expand Down Expand Up @@ -1543,6 +1544,36 @@ export default function solidPlugin(options: Partial<Options> = {}): 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 || {});
},
isFrameworkPkgByName(name) {
return isNonRuntimeSolidPkg(name) ? false : undefined;
},
});
for (const name of workspacePkgs.ssr.noExternal) {
if (!solidPkgsConfig.ssr.noExternal.includes(name)) {
solidPkgsConfig.ssr.noExternal.push(name);
}
}
}

// A semi-framework package is inlined for ONE reason: so it resolves the
// runtime through Vite and shares the app's copy. vitefu's crawl treats
// it like a framework package for its dependencies too — every CJS
Expand Down
Loading