基础配置

  1. const { resolve } = require('path');
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3. module.exports = {
  4. mode: 'development',
  5. entry: './src/index.js',
  6. // 绝对路径
  7. output: {
  8. // 对应主入口的输出文件名
  9. filename: 'js/[name].[hash:8].bundle.js',
  10. // 文件输出路径
  11. path: resolve(__dirname, 'build'),
  12. },
  13. // loader
  14. module: {
  15. rules: [
  16. {
  17. test: /\.css$/,
  18. use: [
  19. // 创建style标签,将样式插入
  20. 'style-loader',
  21. // css文件转成commonjs文件加载到js中,内容是字符串
  22. 'css-loader',
  23. ],
  24. },
  25. {
  26. test: /\.less$/,
  27. use: ['style-loader', 'css-loader', 'less-loader'],
  28. },
  29. {
  30. // url-loader 的问题:处理样式中的图片,无法处理html中的img路径
  31. test: /\.(png|jpe?g|gif|webp)$/i,
  32. // 需要下载 url-load file-loader url-loader 依赖 file-load
  33. // ⚠️:只使用一个loader时候,用loader: 'xxx', use: [{loader: 'xxx'}] 或者 use: ['xxx']
  34. loader: 'url-loader',
  35. options: {
  36. // 20k 以下的,转成 base64
  37. limit: 20 * 1024,
  38. // 问题:url-loader 默认使用 es6 模块化,而 html-loader 引入图片使用 commonjs
  39. // 解决:关闭url-loaderes6模块化,使用 commonjs 解析
  40. esModule: false,
  41. // 重命名
  42. name: '[hash:8].[ext]',
  43. outputPath: 'imgs',
  44. },
  45. },
  46. {
  47. // html-loader 不是打包html,而是对htmlimg的相对路径进行处理,配合 url-loader 使用
  48. // html-loader 负责引入 img,从而能被 url-loader 处理
  49. test: /\.html$/,
  50. loader: 'html-loader',
  51. },
  52. {
  53. // 其他静态资源,复制
  54. // exclude 排除这些文件
  55. exclude: /\.(css|html|js|less|sass|jpe?g|gif|png|webp)$/,
  56. loader: 'file-loader',
  57. options: {
  58. name: '[hash:10].[ext]',
  59. outputPath: 'public',
  60. },
  61. },
  62. ],
  63. },
  64. // 插件
  65. plugins: [
  66. // 默认创建一个空的html,自动引入打包输出的所有资源(css/js)
  67. new HtmlWebpackPlugin({
  68. // 配置自定义的html模板
  69. template: './src/index.html',
  70. }),
  71. ],
  72. // 开发服务器,需要安装 webpack-dev-server,不会产生任何输出,只在内存中编译打包
  73. // 启动指令:npx webpack-dev-server
  74. devServer: {
  75. // 项目构建后路径
  76. contentBase: resolve(__dirname, 'build'),
  77. // 开启gzip压缩
  78. compress: true,
  79. port: 3333,
  80. // 自动打开浏览器
  81. open: true,
  82. // 启动热更新
  83. // 默认只对 css 的热更新有作用(style-loader 处理),要想支持 js 热更新,需要在入口文件处监听 module.hot 下面有详细说明
  84. hot: true,
  85. },
  86. };


eslint 配置

  1. ...
  2. module: {
  3. rules: [
  4. /*
  5. 语法检查 eslint eslint-loader
  6. 设置检查规则:
  7. package.json 中的 eslintConfig 设置或者 .eslintrc.js 设置
  8. "eslintConfig": {
  9. "extends": "aribnb-base",
  10. "env": {
  11. "browser": true, // 指明环境为浏览器
  12. }
  13. }
  14. 要使用 airbnb , 需要安装 eslint eslint-config-airbnb-base eslint-plugin-import
  15. */
  16. {
  17. test: /\.js$/,
  18. exclude: /node_modules/,
  19. loader: 'eslint-loader',
  20. options: {
  21. fix: true,
  22. },
  23. },
  24. ],
  25. },
  26. ...

