如何构建输出分离的Angular 2?多应用共享公共库缓存方案
刚好我之前帮团队做过类似的多Angular应用共享公共依赖的方案,既能分离打包业务代码和公共库,又能最大化利用浏览器缓存,给你详细讲讲具体怎么实现:
我们要把四个应用共享的依赖(Angular、Angular Material、RxJS这些)单独打包成一个带内容哈希的vendor文件,部署到统一的静态资源地址;然后每个应用构建时排除这些公共依赖,只打包业务代码,最后在应用的入口页面引用这个公共vendor文件。这样浏览器只要加载过一次vendor包,后续访问其他应用就会直接用缓存,不用重复下载。
步骤1:单独构建公共依赖包
首先我们需要生成一个独立的vendor bundle,这里可以用Webpack自定义配置来实现(Angular CLI也支持集成自定义Webpack配置):
- 任选一个应用项目(或者单独建个极简的Angular项目),创建
webpack.vendor.config.js配置文件:
const path = require('path'); const webpack = require('webpack'); const { AngularCompilerPlugin } = require('@ngtools/webpack'); module.exports = { entry: { vendor: [ '@angular/core', '@angular/common', '@angular/material', '@angular/cdk', 'rxjs', 'zone.js' // 把所有四个应用共享的依赖都列在这里 ] }, output: { path: path.resolve(__dirname, '../shared-vendor'), // 输出到公共目录 filename: 'vendor.bundle.[contenthash].js', // 用contenthash确保依赖不变时文件名不变 library: 'vendor_lib' // 暴露全局变量给应用使用 }, plugins: [ new webpack.ContextReplacementPlugin( /@angular(\\|\/)core(\\|\/)fesm2015/, path.resolve(__dirname, './src') ), new AngularCompilerPlugin({ tsConfigPath: './tsconfig.vendor.json', entryModule: './src/app/app.module#AppModule', // 随便用一个应用的模块就行,只是为了让Angular编译器正常工作 skipCodeGeneration: true }), new webpack.HashedModuleIdsPlugin() // 确保依赖模块ID稳定,哈希不会无故变化 ], mode: 'production' };
- 创建配套的
tsconfig.vendor.json,只保留基础编译配置:
{ "compilerOptions": { "target": "es2020", "module": "es2020", "moduleResolution": "node", "declaration": false, "noImplicitAny": false, "removeComments": true, "skipLibCheck": true, "strictNullChecks": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "resolveJsonModule": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "lib": ["es2020", "dom"], "typeRoots": ["node_modules/@types"], "types": ["node"] }, "exclude": ["node_modules", "dist"] }
- 在项目的
package.json里添加构建脚本:
"scripts": { "build:vendor": "webpack --config webpack.vendor.config.js" }
运行npm run build:vendor,就能在shared-vendor目录下生成带哈希的vendor包,比如vendor.bundle.abc123def.js。这个哈希只有当你更新依赖版本时才会变化,完美适配浏览器缓存策略。
步骤2:配置每个应用排除公共依赖
每个应用构建时要告诉Angular CLI不要打包那些已经在vendor包里的依赖,这里用自定义Webpack的externals配置:
- 安装
@angular-builders/custom-webpack:
npm install @angular-builders/custom-webpack --save-dev
- 修改应用的
angular.json,把构建器换成自定义Webpack的版本:
"projects": { "your-app-name": { "architect": { "build": { "builder": "@angular-builders/custom-webpack:browser", "options": { "customWebpackConfig": { "path": "./webpack.app.config.js" }, // 保留原来的其他配置(输出路径、assets等) } } } } }
- 创建
webpack.app.config.js,把公共依赖声明为外部资源:
module.exports = { externals: { '@angular/core': 'vendor_lib', '@angular/common': 'vendor_lib', '@angular/material': 'vendor_lib', '@angular/cdk': 'vendor_lib', 'rxjs': 'vendor_lib', 'zone.js': 'vendor_lib' // 所有在vendor包里的依赖都要映射到全局的vendor_lib变量 } };
这样构建应用时,Webpack会把这些依赖当成外部资源,不会打包进应用的bundle里,而是直接引用全局的vendor_lib变量。
步骤3:在应用中引入公共vendor包
把生成的vendor包部署到一个统一的静态资源服务地址(比如你的CDN或者服务器的静态资源目录),然后在每个应用的index.html头部添加引用:
<script src="https://your-shared-domain.com/vendor.bundle.abc123def.js"></script>
注意这里的URL要使用带哈希的文件名,当你更新依赖重新构建vendor包后,哈希会变,浏览器会自动加载新的文件;如果依赖没变化,浏览器会直接用缓存的旧文件。
步骤4:配置服务器缓存策略
为了让浏览器的缓存效果最大化,需要在服务器上给vendor包设置合适的响应头:
- 对于
vendor.bundle.[contenthash].js,设置Cache-Control: public, max-age=31536000, immutable,意思是浏览器可以缓存这个文件一年,而且不需要再向服务器验证是否更新(因为哈希变了才会有新的URL)。 - 对于应用的业务代码bundle(比如
main.xyz789.js),可以设置Cache-Control: public, max-age=0, must-revalidate,或者也给业务代码加上contenthash,这样业务更新时URL也会变,同样可以利用缓存。
额外注意事项
- 版本统一:确保四个应用使用的公共依赖版本完全一致,否则可能出现兼容性问题。可以用
npm install --save-exact来固定版本,或者用pnpm workspace、lerna这类工具统一管理依赖。 - CI/CD自动化:每次更新依赖后,重新构建vendor包,然后可以用脚本自动替换所有应用index.html里的vendor引用URL,避免手动修改出错。
- 懒加载模块:如果应用有懒加载模块,要确保懒加载模块里用到的公共依赖也已经包含在vendor包里,避免重复打包。
内容的提问来源于stack exchange,提问作者molikh

