@gjsify/lightningcss-wasm 0.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/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@gjsify/lightningcss-wasm",
3
+ "version": "0.4.0",
4
+ "description": "Phase D-2 POC: GJS-compatible loader for the lightningcss WASM bundle. Re-exports the `lightningcss-wasm` npm package's transform/bundle/bundleAsync API on top of SpiderMonkey 140's synchronous `new WebAssembly.Module/Instance` constructors. Reads the .wasm via @gjsify/fs (or Node's native fs), resolves randomness via globalThis.crypto. No WASI, no SharedArrayBuffer — single-shot CSS pipeline. Intended as the WASM-track counterpart to `@gjsify/lightningcss-native`; the better of the two wins the slot in `@gjsify/rolldown-plugin-gjsify`'s cssAsStringPlugin (see Phase D-2 decision matrix).",
5
+ "type": "module",
6
+ "main": "src/index.mjs",
7
+ "module": "src/index.mjs",
8
+ "exports": {
9
+ ".": {
10
+ "default": "./src/index.mjs"
11
+ },
12
+ "./wasm/lightningcss_node.wasm": "./wasm/lightningcss_node.wasm"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "wasm"
17
+ ],
18
+ "scripts": {
19
+ "clear": "rm -rf lib tsconfig.tsbuildinfo || exit 0",
20
+ "check": "echo 'no typecheck — vendored JS only' && exit 0"
21
+ },
22
+ "keywords": [
23
+ "gjs",
24
+ "lightningcss",
25
+ "wasm",
26
+ "css",
27
+ "minify",
28
+ "browserslist",
29
+ "napi-wasm"
30
+ ],
31
+ "dependencies": {
32
+ "@gjsify/fs": "^0.4.0",
33
+ "@gjsify/webcrypto": "^0.4.0"
34
+ },
35
+ "author": "Pascal Garber <pascal@artandcode.studio>",
36
+ "license": "MIT"
37
+ }
package/src/async.mjs ADDED
@@ -0,0 +1,74 @@
1
+ let cur_await_promise_sync;
2
+ export function await_promise_sync(promise_addr, result_addr, error_addr) {
3
+ cur_await_promise_sync(promise_addr, result_addr, error_addr);
4
+ }
5
+
6
+ const State = {
7
+ None: 0,
8
+ Unwinding: 1,
9
+ Rewinding: 2
10
+ };
11
+
12
+ // This uses Binaryen's Asyncify transform to suspend native code execution while a promise is resolving.
13
+ // That allows synchronous Rust code to call async JavaScript functions without multi-threading.
14
+ // When Rust wants to await a promise, it calls await_promise_sync, which saves the stack state and unwinds.
15
+ // That causes the bundle function to return early. If a promise has been queued, we can then await it
16
+ // and "rewind" the function back to where it was before by calling it again. This time the result of
17
+ // the promise can be returned, and the function can continue where it left off.
18
+ // See the docs in https://github.com/WebAssembly/binaryen/blob/main/src/passes/Asyncify.cpp
19
+ // The code here is also partially based on https://github.com/GoogleChromeLabs/asyncify
20
+ export function createBundleAsync(env) {
21
+ let {instance, exports} = env;
22
+ let {asyncify_get_state, asyncify_start_unwind, asyncify_stop_unwind, asyncify_start_rewind, asyncify_stop_rewind} = instance.exports;
23
+
24
+ // allocate __asyncify_data
25
+ // Stack data goes right after the initial descriptor.
26
+ let DATA_ADDR = instance.exports.napi_wasm_malloc(8 + 4096);
27
+ let DATA_START = DATA_ADDR + 8;
28
+ let DATA_END = DATA_ADDR + 8 + 4096;
29
+ new Int32Array(env.memory.buffer, DATA_ADDR).set([DATA_START, DATA_END]);
30
+
31
+ function assertNoneState() {
32
+ if (asyncify_get_state() !== State.None) {
33
+ throw new Error(`Invalid async state ${asyncify_get_state()}, expected 0.`);
34
+ }
35
+ }
36
+
37
+ let promise, result, error;
38
+ cur_await_promise_sync = (promise_addr, result_addr, error_addr) => {
39
+ let state = asyncify_get_state();
40
+ if (state === State.Rewinding) {
41
+ asyncify_stop_rewind();
42
+ if (result != null) {
43
+ env.createValue(result, result_addr);
44
+ }
45
+ if (error != null) {
46
+ env.createValue(error, error_addr);
47
+ }
48
+ promise = result = error = null;
49
+ return;
50
+ }
51
+ assertNoneState();
52
+ promise = env.get(promise_addr);
53
+ asyncify_start_unwind(DATA_ADDR);
54
+ };
55
+
56
+ return async function bundleAsync(options) {
57
+ assertNoneState();
58
+ let res = exports.bundle(options);
59
+ while (asyncify_get_state() === State.Unwinding) {
60
+ asyncify_stop_unwind();
61
+ try {
62
+ result = await promise;
63
+ } catch (err) {
64
+ error = err;
65
+ }
66
+ assertNoneState();
67
+ asyncify_start_rewind(DATA_ADDR);
68
+ res = exports.bundle(options);
69
+ }
70
+
71
+ assertNoneState();
72
+ return res;
73
+ };
74
+ }
@@ -0,0 +1,48 @@
1
+ const BROWSER_MAPPING = {
2
+ and_chr: 'chrome',
3
+ and_ff: 'firefox',
4
+ ie_mob: 'ie',
5
+ op_mob: 'opera',
6
+ and_qq: null,
7
+ and_uc: null,
8
+ baidu: null,
9
+ bb: null,
10
+ kaios: null,
11
+ op_mini: null,
12
+ };
13
+
14
+ function browserslistToTargets(browserslist) {
15
+ let targets = {};
16
+ for (let browser of browserslist) {
17
+ let [name, v] = browser.split(' ');
18
+ if (BROWSER_MAPPING[name] === null) {
19
+ continue;
20
+ }
21
+
22
+ let version = parseVersion(v);
23
+ if (version == null) {
24
+ continue;
25
+ }
26
+
27
+ if (targets[name] == null || version < targets[name]) {
28
+ targets[name] = version;
29
+ }
30
+ }
31
+
32
+ return targets;
33
+ }
34
+
35
+ function parseVersion(version) {
36
+ let [major, minor = 0, patch = 0] = version
37
+ .split('-')[0]
38
+ .split('.')
39
+ .map(v => parseInt(v, 10));
40
+
41
+ if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
42
+ return null;
43
+ }
44
+
45
+ return (major << 16) | (minor << 8) | patch;
46
+ }
47
+
48
+ export {browserslistToTargets};
@@ -0,0 +1,450 @@
1
+ // @ts-check
2
+ /** @typedef {import('./index').Visitor} Visitor */
3
+ /** @typedef {import('./index').VisitorFunction} VisitorFunction */
4
+
5
+ /**
6
+ * Composes multiple visitor objects into a single one.
7
+ * @param {(Visitor | VisitorFunction)[]} visitors
8
+ * @return {Visitor | VisitorFunction}
9
+ */
10
+ function composeVisitors(visitors) {
11
+ if (visitors.length === 1) {
12
+ return visitors[0];
13
+ }
14
+
15
+ if (visitors.some(v => typeof v === 'function')) {
16
+ return (opts) => {
17
+ let v = visitors.map(v => typeof v === 'function' ? v(opts) : v);
18
+ return composeVisitors(v);
19
+ };
20
+ }
21
+
22
+ /** @type Visitor */
23
+ let res = {};
24
+ composeSimpleVisitors(res, visitors, 'StyleSheet');
25
+ composeSimpleVisitors(res, visitors, 'StyleSheetExit');
26
+ composeObjectVisitors(res, visitors, 'Rule', ruleVisitor, wrapCustomAndUnknownAtRule);
27
+ composeObjectVisitors(res, visitors, 'RuleExit', ruleVisitor, wrapCustomAndUnknownAtRule);
28
+ composeObjectVisitors(res, visitors, 'Declaration', declarationVisitor, wrapCustomProperty);
29
+ composeObjectVisitors(res, visitors, 'DeclarationExit', declarationVisitor, wrapCustomProperty);
30
+ composeSimpleVisitors(res, visitors, 'Url');
31
+ composeSimpleVisitors(res, visitors, 'Color');
32
+ composeSimpleVisitors(res, visitors, 'Image');
33
+ composeSimpleVisitors(res, visitors, 'ImageExit');
34
+ composeSimpleVisitors(res, visitors, 'Length');
35
+ composeSimpleVisitors(res, visitors, 'Angle');
36
+ composeSimpleVisitors(res, visitors, 'Ratio');
37
+ composeSimpleVisitors(res, visitors, 'Resolution');
38
+ composeSimpleVisitors(res, visitors, 'Time');
39
+ composeSimpleVisitors(res, visitors, 'CustomIdent');
40
+ composeSimpleVisitors(res, visitors, 'DashedIdent');
41
+ composeArrayFunctions(res, visitors, 'MediaQuery');
42
+ composeArrayFunctions(res, visitors, 'MediaQueryExit');
43
+ composeSimpleVisitors(res, visitors, 'SupportsCondition');
44
+ composeSimpleVisitors(res, visitors, 'SupportsConditionExit');
45
+ composeArrayFunctions(res, visitors, 'Selector');
46
+ composeTokenVisitors(res, visitors, 'Token', 'token', false);
47
+ composeTokenVisitors(res, visitors, 'Function', 'function', false);
48
+ composeTokenVisitors(res, visitors, 'FunctionExit', 'function', true);
49
+ composeTokenVisitors(res, visitors, 'Variable', 'var', false);
50
+ composeTokenVisitors(res, visitors, 'VariableExit', 'var', true);
51
+ composeTokenVisitors(res, visitors, 'EnvironmentVariable', 'env', false);
52
+ composeTokenVisitors(res, visitors, 'EnvironmentVariableExit', 'env', true);
53
+ return res;
54
+ }
55
+
56
+ export { composeVisitors };
57
+
58
+ function wrapCustomAndUnknownAtRule(k, f) {
59
+ if (k === 'unknown') {
60
+ return (value => f({ type: 'unknown', value }));
61
+ }
62
+ if (k === 'custom') {
63
+ return (value => f({ type: 'custom', value }));
64
+ }
65
+ return f;
66
+ }
67
+
68
+ function wrapCustomProperty(k, f) {
69
+ return k === 'custom' ? (value => f({ property: 'custom', value })) : f;
70
+ }
71
+
72
+ /**
73
+ * @param {import('./index').Visitor['Rule']} f
74
+ * @param {import('./ast').Rule} item
75
+ */
76
+ function ruleVisitor(f, item) {
77
+ if (typeof f === 'object') {
78
+ if (item.type === 'unknown') {
79
+ let v = f.unknown;
80
+ if (typeof v === 'object') {
81
+ v = v[item.value.name];
82
+ }
83
+ return v?.(item.value);
84
+ }
85
+ if (item.type === 'custom') {
86
+ let v = f.custom;
87
+ if (typeof v === 'object') {
88
+ v = v[item.value.name];
89
+ }
90
+ return v?.(item.value);
91
+ }
92
+ return f[item.type]?.(item);
93
+ }
94
+ return f?.(item);
95
+ }
96
+
97
+ /**
98
+ * @param {import('./index').Visitor['Declaration']} f
99
+ * @param {import('./ast').Declaration} item
100
+ */
101
+ function declarationVisitor(f, item) {
102
+ if (typeof f === 'object') {
103
+ /** @type {string} */
104
+ let name = item.property;
105
+ if (item.property === 'unparsed') {
106
+ name = item.value.propertyId.property;
107
+ } else if (item.property === 'custom') {
108
+ let v = f.custom;
109
+ if (typeof v === 'object') {
110
+ v = v[item.value.name];
111
+ }
112
+ return v?.(item.value);
113
+ }
114
+ return f[name]?.(item);
115
+ }
116
+ return f?.(item);
117
+ }
118
+
119
+ /**
120
+ *
121
+ * @param {Visitor[]} visitors
122
+ * @param {string} key
123
+ * @returns {[any[], boolean, Set<string>]}
124
+ */
125
+ function extractObjectsOrFunctions(visitors, key) {
126
+ let values = [];
127
+ let hasFunction = false;
128
+ let allKeys = new Set();
129
+ for (let visitor of visitors) {
130
+ let v = visitor[key];
131
+ if (v) {
132
+ if (typeof v === 'function') {
133
+ hasFunction = true;
134
+ } else {
135
+ for (let key in v) {
136
+ allKeys.add(key);
137
+ }
138
+ }
139
+ values.push(v);
140
+ }
141
+ }
142
+ return [values, hasFunction, allKeys];
143
+ }
144
+
145
+ /**
146
+ * @template {keyof Visitor} K
147
+ * @param {Visitor} res
148
+ * @param {Visitor[]} visitors
149
+ * @param {K} key
150
+ * @param {(visitor: Visitor[K], item: any) => any | any[] | void} apply
151
+ * @param {(k: string, f: any) => any} wrapKey
152
+ */
153
+ function composeObjectVisitors(res, visitors, key, apply, wrapKey) {
154
+ let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key);
155
+ if (values.length === 0) {
156
+ return;
157
+ }
158
+
159
+ if (values.length === 1) {
160
+ res[key] = values[0];
161
+ return;
162
+ }
163
+
164
+ let f = createArrayVisitor(visitors, (visitor, item) => apply(visitor[key], item));
165
+ if (hasFunction) {
166
+ res[key] = f;
167
+ } else {
168
+ /** @type {any} */
169
+ let v = {};
170
+ for (let k of allKeys) {
171
+ v[k] = wrapKey(k, f);
172
+ }
173
+ res[key] = v;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * @param {Visitor} res
179
+ * @param {Visitor[]} visitors
180
+ * @param {string} key
181
+ * @param {import('./ast').TokenOrValue['type']} type
182
+ * @param {boolean} isExit
183
+ */
184
+ function composeTokenVisitors(res, visitors, key, type, isExit) {
185
+ let [values, hasFunction, allKeys] = extractObjectsOrFunctions(visitors, key);
186
+ if (values.length === 0) {
187
+ return;
188
+ }
189
+
190
+ if (values.length === 1) {
191
+ res[key] = values[0];
192
+ return;
193
+ }
194
+
195
+ let f = createTokenVisitor(visitors, type, isExit);
196
+ if (hasFunction) {
197
+ res[key] = f;
198
+ } else {
199
+ let v = {};
200
+ for (let key of allKeys) {
201
+ v[key] = f;
202
+ }
203
+ res[key] = v;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @param {Visitor[]} visitors
209
+ * @param {import('./ast').TokenOrValue['type']} type
210
+ */
211
+ function createTokenVisitor(visitors, type, isExit) {
212
+ let v = createArrayVisitor(visitors, (visitor, /** @type {import('./ast').TokenOrValue} */ item) => {
213
+ let f;
214
+ switch (item.type) {
215
+ case 'token':
216
+ f = visitor.Token;
217
+ if (typeof f === 'object') {
218
+ f = f[item.value.type];
219
+ }
220
+ break;
221
+ case 'function':
222
+ f = isExit ? visitor.FunctionExit : visitor.Function;
223
+ if (typeof f === 'object') {
224
+ f = f[item.value.name];
225
+ }
226
+ break;
227
+ case 'var':
228
+ f = isExit ? visitor.VariableExit : visitor.Variable;
229
+ break;
230
+ case 'env':
231
+ f = isExit ? visitor.EnvironmentVariableExit : visitor.EnvironmentVariable;
232
+ if (typeof f === 'object') {
233
+ let name;
234
+ switch (item.value.name.type) {
235
+ case 'ua':
236
+ case 'unknown':
237
+ name = item.value.name.value;
238
+ break;
239
+ case 'custom':
240
+ name = item.value.name.ident;
241
+ break;
242
+ }
243
+ f = f[name];
244
+ }
245
+ break;
246
+ case 'color':
247
+ f = visitor.Color;
248
+ break;
249
+ case 'url':
250
+ f = visitor.Url;
251
+ break;
252
+ case 'length':
253
+ f = visitor.Length;
254
+ break;
255
+ case 'angle':
256
+ f = visitor.Angle;
257
+ break;
258
+ case 'time':
259
+ f = visitor.Time;
260
+ break;
261
+ case 'resolution':
262
+ f = visitor.Resolution;
263
+ break;
264
+ case 'dashed-ident':
265
+ f = visitor.DashedIdent;
266
+ break;
267
+ }
268
+
269
+ if (!f) {
270
+ return;
271
+ }
272
+
273
+ let res = f(item.value);
274
+ switch (item.type) {
275
+ case 'color':
276
+ case 'url':
277
+ case 'length':
278
+ case 'angle':
279
+ case 'time':
280
+ case 'resolution':
281
+ case 'dashed-ident':
282
+ if (Array.isArray(res)) {
283
+ res = res.map(value => ({ type: item.type, value }))
284
+ } else if (res) {
285
+ res = { type: item.type, value: res };
286
+ }
287
+ break;
288
+ }
289
+
290
+ return res;
291
+ });
292
+
293
+ return value => v({ type, value });
294
+ }
295
+
296
+ /**
297
+ * @param {Visitor[]} visitors
298
+ * @param {string} key
299
+ */
300
+ function extractFunctions(visitors, key) {
301
+ let functions = [];
302
+ for (let visitor of visitors) {
303
+ let f = visitor[key];
304
+ if (f) {
305
+ functions.push(f);
306
+ }
307
+ }
308
+ return functions;
309
+ }
310
+
311
+ /**
312
+ * @param {Visitor} res
313
+ * @param {Visitor[]} visitors
314
+ * @param {string} key
315
+ */
316
+ function composeSimpleVisitors(res, visitors, key) {
317
+ let functions = extractFunctions(visitors, key);
318
+ if (functions.length === 0) {
319
+ return;
320
+ }
321
+
322
+ if (functions.length === 1) {
323
+ res[key] = functions[0];
324
+ return;
325
+ }
326
+
327
+ res[key] = arg => {
328
+ let mutated = false;
329
+ for (let f of functions) {
330
+ let res = f(arg);
331
+ if (res) {
332
+ arg = res;
333
+ mutated = true;
334
+ }
335
+ }
336
+
337
+ return mutated ? arg : undefined;
338
+ };
339
+ }
340
+
341
+ /**
342
+ * @param {Visitor} res
343
+ * @param {Visitor[]} visitors
344
+ * @param {string} key
345
+ */
346
+ function composeArrayFunctions(res, visitors, key) {
347
+ let functions = extractFunctions(visitors, key);
348
+ if (functions.length === 0) {
349
+ return;
350
+ }
351
+
352
+ if (functions.length === 1) {
353
+ res[key] = functions[0];
354
+ return;
355
+ }
356
+
357
+ res[key] = createArrayVisitor(functions, (f, item) => f(item));
358
+ }
359
+
360
+ /**
361
+ * @template T
362
+ * @template V
363
+ * @param {T[]} visitors
364
+ * @param {(visitor: T, item: V) => V | V[] | void} apply
365
+ * @returns {(item: V) => V | V[] | void}
366
+ */
367
+ function createArrayVisitor(visitors, apply) {
368
+ let seen = new Bitset(visitors.length);
369
+ return arg => {
370
+ let arr = [arg];
371
+ let mutated = false;
372
+ seen.clear();
373
+ for (let i = 0; i < arr.length; i++) {
374
+ // For each value, call all visitors. If a visitor returns a new value,
375
+ // we start over, but skip the visitor that generated the value or saw
376
+ // it before (to avoid cycles). This way, visitors can be composed in any order.
377
+ for (let v = 0; v < visitors.length && i < arr.length;) {
378
+ if (seen.get(v)) {
379
+ v++;
380
+ continue;
381
+ }
382
+
383
+ let item = arr[i];
384
+ let visitor = visitors[v];
385
+ let res = apply(visitor, item);
386
+ if (Array.isArray(res)) {
387
+ if (res.length === 0) {
388
+ arr.splice(i, 1);
389
+ } else if (res.length === 1) {
390
+ arr[i] = res[0];
391
+ } else {
392
+ arr.splice(i, 1, ...res);
393
+ }
394
+ mutated = true;
395
+ seen.set(v);
396
+ v = 0;
397
+ } else if (res) {
398
+ arr[i] = res;
399
+ mutated = true;
400
+ seen.set(v);
401
+ v = 0;
402
+ } else {
403
+ v++;
404
+ }
405
+ }
406
+ }
407
+
408
+ if (!mutated) {
409
+ return;
410
+ }
411
+
412
+ return arr.length === 1 ? arr[0] : arr;
413
+ };
414
+ }
415
+
416
+ class Bitset {
417
+ constructor(maxBits = 32) {
418
+ this.bits = 0;
419
+ this.more = maxBits > 32 ? new Uint32Array(Math.ceil((maxBits - 32) / 32)) : null;
420
+ }
421
+
422
+ /** @param {number} bit */
423
+ get(bit) {
424
+ if (bit >= 32 && this.more) {
425
+ let i = Math.floor((bit - 32) / 32);
426
+ let b = bit % 32;
427
+ return Boolean(this.more[i] & (1 << b));
428
+ } else {
429
+ return Boolean(this.bits & (1 << bit));
430
+ }
431
+ }
432
+
433
+ /** @param {number} bit */
434
+ set(bit) {
435
+ if (bit >= 32 && this.more) {
436
+ let i = Math.floor((bit - 32) / 32);
437
+ let b = bit % 32;
438
+ this.more[i] |= 1 << b;
439
+ } else {
440
+ this.bits |= 1 << bit;
441
+ }
442
+ }
443
+
444
+ clear() {
445
+ this.bits = 0;
446
+ if (this.more) {
447
+ this.more.fill(0);
448
+ }
449
+ }
450
+ }
package/src/flags.js ADDED
@@ -0,0 +1,28 @@
1
+ // This file is autogenerated by build-prefixes.js. DO NOT EDIT!
2
+
3
+ export const Features = {
4
+ Nesting: 1,
5
+ NotSelectorList: 2,
6
+ DirSelector: 4,
7
+ LangSelectorList: 8,
8
+ IsSelector: 16,
9
+ TextDecorationThicknessPercent: 32,
10
+ MediaIntervalSyntax: 64,
11
+ MediaRangeSyntax: 128,
12
+ CustomMediaQueries: 256,
13
+ ClampFunction: 512,
14
+ ColorFunction: 1024,
15
+ OklabColors: 2048,
16
+ LabColors: 4096,
17
+ P3Colors: 8192,
18
+ HexAlphaColors: 16384,
19
+ SpaceSeparatedColorNotation: 32768,
20
+ FontFamilySystemUi: 65536,
21
+ DoublePositionGradients: 131072,
22
+ VendorPrefixes: 262144,
23
+ LogicalProperties: 524288,
24
+ LightDark: 1048576,
25
+ Selectors: 31,
26
+ MediaQueries: 448,
27
+ Colors: 1113088,
28
+ };