js 兼容性处理

  1. module: {
  2. rules: [
  3. /*
  4. js 兼容性处理
  5. 1.基本兼容性处理:@babel/preset-env
  6. 问题:只能处理基础的语法(箭头函数), promise 没法转
  7. 2.全部的兼容性处理:@babel/polyfill 不需要配置,直接在入口文件处引入就行
  8. 问题:所有的兼容性代码都引入了,体积太大了~
  9. 3.按需加载,core-js, 需要进行额外的配置
  10. @babel/polyfill core-js 只能二选一
  11. */
  12. {
  13. test: /\.js$/,
  14. loader: 'babel-loader',
  15. options: {
  16. presets: [
  17. [
  18. '@babel/preset-env',
  19. {
  20. // 按需加载
  21. useBuiltIns: 'usage',
  22. // 指定 core-js 版本
  23. corejs: {
  24. version: '3',
  25. },
  26. // 指定兼容性做到那个版本的浏览器
  27. targets: {
  28. chrome: '60',
  29. firefox: '60',
  30. safari: '34',
  31. ie: '9',
  32. edge: '17',
  33. },
  34. },
  35. ],
  36. ],
  37. },
  38. },
  39. ],
  40. },

js 压缩 & html 压缩

  1. * 通过指定 mode production 即可自动开启 js 压缩
  2. plugins: [
  3. new HtmlWebpackPlugin({
  4. template: './src/index.html',
  5. // 压缩 html
  6. minify: {
  7. // 移除空格
  8. collapseWhitespace: true,
  9. // 移除注释
  10. removeComments: true
  11. }
  12. })
  13. ]

css module

  1. const commonCssLoader = [
  2. ];
  3. {
  4. test: /\.less$/,
  5. use: [
  6. // 将css从js中提取成单独的文件
  7. MiniCssExtractPlugin.loader,
  8. 'css-loader',
  9. {
  10. loader: 'postcss-loader',
  11. options: {
  12. modules: true
  13. },
  14. },
  15. 'less-loader'
  16. ],
  17. },

生产环境配置(部分)

  1. // mimi-css-extract-plugin css抽出成单独的文件
  2. // optimize-css-assets-webpack-plugin 压缩css
  3. const { resolve } = require('path');
  4. const HtmlWebpackPlugin = require('html-webpack-plugin');
  5. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  6. const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
  7. process.env.NODE_ENV = 'development';
  8. module.exports = {
  9. mode: 'development',
  10. entry: './src/index.js',
  11. output: {
  12. filename: 'js/bundle.[hash:8].js',
  13. path: resolve(__dirname, 'build'),
  14. },
  15. module: {
  16. rules: [
  17. {
  18. test: /\.css$/,
  19. use: [
  20. // MiniCssExtractPlugin.loader 替代 style-loader
  21. // style-loader 用于将js里面的css抽离,并以style标签的形式插入html
  22. // MiniCssExtractPlugin.loader 用于将js里面的css抽离成单独的文件
  23. MiniCssExtractPlugin.loader,
  24. // css打包进js里面
  25. 'css-loader',
  26. // css 兼容性处理,postcss --> 需要安装 postcss-loader & postcss-preset-env
  27. /*
  28. postcss-preset-env 帮助 postcss 找到 package.json 里面的 browserslist 配置,根据这个配置加载制定的css兼容样式
  29. "browserslist": {
  30. // 要指定 postcss 的开发环境,需要设置node环境变量,process.env.NODE_ENV = 'development'
  31. "development": [
  32. "last 1 chrome version",
  33. "last 1 firefox version",
  34. "last 1 safari version"
  35. ],
  36. // postcss 处理默认是生产环境,与配置的 mode 无关
  37. "production": [
  38. ">0.2%",
  39. "not dead",
  40. "not op_mini all"
  41. ]
  42. }
  43. */
  44. {
  45. // 下面的插件使用不生效。。我去,可能是 webpack 版本不兼容的问题
  46. loader: 'postcss-loader',
  47. // options: {
  48. // // 固定写法
  49. // ident: 'postcss',
  50. // plugins: [require('postcss-preset-env')()],
  51. // },
  52. },
  53. ],
  54. },
  55. {
  56. test: /\.less$/,
  57. use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
  58. },
  59. ],
  60. },
  61. plugins: [
  62. new HtmlWebpackPlugin({
  63. template: './src/index.html',
  64. }),
  65. // css抽离成单独的文件,需要将 style-loader 更换为 MiniCssExtractPlugin.loader
  66. new MiniCssExtractPlugin({
  67. // 对输出的css文件进行重命名
  68. filename: 'css/[name].[hash:8].css',
  69. }),
  70. // 压缩css
  71. new OptimizeCssAssetsWebpackPlugin(),
  72. ],
  73. };

