webpack5工程化
npm init -y 快速创建package.json
npm install webpack webpack-cli --save-dev
下饭菜:
webpack4之后的0配置原则,遵循默认的项目结构可以不用编写webpack的配置文件:
- 创建 src/index.js文件,随便写写console.log(9527)之类的就行
- 运行 $ npx webpack指令就会在项目目录下生成一个 dist 目录,会以index.js为入口生成一个main.js文件
创建配置文件
- 创建 - build/webpack.config.js文件:- const path = require('path');
 module.exports = {
 entry: './src/main.js',
 output: {
  filename: 'bundle.js',
  path: path.resolve(__dirname, '../dist')
 }
 };
- 修改package.json,增加npm script: - "build": "webpack --config build/webpack.config.js"
- 执行 - npm run build,效果是一样的,只不过配置由自己掌控。看,dist目录生成的js文件已经叫bundle.js了
完成一份支持html+css+js的配置
支持html模板
引入 npm i --save-dev html-webpack-plugin 插件,然后再修改webpack配置:
output: {...},
plugins: [
  new HtmlWebpackPlugin({
    inject: 'body',
    filename: 'index.html',
    template: path.resolve(__dirname, '../src/index.spa.html')
  }),
]
在src下创建一个index.spa.html作为项目模板
多页应用,可以实例多个插件:
  entry: {
    'main': './src/index.js',
    'beitai': './src/index2.js',
  },
  output: {
    filename: 'js/[name]-[fullhash:8].js',
    path: path.resolve(__dirname, '../dist'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      inject: 'body',
      filename: 'index.html',
      template: path.resolve(__dirname, '../src/index.spa.html')
    }),
    new HtmlWebpackPlugin({
      multihtmlCatch: true, // 开启多入口缓存
      inject: 'body',
      filename: 'beitai.html',
      chunks: ["beitai", "vendor", "manifest"], // 每个html引用的js模块
      template: path.resolve(__dirname, '../src/index.spa.html')
    }),
  ]
}
此时会以index.spa.html输出两个html页面,index页面因没有指定chunks,会引入两个js,引入顺序是entry定义的顺序。beitai.html则只会引入beitai.js
支持css预处理器的配置
处理文件内容是需要loader的,即使原生css都不可以,毕竟js不认识css语法。一次性都装上 npm install --save-dev style-loader css-loader sass sass-loader ,如果不用预处理器,去掉后面sass的就可以
编写loader:
  output: {...},
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          'style-loader',
          { loader: 'css-loader' },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            },
          },
        ]
      }
    ]
  },
创建一个style.scss文件:
div {
  display: flex;
}
js文件引入该css
import './style.scss'
console.log(111)
打包 npm run build
发现并不会多出一个css文件出来,其实没有那么智能~。从打包出来的main.js文件搜一下“flex”,css被打包进js的内容了!看页面,样式也是生效的。这个我知道!vue spa模式就这么插入的内容,但是有两个问题
不利于资源复用,flex并没有添加浏览器前缀处理兼容性问题。接下来一一解决
添加浏览器前缀:
- 添加 postcss npm install -D postcss-loader postcss。它的功能是:
把css解析为一个抽象语法树 调用插件处理抽象语法树并添加功能
- 再添加我们的老朋友 autoprefixer  npm i -D autoprefixer
修改配置cssloader
          { loader: 'css-loader' },
          { 
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: {
                  autoprefixer: {
                    browsers: ['Android >= 4.0', 'iOS >= 7', "last 2 versions"]
                  },
                }
              }
            }
          },
