@fastkit/plugboy 1.5.0 → 1.6.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
@@ -190,10 +190,12 @@ Set it in `plugboy.project.ts` to apply defaults to every workspace; a workspace
190
190
  value is shallow-merged over the project one, so it only needs to restate the
191
191
  keys it changes.
192
192
 
193
- Plugins may seed defaults here during workspace setup the vanilla-extract
194
- plugin, for instance, owns `splitting` and `fileName` so the emitted file matches
195
- the CSS export plugboy declares. A configured value always wins over a plugin
196
- default, so check the plugin's documentation before overriding a key it manages.
193
+ `splitting` is best left alone. In a workspace with `css: true` entries plugboy
194
+ turns it on and builds the declared stylesheets itself, in dependency order (see
195
+ [Stylesheets of the CSS entries](#stylesheets-of-the-css-entries)); `fileName`
196
+ still names the result. Declaring `splitting` hands the merge back to tsdown as
197
+ written. Plugins may seed other defaults here during workspace setup; a configured
198
+ value always wins over them.
197
199
 
198
200
  #### CSS Optimization
199
201
 
@@ -236,22 +238,40 @@ Both are captured from every stylesheet in the module graph, whether its content
236
238
  were authored or generated by a plugin — vanilla-extract emits its `@layer`
237
239
  statements into a virtual module, and those are covered too.
238
240
 
239
- #### One stylesheet per CSS entry
241
+ #### Stylesheets of the CSS entries
240
242
 
241
243
  Every entry with `css: true` gets a `./<entry>.css` export, and each of those files
242
- is guaranteed to exist and to be usable on its own.
243
-
244
- With several such entries the build emits one stylesheet per output *chunk*
245
- (`css.splitting`), which does not line up: CSS reached from more than one entry is
246
- moved into a shared chunk and emitted under a hashed name that no export points at,
247
- and an entry whose CSS comes only from there gets no stylesheet at all. plugboy
248
- rebuilds each entry's stylesheet from its own CSS plus the CSS of every chunk it
249
- imports, dependencies first, and drops the leftover per-chunk files. Shared CSS is
250
- duplicated into each entry that needs it, which is what makes a single
251
- `./<entry>.css` import complete.
252
-
253
- This is skipped when `css.inject` is on, since the JavaScript then imports the
254
- per-chunk stylesheets by name.
244
+ is guaranteed to exist, to be usable on its own, and to be in **dependency order**:
245
+ a style that another one composes from or imports comes before it. That is what
246
+ lets the rules built on a shared reset, or on the `base` of a vanilla-extract
247
+ `style([base, { }])`, win over it as intended.
248
+
249
+ plugboy builds these files itself. The build emits one stylesheet per output
250
+ *chunk* (`css.splitting`), and plugboy concatenates them in the order the chunks
251
+ load the order Vite uses for `build.cssCodeSplit: false`: each chunk after the
252
+ chunks it imports statically, chunks reached only through `import()` after all of
253
+ those. tsdown's own single-file merge is not used, because it concatenates chunks
254
+ in bundle order, which puts a shared chunk *after* the entries depending on it.
255
+
256
+ - **One CSS entry**: the package's one stylesheet holds the CSS of every chunk,
257
+ including what is loaded through `import()` — nothing loads a per-chunk
258
+ stylesheet at runtime without `css.inject`. It is written under `css.fileName`
259
+ when that is set.
260
+ - **Several**: each entry's stylesheet holds its own CSS and that of every chunk it
261
+ reaches. CSS shared between entries is duplicated into each, so a single
262
+ `./<entry>.css` import is complete.
263
+
264
+ The per-chunk files are removed afterwards, since no export points at them.
265
+
266
+ The guarantee is dependency order, per chunk. It is not the order Vite's dev
267
+ server — and so Storybook — applies, which injects styles one module at a time:
268
+ two rules that do not depend on each other can end up in a different relative
269
+ order. If such a pair targets the same element with the same specificity, make the
270
+ intended winner explicit with `@layer` or by composing one from the other.
271
+
272
+ Nothing is assembled with `css.inject` (the JavaScript then imports the per-chunk
273
+ stylesheets by name, so they stay as emitted), or when `splitting` is declared,
274
+ which leaves the merge to tsdown.
255
275
 
256
276
  ### defineProjectConfig
257
277
 
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { i as getWorkspace, t as generateWorkspace } from "./workspace-Dngfi1p6.mjs";
1
+ import { i as getWorkspace, t as generateWorkspace } from "./workspace-DdhCZjQE.mjs";
2
2
  import { cac } from "cac";
3
3
  //#region package.json
4
- var version = "1.5.0";
4
+ var version = "1.6.0";
5
5
  //#endregion
6
6
  //#region src/cli.ts
7
7
  async function main() {
@@ -320,6 +320,83 @@ declare class Builder {
320
320
  //#region src/workspace/generate.d.ts
321
321
  declare function generateWorkspace(workspaceName?: string, cwd?: string): Promise<void>;
322
322
  //#endregion
323
+ //#region src/workspace/chunk-order.d.ts
324
+ /**
325
+ * The order in which the chunks of a build load their stylesheets.
326
+ *
327
+ * A stylesheet's rules win over an earlier one's at equal specificity, so the
328
+ * order chunks' CSS is concatenated in is part of what the CSS means. What a
329
+ * package composes from — a shared reset, a vanilla-extract `style([base, …])`
330
+ * — has to come before the rules that build on it.
331
+ *
332
+ * The bundle's own order does not give that. Entry chunks come first and the
333
+ * chunks they import follow, so a shared chunk lands *after* the entries that
334
+ * depend on it. `@tsdown/css` concatenates in that order when `css.splitting` is
335
+ * off, and so did everything in plugboy that took `Object.values(bundle)` as the
336
+ * load order.
337
+ *
338
+ * This walks the chunk graph the way Vite does for `build.cssCodeSplit: false`:
339
+ * from each root, static imports first and the chunk itself after them; then the
340
+ * dynamically imported chunks, which load later, in the order they were reached.
341
+ * Every CSS producer in plugboy orders by this, so a package gets the same order
342
+ * whichever of them emitted its styles.
343
+ */
344
+ /** What the ordering needs to know about a chunk. */
345
+ interface ChunkGraphNode {
346
+ /** The chunk's name — for an entry chunk, the entry id plugboy declared. */
347
+ name: string;
348
+ isEntry: boolean;
349
+ /** File names of the chunks this one imports statically. */
350
+ imports: string[];
351
+ /** File names of the chunks this one imports with `import()`. */
352
+ dynamicImports: string[];
353
+ }
354
+ /** Chunk file name -> its node. Iteration order is the bundle's. */
355
+ type ChunkGraph = Map<string, ChunkGraphNode>;
356
+ interface BundleChunkLike {
357
+ type: string;
358
+ fileName: string;
359
+ name?: string;
360
+ isEntry?: boolean;
361
+ imports?: string[];
362
+ dynamicImports?: string[];
363
+ }
364
+ /**
365
+ * Capture the chunk graph of a bundle.
366
+ *
367
+ * Call it from `generateBundle`, before `@tsdown/css` runs: that removes the
368
+ * chunks holding nothing but CSS, together with every import of them, so a
369
+ * graph captured later has lost exactly the edges that matter here.
370
+ */
371
+ declare function captureChunkGraph(bundle: Record<string, BundleChunkLike>): ChunkGraph;
372
+ /**
373
+ * The entry chunks of a graph, in the order the entries were declared.
374
+ *
375
+ * `entryIds` are the names plugboy gives the entries (`.` normalized to the
376
+ * package directory name). Entry chunks it does not name keep the bundle's order,
377
+ * after the named ones.
378
+ */
379
+ declare function entryChunksInDeclaredOrder(graph: ChunkGraph, entryIds?: readonly string[]): string[];
380
+ interface OrderChunksOptions {
381
+ /**
382
+ * Append the chunks no root reaches, in bundle order. Set it when the result
383
+ * stands for the whole package; leave it off when it stands for one entry.
384
+ */
385
+ includeUnreached?: boolean;
386
+ }
387
+ /**
388
+ * Chunk file names in load order, starting from `roots`.
389
+ *
390
+ * Each chunk appears once, after every chunk it imports statically. Chunks
391
+ * reached only through `import()` follow all of those.
392
+ */
393
+ declare function orderChunks(graph: ChunkGraph, roots: readonly string[], options?: OrderChunksOptions): string[];
394
+ /**
395
+ * Every chunk of the package in load order: the entries as declared, then what
396
+ * they import dynamically, then anything left over.
397
+ */
398
+ declare function orderPackageChunks(graph: ChunkGraph, entryIds?: readonly string[]): string[];
399
+ //#endregion
323
400
  //#region src/types/dts.d.ts
324
401
  interface EmitDTSOptions {
325
402
  cwd?: string;
@@ -640,11 +717,14 @@ interface UserWorkspaceConfig extends TSDownSyncOptions {
640
717
  * Shallow-merged over the project configuration, so a workspace only needs to
641
718
  * restate the keys it changes.
642
719
  *
643
- * Plugins may seed defaults here during workspace setup (e.g. the
644
- * vanilla-extract plugin sets `splitting` / `fileName`, which it needs to own
645
- * to keep its CSS pipeline intact). A value declared in the configuration
646
- * always wins over such a default consult the plugin's documentation before
647
- * overriding a key it manages.
720
+ * In a workspace with `css: true` entries, plugboy turns `splitting` on and
721
+ * builds each declared `./<entry>.css` itself, in dependency order;
722
+ * `fileName` names the stylesheet of a single CSS entry. Declaring `splitting`
723
+ * leaves the merge to tsdown as written, which concatenates chunks in bundle
724
+ * order leave it unset unless that is what you want.
725
+ *
726
+ * Plugins may seed other defaults here during workspace setup. A value declared
727
+ * in the configuration always wins over such a default.
648
728
  *
649
729
  * `css.target` defaults to {@link UserWorkspaceConfig.target}.
650
730
  *
@@ -1018,4 +1098,4 @@ interface GetWorkspacePackageJsonResult {
1018
1098
  declare function getWorkspacePackageJson<AllowMissing extends boolean | undefined = false>(searchDir?: string, allowMissing?: AllowMissing): Promise<AllowMissing extends true ? GetWorkspacePackageJsonResult | null : GetWorkspacePackageJsonResult>;
1019
1099
  declare function findWorkspacePackages(dir: string): Promise<GetWorkspacePackageJsonResult[]>;
1020
1100
  //#endregion
1021
- export { BuildedHooks, Builder, DANGLING_DTS_SOURCE_MAP_RE, DTSCompilerFunction, DTSCompilerOption, DTSNormalizer, DTSPreserveTypeSettings, DTSPreserveTypeTarget, DTSSettings, EmitDTSOptions, ExposeEntriesSettings, ExternalOption, FindConfigResult, GetProjectPackageJsonResult, GetWorkspacePackageJsonResult, HookArgs, HookName, HookReturnType, HookTypes, Listable, NoExternalOption, NormalizedDTSPreserveTypeSettings, NormalizedDTSPreserveTypeTarget, NormalizedDTSSettings, OptimizeCSSOptions, PROJECT_REQUIRED_FIELDS, Path, PlugboyProject, PlugboyWorkspace, Plugin, ProjectPackageJson, ProjectScriptsTemplate, ProjectSetupContext, RawExposeEntriesSettings, RawWorkspaceEntries, RawWorkspaceEntry, RawWorkspaceEntryObject, ResolvedHooks, ResolvedOptimizeCSSOptions, ResolvedProjectConfig, ResolvedWorkspaceConfig, TSConfigJSON, TSDOWN_SYNC_OPTIONS, TryGetWorkspace, type TsdownPlugin, UnPromisify, UserHooks, UserPluginOption, UserProjectConfig, UserWorkspaceConfig, WORKSPACE_PACKAGE_SYNC_FIELDS, WORKSPACE_REQUIRED_FIELDS, WorkspaceDirs, WorkspaceEntries, WorkspaceEntry, WorkspaceExport, WorkspaceMeta, WorkspaceObjectExport, WorkspacePackageJson, WorkspaceSetupContext, WorkspaceStubLink, WorkspaceStubLinkType, buildHooks, collectExternalStringPrefixes, copyDirSync, createHooksDefaults, definePlugin, defineProjectConfig, defineWorkspaceConfig, exitHook, exposeEntries, extractProjectPlugins, findConfig, findFile, findProjectPlugin, findWorkspacePackages, generateWorkspace, getDirname, getFilename, getProject, getProjectPackageJson, getWorkspace, getWorkspacePackageJson, isFileNotFoundException, isProjectPackageJson, isPromise, isWorkspacePackageJson, loadProjectConfig, loadWorkspaceConfig, mergeChunkAddons, mergeDTSSettingsList, mergeExternals, mergeNoExternals, normalizeDTSPreserveTypeSettings, normalizeDTSPreserveTypeTarget, normalizeDTSSettings, pathExists, resolveBundledConfigOutputFile, resolveListable, resolveOptimizeCSSOptions, resolveRawExposeEntriesSettings, resolveRawWorkspaceEntries, resolveRawWorkspaceEntry, resolveUserHooks, resolveUserPluginOption, resolveUserProjectConfig, resolveUserWorkspaceConfig, rmrf, stripDanglingDTSSourceMaps, syncWorkspacePackageFields, writeFileAtomic };
1101
+ export { BuildedHooks, Builder, ChunkGraph, ChunkGraphNode, DANGLING_DTS_SOURCE_MAP_RE, DTSCompilerFunction, DTSCompilerOption, DTSNormalizer, DTSPreserveTypeSettings, DTSPreserveTypeTarget, DTSSettings, EmitDTSOptions, ExposeEntriesSettings, ExternalOption, FindConfigResult, GetProjectPackageJsonResult, GetWorkspacePackageJsonResult, HookArgs, HookName, HookReturnType, HookTypes, Listable, NoExternalOption, NormalizedDTSPreserveTypeSettings, NormalizedDTSPreserveTypeTarget, NormalizedDTSSettings, OptimizeCSSOptions, OrderChunksOptions, PROJECT_REQUIRED_FIELDS, Path, PlugboyProject, PlugboyWorkspace, Plugin, ProjectPackageJson, ProjectScriptsTemplate, ProjectSetupContext, RawExposeEntriesSettings, RawWorkspaceEntries, RawWorkspaceEntry, RawWorkspaceEntryObject, ResolvedHooks, ResolvedOptimizeCSSOptions, ResolvedProjectConfig, ResolvedWorkspaceConfig, TSConfigJSON, TSDOWN_SYNC_OPTIONS, TryGetWorkspace, type TsdownPlugin, UnPromisify, UserHooks, UserPluginOption, UserProjectConfig, UserWorkspaceConfig, WORKSPACE_PACKAGE_SYNC_FIELDS, WORKSPACE_REQUIRED_FIELDS, WorkspaceDirs, WorkspaceEntries, WorkspaceEntry, WorkspaceExport, WorkspaceMeta, WorkspaceObjectExport, WorkspacePackageJson, WorkspaceSetupContext, WorkspaceStubLink, WorkspaceStubLinkType, buildHooks, captureChunkGraph, collectExternalStringPrefixes, copyDirSync, createHooksDefaults, definePlugin, defineProjectConfig, defineWorkspaceConfig, entryChunksInDeclaredOrder, exitHook, exposeEntries, extractProjectPlugins, findConfig, findFile, findProjectPlugin, findWorkspacePackages, generateWorkspace, getDirname, getFilename, getProject, getProjectPackageJson, getWorkspace, getWorkspacePackageJson, isFileNotFoundException, isProjectPackageJson, isPromise, isWorkspacePackageJson, loadProjectConfig, loadWorkspaceConfig, mergeChunkAddons, mergeDTSSettingsList, mergeExternals, mergeNoExternals, normalizeDTSPreserveTypeSettings, normalizeDTSPreserveTypeTarget, normalizeDTSSettings, orderChunks, orderPackageChunks, pathExists, resolveBundledConfigOutputFile, resolveListable, resolveOptimizeCSSOptions, resolveRawExposeEntriesSettings, resolveRawWorkspaceEntries, resolveRawWorkspaceEntry, resolveUserHooks, resolveUserPluginOption, resolveUserProjectConfig, resolveUserWorkspaceConfig, rmrf, stripDanglingDTSSourceMaps, syncWorkspacePackageFields, writeFileAtomic };
package/dist/plugboy.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as resolveOptimizeCSSOptions, A as resolveRawExposeEntriesSettings, B as resolveBundledConfigOutputFile, C as findProjectPlugin, D as loadProjectConfig, E as isProjectPackageJson, F as getFilename, G as mergeChunkAddons, H as stripDanglingDTSSourceMaps, I as isFileNotFoundException, J as isPromise, K as mergeExternals, L as pathExists, M as findConfig, N as findFile, O as resolveUserProjectConfig, P as getDirname, Q as WORKSPACE_REQUIRED_FIELDS, R as rmrf, S as extractProjectPlugins, T as defineProjectConfig, U as exitHook, V as DANGLING_DTS_SOURCE_MAP_RE, W as collectExternalStringPrefixes, X as PROJECT_REQUIRED_FIELDS, Y as resolveListable, Z as TSDOWN_SYNC_OPTIONS, _ as loadWorkspaceConfig, a as syncWorkspacePackageFields, b as resolveUserWorkspaceConfig, c as Builder, d as getWorkspacePackageJson, et as mergeDTSSettingsList, f as Path, g as isWorkspacePackageJson, h as defineWorkspaceConfig, i as getWorkspace, it as createHooksDefaults, j as copyDirSync, k as exposeEntries, l as findWorkspacePackages, m as resolveUserHooks, n as PlugboyWorkspace, nt as normalizeDTSPreserveTypeTarget, o as PlugboyProject, p as buildHooks, q as mergeNoExternals, r as WORKSPACE_PACKAGE_SYNC_FIELDS, rt as normalizeDTSSettings, s as getProject, t as generateWorkspace, tt as normalizeDTSPreserveTypeSettings, u as getProjectPackageJson, v as resolveRawWorkspaceEntries, w as resolveUserPluginOption, x as definePlugin, y as resolveRawWorkspaceEntry, z as writeFileAtomic } from "./workspace-Dngfi1p6.mjs";
2
- export { Builder, DANGLING_DTS_SOURCE_MAP_RE, PROJECT_REQUIRED_FIELDS, Path, PlugboyProject, PlugboyWorkspace, TSDOWN_SYNC_OPTIONS, WORKSPACE_PACKAGE_SYNC_FIELDS, WORKSPACE_REQUIRED_FIELDS, buildHooks, collectExternalStringPrefixes, copyDirSync, createHooksDefaults, definePlugin, defineProjectConfig, defineWorkspaceConfig, exitHook, exposeEntries, extractProjectPlugins, findConfig, findFile, findProjectPlugin, findWorkspacePackages, generateWorkspace, getDirname, getFilename, getProject, getProjectPackageJson, getWorkspace, getWorkspacePackageJson, isFileNotFoundException, isProjectPackageJson, isPromise, isWorkspacePackageJson, loadProjectConfig, loadWorkspaceConfig, mergeChunkAddons, mergeDTSSettingsList, mergeExternals, mergeNoExternals, normalizeDTSPreserveTypeSettings, normalizeDTSPreserveTypeTarget, normalizeDTSSettings, pathExists, resolveBundledConfigOutputFile, resolveListable, resolveOptimizeCSSOptions, resolveRawExposeEntriesSettings, resolveRawWorkspaceEntries, resolveRawWorkspaceEntry, resolveUserHooks, resolveUserPluginOption, resolveUserProjectConfig, resolveUserWorkspaceConfig, rmrf, stripDanglingDTSSourceMaps, syncWorkspacePackageFields, writeFileAtomic };
1
+ import { $ as resolveListable, A as isProjectPackageJson, B as isFileNotFoundException, C as resolveRawWorkspaceEntry, D as findProjectPlugin, E as extractProjectPlugins, F as copyDirSync, G as DANGLING_DTS_SOURCE_MAP_RE, H as rmrf, I as findConfig, J as collectExternalStringPrefixes, K as stripDanglingDTSSourceMaps, L as findFile, M as resolveUserProjectConfig, N as exposeEntries, O as resolveUserPluginOption, P as resolveRawExposeEntriesSettings, Q as isPromise, R as getDirname, S as resolveRawWorkspaceEntries, T as definePlugin, U as writeFileAtomic, V as pathExists, W as resolveBundledConfigOutputFile, X as mergeExternals, Y as mergeChunkAddons, Z as mergeNoExternals, _ as buildHooks, a as syncWorkspacePackageFields, at as normalizeDTSPreserveTypeSettings, b as isWorkspacePackageJson, c as orderChunks, ct as createHooksDefaults, d as getProject, et as PROJECT_REQUIRED_FIELDS, f as Builder, g as Path, h as getWorkspacePackageJson, i as getWorkspace, it as mergeDTSSettingsList, j as loadProjectConfig, k as defineProjectConfig, l as orderPackageChunks, m as getProjectPackageJson, n as PlugboyWorkspace, nt as WORKSPACE_REQUIRED_FIELDS, o as captureChunkGraph, ot as normalizeDTSPreserveTypeTarget, p as findWorkspacePackages, q as exitHook, r as WORKSPACE_PACKAGE_SYNC_FIELDS, rt as resolveOptimizeCSSOptions, s as entryChunksInDeclaredOrder, st as normalizeDTSSettings, t as generateWorkspace, tt as TSDOWN_SYNC_OPTIONS, u as PlugboyProject, v as resolveUserHooks, w as resolveUserWorkspaceConfig, x as loadWorkspaceConfig, y as defineWorkspaceConfig, z as getFilename } from "./workspace-DdhCZjQE.mjs";
2
+ export { Builder, DANGLING_DTS_SOURCE_MAP_RE, PROJECT_REQUIRED_FIELDS, Path, PlugboyProject, PlugboyWorkspace, TSDOWN_SYNC_OPTIONS, WORKSPACE_PACKAGE_SYNC_FIELDS, WORKSPACE_REQUIRED_FIELDS, buildHooks, captureChunkGraph, collectExternalStringPrefixes, copyDirSync, createHooksDefaults, definePlugin, defineProjectConfig, defineWorkspaceConfig, entryChunksInDeclaredOrder, exitHook, exposeEntries, extractProjectPlugins, findConfig, findFile, findProjectPlugin, findWorkspacePackages, generateWorkspace, getDirname, getFilename, getProject, getProjectPackageJson, getWorkspace, getWorkspacePackageJson, isFileNotFoundException, isProjectPackageJson, isPromise, isWorkspacePackageJson, loadProjectConfig, loadWorkspaceConfig, mergeChunkAddons, mergeDTSSettingsList, mergeExternals, mergeNoExternals, normalizeDTSPreserveTypeSettings, normalizeDTSPreserveTypeTarget, normalizeDTSSettings, orderChunks, orderPackageChunks, pathExists, resolveBundledConfigOutputFile, resolveListable, resolveOptimizeCSSOptions, resolveRawExposeEntriesSettings, resolveRawWorkspaceEntries, resolveRawWorkspaceEntry, resolveUserHooks, resolveUserPluginOption, resolveUserProjectConfig, resolveUserWorkspaceConfig, rmrf, stripDanglingDTSSourceMaps, syncWorkspacePackageFields, writeFileAtomic };
@@ -1088,19 +1088,39 @@ function declaredStylesheets(workspace) {
1088
1088
  return new Set(workspace.exports.filter((exp) => exp.id.endsWith(".css")).map((exp) => path.posix.basename(exp.id)));
1089
1089
  }
1090
1090
  /**
1091
+ * The file name of the package's one assembled stylesheet, or `undefined` when
1092
+ * plugboy does not assemble one.
1093
+ *
1094
+ * With a single CSS entry the package publishes one stylesheet holding all of its
1095
+ * CSS. `plugboy-assemble-entry-css` writes it under `css.fileName` when that is
1096
+ * set, and under the declared `<entry>.css` otherwise. With several CSS entries
1097
+ * each gets its own, so there is no package-wide one.
1098
+ */
1099
+ function assembledStylesheetName(workspace) {
1100
+ const { cssOptions } = workspace;
1101
+ if (!cssOptions?.splitting || cssOptions.inject) return void 0;
1102
+ const declared = declaredStylesheets(workspace);
1103
+ if (declared.size !== 1) return void 0;
1104
+ return cssOptions.fileName ?? [...declared][0];
1105
+ }
1106
+ /**
1091
1107
  * Every stylesheet the build has written to `dir`, as file names.
1092
1108
  *
1093
- * The bundle is not the whole story: `plugboy-assemble-entry-css` writes an
1094
- * entry's stylesheet directly to disk when the build produced none for it, so a
1095
- * `writeBundle` stage that only walked the bundle assets would skip exactly the
1096
- * file that needed the most work. The declared exports fill that in they are the
1097
- * stylesheets the package publishes, so anything present under one of those names
1098
- * belongs in the set.
1109
+ * The bundle is not the whole story: `plugboy-assemble-entry-css` writes the
1110
+ * declared stylesheets directly to disk, often under a name the build emitted
1111
+ * nothing for, so a `writeBundle` stage that only walked the bundle assets would
1112
+ * skip exactly the file that needed the most work. The declared exports — and the
1113
+ * package stylesheet's `css.fileName`, when one is set fill that in: they are
1114
+ * the stylesheets the package publishes, so anything present under one of those
1115
+ * names belongs in the set.
1099
1116
  */
1100
1117
  async function listEmittedStylesheets(workspace, dir, bundle) {
1101
1118
  const names = /* @__PURE__ */ new Set();
1102
1119
  for (const chunk of Object.values(bundle)) if (chunk.type === "asset" && chunk.fileName.endsWith(".css")) names.add(chunk.fileName);
1103
- await Promise.all([...declaredStylesheets(workspace)].map(async (name) => {
1120
+ const assembled = assembledStylesheetName(workspace);
1121
+ const candidates = new Set(declaredStylesheets(workspace));
1122
+ if (assembled) candidates.add(assembled);
1123
+ await Promise.all([...candidates].map(async (name) => {
1104
1124
  if (names.has(name)) return;
1105
1125
  try {
1106
1126
  await fs$1.access(path.join(dir, name));
@@ -1109,6 +1129,38 @@ async function listEmittedStylesheets(workspace, dir, bundle) {
1109
1129
  }));
1110
1130
  return [...names];
1111
1131
  }
1132
+ /**
1133
+ * Fill in the `css` options plugboy needs to deliver its declared stylesheets.
1134
+ *
1135
+ * `cssEntryIds` are the entries with `css: true`, named as plugboy names them
1136
+ * (`.` normalized to the package directory name).
1137
+ *
1138
+ * By default the build emits one stylesheet per chunk (`splitting: true`) and
1139
+ * `plugboy-assemble-entry-css` builds each declared stylesheet from them, in
1140
+ * dependency order. tsdown's own single-file merge is not used, because it
1141
+ * concatenates chunks in bundle order: a shared chunk lands after the entries
1142
+ * that compose from it, and loses to the base styles it was meant to override.
1143
+ * A `fileName` is still honored — it names the assembled stylesheet.
1144
+ *
1145
+ * tsdown merges into one file itself only where plugboy cannot assemble:
1146
+ * - `splitting` declared explicitly, which is applied as written;
1147
+ * - `inject`, where the JavaScript imports the per-chunk stylesheets by name, so
1148
+ * they have to stay as emitted. One CSS entry then keeps a single file.
1149
+ *
1150
+ * A single file is named after the CSS entry unless `fileName` says otherwise, so
1151
+ * it is the file the `./<entry>.css` export points at. A workspace without CSS
1152
+ * entries is left to tsdown's defaults.
1153
+ */
1154
+ function resolveCssOptions(css, cssEntryIds) {
1155
+ if (!cssEntryIds.length) return css;
1156
+ const splitting = css?.splitting ?? (css?.inject ? cssEntryIds.length > 1 : true);
1157
+ const resolved = {
1158
+ ...css,
1159
+ splitting
1160
+ };
1161
+ if (!splitting && cssEntryIds.length === 1) resolved.fileName ??= `${cssEntryIds[0]}.css`;
1162
+ return resolved;
1163
+ }
1112
1164
  //#endregion
1113
1165
  //#region src/postcss/plugin.ts
1114
1166
  async function getPostcss(options) {
@@ -1196,69 +1248,174 @@ function OptimizeCSSPlugin(workspace) {
1196
1248
  };
1197
1249
  }
1198
1250
  //#endregion
1251
+ //#region src/workspace/chunk-order.ts
1252
+ /**
1253
+ * Capture the chunk graph of a bundle.
1254
+ *
1255
+ * Call it from `generateBundle`, before `@tsdown/css` runs: that removes the
1256
+ * chunks holding nothing but CSS, together with every import of them, so a
1257
+ * graph captured later has lost exactly the edges that matter here.
1258
+ */
1259
+ function captureChunkGraph(bundle) {
1260
+ const graph = /* @__PURE__ */ new Map();
1261
+ for (const chunk of Object.values(bundle)) {
1262
+ if (chunk.type !== "chunk") continue;
1263
+ graph.set(chunk.fileName, {
1264
+ name: chunk.name ?? chunk.fileName,
1265
+ isEntry: !!chunk.isEntry,
1266
+ imports: [...chunk.imports ?? []],
1267
+ dynamicImports: [...chunk.dynamicImports ?? []]
1268
+ });
1269
+ }
1270
+ return graph;
1271
+ }
1272
+ /**
1273
+ * The entry chunks of a graph, in the order the entries were declared.
1274
+ *
1275
+ * `entryIds` are the names plugboy gives the entries (`.` normalized to the
1276
+ * package directory name). Entry chunks it does not name keep the bundle's order,
1277
+ * after the named ones.
1278
+ */
1279
+ function entryChunksInDeclaredOrder(graph, entryIds = []) {
1280
+ const rank = (node) => {
1281
+ const index = entryIds.indexOf(node.name);
1282
+ return index === -1 ? entryIds.length : index;
1283
+ };
1284
+ return [...graph].filter(([, node]) => node.isEntry).map(([fileName, node], position) => ({
1285
+ fileName,
1286
+ node,
1287
+ position
1288
+ })).sort((a, b) => rank(a.node) - rank(b.node) || a.position - b.position).map(({ fileName }) => fileName);
1289
+ }
1290
+ /**
1291
+ * Chunk file names in load order, starting from `roots`.
1292
+ *
1293
+ * Each chunk appears once, after every chunk it imports statically. Chunks
1294
+ * reached only through `import()` follow all of those.
1295
+ */
1296
+ function orderChunks(graph, roots, options = {}) {
1297
+ const visited = /* @__PURE__ */ new Set();
1298
+ const ordered = [];
1299
+ const dynamic = /* @__PURE__ */ new Set();
1300
+ const walk = (fileName) => {
1301
+ if (visited.has(fileName)) return;
1302
+ visited.add(fileName);
1303
+ const node = graph.get(fileName);
1304
+ if (!node) return;
1305
+ for (const imported of node.imports) walk(imported);
1306
+ for (const imported of node.dynamicImports) dynamic.add(imported);
1307
+ ordered.push(fileName);
1308
+ };
1309
+ for (const root of roots) walk(root);
1310
+ for (const fileName of dynamic) walk(fileName);
1311
+ if (options.includeUnreached) for (const fileName of graph.keys()) walk(fileName);
1312
+ return ordered;
1313
+ }
1314
+ /**
1315
+ * Every chunk of the package in load order: the entries as declared, then what
1316
+ * they import dynamically, then anything left over.
1317
+ */
1318
+ function orderPackageChunks(graph, entryIds) {
1319
+ return orderChunks(graph, entryChunksInDeclaredOrder(graph, entryIds), { includeUnreached: true });
1320
+ }
1321
+ //#endregion
1199
1322
  //#region src/workspace/plugins/assemble-entry-css.ts
1323
+ /**
1324
+ * Plugin that builds every stylesheet plugboy declares — `./<entry>.css` for each
1325
+ * `css: true` entry — from the per-chunk stylesheets the build emits, in the
1326
+ * order they load.
1327
+ *
1328
+ * plugboy runs tsdown with `css.splitting` on (see `resolveCssOptions`), which
1329
+ * emits one stylesheet per output *chunk*. Neither that nor tsdown's single-file
1330
+ * merge matches what plugboy publishes:
1331
+ *
1332
+ * - tsdown's merge concatenates the chunks in bundle order — entries first,
1333
+ * shared chunks after. A package that composes from a shared base style then
1334
+ * ships the base *after* the rules built on it, and the base wins.
1335
+ * - Per-chunk stylesheets are named after chunks. CSS reached from several
1336
+ * entries lands in a shared chunk under a hashed name no export points at, and
1337
+ * an entry whose CSS comes only from there gets no stylesheet at all.
1338
+ *
1339
+ * So each declared stylesheet is rebuilt here, with the chunks ordered as Vite
1340
+ * orders them for `build.cssCodeSplit: false` (`orderChunks`): static imports
1341
+ * before their importer, dynamically imported chunks after. Nothing is left for a
1342
+ * dynamic `import()` to load, since without `css.inject` the JavaScript imports no
1343
+ * stylesheet at all.
1344
+ *
1345
+ * - **One CSS entry**: the package publishes one stylesheet, so it gets the CSS of
1346
+ * every chunk — the same content tsdown's merge would have produced, in
1347
+ * dependency order. It is written under `css.fileName` when one is set.
1348
+ * - **Several**: each gets its own CSS and that of every chunk it reaches. CSS
1349
+ * shared between entries is duplicated into each, which is what makes a single
1350
+ * `./<entry>.css` import complete.
1351
+ *
1352
+ * The per-chunk stylesheets that were folded in are deleted, since nothing
1353
+ * exports them.
1354
+ *
1355
+ * The chunk graph has to be captured in `generateBundle`, because it is gone by
1356
+ * the time the stylesheets exist: a chunk holding nothing but CSS is dropped once
1357
+ * tsdown's CSS pipeline (a *post* plugin, so it runs after this hook) has emitted
1358
+ * its stylesheet, and its importers' `imports` are emptied along with it. By
1359
+ * `writeBundle` the shared chunk is neither in the bundle nor on disk — only its
1360
+ * orphaned stylesheet is.
1361
+ *
1362
+ * Nothing is assembled when tsdown merged the stylesheet itself (`splitting` off,
1363
+ * declared explicitly), or with `css.inject`, where the JavaScript imports the
1364
+ * per-chunk stylesheets by name.
1365
+ */
1200
1366
  function createAssembleEntryCssPlugin(workspace) {
1201
- /** Chunk file name -> its identity and static imports, as of `generateBundle`. */
1202
- const graph = /* @__PURE__ */ new Map();
1367
+ let graph = /* @__PURE__ */ new Map();
1203
1368
  return definePlugin({
1204
1369
  name: "plugboy-assemble-entry-css",
1205
1370
  buildStart() {
1206
- graph.clear();
1371
+ graph = /* @__PURE__ */ new Map();
1207
1372
  },
1208
1373
  generateBundle(_options, bundle) {
1209
- for (const chunk of Object.values(bundle)) {
1210
- if (chunk.type !== "chunk") continue;
1211
- graph.set(chunk.fileName, {
1212
- name: chunk.name,
1213
- isEntry: chunk.isEntry,
1214
- imports: [...chunk.imports]
1215
- });
1216
- }
1374
+ graph = captureChunkGraph(bundle);
1217
1375
  },
1218
1376
  async writeBundle(options, bundle) {
1219
1377
  const { dir } = options;
1220
1378
  if (!dir) return;
1221
- if (workspace.cssOptions?.inject) return;
1379
+ const { cssOptions } = workspace;
1380
+ if (!cssOptions?.splitting || cssOptions.inject) return;
1222
1381
  const targets = declaredStylesheets(workspace);
1223
1382
  if (!targets.size) return;
1224
1383
  const emitted = new Set(Object.values(bundle).filter((chunk) => chunk.type === "asset" && chunk.fileName.endsWith(".css")).map((chunk) => chunk.fileName));
1225
1384
  if (!emitted.size) return;
1226
- /** The stylesheet a chunk owns, if the build emitted one. */
1227
- const cssOf = (fileName) => {
1228
- const css = toCssFileName(fileName);
1229
- return css !== fileName && emitted.has(css) ? css : void 0;
1230
- };
1231
- /** Stylesheets an entry needs, dependencies first, each listed once. */
1232
- const collect = (entryFileName) => {
1233
- const visited = /* @__PURE__ */ new Set();
1385
+ /** The stylesheets of `chunks`, in the same order, each listed once. */
1386
+ const stylesheetsOf = (chunks) => {
1234
1387
  const ordered = [];
1235
- const walk = (fileName) => {
1236
- if (visited.has(fileName)) return;
1237
- visited.add(fileName);
1238
- const node = graph.get(fileName);
1239
- if (!node) return;
1240
- for (const imported of node.imports) walk(imported);
1241
- const css = cssOf(fileName);
1242
- if (css && !ordered.includes(css)) ordered.push(css);
1243
- };
1244
- walk(entryFileName);
1388
+ for (const fileName of chunks) {
1389
+ const css = toCssFileName(fileName);
1390
+ if (css === fileName || !emitted.has(css)) continue;
1391
+ if (!ordered.includes(css)) ordered.push(css);
1392
+ }
1245
1393
  return ordered;
1246
1394
  };
1247
- const folded = /* @__PURE__ */ new Set();
1248
- await Promise.all([...graph].map(async ([fileName, node]) => {
1249
- if (!node.isEntry) return;
1395
+ /** Output file name -> the stylesheets it is built from. */
1396
+ const plan = /* @__PURE__ */ new Map();
1397
+ const packageStylesheet = assembledStylesheetName(workspace);
1398
+ if (packageStylesheet) plan.set(packageStylesheet, stylesheetsOf(orderPackageChunks(graph, Object.keys(workspace.entry))));
1399
+ else for (const [fileName, node] of graph) {
1400
+ if (!node.isEntry) continue;
1250
1401
  const target = `${node.name}.css`;
1251
- if (!targets.has(target)) return;
1252
- const sources = collect(fileName);
1253
- if (!sources.length) return;
1402
+ if (!targets.has(target)) continue;
1403
+ plan.set(target, stylesheetsOf(orderChunks(graph, [fileName])));
1404
+ }
1405
+ const outputs = new Set(plan.keys());
1406
+ const folded = /* @__PURE__ */ new Set();
1407
+ const assembled = await Promise.all([...plan].map(async ([target, sources]) => {
1408
+ if (!sources.length) return void 0;
1254
1409
  sources.forEach((source) => folded.add(source));
1255
- if (sources.length === 1 && sources[0] === target) return;
1410
+ if (sources.length === 1 && sources[0] === target) return void 0;
1256
1411
  const css = (await Promise.all(sources.map((source) => fs$1.readFile(path.join(dir, source), "utf8").catch(() => "")))).filter(Boolean).join("\n");
1257
- if (!css) return;
1258
- await fs$1.writeFile(path.join(dir, target), css);
1259
- folded.add(target);
1412
+ return css ? {
1413
+ target,
1414
+ css
1415
+ } : void 0;
1260
1416
  }));
1261
- await Promise.all([...folded].filter((css) => !targets.has(css)).map((css) => fs$1.rm(path.join(dir, css), { force: true })));
1417
+ await Promise.all(assembled.map((output) => output && fs$1.writeFile(path.join(dir, output.target), output.css)));
1418
+ await Promise.all([...folded].filter((css) => !outputs.has(css)).map((css) => fs$1.rm(path.join(dir, css), { force: true })));
1262
1419
  }
1263
1420
  });
1264
1421
  }
@@ -1461,8 +1618,10 @@ function createPreserveCssImportsPlugin(workspace) {
1461
1618
  generateBundle(_options, bundle) {
1462
1619
  if (!layersByModule.size) return;
1463
1620
  const sequences = [];
1464
- for (const chunk of Object.values(bundle)) {
1465
- if (chunk.type !== "chunk") continue;
1621
+ const ordered = orderPackageChunks(captureChunkGraph(bundle), Object.keys(workspace.entry));
1622
+ for (const fileName of ordered) {
1623
+ const chunk = bundle[fileName];
1624
+ if (chunk?.type !== "chunk") continue;
1466
1625
  for (const id of chunk.moduleIds) {
1467
1626
  const names = layersByModule.get(id);
1468
1627
  if (names) sequences.push(names);
@@ -1669,9 +1828,9 @@ var PlugboyWorkspace = class {
1669
1828
  ];
1670
1829
  this.hooks = hooks;
1671
1830
  this.dts = dts;
1672
- this.cssOptions = css;
1673
1831
  this.optimizeCSSOptions = optimizeCSS ? resolveOptimizeCSSOptions(optimizeCSS) : false;
1674
1832
  const entry = {};
1833
+ const cssEntryIds = [];
1675
1834
  const exports = [{
1676
1835
  id: `./${PACKAGE_JSON_FILENAME}`,
1677
1836
  at: `./${PACKAGE_JSON_FILENAME}`
@@ -1685,6 +1844,7 @@ var PlugboyWorkspace = class {
1685
1844
  const destFullPath = dir.join(dest).value;
1686
1845
  entry[normalizedId] = src;
1687
1846
  if (css) {
1847
+ cssEntryIds.push(normalizedId);
1688
1848
  const cssDest = `./dist/${normalizedId}.css`;
1689
1849
  exports.push({
1690
1850
  id: `./${normalizedId}.css`,
@@ -1716,6 +1876,7 @@ var PlugboyWorkspace = class {
1716
1876
  });
1717
1877
  this.entry = entry;
1718
1878
  this.exports = exports;
1879
+ this.cssOptions = resolveCssOptions(css, cssEntryIds);
1719
1880
  this.builder = new Builder(this);
1720
1881
  }
1721
1882
  clean(withDepsAndCache) {
@@ -1772,11 +1933,8 @@ var PlugboyWorkspace = class {
1772
1933
  cloned.type = cloned.type || "module";
1773
1934
  await this.hooks.preparePackageJSON(json, this);
1774
1935
  const sorted = sortPackageJson(cloned);
1775
- const toStr = JSON.stringify(sorted, null, 2);
1776
- if (originalJSONString !== toStr) {
1777
- await writeFileAtomic(this.dir.join(PACKAGE_JSON_FILENAME).value, toStr);
1778
- this._json = sorted;
1779
- }
1936
+ if (originalJSONString !== JSON.stringify(sorted)) await writeFileAtomic(this.dir.join(PACKAGE_JSON_FILENAME).value, `${JSON.stringify(sorted, null, 2)}\n`);
1937
+ this._json = sorted;
1780
1938
  return sorted;
1781
1939
  }
1782
1940
  getStubLinks() {
@@ -1960,7 +2118,7 @@ async function generateWorkspace(workspaceName, cwd = process.cwd()) {
1960
2118
  process.exit(1);
1961
2119
  }
1962
2120
  await fs$1.mkdir(workspaceDir);
1963
- await fs$1.writeFile(path.join(workspaceDir, "package.json"), JSON.stringify(json, null, 2));
2121
+ await fs$1.writeFile(path.join(workspaceDir, "package.json"), `${JSON.stringify(json, null, 2)}\n`);
1964
2122
  await fs$1.writeFile(path.join(workspaceDir, "README.md"), config.readme(json));
1965
2123
  if (withGenSource) {
1966
2124
  const srcDir = path.join(workspaceDir, "src");
@@ -1970,7 +2128,7 @@ async function generateWorkspace(workspaceName, cwd = process.cwd()) {
1970
2128
  await fs$1.writeFile(path.join(srcDir, "index.ts"), indexCode);
1971
2129
  await fs$1.writeFile(path.join(srcDir, `${workspaceName}.ts`), modCode);
1972
2130
  const { tsconfig } = config;
1973
- if (tsconfig) await fs$1.writeFile(path.join(workspaceDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
2131
+ if (tsconfig) await fs$1.writeFile(path.join(workspaceDir, "tsconfig.json"), `${JSON.stringify(tsconfig, null, 2)}\n`);
1974
2132
  const configFileCode = `${`
1975
2133
  import { defineWorkspaceConfig } from '@fastkit/plugboy';
1976
2134
 
@@ -1985,6 +2143,6 @@ export default defineWorkspaceConfig({
1985
2143
  }
1986
2144
  }
1987
2145
  //#endregion
1988
- export { resolveOptimizeCSSOptions as $, resolveRawExposeEntriesSettings as A, resolveBundledConfigOutputFile as B, findProjectPlugin as C, loadProjectConfig as D, isProjectPackageJson as E, getFilename as F, mergeChunkAddons as G, stripDanglingDTSSourceMaps as H, isFileNotFoundException as I, isPromise as J, mergeExternals as K, pathExists as L, findConfig as M, findFile as N, resolveUserProjectConfig as O, getDirname as P, WORKSPACE_REQUIRED_FIELDS as Q, rmrf as R, extractProjectPlugins as S, defineProjectConfig as T, exitHook as U, DANGLING_DTS_SOURCE_MAP_RE as V, collectExternalStringPrefixes as W, PROJECT_REQUIRED_FIELDS as X, resolveListable as Y, TSDOWN_SYNC_OPTIONS as Z, loadWorkspaceConfig as _, syncWorkspacePackageFields as a, resolveUserWorkspaceConfig as b, Builder as c, getWorkspacePackageJson as d, mergeDTSSettingsList as et, Path as f, isWorkspacePackageJson as g, defineWorkspaceConfig as h, getWorkspace as i, createHooksDefaults as it, copyDirSync as j, exposeEntries as k, findWorkspacePackages as l, resolveUserHooks as m, PlugboyWorkspace as n, normalizeDTSPreserveTypeTarget as nt, PlugboyProject as o, buildHooks as p, mergeNoExternals as q, WORKSPACE_PACKAGE_SYNC_FIELDS as r, normalizeDTSSettings as rt, getProject as s, generateWorkspace as t, normalizeDTSPreserveTypeSettings as tt, getProjectPackageJson as u, resolveRawWorkspaceEntries as v, resolveUserPluginOption as w, definePlugin as x, resolveRawWorkspaceEntry as y, writeFileAtomic as z };
2146
+ export { resolveListable as $, isProjectPackageJson as A, isFileNotFoundException as B, resolveRawWorkspaceEntry as C, findProjectPlugin as D, extractProjectPlugins as E, copyDirSync as F, DANGLING_DTS_SOURCE_MAP_RE as G, rmrf as H, findConfig as I, collectExternalStringPrefixes as J, stripDanglingDTSSourceMaps as K, findFile as L, resolveUserProjectConfig as M, exposeEntries as N, resolveUserPluginOption as O, resolveRawExposeEntriesSettings as P, isPromise as Q, getDirname as R, resolveRawWorkspaceEntries as S, definePlugin as T, writeFileAtomic as U, pathExists as V, resolveBundledConfigOutputFile as W, mergeExternals as X, mergeChunkAddons as Y, mergeNoExternals as Z, buildHooks as _, syncWorkspacePackageFields as a, normalizeDTSPreserveTypeSettings as at, isWorkspacePackageJson as b, orderChunks as c, createHooksDefaults as ct, getProject as d, PROJECT_REQUIRED_FIELDS as et, Builder as f, Path as g, getWorkspacePackageJson as h, getWorkspace as i, mergeDTSSettingsList as it, loadProjectConfig as j, defineProjectConfig as k, orderPackageChunks as l, getProjectPackageJson as m, PlugboyWorkspace as n, WORKSPACE_REQUIRED_FIELDS as nt, captureChunkGraph as o, normalizeDTSPreserveTypeTarget as ot, findWorkspacePackages as p, exitHook as q, WORKSPACE_PACKAGE_SYNC_FIELDS as r, resolveOptimizeCSSOptions as rt, entryChunksInDeclaredOrder as s, normalizeDTSSettings as st, generateWorkspace as t, TSDOWN_SYNC_OPTIONS as tt, PlugboyProject as u, resolveUserHooks as v, resolveUserWorkspaceConfig as w, loadWorkspaceConfig as x, defineWorkspaceConfig as y, getFilename as z };
1989
2147
 
1990
- //# sourceMappingURL=workspace-Dngfi1p6.mjs.map
2148
+ //# sourceMappingURL=workspace-DdhCZjQE.mjs.map