Skip to content
Open
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 @@
---
'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).
2 changes: 1 addition & 1 deletion examples/vite-6/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
61 changes: 61 additions & 0 deletions examples/vite-6/tests/workspace-root.mjs
Original file line number Diff line number Diff line change
@@ -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 = () => <p>lib</p>;\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');
30 changes: 29 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -237,6 +238,33 @@ 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 || {});
},
});
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']
Expand Down
Loading