如何对全局变量进行 polyfill 注入

解读

面试官抛出“全局变量 polyfill 注入”这一问,表面看是考察 Grunt 能不能“补环境”,实质想确认三点:

  1. 你是否理解 “构建时注入”与“运行时注入” 的差异;
  2. 能否在 Grunt 插件体系里找到 最轻量、最可维护 的方案,而不是自己写一堆 hack;
  3. 是否知道国内线上环境对 core-js、regenerator-runtime、intersection-observer 等高频垫片的版本锁定策略。
    答得太浅(“直接 script 标签引入”)会被认为没玩过工程化;答得太深(“自己写 babel plugin”)又容易让面试官觉得过度设计。因此,用 Grunt 官方链 + 社区插件组合,给出可复制的配置片段,才是国内一线团队最想听到的答案。

知识点

  1. Grunt 注入时机

    • 构建时注入:借助 grunt-babel + @babel/preset-env + useBuiltIns: 'entry',把 import "core-js/stable" 打到 bundle 头部;
    • 构建后注入:用 grunt-copygrunt-string-replace 把一段 <script>window.Promise||(window.Promise=...)</script> 直接插到 html 的 <head> 前;
    • 运行时注入:通过 grunt-contrib-uglify 的 banner 选项,把整份 core-js 3 的 cdn 链接以 /* eslint-disable */ 方式包裹,避免被重复压缩。
  2. 全局变量判定

    • 必须区分 “真正缺失”“已存在但实现不完整”;例如 IE11 有 Promise,但无 finally,需 core-js/es/promise/finally 单独补。
  3. 国内 CDN 与锁版本

    • 阿里、字节、腾讯前端统一走 npmmirror.com 镜像,lock 住 core-js@3.38.1 防止次版本差异导致体积暴涨;
    • 若走公司自研 CDN,需用 grunt-cdnify 把本地垫片路径批量替换为带 SRI 哈希的 URL,满足安全审计。
  4. Grunt 插件生态限制

    • grunt-webpack 已停止维护,不要混用 webpack 的 ProvidePlugin
    • grunt-babel 7 以上必须显式安装 @babel/core,否则会出现 “Cannot find module @babel/core” 的 CI 错误。

答案

分三步落地,全部用 Grunt 插件完成,零手写 loader:

  1. 安装依赖

    npm i -D grunt-babel @babel/core @babel/preset-env core-js@3 grunt-contrib-clean grunt-contrib-uglify grunt-string-replace
    
  2. Gruntfile.js 关键配置

    module.exports = function(grunt) {
      grunt.initConfig({
        // 1. 先清掉旧文件
        clean: ['dist/'],
        // 2. babel 转译 + 自动注入 core-js
        babel: {
          options: {
            presets: [
              ['@babel/preset-env', {
                useBuiltIns: 'entry',      // 关键:根据 browserslist 按需打垫片
                corejs: 3,                 // 锁定大版本
                modules: false             // 保留 ESModule,供后续 tree-shaking
              }]
            ]
          },
          dist: {
            files: [{
              expand: true,
              cwd: 'src/',
              src: '**/*.js',
              dest: 'dist/js'
            }]
          }
        },
        // 3. 把 core-js 运行时包进全局变量检测,避免重复加载
        uglify: {
          polyfill: {
            options: {
              banner: '!function(){if(typeof window=="undefined")return;var d=document,s=d.createElement("script");s.src="https://cdn.npmmirror.com/npm/core-js-bundle@3.38.1/minified.js";d.head.appendChild(s);}();'
            },
            files: {
              'dist/js/polyfill.min.js': ['empty-placeholder']
            }
          }
        },
        // 4. 将 polyfill.min.js 插到 html 最前面
        'string-replace': {
          html: {
            src: 'src/*.html',
            dest: 'dist/',
            options: {
              replacements: [{
                pattern: /<head>/,
                replacement: '<head><script src="js/polyfill.min.js"></script>'
              }]
            }
          }
        }
      });
    
      grunt.loadNpmTasks('grunt-contrib-clean');
      grunt.loadNpmTasks('grunt-babel');
      grunt.loadNpmTasks('grunt-contrib-uglify');
      grunt.loadNpmTasks('grunt-string-replace');
    
      grunt.registerTask('default', ['clean', 'babel', 'uglify:polyfill', 'string-replace']);
    };
    
  3. 源码入口第一行加

    import "core-js/stable";
    import "regenerator-runtime/runtime";
    

    这样 browserslist 里声明的最低版本(如 > 0.5%, last 2 versions, IE 9) 缺失的全局变量会被精准打进去,未用到的垫片不会增加 1KB 体积

结果:

  • 现代浏览器只加载 1.2 kB 的检测脚本;
  • IE9~11 自动拉取 37 kB 的 core-js-bundle,window.Promise、Array.from、Object.assign 全部一次性补齐
  • 后续业务代码无需再写任何兼容性判断,直接放心使用新 API。

拓展思考

  1. 微前端场景:子应用通过 qiankun 接入,主应用已注入 core-js,子应用若重复打包会造成 运行时冲突。此时应把 Grunt 的 useBuiltIns 改成 'usage',并在 babel 配置加

    ignore: [/\/node_modules\/core-js/]
    

    让子应用只补自己用到的片段,避免全局变量二次覆盖

  2. SSR 同构:Node 端不存在 window,若把 polyfill 直接打进去会报错。可以用 grunt-env 区分构建环境,在 Node 侧用

    require('core-js/stable');
    

    而在浏览器侧继续走 grunt-babel 注入,保证同一份源码两端运行

  3. 性能极限优化

    • grunt-terser 替代 uglify,开启 compress.global_defs={DEBUG:false},把开发期调试代码完全裁掉;
    • IntersectionObserver、ResizeObserver 等超大垫片,单独走 动态 import() + webpackMagicComments,Grunt 侧通过 grunt-gzip 预生成 .br 文件,把首屏体积再降 18%
  4. 合规审计:国内金融、政务项目要求 所有外链资源必须加 SRI,可用 grunt-sri-hash 在构建后自动计算 integrity 值并回写 HTML,一次性通过等保测评