@jesscss/plugin-js 2.0.0-alpha.6 → 2.0.0-alpha.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/bridge.d.ts +51 -0
- package/lib/bridge.d.ts.map +1 -0
- package/lib/index.cjs +158 -6
- package/lib/index.d.ts +17 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +158 -7
- package/lib/runtime-worker.cjs +412 -0
- package/lib/runtime-worker.js +411 -0
- package/package.json +3 -4
package/lib/bridge.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type JsBridgeDeclaration = {
|
|
2
|
+
name: string;
|
|
3
|
+
value: JsBridgeValue;
|
|
4
|
+
};
|
|
5
|
+
export type JsBridgeValue = {
|
|
6
|
+
__jessBridge: true;
|
|
7
|
+
kind: 'scalar';
|
|
8
|
+
value: string | number | boolean;
|
|
9
|
+
} | {
|
|
10
|
+
__jessBridge: true;
|
|
11
|
+
kind: 'dimension';
|
|
12
|
+
value: number;
|
|
13
|
+
unit?: string;
|
|
14
|
+
} | {
|
|
15
|
+
__jessBridge: true;
|
|
16
|
+
kind: 'color';
|
|
17
|
+
rgb: [number, number, number];
|
|
18
|
+
alpha?: number;
|
|
19
|
+
format?: string;
|
|
20
|
+
} | {
|
|
21
|
+
__jessBridge: true;
|
|
22
|
+
kind: 'quoted';
|
|
23
|
+
value: string;
|
|
24
|
+
quote?: '"' | '\'';
|
|
25
|
+
escaped?: boolean;
|
|
26
|
+
} | {
|
|
27
|
+
__jessBridge: true;
|
|
28
|
+
kind: 'keyword';
|
|
29
|
+
value: string;
|
|
30
|
+
} | {
|
|
31
|
+
__jessBridge: true;
|
|
32
|
+
kind: 'anonymous';
|
|
33
|
+
value: string;
|
|
34
|
+
} | {
|
|
35
|
+
__jessBridge: true;
|
|
36
|
+
kind: 'list';
|
|
37
|
+
items: JsBridgeValue[];
|
|
38
|
+
separator?: ',' | ';' | '/';
|
|
39
|
+
} | {
|
|
40
|
+
__jessBridge: true;
|
|
41
|
+
kind: 'sequence';
|
|
42
|
+
items: JsBridgeValue[];
|
|
43
|
+
} | {
|
|
44
|
+
__jessBridge: true;
|
|
45
|
+
kind: 'detached';
|
|
46
|
+
rules: JsBridgeDeclaration[];
|
|
47
|
+
};
|
|
48
|
+
export declare function encodeBridgeValue(value: unknown): unknown;
|
|
49
|
+
export declare function decodeBridgeValue(value: unknown): unknown;
|
|
50
|
+
export declare function encodeBridgeArgs(args: unknown[]): unknown[];
|
|
51
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAEzE,MAAM,MAAM,aAAa,GACrB;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GACxE;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACrG;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5F;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;CAAE,GACzF;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAChE;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC;AAqC3E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmEzD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA+BzD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAE3D"}
|
package/lib/index.cjs
CHANGED
|
@@ -33,6 +33,99 @@ let node_path = require("node:path");
|
|
|
33
33
|
node_path = __toESM(node_path);
|
|
34
34
|
let node_url = require("node:url");
|
|
35
35
|
let node_child_process = require("node:child_process");
|
|
36
|
+
//#region src/bridge.ts
|
|
37
|
+
const isBridgeValue = (value) => typeof value === "object" && value !== null && value.__jessBridge === true && typeof value.kind === "string";
|
|
38
|
+
const colorFormatFromString = (value) => {
|
|
39
|
+
if (!value) return;
|
|
40
|
+
if (value in _jesscss_core.ColorFormat) return _jesscss_core.ColorFormat[value];
|
|
41
|
+
if (Object.values(_jesscss_core.ColorFormat).includes(value)) return value;
|
|
42
|
+
};
|
|
43
|
+
function encodeBridgeChildValue(value) {
|
|
44
|
+
const encoded = encodeBridgeValue(value);
|
|
45
|
+
if (isBridgeValue(encoded)) return encoded;
|
|
46
|
+
return {
|
|
47
|
+
__jessBridge: true,
|
|
48
|
+
kind: "scalar",
|
|
49
|
+
value: typeof encoded === "string" || typeof encoded === "number" || typeof encoded === "boolean" ? encoded : String(encoded)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function encodeBridgeValue(value) {
|
|
53
|
+
if (value instanceof _jesscss_core.Dimension) return {
|
|
54
|
+
__jessBridge: true,
|
|
55
|
+
kind: "dimension",
|
|
56
|
+
value: value.number,
|
|
57
|
+
unit: value.unit
|
|
58
|
+
};
|
|
59
|
+
if (value instanceof _jesscss_core.Color) return {
|
|
60
|
+
__jessBridge: true,
|
|
61
|
+
kind: "color",
|
|
62
|
+
rgb: value.rgb,
|
|
63
|
+
alpha: value.alpha,
|
|
64
|
+
format: value.options.format === void 0 ? void 0 : _jesscss_core.ColorFormat[value.options.format]
|
|
65
|
+
};
|
|
66
|
+
if (value instanceof _jesscss_core.Quoted) return {
|
|
67
|
+
__jessBridge: true,
|
|
68
|
+
kind: "quoted",
|
|
69
|
+
value: String(value.value),
|
|
70
|
+
quote: value.quote,
|
|
71
|
+
escaped: value.escaped
|
|
72
|
+
};
|
|
73
|
+
if (value instanceof _jesscss_core.Any) return {
|
|
74
|
+
__jessBridge: true,
|
|
75
|
+
kind: value.role === "keyword" ? "keyword" : "anonymous",
|
|
76
|
+
value: value.value
|
|
77
|
+
};
|
|
78
|
+
if (value instanceof _jesscss_core.List) return {
|
|
79
|
+
__jessBridge: true,
|
|
80
|
+
kind: "list",
|
|
81
|
+
items: value.value.map(encodeBridgeChildValue),
|
|
82
|
+
separator: value.options.sep
|
|
83
|
+
};
|
|
84
|
+
if (value instanceof _jesscss_core.Sequence) return {
|
|
85
|
+
__jessBridge: true,
|
|
86
|
+
kind: "sequence",
|
|
87
|
+
items: value.value.map(encodeBridgeChildValue)
|
|
88
|
+
};
|
|
89
|
+
if (value instanceof _jesscss_core.Rules) {
|
|
90
|
+
const rules = [];
|
|
91
|
+
for (const rule of value.rules ?? []) if (rule instanceof _jesscss_core.Declaration && typeof rule.name === "string") rules.push({
|
|
92
|
+
name: rule.name,
|
|
93
|
+
value: encodeBridgeChildValue(rule.value)
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
__jessBridge: true,
|
|
97
|
+
kind: "detached",
|
|
98
|
+
rules
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
function decodeBridgeValue(value) {
|
|
104
|
+
if (!isBridgeValue(value)) return value;
|
|
105
|
+
switch (value.kind) {
|
|
106
|
+
case "scalar": return new _jesscss_core.Any(String(value.value));
|
|
107
|
+
case "dimension": return new _jesscss_core.Dimension({
|
|
108
|
+
number: value.value,
|
|
109
|
+
unit: value.unit
|
|
110
|
+
});
|
|
111
|
+
case "color": return new _jesscss_core.Color({
|
|
112
|
+
rgb: value.rgb,
|
|
113
|
+
alpha: value.alpha
|
|
114
|
+
}, { format: colorFormatFromString(value.format) });
|
|
115
|
+
case "quoted": return new _jesscss_core.Quoted(value.value, {
|
|
116
|
+
quote: value.quote,
|
|
117
|
+
escaped: value.escaped
|
|
118
|
+
});
|
|
119
|
+
case "keyword": return new _jesscss_core.Any(value.value, { role: "keyword" });
|
|
120
|
+
case "anonymous": return new _jesscss_core.Any(value.value);
|
|
121
|
+
case "list": return new _jesscss_core.List(value.items.map((item) => decodeBridgeValue(item)), { sep: value.separator });
|
|
122
|
+
case "sequence": return new _jesscss_core.Sequence(value.items.map((item) => decodeBridgeValue(item)));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function encodeBridgeArgs(args) {
|
|
126
|
+
return args.map(encodeBridgeValue);
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
36
129
|
//#region src/index.ts
|
|
37
130
|
const SCRIPT_EXTENSIONS = new Set([
|
|
38
131
|
".js",
|
|
@@ -90,6 +183,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
90
183
|
name = "js";
|
|
91
184
|
supportedExtensions = Array.from(SCRIPT_EXTENSIONS);
|
|
92
185
|
runtimeState = { status: "idle" };
|
|
186
|
+
shuttingDown = false;
|
|
93
187
|
brokerServer;
|
|
94
188
|
brokerSocketPath;
|
|
95
189
|
worker;
|
|
@@ -142,6 +236,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
142
236
|
}
|
|
143
237
|
if (this.runtimeState.status === "initializing") return this.runtimeState.promise;
|
|
144
238
|
if (this.runtimeState.status === "failed") return Promise.reject(this.runtimeState.error);
|
|
239
|
+
if (this.runtimeState.status === "disposed") return Promise.reject(/* @__PURE__ */ new Error("Deno worker has been disposed."));
|
|
145
240
|
const promise = this.startRuntime().then(() => {
|
|
146
241
|
this.runtimeState = { status: "ready" };
|
|
147
242
|
this.scheduleIdleShutdown();
|
|
@@ -206,6 +301,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
206
301
|
const socketPath = this.createBrokerPath();
|
|
207
302
|
if (process.platform !== "win32" && node_fs.existsSync(socketPath)) node_fs.unlinkSync(socketPath);
|
|
208
303
|
const server = node_net.createServer((socket) => {
|
|
304
|
+
socket.unref();
|
|
209
305
|
let buf = "";
|
|
210
306
|
socket.setEncoding("utf8");
|
|
211
307
|
socket.on("data", (chunk) => {
|
|
@@ -236,6 +332,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
236
332
|
server.once("error", reject);
|
|
237
333
|
server.listen(socketPath, () => resolve());
|
|
238
334
|
});
|
|
335
|
+
server.unref();
|
|
239
336
|
this.brokerServer = server;
|
|
240
337
|
this.brokerSocketPath = socketPath;
|
|
241
338
|
return socketPath;
|
|
@@ -248,7 +345,8 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
248
345
|
const child = (0, node_child_process.spawn)(denoCommand, [
|
|
249
346
|
"run",
|
|
250
347
|
"--no-prompt",
|
|
251
|
-
node_fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath
|
|
348
|
+
node_fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath,
|
|
349
|
+
`--runtime-api=${this.opts.runtimeApi ?? "module"}`
|
|
252
350
|
], {
|
|
253
351
|
stdio: [
|
|
254
352
|
"pipe",
|
|
@@ -261,10 +359,19 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
261
359
|
}
|
|
262
360
|
});
|
|
263
361
|
this.worker = child;
|
|
362
|
+
child.unref();
|
|
363
|
+
child.stdin.unref?.();
|
|
364
|
+
child.stdout.unref?.();
|
|
365
|
+
child.stderr.unref?.();
|
|
264
366
|
child.stdout.setEncoding("utf8");
|
|
265
367
|
child.stderr.setEncoding("utf8");
|
|
266
368
|
child.stdout.on("data", (chunk) => this.onWorkerStdout(chunk));
|
|
267
369
|
child.on("exit", () => {
|
|
370
|
+
this.worker = void 0;
|
|
371
|
+
if (this.shuttingDown) {
|
|
372
|
+
this.shuttingDown = false;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
268
375
|
const err = /* @__PURE__ */ new Error("Deno worker exited unexpectedly.");
|
|
269
376
|
this.rejectAllPending(err);
|
|
270
377
|
if (this.runtimeState.status !== "failed") this.runtimeState = {
|
|
@@ -273,9 +380,13 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
273
380
|
};
|
|
274
381
|
});
|
|
275
382
|
return new Promise((resolve, reject) => {
|
|
383
|
+
let stderrText = "";
|
|
276
384
|
const timer = setTimeout(() => {
|
|
277
|
-
reject(/* @__PURE__ */ new Error("Timed out waiting for Deno worker startup."));
|
|
385
|
+
reject(/* @__PURE__ */ new Error(stderrText.trim() ? `Timed out waiting for Deno worker startup.\n${stderrText.trim()}` : "Timed out waiting for Deno worker startup."));
|
|
278
386
|
}, BOOT_TIMEOUT_MS);
|
|
387
|
+
const onStderr = (chunk) => {
|
|
388
|
+
stderrText += chunk;
|
|
389
|
+
};
|
|
279
390
|
const onData = (chunk) => {
|
|
280
391
|
this.workerBuffer += chunk;
|
|
281
392
|
let idx = this.workerBuffer.indexOf("\n");
|
|
@@ -288,6 +399,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
288
399
|
if (JSON.parse(line).type === "ready") {
|
|
289
400
|
clearTimeout(timer);
|
|
290
401
|
child.stdout.off("data", onData);
|
|
402
|
+
child.stderr.off("data", onStderr);
|
|
291
403
|
resolve();
|
|
292
404
|
return;
|
|
293
405
|
}
|
|
@@ -295,9 +407,11 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
295
407
|
}
|
|
296
408
|
};
|
|
297
409
|
child.stdout.on("data", onData);
|
|
410
|
+
child.stderr.on("data", onStderr);
|
|
298
411
|
child.once("error", (err) => {
|
|
299
412
|
clearTimeout(timer);
|
|
300
413
|
child.stdout.off("data", onData);
|
|
414
|
+
child.stderr.off("data", onStderr);
|
|
301
415
|
reject(err);
|
|
302
416
|
});
|
|
303
417
|
});
|
|
@@ -366,7 +480,13 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
366
480
|
}
|
|
367
481
|
shutdown() {
|
|
368
482
|
this.clearIdleTimer();
|
|
369
|
-
if (this.worker && !this.worker.killed)
|
|
483
|
+
if (this.worker && !this.worker.killed) {
|
|
484
|
+
this.shuttingDown = true;
|
|
485
|
+
this.worker.stdin.destroy();
|
|
486
|
+
this.worker.stdout.destroy();
|
|
487
|
+
this.worker.stderr.destroy();
|
|
488
|
+
this.worker.kill();
|
|
489
|
+
}
|
|
370
490
|
this.worker = void 0;
|
|
371
491
|
if (this.brokerServer) {
|
|
372
492
|
this.brokerServer.close();
|
|
@@ -379,7 +499,8 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
379
499
|
}
|
|
380
500
|
dispose() {
|
|
381
501
|
this.shutdown();
|
|
382
|
-
this
|
|
502
|
+
JsPlugin.liveInstances.delete(this);
|
|
503
|
+
this.runtimeState = { status: "disposed" };
|
|
383
504
|
}
|
|
384
505
|
assertAllowedPath(absoluteFilePath) {
|
|
385
506
|
const resolvedPath = node_path.resolve(absoluteFilePath);
|
|
@@ -408,10 +529,10 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
408
529
|
type: "invoke",
|
|
409
530
|
modulePath,
|
|
410
531
|
exportName: item.name,
|
|
411
|
-
args
|
|
532
|
+
args: encodeBridgeArgs(args)
|
|
412
533
|
});
|
|
413
534
|
if (!invokeResult.ok) throw new Error(invokeResult.error);
|
|
414
|
-
return invokeResult.value;
|
|
535
|
+
return decodeBridgeValue(invokeResult.value);
|
|
415
536
|
};
|
|
416
537
|
else moduleObject[item.name] = item.value;
|
|
417
538
|
return moduleObject;
|
|
@@ -421,6 +542,36 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
421
542
|
for (const [key, value] of Object.entries(module)) if (typeof value === "function" || isJsonValue(value)) safeModule[key] = value;
|
|
422
543
|
return safeModule;
|
|
423
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* Loads a legacy Less `@plugin` wrapper file in Deno Less-compat mode.
|
|
547
|
+
*
|
|
548
|
+
* @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
|
|
549
|
+
* ESM-style script imports or `@-use` for Sass-module-style namespace imports.
|
|
550
|
+
*/
|
|
551
|
+
async importLessPlugin(absoluteFilePath) {
|
|
552
|
+
const ext = node_path.extname(absoluteFilePath);
|
|
553
|
+
if (!SCRIPT_EXTENSIONS.has(ext)) throw new Error(`Plugin "${this.name}" cannot import Less plugin "${absoluteFilePath}"`);
|
|
554
|
+
this.assertAllowedPath(absoluteFilePath);
|
|
555
|
+
await this.ensureRuntime();
|
|
556
|
+
const modulePath = node_path.resolve(absoluteFilePath);
|
|
557
|
+
const loadResult = await this.callWorker({
|
|
558
|
+
type: "loadLessPlugin",
|
|
559
|
+
modulePath
|
|
560
|
+
});
|
|
561
|
+
if (!loadResult.ok) throw new Error(loadResult.error);
|
|
562
|
+
const functions = {};
|
|
563
|
+
for (const functionName of loadResult.functions ?? []) functions[functionName] = async (...args) => {
|
|
564
|
+
const invokeResult = await this.callWorker({
|
|
565
|
+
type: "invokeLessPluginFunction",
|
|
566
|
+
modulePath,
|
|
567
|
+
functionName,
|
|
568
|
+
args: encodeBridgeArgs(args)
|
|
569
|
+
});
|
|
570
|
+
if (!invokeResult.ok) throw new Error(invokeResult.error);
|
|
571
|
+
return decodeBridgeValue(invokeResult.value);
|
|
572
|
+
};
|
|
573
|
+
return { functions };
|
|
574
|
+
}
|
|
424
575
|
};
|
|
425
576
|
/**
|
|
426
577
|
* Global flag set when @jesscss/plugin-js is loaded.
|
|
@@ -434,4 +585,5 @@ const jsPlugin = ((opts) => {
|
|
|
434
585
|
//#endregion
|
|
435
586
|
exports.JESS_PLUGIN_JS_GLOBAL = JESS_PLUGIN_JS_GLOBAL;
|
|
436
587
|
exports.JsPlugin = JsPlugin;
|
|
588
|
+
exports.__toESM = __toESM;
|
|
437
589
|
exports.default = jsPlugin;
|
package/lib/index.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export type JavaScriptSandboxConfig = {
|
|
|
6
6
|
};
|
|
7
7
|
export interface JsPluginOptions extends JavaScriptSandboxConfig {
|
|
8
8
|
denoCommand?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Runtime API exposed inside the Deno worker.
|
|
11
|
+
*
|
|
12
|
+
* Jess `@-use`/script imports run as plain ESM modules. Legacy Less
|
|
13
|
+
* `@plugin` loading can opt into the Less-compatible global shape.
|
|
14
|
+
*/
|
|
15
|
+
runtimeApi?: 'module' | 'less';
|
|
9
16
|
}
|
|
10
17
|
export declare class JsPlugin extends AbstractPlugin {
|
|
11
18
|
opts: JsPluginOptions;
|
|
@@ -14,6 +21,7 @@ export declare class JsPlugin extends AbstractPlugin {
|
|
|
14
21
|
name: string;
|
|
15
22
|
supportedExtensions: string[];
|
|
16
23
|
private runtimeState;
|
|
24
|
+
private shuttingDown;
|
|
17
25
|
private brokerServer;
|
|
18
26
|
private brokerSocketPath;
|
|
19
27
|
private worker;
|
|
@@ -42,6 +50,15 @@ export declare class JsPlugin extends AbstractPlugin {
|
|
|
42
50
|
dispose(): void;
|
|
43
51
|
private assertAllowedPath;
|
|
44
52
|
import(absoluteFilePath: string): Promise<Record<string, any>>;
|
|
53
|
+
/**
|
|
54
|
+
* Loads a legacy Less `@plugin` wrapper file in Deno Less-compat mode.
|
|
55
|
+
*
|
|
56
|
+
* @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
|
|
57
|
+
* ESM-style script imports or `@-use` for Sass-module-style namespace imports.
|
|
58
|
+
*/
|
|
59
|
+
importLessPlugin(absoluteFilePath: string): Promise<{
|
|
60
|
+
functions: Record<string, (...args: unknown[]) => Promise<unknown>>;
|
|
61
|
+
}>;
|
|
45
62
|
}
|
|
46
63
|
/**
|
|
47
64
|
* Global flag set when @jesscss/plugin-js is loaded.
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EACf,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EACf,MAAM,eAAe,CAAC;AAQvB,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,eAAgB,SAAQ,uBAAuB;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAChC;AA8GD,qBAAa,QAAS,SAAQ,cAAc;IAoBvB,IAAI,EAAE,eAAe;IAnBxC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAS;IACzC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAuB;IACnD,IAAI,SAAQ;IACZ,mBAAmB,WAAiC;IACpD,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,OAAO,CAIV;IAEL,OAAO,CAAC,SAAS,CAA6B;gBAE3B,IAAI,GAAE,eAAoB;IAM7C,OAAO;IAIP,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAkBrC,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,aAAa;IA4BrB,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,mBAAmB;YA0Bb,WAAW;IA8CzB,OAAO,CAAC,WAAW;YAwFL,YAAY;IAW1B,OAAO,CAAC,cAAc;IAiCtB,OAAO,CAAC,gBAAgB;YAQV,UAAU;IAwBxB,OAAO,CAAC,QAAQ;IA0BhB,OAAO;IAMP,OAAO,CAAC,iBAAiB;IAgBnB,MAAM,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IA+CpE;;;;;OAKG;IACG,gBAAgB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;KAAE,CAAC;CA6BnI;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,iCAAiC,CAAC;AAOpE,QAAA,MAAM,QAAQ,UAAY,eAAe,aAEtB,CAAC;AAEpB,eAAe,QAAQ,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,102 @@
|
|
|
1
|
-
import { AbstractPlugin } from "@jesscss/core";
|
|
1
|
+
import { AbstractPlugin, Any, Color, ColorFormat, Declaration, Dimension, List, Quoted, Rules, Sequence } from "@jesscss/core";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as net from "node:net";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
+
//#region src/bridge.ts
|
|
8
|
+
const isBridgeValue = (value) => typeof value === "object" && value !== null && value.__jessBridge === true && typeof value.kind === "string";
|
|
9
|
+
const colorFormatFromString = (value) => {
|
|
10
|
+
if (!value) return;
|
|
11
|
+
if (value in ColorFormat) return ColorFormat[value];
|
|
12
|
+
if (Object.values(ColorFormat).includes(value)) return value;
|
|
13
|
+
};
|
|
14
|
+
function encodeBridgeChildValue(value) {
|
|
15
|
+
const encoded = encodeBridgeValue(value);
|
|
16
|
+
if (isBridgeValue(encoded)) return encoded;
|
|
17
|
+
return {
|
|
18
|
+
__jessBridge: true,
|
|
19
|
+
kind: "scalar",
|
|
20
|
+
value: typeof encoded === "string" || typeof encoded === "number" || typeof encoded === "boolean" ? encoded : String(encoded)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function encodeBridgeValue(value) {
|
|
24
|
+
if (value instanceof Dimension) return {
|
|
25
|
+
__jessBridge: true,
|
|
26
|
+
kind: "dimension",
|
|
27
|
+
value: value.number,
|
|
28
|
+
unit: value.unit
|
|
29
|
+
};
|
|
30
|
+
if (value instanceof Color) return {
|
|
31
|
+
__jessBridge: true,
|
|
32
|
+
kind: "color",
|
|
33
|
+
rgb: value.rgb,
|
|
34
|
+
alpha: value.alpha,
|
|
35
|
+
format: value.options.format === void 0 ? void 0 : ColorFormat[value.options.format]
|
|
36
|
+
};
|
|
37
|
+
if (value instanceof Quoted) return {
|
|
38
|
+
__jessBridge: true,
|
|
39
|
+
kind: "quoted",
|
|
40
|
+
value: String(value.value),
|
|
41
|
+
quote: value.quote,
|
|
42
|
+
escaped: value.escaped
|
|
43
|
+
};
|
|
44
|
+
if (value instanceof Any) return {
|
|
45
|
+
__jessBridge: true,
|
|
46
|
+
kind: value.role === "keyword" ? "keyword" : "anonymous",
|
|
47
|
+
value: value.value
|
|
48
|
+
};
|
|
49
|
+
if (value instanceof List) return {
|
|
50
|
+
__jessBridge: true,
|
|
51
|
+
kind: "list",
|
|
52
|
+
items: value.value.map(encodeBridgeChildValue),
|
|
53
|
+
separator: value.options.sep
|
|
54
|
+
};
|
|
55
|
+
if (value instanceof Sequence) return {
|
|
56
|
+
__jessBridge: true,
|
|
57
|
+
kind: "sequence",
|
|
58
|
+
items: value.value.map(encodeBridgeChildValue)
|
|
59
|
+
};
|
|
60
|
+
if (value instanceof Rules) {
|
|
61
|
+
const rules = [];
|
|
62
|
+
for (const rule of value.rules ?? []) if (rule instanceof Declaration && typeof rule.name === "string") rules.push({
|
|
63
|
+
name: rule.name,
|
|
64
|
+
value: encodeBridgeChildValue(rule.value)
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
__jessBridge: true,
|
|
68
|
+
kind: "detached",
|
|
69
|
+
rules
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function decodeBridgeValue(value) {
|
|
75
|
+
if (!isBridgeValue(value)) return value;
|
|
76
|
+
switch (value.kind) {
|
|
77
|
+
case "scalar": return new Any(String(value.value));
|
|
78
|
+
case "dimension": return new Dimension({
|
|
79
|
+
number: value.value,
|
|
80
|
+
unit: value.unit
|
|
81
|
+
});
|
|
82
|
+
case "color": return new Color({
|
|
83
|
+
rgb: value.rgb,
|
|
84
|
+
alpha: value.alpha
|
|
85
|
+
}, { format: colorFormatFromString(value.format) });
|
|
86
|
+
case "quoted": return new Quoted(value.value, {
|
|
87
|
+
quote: value.quote,
|
|
88
|
+
escaped: value.escaped
|
|
89
|
+
});
|
|
90
|
+
case "keyword": return new Any(value.value, { role: "keyword" });
|
|
91
|
+
case "anonymous": return new Any(value.value);
|
|
92
|
+
case "list": return new List(value.items.map((item) => decodeBridgeValue(item)), { sep: value.separator });
|
|
93
|
+
case "sequence": return new Sequence(value.items.map((item) => decodeBridgeValue(item)));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function encodeBridgeArgs(args) {
|
|
97
|
+
return args.map(encodeBridgeValue);
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
7
100
|
//#region src/index.ts
|
|
8
101
|
const SCRIPT_EXTENSIONS = new Set([
|
|
9
102
|
".js",
|
|
@@ -61,6 +154,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
61
154
|
name = "js";
|
|
62
155
|
supportedExtensions = Array.from(SCRIPT_EXTENSIONS);
|
|
63
156
|
runtimeState = { status: "idle" };
|
|
157
|
+
shuttingDown = false;
|
|
64
158
|
brokerServer;
|
|
65
159
|
brokerSocketPath;
|
|
66
160
|
worker;
|
|
@@ -113,6 +207,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
113
207
|
}
|
|
114
208
|
if (this.runtimeState.status === "initializing") return this.runtimeState.promise;
|
|
115
209
|
if (this.runtimeState.status === "failed") return Promise.reject(this.runtimeState.error);
|
|
210
|
+
if (this.runtimeState.status === "disposed") return Promise.reject(/* @__PURE__ */ new Error("Deno worker has been disposed."));
|
|
116
211
|
const promise = this.startRuntime().then(() => {
|
|
117
212
|
this.runtimeState = { status: "ready" };
|
|
118
213
|
this.scheduleIdleShutdown();
|
|
@@ -177,6 +272,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
177
272
|
const socketPath = this.createBrokerPath();
|
|
178
273
|
if (process.platform !== "win32" && fs.existsSync(socketPath)) fs.unlinkSync(socketPath);
|
|
179
274
|
const server = net.createServer((socket) => {
|
|
275
|
+
socket.unref();
|
|
180
276
|
let buf = "";
|
|
181
277
|
socket.setEncoding("utf8");
|
|
182
278
|
socket.on("data", (chunk) => {
|
|
@@ -207,6 +303,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
207
303
|
server.once("error", reject);
|
|
208
304
|
server.listen(socketPath, () => resolve());
|
|
209
305
|
});
|
|
306
|
+
server.unref();
|
|
210
307
|
this.brokerServer = server;
|
|
211
308
|
this.brokerSocketPath = socketPath;
|
|
212
309
|
return socketPath;
|
|
@@ -219,7 +316,8 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
219
316
|
const child = spawn(denoCommand, [
|
|
220
317
|
"run",
|
|
221
318
|
"--no-prompt",
|
|
222
|
-
fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath
|
|
319
|
+
fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath,
|
|
320
|
+
`--runtime-api=${this.opts.runtimeApi ?? "module"}`
|
|
223
321
|
], {
|
|
224
322
|
stdio: [
|
|
225
323
|
"pipe",
|
|
@@ -232,10 +330,19 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
232
330
|
}
|
|
233
331
|
});
|
|
234
332
|
this.worker = child;
|
|
333
|
+
child.unref();
|
|
334
|
+
child.stdin.unref?.();
|
|
335
|
+
child.stdout.unref?.();
|
|
336
|
+
child.stderr.unref?.();
|
|
235
337
|
child.stdout.setEncoding("utf8");
|
|
236
338
|
child.stderr.setEncoding("utf8");
|
|
237
339
|
child.stdout.on("data", (chunk) => this.onWorkerStdout(chunk));
|
|
238
340
|
child.on("exit", () => {
|
|
341
|
+
this.worker = void 0;
|
|
342
|
+
if (this.shuttingDown) {
|
|
343
|
+
this.shuttingDown = false;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
239
346
|
const err = /* @__PURE__ */ new Error("Deno worker exited unexpectedly.");
|
|
240
347
|
this.rejectAllPending(err);
|
|
241
348
|
if (this.runtimeState.status !== "failed") this.runtimeState = {
|
|
@@ -244,9 +351,13 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
244
351
|
};
|
|
245
352
|
});
|
|
246
353
|
return new Promise((resolve, reject) => {
|
|
354
|
+
let stderrText = "";
|
|
247
355
|
const timer = setTimeout(() => {
|
|
248
|
-
reject(/* @__PURE__ */ new Error("Timed out waiting for Deno worker startup."));
|
|
356
|
+
reject(/* @__PURE__ */ new Error(stderrText.trim() ? `Timed out waiting for Deno worker startup.\n${stderrText.trim()}` : "Timed out waiting for Deno worker startup."));
|
|
249
357
|
}, BOOT_TIMEOUT_MS);
|
|
358
|
+
const onStderr = (chunk) => {
|
|
359
|
+
stderrText += chunk;
|
|
360
|
+
};
|
|
250
361
|
const onData = (chunk) => {
|
|
251
362
|
this.workerBuffer += chunk;
|
|
252
363
|
let idx = this.workerBuffer.indexOf("\n");
|
|
@@ -259,6 +370,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
259
370
|
if (JSON.parse(line).type === "ready") {
|
|
260
371
|
clearTimeout(timer);
|
|
261
372
|
child.stdout.off("data", onData);
|
|
373
|
+
child.stderr.off("data", onStderr);
|
|
262
374
|
resolve();
|
|
263
375
|
return;
|
|
264
376
|
}
|
|
@@ -266,9 +378,11 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
266
378
|
}
|
|
267
379
|
};
|
|
268
380
|
child.stdout.on("data", onData);
|
|
381
|
+
child.stderr.on("data", onStderr);
|
|
269
382
|
child.once("error", (err) => {
|
|
270
383
|
clearTimeout(timer);
|
|
271
384
|
child.stdout.off("data", onData);
|
|
385
|
+
child.stderr.off("data", onStderr);
|
|
272
386
|
reject(err);
|
|
273
387
|
});
|
|
274
388
|
});
|
|
@@ -337,7 +451,13 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
337
451
|
}
|
|
338
452
|
shutdown() {
|
|
339
453
|
this.clearIdleTimer();
|
|
340
|
-
if (this.worker && !this.worker.killed)
|
|
454
|
+
if (this.worker && !this.worker.killed) {
|
|
455
|
+
this.shuttingDown = true;
|
|
456
|
+
this.worker.stdin.destroy();
|
|
457
|
+
this.worker.stdout.destroy();
|
|
458
|
+
this.worker.stderr.destroy();
|
|
459
|
+
this.worker.kill();
|
|
460
|
+
}
|
|
341
461
|
this.worker = void 0;
|
|
342
462
|
if (this.brokerServer) {
|
|
343
463
|
this.brokerServer.close();
|
|
@@ -350,7 +470,8 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
350
470
|
}
|
|
351
471
|
dispose() {
|
|
352
472
|
this.shutdown();
|
|
353
|
-
this
|
|
473
|
+
JsPlugin.liveInstances.delete(this);
|
|
474
|
+
this.runtimeState = { status: "disposed" };
|
|
354
475
|
}
|
|
355
476
|
assertAllowedPath(absoluteFilePath) {
|
|
356
477
|
const resolvedPath = path.resolve(absoluteFilePath);
|
|
@@ -379,10 +500,10 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
379
500
|
type: "invoke",
|
|
380
501
|
modulePath,
|
|
381
502
|
exportName: item.name,
|
|
382
|
-
args
|
|
503
|
+
args: encodeBridgeArgs(args)
|
|
383
504
|
});
|
|
384
505
|
if (!invokeResult.ok) throw new Error(invokeResult.error);
|
|
385
|
-
return invokeResult.value;
|
|
506
|
+
return decodeBridgeValue(invokeResult.value);
|
|
386
507
|
};
|
|
387
508
|
else moduleObject[item.name] = item.value;
|
|
388
509
|
return moduleObject;
|
|
@@ -392,6 +513,36 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
392
513
|
for (const [key, value] of Object.entries(module)) if (typeof value === "function" || isJsonValue(value)) safeModule[key] = value;
|
|
393
514
|
return safeModule;
|
|
394
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Loads a legacy Less `@plugin` wrapper file in Deno Less-compat mode.
|
|
518
|
+
*
|
|
519
|
+
* @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
|
|
520
|
+
* ESM-style script imports or `@-use` for Sass-module-style namespace imports.
|
|
521
|
+
*/
|
|
522
|
+
async importLessPlugin(absoluteFilePath) {
|
|
523
|
+
const ext = path.extname(absoluteFilePath);
|
|
524
|
+
if (!SCRIPT_EXTENSIONS.has(ext)) throw new Error(`Plugin "${this.name}" cannot import Less plugin "${absoluteFilePath}"`);
|
|
525
|
+
this.assertAllowedPath(absoluteFilePath);
|
|
526
|
+
await this.ensureRuntime();
|
|
527
|
+
const modulePath = path.resolve(absoluteFilePath);
|
|
528
|
+
const loadResult = await this.callWorker({
|
|
529
|
+
type: "loadLessPlugin",
|
|
530
|
+
modulePath
|
|
531
|
+
});
|
|
532
|
+
if (!loadResult.ok) throw new Error(loadResult.error);
|
|
533
|
+
const functions = {};
|
|
534
|
+
for (const functionName of loadResult.functions ?? []) functions[functionName] = async (...args) => {
|
|
535
|
+
const invokeResult = await this.callWorker({
|
|
536
|
+
type: "invokeLessPluginFunction",
|
|
537
|
+
modulePath,
|
|
538
|
+
functionName,
|
|
539
|
+
args: encodeBridgeArgs(args)
|
|
540
|
+
});
|
|
541
|
+
if (!invokeResult.ok) throw new Error(invokeResult.error);
|
|
542
|
+
return decodeBridgeValue(invokeResult.value);
|
|
543
|
+
};
|
|
544
|
+
return { functions };
|
|
545
|
+
}
|
|
395
546
|
};
|
|
396
547
|
/**
|
|
397
548
|
* Global flag set when @jesscss/plugin-js is loaded.
|