@fastkit/plugboy 1.3.0 → 1.4.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
|
@@ -191,9 +191,67 @@ value is shallow-merged over the project one, so it only needs to restate the
|
|
|
191
191
|
keys it changes.
|
|
192
192
|
|
|
193
193
|
Plugins may seed defaults here during workspace setup — the vanilla-extract
|
|
194
|
-
plugin, for instance, owns `splitting` and `fileName`
|
|
195
|
-
|
|
196
|
-
the plugin's documentation before overriding a key it manages.
|
|
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.
|
|
197
|
+
|
|
198
|
+
#### CSS Optimization
|
|
199
|
+
|
|
200
|
+
`optimizeCSS` applies plugboy's own postcss pass on top of whatever tsdown
|
|
201
|
+
produced: duplicate `@layer` / `@media` blocks are merged, and the selectors
|
|
202
|
+
listed in `combineRules` are combined into a single rule. Pass `false` to disable
|
|
203
|
+
it.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
export default defineWorkspaceConfig({
|
|
207
|
+
optimizeCSS: {
|
|
208
|
+
combineRules: {
|
|
209
|
+
rules: [':root']
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
It runs in `writeBundle`, on the stylesheets on disk, so it covers **every**
|
|
216
|
+
stylesheet the build writes — including the ones tsdown's own CSS pipeline emits,
|
|
217
|
+
which it does after every plugin has had its say.
|
|
218
|
+
|
|
219
|
+
#### Preserved at the top of a stylesheet
|
|
220
|
+
|
|
221
|
+
Two things tsdown's CSS pipeline would rewrite are restored afterwards, in this
|
|
222
|
+
order, above every rule:
|
|
223
|
+
|
|
224
|
+
1. **The authored `@layer` order.** lightningcss prunes a name from an
|
|
225
|
+
`@layer a, b, c;` statement once a block for it appears in the same
|
|
226
|
+
stylesheet. For a library that is wrong whenever the statement also orders
|
|
227
|
+
layers owned by *other* packages — the pruned layer's position then depends on
|
|
228
|
+
where its block happens to land relative to those. plugboy reads the statements
|
|
229
|
+
before the transform and re-emits them verbatim.
|
|
230
|
+
2. **External `@import`s.** A bare package specifier (e.g.
|
|
231
|
+
`@import url('material-symbols/rounded.css') layer(...)`) stays external
|
|
232
|
+
instead of being inlined, so the consumer's bundler resolves it and the
|
|
233
|
+
imported package's own relative asset URLs keep working.
|
|
234
|
+
|
|
235
|
+
Both are captured from every stylesheet in the module graph, whether its contents
|
|
236
|
+
were authored or generated by a plugin — vanilla-extract emits its `@layer`
|
|
237
|
+
statements into a virtual module, and those are covered too.
|
|
238
|
+
|
|
239
|
+
#### One stylesheet per CSS entry
|
|
240
|
+
|
|
241
|
+
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.
|
|
197
255
|
|
|
198
256
|
### defineProjectConfig
|
|
199
257
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { i as getWorkspace, t as generateWorkspace } from "./workspace-
|
|
1
|
+
import { i as getWorkspace, t as generateWorkspace } from "./workspace-B6BoRqWL.mjs";
|
|
2
2
|
import { cac } from "cac";
|
|
3
3
|
//#region package.json
|
|
4
|
-
var version = "1.
|
|
4
|
+
var version = "1.4.0";
|
|
5
5
|
//#endregion
|
|
6
6
|
//#region src/cli.ts
|
|
7
7
|
async function main() {
|
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-
|
|
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-B6BoRqWL.mjs";
|
|
2
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 };
|
|
@@ -1057,6 +1057,38 @@ async function getProject(searchDir, allowMissing, skipLoadConfig) {
|
|
|
1057
1057
|
});
|
|
1058
1058
|
}
|
|
1059
1059
|
//#endregion
|
|
1060
|
+
//#region src/workspace/stylesheets.ts
|
|
1061
|
+
/** `foo.mjs` -> `foo.css`, mirroring how `@tsdown/css` names a chunk's CSS. */
|
|
1062
|
+
function toCssFileName(jsFileName) {
|
|
1063
|
+
return jsFileName.replace(/(?:\.module)?(\.[cm]?js)$/, ".css");
|
|
1064
|
+
}
|
|
1065
|
+
/** The stylesheets a package declares: `./<entry>.css` per `css: true` entry. */
|
|
1066
|
+
function declaredStylesheets(workspace) {
|
|
1067
|
+
return new Set(workspace.exports.filter((exp) => exp.id.endsWith(".css")).map((exp) => path.posix.basename(exp.id)));
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Every stylesheet the build has written to `dir`, as file names.
|
|
1071
|
+
*
|
|
1072
|
+
* The bundle is not the whole story: `plugboy-assemble-entry-css` writes an
|
|
1073
|
+
* entry's stylesheet directly to disk when the build produced none for it, so a
|
|
1074
|
+
* `writeBundle` stage that only walked the bundle assets would skip exactly the
|
|
1075
|
+
* file that needed the most work. The declared exports fill that in — they are the
|
|
1076
|
+
* stylesheets the package publishes, so anything present under one of those names
|
|
1077
|
+
* belongs in the set.
|
|
1078
|
+
*/
|
|
1079
|
+
async function listEmittedStylesheets(workspace, dir, bundle) {
|
|
1080
|
+
const names = /* @__PURE__ */ new Set();
|
|
1081
|
+
for (const chunk of Object.values(bundle)) if (chunk.type === "asset" && chunk.fileName.endsWith(".css")) names.add(chunk.fileName);
|
|
1082
|
+
await Promise.all([...declaredStylesheets(workspace)].map(async (name) => {
|
|
1083
|
+
if (names.has(name)) return;
|
|
1084
|
+
try {
|
|
1085
|
+
await fs$1.access(path.join(dir, name));
|
|
1086
|
+
names.add(name);
|
|
1087
|
+
} catch {}
|
|
1088
|
+
}));
|
|
1089
|
+
return [...names];
|
|
1090
|
+
}
|
|
1091
|
+
//#endregion
|
|
1060
1092
|
//#region src/postcss/plugin.ts
|
|
1061
1093
|
async function getPostcss(options) {
|
|
1062
1094
|
const { layer, media, combineRules, cssnano } = options;
|
|
@@ -1077,38 +1109,137 @@ async function getPostcss(options) {
|
|
|
1077
1109
|
const SOURCE_MAPPING_URL_COMMENT_RE = /\/\*# sourceMappingURL=.+? \*\//g;
|
|
1078
1110
|
const allLayerDefRe = /(^|\n)@layer\s+([a-zA-Z\d\-_$. ,]+);/g;
|
|
1079
1111
|
const layerDefTrimRe = /((^|\n)@layer\s+|;)/g;
|
|
1080
|
-
async function optimizeCSS(
|
|
1112
|
+
async function optimizeCSS(css, fileName, options) {
|
|
1081
1113
|
const postcss = await getPostcss(options);
|
|
1082
|
-
function prepare(
|
|
1114
|
+
function prepare(source) {
|
|
1083
1115
|
return (() => {
|
|
1084
|
-
const matched =
|
|
1116
|
+
const matched = source.match(allLayerDefRe);
|
|
1085
1117
|
if (!matched) return "";
|
|
1086
1118
|
const layerNames = [];
|
|
1087
1119
|
matched.forEach((row) => {
|
|
1088
1120
|
row.replace(layerDefTrimRe, "").split(",").forEach((chunk) => layerNames.push(chunk.trim()));
|
|
1089
1121
|
});
|
|
1090
1122
|
return `@layer ${Array.from(new Set(layerNames)).join(", ")};\n`;
|
|
1091
|
-
})() +
|
|
1123
|
+
})() + source.replace(allLayerDefRe, "");
|
|
1092
1124
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
to: asset.fileName,
|
|
1125
|
+
return (await postcss.process(prepare(css), {
|
|
1126
|
+
from: fileName,
|
|
1127
|
+
to: fileName,
|
|
1097
1128
|
map: { inline: false }
|
|
1098
1129
|
})).css.replace(SOURCE_MAPPING_URL_COMMENT_RE, "");
|
|
1099
1130
|
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Applies {@link PlugboyWorkspace.optimizeCSSOptions} to every stylesheet the
|
|
1133
|
+
* build writes.
|
|
1134
|
+
*
|
|
1135
|
+
* This runs in `writeBundle`, on the files on disk, rather than on the bundle
|
|
1136
|
+
* assets in `generateBundle` — because not every stylesheet exists as a bundle
|
|
1137
|
+
* asset by then. tsdown's own CSS pipeline emits from a *post* plugin, which
|
|
1138
|
+
* runs after every user plugin's `generateBundle`, so CSS that tsdown produces
|
|
1139
|
+
* (a plain `.css` / `.scss` import, or — since the vanilla-extract plugin routes
|
|
1140
|
+
* its `.css.ts` through tsdown — extracted CSS) used to skip these
|
|
1141
|
+
* optimizations entirely, while CSS a plugin emitted itself received them. By
|
|
1142
|
+
* `writeBundle` every producer has finished and the whole set is on disk.
|
|
1143
|
+
*
|
|
1144
|
+
* `preserve-css-imports` re-injects external `@import`s in its own
|
|
1145
|
+
* `writeBundle`; it is registered after this plugin, so it always sees the
|
|
1146
|
+
* optimized file.
|
|
1147
|
+
*/
|
|
1100
1148
|
function OptimizeCSSPlugin(workspace) {
|
|
1149
|
+
const processed = /* @__PURE__ */ new Set();
|
|
1101
1150
|
return {
|
|
1102
1151
|
name: "plugboy-optimize-css",
|
|
1103
|
-
|
|
1152
|
+
buildStart() {
|
|
1153
|
+
processed.clear();
|
|
1154
|
+
},
|
|
1155
|
+
async writeBundle(options, bundle) {
|
|
1104
1156
|
const { optimizeCSSOptions } = workspace;
|
|
1105
1157
|
if (!optimizeCSSOptions) return;
|
|
1158
|
+
const { dir } = options;
|
|
1159
|
+
if (!dir) return;
|
|
1160
|
+
const stylesheets = await listEmittedStylesheets(workspace, dir, bundle);
|
|
1161
|
+
await Promise.all(stylesheets.map(async (fileName) => {
|
|
1162
|
+
const filePath = path.join(dir, fileName);
|
|
1163
|
+
if (processed.has(filePath)) return;
|
|
1164
|
+
let css;
|
|
1165
|
+
try {
|
|
1166
|
+
css = await fs$1.readFile(filePath, "utf8");
|
|
1167
|
+
} catch {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
processed.add(filePath);
|
|
1171
|
+
const optimized = await optimizeCSS(css, fileName, optimizeCSSOptions);
|
|
1172
|
+
if (optimized !== css) await fs$1.writeFile(filePath, optimized);
|
|
1173
|
+
}));
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/workspace/plugins/assemble-entry-css.ts
|
|
1179
|
+
function createAssembleEntryCssPlugin(workspace) {
|
|
1180
|
+
/** Chunk file name -> its identity and static imports, as of `generateBundle`. */
|
|
1181
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1182
|
+
return definePlugin({
|
|
1183
|
+
name: "plugboy-assemble-entry-css",
|
|
1184
|
+
buildStart() {
|
|
1185
|
+
graph.clear();
|
|
1186
|
+
},
|
|
1187
|
+
generateBundle(_options, bundle) {
|
|
1106
1188
|
for (const chunk of Object.values(bundle)) {
|
|
1107
|
-
if (chunk.type !== "
|
|
1108
|
-
|
|
1189
|
+
if (chunk.type !== "chunk") continue;
|
|
1190
|
+
graph.set(chunk.fileName, {
|
|
1191
|
+
name: chunk.name,
|
|
1192
|
+
isEntry: chunk.isEntry,
|
|
1193
|
+
imports: [...chunk.imports]
|
|
1194
|
+
});
|
|
1109
1195
|
}
|
|
1196
|
+
},
|
|
1197
|
+
async writeBundle(options, bundle) {
|
|
1198
|
+
const { dir } = options;
|
|
1199
|
+
if (!dir) return;
|
|
1200
|
+
if (workspace.cssOptions?.inject) return;
|
|
1201
|
+
const targets = declaredStylesheets(workspace);
|
|
1202
|
+
if (!targets.size) return;
|
|
1203
|
+
const emitted = new Set(Object.values(bundle).filter((chunk) => chunk.type === "asset" && chunk.fileName.endsWith(".css")).map((chunk) => chunk.fileName));
|
|
1204
|
+
if (!emitted.size) return;
|
|
1205
|
+
/** The stylesheet a chunk owns, if the build emitted one. */
|
|
1206
|
+
const cssOf = (fileName) => {
|
|
1207
|
+
const css = toCssFileName(fileName);
|
|
1208
|
+
return css !== fileName && emitted.has(css) ? css : void 0;
|
|
1209
|
+
};
|
|
1210
|
+
/** Stylesheets an entry needs, dependencies first, each listed once. */
|
|
1211
|
+
const collect = (entryFileName) => {
|
|
1212
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1213
|
+
const ordered = [];
|
|
1214
|
+
const walk = (fileName) => {
|
|
1215
|
+
if (visited.has(fileName)) return;
|
|
1216
|
+
visited.add(fileName);
|
|
1217
|
+
const node = graph.get(fileName);
|
|
1218
|
+
if (!node) return;
|
|
1219
|
+
for (const imported of node.imports) walk(imported);
|
|
1220
|
+
const css = cssOf(fileName);
|
|
1221
|
+
if (css && !ordered.includes(css)) ordered.push(css);
|
|
1222
|
+
};
|
|
1223
|
+
walk(entryFileName);
|
|
1224
|
+
return ordered;
|
|
1225
|
+
};
|
|
1226
|
+
const folded = /* @__PURE__ */ new Set();
|
|
1227
|
+
await Promise.all([...graph].map(async ([fileName, node]) => {
|
|
1228
|
+
if (!node.isEntry) return;
|
|
1229
|
+
const target = `${node.name}.css`;
|
|
1230
|
+
if (!targets.has(target)) return;
|
|
1231
|
+
const sources = collect(fileName);
|
|
1232
|
+
if (!sources.length) return;
|
|
1233
|
+
sources.forEach((source) => folded.add(source));
|
|
1234
|
+
if (sources.length === 1 && sources[0] === target) return;
|
|
1235
|
+
const css = (await Promise.all(sources.map((source) => fs$1.readFile(path.join(dir, source), "utf8").catch(() => "")))).filter(Boolean).join("\n");
|
|
1236
|
+
if (!css) return;
|
|
1237
|
+
await fs$1.writeFile(path.join(dir, target), css);
|
|
1238
|
+
folded.add(target);
|
|
1239
|
+
}));
|
|
1240
|
+
await Promise.all([...folded].filter((css) => !targets.has(css)).map((css) => fs$1.rm(path.join(dir, css), { force: true })));
|
|
1110
1241
|
}
|
|
1111
|
-
};
|
|
1242
|
+
});
|
|
1112
1243
|
}
|
|
1113
1244
|
//#endregion
|
|
1114
1245
|
//#region src/workspace/plugins/raw-loader.ts
|
|
@@ -1134,20 +1265,34 @@ const rawLoaderPlugin = {
|
|
|
1134
1265
|
//#endregion
|
|
1135
1266
|
//#region src/workspace/plugins/preserve-css-imports.ts
|
|
1136
1267
|
/**
|
|
1137
|
-
* Plugin to preserve
|
|
1268
|
+
* Plugin to preserve what tsdown's CSS pipeline would rewrite away at the top of
|
|
1269
|
+
* a stylesheet: external `@import` statements and the authored `@layer` order.
|
|
1138
1270
|
*
|
|
1139
|
-
* rolldown / tsdown's CSS pipeline (lightningcss)
|
|
1140
|
-
* `@import` it can. For bare package specifiers (e.g.
|
|
1271
|
+
* **External `@import`s.** rolldown / tsdown's CSS pipeline (lightningcss)
|
|
1272
|
+
* resolves and inlines every `@import` it can. For bare package specifiers (e.g.
|
|
1141
1273
|
* `@import url('material-symbols/rounded.css') layer(...)`) that is wrong for a
|
|
1142
1274
|
* library build: it bloats the output and rebases the imported package's own
|
|
1143
1275
|
* relative asset URLs (fonts) against our `dist`, breaking them. Such imports
|
|
1144
1276
|
* should stay external so the consumer's bundler resolves them.
|
|
1145
1277
|
*
|
|
1146
|
-
*
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1278
|
+
* **Layer order.** lightningcss drops a name from an `@layer a, b, c;` statement
|
|
1279
|
+
* when a block for it follows in the same stylesheet, since the block establishes
|
|
1280
|
+
* the same order. That holds for a standalone document, but not for a library
|
|
1281
|
+
* stylesheet whose statement also orders layers belonging to *other* packages:
|
|
1282
|
+
* once the name is gone, its position is decided by wherever its block happens to
|
|
1283
|
+
* land relative to those, and the authored order is lost. `@fastkit/vui` declares
|
|
1284
|
+
* `@layer vui-normalize, vui-color-scheme, …, vui;`, and losing `vui-normalize`
|
|
1285
|
+
* from it promoted the reset layer above the packages it is supposed to lose to.
|
|
1286
|
+
*
|
|
1287
|
+
* Both are captured from a `transform` hook declared `order: 'pre'`, which runs
|
|
1288
|
+
* ahead of tsdown's CSS handling even though that is registered as a *pre plugin*
|
|
1289
|
+
* — hook order wins over plugin order. It is the only point that sees the CSS of
|
|
1290
|
+
* **every** stylesheet in the graph, including a virtual one another plugin
|
|
1291
|
+
* supplies from `load`: vanilla-extract generates its `@layer` statements into
|
|
1292
|
+
* such a module, so a `load`-based capture would miss exactly the case where the
|
|
1293
|
+
* generated statement is the only record of the intended order. The captured
|
|
1294
|
+
* values are re-emitted in `writeBundle`, after every CSS producer has written
|
|
1295
|
+
* its final file to disk.
|
|
1151
1296
|
*/
|
|
1152
1297
|
/**
|
|
1153
1298
|
* Matches a single `@import` statement, capturing the quote (group 1) and the
|
|
@@ -1165,47 +1310,133 @@ function isInternalImportSpecifier(spec) {
|
|
|
1165
1310
|
}
|
|
1166
1311
|
/** Matches a top-level `@layer <names>;` statement (declaration, not a block). */
|
|
1167
1312
|
const LAYER_STATEMENT_RE = /@layer\s+([^{};]+);[ \t]*\n?/g;
|
|
1168
|
-
|
|
1313
|
+
/** Stylesheet ids, with any query (`?source=…`, `?inline`) still attached. */
|
|
1314
|
+
const STYLE_ID_RE = /\.(?:css|scss|sass|less|styl|stylus)(?:$|\?)/;
|
|
1315
|
+
/** The layer names a stylesheet declares, in the order it declares them. */
|
|
1316
|
+
function collectLayerNames(css) {
|
|
1317
|
+
const names = [];
|
|
1318
|
+
for (const [, group] of css.matchAll(LAYER_STATEMENT_RE)) for (const name of group.split(",")) {
|
|
1319
|
+
const trimmed = name.trim();
|
|
1320
|
+
if (trimmed && !names.includes(trimmed)) names.push(trimmed);
|
|
1321
|
+
}
|
|
1322
|
+
return names;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Merge several declaration orders into one that contradicts none of them.
|
|
1326
|
+
*
|
|
1327
|
+
* Concatenating them and dropping repeats does not work, because a name's first
|
|
1328
|
+
* appearance is rarely where its order is decided: vanilla-extract re-declares a
|
|
1329
|
+
* layer at the top of *every* stylesheet that puts a rule in it, so a single
|
|
1330
|
+
* `@layer that-one;` from some component is seen before the module that declares
|
|
1331
|
+
* how all the layers relate — and once the component's name is in the list, the
|
|
1332
|
+
* declaring module's order is silently dropped for it.
|
|
1333
|
+
*
|
|
1334
|
+
* Each sequence is therefore read as a set of "must come before" constraints and
|
|
1335
|
+
* the result is a topological sort of them, preferring the earliest-seen name when
|
|
1336
|
+
* several are free. Earlier sequences win: a constraint that would contradict one
|
|
1337
|
+
* already recorded is skipped, so a stylesheet's own surviving statement — whose
|
|
1338
|
+
* order tsdown may have rewritten — can add names without reordering anything.
|
|
1339
|
+
*/
|
|
1340
|
+
function mergeLayerOrder(sequences) {
|
|
1341
|
+
const nodes = [];
|
|
1342
|
+
const next = /* @__PURE__ */ new Map();
|
|
1343
|
+
const add = (name) => {
|
|
1344
|
+
if (next.has(name)) return;
|
|
1345
|
+
nodes.push(name);
|
|
1346
|
+
next.set(name, /* @__PURE__ */ new Set());
|
|
1347
|
+
};
|
|
1348
|
+
/** Whether `to` already has to come after `from`. */
|
|
1349
|
+
const precedes = (from, to) => {
|
|
1350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1351
|
+
const stack = [from];
|
|
1352
|
+
while (stack.length) {
|
|
1353
|
+
const current = stack.pop();
|
|
1354
|
+
if (current === to) return true;
|
|
1355
|
+
if (seen.has(current)) continue;
|
|
1356
|
+
seen.add(current);
|
|
1357
|
+
stack.push(...next.get(current) ?? []);
|
|
1358
|
+
}
|
|
1359
|
+
return false;
|
|
1360
|
+
};
|
|
1361
|
+
for (const sequence of sequences) {
|
|
1362
|
+
sequence.forEach(add);
|
|
1363
|
+
for (let i = 0; i + 1 < sequence.length; i++) {
|
|
1364
|
+
const from = sequence[i];
|
|
1365
|
+
const to = sequence[i + 1];
|
|
1366
|
+
if (from === to || precedes(to, from)) continue;
|
|
1367
|
+
next.get(from).add(to);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
const incoming = new Map(nodes.map((name) => [name, 0]));
|
|
1371
|
+
for (const [, targets] of next) for (const target of targets) incoming.set(target, (incoming.get(target) ?? 0) + 1);
|
|
1372
|
+
const remaining = new Set(nodes);
|
|
1373
|
+
const merged = [];
|
|
1374
|
+
while (remaining.size) {
|
|
1375
|
+
const name = nodes.find((it) => remaining.has(it) && incoming.get(it) === 0) ?? nodes.find((it) => remaining.has(it));
|
|
1376
|
+
remaining.delete(name);
|
|
1377
|
+
merged.push(name);
|
|
1378
|
+
for (const target of next.get(name) ?? []) incoming.set(target, (incoming.get(target) ?? 1) - 1);
|
|
1379
|
+
}
|
|
1380
|
+
return merged;
|
|
1381
|
+
}
|
|
1382
|
+
function createPreserveCssImportsPlugin(workspace) {
|
|
1169
1383
|
const externalImports = [];
|
|
1384
|
+
const layersByModule = /* @__PURE__ */ new Map();
|
|
1385
|
+
const declaredLayers = [];
|
|
1170
1386
|
const processed = /* @__PURE__ */ new Set();
|
|
1171
1387
|
return definePlugin({
|
|
1172
1388
|
name: "preserve-css-imports",
|
|
1173
1389
|
buildStart() {
|
|
1174
1390
|
externalImports.length = 0;
|
|
1391
|
+
layersByModule.clear();
|
|
1392
|
+
declaredLayers.length = 0;
|
|
1175
1393
|
processed.clear();
|
|
1176
1394
|
},
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1395
|
+
transform: {
|
|
1396
|
+
order: "pre",
|
|
1397
|
+
filter: { id: STYLE_ID_RE },
|
|
1398
|
+
handler(code, id) {
|
|
1399
|
+
const file = id.split("?")[0];
|
|
1400
|
+
if (!STYLE_ID_RE.test(file)) return null;
|
|
1401
|
+
const names = collectLayerNames(code);
|
|
1402
|
+
if (names.length) layersByModule.set(id, names);
|
|
1403
|
+
if (!file.endsWith(".css") || !code.includes("@import")) return null;
|
|
1404
|
+
let changed = false;
|
|
1405
|
+
const stripped = code.replace(IMPORT_RE, (statement, _quote, spec) => {
|
|
1406
|
+
if (isInternalImportSpecifier(spec)) return statement;
|
|
1407
|
+
changed = true;
|
|
1408
|
+
const normalized = statement.trim();
|
|
1409
|
+
if (!externalImports.includes(normalized)) externalImports.push(normalized);
|
|
1410
|
+
return "";
|
|
1411
|
+
});
|
|
1412
|
+
if (!changed) return null;
|
|
1413
|
+
return {
|
|
1414
|
+
code: stripped,
|
|
1415
|
+
map: null
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
generateBundle(_options, bundle) {
|
|
1420
|
+
if (!layersByModule.size) return;
|
|
1421
|
+
const sequences = [];
|
|
1422
|
+
for (const chunk of Object.values(bundle)) {
|
|
1423
|
+
if (chunk.type !== "chunk") continue;
|
|
1424
|
+
for (const id of chunk.moduleIds) {
|
|
1425
|
+
const names = layersByModule.get(id);
|
|
1426
|
+
if (names) sequences.push(names);
|
|
1427
|
+
}
|
|
1185
1428
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
const stripped = code.replace(IMPORT_RE, (statement, _quote, spec) => {
|
|
1189
|
-
if (isInternalImportSpecifier(spec)) return statement;
|
|
1190
|
-
changed = true;
|
|
1191
|
-
const normalized = statement.trim();
|
|
1192
|
-
if (!externalImports.includes(normalized)) externalImports.push(normalized);
|
|
1193
|
-
return "";
|
|
1194
|
-
});
|
|
1195
|
-
if (!changed) return null;
|
|
1196
|
-
return {
|
|
1197
|
-
code: stripped,
|
|
1198
|
-
map: null
|
|
1199
|
-
};
|
|
1429
|
+
declaredLayers.length = 0;
|
|
1430
|
+
declaredLayers.push(...mergeLayerOrder(sequences));
|
|
1200
1431
|
},
|
|
1201
1432
|
async writeBundle(options, bundle) {
|
|
1202
|
-
if (!externalImports.length) return;
|
|
1433
|
+
if (!externalImports.length && !declaredLayers.length) return;
|
|
1203
1434
|
const { dir } = options;
|
|
1204
1435
|
if (!dir) return;
|
|
1205
|
-
const importBlock = `${externalImports.join("\n")}\n
|
|
1206
|
-
await
|
|
1207
|
-
|
|
1208
|
-
const filePath = path.join(dir,
|
|
1436
|
+
const importBlock = externalImports.length ? `${externalImports.join("\n")}\n` : "";
|
|
1437
|
+
const stylesheets = await listEmittedStylesheets(workspace, dir, bundle);
|
|
1438
|
+
await Promise.all(stylesheets.map(async (fileName) => {
|
|
1439
|
+
const filePath = path.join(dir, fileName);
|
|
1209
1440
|
if (processed.has(filePath)) return;
|
|
1210
1441
|
let css;
|
|
1211
1442
|
try {
|
|
@@ -1214,14 +1445,10 @@ function createPreserveCssImportsPlugin() {
|
|
|
1214
1445
|
return;
|
|
1215
1446
|
}
|
|
1216
1447
|
processed.add(filePath);
|
|
1217
|
-
const layerNames = [];
|
|
1218
|
-
for (const [, names] of css.matchAll(LAYER_STATEMENT_RE)) for (const name of names.split(",")) {
|
|
1219
|
-
const trimmed = name.trim();
|
|
1220
|
-
if (trimmed && !layerNames.includes(trimmed)) layerNames.push(trimmed);
|
|
1221
|
-
}
|
|
1448
|
+
const layerNames = mergeLayerOrder([declaredLayers, collectLayerNames(css)]);
|
|
1222
1449
|
const body = css.replace(LAYER_STATEMENT_RE, "");
|
|
1223
|
-
const
|
|
1224
|
-
await fs$1.writeFile(filePath,
|
|
1450
|
+
const next = `${layerNames.length ? `@layer ${layerNames.join(", ")};\n` : ""}${importBlock}${body}`;
|
|
1451
|
+
if (next !== css) await fs$1.writeFile(filePath, next);
|
|
1225
1452
|
}));
|
|
1226
1453
|
}
|
|
1227
1454
|
});
|
|
@@ -1392,10 +1619,11 @@ var PlugboyWorkspace = class {
|
|
|
1392
1619
|
createExternalImportsPlugin(this),
|
|
1393
1620
|
createSuppressDtsSourcemapWarningPlugin(),
|
|
1394
1621
|
...plugins,
|
|
1622
|
+
createAssembleEntryCssPlugin(this),
|
|
1395
1623
|
OptimizeCSSPlugin(this),
|
|
1396
1624
|
WorkspaceEnvPlugin(this),
|
|
1397
1625
|
rawLoaderPlugin,
|
|
1398
|
-
createPreserveCssImportsPlugin()
|
|
1626
|
+
createPreserveCssImportsPlugin(this)
|
|
1399
1627
|
];
|
|
1400
1628
|
this.hooks = hooks;
|
|
1401
1629
|
this.dts = dts;
|
|
@@ -1717,4 +1945,4 @@ export default defineWorkspaceConfig({
|
|
|
1717
1945
|
//#endregion
|
|
1718
1946
|
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 };
|
|
1719
1947
|
|
|
1720
|
-
//# sourceMappingURL=workspace-
|
|
1948
|
+
//# sourceMappingURL=workspace-B6BoRqWL.mjs.map
|