diff --git a/.changeset/remove-cjs.md b/.changeset/remove-cjs.md new file mode 100644 index 000000000..e217a3fa4 --- /dev/null +++ b/.changeset/remove-cjs.md @@ -0,0 +1,21 @@ +--- +"@solidjs/signals": patch +"solid-js": patch +"@solidjs/web": patch +"@solidjs/universal": patch +"@solidjs/h": patch +"@solidjs/html": patch +--- + +**Breaking:** all runtime packages are ESM only and declare `engines.node >= 22.12`. + +Every `.cjs` artifact, every `require` branch in the exports maps, and the `types-cjs/` declaration mirrors are gone. Node 22.12+ loads ESM through `require()` natively, so a CommonJS host resolves the same files through the same export conditions it always did (`browser`, `node`, `development`, `observe`, …) — there is one module graph per tier rather than two to keep in step. `main` now points at the ESM server entry. + +For consumers: + +- ESM apps, Vite, Vitest, Bun, Deno, workers: no change. +- CommonJS Node apps: require Node 22.12 or later. `require("solid-js")` keeps working. +- TypeScript CommonJS projects: use `module: "NodeNext"` (TS 5.8+), which type-checks `require()` of ESM packages; `module: "Node16"` will report TS1479. +- Jest: needs Node 22.12+ for `require(esm)`; any preset that maps specifiers to `.cjs` paths (as `solid-jest` does for Solid 1.x) has nothing to map to and must be updated. + +`@solidjs/signals` drops its flat `dist/node*.cjs` builds; its ESM entries (`dist/prod/`, `dist/observe/`, `dist/dev.js`) are the only ones. `@solidjs/babel-plugin` and `@solidjs/compiler` (build-time tooling loaded by Babel/Node) are unchanged. diff --git a/.cursor/rules/signals.mdc b/.cursor/rules/signals.mdc index c59cf08fc..58d5662a6 100644 --- a/.cursor/rules/signals.mdc +++ b/.cursor/rules/signals.mdc @@ -22,7 +22,7 @@ alwaysApply: false ## Changing This Package — Checklist 1. **Bundle size is budgeted.** Scenarios and ratcheted caps live in `scripts/size/.size-limit.js` (run `npm run size` from `scripts/size/` after `pnpm build:js`). Every cap bump needs an audit note there. Restructuring golf is measured NO-WIN (brotli compresses repeats free; indirection adds unique tokens — 2026-09-04 notes). The winning move is **relocation into pay-for-use modules** (e.g. the optimistic module) so floor bundles don't pay. -2. **Two gates, three tiers.** Checks (strict reads, invariants, forbidden scopes, console reporting, dev-only error text) go under `__DEV__` — dev builds only. Wiring (attribution hook call sites, `_name` labels, graph edge counters, `emitDiagnostic`) goes under `__OBSERVE__` — dev AND the `dist/observe` tier, which production observability consumers select via the `observe` export condition. `__DEV__` implies `__OBSERVE__` (dev.ts asserts it). Prod folds both — free. The observe tier has its own size scenario; the prod scenarios must not move because of it. Public surface: `OBSERVE` (diagnostics channel + the attribution hook slot and interaction frame, observe-legal) vs `DEV` (devtools hooks, graph getters, console face). The attribution ENGINE is the separate `@solidjs/signals/attribution` entry (`src/attribution.ts`; prod resolves the inert `src/attribution.prod.ts`): nothing in the core may import `core/attribution.ts`, or the observe tier ships the engine to consumers that never enable it. Every build has two entries (`index` + `attribution`) sharing one module instance — the flat builds are code-split for exactly this. See `documentation/plans/observe-tier-plan.md`. +2. **Two gates, three tiers.** Checks (strict reads, invariants, forbidden scopes, console reporting, dev-only error text) go under `__DEV__` — dev builds only. Wiring (attribution hook call sites, `_name` labels, graph edge counters, `emitDiagnostic`) goes under `__OBSERVE__` — dev AND the `dist/observe` tier, which production observability consumers select via the `observe` export condition. `__DEV__` implies `__OBSERVE__` (dev.ts asserts it). Prod folds both — free. The observe tier has its own size scenario; the prod scenarios must not move because of it. Public surface: `OBSERVE` (diagnostics channel + the attribution hook slot and interaction frame, observe-legal) vs `DEV` (devtools hooks, graph getters, console face). The attribution ENGINE is the separate `@solidjs/signals/attribution` entry (`src/attribution.ts`; prod resolves the inert `src/attribution.prod.ts`): nothing in the core may import `core/attribution.ts`, or the observe tier ships the engine to consumers that never enable it. Every build has two entries (`index` + `attribution`) sharing one module instance — the flat dev build is code-split for exactly this. Artifacts are ESM only (`dist/prod/`, `dist/observe/`, `dist/dev*.js`; no `.cjs`, no `require` branch): Node >= 22.12 `require()`s ESM, so a CJS host gets the same files — which is why no shipped module may contain a top-level `await` (`tests/dist-artifacts.test.ts` scans for it and `require`s each tier). See `documentation/plans/observe-tier-plan.md`. 3. **Downstream tests consume built dist.** After changing signals, `pnpm --filter @solidjs/signals build` and `pnpm --filter solid-js build` BEFORE running `packages/solid` or `packages/web` tests — stale dist has caused false regression alarms repeatedly. 4. **Facades mirror types.** Store/signal API type changes must be applied in `packages/solid/src/client/hydration.ts` and `packages/solid/src/server/signals.ts` too. 5. Internal fields are mangled at build (`scripts/mangle-props.mjs`) in the prod and observe trees; keep the `_`-prefix convention. `_name` is the one reserved (unmangled) field — solid-js writes component labels onto signals' owners through it. `check-pure` verifies `/*#__PURE__*/` annotation count. diff --git a/.cursor/rules/web.mdc b/.cursor/rules/web.mdc index 14751a3e3..1450d8831 100644 --- a/.cursor/rules/web.mdc +++ b/.cursor/rules/web.mdc @@ -69,6 +69,6 @@ Browser-safe stubs that log errors if server APIs are accidentally used in brows - `dist/server.js` — production server (Node/Deno/Worker) - `dist/server.dev.js` — development server (`development` export condition, nested under `node`/`worker`/`deno`) -Every dev artifact is `.dev.{js,cjs}` beside its prod `.{js,cjs}` — the same rule for `frames/`, `server-functions/`, and the other packages. Observe artifacts are `.observe.{js,cjs}` and exist only for entries that contain wiring (here: the browser entry); entries without wiring fall through to prod under `observe`. Tier-ness is a build mode, not an entry. Gate a site on `"_SOLID_OBSERVE_"` when production observability needs it (labels, attribution hooks), on `"_SOLID_DEV_"` when only a developer at a console does. See `documentation/plans/observe-tier-plan.md`. +Every dev artifact is `.dev.js` beside its prod `.js` — the same rule for `frames/`, `server-functions/`, and the other packages. All artifacts are ESM only (no `.cjs` twins, no `require` export branch, no `types-cjs/`): the published `engines.node >= 22.12` floor `require()`s ESM natively, so a CJS host resolves the same files through the same conditions. Nothing shipped may contain a top-level `await` (`require(esm)` throws on it; the signals dist test scans for it). Observe artifacts are `.observe.js` and exist only for entries that contain wiring (here: the browser entry); entries without wiring fall through to prod under `observe`. Tier-ness is a build mode, not an entry. Gate a site on `"_SOLID_OBSERVE_"` when production observability needs it (labels, attribution hooks), on `"_SOLID_DEV_"` when only a developer at a console does. See `documentation/plans/observe-tier-plan.md`. Rollup with Babel for JSX transformation, `@rollup/plugin-replace` for dev/prod flags. diff --git a/documentation/plans/observe-tier-plan.md b/documentation/plans/observe-tier-plan.md index 0ee38c715..22b557963 100644 --- a/documentation/plans/observe-tier-plan.md +++ b/documentation/plans/observe-tier-plan.md @@ -38,16 +38,16 @@ byte-identical to today under every bundler. gates: `"_SOLID_OBSERVE_"` is a replaced literal like `"_SOLID_DEV_"`, and packages ship an observe flavor only for entries that contain wiring: - `@solidjs/signals`: `dist/observe/` (per-module tree like `prod/`, - mangled) and `dist/node.observe.cjs`. - - `solid-js`: `dist/solid.observe.{js,cjs}` — the component root + label + mangled). + - `solid-js`: `dist/solid.observe.js` — the component root + label and the flow-control memo names (``, ``, ``'s "condition value"/"condition"/"value", ``'s, `children`); and - `dist/server.observe.{js,cjs}`, which today differs from prod only in + `dist/server.observe.js`, which today differs from prod only in exporting a live `OBSERVE` — it exists so `import { OBSERVE } from "solid-js"` agrees with `@solidjs/signals` when both resolve under `observe` in one process. P1's server labels give it content. - - `@solidjs/web`: `dist/web.observe.{js,cjs}` (3 `withInteraction` sites). - - `@solidjs/universal`: `dist/universal.observe.{js,cjs}` (renderer-effect + - `@solidjs/web`: `dist/web.observe.js` (3 `withInteraction` sites). + - `@solidjs/universal`: `dist/universal.observe.js` (renderer-effect fallback names, "renderer render"). - frames, server-functions, storage, serialization, h, html, element: no wiring → no flavor; under `observe` they fall through to prod (pinned by @@ -76,9 +76,8 @@ format/formatOrigin` — is `@solidjs/signals/attribution` (re-exported as that import it. Prod resolves an inert twin (`src/attribution.prod.ts`) with the same `Attribution` surface, so the import needs no per-tier guard. Every signals build therefore has two entries sharing one module instance: - the trees via `preserveModules`, the flat dev/CJS builds via code - splitting (`.`, `.attribution.`, `-shared.`, - mangled as one domain). `InteractionRef` moved to `attribution-hooks.ts`; + the trees via `preserveModules`, the flat dev build via code splitting + (`dev.js`, `dev.attribution.js`, `dev-shared.js`). `InteractionRef` moved to `attribution-hooks.ts`; the hooks gained `interactionStart(ref)`/`interactionEnd()`. ## PR A (this plan) — flags, split, flavors, caps, engine entry @@ -87,10 +86,9 @@ format/formatOrigin` — is `@solidjs/signals/attribution` (re-exported as Build: `globals.d.ts` declares `__OBSERVE__`. Rollup adds `dist/observe/` (`__DEV__: "false", __OBSERVE__: "true"`, `preserveModules`, no prettier, -mangled) and `dist/node.observe.cjs`; `dev.js`/`node.dev.cjs` get -`__OBSERVE__: "true"`; `prod/`/`node.cjs` get `"false"`. `mangle-props.mjs` -reserves `_name`. `build:clean` covers the new outputs. Exports: `observe` -after `development` on both `import` and `require`. +mangled); `dev.js` gets `__OBSERVE__: "true"`; `prod/` gets `"false"`. +`mangle-props.mjs` reserves `_name`. `build:clean` covers the new outputs. +Exports: `observe` after `development`. `dev.ts`: construct `OBSERVE` under `__OBSERVE__`, `DEV` under `__DEV__`; `emitDiagnostic` (channel) is observe-tier; `reportDiagnostic`, @@ -194,13 +192,22 @@ alongside server-dev-build-plan P1, which supplies the request half of ## Open questions -- **Client CJS.** Every client entry ships a `.cjs` twin (and its dev/observe - siblings) for one consumer: Jest with `solid-jest`. Everything Solid 2 - documents or ships is Vitest/ESM, `require(esm)` is unflagged on 20.19+, - and Svelte 4, Vite 7, Angular and Lit are ESM-only. Dropping client CJS — - or all CJS, with `engines.node >= 20.19` — would delete roughly a third of - the build matrix and the whole dual-types pipeline. Its own PR, after this - lands. +- **CJS.** _Resolved: removed, across the board._ Every entry used to ship a + `.cjs` twin (and its dev/observe siblings) plus a `types-cjs/` mirror, for + one consumer: Jest with `solid-jest`. Everything Solid 2 documents or ships + is Vitest/ESM; Node 20 is EOL (2026-04) and Node 22.12+ `require()`s ESM + natively; the ESM-native runtimes (Bun, Deno, workers, Tauri, SolidTV's + bundled output) never read the CJS files. Runtime packages now publish + `engines.node >= 22.12` and ESM only — no `require` branch in any exports + map, so a CJS host resolves the very same files through the very same + conditions. That deleted roughly a third of the build matrix, the + dual-types pipeline (`sync-dual-types.mjs`), and signals' three flat CJS + builds. Two guards replaced them: the signals dist test `require()`s each + tier in a child Node and checks it is the same instance `import` yields, + and scans every shipped module for a top-level `await` (the one thing that + breaks `require(esm)`). Build-time tooling (`@solidjs/babel-plugin`, + `@solidjs/compiler`'s napi loader) is unchanged — those are loaded by Babel + and Node tooling, not by app code, and are outside the runtime tier story. ## Out of scope here diff --git a/documentation/plans/server-dev-build-plan.md b/documentation/plans/server-dev-build-plan.md index 1c2edcc08..d9fea1b4b 100644 --- a/documentation/plans/server-dev-build-plan.md +++ b/documentation/plans/server-dev-build-plan.md @@ -21,7 +21,7 @@ attribution work has no server foothold. 1. **`solid-js` has no server dev build.** `packages/solid/rollup.config.js` builds `src/server/index.ts` once, with _no_ `_SOLID_DEV_` replace at all; - `dist/server.{js,cjs}` is the only artifact for the `worker`/`deno`/`node` + `dist/server.js` is the only artifact for the `worker`/`deno`/`node` conditions. `packages/solid/src/server/` contains **zero** `"_SOLID_DEV_"` gates. Its 8 `console.warn`/`console.error` sites fire **unconditionally in production**: `[SERVER_WRITE]` (`signals.ts:696`), nested `` in @@ -38,7 +38,7 @@ attribution work has no server foothold. these ever run outside the test suite (which runs from source, where the string literal is truthy). `head.ts` and `cookies.ts` each have one more. 3. **The pattern already exists in the same package.** - `@solidjs/web/server-functions` ships `server.dev.{js,cjs}` via + `@solidjs/web/server-functions` ships `server.dev.js` via `replaceDev(true)` and nests a `development` condition **inside** `worker`/`deno`/`node` in its exports — the correct shape, because at the top level `node` precedes `development` and would win. `./frames` server @@ -71,7 +71,7 @@ attribution work has no server foothold. **`solid-js`** (`packages/solid/rollup.config.js`, `package.json`): -- Add `dist/server.dev.{js,cjs}` from `src/server/index.ts` with +- Add `dist/server.dev.js` from `src/server/index.ts` with `replaceDev(true)`. - Add `replaceDev(false)` to the existing prod server build. Today it has no replace because the source has no gates; the moment P1 adds one, an @@ -83,9 +83,9 @@ attribution work has no server foothold. **`@solidjs/web`** (`packages/web/rollup.config.js`, `package.json`): -- Add `dist/server.dev.{js,cjs}` from `src/index.server.ts` with +- Add `dist/server.dev.js` from `src/index.server.ts` with `replaceDev(true)`; same external list as the prod server build. -- Add `frames/dist/server.dev.{js,cjs}` likewise; nest `development` under +- Add `frames/dist/server.dev.js` likewise; nest `development` under the server conditions of `./frames` and add it to `./frames/server`. - `./storage`, `./serialization`: no gates today; leave until one appears. @@ -142,13 +142,16 @@ false`), `exports-server-conditions.spec.tsx`; solid `prod/` (chunked dir for the mangle pass; a restructure, not a rename). The resolution test now pins the client pairing under `browser` too. - `@solidjs/signals`' `require` branch had no `development` condition - (`dist/node.cjs` is `__DEV__: false` only), so CJS hosts loading + (`dist/node.cjs` was `__DEV__: false` only), so CJS hosts loading `server.dev.cjs` got signals' prod object and `DEV` came back `undefined` — a dev artifact lying about the one export P1 will emit through. Fixed in - the same PR: `dist/node.dev.cjs` (unmangled twin of `dev.js`), selected by - `require.development`. The resolution test walks the CJS hops - (`@solidjs/web` → `solid-js` → `@solidjs/signals`) and pins all three - flipping together; a signals dist test pins `DEV` per CJS artifact. + the same PR with a `dist/node.dev.cjs`; then made moot when CJS was removed + across the board (observe-tier-plan, "CJS"): there is one module graph per + tier now and `require()` reaches it through the same conditions `import` + does. The resolution test still walks the `require` hops (`@solidjs/web` → + `solid-js` → `@solidjs/signals`) and pins all three landing on the same + ESM files per tier, and loads the chain through a real CJS `require` to + prove the graph is `require(esm)`-safe (no top-level `await`). - `solid-js#test` now depends on `solid-js#build` in `turbo.json` — it had no dist-based tests until this work, so it was the one package whose test task didn't wait for its own build. diff --git a/packages/h/jsx-dev-runtime/package.json b/packages/h/jsx-dev-runtime/package.json index 4397bb870..76c3c40f0 100644 --- a/packages/h/jsx-dev-runtime/package.json +++ b/packages/h/jsx-dev-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@solidjs/h/jsx-dev-runtime", - "main": "../jsx-runtime/dist/jsx.cjs", + "main": "../jsx-runtime/dist/jsx.js", "module": "../jsx-runtime/dist/jsx.js", "types": "../jsx-runtime/types/index.d.ts", "type": "module", diff --git a/packages/h/jsx-runtime/package.json b/packages/h/jsx-runtime/package.json index e573ecafd..deb6db0cb 100644 --- a/packages/h/jsx-runtime/package.json +++ b/packages/h/jsx-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@solidjs/h/jsx-runtime", - "main": "./dist/jsx.cjs", + "main": "./dist/jsx.js", "module": "./dist/jsx.js", "types": "./types/index.d.ts", "type": "module", diff --git a/packages/h/jsx-runtime/src/index.ts b/packages/h/jsx-runtime/src/index.ts index 754730388..54595be0f 100644 --- a/packages/h/jsx-runtime/src/index.ts +++ b/packages/h/jsx-runtime/src/index.ts @@ -7,9 +7,8 @@ function Fragment(props: { children: JSX.Element }) { } // Explicit return annotation keeps tsc from inlining `import("../../types/hyperscript").HyperElement` -// in the emitted .d.ts. `sync-dual-types.mjs` rewrites `.d.ts` -> `.d.cts` extensions but does not -// remap the `types/` <-> `types-cjs/` directory, so a cross-folder inferred reference would break -// Node16 CJS type resolution from `@solidjs/h/jsx-runtime`'s CJS export. +// in the emitted .d.ts; the public type is `JSX.Element`, not a relative path into the parent +// package's declaration folder. function jsx(type: any, props: any): JSX.Element { return h(type, props) as unknown as JSX.Element; } diff --git a/packages/h/package.json b/packages/h/package.json index fefb5f2ff..649b15228 100644 --- a/packages/h/package.json +++ b/packages/h/package.json @@ -13,52 +13,35 @@ "publishConfig": { "access": "public" }, - "main": "./dist/h.cjs", + "main": "./dist/h.js", "module": "./dist/h.js", "types": "./types/index.d.ts", "type": "module", + "engines": { + "node": ">=22.12.0" + }, "sideEffects": false, "files": [ "dist", "types", - "types-cjs", "package.json", "jsx-runtime/dist", "jsx-runtime/types", - "jsx-runtime/types-cjs", "jsx-runtime/package.json", "jsx-dev-runtime/package.json" ], "exports": { ".": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/h.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/h.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/h.js" }, "./jsx-runtime": { - "import": { - "types": "./jsx-runtime/types/index.d.ts", - "default": "./jsx-runtime/dist/jsx.js" - }, - "require": { - "types": "./jsx-runtime/types-cjs/index.d.cts", - "default": "./jsx-runtime/dist/jsx.cjs" - } + "types": "./jsx-runtime/types/index.d.ts", + "default": "./jsx-runtime/dist/jsx.js" }, "./jsx-dev-runtime": { - "import": { - "types": "./jsx-runtime/types/index.d.ts", - "default": "./jsx-runtime/dist/jsx.js" - }, - "require": { - "types": "./jsx-runtime/types-cjs/index.d.cts", - "default": "./jsx-runtime/dist/jsx.cjs" - } + "types": "./jsx-runtime/types/index.d.ts", + "default": "./jsx-runtime/dist/jsx.js" }, "./types/*": "./types/*" }, @@ -66,12 +49,11 @@ "build": "npm-run-all -nl build:clean types:copy build:js", "build:clean": "rimraf dist/ coverage/ /jsx-runtime/dist", "build:js": "rollup -c", - "types": "npm-run-all -nl types:clean types:copy types:h types:jsx types:cjs", - "types:clean": "rimraf types/ types-cjs/ jsx-runtime/types-cjs/", + "types": "npm-run-all -nl types:clean types:copy types:h types:jsx", + "types:clean": "rimraf types/", "types:copy": "ncp ../web/jsx/jsx-properties.d.ts ./jsx-runtime/src/jsx-properties.d.ts && node ../web/scripts/jsx-sync.mjs --input ../web/jsx/jsx-h.d.ts --output ./jsx-runtime/src/jsx.d.ts --element \"SolidElement | Node | FunctionElement | ArrayElement\" --import \"import type { Element as SolidElement } from \\\"solid-js\\\";\"", "types:h": "tsc --project ./tsconfig.json", "types:jsx": "rimraf ./jsx-runtime/types && tsc --project ./jsx-runtime/tsconfig.json && ncp ../web/jsx/jsx-properties.d.ts ./jsx-runtime/types/jsx-properties.d.ts && node ../web/scripts/jsx-sync.mjs --input ../web/jsx/jsx-h.d.ts --output ./jsx-runtime/types/jsx.d.ts --element \"SolidElement | Node | FunctionElement | ArrayElement\" --import \"import type { Element as SolidElement } from \\\"solid-js\\\";\"", - "types:cjs": "node ../../scripts/sync-dual-types.mjs ./types ./types-cjs ./jsx-runtime/types ./jsx-runtime/types-cjs", "link": "symlink-dir . node_modules/@solidjs/h", "test": "vitest run" }, diff --git a/packages/h/rollup.config.js b/packages/h/rollup.config.js index 60b3e1719..aef2bd2cd 100644 --- a/packages/h/rollup.config.js +++ b/packages/h/rollup.config.js @@ -22,32 +22,13 @@ const plugins = [ export default [ { input: "src/index.ts", - output: [ - { - file: "dist/h.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "dist/h.js", - format: "es" - } - ], + output: { file: "dist/h.js", format: "es" }, external: ["@solidjs/web"], plugins }, { input: "jsx-runtime/src/index.ts", - output: [ - { - file: "jsx-runtime/dist/jsx.cjs", - format: "cjs" - }, - { - file: "jsx-runtime/dist/jsx.js", - format: "es" - } - ], + output: { file: "jsx-runtime/dist/jsx.js", format: "es" }, external: ["@solidjs/h"], plugins } diff --git a/packages/html/package.json b/packages/html/package.json index 9f8bfb6f6..964d2004d 100644 --- a/packages/html/package.json +++ b/packages/html/package.json @@ -13,37 +13,32 @@ "publishConfig": { "access": "public" }, - "main": "./dist/html.cjs", + "main": "./dist/html.js", "module": "./dist/html.js", "types": "./types/index.d.ts", "type": "module", + "engines": { + "node": ">=22.12.0" + }, "sideEffects": false, "files": [ "dist", "types", - "types-cjs", "package.json" ], "exports": { ".": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/html.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/html.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/html.js" } }, "scripts": { "build": "npm-run-all -nl build:*", "build:clean": "rimraf dist/ coverage/", "build:js": "rollup -c", - "types": "npm-run-all -nl types:clean types:html types:cjs", - "types:clean": "rimraf types/ types-cjs/", + "types": "npm-run-all -nl types:clean types:html", + "types:clean": "rimraf types/", "types:html": "tsc --project ./tsconfig.json", - "types:cjs": "node ../../scripts/sync-dual-types.mjs ./types ./types-cjs", "test": "vitest run" }, "peerDependencies": { diff --git a/packages/html/rollup.config.js b/packages/html/rollup.config.js index 7190b3783..7faa163f8 100644 --- a/packages/html/rollup.config.js +++ b/packages/html/rollup.config.js @@ -22,17 +22,7 @@ const plugins = [ export default [ { input: "src/index.ts", - output: [ - { - file: "dist/html.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "dist/html.js", - format: "es" - } - ], + output: { file: "dist/html.js", format: "es" }, external: ["@solidjs/web"], plugins } diff --git a/packages/signals/package.json b/packages/signals/package.json index d40a68d78..6dcd7e30c 100644 --- a/packages/signals/package.json +++ b/packages/signals/package.json @@ -13,12 +13,15 @@ "publishConfig": { "access": "public" }, - "main": "./dist/node.cjs", + "main": "./dist/prod/index.js", "module": "./dist/prod/index.js", "unpkg": "./dist/prod/index.js", "jsdelivr": "./dist/prod/index.js", "types": "./dist/types/index.d.ts", "type": "module", + "engines": { + "node": ">=22.12.0" + }, "sideEffects": false, "files": [ "dist", @@ -26,42 +29,26 @@ ], "exports": { ".": { - "import": { - "types": "./dist/types/index.d.ts", - "test": "./dist/dev.js", - "development": "./dist/dev.js", - "observe": "./dist/observe/index.js", - "default": "./dist/prod/index.js" - }, - "require": { - "types": "./dist/types-cjs/index.d.cts", - "development": "./dist/node.dev.cjs", - "observe": "./dist/node.observe.cjs", - "default": "./dist/node.cjs" - } + "types": "./dist/types/index.d.ts", + "test": "./dist/dev.js", + "development": "./dist/dev.js", + "observe": "./dist/observe/index.js", + "default": "./dist/prod/index.js" }, "./attribution": { - "import": { - "types": "./dist/types/attribution.d.ts", - "test": "./dist/dev.attribution.js", - "development": "./dist/dev.attribution.js", - "observe": "./dist/observe/attribution.js", - "default": "./dist/prod/attribution.js" - }, - "require": { - "types": "./dist/types-cjs/attribution.d.cts", - "development": "./dist/node.dev.attribution.cjs", - "observe": "./dist/node.observe.attribution.cjs", - "default": "./dist/node.attribution.cjs" - } + "types": "./dist/types/attribution.d.ts", + "test": "./dist/dev.attribution.js", + "development": "./dist/dev.attribution.js", + "observe": "./dist/observe/attribution.js", + "default": "./dist/prod/attribution.js" }, "./package.json": "./package.json" }, "scripts": { "build": "npm-run-all -nl build:* && pnpm types", - "build:clean": "rimraf dist/dev dist/prod dist/observe dist/node dist/*.js dist/*.cjs", - "build:js": "rollup -c && node ./scripts/mangle-props.mjs dist/prod dist/observe dist/node.cjs,dist/node.attribution.cjs dist/node.observe.cjs,dist/node.observe.attribution.cjs,dist/node.observe-shared.cjs && node ./scripts/check-pure.mjs dist/prod && node ./scripts/check-pure.mjs dist/observe", - "types": "tsc -p tsconfig.build.json && node ../../scripts/sync-dual-types.mjs ./dist/types ./dist/types-cjs", + "build:clean": "rimraf dist/dev dist/prod dist/observe dist/*.js", + "build:js": "rollup -c && node ./scripts/mangle-props.mjs dist/prod dist/observe && node ./scripts/check-pure.mjs dist/prod && node ./scripts/check-pure.mjs dist/observe", + "types": "tsc -p tsconfig.build.json", "test": "vitest run", "test:watch": "vitest watch tests", "test:gc": "node --expose-gc ./vitest.js", diff --git a/packages/signals/rollup.config.js b/packages/signals/rollup.config.js index 8f20016cb..402668ae1 100644 --- a/packages/signals/rollup.config.js +++ b/packages/signals/rollup.config.js @@ -9,16 +9,17 @@ import prettier from "rollup-plugin-prettier"; // the pre-observe prod build // `__DEV__` implies `__OBSERVE__`; dev.ts asserts it at module init. // -// Only the prod and observe ESM builds are per-module trees -// (`preserveModules`); they are consumed exclusively by bundlers, which can -// drop whole feature modules — including their top-level GlobalQueue hook -// installs, which statement-level shaking of a flat file can never remove -// (#2883) — and scope-hoist the rest back into one module. Dev and node -// (prod, observe and dev CJS) stay flat single files: dev bundle size doesn't -// matter (and vitest's per-module SSR transform makes a chunked tree ~2x -// slower in the flush hot path, poisoning CI benches), and CJS `require` -// can't tree-shake, so a tree would charge unbundled SSR the per-module- -// boundary cost (~10% native ESM) for nothing. +// ESM only. Node >= 22.12 (the `engines` floor) `require()`s ESM natively, so +// a CJS host gets these same artifacts through the same export conditions — +// there is no second module graph to keep in step with the first. +// +// The prod and observe builds are per-module trees (`preserveModules`); they +// are consumed by bundlers, which can drop whole feature modules — including +// their top-level GlobalQueue hook installs, which statement-level shaking of +// a flat file can never remove (#2883) — and scope-hoist the rest back into +// one module. Dev stays a flat file: dev bundle size doesn't matter, and +// vitest's per-module SSR transform makes a chunked tree ~2x slower in the +// flush hot path, poisoning CI benches. // // `_`-prefixed property mangling for prod and observe outputs runs as a // single sequential post-pass (scripts/mangle-props.mjs) with one shared @@ -30,10 +31,10 @@ import prettier from "rollup-plugin-prettier"; // behind `@solidjs/signals/attribution`). The engine reads the core's live // state, so both MUST share one module instance per tier — never two flat // bundles that each carry their own copy of core. The trees get that for -// free (preserveModules); the flat builds are code-split instead: the two -// entries plus one shared chunk (`-shared.`), all mangled as one -// consistency domain. Prod's engine entry is `attribution.prod.ts`, an inert -// twin with the same surface — a prod build has no hook sites to feed one. +// free (preserveModules); the flat dev build is code-split instead: the two +// entries plus one shared chunk (`dev-shared.js`). Prod's engine entry is +// `attribution.prod.ts`, an inert twin with the same surface — a prod build +// has no hook sites to feed one. const flags = (dev, observe) => replace({ @@ -67,39 +68,25 @@ const tree = (dir, dev, observe) => ({ plugins: [flags(dev, observe), ts(dir)] }); -// `name` is the stem: dist/. (core), dist/.attribution. -// (engine), dist/-shared. (the one chunk both import). Prod has no -// chunk: its inert engine imports nothing from core. -const flat = (name, format, dev, observe) => { - const ext = format === "cjs" ? "cjs" : "js"; - return { - input: { [name]: "src/index.ts", [`${name}.attribution`]: engine(observe) }, - output: { - dir: "dist", - format, - exports: "named", - entryFileNames: `[name].${ext}`, - chunkFileNames: `${name}-shared.${ext}` - }, - plugins: [flags(dev, observe), ts("dist"), pretty] - }; -}; +// `name` is the stem: dist/.js (core), dist/.attribution.js +// (engine), dist/-shared.js (the one chunk both import). +const flat = (name, dev, observe) => ({ + input: { [name]: "src/index.ts", [`${name}.attribution`]: engine(observe) }, + output: { + dir: "dist", + format: "esm", + entryFileNames: "[name].js", + chunkFileNames: `${name}-shared.js` + }, + plugins: [flags(dev, observe), ts("dist"), pretty] +}); export default [ - flat("dev", "esm", true, true), + flat("dev", true, true), tree("dist/prod", false, false), // Observe tier: the ~40 wiring sites survive (attribution hooks, `_name`, // edge counters, the diagnostics channel), every check folds out. Selected // by the `observe` export condition. Gets its own size-limit scenario; the // prod tree's caps must not move because of it. - tree("dist/observe", false, true), - flat("node", "cjs", false, false), - // Dev CJS — the `require` twin of dist/dev.js, selected by the `development` - // condition on the `require` branch. Without it a CJS host that resolved - // solid-js's `dist/server.dev.cjs` would `require` the prod `dist/node.cjs` - // and get `DEV === undefined` from a dev artifact — a lie that goes - // unnoticed until something emits into the diagnostics channel. Not mangled - // (dev outputs never are — see build:js). - flat("node.dev", "cjs", true, true), - flat("node.observe", "cjs", false, true) + tree("dist/observe", false, true) ]; diff --git a/packages/signals/scripts/mangle-props.mjs b/packages/signals/scripts/mangle-props.mjs index 03c1e17f4..89dc6c8f0 100644 --- a/packages/signals/scripts/mangle-props.mjs +++ b/packages/signals/scripts/mangle-props.mjs @@ -8,11 +8,9 @@ * modules and break cross-module member access at runtime (#2883). * * Each argument is one consistency domain with its own nameCache: a directory - * tree, a single file, or a comma-separated group of files — the code-split - * flat builds (`node.cjs,node.attribution.cjs,node-shared.cjs`) are three - * files that share one module graph and must mangle as one. + * tree (the prod and observe trees) or a single file. * - * Usage: node scripts/mangle-props.mjs [...] + * Usage: node scripts/mangle-props.mjs [...] */ import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { join } from "node:path"; @@ -26,16 +24,14 @@ function walk(dir) { )) { const path = join(dir, entry.name); if (entry.isDirectory()) files.push(...walk(path)); - else if (/\.(js|cjs)$/.test(entry.name)) files.push(path); + else if (/\.js$/.test(entry.name)) files.push(path); } return files; } -const domain = arg => arg.split(",").flatMap(walk); - for (const dir of process.argv.slice(2)) { const nameCache = {}; - for (const file of domain(dir)) { + for (const file of walk(dir)) { const code = readFileSync(file, "utf8"); const result = await minify(code, { compress: false, @@ -58,5 +54,5 @@ for (const dir of process.argv.slice(2)) { }); writeFileSync(file, result.code); } - console.log(`mangled _-props across ${domain(dir).length} files in ${dir}`); + console.log(`mangled _-props across ${walk(dir).length} files in ${dir}`); } diff --git a/packages/signals/tests/dist-cjs-artifacts.test.ts b/packages/signals/tests/dist-artifacts.test.ts similarity index 52% rename from packages/signals/tests/dist-cjs-artifacts.test.ts rename to packages/signals/tests/dist-artifacts.test.ts index bb08b0360..1c13e7446 100644 --- a/packages/signals/tests/dist-cjs-artifacts.test.ts +++ b/packages/signals/tests/dist-artifacts.test.ts @@ -8,23 +8,25 @@ * observe DEV undefined OBSERVE live (wiring, no checks) * dev DEV live OBSERVE live (dev is a superset of observe) * - * The CJS tiers exist so a CJS host resolving solid-js's `dist/server.dev.cjs` - * (or `server.observe.cjs`) gets a signals with the matching objects — one - * tier per process, not one per module format. The ESM trees are checked - * through the same lens via dynamic import. Requires a prior build (the turbo - * `test` task depends on `build`). + * Every artifact is ESM. A CJS host on Node >= 22.12 (the `engines` floor) + * reaches the same files through `require()` — Node loads ESM synchronously + * there, provided the graph has no top-level await — so the second half of + * this file `require`s each tier and checks it is the SAME module instance + * `import` produced. One tier per process, not one per module format. + * Requires a prior build (the turbo `test` task depends on `build`). * * The `./attribution` entry is checked per tier too: the engine must share - * the core's module instance (the code-split flat builds exist for exactly - * this), prod's inert twin must never install anything, and the observe - * core must not carry the engine — that is the whole point of the entry. + * the core's module instance (the code-split flat dev build exists for + * exactly this), prod's inert twin must never install anything, and the + * observe core must not carry the engine — that is the whole point of the + * entry. */ -import { readFileSync } from "node:fs"; -import { createRequire } from "node:module"; +import { execFileSync } from "node:child_process"; +import { readFileSync, readdirSync, statSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; import { describe, expect, test } from "vitest"; -const require = createRequire(import.meta.url); - type Tier = { DEV: unknown; OBSERVE: unknown }; type Engine = { attribution: any }; @@ -105,85 +107,153 @@ function expectDevLive(mod: Tier) { expect((mod.OBSERVE as any).hooks).toBeUndefined(); } -describe("@solidjs/signals CJS artifacts", () => { - test("dist/node.cjs (prod) exports DEV and OBSERVE as undefined", () => { - const prod = require("../dist/node.cjs") as Tier; +/** The three tiers' (core, engine) entry pairs, relative to dist/. */ +const TIERS = { + prod: ["prod/index.js", "prod/attribution.js"], + observe: ["observe/index.js", "observe/attribution.js"], + dev: ["dev.js", "dev.attribution.js"] +} as const; + +describe("@solidjs/signals artifacts", () => { + test("dist/prod/index.js exports DEV and OBSERVE as undefined", async () => { + const prod = (await import("../dist/prod/index.js")) as Tier; expect(prod.DEV).toBeUndefined(); expect(prod.OBSERVE).toBeUndefined(); }); - test("dist/node.observe.cjs exports a live OBSERVE and no DEV", () => { - const observe = require("../dist/node.observe.cjs") as Tier; + test("dist/observe/index.js exports a live OBSERVE and no DEV", async () => { + const observe = (await import("../dist/observe/index.js")) as Tier; expectObserveLive(observe); expect(observe.DEV).toBeUndefined(); }); - test("dist/node.dev.cjs exports live OBSERVE and DEV", () => { - const dev = require("../dist/node.dev.cjs") as Tier; + test("dist/dev.js exports live OBSERVE and DEV", async () => { + const dev = (await import("../dist/dev.js")) as Tier; expectObserveLive(dev); expectDevLive(dev); }); - test("the three artifacts export the same surface", () => { - const prod = require("../dist/node.cjs"); - const observe = require("../dist/node.observe.cjs"); - const dev = require("../dist/node.dev.cjs"); + test("the three tiers export the same surface", async () => { + const prod = await import("../dist/prod/index.js"); + const observe = await import("../dist/observe/index.js"); + const dev = await import("../dist/dev.js"); expect(Object.keys(observe).sort()).toEqual(Object.keys(prod).sort()); expect(Object.keys(dev).sort()).toEqual(Object.keys(prod).sort()); }); - test("./attribution: node.attribution.cjs (prod) is the inert engine", () => { - expectEngineInert(require("../dist/node.attribution.cjs")); - // Inert means inert: it must not even import the core. - expect( - readFileSync(new URL("../dist/node.attribution.cjs", import.meta.url), "utf8") - ).not.toMatch(/require\(/); - }); - - test("./attribution: node.observe.attribution.cjs drives node.observe.cjs", () => { - expectEngineDrivesCore( - require("../dist/node.observe.cjs"), - require("../dist/node.observe.attribution.cjs") - ); - }); - - test("./attribution: node.dev.attribution.cjs drives node.dev.cjs", () => { - expectEngineDrivesCore( - require("../dist/node.dev.cjs"), - require("../dist/node.dev.attribution.cjs") - ); - }); - - test("the engine is not in the observe core", () => { + test("the dev build's engine is not in its core", () => { // The code-split chunk is what the two entries share; the engine's own - // code must sit only in its entry, or the observe tier ships it to every - // consumer whether or not it ever enables attribution. + // code must sit only in its entry, or every dev consumer carries it + // whether or not it ever enables attribution. const read = (f: string) => readFileSync(new URL(`../dist/${f}`, import.meta.url), "utf8"); - expect(read("node.observe.cjs")).not.toContain(ENGINE_MARK); - expect(read("node.observe-shared.cjs")).not.toContain(ENGINE_MARK); - expect(read("node.observe.attribution.cjs")).toContain(ENGINE_MARK); + expect(read("dev.js")).not.toContain(ENGINE_MARK); + expect(read("dev-shared.js")).not.toContain(ENGINE_MARK); + expect(read("dev.attribution.js")).toContain(ENGINE_MARK); }); }); -describe("@solidjs/signals ESM artifacts", () => { - test("dist/prod/index.js exports DEV and OBSERVE as undefined", async () => { - const prod = (await import("../dist/prod/index.js")) as Tier; - expect(prod.DEV).toBeUndefined(); - expect(prod.OBSERVE).toBeUndefined(); - }); +describe("@solidjs/signals artifacts under require()", () => { + // Node >= 22.12 loads ESM through `require()` synchronously. That is what + // lets the package ship ESM only: a CJS host follows the same export + // conditions to the same files. Two things must hold for it to work, and + // each is pinned here against the real loader in a child Node rather than + // through vitest's module runner (which would give `import` its own + // transformed instance and make the identity check meaningless): + // + // 1. No entry's graph may contain a top-level `await` — Node throws + // ERR_REQUIRE_ASYNC_MODULE for that, and a bundler-oriented tree is one + // stray `await` from breaking every CJS consumer at once. + // 2. `require` and `import` of one artifact must yield ONE module + // instance, or a CJS host with an ESM dependency would run two cores + // and the engine would install into the wrong one. + const RESOLVE_PROBE = ` + import { createRequire } from "node:module"; + import { pathToFileURL } from "node:url"; + const require = createRequire(import.meta.url); + const [core, engine] = process.argv.slice(1); + const out = { requireModule: process.features.require_module }; + const viaRequire = require(core); + const viaImport = await import(pathToFileURL(core).href); + out.sameInstance = viaRequire.createSignal === viaImport.createSignal; + out.sameObserve = viaRequire.OBSERVE === viaImport.OBSERVE; + out.hasDefaultLeak = "default" in viaRequire; + const engineViaRequire = require(engine); + out.engineSameInstance = + engineViaRequire.attribution === (await import(pathToFileURL(engine).href)).attribution; + process.stdout.write(JSON.stringify(out)); + `; - test("dist/observe/index.js exports a live OBSERVE and no DEV", async () => { - const observe = (await import("../dist/observe/index.js")) as Tier; - expectObserveLive(observe); - expect(observe.DEV).toBeUndefined(); + function probe(tier: keyof typeof TIERS) { + const dist = (f: string) => fileURLToPath(new URL(`../dist/${f}`, import.meta.url)); + const [core, engine] = TIERS[tier]; + return JSON.parse( + execFileSync( + process.execPath, + ["--input-type=module", "-e", RESOLVE_PROBE, "--", dist(core), dist(engine)], + { encoding: "utf8" } + ) + ); + } + + test("this Node has require(esm) (the engines floor)", () => { + expect(process.features.require_module).toBe(true); }); - test("dist/dev.js exports live OBSERVE and DEV", async () => { - const dev = (await import("../dist/dev.js")) as Tier; - expectObserveLive(dev); - expectDevLive(dev); + for (const tier of Object.keys(TIERS) as (keyof typeof TIERS)[]) { + test(`${tier}: require() and import() of the core and engine yield one instance each`, () => { + const result = probe(tier); + expect(result.requireModule).toBe(true); + // The require succeeding at all is the top-level-await guard. + expect(result.sameInstance).toBe(true); + expect(result.sameObserve).toBe(true); + expect(result.engineSameInstance).toBe(true); + // No default export anywhere, so the namespace `require` hands back is + // the same flat surface `import * as` gives; Node marks it __esModule + // for interop only when a default export exists. + expect(result.hasDefaultLeak).toBe(false); + }); + } + + test("no shipped artifact contains a top-level await", () => { + // Belt to the probe's braces: the probe only loads what the entries reach + // today; a module that a future entry pulls in would still be caught by + // scanning every file the trees ship. + const walk = (dir: string): string[] => + readdirSync(dir).flatMap(name => { + const p = join(dir, name); + return statSync(p).isDirectory() ? walk(p) : /\.js$/.test(name) ? [p] : []; + }); + const dist = fileURLToPath(new URL("../dist/", import.meta.url)); + const files = walk(dist).filter(f => !f.includes("/types/")); + expect(files.length).toBeGreaterThan(3); + for (const file of files) { + const code = readFileSync(file, "utf8"); + // Any `await` at brace depth zero. Cheap and conservative: the engine + // and core have no async functions at module scope, so a match is a + // real top-level await, not a false positive from an inner body. + let depth = 0; + let inString: string | null = null; + let tla = false; + for (let i = 0; i < code.length && !tla; i++) { + const c = code[i]; + if (inString) { + if (c === "\\") i++; + else if (c === inString) inString = null; + continue; + } + if (c === '"' || c === "'" || c === "`") inString = c; + else if (c === "{") depth++; + else if (c === "}") depth--; + else if (depth === 0 && code.startsWith("await", i) && !/\w/.test(code[i - 1] ?? " ")) { + tla = !/\w/.test(code[i + 5] ?? " "); + } + } + expect(tla, `${file} has a top-level await`).toBe(false); + } }); +}); +describe("@solidjs/signals engine per tier", () => { test("./attribution: prod/attribution.js is the inert engine and imports nothing", async () => { expectEngineInert((await import("../dist/prod/attribution.js")) as Engine); const src = readFileSync(new URL("../dist/prod/attribution.js", import.meta.url), "utf8"); diff --git a/packages/solid/package.json b/packages/solid/package.json index 7f832b332..0e45167d7 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -10,17 +10,19 @@ "url": "git+https://github.com/solidjs/solid.git", "directory": "packages/solid" }, - "main": "./dist/server.cjs", + "main": "./dist/server.js", "module": "./dist/server.js", "unpkg": "./dist/solid.js", "jsdelivr": "./dist/solid.js", "types": "types/index.d.ts", "sideEffects": false, "type": "module", + "engines": { + "node": ">=22.12.0" + }, "files": [ "dist", "types", - "types-cjs", "package.json", "CHEATSHEET.md", "skills" @@ -29,182 +31,74 @@ ".": { "worker": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.dev.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.dev.js" }, "observe": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.observe.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.observe.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/server.js" + "default": "./dist/server.observe.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.js" }, "browser": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/solid.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/solid.dev.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/solid.dev.js" }, "observe": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/solid.observe.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/solid.observe.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/solid.js" + "default": "./dist/solid.observe.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/solid.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/solid.js" }, "deno": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.dev.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.dev.js" }, "observe": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.observe.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.observe.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/server.js" + "default": "./dist/server.observe.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.js" }, "node": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.dev.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.dev.js" }, "observe": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.observe.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.observe.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/server.js" + "default": "./dist/server.observe.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.js" }, "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/solid.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/solid.dev.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/solid.dev.js" }, "observe": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/solid.observe.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/solid.observe.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/solid.js" + "default": "./dist/solid.observe.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/solid.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/solid.js" }, "./refresh": { "development": { - "import": { - "types": "./types/refresh/index.d.ts", - "default": "./dist/refresh.dev.js" - }, - "require": { - "types": "./types-cjs/refresh/index.d.cts", - "default": "./dist/refresh.dev.cjs" - } - }, - "import": { "types": "./types/refresh/index.d.ts", - "default": "./dist/refresh.js" + "default": "./dist/refresh.dev.js" }, - "require": { - "types": "./types-cjs/refresh/index.d.cts", - "default": "./dist/refresh.cjs" - } + "types": "./types/refresh/index.d.ts", + "default": "./dist/refresh.js" }, "./attribution": { - "import": { - "types": "./types/attribution.d.ts", - "default": "./dist/attribution.js" - }, - "require": { - "types": "./types-cjs/attribution.d.cts", - "default": "./dist/attribution.cjs" - } + "types": "./types/attribution.d.ts", + "default": "./dist/attribution.js" }, "./package.json": "./package.json" }, @@ -212,10 +106,9 @@ "build": "npm-run-all -nl build:*", "build:clean": "rimraf dist/ coverage/", "build:js": "rollup -c", - "types": "npm-run-all -nl types:clean types:src types:cjs", - "types:clean": "rimraf types/ types-cjs/", + "types": "npm-run-all -nl types:clean types:src", + "types:clean": "rimraf types/", "types:src": "tsc --project ./tsconfig.build.json", - "types:cjs": "node ../../scripts/sync-dual-types.mjs ./types ./types-cjs", "test": "vitest run", "coverage": "vitest run --coverage", "test-types": "tsc --project tsconfig.test.json && tsc --project tsconfig.no-dom.json" diff --git a/packages/solid/rollup.config.js b/packages/solid/rollup.config.js index b5ccaa050..35c830dd5 100644 --- a/packages/solid/rollup.config.js +++ b/packages/solid/rollup.config.js @@ -36,12 +36,11 @@ const replaceFlags = (isDev, isObserve) => delimiters: ["", ""] }); +// ESM only: Node >= 22.12 (the `engines` floor) `require()`s ESM natively, so +// CJS hosts resolve these same files through the same export conditions. const build = (input, name, external, isDev, isObserve) => ({ input, - output: [ - { file: `dist/${name}.cjs`, format: "cjs" }, - { file: `dist/${name}.js`, format: "es" } - ], + output: { file: `dist/${name}.js`, format: "es" }, external, plugins: [replaceFlags(isDev, isObserve)].concat(plugins) }); diff --git a/packages/test-integration/fixtures/packaged-types/node16-cjs/solid-web.ts b/packages/test-integration/fixtures/packaged-types/node16-cjs/solid-web.ts deleted file mode 100644 index 8ebd6a4a9..000000000 --- a/packages/test-integration/fixtures/packaged-types/node16-cjs/solid-web.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {} from "@solidjs/web"; -import {} from "@solidjs/web/jsx-runtime"; -import {} from "@solidjs/web/jsx-dev-runtime"; -import {} from "@solidjs/web/storage"; -// Note: @solidjs/web/server-functions (and /serialization) types are not -// importable from Node16 CJS without skipLibCheck — their d.cts chain -// reaches seroval, whose published types are ESM-only (unqualified relative -// imports, no .d.cts). Upstream seroval limitation, tracked separately. diff --git a/packages/test-integration/fixtures/packaged-types/node16-cjs/package.json b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/package.json similarity index 51% rename from packages/test-integration/fixtures/packaged-types/node16-cjs/package.json rename to packages/test-integration/fixtures/packaged-types/nodenext-cjs/package.json index 028df66c1..04414d6c8 100644 --- a/packages/test-integration/fixtures/packaged-types/node16-cjs/package.json +++ b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/package.json @@ -1,5 +1,5 @@ { - "name": "packaged-types-node16-cjs", + "name": "packaged-types-nodenext-cjs", "private": true, "type": "commonjs" } diff --git a/packages/test-integration/fixtures/packaged-types/node16-cjs/solid-js.ts b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-js.ts similarity index 100% rename from packages/test-integration/fixtures/packaged-types/node16-cjs/solid-js.ts rename to packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-js.ts diff --git a/packages/test-integration/fixtures/packaged-types/node16-cjs/solid-rest.ts b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-rest.ts similarity index 100% rename from packages/test-integration/fixtures/packaged-types/node16-cjs/solid-rest.ts rename to packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-rest.ts diff --git a/packages/test-integration/fixtures/packaged-types/node16-cjs/solid-signals.ts b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-signals.ts similarity index 100% rename from packages/test-integration/fixtures/packaged-types/node16-cjs/solid-signals.ts rename to packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-signals.ts diff --git a/packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-web.ts b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-web.ts new file mode 100644 index 000000000..7fbbd412f --- /dev/null +++ b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/solid-web.ts @@ -0,0 +1,9 @@ +import {} from "@solidjs/web"; +import {} from "@solidjs/web/jsx-runtime"; +import {} from "@solidjs/web/jsx-dev-runtime"; +import {} from "@solidjs/web/storage"; +// Note: @solidjs/web/server-functions, /serialization and /frames types are +// not importable under NodeNext resolution without skipLibCheck — their type +// chain reaches seroval, whose published .d.ts uses extensionless relative +// imports (TS2834 under node16/nodenext). Upstream seroval limitation, +// tracked separately; unrelated to this package's module format. diff --git a/packages/test-integration/fixtures/packaged-types/node16-cjs/tsconfig.json b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/tsconfig.json similarity index 60% rename from packages/test-integration/fixtures/packaged-types/node16-cjs/tsconfig.json rename to packages/test-integration/fixtures/packaged-types/nodenext-cjs/tsconfig.json index d0a2bf269..64c46cd25 100644 --- a/packages/test-integration/fixtures/packaged-types/node16-cjs/tsconfig.json +++ b/packages/test-integration/fixtures/packaged-types/nodenext-cjs/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "module": "Node16", - "moduleResolution": "node16", + "module": "NodeNext", + "moduleResolution": "NodeNext", "noEmit": true, "strict": true }, diff --git a/packages/test-integration/test-imports.mjs b/packages/test-integration/test-imports.mjs index 762559a73..d92430233 100644 --- a/packages/test-integration/test-imports.mjs +++ b/packages/test-integration/test-imports.mjs @@ -1,3 +1,5 @@ +import { createRequire } from "node:module"; + function checkError(error) { // This error happens when missing the type:module field in package.json when it is needed. if ( @@ -9,7 +11,17 @@ function checkError(error) { } // These errors happen if exports are not mapped to files that should be importable. - if (["ERR_PACKAGE_PATH_NOT_EXPORTED", "ERR_MODULE_NOT_FOUND"].includes(error.code)) { + // ERR_REQUIRE_ASYNC_MODULE is the `require()` half: every package ships ESM + // only and relies on Node's require(esm), which refuses a graph containing + // a top-level await — a CJS host would be locked out of that entry. + if ( + [ + "ERR_PACKAGE_PATH_NOT_EXPORTED", + "ERR_MODULE_NOT_FOUND", + "MODULE_NOT_FOUND", + "ERR_REQUIRE_ASYNC_MODULE" + ].includes(error.code) + ) { console.error(error); process.exit(1); } @@ -24,26 +36,49 @@ function checkError(error) { // properly, for example. } -Promise.all([ - import("solid-js").catch(checkError), - - import("@solidjs/signals").catch(checkError), - import("@solidjs/web").catch(checkError), - import("@solidjs/web/jsx-runtime").catch(checkError), - import("@solidjs/web/jsx-dev-runtime").catch(checkError), - import("@solidjs/web/storage").catch(checkError), - import("@solidjs/web/server-functions").catch(checkError), - import("@solidjs/web/server-functions/server").catch(checkError), - import("@solidjs/web/server-functions/client").catch(checkError), - - import("@solidjs/h").catch(checkError), - import("@solidjs/h/jsx-runtime").catch(checkError), - import("@solidjs/h/jsx-dev-runtime").catch(checkError), - import("@solidjs/html").catch(checkError), - import("@solidjs/universal").catch(checkError) -]) +const specifiers = [ + "solid-js", + "solid-js/attribution", + + "@solidjs/signals", + "@solidjs/signals/attribution", + "@solidjs/web", + "@solidjs/web/jsx-runtime", + "@solidjs/web/jsx-dev-runtime", + "@solidjs/web/storage", + "@solidjs/web/serialization", + "@solidjs/web/serialization/decode", + "@solidjs/web/server-functions", + "@solidjs/web/server-functions/server", + "@solidjs/web/server-functions/client", + "@solidjs/web/server-functions/rich-args", + "@solidjs/web/frames", + "@solidjs/web/frames/server", + "@solidjs/web/frames/client", + + "@solidjs/h", + "@solidjs/h/jsx-runtime", + "@solidjs/h/jsx-dev-runtime", + "@solidjs/html", + "@solidjs/universal" +]; + +// The same entries through a CommonJS `require()`: there is no `require` +// branch in any exports map, so this must land on the ESM files and load them +// synchronously (Node >= 22.12). Failing here means a CJS host cannot load +// that entry at all. +const require = createRequire(import.meta.url); +for (const specifier of specifiers) { + try { + require(specifier); + } catch (error) { + checkError(error); + } +} + +Promise.all(specifiers.map(specifier => import(specifier).catch(checkError))) .then(() => { - console.log("ES Module import test passed."); + console.log("ES Module import + require(esm) test passed."); }) .catch(error => { console.error(error); diff --git a/packages/test-integration/tests/packaged-types.spec.ts b/packages/test-integration/tests/packaged-types.spec.ts index 6417002a0..eca753337 100644 --- a/packages/test-integration/tests/packaged-types.spec.ts +++ b/packages/test-integration/tests/packaged-types.spec.ts @@ -14,8 +14,10 @@ function pack(packageRoot: string) { } describe("Packed package type resolution", () => { - const fixtureSource = resolve(join(__dirname, "..", "fixtures", "packaged-types", "node16-cjs")); - const fixtureRun = resolve(join(__dirname, "..", "fixtures", ".generated", "node16-cjs")); + const fixtureSource = resolve( + join(__dirname, "..", "fixtures", "packaged-types", "nodenext-cjs") + ); + const fixtureRun = resolve(join(__dirname, "..", "fixtures", ".generated", "nodenext-cjs")); const rootPackageJson = JSON.parse( readFileSync(resolve(join(__dirname, "../../..", "package.json")), "utf8") ); @@ -59,7 +61,7 @@ describe("Packed package type resolution", () => { }); }, 180_000); - test("Node16 CommonJS consumers can import packed packages", () => { + test("CommonJS consumers on Node >= 22.12 (module: NodeNext, require(esm)) resolve packed types", () => { const result = exec("npx tsc -p tsconfig.json", { cwd: fixtureRun, silent: true }); expect(result.code).toBe(0); }); diff --git a/packages/universal/package.json b/packages/universal/package.json index b11a172e8..c5dd6d3a0 100644 --- a/packages/universal/package.json +++ b/packages/universal/package.json @@ -13,47 +13,31 @@ "publishConfig": { "access": "public" }, - "main": "./dist/universal.cjs", + "main": "./dist/universal.js", "module": "./dist/universal.js", "types": "./types/index.d.ts", "type": "module", + "engines": { + "node": ">=22.12.0" + }, "sideEffects": false, "files": [ "dist", "types", - "types-cjs", "package.json" ], "exports": { ".": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/universal.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/universal.dev.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/universal.dev.js" }, "observe": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/universal.observe.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/universal.observe.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/universal.js" + "default": "./dist/universal.observe.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/universal.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/universal.js" }, "./types/*": "./types/*" }, @@ -61,10 +45,9 @@ "build": "npm-run-all -nl build:*", "build:clean": "rimraf dist/ coverage/", "build:js": "rollup -c", - "types": "npm-run-all -nl types:clean types:universal types:cjs", - "types:clean": "rimraf types/ types-cjs/", + "types": "npm-run-all -nl types:clean types:universal", + "types:clean": "rimraf types/", "types:universal": "tsc --project ./tsconfig.json", - "types:cjs": "node ../../scripts/sync-dual-types.mjs ./types ./types-cjs", "test": "vitest run" }, "peerDependencies": { diff --git a/packages/universal/rollup.config.js b/packages/universal/rollup.config.js index 14f8b698f..9aac80483 100644 --- a/packages/universal/rollup.config.js +++ b/packages/universal/rollup.config.js @@ -33,10 +33,7 @@ const replaceFlags = (isDev, isObserve) => const build = (name, isDev, isObserve) => ({ input: "src/index.ts", - output: [ - { file: `dist/${name}.cjs`, format: "cjs" }, - { file: `dist/${name}.js`, format: "es" } - ], + output: { file: `dist/${name}.js`, format: "es" }, external: ["solid-js"], plugins: [replaceFlags(isDev, isObserve)].concat(plugins) }); diff --git a/packages/web/frames/package.json b/packages/web/frames/package.json index ce6106523..9f982ae47 100644 --- a/packages/web/frames/package.json +++ b/packages/web/frames/package.json @@ -1,6 +1,6 @@ { "name": "@solidjs/web/frames", - "main": "./dist/server.cjs", + "main": "./dist/server.js", "module": "./dist/server.js", "types": "../types/frames/server.d.ts", "type": "module", @@ -8,33 +8,15 @@ "exports": { ".": { "browser": { - "import": { - "types": "../types/frames/client.d.ts", - "default": "./dist/client.js" - }, - "require": { - "types": "../types-cjs/frames/client.d.cts", - "default": "./dist/client.cjs" - } + "types": "../types/frames/client.d.ts", + "default": "./dist/client.js" }, "development": { - "import": { - "types": "../types/frames/server.d.ts", - "default": "./dist/server.dev.js" - }, - "require": { - "types": "../types-cjs/frames/server.d.cts", - "default": "./dist/server.dev.cjs" - } - }, - "import": { "types": "../types/frames/server.d.ts", - "default": "./dist/server.js" + "default": "./dist/server.dev.js" }, - "require": { - "types": "../types-cjs/frames/server.d.cts", - "default": "./dist/server.cjs" - } + "types": "../types/frames/server.d.ts", + "default": "./dist/server.js" } } } diff --git a/packages/web/package.json b/packages/web/package.json index ba8c26b83..613a3b723 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -13,25 +13,25 @@ "publishConfig": { "access": "public" }, - "main": "./dist/server.cjs", + "main": "./dist/server.js", "module": "./dist/server.js", "unpkg": "./dist/web.js", "jsdelivr": "./dist/web.js", "types": "./types/index.d.ts", "type": "module", + "engines": { + "node": ">=22.12.0" + }, "sideEffects": false, "files": [ "dist", "types", - "types-cjs", "package.json", "storage/dist", "storage/types", - "storage/types-cjs", "storage/package.json", "serialization/dist", "serialization/types", - "serialization/types-cjs", "serialization/package.json", "serialization/decode/package.json", "server-functions/dist", @@ -44,422 +44,170 @@ ".": { "worker": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.dev.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/server.js" + "default": "./dist/server.dev.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.js" }, "browser": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/web.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/web.dev.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/web.dev.js" }, "observe": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/web.observe.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/web.observe.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/web.js" + "default": "./dist/web.observe.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/web.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/web.js" }, "deno": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.dev.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/server.js" + "default": "./dist/server.dev.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.js" }, "node": { "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.dev.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/server.js" + "default": "./dist/server.dev.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/server.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/server.js" }, "development": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/web.dev.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/web.dev.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/web.dev.js" }, "observe": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/web.observe.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/web.observe.cjs" - } - }, - "import": { "types": "./types/index.d.ts", - "default": "./dist/web.js" + "default": "./dist/web.observe.js" }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/web.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/web.js" }, "./jsx-runtime": { - "import": { - "types": "./types/jsx.d.ts", - "default": "./dist/web.js" - }, - "require": { - "types": "./types-cjs/jsx.d.cts", - "default": "./dist/web.cjs" - } + "types": "./types/jsx.d.ts", + "default": "./dist/web.js" }, "./jsx-dev-runtime": { - "import": { - "types": "./types/jsx.d.ts", - "default": "./dist/web.dev.js" - }, - "require": { - "types": "./types-cjs/jsx.d.cts", - "default": "./dist/web.dev.cjs" - } + "types": "./types/jsx.d.ts", + "default": "./dist/web.dev.js" }, "./storage": { - "import": { - "types": "./storage/types/index.d.ts", - "default": "./storage/dist/storage.js" - }, - "require": { - "types": "./storage/types-cjs/index.d.cts", - "default": "./storage/dist/storage.cjs" - } + "types": "./storage/types/index.d.ts", + "default": "./storage/dist/storage.js" }, "./serialization": { - "import": { - "types": "./serialization/types/index.d.ts", - "default": "./serialization/dist/serialization.js" - }, - "require": { - "types": "./serialization/types-cjs/index.d.cts", - "default": "./serialization/dist/serialization.cjs" - } + "types": "./serialization/types/index.d.ts", + "default": "./serialization/dist/serialization.js" }, "./serialization/decode": { - "import": { - "types": "./serialization/types/serializer-decode.d.ts", - "default": "./serialization/dist/decode.js" - }, - "require": { - "types": "./serialization/types-cjs/serializer-decode.d.cts", - "default": "./serialization/dist/decode.cjs" - } + "types": "./serialization/types/serializer-decode.d.ts", + "default": "./serialization/dist/decode.js" }, "./server-functions": { "worker": { "development": { - "import": { - "types": "./types/server-functions/server.d.ts", - "default": "./server-functions/dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/server-functions/server.d.cts", - "default": "./server-functions/dist/server.dev.cjs" - } - }, - "import": { "types": "./types/server-functions/server.d.ts", - "default": "./server-functions/dist/server.js" + "default": "./server-functions/dist/server.dev.js" }, - "require": { - "types": "./types-cjs/server-functions/server.d.cts", - "default": "./server-functions/dist/server.cjs" - } + "types": "./types/server-functions/server.d.ts", + "default": "./server-functions/dist/server.js" }, "browser": { - "import": { - "types": "./types/server-functions/client.d.ts", - "default": "./server-functions/dist/client.js" - }, - "require": { - "types": "./types-cjs/server-functions/client.d.cts", - "default": "./server-functions/dist/client.cjs" - } + "types": "./types/server-functions/client.d.ts", + "default": "./server-functions/dist/client.js" }, "deno": { "development": { - "import": { - "types": "./types/server-functions/server.d.ts", - "default": "./server-functions/dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/server-functions/server.d.cts", - "default": "./server-functions/dist/server.dev.cjs" - } - }, - "import": { "types": "./types/server-functions/server.d.ts", - "default": "./server-functions/dist/server.js" + "default": "./server-functions/dist/server.dev.js" }, - "require": { - "types": "./types-cjs/server-functions/server.d.cts", - "default": "./server-functions/dist/server.cjs" - } + "types": "./types/server-functions/server.d.ts", + "default": "./server-functions/dist/server.js" }, "node": { "development": { - "import": { - "types": "./types/server-functions/server.d.ts", - "default": "./server-functions/dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/server-functions/server.d.cts", - "default": "./server-functions/dist/server.dev.cjs" - } - }, - "import": { "types": "./types/server-functions/server.d.ts", - "default": "./server-functions/dist/server.js" + "default": "./server-functions/dist/server.dev.js" }, - "require": { - "types": "./types-cjs/server-functions/server.d.cts", - "default": "./server-functions/dist/server.cjs" - } - }, - "import": { - "types": "./types/server-functions/client.d.ts", - "default": "./server-functions/dist/client.js" + "types": "./types/server-functions/server.d.ts", + "default": "./server-functions/dist/server.js" }, - "require": { - "types": "./types-cjs/server-functions/client.d.cts", - "default": "./server-functions/dist/client.cjs" - } + "types": "./types/server-functions/client.d.ts", + "default": "./server-functions/dist/client.js" }, "./server-functions/server": { "development": { - "import": { - "types": "./types/server-functions/server.d.ts", - "default": "./server-functions/dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/server-functions/server.d.cts", - "default": "./server-functions/dist/server.dev.cjs" - } - }, - "import": { "types": "./types/server-functions/server.d.ts", - "default": "./server-functions/dist/server.js" + "default": "./server-functions/dist/server.dev.js" }, - "require": { - "types": "./types-cjs/server-functions/server.d.cts", - "default": "./server-functions/dist/server.cjs" - } + "types": "./types/server-functions/server.d.ts", + "default": "./server-functions/dist/server.js" }, "./server-functions/client": { - "import": { - "types": "./types/server-functions/client.d.ts", - "default": "./server-functions/dist/client.js" - }, - "require": { - "types": "./types-cjs/server-functions/client.d.cts", - "default": "./server-functions/dist/client.cjs" - } + "types": "./types/server-functions/client.d.ts", + "default": "./server-functions/dist/client.js" }, "./server-functions/rich-args": { - "import": { - "types": "./types/server-functions/rich-args.d.ts", - "default": "./server-functions/dist/rich-args.js" - }, - "require": { - "types": "./types-cjs/server-functions/rich-args.d.cts", - "default": "./server-functions/dist/rich-args.cjs" - } + "types": "./types/server-functions/rich-args.d.ts", + "default": "./server-functions/dist/rich-args.js" }, "./frames": { "worker": { "development": { - "import": { - "types": "./types/frames/server.d.ts", - "default": "./frames/dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/frames/server.d.cts", - "default": "./frames/dist/server.dev.cjs" - } - }, - "import": { "types": "./types/frames/server.d.ts", - "default": "./frames/dist/server.js" + "default": "./frames/dist/server.dev.js" }, - "require": { - "types": "./types-cjs/frames/server.d.cts", - "default": "./frames/dist/server.cjs" - } + "types": "./types/frames/server.d.ts", + "default": "./frames/dist/server.js" }, "browser": { "development": { - "import": { - "types": "./types/frames/client.d.ts", - "default": "./frames/dist/client.dev.js" - }, - "require": { - "types": "./types-cjs/frames/client.d.cts", - "default": "./frames/dist/client.dev.cjs" - } - }, - "import": { "types": "./types/frames/client.d.ts", - "default": "./frames/dist/client.js" + "default": "./frames/dist/client.dev.js" }, - "require": { - "types": "./types-cjs/frames/client.d.cts", - "default": "./frames/dist/client.cjs" - } + "types": "./types/frames/client.d.ts", + "default": "./frames/dist/client.js" }, "deno": { "development": { - "import": { - "types": "./types/frames/server.d.ts", - "default": "./frames/dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/frames/server.d.cts", - "default": "./frames/dist/server.dev.cjs" - } - }, - "import": { "types": "./types/frames/server.d.ts", - "default": "./frames/dist/server.js" + "default": "./frames/dist/server.dev.js" }, - "require": { - "types": "./types-cjs/frames/server.d.cts", - "default": "./frames/dist/server.cjs" - } + "types": "./types/frames/server.d.ts", + "default": "./frames/dist/server.js" }, "node": { "development": { - "import": { - "types": "./types/frames/server.d.ts", - "default": "./frames/dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/frames/server.d.cts", - "default": "./frames/dist/server.dev.cjs" - } - }, - "import": { "types": "./types/frames/server.d.ts", - "default": "./frames/dist/server.js" + "default": "./frames/dist/server.dev.js" }, - "require": { - "types": "./types-cjs/frames/server.d.cts", - "default": "./frames/dist/server.cjs" - } + "types": "./types/frames/server.d.ts", + "default": "./frames/dist/server.js" }, "development": { - "import": { - "types": "./types/frames/client.d.ts", - "default": "./frames/dist/client.dev.js" - }, - "require": { - "types": "./types-cjs/frames/client.d.cts", - "default": "./frames/dist/client.dev.cjs" - } - }, - "import": { "types": "./types/frames/client.d.ts", - "default": "./frames/dist/client.js" + "default": "./frames/dist/client.dev.js" }, - "require": { - "types": "./types-cjs/frames/client.d.cts", - "default": "./frames/dist/client.cjs" - } + "types": "./types/frames/client.d.ts", + "default": "./frames/dist/client.js" }, "./frames/server": { "development": { - "import": { - "types": "./types/frames/server.d.ts", - "default": "./frames/dist/server.dev.js" - }, - "require": { - "types": "./types-cjs/frames/server.d.cts", - "default": "./frames/dist/server.dev.cjs" - } - }, - "import": { "types": "./types/frames/server.d.ts", - "default": "./frames/dist/server.js" + "default": "./frames/dist/server.dev.js" }, - "require": { - "types": "./types-cjs/frames/server.d.cts", - "default": "./frames/dist/server.cjs" - } + "types": "./types/frames/server.d.ts", + "default": "./frames/dist/server.js" }, "./frames/client": { - "import": { - "types": "./types/frames/client.d.ts", - "default": "./frames/dist/client.js" - }, - "require": { - "types": "./types-cjs/frames/client.d.cts", - "default": "./frames/dist/client.cjs" - } + "types": "./types/frames/client.d.ts", + "default": "./frames/dist/client.js" }, "./types/*": "./types/*" }, @@ -468,14 +216,13 @@ "build:clean": "rimraf dist/", "build:js": "rollup -c", "link": "symlink-dir . node_modules/@solidjs/web", - "types": "npm-run-all -nl types:clean types:copy-jsx types:web types:copy-web types:web-storage types:cjs", - "types:clean": "rimraf types/ types-cjs/ storage/types/ storage/types-cjs/ serialization/types/ serialization/types-cjs/ frames/types/ .types-build/", + "types": "npm-run-all -nl types:clean types:copy-jsx types:web types:copy-web types:web-storage", + "types:clean": "rimraf types/ storage/types/ serialization/types/ frames/types/ .types-build/", "jsx-sync": "node ./scripts/jsx-sync.mjs --compile --element \"SolidElement | Node | ArrayElement\" --import \"import type { Element as SolidElement } from \\\"solid-js\\\";\"", "types:copy-jsx": "pnpm run jsx-sync", "types:web": "tsc --project ./tsconfig.build.json", "types:copy-web": "node ./scripts/copy-types.mjs", "types:web-storage": "tsc --project ./storage/tsconfig.build.json", - "types:cjs": "node ../../scripts/sync-dual-types.mjs ./types ./types-cjs ./storage/types ./storage/types-cjs ./serialization/types ./serialization/types-cjs", "test": "vitest run && vitest run --config vite.config.server.mjs && vitest run --config vite.config.hydrate.mjs", "test:server": "vitest run --config vite.config.server.mjs", "coverage": "vitest run --coverage", diff --git a/packages/web/rollup.config.js b/packages/web/rollup.config.js index 4d0637158..6272725c1 100644 --- a/packages/web/rollup.config.js +++ b/packages/web/rollup.config.js @@ -126,16 +126,7 @@ const assertFramesClientTransport = { export default [ { input: "src/index.ts", - output: [ - { - file: "dist/web.cjs", - format: "cjs" - }, - { - file: "dist/web.js", - format: "es" - } - ], + output: { file: "dist/web.js", format: "es" }, external: ["solid-js"], plugins: [replaceDev(false)].concat(plugins) }, @@ -146,16 +137,7 @@ export default [ // folds out. The only client entry with wiring — frames, server-functions // and storage have none and fall through to prod under `observe`. input: "src/index.ts", - output: [ - { - file: "dist/web.observe.cjs", - format: "cjs" - }, - { - file: "dist/web.observe.js", - format: "es" - } - ], + output: { file: "dist/web.observe.js", format: "es" }, external: ["solid-js"], plugins: [replaceFlags(false, true)].concat(plugins) }, @@ -170,16 +152,7 @@ export default [ // behaviorally by test/server/dist-server-artifact.spec.tsx (a string // scan can't catch this: the folding erases the marker either way). input: "src/index.server.ts", - output: [ - { - file: "dist/server.cjs", - format: "cjs" - }, - { - file: "dist/server.js", - format: "es" - } - ], + output: { file: "dist/server.js", format: "es" }, external: ["solid-js", "stream", "seroval", "seroval-plugins/web"], plugins: [replaceDev(false)].concat(plugins) }, @@ -193,63 +166,25 @@ export default [ // Guarded by test/server/dist-server-dev-artifact.spec.tsx: the dev // artifact must THROW on a late header write where prod reports and drops. input: "src/index.server.ts", - output: [ - { - file: "dist/server.dev.cjs", - format: "cjs" - }, - { - file: "dist/server.dev.js", - format: "es" - } - ], + output: { file: "dist/server.dev.js", format: "es" }, external: ["solid-js", "stream", "seroval", "seroval-plugins/web"], plugins: [replaceDev(true)].concat(plugins) }, { input: "src/index.ts", - output: [ - { - file: "dist/web.dev.cjs", - format: "cjs" - }, - { - file: "dist/web.dev.js", - format: "es" - } - ], + output: { file: "dist/web.dev.js", format: "es" }, external: ["solid-js"], plugins: [replaceDev(true)].concat(plugins) }, { input: "storage/src/index.ts", - output: [ - { - file: "storage/dist/storage.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "storage/dist/storage.js", - format: "es" - } - ], + output: { file: "storage/dist/storage.js", format: "es" }, external: ["@solidjs/web"], plugins }, { input: "serialization/src/serializer.ts", - output: [ - { - file: "serialization/dist/serialization.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "serialization/dist/serialization.js", - format: "es" - } - ], + output: { file: "serialization/dist/serialization.js", format: "es" }, external: ["seroval", "seroval-plugins/web"], plugins }, @@ -259,33 +194,13 @@ export default [ // never rides into a browser that only reads. The full entry above // still carries everything (its serializer.js re-exports this module). input: "serialization/src/serializer-decode.ts", - output: [ - { - file: "serialization/dist/decode.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "serialization/dist/decode.js", - format: "es" - } - ], + output: { file: "serialization/dist/decode.js", format: "es" }, external: ["seroval", "seroval-plugins/web"], plugins }, { input: "server-functions/src/client.ts", - output: [ - { - file: "server-functions/dist/client.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "server-functions/dist/client.js", - format: "es" - } - ], + output: { file: "server-functions/dist/client.js", format: "es" }, external: ["seroval", "seroval-plugins/web"], plugins }, @@ -295,17 +210,7 @@ export default [ // externalizeSharedClient), so the dist carries only enableRichArguments // and pulls the serializer write half through the shared instance. input: "server-functions/src/rich-args.ts", - output: [ - { - file: "server-functions/dist/rich-args.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "server-functions/dist/rich-args.js", - format: "es" - } - ], + output: { file: "server-functions/dist/rich-args.js", format: "es" }, external: ["@solidjs/web/server-functions/client", "seroval", "seroval-plugins/web"], plugins: [externalizeSharedClient].concat(plugins) }, @@ -315,17 +220,7 @@ export default [ // become a generic Error) and its dev-only diagnostic bodies. This is // the default resolution — plain node, production bundles. input: "server-functions/src/server.ts", - output: [ - { - file: "server-functions/dist/server.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "server-functions/dist/server.js", - format: "es" - } - ], + output: { file: "server-functions/dist/server.js", format: "es" }, external: ["solid-js", "seroval", "seroval-plugins/web"], plugins: [replaceDev(false)].concat(plugins) }, @@ -335,17 +230,7 @@ export default [ // and the handler's diagnostic bodies for DX and the dev toolbar — // mirroring the frames client's dev/prod split. input: "server-functions/src/server.ts", - output: [ - { - file: "server-functions/dist/server.dev.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "server-functions/dist/server.dev.js", - format: "es" - } - ], + output: { file: "server-functions/dist/server.dev.js", format: "es" }, external: ["solid-js", "seroval", "seroval-plugins/web"], plugins: [replaceDev(true)].concat(plugins) }, @@ -365,17 +250,7 @@ export default [ // The server half bundles the frame sink and its SSR pipeline. { input: "frames/src/client.ts", - output: [ - { - file: "frames/dist/client.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "frames/dist/client.js", - format: "es" - } - ], + output: { file: "frames/dist/client.js", format: "es" }, external: [ "solid-js", "@solidjs/web", @@ -398,17 +273,7 @@ export default [ // Dev build (`development` export condition): keeps the frame runtime's // `_SOLID_DEV_` diagnostics for marker-corruption / CDN-strip debugging. input: "frames/src/client.ts", - output: [ - { - file: "frames/dist/client.dev.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "frames/dist/client.dev.js", - format: "es" - } - ], + output: { file: "frames/dist/client.dev.js", format: "es" }, external: [ "solid-js", "@solidjs/web", @@ -427,17 +292,7 @@ export default [ // without the replace babel folds the truthy literal into the dev branch // — same build-mode bug as #2982, dev-only noise shipped in prod here. input: "frames/src/server.ts", - output: [ - { - file: "frames/dist/server.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "frames/dist/server.js", - format: "es" - } - ], + output: { file: "frames/dist/server.js", format: "es" }, external: ["solid-js", "stream", "seroval", "seroval-plugins/web"], plugins: [replaceDev(false)].concat(plugins) }, @@ -447,17 +302,7 @@ export default [ // bundled SSR pipeline's `_SOLID_DEV_` gates live in dev SSR, matching the // main dist/server.dev entry above. input: "frames/src/server.ts", - output: [ - { - file: "frames/dist/server.dev.cjs", - format: "cjs", - exports: "auto" - }, - { - file: "frames/dist/server.dev.js", - format: "es" - } - ], + output: { file: "frames/dist/server.dev.js", format: "es" }, external: ["solid-js", "stream", "seroval", "seroval-plugins/web"], plugins: [replaceDev(true)].concat(plugins) } diff --git a/packages/web/serialization/decode/package.json b/packages/web/serialization/decode/package.json index 8edecccab..3755f286e 100644 --- a/packages/web/serialization/decode/package.json +++ b/packages/web/serialization/decode/package.json @@ -1,20 +1,14 @@ { "name": "@solidjs/web/serialization/decode", - "main": "../dist/decode.cjs", + "main": "../dist/decode.js", "module": "../dist/decode.js", "types": "../types/serializer-decode.d.ts", "type": "module", "sideEffects": false, "exports": { ".": { - "import": { - "types": "../types/serializer-decode.d.ts", - "default": "../dist/decode.js" - }, - "require": { - "types": "../types-cjs/serializer-decode.d.cts", - "default": "../dist/decode.cjs" - } + "types": "../types/serializer-decode.d.ts", + "default": "../dist/decode.js" } } } diff --git a/packages/web/serialization/package.json b/packages/web/serialization/package.json index 570d0be15..52d5eff31 100644 --- a/packages/web/serialization/package.json +++ b/packages/web/serialization/package.json @@ -1,20 +1,14 @@ { "name": "@solidjs/web/serialization", - "main": "./dist/serialization.cjs", + "main": "./dist/serialization.js", "module": "./dist/serialization.js", "types": "./types/index.d.ts", "type": "module", "sideEffects": false, "exports": { ".": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/serialization.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/serialization.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/serialization.js" } } } diff --git a/packages/web/server-functions/package.json b/packages/web/server-functions/package.json index 9d6730bd1..e16769650 100644 --- a/packages/web/server-functions/package.json +++ b/packages/web/server-functions/package.json @@ -1,6 +1,6 @@ { "name": "@solidjs/web/server-functions", - "main": "./dist/server.cjs", + "main": "./dist/server.js", "module": "./dist/server.js", "types": "../types/server-functions/server.d.ts", "type": "module", @@ -8,33 +8,15 @@ "exports": { ".": { "browser": { - "import": { - "types": "../types/server-functions/client.d.ts", - "default": "./dist/client.js" - }, - "require": { - "types": "../types-cjs/server-functions/client.d.cts", - "default": "./dist/client.cjs" - } + "types": "../types/server-functions/client.d.ts", + "default": "./dist/client.js" }, "development": { - "import": { - "types": "../types/server-functions/server.d.ts", - "default": "./dist/server.dev.js" - }, - "require": { - "types": "../types-cjs/server-functions/server.d.cts", - "default": "./dist/server.dev.cjs" - } - }, - "import": { "types": "../types/server-functions/server.d.ts", - "default": "./dist/server.js" + "default": "./dist/server.dev.js" }, - "require": { - "types": "../types-cjs/server-functions/server.d.cts", - "default": "./dist/server.cjs" - } + "types": "../types/server-functions/server.d.ts", + "default": "./dist/server.js" } } } diff --git a/packages/web/server-functions/rich-args/package.json b/packages/web/server-functions/rich-args/package.json index 02027f263..146a29412 100644 --- a/packages/web/server-functions/rich-args/package.json +++ b/packages/web/server-functions/rich-args/package.json @@ -1,20 +1,14 @@ { "name": "@solidjs/web/server-functions/rich-args", - "main": "../dist/rich-args.cjs", + "main": "../dist/rich-args.js", "module": "../dist/rich-args.js", "types": "../../types/server-functions/rich-args.d.ts", "type": "module", "sideEffects": false, "exports": { ".": { - "import": { - "types": "../../types/server-functions/rich-args.d.ts", - "default": "../dist/rich-args.js" - }, - "require": { - "types": "../../types-cjs/server-functions/rich-args.d.cts", - "default": "../dist/rich-args.cjs" - } + "types": "../../types/server-functions/rich-args.d.ts", + "default": "../dist/rich-args.js" } } } diff --git a/packages/web/storage/package.json b/packages/web/storage/package.json index 81b972961..0aaa3c526 100644 --- a/packages/web/storage/package.json +++ b/packages/web/storage/package.json @@ -1,20 +1,14 @@ { "name": "@solidjs/web/storage", - "main": "./dist/storage.cjs", + "main": "./dist/storage.js", "module": "./dist/storage.js", "types": "./types/index.d.ts", "type": "module", "sideEffects": false, "exports": { ".": { - "import": { - "types": "./types/index.d.ts", - "default": "./dist/storage.js" - }, - "require": { - "types": "./types-cjs/index.d.cts", - "default": "./dist/storage.cjs" - } + "types": "./types/index.d.ts", + "default": "./dist/storage.js" } } } diff --git a/packages/web/test/server/exports-server-conditions.spec.tsx b/packages/web/test/server/exports-server-conditions.spec.tsx index 845e7c5dc..25974c88a 100644 --- a/packages/web/test/server/exports-server-conditions.spec.tsx +++ b/packages/web/test/server/exports-server-conditions.spec.tsx @@ -89,20 +89,65 @@ describe("export conditions: dev/prod artifact pairing", () => { }); }); - test("CJS `require` pairs solid-js's server.dev.cjs with signals' node.dev.cjs", () => { - // The web server bundle externalizes solid-js, and solid-js's server - // bundle externalizes @solidjs/signals, so a CJS host resolves each in - // turn. The dev server artifact is only honest if the signals it requires - // is dev too — its `DEV` export is signals' object — so the `require` - // branch of signals needs its own `development` entry, and this pins - // both hops flipping together. + // A CJS host resolves through `require`, and there is no `require` branch + // anywhere in these exports maps: every package ships ESM only and relies on + // Node >= 22.12 loading ESM through `require()`. So the `require` walk must + // land on exactly the files `import` lands on, hop by hop — the web server + // bundle externalizes solid-js, and solid-js's server bundle externalizes + // @solidjs/signals, so the host resolves each in turn and a tier mismatch + // at any hop is a mixed build (a dev solid-js whose `DEV` export is signals' + // prod `undefined`, say). + function requireHops(conditions: string[]): string[] { const script = `const { createRequire } = require("node:module"); ` + `const r = createRequire(process.cwd() + "/"); ` + + `const web = r.resolve("@solidjs/web"); ` + `const solid = r.resolve("solid-js"); ` + `const signals = createRequire(solid).resolve("@solidjs/signals"); ` + - `const web = r.resolve("@solidjs/web"); ` + `process.stdout.write(JSON.stringify([web, solid, signals].map(p => p.replace(/^.*\\/packages\\//, ""))));`; + return JSON.parse( + execFileSync(process.execPath, [...conditions.map(c => `--conditions=${c}`), "-e", script], { + cwd: process.cwd(), + encoding: "utf8" + }) + ); + } + + test("`require` walks web → solid-js → signals onto the same ESM files as `import`, per tier", () => { + expect(requireHops([])).toEqual([ + "web/dist/server.js", + "solid/dist/server.js", + "signals/dist/prod/index.js" + ]); + expect(requireHops(["development"])).toEqual([ + "web/dist/server.dev.js", + "solid/dist/server.dev.js", + "signals/dist/dev.js" + ]); + expect(requireHops(["observe"])).toEqual([ + // web's server has no wiring yet, so it has no observe artifact. + "web/dist/server.js", + "solid/dist/server.observe.js", + "signals/dist/observe/index.js" + ]); + }); + + test("a CJS host actually loads the ESM server entries through require()", () => { + // Resolution agreeing is necessary, not sufficient: `require(esm)` throws + // ERR_REQUIRE_ASYNC_MODULE if any module in the graph has a top-level + // await. Load the whole chain the way a CJS server would and check the + // instance is shared with `import` — the same signals object on both + // sides, or a CJS host with one ESM dependency would run two cores. + const script = + `const web = require("@solidjs/web"); ` + + `const solid = require("solid-js"); ` + + `import("solid-js").then(esm => { ` + + ` process.stdout.write(JSON.stringify({ ` + + ` renders: typeof web.renderToString === "function", ` + + ` sameSolid: esm.createSignal === solid.createSignal, ` + + ` devIsDefined: solid.DEV !== undefined ` + + ` })); ` + + `});`; const run = (conditions: string[]) => JSON.parse( execFileSync( @@ -111,16 +156,8 @@ describe("export conditions: dev/prod artifact pairing", () => { { cwd: process.cwd(), encoding: "utf8" } ) ); - expect(run([])).toEqual([ - "web/dist/server.cjs", - "solid/dist/server.cjs", - "signals/dist/node.cjs" - ]); - expect(run(["development"])).toEqual([ - "web/dist/server.dev.cjs", - "solid/dist/server.dev.cjs", - "signals/dist/node.dev.cjs" - ]); + expect(run([])).toEqual({ renders: true, sameSolid: true, devIsDefined: false }); + expect(run(["development"])).toEqual({ renders: true, sameSolid: true, devIsDefined: true }); }); test("`observe` selects the observe artifacts where wiring exists and falls through to prod elsewhere", () => { @@ -157,23 +194,6 @@ describe("export conditions: dev/prod artifact pairing", () => { expect(resolveAll(["observe", "development"])).toEqual(resolveAll(["development"])); }); - test("CJS `require` under `observe` pairs solid-js with signals' node.observe.cjs", () => { - const script = - `const { createRequire } = require("node:module"); ` + - `const r = createRequire(process.cwd() + "/"); ` + - `const solid = r.resolve("solid-js"); ` + - `const signals = createRequire(solid).resolve("@solidjs/signals"); ` + - `process.stdout.write(JSON.stringify([solid, signals].map(p => p.replace(/^.*\\/packages\\//, ""))));`; - expect( - JSON.parse( - execFileSync(process.execPath, ["--conditions=observe", "-e", script], { - cwd: process.cwd(), - encoding: "utf8" - }) - ) - ).toEqual(["solid/dist/server.observe.cjs", "signals/dist/node.observe.cjs"]); - }); - test("`solid-js/attribution` hands the engine tier to `@solidjs/signals/attribution`", () => { // solid-js's entry is one tier-less re-export; the engine it reaches is // decided at the signals hop by the SAME conditions that picked the @@ -192,17 +212,17 @@ describe("export conditions: dev/prod artifact pairing", () => { { cwd: process.cwd(), encoding: "utf8" } ) ); - // `createRequire` resolves the `require` branch, so the signals half here - // pins the CJS engine artifacts; the ESM half is pinned by signals' own - // dist test. - expect(run([])).toEqual(["solid/dist/attribution.js", "signals/dist/node.attribution.cjs"]); + // The signals hop goes through `createRequire` on purpose: with no + // `require` branch in the exports map it must reach the same ESM engine + // an `import` would, per tier. + expect(run([])).toEqual(["solid/dist/attribution.js", "signals/dist/prod/attribution.js"]); expect(run(["observe"])).toEqual([ "solid/dist/attribution.js", - "signals/dist/node.observe.attribution.cjs" + "signals/dist/observe/attribution.js" ]); expect(run(["development"])).toEqual([ "solid/dist/attribution.js", - "signals/dist/node.dev.attribution.cjs" + "signals/dist/dev.attribution.js" ]); expect(run(["observe", "development"])).toEqual(run(["development"])); }); diff --git a/scripts/sync-dual-types.mjs b/scripts/sync-dual-types.mjs deleted file mode 100644 index 1ed0ff04d..000000000 --- a/scripts/sync-dual-types.mjs +++ /dev/null @@ -1,54 +0,0 @@ -import { cpSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs"; -import { basename, dirname, resolve } from "node:path"; - -const args = process.argv.slice(2); - -if (args.length === 0 || args.length % 2 !== 0) { - console.error("Usage: node sync-dual-types.mjs [ ...]"); - process.exit(1); -} - -for (let i = 0; i < args.length; i += 2) { - const src = resolve(args[i]); - const dest = resolve(args[i + 1]); - - if (!statSync(src).isDirectory()) { - console.error(`Source is not a directory: ${src}`); - process.exit(1); - } - - // Work around Node 22.6–22.x fs.cpSync false positives when dest's path extends src's - // name as a string prefix (e.g. types -> types-cjs). See nodejs/node#54285. - const staging = resolve(dirname(dest), ".dual-types-staging", basename(dest)); - - rmSync(staging, { recursive: true, force: true }); - rmSync(dest, { recursive: true, force: true }); - mkdirSync(dirname(staging), { recursive: true }); - cpSync(src, staging, { recursive: true }); - rewriteTreeToCjsDeclarations(staging); - writeFileSync( - resolve(staging, "package.json"), - JSON.stringify({ type: "commonjs" }, null, 2) + "\n" - ); - mkdirSync(dirname(dest), { recursive: true }); - renameSync(staging, dest); - rmSync(dirname(staging), { recursive: true, force: true }); -} - -function rewriteTreeToCjsDeclarations(root) { - for (const entry of readdirSync(root, { withFileTypes: true })) { - const fullPath = resolve(root, entry.name); - if (entry.isDirectory()) { - rewriteTreeToCjsDeclarations(fullPath); - continue; - } - - if (!entry.name.endsWith(".d.ts")) continue; - - const source = readFileSync(fullPath, "utf8") - .replace(/((?:\.{1,2}\/)[^"'`]+)\.d\.ts(["'])/g, "$1.d.cts$2") - .replace(/((?:\.{1,2}\/)[^"'`]+)\.js(["'])/g, "$1.cjs$2"); - writeFileSync(fullPath, source); - renameSync(fullPath, fullPath.replace(/\.d\.ts$/, ".d.cts")); - } -} diff --git a/turbo.json b/turbo.json index 9831dcf6d..11e48b612 100644 --- a/turbo.json +++ b/turbo.json @@ -10,7 +10,7 @@ "outputs": ["dist/**"] }, "@solidjs/signals#types": { - "outputs": ["dist/types/**", "dist/types-cjs/**"] + "outputs": ["dist/types/**"] }, "@solidjs/diagnostics#build": { "dependsOn": ["@solidjs/signals#build", "@solidjs/signals#types"], @@ -22,7 +22,7 @@ }, "solid-js#types": { "dependsOn": ["@solidjs/signals#types"], - "outputs": ["types/**", "types-cjs/**", "**/types/**", "**/types-cjs/**"] + "outputs": ["types/**", "**/types/**"] }, "@solidjs/web#link": { "dependsOn": ["solid-js#build"], @@ -45,25 +45,11 @@ }, "types": { "dependsOn": ["^types"], - "outputs": [ - "types/**", - "types-cjs/**", - "dist/types-cjs/**", - "**/types/**", - "**/types-cjs/**", - "**/dist/types-cjs/**" - ] + "outputs": ["types/**", "**/types/**"] }, "@solidjs/web#types": { "dependsOn": ["^types"], - "outputs": [ - "types/**", - "types-cjs/**", - "**/types/**", - "**/types-cjs/**", - "jsx/jsx.d.ts", - "jsx/jsx-properties.d.ts" - ] + "outputs": ["types/**", "**/types/**", "jsx/jsx.d.ts", "jsx/jsx-properties.d.ts"] }, "coverage": { "outputs": ["coverage/**"]