全 生产环境配置

  1. // 生产环境配置
  2. const { resolve } = require('path');
  3. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  4. const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
  5. const HtmlWebpackPlugin = require('html-webpack-plugin');
  6. process.env.NODE_ENV = 'production';
  7. const commonCssLoader = [
  8. // cssjs中提取成单独的文件
  9. MiniCssExtractPlugin.loader,
  10. 'css-loader',
  11. {
  12. loader: 'postcss-loader',
  13. options: {
  14. // ⚠️:自己尝试了,这个写法会抱错,webpack版本 5.42.0
  15. ident: 'postcss',
  16. // 需要指定 process.env.NODE_ENV, package.json 里面找 browserslist 对应的
  17. plugins: () => [
  18. require('postcss-preset-env')(),
  19. ],
  20. },
  21. },
  22. ];
  23. module.exports = {
  24. mode: 'production',
  25. entry: './src/index.js',
  26. output: {
  27. // 对应主入口的输出文件名
  28. filename: 'js/[name].[hash:8].bundle.js',
  29. // 文件输出路径
  30. path: resolve(__dirname, 'build'),
  31. },
  32. module: {
  33. rules: [
  34. {
  35. test: /\.css$/,
  36. use: [...commonCssLoader],
  37. },
  38. {
  39. test: /\.less$/,
  40. use: [...commonCssLoader, 'less-loader'],
  41. },
  42. /*
  43. 对同一个文件,既要执行 eslint 又要执行 兼容性处理
  44. eslint 必须要先执行,可以设置 enforce: 'pre'
  45. */
  46. {
  47. // js 语法检查,配合 package.json 中的 eslintConfig 或者 .eslintrc.js 使用
  48. test: /\.js$/,
  49. loader: 'eslint-loader',
  50. exclude: /node_modules/,
  51. // 优先执行
  52. enforce: 'pre',
  53. options: {
  54. fix: true,
  55. },
  56. },
  57. {
  58. test: /\.js$/,
  59. exclude: /node_modules/,
  60. loader: 'babel-loader',
  61. options: {
  62. presets: [
  63. [
  64. // @babel/preset-envpolyfill使用usage形式(不了解的可以查看官方文档),意思是以项目设置的target环境为前提,根据项目中使用到的api功能进行polyfill
  65. '@babel/preset-env',
  66. {
  67. useBuiltIns: 'usage',
  68. corejs: {
  69. version: '3',
  70. },
  71. targets: {
  72. chrome: '60',
  73. firefox: '50',
  74. safari: '40',
  75. ie: '9',
  76. edge: '16',
  77. },
  78. },
  79. ],
  80. ],
  81. },
  82. },
  83. {
  84. test: /\.(png|jpe?g|gif)$/,
  85. loader: 'url-loader',
  86. options: {
  87. limit: 8 * 1024,
  88. // 输出路径
  89. outputPath: 'imgs',
  90. name: '[hash:10].[ext]',
  91. // 配合 html-loader 处理图片src的路径问题
  92. esModule: false,
  93. },
  94. },
  95. {
  96. // 处理 html 里图片src路径的问题
  97. test: /\.html$/,
  98. loader: 'html-loader',
  99. },
  100. {
  101. // 其他文件,原样复制
  102. exclude: /\.(png|jpe?g|gif|js|css|less|html)$/,
  103. loader: 'file-loader',
  104. options: {
  105. // 输出路径
  106. outputPath: 'media',
  107. },
  108. },
  109. ],
  110. },
  111. plugins: [
  112. // css抽取成单独的文件
  113. new MiniCssExtractPlugin({
  114. filename: 'css/[name].[hash:8].css',
  115. }),
  116. // css 压缩
  117. new OptimizeCssAssetsWebpackPlugin(),
  118. new HtmlWebpackPlugin({
  119. template: './src/index.html',
  120. minify: {
  121. // 移除空格
  122. collapseWhitespace: true,
  123. // 移除注释
  124. removeComments: true,
  125. },
  126. }),
  127. ],
  128. };

