
深入 Angular 编译器内核ngtsccore包、NgCompiler与增量编译机制全解析【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular导读packages/compiler-cli/src/ngtsc/core/是 Angular 官方仓库中定位编译器心脏的目录——无论是命令行工具ngc、Angular CLI 内部使用的NgtscProgram还是 Bazel 规则ts_library的实验性集成NgTscPlugin最终都汇聚到这里的NgCompiler。本文以该目录的 README 为主线结合 core/src/compiler.ts、core/src/host.ts 与 ngtsc/program.ts 等源码为你讲清三条核心主线Angular 编译如何嵌入 TypeScript 编译流程、NgCompiler如何通过CompilationTicket管理增量编译生命周期以及NgCompilerOptions背后分层设计的选项体系。读完你将能够理解并二次开发任何以 TypeScript 编译器为宿主、内嵌 Angular 编译能力的集成方案。Angular 编译的本质装饰器到静态定义字段的翻译Angular 编译的核心任务是把 TypeScript 源码中的 Angular 装饰器Component、Directive、NgModule、Injectable、Pipe等翻译为 Ivy 编译器可识别的静态定义字段static definition fields。编译发生在整个 TypeScript 编译过程之中构建期build timeTypeScript 代码被进行类型检查type-checking再被降级downlevel为 JavaScript 代码沿途产生 TypeScript 自己的诊断信息同时也会产出Angular 特有的诊断信息如模板类型错误、非法装饰器配置等。换句话说Angular 的 AOT 编译并不是一套独立的工具链而是寄生在 TypeScript 编译器工作流中的一个扩展层。core包提供的正是让第三方编译器实现者把 Angular 编译装配进 TypeScript 编译流程的 API。从 ts.CompilerHost 到 NgCompiler融入 Angular 的编译流程标准 TypeScript 编译流程任何使用 TypeScript 编译器 API 的程序都遵循同一套多步骤过程创建ts.CompilerHost用该 Host 外加一组根文件root files创建ts.Program用ts.Program收集各类诊断diagnostics最终调用ts.Program的emit产出 JavaScript 代码。融入 Angular 后的八步流程集成 Angular 编译的编译器依然遵循类似流程只是中间多了几步。README 中的对照流程如下创建ts.CompilerHost将该 Host 包装进一个NgCompilerHost后者会向编译中添加 Angular 特有的文件基于NgCompilerHost及其扩充后的根文件集合创建ts.Program创建一个CompilationTicket可选地携带上一次编译运行的状态使用该CompilationTicket创建NgCompiler照常从ts.Program收集诊断同时也可以从NgCompiler收集诊断在emit之前调用NgCompiler.prepareEmit拿到需要喂给ts.Program.emit的 Angular transformers携带上述 transformers 调用ts.Program.emit产出带 Angular 扩展的 JavaScript。在仓库中的实际落地NgtscProgram这套抽象不只是文档中的设计蓝图Angular CLI 实际消费的NgtscProgram就在 ngtsc/program.ts 中实现。可以看到它与文档描述的步骤一一对应在 program.ts 中通过NgCompilerHost.wrap(delegateHost, rootNames, options, reuseProgram ?? null)完成第 2 步的 Host 包装在 program.ts 中依据是否存在上一次编译程序选择创建 fresh ticket 还是 incremental ticket第 4 步在 program.ts 中将异步资源加载委托给this.compiler.analyzeAsync()在 program.ts 的 emit 阶段通过this.compiler.prepareEmit()取出 transformers 再执行真正的 TS 发射。NgCompiler 与增量编译两种信息、两种模式NgCompiler被源码注释称为 The heart of the Angular Ivy compilerIvy 编译器的心脏定义于 core/src/compiler.ts。它是一个惰性编译器——在调用任一输出方法如getDiagnostics之前不会执行任何实质编译工作。Angular 编译器支持增量编译借助上一次编译的信息来加速下一次编译。编译过程中编译器会产出两大类信息信息类别含义例子本地信息local information与单个文件/组件绑定组件与指令的元数据全局信息global information跨文件汇总的结果物化后的 NgModule 作用域reified NgModule scopes围绕这两类信息增量编译以两种方式管理常规代码变更新建一个NgCompiler可以有选择地从旧实例继承本地信息并且只在底层 TypeScript 文件真正发生变化的地方重算而全局信息在每次编译时总是从头重新计算。资源类变更例如组件资源变化整个NgCompiler可以被整体复用原地更新以吸收变更影响无需重算其他任何信息。注意两种模式的核心区别在于是否需要新NgCompiler实例还是可以复用旧实例。为了不让这种实现复杂度泄漏给调用方、避免调用方去精细管理NgCompiler的生命周期整个过程被抽象为CompilationTicket调用方先拿到一个 ticket取决于本次变更的性质再用 ticket 取回NgCompiler实例在创建 ticket 时编译器内部自行决定复用旧实例还是新建实例。CompilationTicket 三兄弟与 fromTicket 分发CompilationTicket是一个可辨识联合discriminated union源码位于 core/src/compiler.ts。其判别字段是枚举CompilationTicketKind共有三种成员export enum CompilationTicketKind { Fresh, // 全新编译 IncrementalTypeScript, // 增量TypeScript 源码变更 IncrementalResource, // 增量仅组件资源变更 } export type CompilationTicket | FreshCompilationTicket | IncrementalTypeScriptCompilationTicket | IncrementalResourceCompilationTicket;三种 ticket 的语义与构成FreshCompilationTicket从零开始一次编译。携带完整的NgCompilerOptions、ts.Program、增量构建策略、程序驱动ProgramDriver等不引用任何旧状态。IncrementalTypeScriptCompilationTicket处理 TypeScript 代码变化。除了新ts.Program外还携带旧程序对应的IncrementalCompilation状态。IncrementalResourceCompilationTicket只包含被修改的组件资源文件集合与旧的NgCompiler实例本身——这正是整体复用编译器模式的载体。源码提供了四个配套的工厂函数均在 core/src/compiler.ts 中freshCompilationTicket(...)以无任何旧状态的方式创建全新编译的 ticketincrementalFromCompilerTicket(...)根据旧NgCompiler实例 新ts.Program尽可能高效地构造 ticket如果旧程序找不到对应的IncrementalState会回退为 fresh ticketincrementalFromStateTicket(...)从旧的ts.Program、IncrementalState与新的ts.Program直接构造 ticketresourceChangeTicket(...)构造仅处理资源变更的IncrementalResourceCompilationTicket。ticket 的解包逻辑集中在静态方法NgCompiler.fromTicketcore/src/compiler.ts对Fresh与IncrementalTypeScript两种分支它都new一个NgCompiler区别只在于传入IncrementalCompilation.fresh(...)还是复用的incrementalCompilation而对IncrementalResource分支它直接在旧编译器上调用compiler.updateWithChangedResources(ticket.modifiedResourceFiles, ticket.perfRecorder)并返回同一个实例。ngtsc/program.ts 恰好演示了消费方视角当没有旧程序时调用freshCompilationTicket否则调用incrementalFromCompilerTicket(oldProgram.compiler, ...)。异步编译analyzeAsync 与资源加载在某些编译环境例如 Angular CLI 中由 webpack 驱动的编译部分编译输入只能异步产生。典型例子styleUrls指向 SASS 文件时编译 SASS 需要派生一个子 webpack 编译child webpack compilation才能完成。为此 Angular 提供了异步加载此类资源的接口对应ResourceHost语义。使用该接口时在NgCompiler创建之后需额外执行一个异步步骤——调用NgCompiler.analyzeAsync()并await其返回的Promise。该操作完成后所有资源均已加载完毕此后NgCompiler的其余 API 就可以同步使用。实现位于 core/src/compiler.ts它会遍历 trait compiler 收集analyzeAsync(sf)产生的 Promise 并统一等待。与之对应的NgtscProgram.loadNgStructureAsync()也直接透传给NgCompiler见 program.ts。包装 ts.CompilerHost合成文件从哪来Angular 编译会根据配置生成一批合成文件synthetic files即原本并非输入、也不存在于磁盘上的文件。典型的包括请求 flat module 时生成的flat module index 文件如index.js/index.d.ts支撑模板类型检查代码的__ngtypecheck__.ts文件README 中写作__ng_typecheck__.ts。这些文件并不存在于磁盘但对ts.Program而言必须表现得像真实文件一样。解决方式是对外包装ts.CompilerHost对ts.Program来说 Host 是对外界的抽象用一个能在需要时凭空提供这些合成文件的实现去包装它。这正是NgCompilerHost的核心职责实现于 core/src/host.ts。DelegatingCompilerHost全量委托杜绝漏委托core/src/host.ts 中的DelegatingCompilerHost是NgCompilerHost的基类注释道出了它存在的动机TypeScript 不断向ts.CompilerHost添加新的可选方法委托型 Host 漏实现或漏转发这些可选方法不会触发类型错误却会在运行时产生隐蔽故障。因此这里用类型体操保证漏委托 编译期报错如果未来ts.CompilerHost新增了未被转发的方法该类会直接产生类型错误。getSourceFile与fileExists两个方法被刻意排除在委托之外因为它们由NgCompilerHost亲自实现。NgCompilerHost.wrap 的装配逻辑静态工厂NgCompilerHost.wrap(delegate, inputFiles, options, oldProgram)core/src/host.ts把整个包装流程串起来计算根目录rootDirs注册每文件 shim 生成器TypeCheckShimGenerator用于模板类型检查文件若配置了flatModuleOutFile则通过FlatIndexGenerator注册顶层 shim 生成器并寻找 flat module 入口找不到合法入口时产生错误码CONFIG_FLAT_MODULE_NO_INDEX的诊断用ShimAdapter统一管理 shim 的按需生成与缓存maybeGenerate并支持携带oldProgram做 shim 的增量复用用ShimReferenceTagger在用户文件被读取时打上引用标记tag以便类型检查程序能识别 shim 引用组装并返回NgCompilerHost。构造出的 Host 暴露了ignoreForEmit不应输出为 JS 的文件集合多为ngtypecheck类 shim、shimExtensionPrefixes、constructionDiagnosticsHost 构建期间产生的诊断等接口在getSourceFile中会优先询问 shim adapter 是否需要生成 shim否则回落给被委托的 Host 并执行引用标记host.tsfileExists则同时回答真实存在或任一 shim 生成器认识它host.ts。API 定义按用途分层的选项体系core包内散布着跨编译器使用的独立 API 定义通过 api/index.ts 统一导出adapter、interfaces、options、public_options四组模块。NgCompilerOptions统一 TypeScript 与 Angular 的选项最值得关注的接口是NgCompilerOptions定义于 core/api/src/options.ts。它把 Angular 支持的各类编译选项与 TypeScript 自身的ts.CompilerOptions合并为一个整体export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, // View Engine 遗留、仍被 Ivy 兼容消费的选项 BazelAndG3Options, // 面向 Bazel / monorepo 构建的选项 DiagnosticOptions, // 诊断类别配置 TypeCheckingOptions, // 模板类型检查及其严格度 TestOnlyOptions, // 仅测试使用的内部选项 I18nOptions, // i18n 支持 TargetOptions, // 目标相关选项 InternalOptions, // 编译器内部选项 MiscOptions { // 杂项 // 为兼容 ts.CompilerOptions 的索引签名而放宽 [prop: string]: any; }它可赋值给ts.CompilerOptions同时被 transformers/api.ts 中的旧式CompilerOptions类型所实现该类型还叠加了genDir、basePath、skipMetadataEmit、annotationsAs、enableResourceInlining等一代ngc遗留字段。正如 README 指出的各类选项按用途与支持层级被拆分进彼此独立的接口。各类选项族逐层拆解LegacyNgcOptionsView Engine 遗留Ivy 向后兼容消费见 core/api/src/public_options.tsflatModuleOutFile生成指定名字的 flat module index 与对应的.metadata.json适用于以 flat 形式打包的库如angular/core要求files中只有一个.ts入口或显式给定libraryIndex否则报错flatModuleId导入 flat module 时使用的模块 id仅在同时给出flatModuleOutFile时生效strictInjectionParameters构造参数注入类型无法确定时默认产生告警置true则升级为错误preserveWhitespaces是否在编译模板时移除空白文本节点Angular 6 起默认falseallowEmptyCodegenFiles已废弃不再使用。TypeCheckingOptionsAngular 模板类型检查严格度见 core/api/src/public_options.tstypeCheckHostBindings是否开启 host binding 的类型检查strictTemplates总开关置true时隐含开启下方所有模板严格度开关除非单独关闭默认truestrictInputTypes是否校验绑定表达式对指令/组件input字段的赋值类型默认falsestrictInputAccessModifiers是否拦截对readonly/private/protectedinput 的绑定赋值默认false且依赖strictInputTypes开启strictNullInputTypes开启后对可能求值为null/undefined的绑定做严格空值检查需 TSstrictNullChecks默认falsestrictAttributeTypes是否校验被指令消费的文本属性如input matInput disabled默认falsestrictSafeNavigationTypes空安全导航a?.b的返回类型是否取严格类型而非any默认falsestrictDomLocalRefTypes#ref局部模板引用变量是否按createElement结果推断而非any默认falsestrictOutputEventTypes指令输出上$event是否按EventEmitter/Subject泛型推断默认falsestrictDomEventTypesDOM 事件上$event是否按HTMLElementEventMap推断默认falsestrictContextGenerics泛型组件在模板上下文类型中是否保留真实泛型参数默认置any默认falsestrictLiteralTypes模板中的对象/数组字面量使用推断类型还是any默认false、strictTemplates开启后生效strictStandalone置true后禁止非 standalone 声明并使其成为构建错误。DiagnosticOptions诊断类别控制见 core/api/src/public_options.ts配合枚举DiagnosticCategoryLabelwarning/error/suppress同文件extendedDiagnostics.defaultCategory未在checks中覆盖的可配置诊断的默认类别默认warningextendedDiagnostics.checks按扩展模板诊断名称到类别的映射可逐项把某类诊断提级为错误或降级为忽略。I18nOptions国际化见 core/api/src/public_options.tsi18nInLocale导入翻译的语言环境、i18nOutFormat导出格式xlf/xlf2/xmb、i18nOutFile、i18nOutLocale、enableI18nLegacyMessageIdFormat是否用旧的$localize消息 id默认true、i18nUseExternalIds。BazelAndG3Optionsmonorepo / Bazel见 core/api/src/public_options.tsgenerateDeepReexports生成 NgModule 对其指令/管道的私有再导出以支撑深路径导入、onlyPublishPublicTypingsForNgModules.d.ts中只列出公开导出的类型、annotateForClosureCompiler、onlyExplicitDeferDependencyImports、generateExtraImportsInLocalMode、_experimentalAllowEmitDeclarationOnly、legacyOptionalChaining、enableTemplateSourceLocations。TestOnlyOptions 与 InternalOptions内部见 core/api/src/options.ts前者包括_useHostForImportGeneration、_enableTemplateTypeChecker、_compilePoisonedComponents、tracePerformance后者包括supportTestBed默认true供 CLI 使用、supportJitMode默认true、externalRuntimeStyles、_angularCoreVersion、_enableHmr、_enableSelectorless、_isAngularCoreCompilation。注意这些下划线开头选项均为internal不面向普通用户。从 adapter 到 host解耦出可插拔的适配层NgCompilerAdapter定义于 core/api/src/adapter.ts是NgCompilerHost中被NgCompiler真正依赖的那部分ts.CompilerHost实现的子集所以消费方既可以直接使用现成的NgCompilerHost也可以自己实现NgCompilerAdapter。它要求提供entryPoint、constructionDiagnostics、ignoreForEmit、unifiedModulesHost、rootDirs等字段并对文件来源做两类判别isShim(sf)文件是否是 Angular 加入编译的 shim如ngfactory、ngtypecheck用于把类型检查限定在用户文件上isResource(sf)文件是否是资源文件语言服务会把资源作为根文件加入项目时需要。配套的ExtendedTsCompilerHostinterfaces.ts在ts.CompilerHost之上叠加了可选的ResourceHostresourceNameToFileName、readResource、getModifiedResourceFiles、transformResource与UnifiedModulesHostfileNameToModuleName用于 Bazel 这类对模块命名空间有统一视图的构建系统。transformResource目前仅支持style类型资源见ResourceHostContext的type: style其结果是携带content的TransformResourceResult——这正是 CLI 在 HMR 场景中为每个 style 生成确定性标识order字段所依赖的接口。总结与进一步探索core包把Angular 编译如何融入 TypeScript 编译器这一复杂命题收敛成了三件清晰可用的产物NgCompilerHost向ts.Program呈现合成文件、CompilationTicket/NgCompiler管理从全新到增量再到资源热更新的完整生命周期、NgCompilerOptions及细分选项接口把 TS 与 Angular 的配置统一并分层。README 中描述的每一处设计都能在上层NgtscProgram的调用链里找到落点。建议按如下顺序继续深读源码生命周期总览core/src/compiler.tsticket 三兄弟、工厂函数、NgCompiler.fromTicket异步与发射core/src/compiler.ts 与 program.tsHost 包装细节core/src/host.ts选项体系core/api/src/public_options.ts 与 core/api/src/options.ts集成入口与测试program.ts 与 test/compiler_spec.ts。其中 compiler_spec.ts 覆盖了从全新编译到资源变更复用等关键行为的回归验证是观察NgCompiler生命周期语义最直观的窗口。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考