重新打包可以看到css已经添加浏览器前缀
因为后续postcss需要引入的插件可能越来越多,提取一下postcss的配置到单独的文件中:
touch postcss.config.js:module.exports = {
plugins: {
autoprefixer: {
browsers: ['Android >= 4.0', 'iOS >= 7', "last 2 versions"]
},
}
}
修改webpack的配置,只定义postcss即可
{ loader: 'postcss-loader' },效果是一样的
提取css
要用到 mini-css-extract-plugin ,注意,这个loader与style-loader是冲突的,修改配置:
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          MiniCssExtractPlugin.loader, // 提取css文件,不与style-loader共存
          // 'style-loader',
          { loader: 'css-loader' },
          { 
            loader: 'postcss-loader',
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            },
          },
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css",
    })
  ]
}
打包后发现会提取一个main.css,且js文件也暂时干净多了
资源相关
修改js,引入一个图片资源进行打包:
import './assets/img/avatar.jpg'
会发现打包失败,webpack默认只会处理js文件,像css需要css相关loader,其他文件也一样。需要用到 file-loader url-loader 这两个loader: npm install --save-dev file-loader url-loader
file-loader生成的文件的文件名就是文件内容的 MD5 哈希值并会保留所引用资源的原始扩展名
url-loader功能类似于file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL
修改webpack配置,增加loader配置:
      {
        test: /\.(jpe?g|png|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1024, // 限制转base64的图片为1k(1024b),超过1k的输出文件, 设置此项需要安装依赖:file-loader
              name: 'images/[name]-[fullhash:8].[ext]',
            }
          }
        ]
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10240, // 10k
              name: 'fonts/[name]-[fullhash:8].[ext]'
            }
          }
        ]
      },
打包测试查看js可以正常打包静态资源。但是发现css引入的背景图并不正常,发现一张图会生成两个文件,图片文件和一个内容是 export default __webpack_public_path__... 的文件,最终发现,需要在 css-loader 处设置esModule为false:
{ loader: 'css-loader', options: { esModule: false } },
支持js相关
js如果自己用理论上不处理都行~ 但是毕竟还是会写一些新的es语法,这时就用到 babel
基础版: 先改js用来看效果:
import './style.scss'
console.log(111)
document.addEventListener('click', () => {
  console.log(12580)
})
const waitTime = (delay = 300) => new Promise((resolve) => setTimeout(resolve, delay))
waitTime(4396).then(async () => {
  await waitTime(7777)
  console.log(9527)
})
- 首先安装babel: npm install --save-dev @babel/core @babel/cli @babel/preset-env
- 在项目的根目录下创建一个命名为 babel.config.json 的配置文件(需要 v7.8.0 或更高版本): {
 "presets": [
 [
 "@babel/preset-env",
 {
 "targets": {
 "edge": "17",
 "firefox": "60",
 "chrome": "67",
 "safari": "11.1"
 },
 "useBuiltIns": "usage",
 "corejs": "3.6.5"
 }
 ]
 ]
 }
- npm install --save-dev babel-loader再改webpack配置,- build/webpack.config.jsmodule.rules增加一项配置:
      { test: test: /\.(sa|sc|c)ss$/, ...},
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        },
        exclude: /node_modules/
      },
