跳到主要内容

webpack5工程化

npm init -y 快速创建package.json
npm install webpack webpack-cli --save-dev

下饭菜:

webpack4之后的0配置原则,遵循默认的项目结构可以不用编写webpack的配置文件:

  1. 创建 src/index.js 文件,随便写写 console.log(9527) 之类的就行
  2. 运行 $ npx webpack 指令就会在项目目录下生成一个 dist 目录,会以 index.js 为入口生成一个 main.js 文件

创建配置文件

  1. 创建 build/webpack.config.js 文件:

    const path = require('path');

    module.exports = {

    entry: './src/main.js',

    output: {

    ​ filename: 'bundle.js',

    ​ path: path.resolve(__dirname, '../dist')

    }

    };


  2. 修改package.json,增加npm script: "build": "webpack --config build/webpack.config.js"

  3. 执行 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)
})
  1. 首先安装babel: npm install --save-dev @babel/core @babel/cli @babel/preset-env
  2. 在项目的根目录下创建一个命名为 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"
    }
    ]
    ]
    }
  3. npm install --save-dev babel-loader 再改webpack配置,build/webpack.config.js module.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

使用polyfill-service

通过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.querySelectorElement.classList、ES5、ES6、ES7 中的 PromisefetchArray.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>

扩展:

clean-webpack-plugin

每次代码变更,打包都会在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启动项目,有两种方式:

  1. 通过指令调用,修改package.json:

    "scripts": {
    "dev": "node build/dev-server.js",
    "dev2": "webpack serve --config build/webpack.dev.js"
    },
  2. 单独创建一个启动服务的文件 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的热更新有两种方式:

  1. 在开发环境将 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还能帮助我们检查代码,提前发现语法错误,避免到浏览器调试时才能发现

  1. 安装 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 配置文件
  1. 定制代码风格

    经过第一步的配置,项目会有一套基本的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文章介绍的挺全的

  2. 接入到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

  1. 安装

    // 安装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
  2. 创建配置文件 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'
    ],
    }

  1. 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(), // 输出美化
]

效果:imagec3641.png

配合 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']
},
}), // 输出美化
]

image.png

webpackbar

这个是nuxt团队做的美化插件,个人更喜欢它的样式(虽然感觉实用性不如上一个插件)

安装 npm i webpackbar

如果要同时编译多个webpack入口,可以更明显的区分样式:

const WebpackBar = require('webpackbar')

plugins: [
new WebpackBar({ name: 'client', color: 'green' }),
]

imagef9362.png

优化打包速度

核心思路:

加缓存,搞并行,提前做,少执行 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

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

image-20220601181935175

针对这个演示项目,使用缓存在打包速度上提升了400%

其他缓存

  • eslint-plugin、stylelint-plugin配置缓存

        new ESLintPlugin({
    cache: true,
    ...
    }),
    new StylelintPlugin({
    cache: true,
    ...
    }),