Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ jobs:
- name: Check electron peerDependencies stay in sync with devDependencies
run: pnpm run check:electron-peer-sync

# The install steps in this workflow all pass --ignore-scripts, so CI
# never hits ERR_PNPM_IGNORED_BUILDS itself — an undeclared build script
# would land on main and only fail on a developer's own `pnpm install`.
# The scan works here regardless: --ignore-scripts still extracts every
# package, it only skips running their scripts.
- name: Check allowBuilds covers every installed build script
run: pnpm run check:allow-builds

- name: Lint and type-check
run: pnpm turbo run lint check-types --cache-dir=.turbo

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ coverage-detail/

# scripts/patrol.sh 的只读巡检输出
patrol-reports/

# .agents/test_generate_claude.py 运行时产生
.agents/__pycache__/
3 changes: 2 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
shamefully-hoist=true
# pnpm 11+ only reads auth/registry settings from .npmrc. Everything else
# (shamefullyHoist included) lives in pnpm-workspace.yaml now.
2 changes: 1 addition & 1 deletion dimina
Submodule dimina updated 1805 files
10 changes: 2 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"test:coverage": "turbo run test:coverage",
"lint": "turbo run lint",
"dev": "turbo run dev",
"check:allow-builds": "node scripts/check-allow-builds.mjs",
"check:electron-peer-sync": "node scripts/check-electron-peer-sync.mjs",
"check:publish-contract": "node .github/scripts/check-publish-contract.js",
"pawl:check": "pawl check",
Expand All @@ -35,12 +36,5 @@
"typescript": "5.9.2",
"typescript-eslint": "^8.58.2"
},
"pnpm": {
"overrides": {
"html-encoding-sniffer": "4.0.0",
"jsdom": "26.1.0",
"minimist@<1.2.8": "^1.2.8"
}
},
"packageManager": "pnpm@9.15.9"
"packageManager": "pnpm@12.3.4"
}
26 changes: 13 additions & 13 deletions packages/compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"snapshot:upstream-versions": "node scripts/snapshot-upstream-versions.js",
"build:node": "node scripts/build-compiler.js node",
"build:browser": "node scripts/build-compiler.js browser",
"build:types": "tsc -p tsconfig.types.json",
"build:types": "node scripts/build-types.js",
"build": "pnpm run build:node && pnpm run build:browser && pnpm run build:types",
"check-types": "pnpm run build:types && node scripts/test-types.js",
"test:node": "node scripts/build-compiler.js node && node --import ./scripts/register-kit.js scripts/test-node.js",
Expand Down Expand Up @@ -100,32 +100,32 @@
},
"dependencies": {
"@babel/parser": "^7.29.7",
"@vue/compiler-sfc": "^3.5.41",
"@vue/compiler-sfc": "^3.5.42",
"assert": "^2.1.0",
"autoprefixer": "^10.5.3",
"autoprefixer": "^10.5.4",
"buffer": "^6.0.3",
"cheerio": "^1.2.0",
"cssnano": "8.0.6",
"esbuild": "^0.28.1",
"cssnano": "9.0.3",
"esbuild": "^0.28.2",
"estree-walker": "^3.0.3",
"events": "^3.3.0",
"htmlparser2": "^12.0.0",
"less": "^4.9.0",
"less": "^4.9.1",
"magic-string": "1.2.3",
"memfs": "^4.57.8",
"oxc-parser": "^0.145.0",
"oxc-parser": "^0.148.0",
"oxc-walker": "^1.1.1",
"path-browserify": "^1.0.1",
"postcss": "^8.5.26",
"postcss-selector-parser": "^7.1.5",
"sass": "1.102.0",
"postcss": "^8.5.28",
"postcss-selector-parser": "^7.1.6",
"sass": "1.104.0",
"source-map-js": "^1.2.1",
"stream-browserify": "^3.0.0",
"util": "^0.12.5"
},
"peerDependencies": {
"@oxc-parser/binding-wasm32-wasi": "^0.145.0",
"esbuild-wasm": "^0.28.1"
"@oxc-parser/binding-wasm32-wasi": "^0.148.0",
"esbuild-wasm": "^0.28.2"
},
"peerDependenciesMeta": {
"@oxc-parser/binding-wasm32-wasi": {
Expand All @@ -136,7 +136,7 @@
}
},
"devDependencies": {
"esbuild-wasm": "^0.28.1",
"esbuild-wasm": "^0.28.2",
"typescript": "5.9.2"
}
}
31 changes: 28 additions & 3 deletions packages/compiler/scripts/build-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,29 @@ const exportAppend = {
name: 'export-append',
setup(build) {
const appends = {
'logic-compiler.js': '\nexport { writeCompileRes }\nexport function __resetLogicState() { processedModules.clear() }\nexport function __setEnableSourcemap(v) { enableSourcemap = !!v }\n',
'style-compiler.js': '\nexport function __resetStyleState() { compileRes.clear() }\n',
'view-compiler.js': '\nexport function __resetViewState() { compileResCache.clear(); wxsModuleRegistry.clear(); wxsFilePathMap.clear() }\nexport function __setEnableSourcemap(v) { enableSourcemap = !!v }\n',
// `sourcemapTargetPath` is dmcc's own worker-message state (set from
// `sourcemapTargetPath: targetPath` in the message that spawns a build) —
// this driver never sends that message, so writeCompileRes's sourcemap
// branch would dereference it as null. The driver (pool-node.js's runAttempt)
// now threads the caller-known FINAL output dir through runStage's own opts;
// `targetPath` here is that value. Falling back to getTargetPath() (the
// staging dir) when no explicit value is given mirrors dmcc's own
// `targetPath || getTargetPath()`, and runs on every enable/disable — sourcemap
// off always clears it to null — so it can never carry over stale state from a
// prior build sharing this pooled realm.
'logic-compiler.js': '\nexport { writeCompileRes }\nexport function __resetLogicState() { processedModules.clear() }\nexport function __setEnableSourcemap(v, targetPath) { enableSourcemap = !!v; sourcemapTargetPath = enableSourcemap ? (targetPath || getTargetPath()) : null }\n',
// loadSass is otherwise only reachable through the `.scss`/`.sass` branch inside
// compileSS — a style compile with no such file never calls it. Exposing it lets
// compile-core.js's preloadStage warm dart-sass at worker spawn time, by stage
// identity alone, instead of waiting for a matching file to show up.
'style-compiler.js': '\nexport function __resetStyleState() { compileRes.clear() }\nexport function __preloadStyleToolchain() { return loadSass() }\n',
// templateRenderCache/optionalChainingCache are dmcc's own view-compile
// perf caches (named-template render output, optional-chaining rewrites);
// dmcc's own worker-message handler clears them alongside the older
// caches at end-of-task, but this driver calls compileML directly and
// never goes through that handler, so they must be cleared here too or
// a pooled worker realm leaks them across unrelated builds indefinitely.
'view-compiler.js': '\nexport function __resetViewState() { compileResCache.clear(); wxsModuleRegistry.clear(); wxsFilePathMap.clear(); templateRenderCache.clear(); optionalChainingCache.clear() }\nexport function __setEnableSourcemap(v) { enableSourcemap = !!v }\n',
'utils.js': '\nexport function __resetAssets() { for (const k of Object.keys(assetsMap)) delete assetsMap[k] }\n',
}
build.onLoad({ filter: /(core[\\/](logic|style|view)-compiler|common[\\/]utils)\.js$/ }, async (args) => {
Expand Down Expand Up @@ -126,6 +146,11 @@ if (MODE === 'node') {
'crypto': shim('crypto.js'),
'node:async_hooks': shim('async_hooks.js'),
'async_hooks': shim('async_hooks.js'),
// cssnano 9.x uses createRequire(import.meta.url) to dynamically load a
// user-supplied preset/plugin/configFile by string; kit's fixed cssnano()
// call never exercises that path (see src/shims/module.js).
'node:module': shim('module.js'),
'module': shim('module.js'),
'node:path': 'path-browserify',
'path': 'path-browserify',
'node:events': 'events',
Expand Down
38 changes: 38 additions & 0 deletions packages/compiler/scripts/build-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Wraps `tsc -p tsconfig.types.json` because that config's rootDir has to
// cover both packages/compiler/src AND the dimina/fe submodule source it
// imports by relative path (see the comment in tsconfig.types.json) — so tsc
// necessarily also emits declarations for the submodule branch into the
// scratch outDir. This script keeps only the packages/compiler/src branch,
// flattened to the same dist/types/<name>.d.ts layout package.json's
// `exports[*].types` already points at, and discards the rest.
import { createRequire } from 'node:module'
import { spawnSync } from 'node:child_process'
import { cpSync, existsSync, rmSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

const require = createRequire(import.meta.url)
const pkgRoot = path.dirname(fileURLToPath(new URL('../package.json', import.meta.url)))
const rawDir = path.join(pkgRoot, 'dist/types/.raw')
const kitRawDir = path.join(rawDir, 'packages/compiler/src')
const outDir = path.join(pkgRoot, 'dist/types')

rmSync(rawDir, { recursive: true, force: true })

const tscBin = path.join(path.dirname(require.resolve('typescript/package.json')), 'bin', 'tsc')
const tsc = spawnSync(process.execPath, [tscBin, '-p', 'tsconfig.types.json', '--pretty', 'false'], {
cwd: pkgRoot,
encoding: 'utf8',
})
if (tsc.status !== 0) {
console.error(tsc.stdout || '')
console.error(tsc.stderr || '')
process.exit(tsc.status ?? 1)
}

if (!existsSync(kitRawDir)) {
console.error(`build-types: expected tsc output at ${path.relative(pkgRoot, kitRawDir)}, found nothing`)
process.exit(1)
}
cpSync(kitRawDir, outDir, { recursive: true })
rmSync(rawDir, { recursive: true, force: true })
50 changes: 49 additions & 1 deletion packages/compiler/scripts/check-wasm-alignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { execFileSync } from 'node:child_process'
import path from 'node:path'
import { resolveInstalledVersion } from './resolve-installed-version.js'
import { describePnpmConfigKeyLocation } from '../../../scripts/pnpm-config-location.mjs'
import { resolveInstalledVersion, resolveTransitiveVersion } from './resolve-installed-version.js'
import { TRANSITIVE_DEP_PATHS } from './transitive-dep-pairs.js'

function printUsage() {
console.log(
Expand All @@ -29,6 +31,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.resolve(__dirname, '..')
const kitPkgPath = path.join(root, 'package.json')
const diminaRoot = path.resolve(root, '../../dimina')
const repoRoot = path.resolve(root, '../..')
const snapshotPath = path.join(root, 'upstream-lockfile-snapshot.json')

async function readJson(p) {
Expand Down Expand Up @@ -126,6 +129,51 @@ for (const [nativeName, wasmName] of wasmPairs) {
}
}

// Neither side declares these — kit and dmcc each install their own
// `autoprefixer`, which independently pulls in `browserslist`/`caniuse-lite`
// (and `caniuse-lite` again, on a SEPARATE edge, via `browserslist`'s own
// dependency on it). A patch-level caniuse-lite drift alone can flip which
// vendor prefix `overrideBrowserslist: ['cover 99.5%']` resolves to, so this
// has to be checked even though it's invisible to the kitDeps loop above
// (kit's own package.json never lists these names directly). Resolved by
// walking each full path from `autoprefixer`'s own install dir
// (resolveTransitiveVersion), not kit's package root — pnpm's isolated
// node_modules means resolving straight from the root can land in an
// unrelated hoisted copy and misreport it as aligned.
for (const depPath of TRANSITIVE_DEP_PATHS) {
const key = depPath.join('>')
const upstreamVersion = snapshot.transitiveVersions?.[key]
if (!upstreamVersion) {
unresolved.push(` ${key}: snapshot has no recorded version — run scripts/snapshot-upstream-versions.js`)
continue
}

const kitVersion = await resolveTransitiveVersion(kitPkgPath, depPath)
if (!kitVersion) {
unresolved.push(` ${key}: kit=unresolved upstream(snapshot)=${upstreamVersion}`)
continue
}
if (kitVersion !== upstreamVersion) {
// pnpm only reliably honors a 2-segment override key (still true on the
// pinned 12.3.4). A literal join of a 3+-segment path (e.g.
// "autoprefixer>browserslist>caniuse-lite") either throws
// ERR_PNPM_INVALID_SELECTOR (unversioned) or silently no-ops (version-
// qualified on every segment) — the only form that actually takes effect
// is a 2-segment key with a VERSION-QUALIFIED PARENT, anchored on the
// second-to-last package in the chain (e.g. "browserslist@4.28.7>caniuse-lite").
// The suggested key below is built that way so copy-pasting it works.
const parentPathKey = depPath.slice(0, -1).join('>')
const parentVersion = snapshot.transitiveVersions?.[parentPathKey]
const parentSpec = depPath.length <= 2 ? depPath[depPath.length - 2] : `${depPath[depPath.length - 2]}@${parentVersion ?? '<parent-version>'}`
const suggestedKey = `${parentSpec}>${depPath[depPath.length - 1]}`
mismatches.push(
` ${key}: kit resolves ${kitVersion}, upstream snapshot resolves ${upstreamVersion} — ` +
`pin the kit-side version under \`overrides\` in ${describePnpmConfigKeyLocation(repoRoot, 'overrides')} ` +
`("${suggestedKey}": "${upstreamVersion}"), then pnpm install.`,
Comment thread
lbb00 marked this conversation as resolved.
)
}
}

if (unresolved.length > 0) {
console.error(
'check-wasm-alignment: could not resolve installed versions for some shared deps — ' +
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/scripts/gen-bench-fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url'
import path from 'node:path'

const EXAMPLE = process.argv[3] || 'base'
const APP = fileURLToPath(new URL(`../../../dimina/fe/example/${EXAMPLE}`, import.meta.url))
const APP = fileURLToPath(new URL(`../../../dimina/examples/miniprogram/${EXAMPLE}`, import.meta.url))
const TEXT = new Set(['.json', '.js', '.ts', '.wxml', '.ddml', '.wxss', '.ddss', '.less', '.scss', '.sass', '.wxs', '.dds', '.css'])
const out = {}
;(function rd(d, b) {
Expand Down
38 changes: 36 additions & 2 deletions packages/compiler/scripts/resolve-installed-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ async function exists(p) {
// in an unrelated hoisted node_modules — "resolved" would then misreport an
// unrelated install as the answer. Reject any resolution that doesn't land
// under requiredPrefix instead of treating it as valid.
export async function resolveInstalledVersion(anchorPackageJsonPath, depName, requiredPrefix) {
// Resolves `depName` as `anchorPackageJsonPath`'s own directory would resolve
// it, and returns the directory of ITS package.json (not the version) — the
// shared primitive both `resolveInstalledVersion` and
// `resolveTransitiveVersion` walk from.
async function resolvePackageDir(anchorPackageJsonPath, depName, requiredPrefix) {
// Real ESM resolution (`import.meta.resolve`), not CJS `require.resolve`:
// an ESM-only dep whose `exports` map lists only an `import` condition (no
// `require`/`default` fallback) throws ERR_PACKAGE_PATH_NOT_EXPORTED under
Expand Down Expand Up @@ -63,11 +67,41 @@ export async function resolveInstalledVersion(anchorPackageJsonPath, depName, re
const candidate = path.join(dir, 'package.json')
if (await exists(candidate)) {
const pkg = await readJson(candidate)
if (pkg.name === depName) return pkg.version
if (pkg.name === depName) return dir
}
const parent = path.dirname(dir)
if (parent === dir) break
dir = parent
}
return null
}

export async function resolveInstalledVersion(anchorPackageJsonPath, depName, requiredPrefix) {
const dir = await resolvePackageDir(anchorPackageJsonPath, depName, requiredPrefix)
if (!dir) return null
const pkg = await readJson(path.join(dir, 'package.json'))
return pkg.version
}

// Resolves a TRANSITIVE dependency the way the LAST name in `pathNames` would
// actually be required — walking through each intermediate package's own
// resolved directory, not `anchorPackageJsonPath`'s. pnpm's isolated
// node_modules means a package only sees its own declared deps' symlinks —
// resolving e.g. `caniuse-lite` straight from a workspace-root anchor can
// silently land in a DIFFERENT physical install than the one the actual
// requiring package resolves, since node_modules isn't hoisted-flat. A
// single-hop path (e.g. `[autoprefixer, caniuse-lite]`) anchors the second
// resolve at `autoprefixer`'s own resolved package.json; a two-hop path
// (e.g. `[autoprefixer, browserslist, caniuse-lite]`) is needed too —
// `browserslist` has its own independent semver range on `caniuse-lite`,
// a THIRD distinct resolution edge pnpm can drift on its own even when
// `autoprefixer`'s direct `caniuse-lite` edge is pinned.
export async function resolveTransitiveVersion(anchorPackageJsonPath, pathNames, requiredPrefix) {
let anchor = anchorPackageJsonPath
for (let i = 0; i < pathNames.length - 1; i++) {
const dir = await resolvePackageDir(anchor, pathNames[i], requiredPrefix)
if (!dir) return null
anchor = path.join(dir, 'package.json')
}
return resolveInstalledVersion(anchor, pathNames[pathNames.length - 1], requiredPrefix)
}
21 changes: 19 additions & 2 deletions packages/compiler/scripts/snapshot-upstream-versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { readFile, writeFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { execFileSync } from 'node:child_process'
import path from 'node:path'
import { resolveInstalledVersion } from './resolve-installed-version.js'
import { resolveInstalledVersion, resolveTransitiveVersion } from './resolve-installed-version.js'
import { TRANSITIVE_DEP_PATHS } from './transitive-dep-pairs.js'

function printUsage() {
console.log(
Expand Down Expand Up @@ -75,6 +76,22 @@ for (const name of Object.keys(kitDeps)) {
versions[name] = version
}

// Transitive paths are resolved the same way regardless of whether the first
// name is one of the deps kit declares — a package can pull in a transitive
// dep worth tracking even when kit never lists it by name (not the case
// today, but the loop above is deliberately gated on `kitDeps` and this one
// intentionally is not).
const transitiveVersions = {}
for (const depPath of TRANSITIVE_DEP_PATHS) {
const key = depPath.join('>')
const version = await resolveTransitiveVersion(upstreamPkgPath, depPath, diminaFeRoot)
if (!version) {
unresolved.push(key)
continue
}
transitiveVersions[key] = version
}

if (unresolved.length > 0) {
console.error(
'snapshot-upstream-versions: could not resolve upstream-installed versions for:\n' +
Expand All @@ -84,6 +101,6 @@ if (unresolved.length > 0) {
process.exit(2)
}

const snapshot = { diminaCommit, versions }
const snapshot = { diminaCommit, versions, transitiveVersions }
await writeFile(snapshotPath, `${JSON.stringify(snapshot, null, 2)}\n`)
console.log(`snapshot-upstream-versions: wrote ${Object.keys(versions).length} versions to ${path.relative(root, snapshotPath)} (dimina@${diminaCommit})`)
2 changes: 1 addition & 1 deletion packages/compiler/scripts/test-binary-outputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { seedMemfs } from '../src/seed-memfs.js'
await transform('const __warm = 1', {})

const APP = process.env.APP_DIR
|| fileURLToPath(new URL('../../../dimina/fe/example/base', import.meta.url))
|| fileURLToPath(new URL('../../../dimina/examples/miniprogram/base', import.meta.url))

const TEXT_EXT = new Set([
'.json', '.js', '.ts', '.wxml', '.ddml', '.wxss', '.ddss', '.less',
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/scripts/test-decompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import path from 'node:path'
import { Volume, createFsFromVolume } from 'memfs'

const APP = process.env.APP_DIR
|| fileURLToPath(new URL('../../../dimina/fe/example/base', import.meta.url))
|| fileURLToPath(new URL('../../../dimina/examples/miniprogram/base', import.meta.url))

const TEXT_EXT = new Set([
'.json', '.js', '.ts', '.wxml', '.ddml', '.wxss', '.ddss', '.less',
Expand Down
Loading
Loading