打包之后搜一下 12580,箭头函数已经被编译为ES5语法。Promise还直愣愣在结果中,还是要单独处理,接下来要解决:
@babel/polyfill方式(不建议)
npm install --save @babel/polyfill
修改配置:
module.exports = {
  entry: {
    'main': ['@babel/polyfill', './src/index.js'],
  },
然后执行 npm run build。缺点是编译后文件会很大(300b的文件被编译成了87k!),不管是否用到了新的语法都会添加语法垫片导致文件变大
它的初衷是模拟(emulate)一整套 ES2015+ 运行时环境,所以它的确会以全局变量的形式 polyfill Map、Set、Promise 之类的类型,也的确会以类似 Array.prototype.includes() 的方式去注入污染原型,这也是官网中提到最适合应用级开发的 polyfill,再次提醒如果你在开发 library 的话,不推荐使用(或者说绝对不要使用)
不同于插件,你所要做的事情很简单,就是将 babel-polyfill 一次性的引入到你的工程中,通常是和其他的第三方类库(如 jQuery、React 等)一同打包在 vendor.js 中即可。在你写程序的时候,你完全不会感知 babel-polyfill 的存在,如果你的浏览器已经支持 Promise,它会优先使用 native 的 Promise,如果没有的话,则会采用 polyfill 的版本(这个行为与 babel-plugin-transform-runtime 一致),在使用 babel-polyfill 后,你不需要引入 babel-plugin-transform-runtime 插件和其他依赖的类库。它的缺点也显而易见,那就是占文件空间并且无法按需定制。
作者:Henry 链接:https://www.zhihu.com/question/49382420/answer/223915243
@babel/plugin-transform-runtime
polyfill很强(庞)大,我选择 npm install --save-dev @babel/plugin-transform-runtime 
这个插件让 Babel 发现代码中使用到 Symbol、Promise、Map 等新类型时,自动且按需进行 polyfill,因为是“自动”所以非常受大家的欢迎。
然后结合 npm install --save @babel/runtime-corejs3  。
创建babel.config.js
module.exports = function (api) {
  api.cache(true)
  return {
    presets: [
      [
        '@babel/preset-env',
        {}
      ]
    ],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          'absoluteRuntime': false,
          'corejs': 3,
          'helpers': true,
          'regenerator': true
        }
      ]
    ]
  }
}
因为后续babel要做更多的事情(比如lodash)配置太多提取到单文件更容易维护
打包代码,main.js从87k精简到39k
通过cdn方式引入polyfill的cdn资源,该服务会根据ua返回需要添加语法垫片的内容,缺点是webview的ua可能会误导该服务导致代码执行失败
使用不同的浏览器访问 https://polyfill.io/v3/polyfill.min.js 查看返回的内容
使用代码示例:
<script crossorigin="anonymous" src="https://polyfill.io/v3/polyfill.min.js"></script>
Polyfill.io 通过分析请求头信息中的 UserAgent 实现自动加载浏览器所需的 polyfill。
高级用法
Polyfill.io 有一份默认捆绑列表,包括了最常见的 HTML5 中的 document.querySelector、Element.classList、ES5、ES6、ES7 中的 Promise、fetch、Array.from 等等。
可以通过传递 features 参数来自定义功能列表:
<!-- 加载 Promise&fetch --><script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=Promise,fetch"></script>
<!-- 加载所有 ES5&ES6 新特性 --><script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=es5,es6,es7"></script>
Polyfill.io 还提供了其他 API,具体请查阅官方文档:
<!-- 异步加载 -->
<script src="https://cdn.polyfill.io/v3/polyfill.min.js?callback=main" async defer></script>
<!-- 无视 UA,始终加载 -->
<script src="https://cdn.polyfill.io/v3/polyfill.js?features=modernizr:es5array|always"></script>
阿里提供的动态 Polyfill 服务:
<script src="https://polyfill.alicdn.com/polyfill.min.js?features=Promise%2CArray.prototype.includes"></script>
扩展:
每次代码变更,打包都会在dist生成新的文件,次数多了旧的文件会越来越多,可以用 npm install --save-dev clean-webpack-plugin 来清空无关文件:
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
{
    plugins: [
        ...,
        new CleanWebpackPlugin(),
    ],
}
完成对vue支持
首先安装vue,npm i vue@2 -S (不要问我为什么还装v2版本的,v3我想用vite)
然后安装 npm i vue-loader@15 vue-template-compiler vue-style-loader 
Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件
修改html模板:
<body>
  <div id="app"></div>
</body>
新增app.vue:
<template>
  <div id="app">
    vue app
    <button @click="handleClick">bar</button>
  </div>
</template>
<script type="text/ecmascript-6">
export default {
  data () {
    return {
      foo: 'vue app'
    }
  },
  methods: {
    handleClick () {
      alert(8)
    }
  }
}
</script>
<style lang="scss" rel="stylesheet/scss">
#app {
  width: 100px;
  height: 100px;
  border: 1px solid #000;
}
</style>
修改main.js:
import Vue from 'vue'
import App from './app.vue'
import './style.scss'
new Vue({
  el: '#app',
  render: h => h(App)
})
新增webpack配置:
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
  module: {
    rules: [
      /* vue */
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
}
此时vue可以正常编译,并将css、js提取。接下来引入vue-router: npm i vue-router@3 -S
新增&修改若干文件:
// src/app.vue
<template>
  <div id="app">
    vue app
    <router-link to="/">首页</router-link>
    <router-link to="/detail">详情</router-link>
    <button @click="handleClick">bar</button>
    <router-view></router-view>
  </div>
</template>
<script type="text/ecmascript-6">
export default {
  data () {
    return {
      foo: 'vue app'
    }
  },
  methods: {
    handleClick () {
      alert(8)
    }
  }
}
</script>
<style lang="scss" rel="stylesheet/scss">
#app {
  color: peru;
}
</style>
// src/index.js
import Vue from 'vue'
import App from './app.vue'
import './style.scss'
import router from './router'
const app = new Vue({
  el: '#app',
  router,
  render: h => h(App)
})
// src/views/home.vue
<template>
  <div class="home">
    页面 home
  </div>
</template>
<script type="text/ecmascript-6">
export default {}
</script>
<style lang="scss" rel="stylesheet/scss">
.home {
  height: 300px;
  background-color: yellowgreen;
}
</style>
// src/views/detail.vue
<template>
  <div class="detail">
    页面 detail
  </div>
</template>
<script type="text/ecmascript-6">
export default {}
</script>
<style lang="scss" rel="stylesheet/scss">
.detail {
  height: 300px;
  background-color: burlywood;
}
</style>
// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
  mode: 'history',
  routes: [
    { path: '/', component: () => import(/* webpackChunkName: "home" */ '../views/home.vue') },
    { path: '/detail', component: () => import(/* webpackChunkName: "detail" */ '../views/detail.vue') }
  ]
})
打包可以看到路由可以正常匹配了,但是会多一个类似 bundle.js.LICENSE.txt开源协议声明的文件,可以用 terser 压缩:
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
  ...
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false,
        terserOptions: {
          format: {
            comments: false,
          }
        }
       }),
    ]
  }
}
区分打包环境
配置dev环境
首先安装 webpack-dev-server: npm install --save-dev webpack-dev-server
因为要区分开发环境和生成环境,所以单独创建一份dev需要的配置并继承之前编写的基础配置:
// build/webpack.dev.js
const webpack = require('webpack')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.config.js')
module.exports = merge(baseConfig, {
  // Set the mode to development or production
  mode: 'development',
  // Control how source maps are generated
  devtool: 'inline-source-map',
  // Spin up a server for quick development
  devServer: {
    host: '127.0.0.1',
    historyApiFallback: true,
    open: true,
    compress: true,
    hot: true,
    port: 3000,
    proxy: {
    }
  },
  plugins: [
    // [webpack-dev-server] "hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration. 
    //new webpack.HotModuleReplacementPlugin(), // 从 v4.0.0 开始,热模块更换默认处于启用状态,如果手动调用需要设置(devServer.hot: false)
  ],
})
/*  */
然后通过dev-server启动项目,有两种方式:
- 通过指令调用,修改package.json: - "scripts": {
 "dev": "node build/dev-server.js",
 "dev2": "webpack serve --config build/webpack.dev.js"
 },
- 单独创建一个启动服务的文件 - build/dev-server.js:- const Webpack = require('webpack')
 const WebpackDevServer = require('webpack-dev-server')
 const webpackConfig = require('./webpack.dev.js')
 const compiler = Webpack(webpackConfig)
 const devServerOptions = { ...webpackConfig.devServer }
 const server = new WebpackDevServer(devServerOptions, compiler)
 const runServer = async () => {
 console.log('Starting server...')
 await server.start();
 };
 runServer()
执行 npm run dev 或者 npm run dev2 都可以
哦对了,如果想实现css的热更新有两种方式:
- 在开发环境将 mini-css-extract-plugin禁用:
// webpack.config.js
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
  plugins: [
  //  new MiniCssExtractPlugin({
  //    filename: "[name]-[fullhash:8].css",
  //  })
  ],
  module: {
    rules: [
      /* css */
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          // MiniCssExtractPlugin.loader, // 提取css文件,不与style-loader共存
          isProd ?
            {
              loader: MiniCssExtractPlugin.loader,
              options: {
                publicPath: (resourcePath, context) => {
                  return path.relative(path.dirname(resourcePath), context) + "/";
                },
              },
            } :
            'style-loader', // 打包css到style标签
          { loader: 'css-loader', options: { esModule: false } },
          {
            loader: 'postcss-loader',
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            },
          },
        ]
      },
      ...
- 将
顺便提取一下prod的配置
与dev环境一样,创建用于生产环境配置的 webpack.prod.js,把 wepack.config.js 中相关配置提取到prod中:
const { merge } = require('webpack-merge')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const baseConfig = require('./webpack.config.js')
module.exports = merge(baseConfig, {
  mode: 'production',
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name]-[fullhash:8].css",
    })
  ],
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        extractComments: false,
        terserOptions: {
          format: {
            comments: false
          }
        }
      })
    ]
  }
})
修改package.json:  "build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js"
优化打包
代码规范
eslint
“保证代码的一致性和避免错误”
团队成员的代码风格肯定会有不同,假如每个人都按自己的风格写代码。有人写分号有人不写;有人喜欢单引号有人喜欢双引号……代码可读性差。最重要的是eslint还能帮助我们检查代码,提前发现语法错误,避免到浏览器调试时才能发现
- 安装 eslint及初始化
> npm i eslint
> npx eslint --init 或 npm init @eslint/config
eslint 配置问答,以下是我的选项:
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · vue
✔ Does your project use TypeScript? · No
✔ Where does your code run? · browser, node
✔ What format do you want your config file to be in? · JavaScript
回车后在项目根目录会生成一个 .eslintrc.js 配置文件
- 定制代码风格 - 经过第一步的配置,项目会有一套基本的eslint规则。但是不太符合自己的代码习惯,接下来进行一些规则的定制: - 个人不喜欢 - eslint:recommended预设的规则,standard YYDS!- npm install standard --save-dev
- eslint不支持最新的实验性ECMAScript标准需要 - @babel/eslint-parser- ESLint的默认解析器和核心规则仅支持最新的最终 ECMAScript 标准,不支持 Babel 提供的实验性(例如新功能)和非标准(例如Flow或TypeScript类型)语法。@ babel / eslint-parser 是允许 ESLint 在由 Babel 转换的源代码上运行的解析器。 
- 自己添加一些配置,以下为个人配完之后的.eslintrc.js(遇到的一些坑,写在注释上了): 
 - module.exports = {
 env: {
 browser: true,
 es2021: true,
 node: true
 },
 extends: [
 'plugin:vue/essential',
 'standard' // https://github.com/standard/standard/blob/master/docs/RULES-zhcn.md#javascript-standard-style
 ],
 // parser: '@babel/eslint-parser', // vue-eslint和babel-parser二者有冲突。编译器配置在根节点会导致vue sfc模式eslint报错
 parserOptions: {
 parser: '@babel/eslint-parser',
 ecmaVersion: 12,
 sourceType: 'module',
 ecmaFeatures: {
 modules: true
 }
 },
 plugins: [
 'vue'
 ],
 rules: {
 // promise强制要求reject()抛出error【禁止】
 'prefer-promise-reject-errors': 'off',
 // vue 组件要求定义name【禁止】
 'vue/multi-word-component-names': ['off', {}],
 // 箭头函数保护符() 【作为回调必要时】https://eslint.org/docs/rules/arrow-parens#arrow-parens
 'arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }]
 }
 }- 这篇eslint文章介绍的挺全的 
- 接入到webpack - 经过1.2步的配置,eslint是加入到项目中了,npm script添加一条 - "lint": "eslint --ext .js,.vue src",- npm run lint校验,或者通过- "lint:fix": "eslint --ext .js,.vue src server --fix"进行语法修复,但是对开发时不方便。这时就用到了[`eslint-webpack-plugin]`(https://www.npmjs.com/package/eslint-webpack-plugin) :- const ESLintPlugin = require('eslint-webpack-plugin') // 优化编译时eslint展示
 plugins: [
 new ESLintPlugin({
 cache: true,
 emitWarning: true,
 extensions: ['js', 'vue'],
 failOnError: false,
 fix: true
 }),
 ]- 个人喜欢打开fix选项,这样自动修复就不受限于编辑器的配置,且协同工作的时候,可以帮助自己去“纠正”代码习惯。同时也代替了 - prettier的强制修复功能。
stylelint
- 安装 - // 安装stylelint
 npm install --save-dev stylelint stylelint-config-standard // 核心功能
 // 添加vue,scss的插件 (使用高版本stylelint遇到了一些坑导致vue+scss校验有问题,经尝试,以下组合可用【stylelint-config-recommended-scss】)
 npm i stylelint-config-recommended-scss stylelint-config-recommended-vue stylelint-config-standard-scss -D
 // 添加css样式表属性排序规则(如果使用@justwe7/stylelint-order-standard可以不装,包中有依赖)
 npm install stylelint-order --save-dev
- 创建配置文件 - touch .stylelintrc.js(篇幅原因,节选了一部分关键代码):- stylelint-order的排序配置, stylelint-config-recess-order个人更喜欢这份配置表,但是我将配置复制到本地,方便调整。用在项目中最好自己发包(下面有写),保证规则复用 - module.exports = {
 extends: [
 'stylelint-config-standard-scss',
 'stylelint-config-recommended-vue/scss',
 'stylelint-config-standard',
 ],
 plugins: ['stylelint-order'],
 rules: {
 // ...
 'order/properties-order': [
 [
 // 影响元素展示且一般与设计稿样式无关联
 'opacity',
 'visibility',
 'box-sizing',
 'overflow',
 'overflow-x',
 'overflow-y',
 'overflow-scrolling',
 'content',
 'position',
 'top',
 ],
 {
 unspecified: 'bottom',
 severity: 'warning',
 },
 ],
 },
 }- 以上排序规则我封装到了 stylelint-order-standard ,可以直接使用以下配置: - npm i @justwe7/stylelint-order-standard -D- .stylelintrc.js: - module.exports = {
 extends: [
 'stylelint-config-standard-scss',
 'stylelint-config-recommended-vue/scss',
 'stylelint-config-standard',
 '@justwe7/stylelint-order-standard'
 ],
 }
- webpack中配置 - 和eslint一样,其实到1.2步完成已经可以进行校验了。npm script添加一条 - "lint:css": "stylelint \"src/**/*.(vue|scss|css)\"",同样不适用开发环境,需要新增webpack插件:stylelint-webpack-plugin- const StylelintPlugin = require('stylelint-webpack-plugin')
 plugins: [
 new StylelintPlugin({
 cache: true,
 fix: true,
 // failOnError: false,
 extensions: ['scss', 'vue', 'css']
 }),
 ]- 两个插件配置的参数挺一致的,点赞~ 
hysky
约定了代码规范,假如不遵守不如不约定,加一个precommit的校验,可以保证规范的执行。使用husky可以更方便的定义Git Hooks:
npm install husky --save-dev
npm set-script prepare "husky install"
npm run prepare
添加hook:
- Eslint的pre commit校验 - npx husky add .husky/pre-commit "npm run lint"
 git add .husky/pre-commit
- stylelint的pre commit校验 - npx husky add .husky/pre-commit "npm run lint:css"
 git add .husky/pre-commit
会将校验指令写入到 .husky/pre-commit 的内容中
之后进行git commit提交版本库时,会依次进行eslint和stylint的校验,假如不符合规则,流程会终止
美化输出
FriendlyErrorsPlugin
使用friendly-errors-webpack-plugin: npm install @soda/friendly-errors-webpack-plugin --save-dev
新增配置
const FriendlyErrorsWebpackPlugin = require('@soda/friendly-errors-webpack-plugin')
plugins: [
  new FriendlyErrorsWebpackPlugin(), // 输出美化
]
效果:
配合 node-notifier 实现编译报错通知:
const FriendlyErrorsWebpackPlugin = require('@soda/friendly-errors-webpack-plugin')
const notifier = require('node-notifier')
plugins: [
  new FriendlyErrorsWebpackPlugin({
    onErrors: (severity, errors) => {
        if (severity !== 'error') {
          return;
        }
        const error = errors[0];
        notifier.notify({
          title: 'webpackError - ' + error.name,
          message: error.file,
          // message: error.message
        });
      }
      compilationSuccessInfo: {
        messages: ['You application is running here http://localhost:3000'],
        notes: ['Some additional notes to be displayed upon successful compilation']
      },
  }), // 输出美化
]

webpackbar
这个是nuxt团队做的美化插件,个人更喜欢它的样式(虽然感觉实用性不如上一个插件)
安装 npm i webpackbar 
如果要同时编译多个webpack入口,可以更明显的区分样式:
const WebpackBar = require('webpackbar')
plugins: [
  new WebpackBar({ name: 'client', color: 'green' }),
]

优化打包速度
核心思路:
加缓存,搞并行,提前做,少执行 https://juejin.cn/post/6844903952140468232
thread-loader
npm install thread-loader -D 可以将比较耗时的loader放在一个单独的worker池中运行:
多进程打包,把这个 loader 放置在其他 loader 之前, 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行
在 worker 池(worker pool)中运行的 loader 是受到限制的。例如:
- 这些 loader 不能产生新的文件。
- 这些 loader 不能使用定制的 loader API(也就是说,通过插件)。
- 这些 loader 无法获取 webpack 的选项设置。
rules: [    
        {
        test: /\.js$/,
        use: [
          {
            loader: 'thread-loader',
            // 有同样配置的 loader 会共享一个 worker 池
            options: {
              // 产生的 worker 的数量,默认是 (cpu 核心数 - 1),或者,. 在 require('os').cpus() 是 undefined 时回退至 1
              workers: 2,
              // 一个 worker 进程中并行执行工作的数量.默认为 20
              workerParallelJobs: 50,
              // 额外的 node.js 参数
              workerNodeArgs: ['--max-old-space-size=2048'],
              // 允许重新生成一个僵死的 work 池。这个过程会降低整体编译速度.并且开发环境应该设置为 false
              poolRespawn: false,
              // 闲置时定时删除 worker 进程。默认为 500(ms).可以设置为无穷大,这样在监视模式(--watch)下可以保持 worker 持续存在
              poolTimeout: 2000,
              // 池分配给 worker 的工作数量。默认为 200 降低这个数值会降低总体的效率,但是会提升工作分布更均一
              poolParallelJobs: 50,
              // 池的名称.可以修改名称来创建其余选项都一样的池
              name: 'ssr-pool'
            },
          },
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true // default cache directory in node_modules/.cache/babel-loader
            }
          }
        ],
        exclude: /node_modules/
      },
  ]
