@savvy-web/bundler 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -200,7 +200,7 @@ const config = defineBuild({
200
200
  });
201
201
  ```
202
202
 
203
- Each override carries the same `format`, `bundle`, `externals`, `bundleNodeModules`, `bundledPackages` and `dtsExternals` fields as the base config. An override does not inherit the base `externals` — list what that partition needs. The build errors if an override names an export path the package does not declare.
203
+ Each override carries the same `format`, `bundle`, `externals`, `bundleNodeModules`, `bundledPackages` and `dtsExternals` fields as the base config, plus three partition-only fields: `platform` (the JS-pass target, `"node"` by default or `"browser"` for a client runtime), `css` (forwarded to tsdown's `css` option to enable `@tsdown/css`) and `outSubdir` (builds the partition into an isolated `<group>/pkg/<subdir>/` sub-package, for which exactly one export path may be pinned). An override does not inherit the base `externals` — list what that partition needs. The build errors if an override names an export path the package does not declare. These partition fields are what [`@savvy-web/rspress-builder`](https://www.npmjs.com/package/@savvy-web/rspress-builder) composes to build an RSPress plugin's browser runtime bundle.
204
204
 
205
205
  ## Loose files
206
206
 
@@ -277,7 +277,7 @@ const config = defineBuild({
277
277
  - **JSX, config-first** — JSX transform is inherited from `tsconfig.json` and overridable via the `jsx` field, feeding both the dts tsconfig and the tsdown transform.
278
278
  - **Dual-format output** — esm-only by default; set `format` to `["esm", "cjs"]` for a require-able CJS output with default-export interop, `.d.cts` declarations and dual `import`/`require` export conditions.
279
279
  - **Dependency bundling** — declared dependencies stay external by default; `bundle`, `bundleNodeModules`, `bundledPackages` and `dtsExternals` force-inline specific packages or all node_modules into the output, inline select declarations into the `.d.ts` or hold a package out of the declaration bundle when its types cannot be inlined.
280
- - **Per-entry overrides** — `overrides` pins a subset of export entries to their own format and bundling, so one entry can ship dual-format CJS in an otherwise ESM-only package without changing the rest.
280
+ - **Per-entry overrides** — `overrides` pins a subset of export entries to their own format and bundling, so one entry can ship dual-format CJS in an otherwise ESM-only package without changing the rest; partition-only `platform`, `css` and `outSubdir` fields also let an entry build for the browser with CSS modules into its own sub-package.
281
281
  - **Loose files** — `looseFiles` emits standalone bundled files at literal output paths outside the exports/declaration/api-model graph, with the format inferred from the key extension; pair with `bundleNodeModules` for self-contained pnpm config-dependency pnpmfiles.
282
282
  - **Readable prod output** — prod output is unminified by default to keep stack traces legible and pass security scanners; `minify` opts back in.
283
283
  - **Default manifest stripping** — the published `package.json` drops build- and dev-only fields automatically; a custom `transform` replaces the default and can re-apply it via `defaultManifestTransform`.
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BuildFormat, BuildTargetGroupsOptions, ExeConfig, GenerateMetaOptions, Json, JsxConfig, LooseFiles, MetaOptions, MetaResult, PublishTargets, RenderedOutput, RunExeBuildOptions, TargetGroupRef, TargetResolution, TsconfigJsx, defaultManifestTransform } from "@savvy-web/tsdown-plugins";
1
+ import { BuildFormat, BuildPlatform, BuildTargetGroupsOptions, CssOptions, ExeConfig, GenerateMetaOptions, Json, JsxConfig, LooseFiles, MetaOptions, MetaResult, PublishTargets, RenderedOutput, RunExeBuildOptions, TargetGroupRef, TargetResolution, TsconfigJsx, defaultManifestTransform } from "@savvy-web/tsdown-plugins";
2
2
 
3
3
  //#region src/config.d.ts
4
4
  interface BuildEntryOverride {
@@ -10,6 +10,16 @@ interface BuildEntryOverride {
10
10
  readonly bundleNodeModules?: boolean | undefined;
11
11
  readonly bundledPackages?: ReadonlyArray<string> | undefined;
12
12
  readonly dtsExternals?: ReadonlyArray<string> | undefined;
13
+ /** JS-pass platform for this partition (default "node"). Use "browser" for an RSPress runtime. */
14
+ readonly platform?: BuildPlatform | undefined;
15
+ /** CSS handling forwarded to tsdown's `css` option (JS pass only). Enables `@tsdown/css`. */
16
+ readonly css?: CssOptions | undefined;
17
+ /**
18
+ * Build this entry's partition into a `<group>/pkg/<outSubdir>/` subdir as an isolated sub-package
19
+ * (e.g. an RSPress `./runtime`). The export's built path becomes `./<outSubdir>/index.{js,d.ts}`.
20
+ * Exactly ONE export path may be pinned per `outSubdir` override.
21
+ */
22
+ readonly outSubdir?: string | undefined;
13
23
  }
14
24
  interface OutputConfig {
15
25
  readonly console?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/bundler",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "Zero-config tsdown-based bundler for Silk Suite TypeScript packages",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/bundler",
@@ -25,10 +25,11 @@
25
25
  "import": "./index.js"
26
26
  },
27
27
  "./ecma.json": "./public/ecma.json",
28
+ "./tsconfig/ecma.json": "./public/ecma.json",
28
29
  "./package.json": "./package.json"
29
30
  },
30
31
  "dependencies": {
31
- "@savvy-web/tsdown-plugins": "0.4.2",
32
+ "@savvy-web/tsdown-plugins": "0.5.0",
32
33
  "@tsdown/exe": "^0.22.1",
33
34
  "tsdown": "^0.22.2"
34
35
  },
package/public/ecma.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "sourceMap": false,
26
26
  "strict": true,
27
27
  "strictNullChecks": true,
28
- "target": "es2023",
28
+ "target": "es2025",
29
29
  "tsBuildInfoFile": "${configDir}/dist/.tsbuildinfo.lib",
30
30
  "typeRoots": ["${configDir}/node_modules/@types", "${configDir}/types"],
31
31
  "verbatimModuleSyntax": true,
package/run.js CHANGED
@@ -35,6 +35,43 @@ function deriveExportPaths(entries, exportsMap) {
35
35
  for (const [entryName, src] of Object.entries(entries)) out[entryName] = sourceToKey.get(src) ?? (entryName === "index" ? "." : `./${entryName}`);
36
36
  return out;
37
37
  }
38
+ /**
39
+ * Fast-fail validation for `outSubdir` overrides, run on EVERY target path. The dev/prod override-partition
40
+ * loop already validates these (and all other overrides) before building, but `--target meta` returns early
41
+ * (before that loop) and remaps meta dts basenames via `applySubdirMetaEntries` — which assumes validated
42
+ * input. Without this guard, a malformed `outSubdir` override (more than one entry, a non-canonical export
43
+ * path, or an export path that is not a real build entry) would silently remap a wrong/nonexistent key on the
44
+ * meta path. Mirrors the override loop's conditions and messages verbatim so every target path fast-fails
45
+ * identically. No-op when there are no overrides or none set `outSubdir`.
46
+ */
47
+ function validateSubdirOverrides(overrides, entries, packageName) {
48
+ if (overrides === void 0) return;
49
+ for (const ov of overrides) {
50
+ if (ov.outSubdir === void 0) continue;
51
+ if (ov.entries.length !== 1) throw new Error(`overrides: outSubdir "${ov.outSubdir}" must pin exactly one export path (got ${ov.entries.length})`);
52
+ const exportPath = ov.entries[0];
53
+ if (exportPath !== "." && !exportPath.startsWith("./")) throw new Error(`overrides: entry "${exportPath}" must be a canonical export path — use "." for the root or a "./"-prefixed subpath (e.g. "./changesets/markdownlint")`);
54
+ const flatName = createEntryName(exportPath, false);
55
+ if (entries[flatName] === void 0) throw new Error(`overrides: export path "${exportPath}" (entry "${flatName}") is not a build entry of ${packageName}`);
56
+ }
57
+ }
58
+ /**
59
+ * For each `outSubdir` override, point its meta entry at the isolated sub-package barrel: the dts lives
60
+ * at `<subdir>/index.d.ts` (not `<flatName>.d.ts`). Keyed by the stable flattened entry name so it
61
+ * overwrites the default `dtsBasenames[flatName] = flatName` set from the full entry map. No-op when no
62
+ * override sets `outSubdir`.
63
+ */
64
+ function applySubdirMetaEntries(overrides, dtsBasenames, exportPaths) {
65
+ if (overrides === void 0) return;
66
+ for (const ov of overrides) {
67
+ if (ov.outSubdir === void 0) continue;
68
+ const exportPath = ov.entries[0];
69
+ if (exportPath === void 0) continue;
70
+ const flatName = createEntryName(exportPath, false);
71
+ dtsBasenames[flatName] = `${ov.outSubdir}/index`;
72
+ exportPaths[flatName] = exportPath;
73
+ }
74
+ }
38
75
  /** Run a build from a normalized config. Pure orchestration; all IO injectable. */
39
76
  async function runBuild(config, options) {
40
77
  const { target } = parseArgs(options.argv);
@@ -76,6 +113,7 @@ async function runBuild(config, options) {
76
113
  ...config.meta !== void 0 && config.meta !== false ? { meta: config.meta } : {},
77
114
  ...config.looseFiles !== void 0 ? { looseFiles: config.looseFiles } : {}
78
115
  })).pipe(Effect.provide(ConfigValidatorLive)));
116
+ validateSubdirOverrides(config.overrides, entries, packageName);
79
117
  if (target === "meta") {
80
118
  if (config.meta === false) {
81
119
  (options.writeOutput ?? ((o) => process.stdout.write(`${o.content}\n`)))({
@@ -88,13 +126,15 @@ async function runBuild(config, options) {
88
126
  const norm = normalizeMetaOptions(config.meta ?? {});
89
127
  const dtsBasenames = {};
90
128
  for (const name of Object.keys(entries)) dtsBasenames[name] = name;
129
+ const exportPaths = deriveExportPaths(entries, exportsMap);
130
+ applySubdirMetaEntries(config.overrides, dtsBasenames, exportPaths);
91
131
  await runGenerateMeta({
92
132
  cwd,
93
133
  packageName,
94
134
  tsconfigPath,
95
135
  dtsDir: join(cwd, "dist", "dev", "pkg"),
96
136
  entries: dtsBasenames,
97
- exportPaths: deriveExportPaths(entries, exportsMap),
137
+ exportPaths,
98
138
  outMetaDir: join(cwd, "dist", "dev", "meta"),
99
139
  localPaths: norm.localPaths,
100
140
  tsdoc: norm.tsdoc
@@ -124,6 +164,7 @@ async function runBuild(config, options) {
124
164
  let overridePartitions = [];
125
165
  let baseEntries = entries;
126
166
  let dualExports;
167
+ const subdirExports = /* @__PURE__ */ new Set();
127
168
  if (config.overrides) {
128
169
  const partitions = [];
129
170
  const overriddenEntryNames = /* @__PURE__ */ new Set();
@@ -131,14 +172,17 @@ async function runBuild(config, options) {
131
172
  const dualExportKeys = /* @__PURE__ */ new Set();
132
173
  const exportPathByEntry = deriveExportPaths(entries, exportsMap);
133
174
  for (const ov of config.overrides) {
175
+ if (ov.outSubdir !== void 0 && ov.entries.length !== 1) throw new Error(`overrides: outSubdir "${ov.outSubdir}" must pin exactly one export path (got ${ov.entries.length})`);
134
176
  const partEntry = {};
135
177
  for (const exportPath of ov.entries) {
136
178
  if (exportPath !== "." && !exportPath.startsWith("./")) throw new Error(`overrides: entry "${exportPath}" must be a canonical export path — use "." for the root or a "./"-prefixed subpath (e.g. "./changesets/markdownlint")`);
137
- const entryName = createEntryName(exportPath, false);
138
- const src = entries[entryName];
139
- if (src === void 0) throw new Error(`overrides: export path "${exportPath}" (entry "${entryName}") is not a build entry of ${packageName}`);
179
+ const flatName = createEntryName(exportPath, false);
180
+ const src = entries[flatName];
181
+ if (src === void 0) throw new Error(`overrides: export path "${exportPath}" (entry "${flatName}") is not a build entry of ${packageName}`);
182
+ const entryName = ov.outSubdir !== void 0 ? "index" : flatName;
140
183
  partEntry[entryName] = src;
141
- overriddenEntryNames.add(entryName);
184
+ overriddenEntryNames.add(flatName);
185
+ if (ov.outSubdir !== void 0) subdirExports.add(exportPath);
142
186
  if ((ov.format ?? config.format ?? ["esm"]).includes("cjs")) dualExportKeys.add(exportPath);
143
187
  }
144
188
  partitions.push({
@@ -148,7 +192,10 @@ async function runBuild(config, options) {
148
192
  ...ov.bundle !== void 0 ? { bundle: ov.bundle } : {},
149
193
  ...ov.bundleNodeModules !== void 0 ? { bundleNodeModules: ov.bundleNodeModules } : {},
150
194
  ...ov.bundledPackages !== void 0 ? { bundledPackages: ov.bundledPackages } : {},
151
- ...ov.dtsExternals !== void 0 ? { dtsExternals: ov.dtsExternals } : {}
195
+ ...ov.dtsExternals !== void 0 ? { dtsExternals: ov.dtsExternals } : {},
196
+ ...ov.platform !== void 0 ? { platform: ov.platform } : {},
197
+ ...ov.css !== void 0 ? { css: ov.css } : {},
198
+ ...ov.outSubdir !== void 0 ? { outSubdir: ov.outSubdir } : {}
152
199
  });
153
200
  }
154
201
  const onlyBase = {};
@@ -189,6 +236,7 @@ async function runBuild(config, options) {
189
236
  ...config.define !== void 0 ? { define: config.define } : {},
190
237
  ...overridePartitions.length > 0 ? { overrides: overridePartitions } : {},
191
238
  ...dualExports !== void 0 ? { dualExports } : {},
239
+ ...subdirExports.size > 0 ? { subdirExports } : {},
192
240
  ...looseFiles !== void 0 ? { looseFiles } : {}
193
241
  });
194
242
  if (target === "prod" && resolution !== void 0) writeBinding(cwd, resolution);
@@ -197,13 +245,15 @@ async function runBuild(config, options) {
197
245
  const norm = normalizeMetaOptions(config.meta ?? {});
198
246
  const dtsBasenames = {};
199
247
  for (const name of Object.keys(entries)) dtsBasenames[name] = name;
248
+ const exportPaths = deriveExportPaths(entries, exportsMap);
249
+ applySubdirMetaEntries(config.overrides, dtsBasenames, exportPaths);
200
250
  await runGenerateMeta({
201
251
  cwd,
202
252
  packageName,
203
253
  tsconfigPath,
204
254
  dtsDir: join(cwd, "dist", "prod", metaGroupId, "pkg"),
205
255
  entries: dtsBasenames,
206
- exportPaths: deriveExportPaths(entries, exportsMap),
256
+ exportPaths,
207
257
  outMetaDir: join(cwd, "dist", "prod", metaGroupId, "meta"),
208
258
  localPaths: [],
209
259
  tsdoc: norm.tsdoc