如何动态读取 browserslist 配置决定 polyfill 注入
解读
这道题表面问“怎么读 browserslist”,实质考察候选人能否把 “构建目标 → 浏览器范围 → 代码转换策略” 这条链路在 Grunt 里跑通。国内项目普遍要兼容 微信内置 WebView、QQ 浏览器、360 极速、Safari iOS 9+、Chrome 51+ 等“钉子户”,polyfill 一旦打多包体爆炸,打少线上白屏。面试官想听的是:
- 你能在 Grunt 任务里实时拿到 browserslist(不是写死);
- 能根据结果动态收紧或放宽 @babel/preset-env 的 useBuiltIns;
- 整个流程对现有任务无侵入、可缓存、可调试。
知识点
- browserslist 的解析顺序:package.json 字段 → .browserslistrc → grunt 任务参数 → 默认 "> 0.5%, last 2 versions"。
- browserslist 官方解析库:
browserslist本身返回字符串数组,可再交给@babel/helper-compilation-targets得到{ chrome: '51', ios: '9' }结构。 - Grunt 运行时序:
grunt.initConfig之前执行grunt.file.readJSON同步读包,在任务体里再读会触发 IO 抖动;正确姿势是在 Gruntfile 顶部一次性解析,挂到grunt.config('meta.browserslist')上供后续任务消费。 - babel-loader / grunt-babel 的 dynamic 配置:
options.presets[0][1].targets必须是一个对象,不能传字符串数组,否则 @babel/preset-env 会再次调用 browserslist 导致双重解析。 - polyfill 粒度控制:
useBuiltIns: 'entry'需要在入口import 'core-js/stable';useBuiltIns: 'usage'无需手写 import,但要求 @babel/plugin-transform-runtime 关闭 corejs 避免重复注入。
- 国内常见坑:
- 公司私有 Nexus 源里 core-js 版本锁 2.x,必须显式声明 corejs: 3,否则 babel 会静默降级;
- 微信小程序 web-view 页面会强制缓存首次 JS,Grunt 任务里需加
?v=[hash]戳 让入口重新触发 entry polyfill 注入。
答案
-
安装依赖
npm i -D grunt-babel @babel/core @babel/preset-env core-js browserslist -
在 Gruntfile 顶部同步读取并解析 browserslist
const browserslist = require('browserslist'); const { default: getTargets } = require('@babel/helper-compilation-targets'); // 读取项目根目录下的实际配置 const browsers = browserslist(); // 会自动走 .browserslistrc > package.json const targets = getTargets(browsers, {}); module.exports = function (grunt) { grunt.initConfig({ meta: { browserslist: targets }, // 缓存给后面任务用 babel: { options: { presets: [ ['@babel/preset-env', { targets: '<%= meta.browserslist %>', // 动态注入 useBuiltIns: 'usage', corejs: 3 }] ] }, dist: { files: [{ expand: true, cwd: 'src', src: '**/*.js', dest: 'dist' }] } } }); grunt.loadNpmTasks('grunt-babel'); grunt.registerTask('build', ['babel']); }; -
验证
运行grunt build --verbose,控制台会打印[Babel] Based on your targets: { chrome: '51', ios: '9' }, added polyfills: es.promise, es.object.assign …证明 browserslist 被实时解析并精准注入,未出现
core-js@2或多余 polyfill。
拓展思考
- 多环境构建:国内常见“内网演示机”只需兼容 Chrome 最新,而“生产包”需拉回全量 browserslist。可在 Gruntfile 里用
grunt.option('env')区分,动态切换两个 browserslist 查询语句,实现“一套源码,两套靶标”。 - 缓存提速:
grunt-babel每次全量跑太慢,可引入grunt-cache-babel或自建grunt-newer,把 browserslist 字符串作为缓存 key 的一部分,配置一旦变更即失效重编。 - 非 JS 资源:postcss、autoprefixer 同样读 browserslist,在 Grunt 侧统一解析一次后通过
grunt.config('meta.browserslist')共享,避免三处重复查询。 - 微前端场景:子应用独立仓库,母应用通过
browserslist-config-xxx包下发统一靶标。Grunt 任务里require('browserslist-config-xxx')拿到数组再解析,实现“集团统一、项目零感知”。