@dimina-kit/compiler 0.0.2-dev.20260711141929 → 0.0.2-dev.20260716153350
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 +16256 -15627
- package/dist/compile-core.node-chunks/{chunk-LUD2P6RM.js → chunk-3MOLMQLJ.js} +1 -1
- package/dist/compile-core.node-chunks/{chunk-QJ34C5KK.js → chunk-O5CWW2ZX.js} +7 -6
- package/dist/compile-core.node-chunks/{logic-compiler-2SQFOFEO.js → logic-compiler-4BUMLGSW.js} +2 -2
- package/dist/compile-core.node-chunks/{style-compiler-ZEJ3XLTS.js → style-compiler-4F5PSTLH.js} +1 -1
- package/dist/compile-core.node-chunks/{view-compiler-WQNV7H5G.js → view-compiler-HHZZFMFQ.js} +2 -2
- package/dist/compile-core.node.js +4 -4
- package/dist/pool.node-chunks/{chunk-PDHO4Y56.js → chunk-LVUKEB4F.js} +4 -4
- package/dist/pool.node-chunks/{chunk-7FGOYOXU.js → chunk-QLBB5HQB.js} +7 -6
- package/dist/pool.node-chunks/{chunk-C7GEIDCP.js → chunk-S7VHZTMY.js} +1 -1
- package/dist/pool.node-chunks/{logic-compiler-BODAINZQ.js → logic-compiler-PK5DY6DB.js} +2 -2
- package/dist/pool.node-chunks/{style-compiler-TUNDVSR5.js → style-compiler-X4EGM6MY.js} +1 -1
- package/dist/pool.node-chunks/{view-compiler-HOFFL63K.js → view-compiler-4GBP7IAT.js} +2 -2
- package/dist/pool.node.js +2 -2
- package/dist/stage-worker.browser.js +3195 -2566
- package/dist/stage-worker.node.js +2 -2
- package/package.json +2 -1
- package/scripts/build-compiler.js +2 -0
- package/scripts/test-crypto-shim.js +58 -0
- package/scripts/test-pool-scopehash.js +40 -33
- package/src/pool.js +9 -8
- package/src/shims/crypto.js +101 -0
- package/src/stage-worker.js +13 -12
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
preloadStage,
|
|
4
4
|
resetCompilerState,
|
|
5
5
|
runStage
|
|
6
|
-
} from "./pool.node-chunks/chunk-
|
|
6
|
+
} from "./pool.node-chunks/chunk-LVUKEB4F.js";
|
|
7
7
|
import {
|
|
8
8
|
getAppId,
|
|
9
9
|
getAppName,
|
|
10
10
|
resetStoreInfo
|
|
11
|
-
} from "./pool.node-chunks/chunk-
|
|
11
|
+
} from "./pool.node-chunks/chunk-QLBB5HQB.js";
|
|
12
12
|
|
|
13
13
|
// src/stage-worker-node.js
|
|
14
14
|
import { createRequire } from "node:module";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimina-kit/compiler",
|
|
3
|
-
"version": "0.0.2-dev.
|
|
3
|
+
"version": "0.0.2-dev.20260716153350",
|
|
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",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"test:realm-reuse": "node scripts/build-compiler.js node && node --import ./scripts/register-kit.js scripts/test-realm-reuse.js",
|
|
83
83
|
"test:pool-node": "node scripts/build-compiler.js node && node --import ./scripts/register-kit.js scripts/test-pool-node.js",
|
|
84
84
|
"test:pool-scopehash": "node scripts/build-compiler.js node && node --import ./scripts/register-kit.js scripts/test-pool-scopehash.js",
|
|
85
|
+
"test:crypto-shim": "node scripts/test-crypto-shim.js",
|
|
85
86
|
"test:pool-hardening": "node scripts/build-compiler.js node && node scripts/test-pool-hardening.js",
|
|
86
87
|
"test:pool-toolchain-death": "node scripts/build-compiler.js node && node scripts/test-pool-toolchain-death.js",
|
|
87
88
|
"test:pool-worker-hardening": "node scripts/test-pool-worker-hardening.js",
|
|
@@ -121,6 +121,8 @@ if (MODE === 'node') {
|
|
|
121
121
|
'node:url': shim('url.js'),
|
|
122
122
|
'url': shim('url.js'),
|
|
123
123
|
'node:worker_threads': shim('worker_threads.js'),
|
|
124
|
+
'node:crypto': shim('crypto.js'),
|
|
125
|
+
'crypto': shim('crypto.js'),
|
|
124
126
|
'node:path': 'path-browserify',
|
|
125
127
|
'path': 'path-browserify',
|
|
126
128
|
'node:events': 'events',
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// The browser build has no node:crypto, so src/shims/crypto.js provides a synchronous
|
|
2
|
+
// SHA-256 to back @dimina/compiler's scope-id `createHash('sha256').update(path)
|
|
3
|
+
// .digest().readBigUInt64BE(0)`. This asserts the shim is byte-identical to node:crypto
|
|
4
|
+
// on every input the compiler can feed it — if they ever diverge, browser-compiled scope
|
|
5
|
+
// ids would silently disagree with node-compiled ones.
|
|
6
|
+
import nodeCrypto from 'node:crypto'
|
|
7
|
+
import { createHash as shimCreateHash } from '../src/shims/crypto.js'
|
|
8
|
+
|
|
9
|
+
const SAMPLES = [
|
|
10
|
+
'pages/index/index',
|
|
11
|
+
'components/foo/foo',
|
|
12
|
+
'/components/c16wtkpz/index',
|
|
13
|
+
'/components/c1v8pxjl/index',
|
|
14
|
+
'',
|
|
15
|
+
'a',
|
|
16
|
+
'app.json',
|
|
17
|
+
'中文/页面/path',
|
|
18
|
+
'pages/very/deeply/nested/component/index',
|
|
19
|
+
'x'.repeat(1),
|
|
20
|
+
'x'.repeat(55),
|
|
21
|
+
'x'.repeat(56),
|
|
22
|
+
'x'.repeat(64),
|
|
23
|
+
'x'.repeat(120),
|
|
24
|
+
'x'.repeat(1000),
|
|
25
|
+
// Varied bytes across block boundaries — a constant byte exercises the sha256
|
|
26
|
+
// message schedule (w[t] recurrence) weakly; these would catch a w[] indexing typo
|
|
27
|
+
// that repeated-byte inputs mask. Lengths deliberately straddle 55/56/64/65.
|
|
28
|
+
Array.from({ length: 65 }, (_, i) => String.fromCharCode(33 + (i * 7) % 90)).join(''),
|
|
29
|
+
Array.from({ length: 200 }, (_, i) => `seg${(i * 31) % 97}/`).join(''),
|
|
30
|
+
'pages/index/index'.split('').reverse().join('') + '/深/x'.repeat(20),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
let failed = 0
|
|
34
|
+
const fail = (m) => { failed++; console.error(`❌ ${m}`) }
|
|
35
|
+
|
|
36
|
+
for (const p of SAMPLES) {
|
|
37
|
+
const shimHex = shimCreateHash('sha256').update(p).digest().toString('hex')
|
|
38
|
+
const nodeHex = nodeCrypto.createHash('sha256').update(p).digest('hex')
|
|
39
|
+
const shimId = shimCreateHash('sha256').update(p).digest().readBigUInt64BE(0).toString(36)
|
|
40
|
+
const nodeId = nodeCrypto.createHash('sha256').update(p).digest().readBigUInt64BE(0).toString(36)
|
|
41
|
+
if (shimHex !== nodeHex) fail(`hex mismatch for ${JSON.stringify(p.slice(0, 24))}: shim ${shimHex} != node ${nodeHex}`)
|
|
42
|
+
else if (shimId !== nodeId) fail(`scope-id mismatch for ${JSON.stringify(p.slice(0, 24))}: shim ${shimId} != node ${nodeId}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Chained updates must equal a single concatenated update (the compiler only calls
|
|
46
|
+
// update once today, but the shim promises standard incremental semantics).
|
|
47
|
+
const chained = shimCreateHash('sha256').update('pages/').update('index/').update('index').digest().toString('hex')
|
|
48
|
+
const oneShot = nodeCrypto.createHash('sha256').update('pages/index/index').digest('hex')
|
|
49
|
+
if (chained !== oneShot) fail(`chained update() != single update(): ${chained} != ${oneShot}`)
|
|
50
|
+
|
|
51
|
+
// Only sha256 is implemented — anything else must fail loudly, not silently mis-hash.
|
|
52
|
+
try {
|
|
53
|
+
shimCreateHash('md5')
|
|
54
|
+
fail('createHash("md5") should throw (shim implements sha256 only)')
|
|
55
|
+
} catch { /* expected */ }
|
|
56
|
+
|
|
57
|
+
console.log(failed ? `\n❌ FAIL (${failed})` : `\n✅ PASS: crypto shim === node:crypto on ${SAMPLES.length} samples + chained + guard`)
|
|
58
|
+
process.exit(failed ? 1 : 0)
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
// Content-level scope-hash consistency check for the stage-parallel pool.
|
|
2
2
|
//
|
|
3
3
|
// The pool's structural-equivalence tests (test:pool-node) only compare file
|
|
4
|
-
// NAME/length multisets, so they cannot see whether the `data-v
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
4
|
+
// NAME/length multisets, so they cannot see whether the `data-v-<id>` scope hashes
|
|
5
|
+
// baked into the CSS (`[data-v-<id>]` selectors) actually match the ones baked into
|
|
6
|
+
// the compiled render templates (`id:"<id>"`). Scope ids are a deterministic
|
|
7
|
+
// hash(path) (dimina utils.js), so stages compiling in separate realms derive
|
|
8
|
+
// identical ids; were the id ever per-realm state (a random uuid), the view and style
|
|
9
|
+
// stages would diverge and every WXSS rule would target a selector that never renders.
|
|
10
10
|
//
|
|
11
|
-
// This script models the browser pool (src/pool.js + src/stage-worker.js) in
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
11
|
+
// This script models the browser pool (src/pool.js + src/stage-worker.js) in Node:
|
|
12
|
+
// each stage compiles in its OWN fresh memfs, exactly as a separate Web Worker realm
|
|
13
|
+
// would. It asserts, at the content level, that every scope hash used in CSS also
|
|
14
|
+
// appears in the render output — for per-stage independent setup (MODEL A, which
|
|
15
|
+
// deterministic ids keep consistent with no broadcast), the shared-setup broadcast
|
|
16
|
+
// de-dup path (MODEL B), and the real pool orchestration (MODEL C).
|
|
16
17
|
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
17
18
|
import { fileURLToPath } from 'node:url'
|
|
18
19
|
import path from 'node:path'
|
|
@@ -58,10 +59,10 @@ const STAGES = ['logic', 'view', 'style']
|
|
|
58
59
|
const freshFs = () => createFsFromVolume(Volume.fromJSON(seed, workPath))
|
|
59
60
|
const clean = (m) => { for (const k of Object.keys(m)) if (m[k] == null) delete m[k]; return m }
|
|
60
61
|
|
|
61
|
-
// CSS carries the scope hash as `[data-v
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
62
|
+
// CSS carries the scope hash as `[data-v-<id>]`; the compiled render carries the SAME
|
|
63
|
+
// id bare in each `Module({ …, id:"<id>", … })` (the runtime prepends `data-v-`).
|
|
64
|
+
// Extract each with its own pattern so we compare the cross-file linkage the runtime
|
|
65
|
+
// actually relies on.
|
|
65
66
|
function hashesIn(files, pred, re) {
|
|
66
67
|
const set = new Set()
|
|
67
68
|
for (const k of Object.keys(files)) {
|
|
@@ -74,8 +75,11 @@ function hashesIn(files, pred, re) {
|
|
|
74
75
|
}
|
|
75
76
|
return set
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
// `{5,}` (not a fixed width): the scope id is a base36 hash(path) (~13 chars today).
|
|
79
|
+
// Keeping an open lower bound of 5 still matches a regressed random 5-char id, so a
|
|
80
|
+
// revert to per-realm random ids makes MODEL A orphan again instead of matching nothing.
|
|
81
|
+
const CSS_RE = /data-v-([a-z0-9]{5,})/g
|
|
82
|
+
const RENDER_RE = /\bid:\s*["']([a-z0-9]{5,})["']/g
|
|
79
83
|
const isCss = (k) => k.endsWith('.css')
|
|
80
84
|
const isRender = (k) => k.endsWith('.js') && k !== 'main/logic.js' && !k.endsWith('/logic.js')
|
|
81
85
|
|
|
@@ -93,7 +97,9 @@ function report(label, files) {
|
|
|
93
97
|
return { css, js, orphanCss, matched }
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
// --- MODEL A:
|
|
100
|
+
// --- MODEL A: each stage runs setupCompile in its OWN realm, no broadcast ---
|
|
101
|
+
// The direct regression guard: deterministic hash(path) ids must keep independent
|
|
102
|
+
// realms consistent. A revert to per-realm random ids surfaces here as orphan CSS.
|
|
97
103
|
async function buildPerStageSetup() {
|
|
98
104
|
const merged = {}
|
|
99
105
|
for (const stage of STAGES) {
|
|
@@ -106,11 +112,11 @@ async function buildPerStageSetup() {
|
|
|
106
112
|
return merged
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
// --- MODEL B:
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
115
|
+
// --- MODEL B: setup ONCE, broadcast the bundle across stage realms (de-dup path) ---
|
|
116
|
+
// Models the pool's broadcast path (mirrors the Node disk pool): one realm runs
|
|
117
|
+
// setupCompile (npm/app-config scaffold + { pages, storeInfo }); that bundle is
|
|
118
|
+
// broadcast to every stage worker, which only runs compileStage against it — so the
|
|
119
|
+
// heavy setup runs once. Setup scaffold (app-config.json + npm) is merged in.
|
|
114
120
|
async function buildSharedSetup() {
|
|
115
121
|
realm()
|
|
116
122
|
const setupFs = freshFs()
|
|
@@ -186,10 +192,10 @@ const inline = clean((await compileMiniApp({ fs: freshFs(), workPath })).files)
|
|
|
186
192
|
const gt = report('inline (single realm, ground truth)', inline)
|
|
187
193
|
|
|
188
194
|
const modelA = await buildPerStageSetup()
|
|
189
|
-
const rA = report('MODEL A — per-stage independent setup (
|
|
195
|
+
const rA = report('MODEL A — per-stage independent setup (no broadcast)', modelA)
|
|
190
196
|
|
|
191
197
|
const modelB = await buildSharedSetup()
|
|
192
|
-
const rB = report('MODEL B — shared setup
|
|
198
|
+
const rB = report('MODEL B — shared setup broadcast (de-dup path)', modelB)
|
|
193
199
|
|
|
194
200
|
const poolRes = await buildViaPool()
|
|
195
201
|
const modelC = clean(poolRes.files)
|
|
@@ -203,25 +209,26 @@ const pass = (m) => console.log(`✅ ${m}`)
|
|
|
203
209
|
if (gt.orphanCss.length) fail(`ground truth has ${gt.orphanCss.length} orphan CSS hashes — test harness bug`)
|
|
204
210
|
else pass('ground truth: every CSS scope hash appears in the render output')
|
|
205
211
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
// With deterministic hash(path) scope ids, per-stage INDEPENDENT setup (MODEL A) must
|
|
213
|
+
// stay scope-consistent too: separate realms derive the SAME id from the same path with
|
|
214
|
+
// no broadcast. This is the guarantee stable ids buy, and the direct regression guard —
|
|
215
|
+
// a revert to per-realm random ids would make MODEL A orphan again.
|
|
216
|
+
if (rA.orphanCss.length === 0 && rA.css.size === gt.css.size) {
|
|
217
|
+
pass(`STABLE ID: per-stage independent setup yields 0 orphan CSS hashes, ${rA.matched.length} matched (== ground truth ${gt.css.size}) — deterministic hash(path) keeps realms consistent without a broadcast`)
|
|
211
218
|
} else {
|
|
212
|
-
fail(
|
|
219
|
+
fail(`per-stage independent setup has ${rA.orphanCss.length} orphan CSS hashes (css=${rA.css.size} gt=${gt.css.size}) — scope id is not a deterministic function of path across realms`)
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
// Model B (fix) must be fully consistent AND structurally complete.
|
|
216
223
|
if (rB.orphanCss.length === 0 && rB.css.size === gt.css.size) {
|
|
217
|
-
pass(`
|
|
224
|
+
pass(`BROADCAST PATH: shared setup yields 0 orphan CSS hashes, ${rB.matched.length} matched (== ground truth ${gt.css.size})`)
|
|
218
225
|
} else {
|
|
219
226
|
fail(`shared setup still has ${rB.orphanCss.length} orphan CSS hashes (css=${rB.css.size} gt=${gt.css.size})`)
|
|
220
227
|
}
|
|
221
228
|
// Both models must emit the SAME file set as the single-realm ground truth — this is
|
|
222
229
|
// what the structural test already checks, and why it stays green while WXSS is broken.
|
|
223
230
|
if (fileSet(modelA) !== fileSet(inline)) fail('MODEL A file set differs from ground truth (unexpected)')
|
|
224
|
-
else pass('MODEL A file set == ground truth (
|
|
231
|
+
else pass('MODEL A file set == ground truth (the name/length structural test can only see this, not the scope-hash linkage this content check verifies)')
|
|
225
232
|
if (fileSet(modelB) !== fileSet(inline)) fail('MODEL B (fix) file set differs from ground truth')
|
|
226
233
|
else pass('MODEL B (fix) file set == ground truth')
|
|
227
234
|
|
package/src/pool.js
CHANGED
|
@@ -162,14 +162,15 @@ export function createCompilerPool(options = {}) {
|
|
|
162
162
|
async function runAttempt(files, workPath, options) {
|
|
163
163
|
await settleAll(workers.map(ensureWarm))
|
|
164
164
|
|
|
165
|
-
// Setup step — one worker runs setup ONCE: it
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
165
|
+
// Setup step — one worker runs setup ONCE: it parses config, builds
|
|
166
|
+
// miniprogram_npm + app-config.json (the npm build can invoke the wasm toolchain)
|
|
167
|
+
// and produces the scaffold. The resulting { pages, storeInfo } bundle is
|
|
168
|
+
// broadcast to every stage so this heavy work runs once instead of per stage.
|
|
169
|
+
// Scope ids are a deterministic hash(path) (dimina utils.js), so each stage would
|
|
170
|
+
// derive identical `data-v-<id>` even from its own setup — the broadcast is a
|
|
171
|
+
// de-dup optimization, NOT a scope-correctness requirement (scripts/test-pool-scopehash.js
|
|
172
|
+
// asserts per-stage independent setup stays scope-consistent). Mirrors the Node
|
|
173
|
+
// disk pool, which likewise sets up once and fans the same { pages, storeInfo } out.
|
|
173
174
|
// `options` (e.g. { fileTypes }) only needs to reach THIS setup call: dmcc's
|
|
174
175
|
// storeInfo() bakes the normalized dialect into the returned storeInfo object
|
|
175
176
|
// (dimina/fe/packages/compiler/src/env.js:106-120), which rides inside `bundle`
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// node:crypto shim for the browser build. @dimina/compiler derives its CSS scope id
|
|
2
|
+
// via `createHash('sha256').update(path).digest().readBigUInt64BE(0)` (utils.js). The
|
|
3
|
+
// browser has no node:crypto, and Web Crypto's `subtle.digest` is async — it cannot
|
|
4
|
+
// back a synchronous `uuid()`. So this provides a self-contained synchronous SHA-256.
|
|
5
|
+
// Only the sha256 + digest()->Buffer path the compiler actually uses is implemented.
|
|
6
|
+
import { Buffer } from 'buffer'
|
|
7
|
+
|
|
8
|
+
const K = new Uint32Array([
|
|
9
|
+
0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
|
|
10
|
+
0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
|
|
11
|
+
0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
|
|
12
|
+
0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
|
|
13
|
+
0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
|
|
14
|
+
0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
|
|
15
|
+
0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
|
|
16
|
+
0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2,
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
function rotr(x, n) {
|
|
20
|
+
return (x >>> n) | (x << (32 - n))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// SHA-256 over a Uint8Array, returning the 32-byte digest as a Uint8Array.
|
|
24
|
+
function sha256(msg) {
|
|
25
|
+
let h0 = 0x6A09E667, h1 = 0xBB67AE85, h2 = 0x3C6EF372, h3 = 0xA54FF53A
|
|
26
|
+
let h4 = 0x510E527F, h5 = 0x9B05688C, h6 = 0x1F83D9AB, h7 = 0x5BE0CD19
|
|
27
|
+
|
|
28
|
+
const l = msg.length
|
|
29
|
+
const bitLen = l * 8
|
|
30
|
+
// pad: 0x80, then zeros so length ≡ 56 (mod 64), then 64-bit big-endian bit length.
|
|
31
|
+
const total = (((l + 8) >> 6) + 1) << 6
|
|
32
|
+
const buf = new Uint8Array(total)
|
|
33
|
+
buf.set(msg)
|
|
34
|
+
buf[l] = 0x80
|
|
35
|
+
const dv = new DataView(buf.buffer)
|
|
36
|
+
dv.setUint32(total - 8, Math.floor(bitLen / 0x100000000), false)
|
|
37
|
+
dv.setUint32(total - 4, bitLen >>> 0, false)
|
|
38
|
+
|
|
39
|
+
const w = new Uint32Array(64)
|
|
40
|
+
for (let i = 0; i < total; i += 64) {
|
|
41
|
+
for (let t = 0; t < 16; t++) w[t] = dv.getUint32(i + t * 4, false)
|
|
42
|
+
for (let t = 16; t < 64; t++) {
|
|
43
|
+
const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ (w[t - 15] >>> 3)
|
|
44
|
+
const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ (w[t - 2] >>> 10)
|
|
45
|
+
w[t] = (w[t - 16] + s0 + w[t - 7] + s1) >>> 0
|
|
46
|
+
}
|
|
47
|
+
let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7
|
|
48
|
+
for (let t = 0; t < 64; t++) {
|
|
49
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)
|
|
50
|
+
const ch = (e & f) ^ (~e & g)
|
|
51
|
+
const t1 = (h + S1 + ch + K[t] + w[t]) >>> 0
|
|
52
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)
|
|
53
|
+
const maj = (a & b) ^ (a & c) ^ (b & c)
|
|
54
|
+
const t2 = (S0 + maj) >>> 0
|
|
55
|
+
h = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0
|
|
56
|
+
}
|
|
57
|
+
h0 = (h0 + a) >>> 0; h1 = (h1 + b) >>> 0; h2 = (h2 + c) >>> 0; h3 = (h3 + d) >>> 0
|
|
58
|
+
h4 = (h4 + e) >>> 0; h5 = (h5 + f) >>> 0; h6 = (h6 + g) >>> 0; h7 = (h7 + h) >>> 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const out = new Uint8Array(32)
|
|
62
|
+
const odv = new DataView(out.buffer)
|
|
63
|
+
const hs = [h0, h1, h2, h3, h4, h5, h6, h7]
|
|
64
|
+
for (let i = 0; i < 8; i++) odv.setUint32(i * 4, hs[i], false)
|
|
65
|
+
return out
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class Hash {
|
|
69
|
+
constructor() {
|
|
70
|
+
this._chunks = []
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Accepts a string (UTF-8 encoded, matching node's default) or raw bytes
|
|
74
|
+
// (Uint8Array/Buffer, copied). No inputEncoding form (`update(x, 'hex')`) — the
|
|
75
|
+
// compiler only ever hashes a path string, and silently accepting an encoding we
|
|
76
|
+
// ignore would diverge from node:crypto.
|
|
77
|
+
update(data) {
|
|
78
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : new Uint8Array(data)
|
|
79
|
+
this._chunks.push(bytes)
|
|
80
|
+
return this
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
digest() {
|
|
84
|
+
let len = 0
|
|
85
|
+
for (const c of this._chunks) len += c.length
|
|
86
|
+
const all = new Uint8Array(len)
|
|
87
|
+
let off = 0
|
|
88
|
+
for (const c of this._chunks) { all.set(c, off); off += c.length }
|
|
89
|
+
// Return a Buffer so callers can use Buffer methods (readBigUInt64BE) on the digest.
|
|
90
|
+
return Buffer.from(sha256(all))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createHash(algorithm) {
|
|
95
|
+
if (algorithm !== 'sha256') {
|
|
96
|
+
throw new Error(`[compiler] browser crypto shim only implements sha256 (got '${algorithm}')`)
|
|
97
|
+
}
|
|
98
|
+
return new Hash()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default { createHash }
|
package/src/stage-worker.js
CHANGED
|
@@ -58,13 +58,13 @@ function freshFs(files, workPath) {
|
|
|
58
58
|
return createFsFromVolume(Volume.fromJSON(files, workPath))
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// Run setupCompile ONCE for a compile: parse config,
|
|
62
|
-
// (
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
61
|
+
// Run setupCompile ONCE for a compile: parse config, build the scaffold
|
|
62
|
+
// (app-config.json + miniprogram_npm), and collect the { pages, storeInfo } bundle.
|
|
63
|
+
// Sharing this one bundle across the per-stage realms lets the heavy setup work (npm
|
|
64
|
+
// build, config parse) run once instead of per stage. Scope ids are a deterministic
|
|
65
|
+
// hash(path) (dimina utils.js), so the CSS `[data-v-<id>]` selectors and the render
|
|
66
|
+
// `Module id` agree across stages no matter who runs setup — the shared bundle is a
|
|
67
|
+
// de-dup optimization, not a scope-correctness requirement (see scripts/test-pool-scopehash.js).
|
|
68
68
|
async function runSetup(files, workPath, options) {
|
|
69
69
|
const fs = freshFs(files, workPath)
|
|
70
70
|
resetCompilerState()
|
|
@@ -87,11 +87,12 @@ async function runSetup(files, workPath, options) {
|
|
|
87
87
|
// stays correct across compiles. Stages write disjoint products; we return this
|
|
88
88
|
// worker's subset and the pool unions them.
|
|
89
89
|
//
|
|
90
|
-
// With a `bundle` (from runSetup), the stage reuses the coordinator's
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
// disjoint products; they never read the setup
|
|
90
|
+
// With a `bundle` (from runSetup), the stage reuses the coordinator's { pages, storeInfo }
|
|
91
|
+
// instead of re-running setupCompile — so the npm build / config parse happens once, not
|
|
92
|
+
// per stage (mirroring the Node disk pool). Scope ids are a deterministic hash(path), so
|
|
93
|
+
// reusing the bundle vs re-deriving would yield the same `data-v-<id>` either way. Stages
|
|
94
|
+
// read source from `workPath` and write disjoint products; they never read the setup
|
|
95
|
+
// scaffold, so it is not seeded here.
|
|
95
96
|
// Without a bundle the worker stays self-contained (single-worker / legacy callers).
|
|
96
97
|
// `options` (e.g. { fileTypes }) is only used on the no-bundle fallback path: with a
|
|
97
98
|
// bundle, its storeInfo already carries the normalized dialect from the coordinator's
|