Skip to content

chore: Node >=22.12 floor to match the rc runtime; dedupe @solidjs/signals - #377

Merged
ryansolid merged 1 commit into
nextfrom
node-floor-signals-dedupe
Sep 27, 2026
Merged

ryansolid merged 1 commit into
nextfrom
node-floor-signals-dedupe

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Two maintainer-approved housekeeping changes for the 2.0 line. No plugin option is added, removed, or renamed.

1. Node engines floor: >=22.12.0

What. package.json engines.node moves from ^20.19.0 || >=22.12.0 to >=22.12.0; the README requirement line (## Requirements) says the same and notes it is the runtime's floor.

Why. Since Solid 2.0.0-rc.8 the runtime packages are ESM-only with engines.node: ">=22.12.0" — verified on solid origin/next in packages/solid/package.json, packages/web/package.json, packages/signals/package.json. With the peer range this plugin declares (solid-js ^2.0.0-rc.10), the Node 20 lane was already unusable; the manifest now says so.

Where. package.json:7, README.md:31-32.

Swept. rg '20\.19|Node 20' outside node_modules/lockfile: only the two sites above. No .npmrc (so no engine-strict). No examples/*/package.json declares engines.

CI matrix — before / after

Unchanged. Every workflow already runs Node 24 and nothing runs Node 20:

workflow job Node before Node after
cr.yml Continuous Releases 24 24
e2e.yml E2E tests "24" "24"
release.yml Test, Release 24.x 24.x
vitest.yml Vitest (vite-8) '24' '24'

2. @solidjs/signals in resolve.dedupe (gated on a root copy)

What. Under serve, @solidjs/signals joins solid-js and @solidjs/web in resolve.dedupe when the app root can reach a copy (node_modules/@solidjs/signals, walking up — an npm-style hoist or a direct dependency). It does not join optimizeDeps.include.

Why. @solidjs/diagnostics/browser (packages/diagnostics/src/browser.ts) and solid-js/attribution (export * from "@solidjs/signals/attribution") import the signals core directly, so a nested/duplicated install yields a second engine beside the one solid-js loads — the same two-engines symptom #374 addressed for the pre-bundle path.

Where. src/index.ts:1290-1313 (the gated dedupe list, using the existing findPackageDir root walk-up), src/index.ts:1388 (resolve.dedupe).

⚠️ Deviation from the brief: why the entry is gated, not unconditional

The brief asked for an unconditional entry. That is a regression for strict-pnpm apps, reproduced before shipping:

  • resolve.dedupe resolves the listed package from the root. Vite 8's native rolldown resolver (vite_resolve_plugin.rs) falls back to the importer when that misses — which is why the example suites passed with the unconditional entry. But the JS tryNodeResolve still used by the SSR externalize decision (createIsConfiguredAsExternal) and by fetchModule (module-runner resolution of externalized bare imports) has no fallback.
  • Under pnpm's isolated layout, @solidjs/signals exists only as solid-js's transitive dependency (this repo's own examples included). vitefu externalizes the non-Solid dependencies of semi-framework packages, and @solidjs/diagnostics lists @solidjs/signals under dependencies → signals lands in ssr.external → the bare import from the inlined solid-js/dist/server.dev.js reaches fetchModule → root-only resolve misses → ERR_MODULE_NOT_FOUND: Cannot find module '@solidjs/signals'.
  • Reproduction (strict pnpm, solid-js + @solidjs/web + @solidjs/diagnostics, plugin linked, createServerModuleRunner(ssr).import(entry)): baseline ok; unconditional dedupe throws as above; dedupe with a root copy ok (and the resolver check shows solid-js's import landing on the root copy — dedupe honored).
  • With no root copy there is nothing to dedupe to, so the gate loses nothing; where a root copy exists, the intended effect is delivered.

Why not optimizeDeps.include. nestedDeps also feeds optimizeDeps.include. The optimizer already reaches signals through solid-js, and an include entry that doesn't resolve from the root logs Failed to resolve dependency: @solidjs/signals, present in client 'optimizeDeps.include' on every dev start (observed in this repo's own examples). So signals goes to dedupe only, per the brief's fallback instruction.

Other consumers checked. ssr.noExternal and the vitefu crawl are not fed by nestedDeps (they use SOLID_RUNTIME_PKGS / the crawl's own classification); untouched. pnpm why @solidjs/signals: present in the plugin's install as a transitive dependency of solid-js (and a direct dependency of @solidjs/diagnostics), never hoisted to the root.

Tests

  • New examples/start-ssr/test/dedupe.mjs (pure resolveConfig, wired into the example's test script): in the example's own pnpm layout signals is absent from resolve.dedupe and optimizeDeps.include; in a temp root with a stub node_modules/@solidjs/signals it is present in dedupe and still absent from include; under build the list is empty. 8/8.
  • pnpm build ✓, pnpm check ✓ (100/100), root pnpm test (Cypress vite-8) ✓ 1/1.
  • examples/start-ssr full suite ✓ (607/607 run, 10/10 http-bridge, 11/11 components-warning, 12/12 webworker-warning, 8/8 dedupe); examples/start-client ✓ 65/65; examples/start-env ✓ 47/47.

Public API changes

  • Published manifest: engines.node changes from ^20.19.0 || >=22.12.0 to >=22.12.0. Installing the plugin under Node 20 now fails engines checks (a hard failure only with engine-strict; otherwise a warning). This is the one user-visible effect of the PR.
  • No plugin options added, removed, or renamed. No new exports or diagnostics. The resolve.dedupe addition is a config-shape change visible to apps only via their resolved Vite config.

Co-authored-by: Claude via Cursor

…gnals

`engines.node` moves from `^20.19.0 || >=22.12.0` to `>=22.12.0`. Since
Solid 2.0.0-rc.8 the runtime packages (`solid-js`, `@solidjs/web`,
`@solidjs/signals`) are ESM-only with that same floor, so the plugin's
Node 20 lane was already unusable with the peer range it declares. The
README requirement line follows. CI already runs Node 24 in every
workflow (cr, e2e, release, vitest); no matrix change. No `.npmrc`, and
no example declares its own `engines`.

Under `serve`, `@solidjs/signals` joins `solid-js` and `@solidjs/web` in
`resolve.dedupe` — gated on the app root reaching a copy of it
(`node_modules/@solidjs/signals`, walking up: an npm-style hoist or a
direct dependency). `@solidjs/diagnostics/browser` and
`solid-js/attribution` import the signals core directly, so a
nested/duplicated install yields a second engine beside the one
`solid-js` loads — the same two-engines symptom #374 addressed for the
pre-bundle path with `optimizeDeps.include`.

Why the gate: `resolve.dedupe` resolves the listed package from the root.
Vite 8's native (rolldown) resolver falls back to the importer when that
misses, but the JS `tryNodeResolve` used by the SSR externalize decision
and by `fetchModule` (the module runner's resolution of externalized bare
imports) does not. Under pnpm's isolated layout signals exists only as
`solid-js`'s transitive dependency, so an unconditional entry turned
`import "@solidjs/signals"` from the inlined `solid-js/dist/server.dev.js`
into `ERR_MODULE_NOT_FOUND` as soon as anything externalized it — and
vitefu does exactly that once a semi-framework package such as
`@solidjs/diagnostics` lists signals under `dependencies`. Reproduced in
a strict-pnpm app (solid-js + @solidjs/web + @solidjs/diagnostics) through
the SSR module runner: baseline OK, unconditional dedupe throws, dedupe
with a root copy OK. With no root copy there is nothing to dedupe to, so
the gate loses nothing. Signals stays out of `optimizeDeps.include`: the
optimizer already reaches it through `solid-js`, and an include entry
that doesn't resolve from the root logs `Failed to resolve dependency`
on every start (observed in this repo's own examples).

Tests: start-ssr gains test/dedupe.mjs (pure resolveConfig): in the
example's own pnpm layout signals is absent from `resolve.dedupe` and
`optimizeDeps.include`; in a temp root with a stub
`node_modules/@solidjs/signals` it is present in `dedupe` and still
absent from `include`; under `build` the list is empty.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
@changeset-bot

changeset-bot Bot commented Sep 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: da7c347

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 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: da7c347

@ryansolid
ryansolid merged commit 14e8b8b into next Sep 27, 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.

1 participant