webpack 性能优化

开发环境性能优化

优化打包构建速度
优化开发调试

生产环境性能优化

优化打包构建速度
优化代码的运行性能

HMR

  1. HMR: Hot Module Replace 模块热替换
  2. 作用:一个模块发生改变,只重新打包这一个模块,而不是打包所有模块,提升构建速度
  3. css: 可以使用HMRstyle-loader 内部实现了
  4. js: 默认不能使用HMR,只能处理非入口的热更新
  5. 解决:入口文件处监听文件变化执行回调
  6. if (module.hot) {
  7. module.hot.accept('./print', () => {
  8. // ./print 发生变化,其他模块不会重新打包构建
  9. print();
  10. });
  11. }
  12. html: 不能使用HMRhtml 热更新,需要修改 entry ['./src/index.js', './src/index.html']
  1. // ./src/index.js
  2. // js 热更新
  3. if (module.hot) {
  4. module.hot.accept('./xxxModule', () => {
  5. // ./print 发生变化,其他模块不会重新打包构建
  6. print();
  7. });
  8. }

sourcemap

  1. // 可选择的配置
  2. // [inline-|eval-|hidden-][nosources-][cheap-[module-]]-source-map
  3. [内联] 构建速度更快
  4. 1. source-map [外部]
  5. 错误代码准确信息;源代码错误位置
  6. 2. inline-source-map [内联]
  7. 生成的sourcemap信息,内联在bundle.js
  8. 错误代码准确信息;源代码错误位置
  9. 3. hidden-source-map [外部]
  10. 错误代码准确信息,没有源代码的错误位置;
  11. 隐藏源代码✅(隐藏源代码,显示构建后代码错误信息)
  12. 4. eval-source-map [内联]
  13. 生成的sourcemap信息,内联在eval函数中
  14. 错误代码准确信息;源代码错误位置
  15. 5. nosources-source-map [外部]
  16. 错误代码准确信息;源代码错误位置;没有任何源代码信息;
  17. 隐藏源代码✅(全部隐藏)
  18. 6. cheap-source-map [外部]
  19. 错误代码准确信息;源代码错误位置;没有列信息,构建较快;
  20. 7. cheap-module-source-map
  21. 6
  22. 区别:module 会将 loader source-map加入
  23. 开发和生产如何选择?
  24. 速度: eval > inline > cheap > ...
  25. 调试信息: source-map > cheap-module-source-map > cheap-source-map
  26. 隐藏源代码:nosources-source-map 或者 hidden-source-map
  27. 生产环境不选择内联的source-map,体积非常大
  28. 综合:
  29. 开发环境选择 eval-cheap-module-source-map 或者 eval-source-map(reactvue 用的都是这种)
  30. 生产环境选择
  31. 隐藏源代码:nosources-source-map 或者 hidden-source-map
  32. 不隐藏,用来错误调试:source-map 或者 cheap-module-source-map

oneOf

  1. ...
  2. module: {
  3. rules: [
  4. {
  5. test: /\.js$/,
  6. loader: 'eslint-loader',
  7. exclude: /node_modules/,
  8. enforce: 'pre',
  9. options: {
  10. fix: true,
  11. },
  12. },
  13. // oneOf 里面的loader,只会匹配一个,不能有两个配置处理同一个类型的文件
  14. oneOf: [
  15. {
  16. test: /\.css$/,
  17. use: ['xxx'],
  18. },
  19. {
  20. test: /\.less$/,
  21. use: ['xxx'],
  22. },
  23. {
  24. test: /\.js$/,
  25. loader: 'babel-loader',
  26. exclude: /node_modules/,
  27. options: {
  28. presets: [
  29. [
  30. '@babel/preset-env',
  31. {
  32. useBuiltIns: 'usage',
  33. corejs: {
  34. version: '3',
  35. },
  36. targets: {
  37. chrome: '60',
  38. ie: '9',
  39. safari: '40',
  40. firefox: '70',
  41. },
  42. },
  43. ],
  44. ],
  45. },
  46. },
  47. ...
  48. ]
  49. ]
  50. }
  51. ...


