@gjsify/rolldown-plugin-gjsify 0.16.6 → 0.16.7
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/lib/plugins/css-as-string.js +112 -15
- package/package.json +7 -7
|
@@ -12,12 +12,15 @@
|
|
|
12
12
|
//
|
|
13
13
|
// — the canonical pattern for `Gtk.CssProvider` under GJS.
|
|
14
14
|
//
|
|
15
|
-
// `@import` resolution + nesting/modern-syntax lowering
|
|
16
|
-
// lightningcss `bundleAsync
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// flattened
|
|
20
|
-
//
|
|
15
|
+
// `@import` resolution + nesting/modern-syntax lowering. On the npm backend
|
|
16
|
+
// both happen in one lightningcss `bundleAsync` call. On the native backend
|
|
17
|
+
// `@import`s are resolved + inlined in JS first (`flattenCssImports`, so bare
|
|
18
|
+
// node_modules specifiers work — the native `bundle()` FileProvider can't walk
|
|
19
|
+
// node_modules), then the flattened CSS goes through the native `transform()`
|
|
20
|
+
// for lowering. Both use the same `cssBundleResolver`. The `--app gjs`
|
|
21
|
+
// orchestrator passes `targets: { firefox: 60 << 16 }` so nesting + modern
|
|
22
|
+
// selectors get flattened to GTK4-CSS-engine-compatible output. Targeting is
|
|
23
|
+
// opt-in — a missing `targets` keeps the source pristine.
|
|
21
24
|
//
|
|
22
25
|
// Backend selection (Phase D-2 decision matrix in
|
|
23
26
|
// `docs/poc/lightningcss-decision.md`):
|
|
@@ -77,18 +80,39 @@ async function tryLoadNativeBundler() {
|
|
|
77
80
|
if (!mod.hasNativeLightningcss())
|
|
78
81
|
return null;
|
|
79
82
|
return async (filename, targets) => {
|
|
80
|
-
// The native
|
|
81
|
-
// `
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
83
|
+
// The native `@gjsify/lightningcss-native` `bundle()` resolves
|
|
84
|
+
// `@import` chains through lightningcss's filesystem-backed
|
|
85
|
+
// FileProvider, which walks relative + absolute paths but NOT
|
|
86
|
+
// bare node_modules specifiers (`@import "@scope/pkg/x.css"`) —
|
|
87
|
+
// a JS resolver callback can't cross the GI/Rust boundary. So we
|
|
88
|
+
// do `@import` resolution in JS first, using the SAME
|
|
89
|
+
// `cssBundleResolver` the npm `bundleAsync` path uses (npm-package
|
|
90
|
+
// + `exports`-map aware), and hand the fully-flattened CSS to the
|
|
91
|
+
// native `transform()` for the GTK4 nesting/modern-syntax lowering
|
|
92
|
+
// (`targets`). The native shim accepts a browserslist string; the
|
|
93
|
+
// npm `lightningcss` Targets struct is bitfield-encoded
|
|
94
|
+
// (`firefox: 60 << 16` etc), so convert per browser key.
|
|
95
|
+
let flattened;
|
|
96
|
+
try {
|
|
97
|
+
flattened = await flattenCssImports(filename);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// The native rolldown engine flattens a thrown plugin error to
|
|
101
|
+
// a generic "plugin `gjsify-css-as-string` threw an error"
|
|
102
|
+
// diagnostic — the JS message is lost across the GI boundary.
|
|
103
|
+
// Surface the actionable reason (which file, which @import) on
|
|
104
|
+
// stderr before re-throwing so a bad/missing @import is not an
|
|
105
|
+
// opaque build failure.
|
|
106
|
+
console.error(`[gjsify-css-as-string] ${err.message}`);
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
85
109
|
const query = targetsToBrowserslist(targets);
|
|
86
|
-
return mod.
|
|
110
|
+
return mod.transform({
|
|
87
111
|
filename,
|
|
112
|
+
code: flattened,
|
|
88
113
|
targets: query,
|
|
89
114
|
minify: false,
|
|
90
115
|
sourceMap: false,
|
|
91
|
-
errorRecovery: true,
|
|
92
116
|
});
|
|
93
117
|
};
|
|
94
118
|
}
|
|
@@ -155,13 +179,86 @@ const cssBundleResolver = {
|
|
|
155
179
|
// leave it verbatim so lightningcss keeps the `@font-face` /
|
|
156
180
|
// `url()` rule intact (the consumer serves the asset) instead of
|
|
157
181
|
// crashing the build. A genuine unresolvable CSS `@import`
|
|
158
|
-
// re-throws so the missing dependency
|
|
182
|
+
// re-throws with actionable context so the missing dependency
|
|
183
|
+
// surfaces clearly on both backends (npm and native).
|
|
159
184
|
if (isAssetReference(specifier))
|
|
160
185
|
return specifier;
|
|
161
|
-
throw err
|
|
186
|
+
throw new Error(`cannot resolve @import "${specifier}" from ${from} (${err.message}). ` +
|
|
187
|
+
'If it is a workspace/npm package, ensure it is installed and exposes the CSS ' +
|
|
188
|
+
'file via its package.json "exports".');
|
|
162
189
|
}
|
|
163
190
|
},
|
|
164
191
|
};
|
|
192
|
+
// Matches a CSS `@import` at-rule and captures the specifier + any trailing
|
|
193
|
+
// condition tokens (media query / `layer()` / `supports()`) before the `;`.
|
|
194
|
+
// Covers `@import "x";`, `@import 'x';`, and `@import url("x")` / `url(x)`.
|
|
195
|
+
const IMPORT_RE = /@import\s+(?:url\(\s*)?["']?([^"')\s;]+)["']?\s*\)?\s*([^;]*);/g;
|
|
196
|
+
/** `String.prototype.replace` with an async replacer (sequential, order-preserving). */
|
|
197
|
+
async function replaceAllAsync(source, re, fn) {
|
|
198
|
+
const matches = [...source.matchAll(re)];
|
|
199
|
+
if (matches.length === 0)
|
|
200
|
+
return source;
|
|
201
|
+
let out = '';
|
|
202
|
+
let last = 0;
|
|
203
|
+
for (const m of matches) {
|
|
204
|
+
const index = m.index ?? 0;
|
|
205
|
+
out += source.slice(last, index);
|
|
206
|
+
out += await fn(m[0], ...m.slice(1));
|
|
207
|
+
last = index + m[0].length;
|
|
208
|
+
}
|
|
209
|
+
out += source.slice(last);
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Recursively resolve + inline every bundleable `@import` in `entry`, using the
|
|
214
|
+
* same {@link cssBundleResolver} the npm `bundleAsync` path uses. Returns a
|
|
215
|
+
* single flattened CSS string with no bundleable `@import` statements left — the
|
|
216
|
+
* native `transform()` then applies GTK4 nesting/target lowering.
|
|
217
|
+
*
|
|
218
|
+
* This is the GJS-native counterpart of the npm `lightningcss` `bundleAsync`
|
|
219
|
+
* `@import` resolution: the native `bundle()` can only follow relative/absolute
|
|
220
|
+
* paths (lightningcss's filesystem FileProvider), so this closes the bare
|
|
221
|
+
* node_modules specifier gap (`@import "@scope/pkg/x.css"`) — the shared-CSS
|
|
222
|
+
* package pattern every consumer monorepo uses — without a Rust-side resolver
|
|
223
|
+
* hook. Asset-reference `@import`s (a bare `.woff2`/`.png`, a `data:`/`http:`
|
|
224
|
+
* URL) are kept verbatim (the resolver leaves them alone), matching the npm
|
|
225
|
+
* path. A cycle inlines each file at most once.
|
|
226
|
+
*/
|
|
227
|
+
async function flattenCssImports(entry) {
|
|
228
|
+
const seen = new Set();
|
|
229
|
+
const inline = async (file, isRoot) => {
|
|
230
|
+
const abs = isAbsolute(file) ? file : resolvePath(file);
|
|
231
|
+
if (seen.has(abs))
|
|
232
|
+
return ''; // @import cycle — inline once
|
|
233
|
+
seen.add(abs);
|
|
234
|
+
let source = await readFile(abs, 'utf8');
|
|
235
|
+
// `@charset` is only valid as the very first token of a stylesheet;
|
|
236
|
+
// an inlined sub-file's `@charset` mid-stream would be invalid CSS.
|
|
237
|
+
if (!isRoot)
|
|
238
|
+
source = source.replace(/@charset[^;]*;/gi, '');
|
|
239
|
+
return replaceAllAsync(source, IMPORT_RE, async (match, spec, condition) => {
|
|
240
|
+
const resolved = cssBundleResolver.resolve(spec, abs);
|
|
241
|
+
// Asset-reference `@import` (rare): resolver returns it verbatim —
|
|
242
|
+
// keep the at-rule intact.
|
|
243
|
+
if (resolved === spec)
|
|
244
|
+
return match;
|
|
245
|
+
const cond = condition?.trim();
|
|
246
|
+
if (cond) {
|
|
247
|
+
// Conditional `@import`s (`@import "x" screen`) wrap the inlined
|
|
248
|
+
// content per lightningcss semantics. `layer()`/`supports()`
|
|
249
|
+
// conditions have richer nesting rules we don't model — fail
|
|
250
|
+
// loudly rather than emit subtly-wrong CSS.
|
|
251
|
+
if (/\b(layer|supports)\s*\(/i.test(cond)) {
|
|
252
|
+
throw new Error(`@import "${spec}" in ${abs} uses a layer()/supports() condition, which the ` +
|
|
253
|
+
'GJS-native CSS backend does not support. Inline the import manually or drop the condition.');
|
|
254
|
+
}
|
|
255
|
+
return `@media ${cond} {\n${await inline(resolved, false)}\n}`;
|
|
256
|
+
}
|
|
257
|
+
return inline(resolved, false);
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
return inline(entry, true);
|
|
261
|
+
}
|
|
165
262
|
function targetsToBrowserslist(targets) {
|
|
166
263
|
if (!targets)
|
|
167
264
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/rolldown-plugin-gjsify",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.7",
|
|
4
4
|
"description": "Rolldown / Rollup / Vite plugin orchestrator for GJS, Node, and Browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
],
|
|
56
56
|
"license": "MIT",
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@gjsify/console": "^0.16.
|
|
59
|
-
"@gjsify/resolve-npm": "^0.16.
|
|
60
|
-
"@gjsify/rolldown-plugin-deepkit": "^0.16.
|
|
61
|
-
"@gjsify/rolldown-plugin-pnp": "^0.16.
|
|
62
|
-
"@gjsify/vite-plugin-blueprint": "^0.16.
|
|
58
|
+
"@gjsify/console": "^0.16.7",
|
|
59
|
+
"@gjsify/resolve-npm": "^0.16.7",
|
|
60
|
+
"@gjsify/rolldown-plugin-deepkit": "^0.16.7",
|
|
61
|
+
"@gjsify/rolldown-plugin-pnp": "^0.16.7",
|
|
62
|
+
"@gjsify/vite-plugin-blueprint": "^0.16.7",
|
|
63
63
|
"@rollup/pluginutils": "^5.4.0",
|
|
64
64
|
"acorn": "^8.17.0",
|
|
65
65
|
"acorn-walk": "^8.3.5",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"sass": "^1.101.0"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"@gjsify/lightningcss-native": "^0.16.
|
|
71
|
+
"@gjsify/lightningcss-native": "^0.16.7",
|
|
72
72
|
"rolldown": "^1.1.4"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|