From acd43663df2671315319ec4cf673e45345dfdd14 Mon Sep 17 00:00:00 2001 From: pi-agent Date: Tue, 6 Oct 2026 10:03:44 +0000 Subject: [PATCH 1/4] chore: comment --- src/builder/builder.prod.spec.ts | 29 +- src/builder/builder.spec.ts | 19 +- src/builder/class-style-wxml.spec.ts | 22 +- src/builder/cli-schema.spec.ts | 10 +- .../change-component.build.spec.ts | 13 +- .../change-component.spec.ts | 20 +- .../change-component.ts | 52 +- .../multi-component-per-file.build.spec.ts | 15 +- src/builder/control-flow.spec.ts | 14 +- src/builder/library-meta-sidecar.spec.ts | 86 +-- src/builder/library-multiplatform.spec.ts | 34 +- .../add-declaration-metadata.service.ts | 20 +- src/builder/library/builder.ts | 3 +- src/builder/library/compile-ngc.transform.ts | 18 +- src/builder/library/compile-source-files.ts | 59 +- src/builder/library/const.ts | 13 +- .../library/library-meta-diagnostics.ts | 38 +- src/builder/library/library-meta-reader.ts | 40 +- src/builder/library/library-meta-schema.ts | 77 +- src/builder/library/library-meta-store.ts | 52 +- src/builder/library/library-meta-unit.spec.ts | 39 +- src/builder/library/library.spec.ts | 44 +- src/builder/library/mp-template.ts | 55 +- src/builder/library/ng-packagr-factory.ts | 21 +- .../output-template-metadata.service.ts | 10 +- .../library/setup-component-data.service.ts | 26 +- src/builder/library/stylesheet-processor.ts | 20 +- .../mini-program-compiler/event-name.spec.ts | 6 +- .../mini-program-compiler/event-name.ts | 59 +- .../manifest-registry.ts | 28 +- .../mini-program-compiler.service.ts | 152 +--- .../parse-node/bound-text.ts | 13 +- .../parse-node/component-context.ts | 37 +- .../parse-node/content.ts | 3 +- .../parse-node/element.ts | 67 +- .../parse-node/i18n-attr-emit.spec.ts | 23 +- .../parse-node/interface.ts | 87 +-- .../parse-node/template-definition.spec.ts | 60 +- .../parse-node/template-definition.ts | 718 ++---------------- .../parse-node/template.ts | 4 +- .../parse-node/wxs-emit.spec.ts | 41 +- .../mini-program-compiler/tag-mapping.ts | 52 +- src/builder/mini-program-compiler/type.ts | 46 +- src/builder/ng-content-fallback.spec.ts | 7 +- src/builder/node-manifest-unit.spec.ts | 10 +- src/builder/platform-flags.d.ts | 5 +- src/builder/platform/fs/fs-platform.ts | 8 +- .../platform/library/library.transform.ts | 14 +- src/builder/platform/mp-config-normalize.ts | 9 +- src/builder/platform/mp-project-defaults.ts | 12 +- .../platform/platform-inject-config.ts | 5 +- .../platform/platform-registry.spec.ts | 10 +- src/builder/platform/platform.ts | 30 +- .../transform.base.ts | 9 +- .../wx-like/wx-container.ts | 117 +-- .../wx-like/wx-transform.base.ts | 11 +- .../template/abortcontroller-ponyfill.d.ts | 14 +- .../platform/template/polyfill-entry.ts | 35 +- src/builder/platform/type.ts | 3 +- src/builder/platform/wxs-dialect.spec.ts | 20 +- .../platform/zfb/zfb-event-name.spec.ts | 7 +- src/builder/platform/zfb/zfb-event-name.ts | 19 +- src/builder/platform/zfb/zfb.transform.ts | 6 +- src/builder/ref-class-wxml.spec.ts | 14 +- src/builder/sandbox-copy.spec.ts | 19 +- src/builder/script-syntax.spec.ts | 19 +- src/builder/shared/entry-component.ts | 46 +- ...ni-program-application-analysis.service.ts | 49 +- src/builder/shared/mini-program-test-stack.ts | 13 +- src/builder/shared/spec-discovery.ts | 7 +- src/builder/shared/type.ts | 28 +- src/builder/slot-parity.spec.ts | 52 +- src/builder/util/angular-build-compat.ts | 26 +- src/builder/util/mini-program-style.ts | 19 +- src/builder/util/module-id.ts | 7 +- src/builder/util/path-hygiene.spec.ts | 29 +- src/builder/util/path.spec.ts | 17 +- src/builder/util/path.ts | 155 +--- src/builder/vite/app-config.build.spec.ts | 6 +- src/builder/vite/app-config.ts | 28 +- src/builder/vite/asset-patterns.ts | 32 +- src/builder/vite/budgets.build.spec.ts | 6 +- src/builder/vite/build.spec.ts | 21 +- src/builder/vite/config-hook/index.spec.ts | 31 +- src/builder/vite/config-hook/index.ts | 50 +- src/builder/vite/config-hook/types.d.ts | 14 +- src/builder/vite/config-schema.ts | 26 +- src/builder/vite/config-wiring.spec.ts | 35 +- src/builder/vite/copy-assets.ts | 6 +- .../vite/entry-bootstrap.build.spec.ts | 28 +- src/builder/vite/entry-patterns.ts | 65 +- src/builder/vite/index.ts | 276 ++----- src/builder/vite/localize-polyfill.spec.ts | 22 +- src/builder/vite/merge-config.ts | 58 +- src/builder/vite/mp-config.ts | 29 +- .../vite/native-components.build.spec.ts | 2 +- .../vite/node-index-equivalence.spec.ts | 252 ++---- src/builder/vite/options.spec.ts | 11 +- src/builder/vite/options.ts | 27 +- src/builder/vite/platform-flags.build.spec.ts | 2 +- src/builder/vite/platform-flags.ts | 17 +- src/builder/vite/plugins/budgets.plugin.ts | 21 +- .../plugins/component-transform.plugin.ts | 59 +- .../vite/plugins/entry-bootstrap.plugin.ts | 47 +- .../vite/plugins/library-template.plugin.ts | 83 +- .../plugins/mini-program-assets.plugin.ts | 173 +---- .../vite/plugins/native-components.plugin.ts | 19 +- .../plugins/platform-file-resolve.plugin.ts | 5 +- .../vite/plugins/spec-modules.plugin.ts | 27 +- .../vite/plugins/subpackage-chunk.plugin.ts | 15 +- .../vite/plugins/subpackage-chunk.spec.ts | 8 +- src/builder/vite/plugins/wxs-emit.spec.ts | 7 +- src/builder/vite/plugins/wxs-strip.plugin.ts | 38 +- src/builder/vite/polyfill.spec.ts | 38 +- src/builder/vite/project-config.build.spec.ts | 11 +- src/builder/vite/styles.build.spec.ts | 25 +- src/builder/vite/subpackage.build.spec.ts | 16 +- src/builder/vite/tsconfig-paths.ts | 10 +- src/builder/vite/watch-sources.ts | 17 +- src/builder/vite/watch.spec.ts | 31 +- src/builder/vitest/node/fixture-server.ts | 13 +- src/builder/vitest/node/options.ts | 4 +- src/builder/vitest/node/plugin.ts | 54 +- src/builder/vitest/node/pool-worker.ts | 12 +- src/builder/vitest/node/session.ts | 37 +- src/builder/vitest/protocol.ts | 20 +- .../vitest/runtime/global-polyfills.ts | 80 +- src/builder/vitest/runtime/index.ts | 38 +- src/builder/vitest/runtime/registry.ts | 13 +- src/builder/vitest/runtime/runner.ts | 20 +- src/builder/vitest/runtime/transport.ts | 23 +- src/builder/vitest/runtime/worker.ts | 21 +- src/builder/vitest/vite/globals-define.ts | 28 +- src/builder/vitest/vite/index.ts | 30 +- src/builder/wxml-blocks-unit.spec.ts | 10 +- src/builder/wxs/test-declare-util.ts | 8 +- src/builder/wxs/wxs-angular-strip.spec.ts | 41 +- src/builder/wxs/wxs-angular-strip.ts | 63 +- src/builder/wxs/wxs-call.spec.ts | 12 +- src/builder/wxs/wxs-call.ts | 32 +- src/builder/wxs/wxs-component-rewrite.ts | 44 +- src/builder/wxs/wxs-declare.spec.ts | 8 +- src/builder/wxs/wxs-declare.ts | 35 +- src/builder/wxs/wxs-expr.ts | 93 +-- src/builder/wxs/wxs-rewrite.ts | 73 +- src/builder/wxs/wxs-source.ts | 5 +- src/builder/zoneless.spec.ts | 1 - src/library/api/src/context-wrapper.ts | 9 +- src/library/api/src/domain-types.ts | 16 +- src/library/api/src/event-channel.ts | 8 +- src/library/api/src/event-source.ts | 8 +- src/library/api/src/mp-api.proxy.ts | 14 +- src/library/api/src/mp-api.service.ts | 55 +- .../api/src/mp-app-lifecycle.service.ts | 10 +- src/library/api/src/mp-event-bus.service.ts | 4 +- src/library/api/src/mp-node-query.ts | 12 +- src/library/api/src/pipe-registry.ts | 10 +- src/library/api/src/platform.spec.ts | 5 +- src/library/api/src/platform.ts | 9 +- src/library/api/src/promisify.ts | 14 +- src/library/api/src/protocol-engine.ts | 20 +- src/library/api/src/protocols.ts | 4 +- src/library/api/src/system-info.ts | 18 +- src/library/api/src/types.ts | 15 +- src/library/api/src/validation.spec.ts | 9 +- src/library/api/src/validation.ts | 22 +- src/library/platform/application.spec.ts | 28 +- src/library/platform/application.ts | 69 +- src/library/platform/bd/platform-core.ts | 7 +- .../platform/default/agent-node.spec.ts | 15 +- src/library/platform/default/agent-node.ts | 122 +-- .../component-template-hook.factory.ts | 173 +---- .../platform/default/diff-node-data.spec.ts | 25 +- .../platform/default/diff-node-data.ts | 51 +- .../platform/default/event-name.spec.ts | 9 +- src/library/platform/default/event-name.ts | 36 +- .../platform/default/lview-layout.spec.ts | 53 +- src/library/platform/default/lview-layout.ts | 102 +-- .../default/lview-to-node-list.spec.ts | 64 +- .../platform/default/mini-program.renderer.ts | 25 +- .../default/mp-component-options.spec.ts | 11 +- .../default/path-based-setdata.spec.ts | 64 +- src/library/platform/default/platform-core.ts | 58 +- .../platform/default/ref-class.spec.ts | 26 +- .../default/renderer-class-style.spec.ts | 60 +- .../platform/default/wxs-runtime.spec.ts | 8 +- src/library/platform/default/wxs-runtime.ts | 7 +- src/library/platform/http/backend-di.spec.ts | 20 +- src/library/platform/http/backend.ts | 16 +- src/library/platform/http/provider.spec.ts | 9 +- src/library/platform/http/provider.ts | 38 +- .../platform/i18n-attr-runtime.spec.ts | 28 +- src/library/platform/icu-runtime.spec.ts | 31 +- src/library/platform/localize-runtime.spec.ts | 31 +- .../platform/ng-content-fallback.spec.ts | 11 +- src/library/platform/qq/platform-core.ts | 7 +- src/library/platform/semi-runtime.spec.ts | 13 +- .../platform/template-name-auto.spec.ts | 14 +- .../platform/template-name-coverage.spec.ts | 35 +- .../platform/template-name-derivation.spec.ts | 15 +- src/library/platform/test-util/init-env.ts | 45 +- src/library/platform/type/type.ts | 60 +- .../util/fake-document-runtime.spec.ts | 5 +- .../platform/util/fake-document.spec.ts | 15 +- src/library/platform/util/fake-document.ts | 114 +-- src/library/platform/zfb/event-name.spec.ts | 7 +- src/library/platform/zfb/event-name.ts | 17 +- src/library/platform/zfb/platform-core.ts | 24 +- 208 files changed, 1909 insertions(+), 5529 deletions(-) diff --git a/src/builder/builder.prod.spec.ts b/src/builder/builder.prod.spec.ts index 837d09a9..1ffd7f2f 100644 --- a/src/builder/builder.prod.spec.ts +++ b/src/builder/builder.prod.spec.ts @@ -14,7 +14,6 @@ import { ALL_COMPONENT_NAME_LIST, ALL_PAGE_NAME_LIST, } from '../../test/util/file'; -// 主测试链路已切到 Vite builder(webpack 链路待删除) import { PlatformType } from './platform/platform'; import { runViteBuilder as runBuilder } from './vite'; @@ -52,9 +51,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { const result = await harness.executeOnce(); expect(result).toBeTruthy(); expect(result.error).toBeFalsy(); - // 不能写 logs[0].level !== 'error': - // 1. Vite 链路可能一条日志都不产生,logs[0] 直接 undefined - // 2. 就算有,只看第一条也漏掉了后面的 error + // 只看 logs[0] 会漏:Vite 链路可能一条日志都不产生,也可能 error 不在第一条。 // 本意是「构建过程没有报错」,那就该查全部。 const errorLogs = result.logs.filter((l) => l.level === 'error'); expect({ errorLogs: errorLogs.map((l) => l.value) }).toEqual({ @@ -68,20 +65,13 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { /** * 生产构建下的 wxs 剥离回归。 * - * ## 复现的坑 + * wxs 的绑定应该被分析层从 Angular 模板里剥掉,只留在 wxml:逻辑层 js 里只应该看到枝叶数组属性, + * 不该出现 `fmt.money(...)` 这种逻辑层去调 wxs 的写法(运行时根本没有 `fmt`)。 * - * wxs(渲染层脚本)的绑定应该被分析层从 Angular 模板里剥掉,只留在 wxml: - * 逻辑层 js 里只应该看到「枝叶数组」属性(`[__wxXXXXXX]="[price()]"`), - * **不该**出现 `fmt.money(...)` 这种逻辑层去调 wxs 的鬼东西(运行时根本没有 `fmt`)。 - * - * 但 `fileReplacements` 非空时(生产配置里几乎必然带 environment 替换), - * analog 才会注册它的 `rollup-plugin-replace-files`(也是 `enforce: 'pre'`)。 - * 它和 `mini-program:wxs-strip` 的 resolveId 会互相把对方顶空:两个都返回 null, - * vite 退回默认解析 = 原组件文件,于是 Angular 那份**没剥离**的模板被编进 js - * (wxml 却还是对的,所以只表现为「js 里混进了 wxs」)。 - * dev 没有 environment 替换 ⇒ 插件根本没注册 ⇒ 全绿,只有 `build:prod` 会碎。 - * - * 所以这个用例的关键不是「跑个 prod」,而是 **prod + fileReplacements 同时在场**。 + * `fileReplacements` 非空时 analog 才会注册它的 `rollup-plugin-replace-files`(也是 `enforce: 'pre'`)。 + * 它和 `mini-program:wxs-strip` 的 resolveId 会互相把对方顶空,vite 退回默认解析 = 原组件文件, + * 于是 Angular 那份没剥离的模板被编进 js。dev 没有 environment 替换,插件根本没注册,只有 prod 会碎。 + * 所以这个用例的关键是 prod + fileReplacements 同时在场。 */ const WXS_SCRIPT = `var CURRENCY = '¥'; @@ -186,9 +176,8 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { // 1. 逻辑层 js 不该有任何 `fmt.xxx(` 调用(模板没被剥离的直接症状) expect(entryJs).not.toMatch(/\bfmt\.\w+\s*\(/); - // 2. 这份 js 必须是 AOT 编过的。退回原文件时 analog 不产出模板, - // vite 只把裸 TS 转译一遍,`templateUrl` 装饰器会原样留在产物里 - // (小程序里没有运行时编译器,也没有 .html,必碎)。 + // 2. 这份 js 必须是 AOT 编过的。退回原文件时 analog 不产出模板,vite 只把裸 TS 转译一遍, + // `templateUrl` 装饰器会原样留在产物里 expect(entryJs).not.toContain('templateUrl'); // 3. 剥离后的正确形态:枝叶数组属性 `__wxXXXXXX` expect(entryJs).toMatch(/__wx[0-9a-z]{4,}/); diff --git a/src/builder/builder.spec.ts b/src/builder/builder.spec.ts index 61c27f57..77cf54ec 100644 --- a/src/builder/builder.spec.ts +++ b/src/builder/builder.spec.ts @@ -28,18 +28,10 @@ const angularConfig = { /** * 全量构建的冒烟用例:跑通「页面 + 组件 + 库」整条链路,并核对产物扩展名。 * - * ## 为什么只跑 wx 和 zfb - * - * 十家平台以前各构建一次,一次 2s,光这个文件就 21s。但每条用例真正断言的 - * 只有两件事:构建没报错、产物扩展名对得上(`app.wxss` / `self.wxml` / - * 库组件的 `.qml` `.axml` …)。而「每家平台的 globalObject / 指令前缀 / - * 四种产物扩展名」已经被 `platform/platform-registry.spec.ts` 逐字钉死在 - * 一张表里(十家 1ms 跑完),构建侧的平台差异(define、`.wx.ts` 变体、 - * 死分支 DCE)另有 `vite/platform-flags.build.spec.ts` 覆盖。 - * - * 所以这里留两个代表:wx 是基准,zfb 是差异最大的一家 - * (全局对象 `my`、前缀 `a`、`.axml/.acss/.sjs`)。 - * 新增平台若带来了这两张表没覆盖的行为,再往数组里加。 + * 每条用例真正断言的只有两件事:构建没报错、产物扩展名对得上。而「每家平台的 globalObject / + * 指令前缀 / 四种产物扩展名」已被 `platform/platform-registry.spec.ts` 逐字钉死在一张表里, + * 构建侧的平台差异另有 `vite/platform-flags.build.spec.ts` 覆盖。所以这里留两个代表: + * wx 是基准,zfb 是差异最大的一家(全局对象 `my`、前缀 `a`、`.axml/.acss/.sjs`)。 */ const SMOKE_PLATFORMS = [PlatformType.wx, PlatformType.zfb]; @@ -72,8 +64,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { }); expect(result).toBeTruthy(); expect(result.error).toBeFalsy(); - // Vite 链路可能不产生日志,logs[0] 会是 undefined; - // 本意是「构建没报错」,那就查全部而不是只看第一条。 + // Vite 链路可能不产生日志,logs[0] 会是 undefined;本意是「构建没报错」,那就查全部 expect( result.logs.filter((l) => l.level === 'error').map((l) => l.value), ).toEqual([]); diff --git a/src/builder/class-style-wxml.spec.ts b/src/builder/class-style-wxml.spec.ts index f47e6c34..bc3ee7c4 100644 --- a/src/builder/class-style-wxml.spec.ts +++ b/src/builder/class-style-wxml.spec.ts @@ -26,15 +26,9 @@ async function compile( } /** - * class / style 两条通道的按需输出。 - * - * 以前每个元素都无条件带 `class="{{nodeList[i].class}}"` 和 - * `style="{{nodeList[i].style}}"`,模板里用没用到都一样,实测占掉 wxml - * 近三成体积,数据侧还跟着发一份空串。现在编译期静态分析出「这个元素到底 - * 有没有 class / style 来源」,没有就整条属性都不输出,数据侧也不发。 - * - * 判据漏一条就是「运行时改了 class 而 wxml 不读」的静默丢样式,所以下面 - * 把每种写法逐个钉住,而不是只测一个正例。 + * class / style 两条通道的按需输出。编译期静态分析出「这个元素到底有没有 class / style 来源」, + * 没有就整条属性都不输出,数据侧也不发。 + * 判据漏一条就是「运行时改了 class 而 wxml 不读」的静默丢样式,所以下面把每种写法逐个钉住。 */ describe('class / style 按需输出', () => { it('两个通道都没用到的元素一个属性也不带', async () => { @@ -109,13 +103,9 @@ describe('class / style 按需输出', () => { }); /** - * `tag-name-<原标签>` 标记。 - * - * 用途是「模板写 div、wxml 里已经是 view」时补一个选中把手。映射前后一样 - * 的标签本来就能直接选中,带着它只是每个元素多一个 class token。 - * - * 标记由编译期烘成字面量(运行时不知道映射表),所以没有别的 class 来源的 - * 元素连数据通道都不用留 —— 一个 `class="tag-name-div"` 就完了。 + * `tag-name-<原标签>` 标记。用途是「模板写 div、wxml 里已经是 view」时补一个选中把手。 + * 映射前后一样的标签本来就能直接选中,带着它只是每个元素多一个 class token。 + * 标记由编译期烘成字面量(运行时不知道映射表),所以没有别的 class 来源的元素连数据通道都不用留。 */ describe('tag-name 标记', () => { it('mapped(默认):只给被改写的标签', async () => { diff --git a/src/builder/cli-schema.spec.ts b/src/builder/cli-schema.spec.ts index 61f3c6d9..3204e5f4 100644 --- a/src/builder/cli-schema.spec.ts +++ b/src/builder/cli-schema.spec.ts @@ -21,10 +21,9 @@ function definitionName(builder: string) { } /** - * 发布包里的 `lib/config/schema.json` 由 script/build-cli-schema.ts 生成: - * 基底直读 `@angular/cli/lib/config/schema.json`,再挂上本包三个 builder。 - * 这里钉死「结构没被改坏」——尤其是 target.oneOf 与兜底分支的互斥关系, - * 一旦漏改 not.enum,oneOf 会同时命中两条导致 angular.json 校验失败。 + * 发布包里的 `lib/config/schema.json` 由 script/build-cli-schema.ts 生成:基底直读 + * `@angular/cli/lib/config/schema.json`,再挂上本包三个 builder。这里钉死「结构没被改坏」—— + * 尤其是 target.oneOf 与兜底分支的互斥关系,一旦漏改 not.enum,oneOf 会同时命中两条导致校验失败。 */ describe('cli workspace schema:本包 builder 注入', () => { const schema = buildSchema(); @@ -59,8 +58,7 @@ describe('cli workspace schema:本包 builder 注入', () => { expect(inlined.$schema).toBeUndefined(); expect(inlined.required).toBeUndefined(); const json = JSON.stringify(inlined); - // 内联后所有内部 $ref 必须命名空间化到自身 definitions 下, - // 否则多个 builder 的同名 definition(assetPattern 等)会串在一起 + // 内联后所有内部 $ref 必须命名空间化到自身 definitions 下,否则多个 builder 的同名 definition 会串在一起 expect(json).not.toMatch(/"#\/definitions\/(?!AngularMiniprogram)/); for (const local of Object.keys(inlined.definitions ?? {})) { expect(json).toContain( diff --git a/src/builder/component-template-inject/change-component.build.spec.ts b/src/builder/component-template-inject/change-component.build.spec.ts index f53c657e..8eb2fe13 100644 --- a/src/builder/component-template-inject/change-component.build.spec.ts +++ b/src/builder/component-template-inject/change-component.build.spec.ts @@ -16,12 +16,9 @@ import { PlatformType } from '../platform/platform'; import { runViteBuilder as runBuilder } from '../vite'; /** - * 端到端校验 changeComponent 在真实构建产物里对组件 template 函数的改写。 - * - * 全部走 AST(见 test/util/template-inject-ast.ts)。不用字符串/正则匹配: - * webpack 在不同 chunk 里对同一份注入代码排版不同(entry 是 `}if(rf & 2){`, - * vendor 会排成 `} if (rf & 2) {`,`;;` 会变成 `; ;`),字面量匹配会漏, - * 放宽空白又容易误判。 + * 端到端校验 changeComponent 在真实构建产物里对组件 template 函数的改写。全部走 AST + * (见 test/util/template-inject-ast.ts)。不用字符串/正则匹配:bundler 在不同 chunk 里对同一份 + * 注入代码排版不同,字面量匹配会漏,放宽空白又容易误判。 */ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { describe('component-template-inject: 构建产物 AST 校验', () => { @@ -106,9 +103,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { } } - // 核心不变式:凡是有 rf & 1 的组件模板,都必须被注入。 - // 之前 OutsideTemplateComponent 就是因为选择器把 template 解析成了 - // StringLiteral 而被静默跳过,这条断言就是用来兜住这类漏注入的。 + // 核心不变式:凡是有 rf & 1 的组件模板,都必须被注入。这条断言用来兜住静默漏注入这类问题。 expect({ missed, malformed }).toEqual({ missed: [], malformed: [], diff --git a/src/builder/component-template-inject/change-component.spec.ts b/src/builder/component-template-inject/change-component.spec.ts index 5f2be00d..6fc1821d 100644 --- a/src/builder/component-template-inject/change-component.spec.ts +++ b/src/builder/component-template-inject/change-component.spec.ts @@ -91,8 +91,7 @@ describe('component-template-inject: changeComponent', () => { describe('if (rf & 2) 是空块', () => { it('不能崩,退化成补一个完整更新块', () => { // 空块时 statements 为 [],取 statements[length - 1] 得到 undefined, - // 老逻辑会在 insertNode 里读 getStart 直接抛 - // "Cannot read properties of undefined (reading 'getStart')" + // 老逻辑会在 insertNode 里读 getStart 直接抛。 const r = changeComponent(EMPTY_UPDATE_BLOCK); expect(r).toBeTruthy(); const body = templateBody(r!.content); @@ -102,14 +101,9 @@ describe('component-template-inject: changeComponent', () => { describe('回归:多组件文件 + 类上有 template 字段', () => { /** - * OutsideTemplateComponent 就是这个形状:类自己有个 `template` 字段, - * 同时 ɵɵdefineComponent 里也有 `template: function`。 - * - * 旧实现用 createCssSelectorForTs 的相邻兄弟组合符定位元数据对象, - * 同文件多组件时兄弟关系错位,`PropertyAssignment[name=template]::initializer` - * 会解析到一个 StringLiteral,initIfNode 拿不到,整个组件被 `continue` - * 静默跳过——函数照常返回结果,但一条 propertyChange 都没插进去。 - * 线上表现是该组件的属性变更不会同步到小程序侧。 + * OutsideTemplateComponent 就是这个形状:类自己有个 `template` 字段,同时 ɵɵdefineComponent + * 里也有 `template: function`。用相邻兄弟组合符定位元数据对象时,同文件多组件的兄弟关系会错位, + * `PropertyAssignment[name=template]::initializer` 会解析到一个 StringLiteral,整个组件被静默跳过。 */ it('两个组件都必须被注入', () => { const r = changeComponent(MULTI_WITH_TEMPLATE_FIELD); @@ -153,8 +147,7 @@ describe('component-template-inject: changeComponent', () => { }); it('空模板(无 rf & 1)不注入,但仍按契约返回结果', () => { - // 不能返回 undefined:SetupComponentDataService 靠返回值决定组件是否产出, - // 空模板组件也得正常产出,只是没有注入点而已 + // 不能返回 undefined:调用方靠返回值决定组件是否产出,空模板组件也得正常产出,只是没有注入点 const r = changeComponent(EMPTY_TEMPLATE); expect(r).toBeTruthy(); expect(r!.componentName).toBe('EmptyTpl'); @@ -167,8 +160,7 @@ describe('component-template-inject: changeComponent', () => { const r = changeComponent(TWO_COMPONENTS); expect(r!.componentName).toBe('Comp1'); const body = templateBody(r!.content); - // Comp1 走「有更新块」分支,Comp2 走「无更新块」分支, - // 两个组件的 template 里都应出现 propertyChange + // Comp1 走「有更新块」分支,Comp2 走「无更新块」分支,两个组件的 template 里都应出现 propertyChange const occurrences = (r!.content.match(/amp\.propertyChange/g) || []).length; // import 行本身不含 propertyChange,所以 2 次 = 两个组件各被插了一次 expect(occurrences).toBe(2); diff --git a/src/builder/component-template-inject/change-component.ts b/src/builder/component-template-inject/change-component.ts index 41affbf1..182ba2fc 100644 --- a/src/builder/component-template-inject/change-component.ts +++ b/src/builder/component-template-inject/change-component.ts @@ -6,15 +6,9 @@ const DEFINE_COMPONENT = 'ɵɵdefineComponent'; /** * 找到所有 `ɵɵdefineComponent({...})` 的元数据对象字面量。 - * - * 不用 createCssSelectorForTs 的 - * `PropertyAccessExpression[name=ɵɵdefineComponent]~SyntaxList ObjectLiteralExpression`: - * 那个相邻兄弟(`~`)组合符在同文件多组件时兄弟关系会错位,导致后面按名字取 - * template 时拿到完全不相干的节点(实测会拿到一个 StringLiteral), - * 于是整个组件被静默跳过、不注入。 - * - * webpack 还可能把调用包成 `(0, mod["ɵɵdefineComponent"])(...)`,所以按 callee - * 文本包含来判断。 + * 不用 createCssSelectorForTs 的相邻兄弟(`~`)组合符:它在同文件多组件时会错位, + * 导致后面按名字取 template 时拿到完全不相干的节点,整个组件被静默跳过。 + * webpack 还可能把调用包成 `(0, mod["ɵɵdefineComponent"])(...)`,所以按 callee 文本包含来判断。 */ function findDefineComponentMetaList( sf: ts.SourceFile, @@ -23,8 +17,8 @@ function findDefineComponentMetaList( const walk = (node: ts.Node): void => { if (ts.isCallExpression(node)) { const callee = node.expression.getText(sf); - // 无参调用时 arguments[0] 是 undefined,而 TS 把 Node 索引结果的类型 - // 标成非空,isXxx 系列运行时并不兜底,必须先显式判空 + // 无参调用时 arguments[0] 是 undefined,而 TS 把 Node 索引结果的类型标成非空, + // isXxx 系列运行时并不兜底,必须先显式判空 const first: ts.Node | undefined = node.arguments[0]; if ( callee.includes(DEFINE_COMPONENT) && @@ -54,8 +48,8 @@ function pickProperty( } /** - * 取组件真正的主模板函数(`template: function Xxx_Template(rf, ctx) {...}`)。 - * 必须是函数,不能是字符串——选择器版本就是在这里拿到了 StringLiteral 还没察觉。 + * 取组件真正的主模板函数。必须是函数,不能是字符串——选择器版本就是在这里拿到了 + * StringLiteral 还没察觉。 */ function getTemplateFunction( sf: ts.SourceFile, @@ -82,8 +76,7 @@ function isRfBitTest(expr: ts.Expression, bit: number): boolean { } /** - * 在模板函数体的**顶层**找 `if (rf & bit) { ... }` 的块。 - * 只扫顶层,不递归进嵌套的嵌入式模板函数(Foo_div_1_Template 那种), + * 在模板函数的顶层找 `if (rf & bit) { ... }` 的块。只扫顶层,不递归进嵌套的嵌入式模板函数, * 否则会命中别人的 rf & 1 / rf & 2。 */ function findTopLevelRfBlock( @@ -127,8 +120,7 @@ export function changeComponent(data: string) { if (componentName) { componentNames.push(componentName); } - // 空模板(`template: function X_Template(rf, ctx) {}`)没有 rf & 1, - // 没有可注入的位置,跳过 + // 空模板没有 rf & 1,没有可注入的位置,跳过 const initBlock = findTopLevelRfBlock(templateFn, 1); if (!initBlock) { continue; @@ -148,9 +140,8 @@ export function changeComponent(data: string) { 'end', ); } else { - // 分支 B:没有更新块(或更新块是空的——老逻辑取 statements[length - 1] - // 会得到 undefined,在 insertNode 里读 getStart 直接崩), - // 在 init 块后面补一个完整的 + // 分支 B:没有更新块(或更新块是空的——取 statements[length - 1] 会得到 undefined, + // 在 insertNode 里读 getStart 直接崩),在 init 块后面补一个完整的 updateInsertChange = change.insertNode( initBlock, `if(rf & 2){${updateContent}}`, @@ -162,14 +153,12 @@ export function changeComponent(data: string) { } if (!injectedCount) { - // 没有注入点也要按原契约返回 { content, componentName }: - // SetupComponentDataService 靠返回值是否为 undefined 决定要不要产出组件元数据, - // 这里提前 return undefined 会让空模板组件(如 TestLibraryComponent) - // 整个不产出。只有「本文件根本没有组件」才返回 undefined。 + // 没有注入点也要按原契约返回 { content, componentName }:调用方靠返回值是否为 undefined + // 决定要不要产出组件元数据,这里提前 return undefined 会让空模板组件整个不产出。 + // 只有「本文件根本没有组件」才返回 undefined。 changeList.length = 0; } else { - // import 只需要一份。放在循环里 push 会给每个组件都加一遍, - // 同文件多组件时就是重复的 `import * as amp ...`。 + // import 只需要一份。放在循环里 push 会给每个组件都加一遍 changeList.unshift( new InsertChange(0, `import * as ampNgCore from '@angular/core';\n`), ); @@ -182,20 +171,13 @@ export function changeComponent(data: string) { content: RawUpdater.update(data, changeList), /** 本文件所有组件的类名,顺序与源文件中 ɵɵdefineComponent 出现顺序一致 */ componentNames, - /** - * @deprecated 用 componentNames。保留是为了不破坏已有调用方, - * 等于 componentNames[0]。 - */ + /** @deprecated 用 componentNames。等于 componentNames[0]。 */ componentName: componentNames[0] ?? '', }; } /** - * 只检测本文件里的组件类名,**不改代码**。 - * - * 库构建侧现在只需要「这个文件有哪些组件」来定位元数据,不再往产物里注 - * `amp.propertyChange` —— 那个注入已移到主构建 - * (`vite/plugins/component-transform.plugin.ts`),库产物保持 vanilla。 + * 只检测本文件里的组件类名,不改代码。库产物保持 vanilla,注入在主构建做。 */ export function detectComponentNames(data: string): string[] { const sf = ts.createSourceFile('', data, ts.ScriptTarget.Latest, true); diff --git a/src/builder/component-template-inject/multi-component-per-file.build.spec.ts b/src/builder/component-template-inject/multi-component-per-file.build.spec.ts index a8b98464..b37cf4d0 100644 --- a/src/builder/component-template-inject/multi-component-per-file.build.spec.ts +++ b/src/builder/component-template-inject/multi-component-per-file.build.spec.ts @@ -16,16 +16,9 @@ import { PlatformType } from '../platform/platform'; import { runViteBuilder as runBuilder } from '../vite'; /** - * 同文件多组件支持。 - * - * 小程序侧「一个组件 = 一个 js = 一次 Component() 调用」,所以合法形态是 - * 一个组件源文件导出多个组件,各自用独立 entry 注册。 - * - * 改造前实测(两个组件共用源文件 + 两个独立 entry): - * - 先编译的那个组件模板彻底丢失(A 的 wxml 数 = 0) - * - 所有 import 该文件的 entry 都渲染成最后编译的那个组件 - * 原因就是 outputContent / useComponentPath / style 三个 map 按源文件路径做 key, - * 后写的覆盖先写的。现在 key 改成 `源文件#组件类名`。 + * 同文件多组件支持。小程序侧「一个组件 = 一个 js = 一次 Component() 调用」,所以合法形态是 + * 一个组件源文件导出多个组件,各自用独立 entry 注册。key 用 `源文件#组件类名`, + * 只按源文件路径做 key 会让后写的覆盖先写的。 */ const TWO_COMPONENT_TS = ` @@ -115,7 +108,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { const aWxml = await wxmlOf('entry-a'); const bWxml = await wxmlOf('entry-b'); - // A 的模板必须存在(改造前这里直接是 0,内容整个丢了) + // A 的模板必须存在 expect(aWxml).toContain('A-input'); expect(aWxml).not.toContain('B-input'); diff --git a/src/builder/control-flow.spec.ts b/src/builder/control-flow.spec.ts index 3ce82303..b4d9c02e 100644 --- a/src/builder/control-flow.spec.ts +++ b/src/builder/control-flow.spec.ts @@ -14,7 +14,6 @@ import { ALL_PAGE_NAME_LIST, } from '../../test/util/file'; import { executeOnceShared } from '../../test/util/shared-build'; -// 主测试链路已切到 Vite builder(webpack 链路待删除) import { PlatformType } from './platform/platform'; import { runViteBuilder as runBuilder } from './vite'; @@ -112,8 +111,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { parseInt(m[1], 10), ), ); - // bundler 无关:webpack 产出 `["ɵɵrepeaterCreate"](11,`, - // Vite/Rolldown 产出 `ɵɵrepeaterCreate(11,`。 + // bundler 无关:webpack 产出 `["ɵɵrepeaterCreate"](11,`,Vite/Rolldown 产出 `ɵɵrepeaterCreate(11,`。 // `(?:"])?` 让中间那段 webpack 包装变成可选。 const repeaters = [ ...compiled.matchAll(/repeaterCreate(?:"])?\((\d+),/g), @@ -137,13 +135,9 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { const { wxml, compiled } = await load(); const anchors = collectAnchorsFromWxml(wxml); - // 不能用 `repeaterCreate"]\((\d+),` 这种正则:那是 webpack 的格式化 - // 产物。Vite/Rolldown 会把调用重命名成短别名(`At(10, ...)`), - // 按名字找不到,于是 repeaters 恒为空,断言退化成 - // 「Set(11,14) 等于 Set()」而失败。 - // - // 改用**模板名**推导——Angular 生成的模板函数名自带索引, - // 且不受 bundler 影响: + // 不能用 `repeaterCreate"]\((\d+),` 这种正则:那是 webpack 的格式化产物。Vite/Rolldown 会把调用 + // 重命名成短别名,按名字找不到,于是 repeaters 恒为空,断言退化成「Set(11,14) 等于 Set()」而失败。 + // 改用模板名推导——Angular 生成的模板函数名自带索引,且不受 bundler 影响: // ControlFlowComponent_For_11_Template 主模板 // ControlFlowComponent_ForEmpty_12_Template @empty 模板 const expectedMain = new Set( diff --git a/src/builder/library-meta-sidecar.spec.ts b/src/builder/library-meta-sidecar.spec.ts index 31265a6d..bd329028 100644 --- a/src/builder/library-meta-sidecar.spec.ts +++ b/src/builder/library-meta-sidecar.spec.ts @@ -2,15 +2,12 @@ * 库元数据 sidecar(`<库根>/mp-library-meta.json`)→ wxml 事件绑定。 * * 钉住两条相反的方向,缺一不可: - * * ✅ sidecar 里有正确的 host 元数据,并且真的驱动了 wxml 的 bind:* 事件 - * ❌ `.d.ts` 里**不再**出现 `declare const X_Listeners` 这类内联标记 + * ❌ `.d.ts` 里不出现 `declare const X_Listeners` 这类内联标记 * - * 第二条是这次重构的核心承诺:`.d.ts` 是类型契约,不当 key-value 存储用。 - * 旧方案之所以要「构建后补写」,是因为 ng-packagr 22 的 d.ts 扁平化会把 - * 未导出的 `declare const` tree-shake 掉;标记一丢,应用侧拿到空 listeners - * 并覆盖掉 host.listeners,wxml 一个事件绑定都没有,且毫无报错。 - * 元数据搬到 sidecar 之后,扁平化根本碰不到它。 + * `.d.ts` 是类型契约,不当 key-value 存储用:ng-packagr 的 d.ts 扁平化会把 + * 未导出的 `declare const` tree-shake 掉,标记一丢,应用侧拿到空 listeners + * 并覆盖掉 host.listeners,且毫无报错。元数据搬到 sidecar 后扁平化碰不到它。 */ import { join, normalize } from '@angular-devkit/core'; import * as fs from 'fs'; @@ -70,25 +67,18 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { await h.moveDir(ALL_COMPONENT_NAME_LIST, '__components', 'components'); await h.addPageEntry(ALL_PAGE_NAME_LIST); - // 本 harness 的 builder 是 **app** builder,跑不了 library target, - // 所以 test-library 由 `library/library.spec.ts` 构建后拷进 - // `test/hello-world-app/node_modules/test-library`,这里读那份副本。 - // 谁先跑由 `vitest.config.mts` 的 sequencer 保证,不在脚本里排序。 - // - // 但单独跑本文件(`vitest run library-meta-sidecar`)时 sequencer 帮不上忙, - // 副本可能缺失或是上一轮残留,所以这里显式验新鲜度: + // 本 harness 的 builder 是 app builder,跑不了 library target,所以 test-library + // 由 `library/library.spec.ts` 构建后拷进 node_modules,这里读那份副本。 + // 单独跑本文件时 sequencer 帮不上忙,所以这里显式验新鲜度: // 宁可大声报错,也不要静默地测一个旧副本而给出假绿灯。 - // - // 用 devkit host 读,不用 `fs` + host.root():root 是虚拟路径 - // `/C/code/...`,Windows 下 Node 的 fs 解析不了。 + // 用 devkit host 读,不用 `fs`:root 是虚拟路径,Windows 下 Node 的 fs 解析不了。 const installedLibEntry = join( normalize(root), 'node_modules/test-library/fesm2022/test-library.mjs', ); let installedLib: string; try { - // host.read() 发的是 ArrayBuffer,`fileBufferToString` 不认(会变成 - // "[object ArrayBuffer]"),所以走 Buffer.from。 + // host.read() 发的是 ArrayBuffer,`fileBufferToString` 不认,所以走 Buffer.from installedLib = Buffer.from( await harness.host.read(installedLibEntry).toPromise(), ).toString('utf8'); @@ -166,10 +156,8 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { 'utf8', ), /** - * app 产物里的全部 JS。 - * - * 必须在 `load()` 里就读完:harness 的临时工程目录会在 spec 之间被 - * 清掉,拿着 `base` 稍后再去 scandir 就是 ENOENT。 + * app 产物里的全部 JS。必须在 `load()` 里就读完:harness 的临时工程目录 + * 会在 spec 之间被清掉,拿着 `base` 稍后再去 scandir 就是 ENOENT。 */ allAppJs: (() => { const out: { name: string; content: string }[] = []; @@ -196,10 +184,8 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { }); /** - * 读 app 产物里的全部 JS。 - * - * 用来验证「库组件的 `amp.propertyChange` 是主构建注进去的」—— - * 库自己的 fesm 里没有(vanilla),但 app 的 chunk 里有。 + * 读 app 产物里的全部 JS。用来验证库组件的 `amp.propertyChange` 是主构建注进去的—— + * 库自己的 fesm 里没有。 */ const readAllAppJs = async () => (await load()).allAppJs; @@ -243,7 +229,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { const meta = await readMeta(); const directives = meta.entries['types/angular-miniprogram-forms.d.ts'].directives; - // 监听器 / 输入属性的**集合**要一致,但声明顺序无关,所以两边都排序。 + // 监听器 / 输入属性的集合要一致,但声明顺序无关,所以两边都排序 expect([...directives.DefaultValueAccessor.listeners].sort()).toEqual([ 'bindblur', 'bindinput', @@ -290,8 +276,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { }); it('反向对照:断言不是恒真(wxml 事件全部能追溯到 sidecar 元数据)', async () => { - // 把 sidecar 里的事件名当成集合,与 wxml 中出现的事件一一对应。 - // 若事件是别处硬编码来的,这个对应关系就不会成立。 + // 把 sidecar 里的事件名当成集合,与 wxml 中出现的事件一一对应。若事件是别处硬编码来的,这个对应关系就不会成立 const meta = await readMeta(); const { wxml } = await load(); @@ -324,9 +309,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { /** * 专用 demo 页 `pages/library-meta-demo`:整页都是 test-library 调用。 - * - * 源模板(src/__pages/library-meta-demo/library-meta-demo.component.html) - * 与 sidecar 记录的对应关系,逐字段钉住。 + * 源模板与 sidecar 记录的对应关系,逐字段钉住。 */ describe('库元数据 demo 页(test-library 调用)', () => { it('库指令 TestLibraryDirective 的 listeners → bind:tap / bind:touchstart', async () => { @@ -372,7 +355,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { it('库组件自身产物 wxml 不再是空 block(模板真的编译进去了)', async () => { const { libCompWxml } = await load(); - // 改之前:``,空的,肉眼无法判断渲染了没有。 + // 空 block 与有子节点的 block 肉眼无法判断渲染了没有 expect(libCompWxml).not.toMatch( /<\/block>/, ); @@ -382,8 +365,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { it('库组件模板文本走运行时数据,wxml 里是 {{nodeList[N].value}}', async () => { const { libCompWxml } = await load(); - // 说明:文本节点不进 wxml 字面量,而是存在 vnode 里由运行时填。 - // 所以“渲染标记在不在”不能靠 wxml 字面量查,得看 JS 产物 + wxss。 + // 文本节点不进 wxml 字面量,而是存在 vnode 里由运行时填,所以不能靠 wxml 字面量查 expect(libCompWxml).toContain('{{nodeList[1].value}}'); }); @@ -393,12 +375,9 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { }); /** - * input / output 传值。 - * - * 实测:`` 生成的 wxml 与不传时**一模一样**, - * input 不以任何属性形式出现在 wxml 里(它走 vnode,运行时由组件自己读)。 - * 所以这部分只能查编译后的 JS:Angular 会把 input/output 名保留成字符串 - * 字面量(consts 里的 `[3, 'input1']`、update 里的 `property('input1', ...)`)。 + * input / output 传值。`` 生成的 wxml 与不传时一模一样 + * (input 走 vnode,运行时由组件自己读),所以这部分只能查编译后的 JS: + * Angular 会把 input/output 名保留成字符串字面量。 */ it('库组件 input 传值进了编译产物', async () => { const { demoJs } = await load(); @@ -426,9 +405,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { }); it('反向对照:demo 页的 bind:* 事件全部在 test-library 的 sidecar 里有记录', async () => { - // 注意:demo 页用的是 **test-library(第二个库)**,不是主库 - // angular-miniprogram。所以这里读的是 test-library 自己的 - // mp-library-meta.json(由 library.spec.ts 构建后拷进 node_modules)。 + // demo 页用的是 test-library(第二个库),不是主库,所以读它自己的 mp-library-meta.json const libMetaPath = path.resolve( __dirname, '../../test/hello-world-app/node_modules/test-library/mp-library-meta.json', @@ -469,14 +446,8 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { }); /** - * 运行时 hook 的所有权:库不管,主构建管。 - * - * 重构前:库构建把 `import * as amp` + `amp.propertyChange(...)` 烘进 - * 自己的 fesm,库产物不是 vanilla,且运行时 hook 与库版本死锁。 - * 重构后:库只出 `mp-library-meta.json`,主构建读到 sidecar 后给库组件 - * 补上注入。 - * - * 两个方向都要钉: + * 运行时 hook 的所有权:库不管,主构建管。库只出 `mp-library-meta.json`, + * 主构建读到 sidecar 后给库组件补上注入。两个方向都要钉: * ✅ app chunk 里库组件的 template fn 有且仅有一次 propertyChange * ✅ 库 fesm 本身一个 propertyChange 都没有(证明注入来自 app 侧) */ @@ -583,13 +554,8 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { /** * 二级出口(`test-library/src/secondary`)走主构建。 - * - * 多 entry point 的库,每个 entry 有自己的 fesm、自己的组件。主构建 - * 必须按**组件名**把两边分别对上:碰二级 fesm 只处理二级组件,碰一级 - * fesm 只处理一级组件。 - * - * 重构前这里是 `meta.entry ? [meta.entry] : meta.entries`,碰不到 entry - * 就把整包 emit 一遍 —— 多出口包下会把应用根本没 import 的出口也产出来。 + * 多 entry point 的库,每个 entry 有自己的 fesm、自己的组件。主构建必须按组件名 + * 把两边分别对上:碰二级 fesm 只处理二级组件,碰一级 fesm 只处理一级组件。 */ describe('二级出口在主构建下', () => { it('二级出口组件产出自己的 wxml,内容只含它自己的模板', async () => { diff --git a/src/builder/library-multiplatform.spec.ts b/src/builder/library-multiplatform.spec.ts index 545ee6f9..745af328 100644 --- a/src/builder/library-multiplatform.spec.ts +++ b/src/builder/library-multiplatform.spec.ts @@ -1,28 +1,16 @@ /** * 多平台:一份库产物,通吃非 wx 平台。 * - * 这是 schemaVersion 3 架构的核心卖点,之前完全没测: + * 库构建产出的是 `${}` 插值模板串(平台中立):平台相关处写成 `${directivePrefix}` / + * `${eventListConvert(["tap"])}`,wxml 自己的 `{{hasLoad}}` 是静态文本原样进出; + * 填充发生在主构建,用目标平台的 `LibraryTemplateValues` 跑 `renderLibraryTemplate()`。 + * 所以库只需要构建一次,平台相关的东西一个都不烘进库里。 * - * 库构建产出的是 **`${}` 插值模板串**(平台中立):平台相关处写成 - * `${directivePrefix}` / `${eventListConvert(["tap"])}`,wxml 自己的 - * `{{hasLoad}}` 是静态文本原样进出;填充发生在主构建,用目标平台的 - * `LibraryTemplateValues` 跑 `renderLibraryTemplate()`。 + * 这里用 zfb(支付宝)做对照,因为它跟 wx 的差异足够大:指令前缀 `a:if`、事件名 `onTap`、 + * 模板 `.axml`、样式 `.acss`。 * - * 所以库只需要构建一次,wx / zfb / bd / qq 各自的主构建把同一份 `content` - * 填成各自平台的 wxml + 事件名 + 文件扩展名。平台相关的东西一个都不烘进库里。 - * - * 这里用 zfb(支付宝)做对照,因为它跟 wx 的差异足够大、一眼能看出来: - * - * | 维度 | wx | zfb | - * | ---------- | ----------- | ---------- | - * | 指令前缀 | `wx:if` | `a:if` | - * | 事件名 | `bind:tap` | `onTap` | - * | 模板扩展名 | `.wxml` | `.axml` | - * | 样式扩展名 | `.wxss` | `.acss` | - * - * 单独开一个 spec 文件而不是塞进 `library-meta-sidecar.spec.ts`:那个文件里 - * wx 的 `load()` 是 memoize 的,再跑一次 zfb 构建会把同一批 `.js` chunk - * 覆盖掉,两个平台的断言会互相污染。 + * 单独开一个 spec 文件而不是塞进 `library-meta-sidecar.spec.ts`:那个文件里 wx 的 `load()` 是 + * memoize 的,再跑一次 zfb 构建会把同一批 `.js` chunk 覆盖掉,两个平台的断言会互相污染。 */ import { join, normalize } from '@angular-devkit/core'; import * as fs from 'fs'; @@ -69,8 +57,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { await h.moveDir(ALL_COMPONENT_NAME_LIST, '__components', 'components'); await h.addPageEntry(ALL_PAGE_NAME_LIST); - // 同 library-meta-sidecar.spec.ts:app harness 跑不了 library target, - // test-library 靠外部先构建,这里验一下副本新鲜度,避免假绿灯。 + // app harness 跑不了 library target,test-library 靠外部先构建,这里验一下副本新鲜度,避免假绿灯 const installedLib = Buffer.from( await harness.host .read( @@ -87,8 +74,7 @@ describeBuilder(runBuilder, BROWSER_BUILDER_INFO, (harness) => { '先跑 `vitest run src/builder/library/library.spec.ts` 重新生成', ).toContain('LIB_TEST_LIBRARY_RENDERED'); - // 关键:库**不重新构建**,只是主构建换了平台。 - // 库产物(含 sidecar)与 wx 那次用的是同一份。 + // 关键:库不重新构建,只是主构建换了平台。库产物(含 sidecar)与 wx 那次用的是同一份。 const angularConfig = { ...DEFAULT_ANGULAR_CONFIG, platform: PlatformType.zfb, diff --git a/src/builder/library/add-declaration-metadata.service.ts b/src/builder/library/add-declaration-metadata.service.ts index d6647151..a371d765 100644 --- a/src/builder/library/add-declaration-metadata.service.ts +++ b/src/builder/library/add-declaration-metadata.service.ts @@ -16,16 +16,8 @@ import { ENTRY_POINT_TOKEN } from './token'; /** * 把本 entry 编译期拿到的指令 / 组件 host 元数据登记进 sidecar 暂存区。 - * - * **不再改写 `.d.ts`。** - * - * 旧实现在这里 `return data + markers`,把 `declare const X_Listeners:[...]` - * 拼到中间 d.ts 尾巴上;因为 ng-packagr 22 的 d.ts 扁平化会把这些不在导出 - * 引用图里的 `declare const` tree-shake 掉,还得在构建后再补写一次到最终 - * `dist/types/*.d.ts`。等于把类型契约文件当 key-value 存储,还要跟打包器搏斗。 - * - * 现在 `.d.ts` 原样返回,元数据走 `mp-library-meta.json` sidecar, - * 扁平化爱怎么 shake 怎么 shake,与我们无关。 + * 不改写 `.d.ts`:`.d.ts` 是类型契约,不当 key-value 存储用。元数据走 + * `mp-library-meta.json` sidecar,扁平化怎么处理与我们无关。 */ export class AddDeclarationMetaDataService { private entryPoint = inject(ENTRY_POINT_TOKEN); @@ -39,10 +31,7 @@ export class AddDeclarationMetaDataService { this.componentMap = miniProgramCompilerService.getComponentMap(); } - /** - * 扫描本文件产出的 d.ts,把里面的指令/组件登记进暂存区, - * 然后**原样返回**内容。 - */ + /** 扫描本文件产出的 d.ts,把里面的指令/组件登记进暂存区,然后原样返回内容。 */ run(_dTsFileName: string, data: string): string { const list = createCssSelectorForTs(data).queryAll( `ClassDeclaration`, @@ -71,8 +60,7 @@ export class AddDeclarationMetaDataService { private recordDirectives(list: ts.ClassDeclaration[]) { for (const classDeclaration of list) { - // 组件的 `ɵcmp` 里也带 `ɵdir`,但组件已在上面登记过, - // 这里只处理纯指令,避免同一个类进两个桶。 + // 组件的 `ɵcmp` 里也带 `ɵdir`,但组件已在上面登记过,这里只处理纯指令,避免同一个类进两个桶 if (hasStaticMember(classDeclaration, 'ɵcmp')) { continue; } diff --git a/src/builder/library/builder.ts b/src/builder/library/builder.ts index 40ef71e2..9e3bd49c 100644 --- a/src/builder/library/builder.ts +++ b/src/builder/library/builder.ts @@ -50,8 +50,7 @@ export function execute( const ngPackagrOptions = { cacheEnabled, cacheDirectory: join(cacheDirectory, 'ng-packagr'), - // ng-packagr 自己把它交给 chokidar 的 watchOptions.poll, - // 网络盘 / WSL 挂载上没轮询就看不到改动 + // ng-packagr 自己把它交给 chokidar 的 watchOptions.poll,网络盘 / WSL 挂载上没轮询就看不到改动 poll: options.poll, }; diff --git a/src/builder/library/compile-ngc.transform.ts b/src/builder/library/compile-ngc.transform.ts index 66e81dc8..b04ddd4d 100644 --- a/src/builder/library/compile-ngc.transform.ts +++ b/src/builder/library/compile-ngc.transform.ts @@ -69,7 +69,7 @@ export const myCompileNgcTransformFactory = ( basePath, cssUrl, styleIncludePaths, - // ng-packagr 19 在 cacheDirectory 之前新增了 `sass` 参数 + // ng-packagr 在 cacheDirectory 之前新增了 `sass` 参数 undefined, options.cacheEnabled && options.cacheDirectory, options.watch, @@ -77,13 +77,9 @@ export const myCompileNgcTransformFactory = ( /** * 先把本 entry 的「扁平化 d.ts 路径 / fesm 产物路径」登记进 sidecar 暂存区。 - * - * 用 ng-packagr 自己的 `declarationsBundled`(= `dist/types/.d.ts`), - * 也就是最终 `package.json#typings` 指的那个文件。读取侧拿类的 - * `getSourceFile()` 相对库根一算就能命中,不用反推 exports map。 - * - * fesm 路径同样直接拿:主构建要把「正在 transform 的那个 .mjs」对上 - * 具体 entry,多 entry point 的包才不会误伤。 + * 用 ng-packagr 自己的 `declarationsBundled`,也就是最终 `package.json#typings` 指的那个文件, + * 读取侧拿类的 `getSourceFile()` 相对库根一算就能命中,不用反推 exports map。 + * fesm 路径同样直接拿:主构建要把正在 transform 的那个 .mjs 对上具体 entry。 */ registerLibraryMetaEntry( entryPoint.data.entryPoint.moduleId, @@ -107,10 +103,8 @@ export const myCompileNgcTransformFactory = ( ); /** - * 每个 entry 编译完就落一次盘(全量重写,幂等)。 - * - * 放在这里而不是 build 结束后的钩子上,是因为 watch 模式下 - * 「build 结束」根本不会发生;挂在 entry 编译尾部两条路径都能覆盖。 + * 每个 entry 编译完就落一次盘(全量重写,幂等)。放在这里而不是 build 结束后的钩子上, + * 是因为 watch 模式下「build 结束」根本不会发生。 */ writeLibraryMetaFile(distRoot); } catch (error) { diff --git a/src/builder/library/compile-source-files.ts b/src/builder/library/compile-source-files.ts index cdf001af..4823962f 100644 --- a/src/builder/library/compile-source-files.ts +++ b/src/builder/library/compile-source-files.ts @@ -40,10 +40,7 @@ import { RESOLVED_DATA_GROUP_TOKEN, } from './token'; -/** - * ng-packagr 21 删掉了 `src/lib/utils/load-esm`,改成直接 require compiler-cli。 - * 这里自己保留一个懒加载入口,避免在模块顶层就把 compiler-cli 拉进来。 - */ +/** 懒加载入口,避免在模块顶层就把 compiler-cli 拉进来。 */ async function ngCompilerCli() { return import('@angular/compiler-cli'); } @@ -81,8 +78,7 @@ export async function compileSourceFiles( augmentLibraryMetadata(tsCompilerHost); const cache = entryPoint.cache; const sourceFileCache = cache.sourcesFileCache; - // ng-packagr 19: Angular 诊断缓存从 FileCache 中拆出, - // 改为 entryPoint.cache.angularDiagnosticCache(get/update) + // Angular 诊断缓存从 FileCache 中拆出,改为 entryPoint.cache.angularDiagnosticCache const angularDiagnosticsCache = cache.angularDiagnosticCache; // Create the Angular specific program that contains the Angular compiler @@ -96,8 +92,8 @@ export async function compileSourceFiles( const angularCompiler = angularProgram.compiler; const { ignoreForDiagnostics, ignoreForEmit } = angularCompiler; - // SourceFile versions are required for builder programs. - // The wrapped host inside NgtscProgram adds additional files that will not have versions. + // SourceFile versions are required for builder programs. The wrapped host inside NgtscProgram + // adds additional files that will not have versions. const typeScriptProgram = angularProgram.getTsProgram(); augmentProgramWithVersioning(typeScriptProgram); @@ -125,17 +121,14 @@ export async function compileSourceFiles( const result = builder.getSemanticDiagnosticsOfNextAffectedFile( undefined, (sourceFile) => { - // If the affected file is a TTC shim, add the shim's original source file. - // This ensures that changes that affect TTC are typechecked even when the changes - // are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes. - // For example, changing @Input property types of a directive used in another component's - // template. + // If the affected file is a TTC shim, add the shim's original source file. This ensures + // that changes that affect TTC are typechecked even when the changes are otherwise + // unrelated from a TS perspective and do not result in Ivy codegen changes. if ( ignoreForDiagnostics.has(sourceFile) && sourceFile.fileName.endsWith('.ngtypecheck.ts') ) { - // This file name conversion relies on internal compiler logic and should be converted - // to an official method when available. 15 is length of `.ngtypecheck.ts` + // 依赖内部编译逻辑:15 是 `.ngtypecheck.ts` 的长度 const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts'; const originalSourceFile = builder.getSourceFile(originalFilename); if (originalSourceFile) { @@ -192,22 +185,17 @@ export async function compileSourceFiles( ], }); const miniProgramCompilerService = injector.get(MiniProgramCompilerService); - // Required to support asynchronous resource loading - // Must be done before creating transformers or getting template diagnostics + // Required to support asynchronous resource loading. Must be done before creating transformers + // or getting template diagnostics await angularCompiler.analyzeAsync(); // inject miniProgramCompilerService.init(); const metaMap = await miniProgramCompilerService.exportComponentBuildMetaMap(); /** - * 把内联样式(`@Component.styles` / 模板 `