缓存

  1. 1. babel 缓存,cacheDirectory: true,让第二次打包构建速度更快
  2. 2. 文件资源缓存,利用hash
  3. 三种:
  4. - hash 每次构建都会生成一个唯一的hashjs css 使用同一个hash
  5. 问题:重新打包会导致所有的缓存失效,可能只改了一个文件,所有的文件都得重新打包
  6. - chunkhash,根据chunk生成hash,如果打包来源于同一个chunkhash就一样
  7. 问题:css是从js从抽离出来的,属于同一个hash,生成的chunkhash一样
  8. - contenthash,根据文件内容生成hash,文件改变生成的contenthash就改变
  9. --> 让代码上线运行缓存更好利用
  10. ...
  11. output: {
  12. filename: '[name].[contenthash:8].js',
  13. path: resolve(__dirname, 'build')
  14. },
  15. plugins: [
  16. new MiniCssExtractPlugin({
  17. filename: '[name].[contenthash:8].css'
  18. })
  19. ],
  20. module: {
  21. rules: [
  22. ...
  23. {
  24. test: /\.js$/,
  25. loader: 'babel-loader',
  26. exclude: /node_modules/,
  27. options: {
  28. presets: [
  29. [
  30. '@babel/preset-env',
  31. {
  32. useBuiltIns: 'usage',
  33. corejs: {
  34. version: '3',
  35. },
  36. targets: {
  37. chrome: '60',
  38. ie: '9',
  39. safari: '40',
  40. firefox: '70',
  41. },
  42. },
  43. ],
  44. ],
  45. // 开启babel缓存,第二次构建时会读取之前的缓存
  46. // 比如有100js文件,改了其中一个,只重新构建改动的那个,其他的从缓存中拿
  47. cacheDirectory: true,
  48. },
  49. },
  50. ...
  51. ]
  52. },
  53. ...

tree shaking

  1. tree shaking
  2. 作用:去掉无用的代码
  3. 条件:1.mode production 2.必须使用 es module
  4. package.josn 中的 "sideEffects": false , 表示所有的代码都没有副作用(可以进行tree shaking), 可能会把 css 或者 @babel/polyfill 干掉
  5. 个别文件不需要tree shaking,配置 "sideEffects": ["*.css", "xxx"]
  6. 具体需要改动的配置如下:
  7. webpack.config.js 增加配置:
  8. ...
  9. optimization: {
  10. usedExports: true,
  11. sideEffects: true,
  12. }
  13. ...
  14. package.json 增加配置:
  15. ...
  16. "sideEffects": [
  17. "*.css",
  18. "*.less",
  19. "*.scss",
  20. "*.sass"
  21. ]
  22. ...
  23. .babelrc 增加配置:
  24. ...
  25. presets: [
  26. [
  27. "@babel/preset-env",
  28. {
  29. modules: false,
  30. // ...其他配置省略
  31. }
  32. ],
  33. // ...其他配置省略
  34. ]
  35. ...

code split

  1. ...
  2. // 多入口
  3. entry: {
  4. main: './src/index.js',
  5. some: './src/some.js',
  6. },
  7. // 一个入口对应一个bundle
  8. output: {
  9. filename: '[name].[contenthash:8].js',
  10. path: resolve(__dirname, 'build'),
  11. },
  12. /*
  13. 1.可以将node_modules中的代码打包成一个chunk单独输出
  14. 2.自动分析多入口文件中的公共文件,打包生成一个chunk
  15. ⚠️:20kb 以上的代码才会单独打包
  16. */
  17. optimization: {
  18. splitChunks: {
  19. chunks: 'all',
  20. },
  21. },
  22. ...

单入口的情况下,怎么把某个js文件单独打包成一个 bundle
  • 通过 import('xxx路径') ,返回一个 promise
  • 可通过添加注释 webpackChunkName: '自定义name' 的方式指定生成的 bundle 的 name
    1. /*
    2. 通过js代码,让某个文件能被单独打包成一个chunk
    3. import动态导入语法,能将某个文件单独打包
    4. 可通过注释语法 webpackChunkName 来指定生成的bundle的名字
    5. */
    6. import(/* webpackChunkName: 'myname' */ './test.js')
    7. .then(({ count }) => {
    8. console.log('wy->module', count(2, 3));
    9. })
    10. .catch((err) => {
    11. console.log('wy->err', err);
    12. });

image.png

懒加载 & 预加载

  • 通过 import() 动态导入实现懒加载和预加载
  • 通过注释 webpackPrefetch: true 来实现 预加载
  • 预加载有兼容性问题,只能在高版本浏览器里使用; ```javascript // 懒加载 & 预加载 模拟 // 定义个按钮,点击这个按钮的时候采取加载 test.js 文件

document.querySelector(‘#btn’).onclick = function () { import(/ webpackChunkName: ‘myname’, webpackPrefetch: true / ‘./test.js’) .then(({ count }) => { console.log(‘wy->module’, count(2, 3)); }) .catch((err) => { console.log(‘wy->err’, err); }); };

  1. <a name="kjmXP"></a>
  2. #### 多进程打包
  3. ```javascript
  4. ...
  5. module: {
  6. rules: [
  7. {
  8. test: /\.js$/,
  9. exclude: /node_modules/,
  10. use: [
  11. {
  12. // 使用:npm i thread-loader -D
  13. // 开启多线程打包,进程启动时间大概600ms,进程间通信也有开销
  14. // 打包时间较长,才去使用多进程打包
  15. loader: 'thread-loader',
  16. options: {
  17. workers: 2 // 进程2个
  18. }
  19. },
  20. {
  21. loader: 'babel-loader',
  22. options: {
  23. presets: [
  24. [
  25. '@babel/preset-env',
  26. {
  27. useBuiltIns: 'usage',
  28. corejs: {
  29. version: '3',
  30. },
  31. targets: {
  32. chrome: 'xx',
  33. firefox: 'xx',
  34. safari: 'xx',
  35. ie: 'xx',
  36. edge: 'xx',
  37. }
  38. }
  39. ]
  40. ]
  41. }
  42. }
  43. ]
  44. }
  45. ]
  46. }
  47. ...

externals

  1. ...
  2. // 避免被打包的库,通过cdn的方式引入,如:react,react-dom,jquery 等
  3. externals: {
  4. // 全局变量: 'npm 包名'
  5. jquery: 'jQuery'
  6. }
  7. ...

pwa

dll

webpack.dll.js 需要单独执行,webpack —config webpack.dll.js 打包第三方库,只需要打包一次,不需要重复打包

image.png

webpack.config.js

image.png

package.json

  1. {
  2. "name": "webpack-wy",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "index.js",
  6. "scripts": {
  7. "test": "echo \"Error: no test specified\" && exit 1"
  8. },
  9. "keywords": [],
  10. "author": "",
  11. "license": "ISC",
  12. "devDependencies": {
  13. "@babel/core": "^7.14.6",
  14. "@babel/polyfill": "^7.12.1",
  15. "@babel/preset-env": "^7.14.7",
  16. "babel-loader": "^8.2.2",
  17. "clean-webpack-plugin": "^4.0.0-alpha.0",
  18. "core-js": "^3.15.2",
  19. "css-loader": "^5.2.6",
  20. "eslint": "^7.30.0",
  21. "eslint-config-airbnb-base": "^14.2.1",
  22. "eslint-plugin-import": "^2.23.4",
  23. "file-loader": "^6.2.0",
  24. "html-loader": "^2.1.2",
  25. "html-webpack-plugin": "^5.3.2",
  26. "less": "^4.1.1",
  27. "less-loader": "^10.0.1",
  28. "mini-css-extract-plugin": "^2.0.0",
  29. "optimize-css-assets-webpack-plugin": "^6.0.1",
  30. "postcss-loader": "^6.1.1",
  31. "postcss-preset-env": "^6.7.0",
  32. "style-loader": "^3.0.0",
  33. "url-loader": "^4.1.1",
  34. "webpack": "^5.42.0",
  35. "webpack-cli": "^3.3.12",
  36. "webpack-dev-server": "^3.11.2"
  37. },
  38. "browserslist": {
  39. "development": [
  40. "last 1 chrome version",
  41. "last 1 firefox version",
  42. "last 1 safari version"
  43. ],
  44. "production": [
  45. ">0.2%",
  46. "not dead",
  47. "not op_mini all"
  48. ]
  49. }
  50. }