在 Angular 单体应用中,本地开发使用 CLI 运行@angular-devkit/build-angular:dev-server构建器启动本地开发服务器,部署时通过 CLI 内置的 ng build 构建,把dist构建输出文件部署到nginx服务器,CLI 已经帮开发者处理了本地开发和部署构建等一系列问题。

那么在微前端架构下,因为存在多个独立的 Angular 应用,甚至还是跨仓储多团队分别维护的,那么本地开发和生成环境部署会相对复杂一些,本地开发分"独立端口访问"和"使用代理访问"。

主应用和子应用的本地开发代理服务器使用不同的端口访问,这样注册子应用时无需加路径区分子产品,因为端口号就代表了某个子产品,如:

注册子应用代码为:

this.planet.registerApps([ { name: 'app1', hostParent: '#app-host-container', routerPathPrefix: '/app1', entry: 'http://127.0.0.1:3001/index.html', switchMode: SwitchModes.coexist, preload: true, extra: { name: '应用1', color: '#ffa415' } }, { name: 'app2', hostParent: '#app-host-container', routerPathPrefix: '/app2', entry: { basePath: 'http://127.0.0.1:3002', manifest: 'http://127.0.0.1:3002/index.html', scripts: ['main.js'], styles: ['main.css'] }, switchMode: SwitchModes.coexist, preload: true, extra: { name: '应用2', color: '#ffa415' } } ]);
entry同时支持字符串和对象配置,了解更多参考:PlanetApplication.entry
entry: "http://127.0.0.1:3002/static/app1/index.html" // 等价于 entry: { basePath: "http://127.0.0.1:3002/static/app1/", manifest: "http://127.0.0.1:3002/static/app1/index.html" }

如果本地开发采用不同的端口,生产环境部署却是在同一个域名下,那么主应用需要根据环境变量是否为本地开发和生成环境配置不同的entry地址:

this.planet.registerApps([ { name: 'app1', hostParent: '#app-host-container', routerPathPrefix: '/app1', entry: environment.production ? 'static/app1/index.html' : 'http://127.0.0.1:3001/index.html', switchMode: SwitchModes.coexist, preload: true, extra: { name: '应用1', color: '#ffa415' } } ]);

这里的访问地址需要根据部署环境的差异设置,子产品的静态资源包含两部分index.html和其他资源(脚本、样式、图片、字体等),除了index.html外其他静态资源可能会部署在独立的 CDN 中。

另外一种本地开发的方式就是主应用通过代理配置每个子产品的访问路径。

修改angular.json中的serve.options.proxyConfig配置一个代理文件proxy.conf.js

{ ... "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "proxyConfig": "./proxy.conf.js" } }

修改proxy.conf.js为:

const PROXY_CONFIG = {}; PROXY_CONFIG['/static/app1'] = { target: 'http://localhost:3001', secure: false, changeOrigin: false }; PROXY_CONFIG['/static/app2'] = { target: 'http://localhost:3002', secure: false, changeOrigin: true }; module.exports = PROXY_CONFIG;

这里通过static/{appName}区分每个应用静态资源的访问路径,避免出现冲突,采用代理启动本地开发服务器时都是通过主应用的端口访问子应用资源:

注册应用的entry直接配置为代理的静态资源路径,比如:

this.planet.registerApps([ { name: 'app1', hostParent: '#app-host-container', routerPathPrefix: '/app1', entry: 'static/app1/index.html', switchMode: SwitchModes.coexist, preload: true, extra: { name: '应用1', color: '#ffa415' } } ]);

当使用此种模式时推荐生产环境部署也遵循同样的路径规则,这样不管是在本地开发还是生产环境,子产品的资源路径都是static/app1/*

子应用本地启动需要设置serve-path=static/app1/,这样子应用的静态资源访问路都是static/app1/*,否则本地访问失败。

{ "name": "app1", "scripts": { "start": "ng serve --port 3001 --serve-path=static/app1/", ... } }

Angular 从 17 版本开始,默认采用了 Vite 和 ESBuild 构建,如果主应用和子应用需要使用 ESBuild 插件和自定义配置,可以通过 @angular-builders/custom-esbuild 构建器实现。

npm i -D @angular-builders/custom-esbuild

通过 plugins 设置插件和index.html转换器。

"architect": { ... "build": { "builder": "@angular-builders/custom-esbuild:application", "options": { "plugins": ["./esbuild/plugins.ts", "./esbuild/plugin-2.js"], "indexHtmlTransformer": "./esbuild/index-html-transformer.js", "outputPath": "dist/my-cool-client", "index": "src/index.html", "browser": "src/main.ts", "polyfills": ["zone.js"], "tsConfig": "src/tsconfig.app.json" } } }

如果主应用和子应用都采用 Webpack 构建器,可以通过 @angular-builders/custom-webpack 构建器实现 Webpack 自定义配置。

npm i -D @angular-builders/custom-webpack

通过customWebpackConfig配置额外的 Webpack 配置文件

"architect": { ... "build": { "builder": "@angular-builders/custom-webpack:browser", "options": { "customWebpackConfig": { "path": "./extra-webpack.config.js" }, ... } }, "serve": { "builder": "@angular-builders/custom-webpack:dev-server", "options": { "browserTarget": "my-project:build" } }

如果注册应用entry.manifest或者manifest配置的为assets-manifest.json文件,那么需要通过webpack-assets-manifest插件单独生成assets-manifest.json,这里推荐配置为index.html,这样减少插件的安装。

const WebpackAssetsManifest = require('webpack-assets-manifest'); module.exports = { output: { publicPath: '/static/app2/' }, plugins: [new WebpackAssetsManifest()], };

如果子产品注册的时候 scripts 配置了相关脚本,那么只会加载配置的脚本,例如只设置了["main.js"],那么需要修改extra-webpack.config.js设置runtimeChunk: false,这样就不会生成runtime.js文件:

optimization: { runtimeChunk: false }

否则需要把runtime.js加入到scripts配置中:

this.planet.registerApps([{ name: "app1", ... scripts: ["runtime.js", "main.js"] }]);
angular.json中的vendorChunk配置也是如此,当设置为true时需要把vendor.js加入到scripts配置中:
this.planet.registerApps([{ name: "app1", ... scripts: ["vendor.js", "main.js"] }]);

推荐使用index.html作为entrymanifest入口文件,scripts无需设置,这个会加载index.html输出的所有 script 标签脚本文件。