尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Angular cli-hello-world-lazy 集成测试:如何验证懒加载分包与 ngDevMode 的构建期移除

Angular cli-hello-world-lazy 集成测试:如何验证懒加载分包与 ngDevMode 的构建期移除 Angular cli-hello-world-lazy 集成测试如何验证懒加载分包与 ngDevMode 的构建期移除【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angularcli-hello-world-lazy是 Angular 仓库integration/目录下的一组 CLI 集成测试核心目标有两个一是验证当应用存在懒加载模块时生产构建的 bundle 体积是否符合预期基线二是验证全局变量ngDevMode及其在 ng_dev_mode.ts 中的字符串引用在 production 构建后被正确 tree-shaking 移除。读完本文你可以掌握这套构建产物静态扫描 体积基线比对的回归防护机制的实现方式并了解如何复用到自己项目的 CI 流程中。测试意图两个必须跨 chunk 成立的构建保证原始文档 README.md 对该测试的定位是This test checks bundle sizes when there is a lazy module. It also checks if thengDevModeglobal variable and string references inpackages/core/src/util/ng_dev_mode.tsare correctly removed. This test contains a lazy route to ensurengDevModeremoval happens even across chunks, and a payload size check to ensure extra code is not retained accidentally.拆解开来它守护着两条容易在编译器/打包器改动中被悄悄破坏的保证ngDevMode必须从产物中消失。ngDevMode是 Angular 的开发模式全局开关仅应在运行时按需存在。生产构建中如果ngDevMode的引用没有被移除说明 dev-mode 相关的检查代码可能被错误保留体积泄漏或者用户无法再通过window.ngDevMode false等手段干预框架行为。懒加载 chunk 必须真正懒。仅仅检查主 chunk 是不够的——ngDevMode的引用同样可能出现在路由懒加载生成的独立 chunk 里。因此该测试特意内置了一条懒路由确保移除逻辑对跨 chunk 的产物同样生效。payload 体积有基线约束。通过 size.json 记录的体积基线防止意外保留的多余代码例如本应被摇掉的 dev 分支让产物悄悄膨胀。最小应用骨架一条懒路由 独立组件测试应用本身是一个刻意保持最小的 standalone 应用全部源码位于 integration/cli-hello-world-lazy/src 下。入口 main.ts 使用bootstrapApplication引导根组件并通过provideRouter(appRoutes)注册路由import {bootstrapApplication, provideProtractorTestingSupport} from angular/platform-browser; import {provideRouter} from angular/router; import {AppComponent} from ./app/app.component; import {appRoutes} from ./app/app.routes; bootstrapApplication(AppComponent, { providers: [provideRouter(appRoutes), provideProtractorTestingSupport()], }).catch(console.error);关键在于 app.routes.ts 中定义的唯一一条路由它使用loadChildren配合动态import()指向独立的懒路由文件export const appRoutes: Routes [ { path: lazy, loadChildren: () import(./lazy/lazy.routes).then((routes) routes.lazyRoutes), }, ];而 lazy.routes.ts 只包含一个极简组件export const lazyRoutes: Routes [{path: , component: LazyComponent}];LazyComponent 的模板只有一行plazy works!/p。根组件 AppComponent 则通过RouterOutlet提供渲染出口。这个骨架的意义在于import(./lazy/lazy.routes)这条动态导入是打包器切分 chunk 的唯一依据——构建后它会生成一个独立于main.js的懒加载 chunk正是检查脚本需要扫描的对象。构建配置为什么 production 配置是检查的前提angular.json 使用了新版angular/build:application构建器其配置对两个检查目标都做了针对性设计配置项默认devproduction 配置作用optimizationfalsetrue开启 terser 压缩与 dead code elimination这是ngDevMode被移除的前提aottrue—全程 AOT 编译namedChunkstruetrue用模块名而非数字编号命名 chunk产物可读、可定位outputHashingnonenone主入口文件名稳定便于检查脚本与体积基线按固定文件名比对sourceMap—false减小产物且避免 map 文件干扰.js扫描extractLicenses—true与真实生产发布对齐polyfills[zone.js]—使用 zone.js 调度production 配置还声明了两组 budgetsbudgets: [ {type: initial, maximumWarning: 2mb, maximumError: 5mb}, {type: anyComponentStyle, maximumWarning: 6kb, maximumError: 10kb} ]这些预算在ng build --configuration production阶段即构成第一道体积防线initial chunk 超过 2mb 警告、5mb 直接报错。而真正的精确基线检查则由 size.json 承担当前记录的体积基线为{ dist/main.js: 108611, dist/polyfills.js: 34169, dist/lazy.routes-[hash].js: 361 }可以看到懒加载 chunklazy.routes-[hash].js被单独列名且仅约 361 字节——这正体现了懒代码不混入初始 payload的预期初始main.js与懒 chunk 的体积各自独立受控。ngDevMode 移除检查一个扫描 dist 的 Node 脚本检查逻辑全部集中在 check-output-for-ngdevmode.js全文如下const fs require(fs); const path require(path); const distPath ./dist/; const ngDevModeVariable ngDevMode; const filesWithNgDevMode fs .readdirSync(distPath) .filter((p) p.endsWith(.js)) .filter((p) fs.readFileSync(path.join(distPath, p), utf-8).includes(ngDevModeVariable)); if (filesWithNgDevMode.length 0) { throw new Error( Found ${ngDevMode} referenced in ${filesWithNgDevMode}. These references should be tree-shaken away!, ); } else { console.log(No ${ngDevMode} references found in ${distPath}); }脚本的思路非常直接但足够严格递归无关逐个文件、逐字节。它列出dist/目录下所有.js文件即同时覆盖main.js与懒加载 chunk对每个文件做子串匹配ngDevMode只要任何一个文件命中就抛出错误并列出命中文件名使pnpm test失败。这个字符串级检查比变量级检查更强——它连压缩后残留的字符串字面量都能捕获。这个检查之所以必要且可行可以从 ng_dev_mode.ts 的源码实现得到印证。框架通过declare global声明了ngDevMode全局量其值可以是null | NgDevModePerfCounters还附带 hydration 性能计数器并可能为false。初始化入口是export function initNgDevMode(): boolean { if (typeof ngDevMode undefined || ngDevMode) { if (typeof ngDevMode ! object || Object.keys(ngDevMode).length 0) { ngDevModeResetPerfCounters(); } return typeof ngDevMode ! undefined !!ngDevMode; } return false; }参见 ng_dev_mode.ts#L80-L92值得注意的是 ng_dev_mode.ts#L49-L52 中的这段注释与写法// Make sure to refer to ngDevMode as [ngDevMode] for closure. ... global[ngDevMode] false;框架源码刻意用global[ngDevMode]的括号取值方式而非global.ngDevMode目的就是配合编译器closure在 production 优化时把对全局的探测彻底擦除。换言之产物里不应再出现ngDevMode字符串是框架源码写法、optimization: true的 dead code elimination、以及本检查脚本三者共同约定并守护的契约。该测试中的懒路由则进一步保证这份契约在每一个chunk 上都成立——这正是 README 中 even across chunks 的含义。测试执行链pnpm 脚本与 Bazel 编排在 package.json 中整条测试链被压缩为两个脚本build: ng build --configuration production, test: pnpm build node check-output-for-ngdevmode.js即先用 production 配置执行真实构建optimization: true再运行产物扫描脚本。任何一步失败构建超预算、产物中出现ngDevMode都会使test退出非零。该目录下的 BUILD.bazel 只有一行ng_integration_test( name test, )具体的行为由 integration/index.bzl 中的ng_integration_test宏统一提供。从该宏的源码结构看它会自动以native.glob收集本目录全部源文件排除node_modules作为测试输入默认执行命令为pnpm installpnpm run test并用tool_mappings注入 pnpm 与 node 工具链把仓库内所有INTEGRATION_PACKAGES即packages/下各 Angular 包以本地构建的 tgz 归档覆盖进测试的node_modules——这意味着该集成测试验证的是当前分支刚构建出来的 Angular 框架代码而非 npm 上的已发布版本为测试打上integration与no-sandbox标签因为它可能启动宿主应用超时设为long15 分钟体量标记为enormous。这套机制的可复用价值cli-hello-world-lazy虽然代码量极小但它示范了一种对前端框架项目非常有价值的回归防护范式三个构件均可独立借鉴字符串级产物扫描用一个几十行的 Node 脚本断言某些标识符/字符串绝不能出现在 dist 中比单元测试更早发现构建管线回归对应 check-output-for-ngdevmode.js体积基线文件用 size.json 之类的基线把产物不能意外变大变成可 diff 的显式断言与budgets的粗粒度警告形成两级防护最小应用 真实构建路径不复用大型 demo而是维护一个几十行的最小应用standalone 根组件、一条loadChildren懒路由走真实的ng build --configuration production流程从而精确聚焦某一类构建行为——这里是懒加载分包与dev 全局变量移除。需要注意适用前提该检查脚本依赖outputHashing: none下稳定的文件名约定与dist/的扁平目录结构若你的项目启用了内容哈希或嵌套输出目录需要先调整扫描路径的枚举方式。另外该集成测试在 Bazel 环境下运行时会用仓库内构建的 Angular 包覆盖 npm 依赖因此它验证的是当前源码构建出的框架的行为这一前提在移植到其他仓库时应替换为锁定具体依赖版本。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表