@savvy-web/bundler 0.4.1 → 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 +2 -2
- package/index.d.ts +11 -1
- package/package.json +3 -2
- package/public/ecma.json +1 -1
- package/run.js +57 -7
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.
|
|
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.
|
|
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": "
|
|
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
|
|
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
|
|
138
|
-
const src = entries[
|
|
139
|
-
if (src === void 0) throw new Error(`overrides: export path "${exportPath}" (entry "${
|
|
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(
|
|
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
|
|
256
|
+
exportPaths,
|
|
207
257
|
outMetaDir: join(cwd, "dist", "prod", metaGroupId, "meta"),
|
|
208
258
|
localPaths: [],
|
|
209
259
|
tsdoc: norm.tsdoc
|