@dimina-kit/compiler 0.0.1-dev.20260707110006 → 0.0.2-dev.20260710085051
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/dist/compile-core.browser.js +17 -8
- package/dist/compile-core.node.js +2 -2
- package/dist/pool.browser.js +6 -5
- package/dist/stage-worker.browser.js +23 -14
- package/package.json +3 -2
- package/scripts/test-pool-filetypes.js +159 -0
- package/scripts/toolchain-setup-node-native.js +13 -0
- package/src/compile-core.js +6 -3
- package/src/pool.js +21 -8
- package/src/stage-worker.js +12 -8
|
@@ -90457,9 +90457,9 @@ var require_dist3 = __commonJS({
|
|
|
90457
90457
|
}
|
|
90458
90458
|
});
|
|
90459
90459
|
|
|
90460
|
-
// ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.
|
|
90460
|
+
// ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.51/node_modules/node-releases/data/processed/envs.json
|
|
90461
90461
|
var require_envs2 = __commonJS({
|
|
90462
|
-
"../../dimina/fe/node_modules/.pnpm/node-releases@2.0.
|
|
90462
|
+
"../../dimina/fe/node_modules/.pnpm/node-releases@2.0.51/node_modules/node-releases/data/processed/envs.json"(exports, module) {
|
|
90463
90463
|
module.exports = [
|
|
90464
90464
|
{
|
|
90465
90465
|
name: "nodejs",
|
|
@@ -93420,6 +93420,14 @@ var require_envs2 = __commonJS({
|
|
|
93420
93420
|
lts: false,
|
|
93421
93421
|
security: false,
|
|
93422
93422
|
v8: "14.6.202.34"
|
|
93423
|
+
},
|
|
93424
|
+
{
|
|
93425
|
+
name: "nodejs",
|
|
93426
|
+
version: "26.5.0",
|
|
93427
|
+
date: "2026-07-08",
|
|
93428
|
+
lts: false,
|
|
93429
|
+
security: false,
|
|
93430
|
+
v8: "14.6.202.34"
|
|
93423
93431
|
}
|
|
93424
93432
|
];
|
|
93425
93433
|
}
|
|
@@ -93509,9 +93517,9 @@ var require_agents4 = __commonJS({
|
|
|
93509
93517
|
}
|
|
93510
93518
|
});
|
|
93511
93519
|
|
|
93512
|
-
// ../../dimina/fe/node_modules/.pnpm/electron-to-chromium@1.5.
|
|
93520
|
+
// ../../dimina/fe/node_modules/.pnpm/electron-to-chromium@1.5.389/node_modules/electron-to-chromium/versions.js
|
|
93513
93521
|
var require_versions2 = __commonJS({
|
|
93514
|
-
"../../dimina/fe/node_modules/.pnpm/electron-to-chromium@1.5.
|
|
93522
|
+
"../../dimina/fe/node_modules/.pnpm/electron-to-chromium@1.5.389/node_modules/electron-to-chromium/versions.js"(exports, module) {
|
|
93515
93523
|
module.exports = {
|
|
93516
93524
|
"0.20": "39",
|
|
93517
93525
|
"0.21": "41",
|
|
@@ -93771,14 +93779,15 @@ var require_versions2 = __commonJS({
|
|
|
93771
93779
|
"42.5": "148",
|
|
93772
93780
|
"42.6": "148",
|
|
93773
93781
|
"43.0": "150",
|
|
93782
|
+
"43.1": "150",
|
|
93774
93783
|
"44.0": "151"
|
|
93775
93784
|
};
|
|
93776
93785
|
}
|
|
93777
93786
|
});
|
|
93778
93787
|
|
|
93779
|
-
// ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.
|
|
93788
|
+
// ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.51/node_modules/node-releases/data/release-schedule/release-schedule.json
|
|
93780
93789
|
var require_release_schedule2 = __commonJS({
|
|
93781
|
-
"../../dimina/fe/node_modules/.pnpm/node-releases@2.0.
|
|
93790
|
+
"../../dimina/fe/node_modules/.pnpm/node-releases@2.0.51/node_modules/node-releases/data/release-schedule/release-schedule.json"(exports, module) {
|
|
93782
93791
|
module.exports = {
|
|
93783
93792
|
"v0.8": {
|
|
93784
93793
|
start: "2012-06-25",
|
|
@@ -282555,8 +282564,8 @@ function compileMiniApp(opts = {}) {
|
|
|
282555
282564
|
});
|
|
282556
282565
|
return result2;
|
|
282557
282566
|
}
|
|
282558
|
-
async function runCompile({ fs: fs2, workPath = "/work" } = {}) {
|
|
282559
|
-
const ctx = await setupCompile({ fs: fs2, workPath });
|
|
282567
|
+
async function runCompile({ fs: fs2, workPath = "/work", options = {} } = {}) {
|
|
282568
|
+
const ctx = await setupCompile({ fs: fs2, workPath, options });
|
|
282560
282569
|
const { storeInfo: bundle, pages, appId, name, targetPath } = ctx;
|
|
282561
282570
|
await compileStage({ stage: "logic", pages, storeInfo: bundle, fs: fs2 });
|
|
282562
282571
|
await compileStage({ stage: "view", pages, storeInfo: bundle, fs: fs2 });
|
|
@@ -477,8 +477,8 @@ function compileMiniApp(opts = {}) {
|
|
|
477
477
|
});
|
|
478
478
|
return result;
|
|
479
479
|
}
|
|
480
|
-
async function runCompile({ fs, workPath = "/work" } = {}) {
|
|
481
|
-
const ctx = await setupCompile({ fs, workPath });
|
|
480
|
+
async function runCompile({ fs, workPath = "/work", options = {} } = {}) {
|
|
481
|
+
const ctx = await setupCompile({ fs, workPath, options });
|
|
482
482
|
const { storeInfo: bundle, pages, appId, name, targetPath } = ctx;
|
|
483
483
|
await compileStage({ stage: "logic", pages, storeInfo: bundle, fs });
|
|
484
484
|
await compileStage({ stage: "view", pages, storeInfo: bundle, fs });
|
package/dist/pool.browser.js
CHANGED
|
@@ -252,9 +252,9 @@ function createCompilerPool(options = {}) {
|
|
|
252
252
|
function warmup() {
|
|
253
253
|
return settleAll(workers.map(ensureWarm));
|
|
254
254
|
}
|
|
255
|
-
async function runAttempt(files, workPath) {
|
|
255
|
+
async function runAttempt(files, workPath, options2) {
|
|
256
256
|
await settleAll(workers.map(ensureWarm));
|
|
257
|
-
const s = await requestChecked(workers[0], { type: "setup", files, workPath, wantHeartbeat: true }, "setup", sendTimeoutMs);
|
|
257
|
+
const s = await requestChecked(workers[0], { type: "setup", files, workPath, options: options2, wantHeartbeat: true }, "setup", sendTimeoutMs);
|
|
258
258
|
const { bundle, scaffold } = s;
|
|
259
259
|
const parts = await settleAll(workers.map((x) => requestChecked(x, { type: "compile-subset", files, workPath, stages: [x.stage], bundle, wantHeartbeat: true }, "compile-subset", sendTimeoutMs)));
|
|
260
260
|
const merged = { ...scaffold || {} };
|
|
@@ -271,14 +271,15 @@ function createCompilerPool(options = {}) {
|
|
|
271
271
|
}
|
|
272
272
|
const files = input.files || input;
|
|
273
273
|
if (!files || typeof files !== "object" || !Object.keys(files).length) {
|
|
274
|
-
throw new Error("[compiler] pool.compile expects { files: { relPath: content }, workPath? } (or a non-empty files map)");
|
|
274
|
+
throw new Error("[compiler] pool.compile expects { files: { relPath: content }, workPath?, options? } (or a non-empty files map)");
|
|
275
275
|
}
|
|
276
276
|
const workPath = input.workPath || defaultWorkPath;
|
|
277
|
+
const options2 = input.options || {};
|
|
277
278
|
try {
|
|
278
|
-
return await runAttempt(files, workPath);
|
|
279
|
+
return await runAttempt(files, workPath, options2);
|
|
279
280
|
} catch (err) {
|
|
280
281
|
if (!retryOnWorkerDeath || !err || !WORKER_DEATH_CODES.has(err.code)) throw err;
|
|
281
|
-
return await runAttempt(files, workPath);
|
|
282
|
+
return await runAttempt(files, workPath, options2);
|
|
282
283
|
}
|
|
283
284
|
});
|
|
284
285
|
chain = run.then(() => {
|
|
@@ -104394,9 +104394,9 @@ var require_dist3 = __commonJS({
|
|
|
104394
104394
|
}
|
|
104395
104395
|
});
|
|
104396
104396
|
|
|
104397
|
-
// ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.
|
|
104397
|
+
// ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.51/node_modules/node-releases/data/processed/envs.json
|
|
104398
104398
|
var require_envs2 = __commonJS({
|
|
104399
|
-
"../../dimina/fe/node_modules/.pnpm/node-releases@2.0.
|
|
104399
|
+
"../../dimina/fe/node_modules/.pnpm/node-releases@2.0.51/node_modules/node-releases/data/processed/envs.json"(exports, module) {
|
|
104400
104400
|
module.exports = [
|
|
104401
104401
|
{
|
|
104402
104402
|
name: "nodejs",
|
|
@@ -107357,6 +107357,14 @@ var require_envs2 = __commonJS({
|
|
|
107357
107357
|
lts: false,
|
|
107358
107358
|
security: false,
|
|
107359
107359
|
v8: "14.6.202.34"
|
|
107360
|
+
},
|
|
107361
|
+
{
|
|
107362
|
+
name: "nodejs",
|
|
107363
|
+
version: "26.5.0",
|
|
107364
|
+
date: "2026-07-08",
|
|
107365
|
+
lts: false,
|
|
107366
|
+
security: false,
|
|
107367
|
+
v8: "14.6.202.34"
|
|
107360
107368
|
}
|
|
107361
107369
|
];
|
|
107362
107370
|
}
|
|
@@ -107446,9 +107454,9 @@ var require_agents4 = __commonJS({
|
|
|
107446
107454
|
}
|
|
107447
107455
|
});
|
|
107448
107456
|
|
|
107449
|
-
// ../../dimina/fe/node_modules/.pnpm/electron-to-chromium@1.5.
|
|
107457
|
+
// ../../dimina/fe/node_modules/.pnpm/electron-to-chromium@1.5.389/node_modules/electron-to-chromium/versions.js
|
|
107450
107458
|
var require_versions2 = __commonJS({
|
|
107451
|
-
"../../dimina/fe/node_modules/.pnpm/electron-to-chromium@1.5.
|
|
107459
|
+
"../../dimina/fe/node_modules/.pnpm/electron-to-chromium@1.5.389/node_modules/electron-to-chromium/versions.js"(exports, module) {
|
|
107452
107460
|
module.exports = {
|
|
107453
107461
|
"0.20": "39",
|
|
107454
107462
|
"0.21": "41",
|
|
@@ -107708,14 +107716,15 @@ var require_versions2 = __commonJS({
|
|
|
107708
107716
|
"42.5": "148",
|
|
107709
107717
|
"42.6": "148",
|
|
107710
107718
|
"43.0": "150",
|
|
107719
|
+
"43.1": "150",
|
|
107711
107720
|
"44.0": "151"
|
|
107712
107721
|
};
|
|
107713
107722
|
}
|
|
107714
107723
|
});
|
|
107715
107724
|
|
|
107716
|
-
// ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.
|
|
107725
|
+
// ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.51/node_modules/node-releases/data/release-schedule/release-schedule.json
|
|
107717
107726
|
var require_release_schedule2 = __commonJS({
|
|
107718
|
-
"../../dimina/fe/node_modules/.pnpm/node-releases@2.0.
|
|
107727
|
+
"../../dimina/fe/node_modules/.pnpm/node-releases@2.0.51/node_modules/node-releases/data/release-schedule/release-schedule.json"(exports, module) {
|
|
107719
107728
|
module.exports = {
|
|
107720
107729
|
"v0.8": {
|
|
107721
107730
|
start: "2012-06-25",
|
|
@@ -291679,10 +291688,10 @@ function needsToolchain(stages) {
|
|
|
291679
291688
|
function freshFs(files, workPath) {
|
|
291680
291689
|
return (0, import_memfs.createFsFromVolume)(import_memfs.Volume.fromJSON(files, workPath));
|
|
291681
291690
|
}
|
|
291682
|
-
async function runSetup(files, workPath) {
|
|
291691
|
+
async function runSetup(files, workPath, options) {
|
|
291683
291692
|
const fs2 = freshFs(files, workPath);
|
|
291684
291693
|
resetCompilerState();
|
|
291685
|
-
const ctx = await setupCompile({ fs: fs2, workPath });
|
|
291694
|
+
const ctx = await setupCompile({ fs: fs2, workPath, options });
|
|
291686
291695
|
const map4 = collectOutputs({ fs: fs2, targetPath: ctx.targetPath });
|
|
291687
291696
|
const scaffold = {};
|
|
291688
291697
|
for (const k of Object.keys(map4)) if (map4[k] != null) scaffold[k] = map4[k];
|
|
@@ -291695,7 +291704,7 @@ async function runSetup(files, workPath) {
|
|
|
291695
291704
|
};
|
|
291696
291705
|
return { bundle, scaffold };
|
|
291697
291706
|
}
|
|
291698
|
-
async function compileSubset(files, workPath, stages, bundle) {
|
|
291707
|
+
async function compileSubset(files, workPath, stages, bundle, options) {
|
|
291699
291708
|
const fs2 = freshFs(files, workPath);
|
|
291700
291709
|
resetCompilerState();
|
|
291701
291710
|
let appId, name, targetPath;
|
|
@@ -291706,7 +291715,7 @@ async function compileSubset(files, workPath, stages, bundle) {
|
|
|
291706
291715
|
;
|
|
291707
291716
|
({ appId, name, targetPath } = bundle);
|
|
291708
291717
|
} else {
|
|
291709
|
-
const ctx = await setupCompile({ fs: fs2, workPath });
|
|
291718
|
+
const ctx = await setupCompile({ fs: fs2, workPath, options });
|
|
291710
291719
|
for (const stage of stages) {
|
|
291711
291720
|
await compileStage({ stage, pages: ctx.pages, storeInfo: ctx.storeInfo, fs: fs2 });
|
|
291712
291721
|
}
|
|
@@ -291743,21 +291752,21 @@ self.onmessage = async (e) => {
|
|
|
291743
291752
|
return;
|
|
291744
291753
|
}
|
|
291745
291754
|
if (type === "setup") {
|
|
291746
|
-
const { files, workPath = "/work", toolchainSetupURL } = e.data;
|
|
291755
|
+
const { files, workPath = "/work", options, toolchainSetupURL } = e.data;
|
|
291747
291756
|
if (toolchainSetupURL) toolchainURL = toolchainSetupURL;
|
|
291748
291757
|
await ensureToolchain();
|
|
291749
291758
|
const t = performance.now();
|
|
291750
|
-
const { bundle, scaffold } = await runSetup(files, workPath);
|
|
291759
|
+
const { bundle, scaffold } = await runSetup(files, workPath, options);
|
|
291751
291760
|
self.postMessage({ type: "setup-done", bundle, scaffold, ms: Math.round(performance.now() - t) });
|
|
291752
291761
|
return;
|
|
291753
291762
|
}
|
|
291754
291763
|
if (type === "compile-subset") {
|
|
291755
|
-
const { files, workPath = "/work", stages = ["logic", "view", "style"], bundle, toolchainSetupURL } = e.data;
|
|
291764
|
+
const { files, workPath = "/work", stages = ["logic", "view", "style"], bundle, options, toolchainSetupURL } = e.data;
|
|
291756
291765
|
if (toolchainSetupURL) toolchainURL = toolchainSetupURL;
|
|
291757
291766
|
if (needsToolchain(stages)) await ensureToolchain();
|
|
291758
291767
|
const warm = !!toolchainReady;
|
|
291759
291768
|
const t = performance.now();
|
|
291760
|
-
const result2 = await compileSubset(files, workPath, stages, bundle);
|
|
291769
|
+
const result2 = await compileSubset(files, workPath, stages, bundle, options);
|
|
291761
291770
|
self.postMessage({ type: "done", result: result2, ms: Math.round(performance.now() - t), warm });
|
|
291762
291771
|
return;
|
|
291763
291772
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimina-kit/compiler",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-dev.20260710085051",
|
|
4
4
|
"description": "dmcc compiler bundles (browser + node) that drive @dimina/compiler against a caller-injected node:fs replacement (no bundled fs)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"test:npm-scan": "node scripts/build-compiler.js node && node --import ./scripts/register-kit.js scripts/test-npm-scan.js",
|
|
94
94
|
"test:stage-toolchain": "node scripts/build-compiler.js browser && node scripts/test-stage-toolchain.js",
|
|
95
95
|
"test:stage-worker-message-order": "node scripts/build-compiler.js node && node scripts/test-stage-worker-message-order.js",
|
|
96
|
-
"test:stage-load-retry": "node scripts/build-compiler.js node && node scripts/test-stage-load-retry.js"
|
|
96
|
+
"test:stage-load-retry": "node scripts/build-compiler.js node && node scripts/test-stage-load-retry.js",
|
|
97
|
+
"test:pool-filetypes": "node scripts/build-compiler.js node && node scripts/build-compiler.js browser && node scripts/test-pool-filetypes.js"
|
|
97
98
|
}
|
|
98
99
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Red/green coverage for `options.fileTypes` (custom template/style/view-script
|
|
2
|
+
// dialect, e.g. .qdml/.qdss/.qds) reaching the browser pool's compile protocol.
|
|
3
|
+
//
|
|
4
|
+
// dmcc's env.js already normalizes options.fileTypes into a `compilerOptions`
|
|
5
|
+
// bundle inside storeInfo (dimina/fe/packages/compiler/src/env.js:91-120), and
|
|
6
|
+
// that bundle rides inside bundle.storeInfo through the setup->compile-subset
|
|
7
|
+
// broadcast (compile-core.js's compileStage -> resetStoreInfo, env.js:122-132) —
|
|
8
|
+
// see src/pool.js's runAttempt and src/stage-worker.js's runSetup/compileSubset.
|
|
9
|
+
// This test drives the REAL browser bundles (dist/pool.browser.js +
|
|
10
|
+
// dist/stage-worker.browser.js) in Node, so it fails (red) without the
|
|
11
|
+
// options-threading fix and passes (green) with it — no reimplementation of the
|
|
12
|
+
// protocol handlers under test.
|
|
13
|
+
//
|
|
14
|
+
// Fixture mirrors qdmp's e2e "qd-app" (see
|
|
15
|
+
// ~/code/qdmp/main/packages/qdmp-devtools/e2e/qd-app): a page using .qdml/.qdss/.qds
|
|
16
|
+
// instead of .wxml/.wxss/.wxs, with a <wxs src="./index.qds" module="m" /> view
|
|
17
|
+
// script and a `{{ m.shout(title) }}` mustache expression — so a correct compile
|
|
18
|
+
// must recognize the custom template AND the custom view-script extension.
|
|
19
|
+
//
|
|
20
|
+
// Also covers compile-core.js's compileMiniApp (the one-shot worker entry point)
|
|
21
|
+
// at the bottom, since it shares the exact same options-threading gap.
|
|
22
|
+
|
|
23
|
+
// Warm real esbuild/oxc-parser module eval AND esbuild's long-lived service child
|
|
24
|
+
// process with the REAL process object BEFORE masking (below) — the service, once
|
|
25
|
+
// spawned, is reused for later transforms even under a masked process, so this
|
|
26
|
+
// must run first.
|
|
27
|
+
import { transform } from 'esbuild'
|
|
28
|
+
import 'oxc-parser'
|
|
29
|
+
|
|
30
|
+
await transform('const __warm = 1', {})
|
|
31
|
+
|
|
32
|
+
// dart-sass's bundled browser shim checks process.versions.node at module-eval
|
|
33
|
+
// time (only reached once the lazily-loaded style-compiler chunk imports it) —
|
|
34
|
+
// masking to a browser-shaped stub makes it take the browser branch instead of
|
|
35
|
+
// crashing on `Dynamic require of "url" is not supported` (see
|
|
36
|
+
// scripts/test-stage-toolchain.js for the same technique). Saved so PART B below
|
|
37
|
+
// (a Node-target bundle, which needs the real process — its transitive deps
|
|
38
|
+
// module-eval-check process.versions.node too) can restore it.
|
|
39
|
+
const realProcess = globalThis.process
|
|
40
|
+
globalThis.process = { env: {}, cwd: () => '/' }
|
|
41
|
+
|
|
42
|
+
const WORKER_URL = new URL('../dist/stage-worker.browser.js', import.meta.url).href
|
|
43
|
+
const TOOLCHAIN_URL = new URL('./toolchain-setup-node-native.js', import.meta.url).href
|
|
44
|
+
const WORK_PATH = '/work'
|
|
45
|
+
|
|
46
|
+
const FIXTURE_FILES = {
|
|
47
|
+
'app.json': JSON.stringify({ pages: ['pages/index/index'], window: { navigationBarTitleText: 'QD Ext Demo' } }),
|
|
48
|
+
'project.config.json': JSON.stringify({ appid: 'qd_ext_demo_001', projectname: 'qd-ext-demo' }),
|
|
49
|
+
'app.js': "App({\n onLaunch() {\n console.log('[QDExt] App launched')\n },\n})\n",
|
|
50
|
+
'pages/index/index.js': "Page({\n data: {\n title: 'QD Extensions',\n count: 3,\n },\n})\n",
|
|
51
|
+
'pages/index/index.qdss': '.qd-box {\n padding: 20rpx;\n}\n\n.qd-title {\n font-size: 36rpx;\n color: #4A90D9;\n}\n\n.qd-item {\n font-size: 28rpx;\n color: #333;\n}\n',
|
|
52
|
+
'pages/index/index.qdml': '<wxs src="./index.qds" module="m" />\n<view class="qd-box">\n <view class="qd-title" id="qd-title">{{ m.shout(title) }}</view>\n <view class="qd-item" wx:for="{{count}}" wx:key="*this">item-{{item}}</view>\n</view>\n',
|
|
53
|
+
'pages/index/index.json': JSON.stringify({ navigationBarTitleText: 'QD Ext Index' }),
|
|
54
|
+
'pages/index/index.qds': "function shout(text) {\n return text + '!'\n}\n\nmodule.exports = {\n shout: shout,\n}\n",
|
|
55
|
+
}
|
|
56
|
+
const FILE_TYPES_OPTIONS = { fileTypes: { template: ['qdml'], style: ['qdss'], viewScript: ['qds'] } }
|
|
57
|
+
|
|
58
|
+
let failed = false
|
|
59
|
+
const chk = (cond, msg) => { if (!cond) { failed = true; console.error(`❌ ${msg}`) } else console.log(`✅ ${msg}`) }
|
|
60
|
+
|
|
61
|
+
// Real page module: exists, non-empty, and carries the compiled .qds wxs module
|
|
62
|
+
// (`shout`) plus the compiled .qdml view (`qd-title`) — the assertion this whole
|
|
63
|
+
// test hinges on. Without options.fileTypes this is guaranteed to fail (the
|
|
64
|
+
// custom extensions are unrecognized, so the page template/view-script are never
|
|
65
|
+
// found), which is exactly the RED this test protects against regressing to.
|
|
66
|
+
function pageModuleIsCompiled(files) {
|
|
67
|
+
const js = files && files['main/pages_index_index.js']
|
|
68
|
+
return typeof js === 'string' && js.length > 0 && js.includes('shout') && js.includes('qd-title')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- PART A: real browser pool (dist/pool.browser.js + dist/stage-worker.browser.js) ---
|
|
72
|
+
//
|
|
73
|
+
// Each stage worker is its own dynamically-imported module instance (own closure
|
|
74
|
+
// state, mirroring a real Web Worker realm); ALL of them funnel through one shared
|
|
75
|
+
// `chain` so only one is ever "in flight" at a time. This matters because the
|
|
76
|
+
// worker module resolves the bare `self` identifier against whatever
|
|
77
|
+
// `globalThis.self` currently is (see test-stage-toolchain.js's note on the same
|
|
78
|
+
// mechanism) — serializing every send means no two realms' async continuations
|
|
79
|
+
// can ever race over that shared global.
|
|
80
|
+
let chain = Promise.resolve()
|
|
81
|
+
let instanceCounter = 0
|
|
82
|
+
async function makeStageWorker() {
|
|
83
|
+
const worker = { onmessage: null, onerror: null, terminate() {} }
|
|
84
|
+
const fakeSelf = {
|
|
85
|
+
onmessage: null,
|
|
86
|
+
postMessage(msg) { if (worker.onmessage) worker.onmessage({ data: msg }) },
|
|
87
|
+
}
|
|
88
|
+
globalThis.self = fakeSelf
|
|
89
|
+
instanceCounter += 1
|
|
90
|
+
await import(`${WORKER_URL}?n=${instanceCounter}`)
|
|
91
|
+
// stage-worker.js does `self.onmessage = async (e) => {...}` at module top level
|
|
92
|
+
// against whatever `globalThis.self` was AT IMPORT TIME — capture it now.
|
|
93
|
+
const boundOnMessage = fakeSelf.onmessage
|
|
94
|
+
worker.postMessage = (msg) => {
|
|
95
|
+
chain = chain.then(async () => {
|
|
96
|
+
globalThis.self = fakeSelf
|
|
97
|
+
try {
|
|
98
|
+
await boundOnMessage({ data: msg })
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// The real handler already catches internally and posts { type:'error' };
|
|
101
|
+
// an escaping exception would mean it threw before its own try, so surface
|
|
102
|
+
// it the same way rather than hanging the pool's pending request forever.
|
|
103
|
+
fakeSelf.postMessage({ type: 'error', error: String((err && err.stack) || err) })
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
return worker
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { createCompilerPool } = await import('../dist/pool.browser.js')
|
|
111
|
+
|
|
112
|
+
// One pool, pre-spawned with exactly the 3 default-stage workers this test needs
|
|
113
|
+
// (no crash/respawn expected) — createWorker must be synchronous (pool.js calls it
|
|
114
|
+
// inline), so every worker instance is built up front and handed out from a queue.
|
|
115
|
+
const stageWorkers = []
|
|
116
|
+
for (const stage of ['logic', 'view', 'style']) stageWorkers.push(await makeStageWorker())
|
|
117
|
+
let nextWorker = 0
|
|
118
|
+
const pool = createCompilerPool({
|
|
119
|
+
createWorker: () => stageWorkers[nextWorker++],
|
|
120
|
+
toolchainSetupURL: TOOLCHAIN_URL,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const withoutOptions = await pool.compile({ files: FIXTURE_FILES, workPath: WORK_PATH })
|
|
124
|
+
chk(!pageModuleIsCompiled(withoutOptions.files),
|
|
125
|
+
'PART A (pool.js + stage-worker.js): WITHOUT options.fileTypes, the .qdml/.qdss/.qds page module is NOT compiled '
|
|
126
|
+
+ `(demonstrates the gap this feature closes; got main/pages_index_index.js=${JSON.stringify(withoutOptions.files['main/pages_index_index.js'])})`)
|
|
127
|
+
|
|
128
|
+
const withOptions = await pool.compile({ files: FIXTURE_FILES, workPath: WORK_PATH, options: FILE_TYPES_OPTIONS })
|
|
129
|
+
chk(pageModuleIsCompiled(withOptions.files),
|
|
130
|
+
'PART A (pool.js + stage-worker.js): WITH options.fileTypes, the .qdml/.qdss/.qds page module IS compiled and non-empty '
|
|
131
|
+
+ `(main/pages_index_index.js length=${(withOptions.files['main/pages_index_index.js'] || '').length})`)
|
|
132
|
+
chk(withOptions.appId === 'qd_ext_demo_001', `PART A: compiled appId matches project.config.json (got ${withOptions.appId})`)
|
|
133
|
+
const compiledCss = withOptions.files['main/pages_index_index.css'] || ''
|
|
134
|
+
chk(compiledCss.includes('qd-title'), `PART A: .qdss style file compiled into page CSS (got ${JSON.stringify(compiledCss)})`)
|
|
135
|
+
|
|
136
|
+
await pool.dispose()
|
|
137
|
+
|
|
138
|
+
// --- PART B: compile-core.js's compileMiniApp (one-shot worker entry point) ---
|
|
139
|
+
// Same options-threading gap, covered directly at the compile-core level (no
|
|
140
|
+
// pool/worker-protocol involved) so a future pool refactor still has this pinned
|
|
141
|
+
// independently of the browser-pool wiring above. This is the NODE-target bundle
|
|
142
|
+
// (dist/compile-core.node.js), whose deps expect a real `process` — restore it
|
|
143
|
+
// before importing.
|
|
144
|
+
globalThis.process = realProcess
|
|
145
|
+
const { compileMiniApp } = await import('../dist/compile-core.node.js')
|
|
146
|
+
const { Volume, createFsFromVolume } = await import('memfs')
|
|
147
|
+
const freshFs = () => createFsFromVolume(Volume.fromJSON(FIXTURE_FILES, WORK_PATH))
|
|
148
|
+
|
|
149
|
+
const coreWithout = await compileMiniApp({ fs: freshFs(), workPath: WORK_PATH })
|
|
150
|
+
chk(!pageModuleIsCompiled(coreWithout.files),
|
|
151
|
+
'PART B (compile-core.js compileMiniApp): WITHOUT options.fileTypes, page module NOT compiled')
|
|
152
|
+
|
|
153
|
+
const coreWith = await compileMiniApp({ fs: freshFs(), workPath: WORK_PATH, options: FILE_TYPES_OPTIONS })
|
|
154
|
+
chk(pageModuleIsCompiled(coreWith.files),
|
|
155
|
+
'PART B (compile-core.js compileMiniApp): WITH options.fileTypes, page module IS compiled and non-empty '
|
|
156
|
+
+ `(length=${(coreWith.files['main/pages_index_index.js'] || '').length})`)
|
|
157
|
+
|
|
158
|
+
console.log(failed ? '\n❌ FAIL' : '\n✅ PASS: options.fileTypes reaches the browser pool protocol (pool.js/stage-worker.js) and compile-core.js\'s compileMiniApp')
|
|
159
|
+
process.exit(failed ? 1 : 0)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Test-only toolchainSetupURL: installs globalThis.__esbuildTransform /
|
|
2
|
+
// __oxcParseSync from the NATIVE esbuild/oxc-parser already in this package's own
|
|
3
|
+
// dependencies (see package.json), instead of the real esbuild-wasm/oxc wasm32-wasi
|
|
4
|
+
// bindings a real browser host would fetch. Both native functions have the exact
|
|
5
|
+
// signature the browser shims (src/shims/esbuild-wasm.js / oxc-parser.js) forward
|
|
6
|
+
// to verbatim (transform(input,options) / parseSync(filename,code,opts)), so this
|
|
7
|
+
// exercises the REAL dist/stage-worker.browser.js code path end-to-end in Node
|
|
8
|
+
// without paying for a wasm toolchain load.
|
|
9
|
+
import { transform } from 'esbuild'
|
|
10
|
+
import { parseSync } from 'oxc-parser'
|
|
11
|
+
|
|
12
|
+
globalThis.__esbuildTransform = (input, options) => transform(input, options)
|
|
13
|
+
globalThis.__oxcParseSync = parseSync
|
package/src/compile-core.js
CHANGED
|
@@ -455,13 +455,16 @@ let compileChain = Promise.resolve()
|
|
|
455
455
|
* Compile a mini-program against a caller-injected fs. Calls are serialized per
|
|
456
456
|
* realm (see the singleton note above). Convenience wrapper that runs
|
|
457
457
|
* `setupCompile` + all stages + `collectOutputs` in one realm.
|
|
458
|
-
* @param {{ fs: object, workPath?: string }} opts
|
|
458
|
+
* @param {{ fs: object, workPath?: string, options?: { fileTypes?: { template?: string[], style?: string[], viewScript?: string[] } } }} opts
|
|
459
459
|
* fs: a node:fs replacement (sync subset: existsSync/readFileSync/
|
|
460
460
|
* readdirSync{withFileTypes}/statSync/writeFileSync/mkdirSync{recursive}/
|
|
461
461
|
* copyFileSync/rmSync), already seeded with the project source under
|
|
462
462
|
* `workPath`. The compiler also writes products back into it, and a
|
|
463
463
|
* missing project.config.json appid is written into it.
|
|
464
464
|
* workPath: project root inside the fs, default '/work'.
|
|
465
|
+
* options: forwarded to `setupCompile` -> dmcc's `storeInfo` (custom file-type
|
|
466
|
+
* dialect, e.g. { fileTypes: { template: ['qdml'], style: ['qdss'],
|
|
467
|
+
* viewScript: ['qds'] } }).
|
|
465
468
|
* @returns {Promise<{ appId: string, name: string, files: Record<string,string> }>}
|
|
466
469
|
*/
|
|
467
470
|
export function compileMiniApp(opts = {}) {
|
|
@@ -472,8 +475,8 @@ export function compileMiniApp(opts = {}) {
|
|
|
472
475
|
return result
|
|
473
476
|
}
|
|
474
477
|
|
|
475
|
-
async function runCompile({ fs, workPath = '/work' } = {}) {
|
|
476
|
-
const ctx = await setupCompile({ fs, workPath })
|
|
478
|
+
async function runCompile({ fs, workPath = '/work', options = {} } = {}) {
|
|
479
|
+
const ctx = await setupCompile({ fs, workPath, options })
|
|
477
480
|
const { storeInfo: bundle, pages, appId, name, targetPath } = ctx
|
|
478
481
|
// Same order as the original single pass. Stages are independent (no product
|
|
479
482
|
// read-back), so the order is not load-bearing — the worker pool (pool.js) runs
|
package/src/pool.js
CHANGED
|
@@ -159,7 +159,7 @@ export function createCompilerPool(options = {}) {
|
|
|
159
159
|
// One full compile attempt against the resident realms. Ends quiescent: settleAll
|
|
160
160
|
// guarantees no request is still in flight when it returns/throws, so a retry can
|
|
161
161
|
// start fresh without cross-attempt FIFO pairing.
|
|
162
|
-
async function runAttempt(files, workPath) {
|
|
162
|
+
async function runAttempt(files, workPath, options) {
|
|
163
163
|
await settleAll(workers.map(ensureWarm))
|
|
164
164
|
|
|
165
165
|
// Setup step — one worker runs setup ONCE: it allocates the scope-hash ids
|
|
@@ -170,7 +170,12 @@ export function createCompilerPool(options = {}) {
|
|
|
170
170
|
// the render `Module id` and every WXSS rule would target nothing (regression
|
|
171
171
|
// guarded by scripts/test-pool-scopehash.js). This mirrors the Node disk pool,
|
|
172
172
|
// which likewise sets up once and fans the same { pages, storeInfo } out.
|
|
173
|
-
|
|
173
|
+
// `options` (e.g. { fileTypes }) only needs to reach THIS setup call: dmcc's
|
|
174
|
+
// storeInfo() bakes the normalized dialect into the returned storeInfo object
|
|
175
|
+
// (dimina/fe/packages/compiler/src/env.js:106-120), which rides inside `bundle`
|
|
176
|
+
// below and is restored per-stage via resetStoreInfo (compile-core.js's
|
|
177
|
+
// compileStage -> env.js:122-132) — so compile-subset never needs its own copy.
|
|
178
|
+
const s = await requestChecked(workers[0], { type: 'setup', files, workPath, options, wantHeartbeat: true }, 'setup', sendTimeoutMs)
|
|
174
179
|
const { bundle, scaffold } = s
|
|
175
180
|
|
|
176
181
|
// Compile step — every stage compiles in parallel against the SHARED bundle. The
|
|
@@ -188,9 +193,16 @@ export function createCompilerPool(options = {}) {
|
|
|
188
193
|
let chain = Promise.resolve()
|
|
189
194
|
|
|
190
195
|
/**
|
|
191
|
-
* Single argument, no ambiguity: pass { files, workPath }. A bare
|
|
192
|
-
* map is also accepted (uses the default workPath).
|
|
193
|
-
* @param {{
|
|
196
|
+
* Single argument, no ambiguity: pass { files, workPath, options }. A bare
|
|
197
|
+
* { relPath: content } map is also accepted (uses the default workPath, no options).
|
|
198
|
+
* @param {{
|
|
199
|
+
* files: Record<string,string>,
|
|
200
|
+
* workPath?: string,
|
|
201
|
+
* options?: { fileTypes?: { template?: string[], style?: string[], viewScript?: string[] } },
|
|
202
|
+
* } | Record<string,string>} input
|
|
203
|
+
* options.fileTypes lets a caller register a custom template/style/view-script
|
|
204
|
+
* dialect (e.g. { template: ['qdml'], style: ['qdss'], viewScript: ['qds'] }) —
|
|
205
|
+
* forwarded to the setup worker's `setupCompile` (dmcc's storeInfo).
|
|
194
206
|
* @returns {Promise<{ appId: string, name: string, files: Record<string,string> }>}
|
|
195
207
|
*/
|
|
196
208
|
function compile(input = {}) {
|
|
@@ -200,17 +212,18 @@ export function createCompilerPool(options = {}) {
|
|
|
200
212
|
}
|
|
201
213
|
const files = input.files || input
|
|
202
214
|
if (!files || typeof files !== 'object' || !Object.keys(files).length) {
|
|
203
|
-
throw new Error('[compiler] pool.compile expects { files: { relPath: content }, workPath? } (or a non-empty files map)')
|
|
215
|
+
throw new Error('[compiler] pool.compile expects { files: { relPath: content }, workPath?, options? } (or a non-empty files map)')
|
|
204
216
|
}
|
|
205
217
|
const workPath = input.workPath || defaultWorkPath
|
|
218
|
+
const options = input.options || {}
|
|
206
219
|
try {
|
|
207
|
-
return await runAttempt(files, workPath)
|
|
220
|
+
return await runAttempt(files, workPath, options)
|
|
208
221
|
} catch (err) {
|
|
209
222
|
// A worker death is often transient (memory pressure, tab freeze, a one-off
|
|
210
223
|
// toolchain stall) — retry the WHOLE attempt once on fresh workers. Real
|
|
211
224
|
// compile errors (bad user source) are deterministic and are never retried.
|
|
212
225
|
if (!retryOnWorkerDeath || !err || !WORKER_DEATH_CODES.has(err.code)) throw err
|
|
213
|
-
return await runAttempt(files, workPath)
|
|
226
|
+
return await runAttempt(files, workPath, options)
|
|
214
227
|
}
|
|
215
228
|
})
|
|
216
229
|
// keep the chain alive regardless of this compile's outcome
|
package/src/stage-worker.js
CHANGED
|
@@ -65,10 +65,10 @@ function freshFs(files, workPath) {
|
|
|
65
65
|
// what keeps the CSS `[data-v-X]` selectors and the render `Module id` in agreement:
|
|
66
66
|
// if each stage ran its own setupCompile it would roll its own random uuids and every
|
|
67
67
|
// WXSS rule would target a selector that never renders (see scripts/test-pool-scopehash.js).
|
|
68
|
-
async function runSetup(files, workPath) {
|
|
68
|
+
async function runSetup(files, workPath, options) {
|
|
69
69
|
const fs = freshFs(files, workPath)
|
|
70
70
|
resetCompilerState()
|
|
71
|
-
const ctx = await setupCompile({ fs, workPath })
|
|
71
|
+
const ctx = await setupCompile({ fs, workPath, options })
|
|
72
72
|
const map = collectOutputs({ fs, targetPath: ctx.targetPath })
|
|
73
73
|
const scaffold = {}
|
|
74
74
|
for (const k of Object.keys(map)) if (map[k] != null) scaffold[k] = map[k]
|
|
@@ -93,7 +93,11 @@ async function runSetup(files, workPath) {
|
|
|
93
93
|
// cross-stage scope-hash mismatch. Stages read source from `workPath` and write
|
|
94
94
|
// disjoint products; they never read the setup scaffold, so it is not seeded here.
|
|
95
95
|
// Without a bundle the worker stays self-contained (single-worker / legacy callers).
|
|
96
|
-
|
|
96
|
+
// `options` (e.g. { fileTypes }) is only used on the no-bundle fallback path: with a
|
|
97
|
+
// bundle, its storeInfo already carries the normalized dialect from the coordinator's
|
|
98
|
+
// setupCompile call (see runSetup below / pool.js's runAttempt), restored via
|
|
99
|
+
// compileStage -> resetStoreInfo, so re-deriving it here would be redundant.
|
|
100
|
+
async function compileSubset(files, workPath, stages, bundle, options) {
|
|
97
101
|
const fs = freshFs(files, workPath)
|
|
98
102
|
resetCompilerState()
|
|
99
103
|
let appId, name, targetPath
|
|
@@ -103,7 +107,7 @@ async function compileSubset(files, workPath, stages, bundle) {
|
|
|
103
107
|
}
|
|
104
108
|
;({ appId, name, targetPath } = bundle)
|
|
105
109
|
} else {
|
|
106
|
-
const ctx = await setupCompile({ fs, workPath })
|
|
110
|
+
const ctx = await setupCompile({ fs, workPath, options })
|
|
107
111
|
for (const stage of stages) {
|
|
108
112
|
await compileStage({ stage, pages: ctx.pages, storeInfo: ctx.storeInfo, fs })
|
|
109
113
|
}
|
|
@@ -150,21 +154,21 @@ self.onmessage = async (e) => {
|
|
|
150
154
|
// Coordinator phase: one worker parses config, allocates the shared scope-hash
|
|
151
155
|
// ids and builds miniprogram_npm once. setupCompile's npm build can invoke the
|
|
152
156
|
// wasm toolchain, so ensure it's loaded regardless of this worker's own stage.
|
|
153
|
-
const { files, workPath = '/work', toolchainSetupURL } = e.data
|
|
157
|
+
const { files, workPath = '/work', options, toolchainSetupURL } = e.data
|
|
154
158
|
if (toolchainSetupURL) toolchainURL = toolchainSetupURL
|
|
155
159
|
await ensureToolchain()
|
|
156
160
|
const t = performance.now()
|
|
157
|
-
const { bundle, scaffold } = await runSetup(files, workPath)
|
|
161
|
+
const { bundle, scaffold } = await runSetup(files, workPath, options)
|
|
158
162
|
self.postMessage({ type: 'setup-done', bundle, scaffold, ms: Math.round(performance.now() - t) })
|
|
159
163
|
return
|
|
160
164
|
}
|
|
161
165
|
if (type === 'compile-subset') {
|
|
162
|
-
const { files, workPath = '/work', stages = ['logic', 'view', 'style'], bundle, toolchainSetupURL } = e.data
|
|
166
|
+
const { files, workPath = '/work', stages = ['logic', 'view', 'style'], bundle, options, toolchainSetupURL } = e.data
|
|
163
167
|
if (toolchainSetupURL) toolchainURL = toolchainSetupURL
|
|
164
168
|
if (needsToolchain(stages)) await ensureToolchain()
|
|
165
169
|
const warm = !!toolchainReady
|
|
166
170
|
const t = performance.now()
|
|
167
|
-
const result = await compileSubset(files, workPath, stages, bundle)
|
|
171
|
+
const result = await compileSubset(files, workPath, stages, bundle, options)
|
|
168
172
|
self.postMessage({ type: 'done', result, ms: Math.round(performance.now() - t), warm })
|
|
169
173
|
return
|
|
170
174
|
}
|