Skip to content

fix: inline packages that consume the Solid runtime in dev - #352

Merged
ryansolid merged 2 commits into
solidjs:nextfrom
brenelz:fix/ssr-inline-solid-consumers
Sep 10, 2026
Merged

fix: inline packages that consume the Solid runtime in dev#352
ryansolid merged 2 commits into
solidjs:nextfrom
brenelz:fix/ssr-inline-solid-consumers

Conversation

@brenelz

@brenelz brenelz commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Follow-up to a810d09 (#350), rebased onto it. That commit fixes the split for
solid-js and @solidjs/web; this closes the remaining half.

The gap

a810d09 reasons:

Framework packages that declare the solid export condition are already
inlined via vitefu below and reach the same copy.

@solidjs/meta declares no solid export condition:

@solidjs/router  exports contains "solid": true   -> inlined by vitefu
@solidjs/meta    exports contains "solid": false  -> still externalized
solid-js         false  -> now inlined by a810d09
@solidjs/web     false  -> now inlined by a810d09

So it stays external, Node resolves its own import "solid-js" without the
development condition, and it loads the production server build while the
inlined graph holds the dev one — the same two sharedConfigs, one layer out.
On 3.0.0-next.40 an app that renders a <Title> still dies:

at getContext (solid-js/dist/server.js:104)      <- production build
at useContext (solid-js/dist/server.js:1476)
at headTag    (@solidjs/meta/dist/index.js:36)
at Title      (@solidjs/meta/dist/index.js:79)

It generalises past first-party packages: any component library or helper that
imports solid-js without advertising a solid condition splits the runtime
the same way.

The change

vitefu's semi-framework class is the right bucket — ssr.noExternal without
optimizeDeps.exclude, since these carry no raw Solid components:

isSemiFrameworkPkgByJson(pkgJson) {
  if (!replaceDev) return false;
  return SOLID_RUNTIME_PKGS.some(
    (name) => pkgJson.dependencies?.[name] || pkgJson.peerDependencies?.[name],
  );
},

Gated on replaceDev, so builds are unchanged. It composes with a810d09 rather
than replacing it: that commit still covers the two core packages directly,
including the case where a consumer is absent.

Verified

A start-mode app (start + ssr: true, file routes, server functions,
@solidjs/meta) on solid-js / @solidjs/web 2.0.0-rc.7, @solidjs/router
2.0.0-next.21, Vite 8.2.2:

plugin vite dev
3.0.0-next.39 500 — lazy() … no asset manifest is set
3.0.0-next.40 (a810d09) 500 — useContext in @solidjs/meta, prod build
next.40 + noExternal: ['@solidjs/meta'] by hand 200
this branch 200

vite build + vite preview stayed 200 throughout — a build applies no
development condition, so nothing splits there. Tested with this branch's
built dist/esm/index.mjs dropped into that app with no ssr.noExternal in its
own config. pnpm build (rollup + tsc --emitDeclarationOnly) is clean.

No regression test added — the suites run examples end to end and I could not
tell where a two-instance assertion belongs. examples/ssr and
examples/css-matrix would only catch this with a @solidjs/meta dependency in
the fixture; happy to add that if it is the right shape.

@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4453614

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@solidjs/vite-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@solidjs/vite-plugin@352

commit: 4453614

@brenelz
brenelz force-pushed the fix/ssr-inline-solid-consumers branch from bdf2b24 to 02207d9 Compare September 8, 2026 18:11
@brenelz brenelz changed the title fix: inline solid-js consumers in the SSR dev graph fix: inline packages that consume the Solid runtime in dev Sep 8, 2026
brenelz and others added 2 commits September 9, 2026 17:08
Follow-up to a810d09. Inlining solid-js and @solidjs/web fixes every
resolution those two perform, and vitefu inlines packages advertising a
`solid` export condition. A package that does neither is still external,
so Node resolves its own `import "solid-js"` without `development` and
loads the production server build while the inlined graph holds the dev
one — the same two-instance split, one layer out.

@solidjs/meta is the first-party case: no `solid` export condition, so an
app rendering <Title> still fails in useContext under solid-js
2.0.0-rc.7 with the core packages already inlined.

Classify any package declaring solid-js or @solidjs/web in dependencies
or peerDependencies as a semi-framework package: ssr.noExternal without
optimizeDeps.exclude, which is right here as they carry no raw Solid
components. Gated on replaceDev, so builds are unchanged.
Two guards on the semi-framework rule from the previous commit, plus its
test-mode gate:

- Tooling skip-list (`isFrameworkPkgByName`): `@solidjs/vite-plugin`,
  `vite`, `vitest`, `eslint-plugin-*`, `vite-plugin-*`,
  `prettier-plugin-*`, `@types/*`. These declare solid-js as a peer but
  never run inside the SSR module runner; classifying the plugin itself as
  semi-framework made vitefu crawl ITS dependencies and deep-include
  `@solidjs/vite-plugin > @babel/core` / `> @solidjs/babel-plugin` in the
  CLIENT optimizeDeps (node_modules/.vite/deps 1.5 MB -> 8.3 MB on the
  fullstack template). Mirrors vite-plugin-svelte's
  isCommonDepWithoutSvelteField list.

- Never re-externalize an inlined core: vitefu pushes the non-framework
  `dependencies` of every framework package to `ssr.external` in dev, and
  Vite checks `external` before `noExternal`. @tanstack/solid-router lists
  `@solidjs/web` under `dependencies`, so a810d09's noExternal for it was
  defeated and the tanstack template 500'd with "lazy() called but no
  asset manifest is set". Filter `ssr.external` against the final
  noExternal list in configEnvironment.

- Gate isSemiFrameworkPkgByJson on !isTestMode as well, matching the core
  inlining in configEnvironment (vitest manages inlining via
  test.server.deps).

Verified on the fullstack (@solidjs/meta) and fullstack-tanstack templates:
both 200 under `vite dev` with a single solid-js/@solidjs/web instance, and
the client optimizer output back to its next.40 size.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid
ryansolid force-pushed the fix/ssr-inline-solid-consumers branch from 02207d9 to 4453614 Compare September 10, 2026 00:14
@ryansolid

Copy link
Copy Markdown
Member

Rebased onto next (now includes #354) and pushed one additional commit on top of yours (your commit and authorship untouched): fix: keep the runtime-consumer inlining narrow (4453614).

Verified on the fullstack (@solidjs/meta) and fullstack-tanstack templates that this PR is required — on next.40 both 500 in vite dev (NoOwnerError from the prod solid-js/dist/server.js loaded by the externalized @solidjs/meta; and lazy() called but no asset manifest is set on tanstack) and your semi-framework rule fixes both. Two things the follow-up commit adds, found while probing:

  1. Tooling skip-list (isFrameworkPkgByName): @solidjs/vite-plugin, vite, vitest, eslint-plugin-*, vite-plugin-*, prettier-plugin-*, @types/*. The plugin itself peers on solid-js, so the rule as-is classified it as semi-framework and vitefu then crawled its dependencies into the client optimizeDeps.include (@solidjs/vite-plugin > @babel/core, > @solidjs/babel-plugin) — node_modules/.vite/deps went 1.5 MB → 8.3 MB (fullstack) / 2.5 → 9.2 MB (tanstack). Back to baseline with the skip-list. Mirrors vite-plugin-svelte's isCommonDepWithoutSvelteField.
  2. Never re-externalize an inlined core (configEnvironment): vitefu pushes the non-framework dependencies of framework packages to ssr.external, and Vite checks external before noExternal. @tanstack/solid-router lists @solidjs/web under dependencies, which is what defeated a810d09's noExternal on the tanstack template; your change happens to cover @solidjs/web (it's now semi-framework) but solid-js itself isn't, so a framework package listing solid-js as a dependency would still split the runtime. ssr.external is now filtered against the final noExternal list.
  3. isSemiFrameworkPkgByJson also gated on !isTestMode, matching the core inlining in configEnvironment.

Changeset extended to mention both guards. Both templates 200 with a single solid-js/@solidjs/web instance in the SSR runner; full local gate green (ssr 12/12+8/8, css-matrix 87/87+19/19, start-ssr 508/508+10/10+9/9, start-client 45/45, start-env 47/47, vite-8 vitest + cypress). Merging once CI is green so it ships in next.41 together with #354.

@ryansolid
ryansolid merged commit f89a646 into solidjs:next Sep 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants