@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.
@@ -90457,9 +90457,9 @@ var require_dist3 = __commonJS({
90457
90457
  }
90458
90458
  });
90459
90459
 
90460
- // ../../dimina/fe/node_modules/.pnpm/node-releases@2.0.50/node_modules/node-releases/data/processed/envs.json
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.50/node_modules/node-releases/data/processed/envs.json"(exports, module) {
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.388/node_modules/electron-to-chromium/versions.js
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.388/node_modules/electron-to-chromium/versions.js"(exports, module) {
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.50/node_modules/node-releases/data/release-schedule/release-schedule.json
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.50/node_modules/node-releases/data/release-schedule/release-schedule.json"(exports, module) {
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 });
@@ -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.50/node_modules/node-releases/data/processed/envs.json
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.50/node_modules/node-releases/data/processed/envs.json"(exports, module) {
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.388/node_modules/electron-to-chromium/versions.js
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.388/node_modules/electron-to-chromium/versions.js"(exports, module) {
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.50/node_modules/node-releases/data/release-schedule/release-schedule.json
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.50/node_modules/node-releases/data/release-schedule/release-schedule.json"(exports, module) {
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.1-dev.20260707110006",
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
@@ -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
- const s = await requestChecked(workers[0], { type: 'setup', files, workPath, wantHeartbeat: true }, 'setup', sendTimeoutMs)
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 { relPath: content }
192
- * map is also accepted (uses the default workPath).
193
- * @param {{ files: Record<string,string>, workPath?: string } | Record<string,string>} input
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
@@ -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
- async function compileSubset(files, workPath, stages, bundle) {
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
  }