描述在 grunt 中实现基座应用按需加载子应用

解读

国内微前端落地场景里,基座应用(主应用) 往往采用“先加载壳,后拉子”的按需策略,以减少首屏资源体积。Grunt 作为老牌任务运行器,本身不负责运行时调度,但可以通过构建期拆分产物 + 运行时动态注入的方式,让基座在浏览器端按路由或事件懒加载子应用。面试时,考官想确认两点:

  1. 你能否用 Grunt 把子应用构建成可独立消费的 UMD/ES 模块包
  2. 你能否在基座侧提供轻量运行时脚本完成动态加载,并保证公共依赖去重沙箱隔离

知识点

  • 多入口 Grunt 配置:利用 grunt-webpackgrunt-browserify 把每个子应用打成独立 bundle,输出到 dist/subApps/[name]/ 目录,并生成 manifest.json(含 nameurldepscss 字段)。
  • 文件指纹与版本:通过 grunt-filerev 给 js/css 加 hash,保证增量发布;grunt-manifest 把最新映射写入 subApps.json,供基座运行时拉取。
  • 运行时加载器:基座引入 2k 左右的 subAppLoader.js,内部用 System.import(或原生 import())按路由匹配拉取对应 entry.js;拉取前用 window.__sharedDepsexternals 去重,拉取后把子应用生命周期函数 bootstrap/mount/unmount 注册到全局路由表
  • 沙箱与样式隔离:构建期通过 grunt-postcss 给子应用样式加前缀选择器.subApp-[name]),运行时用 ShadowDOMproxySandbox 做 JS 隔离。
  • 本地联调grunt-contrib-connect 起服务,配合 grunt-contrib-watch 监听子应用源码,文件变动后热重编并推送 websocket 事件,基座收到后重新执行 import() 实现无刷新子应用替换

答案

  1. 构建阶段
    Gruntfile.js 中定义 subApps 多任务:

    • grunt-webpack 配置 entry: { subApp1: './src/subApp1/index.js', subApp2: './src/subApp2/index.js' }output.library='subApp_[name]'libraryTarget='umd'
    • 通过 externals: { vue: 'window.Vue', rxjs: 'window.Rx } 把基座已托管的依赖排除,减少子包体积;
    • 并行执行 grunt-contrib-uglify 压缩、grunt-contrib-cssmin 合并样式;
    • 最后执行自定义任务 grunt.registerTask('genManifest', function () { … }) 把每个子应用的 js/css/hash 写入 dist/subApps/manifest.json
  2. 部署阶段
    CI 把 dist/subApps/ 整体推到阿里云 OSS公司 CDNmanifest.json 设置 Cache-Control: no-cache,保证基座每次都能拿到最新映射。

  3. 运行阶段
    基座在 router.beforeEach 中判断即将进入的路由对应哪个子应用,若未加载则:

    const { js, css } = await fetch('/subApps/manifest.json').then(r=>r.json()).then(m=>m[subAppName]);
    if(css) loadCSS(css);   // 动态插入 <link>
    const module = await import(js);   // 按需拉取
    await module.bootstrap();          // 启动子应用
    await module.mount({ container: '#subApp-view', baseRoute: '/subApp1' });
    

    卸载时调用 module.unmount() 并移除样式表,完成干净销毁

通过以上三步,即可在纯 Grunt 体系内实现“构建期拆包 + 运行时按需”的微前端方案,兼顾老项目渐进改造首屏性能优化

拓展思考

  • 共享依赖版本冲突:若子应用依赖的 Vue 版本不一致,可在构建期用 grunt-replace 把子应用内的 Vue 重命名为 Vue_subApp1,运行时再挂载到 window,避免全局变量污染
  • 模块联邦替代方案:如果团队后续迁到 webpack5,可用 ModuleFederationPlugin 把子应用打成 remoteEntry.js,Grunt 侧通过 grunt-execute 调用 webpack 完成混合构建,实现更细粒度共享
  • 灰度发布:在 manifest.json 里增加 grayUsers 字段,基座根据用户 ID 判断拉取 js 还是 js?gray=1,实现子应用级灰度,无需整站回滚。