diff --git a/.changeset/ssr-inline-solid-consumers.md b/.changeset/ssr-inline-solid-consumers.md
new file mode 100644
index 0000000..5e3b002
--- /dev/null
+++ b/.changeset/ssr-inline-solid-consumers.md
@@ -0,0 +1,5 @@
+---
+'@solidjs/vite-plugin': patch
+---
+
+Packages that consume the Solid runtime without declaring a `solid` export condition are now inlined in dev server environments too, closing the remaining half of the two-instance split. Inlining `solid-js` and `@solidjs/web` fixes every resolution those two perform, and vitefu inlines packages that advertise a `solid` export condition — but a package that does neither is still externalized, and Node resolves its own `import "solid-js"` without the `development` condition, so it loads the production server build while the inlined graph holds the dev one. `@solidjs/meta` is the first-party example: it has no `solid` condition, so under solid-js 2.0.0-rc.7 an app rendering a `
` still died in `useContext` on a second `sharedConfig` even with the core packages inlined. The crawl now also classifies any package declaring `solid-js` or `@solidjs/web` in its `dependencies` or `peerDependencies` as a semi-framework package — `ssr.noExternal` without `optimizeDeps.exclude`, since these hold no raw Solid components — so third-party component libraries and metadata helpers reach the same copy as everything else. Gated on the dev-condition swap (and off under vitest, which manages inlining itself), leaving builds unchanged. Two guards keep the rule narrow: tooling that declares `solid-js` as a peer but never runs inside the SSR module runner — `@solidjs/vite-plugin` itself, `vite`, `vitest`, `eslint-plugin-*`, `vite-plugin-*`, `prettier-plugin-*`, `@types/*` — is skipped entirely (classifying the plugin would also crawl its dependencies and pre-bundle `@babel/core` and `@solidjs/babel-plugin` into the browser's `optimizeDeps`, several megabytes of dead weight per cold start); and the `ssr.external` list vitefu derives from framework packages' non-framework `dependencies` is filtered against the final `noExternal` list, because Vite gives `external` precedence — a framework package listing `@solidjs/web` under `dependencies` (e.g. `@tanstack/solid-router`) would otherwise re-externalize a core the plugin just inlined and split the runtime again.
diff --git a/src/index.ts b/src/index.ts
index 37eae3d..5e8933f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -442,6 +442,32 @@ function getExtension(filename: string): string {
const index = filename.lastIndexOf('.');
return index < 0 ? '' : filename.substring(index).replace(/\?.+$/, '');
}
+// The packages whose dev/production server builds are selected by the
+// `development` export condition. A dependency on either means the package
+// consumes the runtime and must resolve it through Vite in dev.
+const SOLID_RUNTIME_PKGS = ['solid-js', '@solidjs/web'];
+
+// Tooling that declares solid-js as a peer but never runs inside the SSR
+// module runner. Kept out of the crawl entirely: classifying them as
+// semi-framework would also crawl THEIR dependencies, which vitefu deep-
+// includes in the client optimizer (`@solidjs/vite-plugin > @babel/core`
+// pre-bundled for the browser — ~2.6 MB of dead weight per cold start).
+// Mirrors vite-plugin-svelte's isCommonDepWithoutSvelteField list.
+const NON_RUNTIME_SOLID_PKGS = ['@solidjs/vite-plugin', 'vite', 'vitest', 'eslint-plugin-solid'];
+const NON_RUNTIME_SOLID_PREFIXES = [
+ 'vite-plugin-',
+ 'eslint-plugin-',
+ 'prettier-plugin-',
+ '@types/',
+];
+function isNonRuntimeSolidPkg(name: string): boolean {
+ const bare = name.slice(name.lastIndexOf('/') + 1);
+ return (
+ NON_RUNTIME_SOLID_PKGS.includes(name) ||
+ NON_RUNTIME_SOLID_PREFIXES.some((p) => (p.startsWith('@') ? name : bare).startsWith(p))
+ );
+}
+
function containsSolidField(fields: Record) {
const keys = Object.keys(fields);
for (let i = 0; i < keys.length; i++) {
@@ -1034,6 +1060,30 @@ export default function solidPlugin(options: Partial = {}): Plugin[] {
isFrameworkPkgByJson(pkgJson) {
return containsSolidField(pkgJson.exports || {});
},
+ // `false` = neither framework nor semi-framework, and don't crawl
+ // its deps; `undefined` = unknown, fall through to the json checks.
+ isFrameworkPkgByName(name) {
+ return isNonRuntimeSolidPkg(name) ? false : undefined;
+ },
+ // Under `vite dev` the runtime must not be split in two. Inlined
+ // modules resolve `solid-js` through Vite with `development` (its dev
+ // server build); an externalized package's own imports are resolved by
+ // Node, which has no `development` condition, so it loads the
+ // production build instead. Both then run, each with its own
+ // `sharedConfig` — the manifest `renderToStream` sets lands on one and
+ // `lazy()` reads the other. `resolve.externalConditions` below only
+ // fixes the external's own entry, not what it imports, so every
+ // package that consumes the runtime has to go through Vite as well.
+ // Semi-framework is the right class: `ssr.noExternal` without
+ // `optimizeDeps.exclude`, since these hold no raw Solid components.
+ isSemiFrameworkPkgByJson(pkgJson) {
+ // Same gate as the core inlining in configEnvironment: dev serve
+ // only, never vitest (it manages inlining via test.server.deps).
+ if (!replaceDev || isTestMode) return false;
+ return SOLID_RUNTIME_PKGS.some(
+ (name) => pkgJson.dependencies?.[name] || pkgJson.peerDependencies?.[name],
+ );
+ },
});
// fix for bundling dev in production
@@ -1220,13 +1270,21 @@ export default function solidPlugin(options: Partial = {}): Plugin[] {
// Only set resolve.external if noExternal is not true (to avoid conflicts with plugins like Cloudflare)
if (name === 'ssr' && solidPkgsConfig) {
if (config.resolve.noExternal !== true) {
- config.resolve.noExternal = [
+ const noExternal = [
...(Array.isArray(config.resolve.noExternal) ? config.resolve.noExternal : []),
...solidPkgsConfig.ssr.noExternal,
];
+ config.resolve.noExternal = noExternal;
+ // vitefu externalizes the non-framework deps of every framework
+ // package in dev, and Vite gives `external` precedence over
+ // `noExternal`. A framework package that lists solid-js or
+ // @solidjs/web under `dependencies` (not peer — e.g.
+ // @tanstack/solid-router 2.0.0-rc.7 → @solidjs/web) would therefore
+ // re-externalize a core inlined above and split the runtime again.
+ // Nothing inlined may appear in `external`.
config.resolve.external = [
...(Array.isArray(config.resolve.external) ? config.resolve.external : []),
- ...solidPkgsConfig.ssr.external,
+ ...solidPkgsConfig.ssr.external.filter((dep) => !noExternal.includes(dep)),
];
}
}