babel
- 使用编译缓存
- 使用@babel/plugin-transform-runtime,抛弃polyfill,优化公共包大小,在【js相关】章节有配置方式
module.exports = function (api) {
  api.cache(true) // 使用缓存
  return {
    presets: [
      [
        '@babel/preset-env',
        {}
      ]
    ],
    plugins: [
      [
        '@babel/plugin-transform-runtime',
        {
          absoluteRuntime: false,
          corejs: 3,
          helpers: true,
          regenerator: true
        }
      ]
    ]
  }
}
项目打包会在 node_modules/.cache/babel-loader 生成缓存文件
webpack5-cache
其实使用webpack5自带的缓存机制完全可以替代AutoDllPlugin 和HardSourceWebpackPlugin且比它们更快。更重要的是比他们配置简单:
module.exports = {
  cache: {
    type: 'filesystem', // 默认使用的是memory,空间换时间~使用磁盘缓存
    name: 'clientCache-'
  },
};
之后打包会在 node_modules/.cache/webpack 生成缓存文件
对比
- 未配置缓存,项目三次打包时间依次为10.73、9.34、9.4
- 配置缓存,三次打包时间分别为10.08、2.4、2.66


针对这个演示项目,使用缓存在打包速度上提升了400%
其他缓存
- eslint-plugin、stylelint-plugin配置缓存 - new ESLintPlugin({
 cache: true,
 ...
 }),
 new StylelintPlugin({
 cache: true,
 ...
 }),