阅读 1776
Webpack5 从零配置一个基础的 Vue 项目

Webpack5 从零配置一个基础的 Vue 项目

前言

配置日期2021/6/28,使用的 Node.js 版本14.15.4,最低版本建议升至10.16.0+ 。本文记录使用 Webpack5 手动从零配置一个基础 Vue3 项目的学习过程。

用于参考的项目文件地址:gitee.com/willflow/ju…

若希望使用 vue-cli 创建 Vue3 项目:

  • 安装或者升级 vue-cli:npm install -g @vue/cli
  • 保证 vue-cli 版本在4.5.0以上:vue --version
  • 创建项目选择 vue3.0:vue create 项目名
  • windows 用户命令行无法移动光标可以使用这个命令创建项目:winpty vue.cmd create 项目名

文中各配置项,皆为基础使用方法,更多配置请参考官方文档。文中如有不当之处,还望不吝指正,理性交流。

第一部分:初始化项目

目标:完成一个能够打包运行的最基础配置,为后续逐步完善做准备。 示例的基础目录结构如下

image.png

创建项目目录、初始化 package.json、安装Webpack 并添加配置文件 webpack.config.js

# 创建文件夹,并且进入
mkdir demo && cd $_

# 初始化package.json
npm init                // 或者全部使用默认选项 npm init -y

# 安装 webpack 和 webpack CLI到开发依赖
npm i webpack -D      
npm i webpack-cli -D    //webpack命令执行之后,会寻找webpack-cli这个包并执行,详见入口文件的逻辑:node_modules/webpack/bin/webpack.js
复制代码

根目录创建 webpack.config.js 文件并配置 entry 和 output

//webpack.config.js
const path = require('path');
module.exports = {
    entry: './src/main.js',             // 配置入口文件,单入口使用字符串,多入口使用对象
    output: {                           // 打包后项目文件在硬盘中的存储位置
        filename: '[name].js',          // [name]占位符的写法可支持多入口
        path: path.resolve(__dirname, 'dist'),
    },
    mode: 'production',              // 告知 webpack 使用相应模式的内置优化,默认为 production
}
复制代码

Vue 和相关依赖

npm i vue -S                 //-S 等于 --save   -D 等于 --save-dev   i 等于 install
npm i vue-loader -D          //解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理  
npm i @vue/compiler-sfc -D   //将解析完的vue单页面组件(sfc)编译为js
复制代码
//webpack.config.js
const { VueLoaderPlugin } = require('vue-loader');
module: {
    rules: [
        {
            test: /\.vue$/,
            loader: 'vue-loader',
        },
    ];
}
plugins: [
        new VueLoaderPlugin(),
]
复制代码

在main.js 中使用 Vue

import { createApp } from 'vue'
import App from './App.vue'       // 测试文件 App.vue 和 用于挂载的index.html 自行创建

createApp(App).mount('#app')
复制代码

CSS 相关依赖

npm i css-loader -D  // 用于处理 .css 文件
npm i style-loader -D  //将样式通过 <style>标签的形式 挂载到页面的 head 部分
复制代码
//webpack.config.js
module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                'style-loader',    
                'css-loader'       // loader执行从右到左,这里先执行css-loader
            ]
        },
    ];
}
复制代码

html-webpack-plugin

npm i html-webpack-plugin -D  //在打包结束后,自动生成一个html文件,并把打包生成的js文件引入到这个html文件当中
复制代码
//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
    new HtmlWebpackPlugin({
        template: path.join(__dirname, 'index.html'),
        filename: 'index.html'
    }),
]
复制代码

clean-webpack-plugin

npm i clean-webpack-plugin -D  //在打包之前清空output配置的文件夹
复制代码
// //webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
    new CleanWebpackPlugin()
]
复制代码

webpack-dev-server

npm i webpack-dev-server -D  //以file形式在浏览器打开打包后的文件,无法发送ajax请求。所以需要devServer在本地开启一个服务器,以http的形式发送请求。
复制代码
// //webpack.config.js
module.exports = {
    devServer: {
        contentBase: path.resolve(__dirname, 'dist'),
        open:true,
        port:8888,             // 第三部分会使用 portfinder 自动获取可用端口号            
        hot:true,
        hotOnly:true,
    },
}
复制代码

webpack-cli4版本,将webpack-dev-server的命令集成到了webpack-cli当中,需使用如下命令:

//package.json
"scripts": {
    "dev": "webpack serve --config webpack.config.js"
},
复制代码

此时,在命令行输入 npm run dev,项目已经可以正常打包启动

第二部分:引入常用库

目标:在项目中引入vuex、vue-router、Element Plus、Axios、ESLint

在src文件夹下新建 router,store,views,components,assets文件夹,目录结构如下
image.png

vuex和vue-router

npm i vuex --save
npm i vue-router --save  
复制代码

配置router/index.js,可选 hash 模式(createWebHashHistory)和 history 模式(createWebHistory),history 模式需要后端支持。

import { createRouter, createWebHashHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/about.vue')
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router
复制代码

配置store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})
复制代码

在main.js中引入vue-router和vuex

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'

let app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')
复制代码

配置和引入完成后,vuex和vue-router就可以正常使用了,示例中的测试页面(home.vue,about.vue)自行创建。

Element Plus

npm i element-plus --save 
复制代码

采用官网的按需引入方法,减小打包体积,需要下载插件babel-plugin-import

npm i babel-plugin-import -D  //将import依赖整个组件库的方法,转换成require依赖具体模块,实现组件的按需加载
复制代码

使用该插件需要babel配置文件:babel.config.js

plugins: [
        [
            "import",
            {
              libraryName: 'element-plus',
              customStyleName: (name) => {
                return `element-plus/lib/theme-chalk/${name}.css`;
              },
            },
        ],
],
// 解释:
// 当Babel解析遇到:
import { xxx } from 'element-plus'
// 会转换成:
var xxx = require('element-plus/lib/xxx')
// 将本来引入整个名为element-plus的库,转换成引入具体模块'element-plus/lib/xxx'。这样webpack会认为依赖的是具体的xxx而不是整个element-plus。同时引入对应的样式文件。 
复制代码

main.js

// 在main.js中引入并使用element-plus
import {
    ElButton,
    ElSelect
} from 'element-plus';

const Elcomponents = [
    ElButton,
    ElSelect
]

Elcomponents.forEach(component  => {
    app.use(component )
})
复制代码

Axios

npm i axios -S
复制代码

可以对axios进一步封装

// http.js
import axios from 'axios'

axios.defaults.baseURL = baseURL
// 请求拦截
axios.interceptors.request.use(config => {
    // do something
    return config
},error => {
    return Promise.reject(error)
})
// 返回拦截
axios.interceptors.response.use(response => {
    // do something
    return response
},error => {
    return Promise.reject(error)
})

export function get(url, params) {
  return axios.get(url, {
    params
  }).then((res) => {
    // do something
    return res.data
  }).catch((e) => {
    console.log(e)
  })
}
复制代码

在vue组件中直接使用,或提取成函数后在组件中使用

// 定义获取数据的函数
import { get } from './http'

export function getXxx() {
  return get('/api/getXxx').then((result) => {
    // do something
    return result
  })
}

//使用
import { getXxx } from '...'
const list = await getXxx()
复制代码

ESLint

npm i eslint -D
npm i eslint-plugin-vue -D
复制代码

生成eslint文件

./node_modules/.bin/eslint --init
复制代码

在生成的配置文件中,添加插件

"extends": [
        "plugin:vue/vue3-essential",              // 校验 Vue3 逻辑代码
        "plugin:vue/vue3-strongly-recommended",   // 校验 Vue3 模板
        "eslint:recommended"                      // 引入eslint核心功能
],
复制代码

常见插件:
eslint-plugin-node:node的eslint配置
eslint-plugin-promise:promise语法配置
eslint-plugin-import:针对import语法优化
eslint-plugin-standard:基础配置(已废弃)

第三部分:拆分webpack.config.js

目标:从第三部分开始,为了方便开发调试和上线,需要将webpack.config.js拆分为以下三个文件。

  • webpack.dev.js:开发环境配置
  • webpack.prod.js:生产环境配置
  • webpack.common.js:开发和生产环境配置的共享配置

开发环境和生产环境的区别

  • 生产环境可能需要分离CSS成单独文件,以便多个页面共享同一个CSS文件
  • 生产环境需要压缩 HTML/CSS/JS 代码
  • 开发环境需要 SourceMap 文件方便调试
  • 开发环境需要HMR、devServer等功能
  • ......

总的来说:开发环境帮助我们快速开发测试联调,生产环境保证上线环境的打包流程是最优化体验。

安装 webpack-merge

npm i webpack-merge -D   // 用于连接数组,合并对象,如下所示:
复制代码
merge( 
    {a : [1],b:5,c:20}, 
    {a : [2],b:10, d: 421} 
)
//合并后结果
{a : [1,2] ,b :10 , c : 20, d : 421}
复制代码

引入webpack-merge,按照说明的描述拆分文件,配置很简单,可参考项目文件。

mode

mode 选项用于告知 webpack 使用相应模式的内置优化。可选 development, production 或 none 。通常开发环境使用 development,生产环境使用 production。

module.exports = {
  mode: 'development',
};
复制代码

环境变量

使用 cross-env,跨操作系统设置 node 环境变量。

npm i cross-env -D
复制代码
"scripts": {
    "dev": "cross-env NODE_ENV=development webpack serve --config webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --config webpack.prod.js"
  },
复制代码

sourcemap

sourcemap 产生的文件映射了压缩后的代码所对应的转换前的源代码位置,解决了代码压缩后难以调试的问题。 具体可参照文档,根据需要配置参数值,不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。

// webpack.dev.js
devtool: 'eval-cheap-module-source-map', 
// webpack.prod.js
devtool: 'cheap-module-source-map',
复制代码

第四部分:webpack基础共享配置

目标:完善 webpack.common.js

使用 Babel

npm i babel-loader -D   // 相当于连接webpack和babel的桥梁,并不会转换js代码
复制代码

在 webpack.base.js 的 rules 中新增一条规则。排除 node_modules 文件夹,因为 NPM 引入的工具库已经经过编译,不需要再重复编译,影响打包速度。

// webpack.base.js
{
   test: /\.js$/,
   exclude: /node_modules/,
   loader: 'babel-loader',
}
复制代码

接下来关于 babel polyfill 的配置,可以参考我的前一篇内容,Babel polyfill 常见配置对比

CSS样式相关配置

在第一部分,我们已经安装配置过了css-loader 和 style-loader,使得Webpack可以识别css语法,并将css代码以style标签的形式插入到HTML文件当中,现在我们来进一步完善这一部分的配置。

1. css-loader 的 importLoaders

官方解释:用于配置 css-loader 作用于 @import 的资源之前有多少个 loader。
举个例子: 首先假设我们有两个文件:style.css和body.css。style.css中通过@import 'body.css';引入body.css:

/* style.css */
@import 'body.css';
.style-div {
    display: flex;
}

/* body.css */
.body-import {
    /* body import */
    display: flex;
}
复制代码

测试用的 webpack 配置项如下

rules: [
    {
        test: /\.css$/,
        use: [
            {
                loader: 'css-loader',
                options: {
                    importLoaders: 1   // 另一份配置不使用importLoaders
                }
            },
            'postcss-loader'
        ]
    }
]
复制代码

之后我们来对比一下,开启了importLoaders=1 与未开启 importLoaders 的打包结果

/* 未开启importLoaders */
.body-import {
    /* body import */
    display: flex;
}

.style-div {
    display: -ms-flexbox;
    display: flex;
}
--------------------------------------------------------
/* 开启importLoaders=1 */
.body-import {
    /* body import */
    display: -ms-flexbox;
    display: flex;
}

.style-div {
    display: -ms-flexbox;
    display: flex;
}
复制代码

通过对比可以发现

  • 未使用 importLoaders 的 body.css 内的display: flex; 没有自动添加前缀,说明 Postcss 没有作用到@import引入的文件中;
  • 使用了importLoaders=1 的 body.css 内的 display: flex; 添加了前缀,说明 Postcss 作用到了被 @import引入的文件中。
  • 所以基于项目需要,我们需要使用 importLoaders,确保 css-loader 之后的 loader 会正常作用。

2. CSS预处理器

预处理器可以提升开发效率和体验。在我的项目里使用 Sass 作为预处理器。

node-sass 已经不再维护,改用官方推荐的 dart-sass,直接安装 sass-loader 和依赖的 sass 即可。

npm i sass sass-loader -D  // sass-loader只是将 Sass语法编译成 CSS,后续还需要使用 css-loader 和 style-loader配合使用
复制代码
module.exports = {
    //   ...
    module: {
        rules: [
            {
                test: /\.sass$/,
                use: [
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1
                        }
                    },
                    'sass-loader'
                ]
            }
        ]
    }
};
复制代码

3. PostCSS 和 autoprefixer

PostCSS 能将 CSS 解析成 AST,然后通过各种插件做各种转换,最终生成处理后的新 CSS,跟 Babel 在功能和实现上都类似。我们这里安装 PostCSS,并使用 autoprefixer 实现自动添加浏览器前缀的功能。

npm i postcss-loader postcss autoprefixer -D 
复制代码
module: {
        rules: [
            {
                test: /\.sass$/,
                use: [
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 2
                        }
                    },
                    'sass-loader',
                    'postcss-loader'
                ]
            }
        ]
}
复制代码

在根目录创建 postcss.config.js 和 .browserslistrc 文件来使用 autoprefixer 。

// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

// .browserslistrc 
> 1%
not ie <=8
复制代码
//原始样式
display: flex;
// 自动添加浏览器前缀
display: -webkt-box;
dispaly: -ms-flexbox;
display: flex;
复制代码

4. sass-resources-loader

将 Sass 的 变量、Mixin、Function 自动引入各个 Sass 文件中,未使用的 Mixin 和 Function 在打包后不会编译成 CSS,所以不用担心体积问题。

npm i sass-resources-loader -D  
复制代码
{
    test: /\.scss$/,
    use: [
        {
            loader: MiniCssExtractPlugin.loader,
        },   
        {
            loader: 'css-loader',
            options: {
                importLoaders: 2,
            },
        },
        'sass-loader',
        'postcss-loader',
        { loader: 'sass-resources-loader',
            options: {
            resources: [
                'src/style/tools/index.scss',      // 引入 mixins 和 functions 的文件
                'src/style/settings/var.scss',     // 引入全局 SasS 变量的文件
            ]
            }
        }
    ],
},
复制代码

使用 vue-cli 生成的项目,在 vue.config.js 文件中添加配置。参考文档:vue-cli官方文档 的‘向预处理器Loader传递选项’

module.exports = {
    css: {
      loaderOptions: {
        scss: {
            prependData: `@import "@/style/tools/index.scss";@import "@/style/settings/var.scss";`
        },
      }
    }
}
复制代码

管理静态资源

1. 资源模块类型

Webpack 本身并不识别图片等静态资源,这时候就需要借助 loader 的力量,在 Webpack5 之前有两个常用 loader:file-loader 和 url-loader

  • file-loader:能够根据配置项复制使用到的资源(不局限于图片)到构建之后的文件夹,并且能够更改对应的链接;
  • url-loader:包含 file-loader 的全部功能,并且能够根据配置将符合配置的文件转换成 Base64 方式引入,将小体积的图片 Base64 引入项目可以减少 http 请求,是一个前端常用的优化方式。

Webpack v5 新增了资源模块类型的配置,来使用资源文件,并且无需配置额外的loader。

  • asset/resource 发送一个单独的文件并导出 URL,之前通过 file-loader 实现
  • asset/inline 导出一个资源的data URL,之前通过 url-loader 实现
  • asset/source 导出一个资源的源代码,之前通过 raw-loader 实现
  • asset 导出一个data URL和发送一个单独的文件之间自动选择,之前通过 url-loader,并配置资源体积限制实现。
{
     test: /\.(png|svg|jpg|gif)$/,
     type: 'asset',
     parser: {
        dataUrlCondition: {
            maxSize: 8 * 1024
        }
     }
}
复制代码

2. html-loader

HTML 中使用引入图片等静态资源的时候,需要添加 html-loader 配置,不然不会处理静态资源的路径问题。

npm i html-loader -D      // 将 HTML 导出为字符串。可以压缩 HTML 字符串。
复制代码
{
    test: /\.html$/,
    use: ['html-loader']
},
复制代码

3. image-webpack-loader

借助 image-webpack-loader 可以对使用到的图片进行优化。它支持 JPG、PNG、GIF 和 SVG 格式的图片,该 loader 不能嵌入图片,需要配置enforce: 'pre'保证在嵌入图片之前执行

npm i image-webpack-loader -D 
复制代码
{
   test: /\.(jpe?g|png|gif|svg)$/,
   loader: 'image-webpack-loader',
   enforce: 'pre'
}
复制代码

4. 字体、富媒体

这类资源不需要打包成base64

{
     test: /\.(eot|woff|ttf|woff2|appcache|mp4|pdf)(\?|$)/,
     type: 'asset/resource'
}
复制代码

缩小查找范围

1. oneOf

原本的规则匹配,会将每个规则都匹配一遍,使用 oneOf 表示唯一匹配,命中了一条规则就结束,可以提高 loader 的执行效率。

rules: [
    {
        oneOf: [
            {
                test: /\.css$/,
                use: [...],
            },
            {
                test: /\.scss$/,
                use: [...],
            },
            {
                test: /\.html$/,
                use:[...]
            },
            ....
        ],
    },
],
复制代码

2. alias

配置别名可以加快搜索速度

module.exports = {
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src'),
        },
    },
}    
复制代码

3. extensions

指定 extensions 之后,使用require 和 import 的时候就不需要加文件扩展名了,查找的时候会依次匹配,但同一个目录有不同类型的同名文件时,也只会匹配第一个。

module.exports = {
    resolve: {
        extensions: ['.vue','.js','.json','scss','css'],
    },
}    
复制代码

4. modules

指定第三方依赖的存放目录,默认为'node_modules'

module.exports = {
    resolve: {
        modules: [
            path.resolve(__dirname, '../../node_modules'),
            'node_modules'
        ]
    }
}    
复制代码

第五部分:webpack开发环境配置

安装 portfinder

npm i portfinder -D     // 自动获取可用端口,避免端口被占用
复制代码
// //webpack.dev.js
const path = require('path');
const { merge } = require('webpack-merge');
const commonWebpackConfig = require('./webpack.common');
const portfinder = require('portfinder');

const devConfig = {
   // 开发环境的 webpack 配置项
};

const devWebpackConfig = merge(commonWebpackConfig, devConfig);

module.exports = new Promise((resolve, reject) => {
    portfinder.getPort({
        port: 1081,        // 默认1081端口,若被占用,重复+1,直到找到可用端口或到stopPort才停止
        stopPort: 65535,   // maximum port
    },(err, port) => {
        if(err) {
            reject(err)
            return
        }

        devWebpackConfig.devServer.port = port
        resolve(devWebpackConfig);
    });
})
复制代码

缓存

1. cache

缓存之前打包的内容,配置之后会生成一个.cash文件夹,通过文件缓存,直接缓存到本机磁盘

// //webpack.dev.js
cache: {
    type: 'filesystem',   //默认缓存到 node_modules/.cache/webpack。还可以使用 cacheDirectory选项自定义配置
},
复制代码

2. babel-loader

babel-loader 自带缓存配置。开启后会将缓存放在node_modules/.cache/babel-loader

// //webpack.dev.js
{
    loader: 'babel-loader',
    options: {
        cacheDirectory: true,
    }
}
复制代码

3. cache-loader

使用此loader,可以将其他 loader 的结果缓存到磁盘中,默认路径node_modules/.cache/cache-loader

npm i cache-loader -D
复制代码
{
    test: /\.css$/,
    use: [
        'cache-loader',
        'style-loader',    
        'css-loader'
    ],
},
复制代码

第六部分:webpack生产环境配置

单独提取CSS

在生产环境 CSS 文件应该是单独导出的,这时候就需要 mini-css-extract-plugin

npm i mini-css-extract-plugin -D   //将 CSS 提取到单独文件
复制代码

mini-css-extract-plugin 使用的时候需要分别配置 loader 和 plugin,loader 需要放在css-loader 之后代替 style-loader

// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].css',
            chunkFilename: '[name].[contenthash:8].css'
        })
    ],
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: '../',
                            hmr: false
                        }
                    },
                    'css-loader'
                ]
            }
        ]
    }
};
复制代码

Tree Shaking

webpack5 有更好的 Tree Shaking 处理机制,mode 为 production 时默认开启,也可以手动配置开启。

// webpack.prod.js
optimization {
    usedExports: true,
    minimize: true
    // 还有更多配置...
}
复制代码
// package.json
"sideEffects": [
    "*.css"             // 也可以设置某些文件不 Tree Shaking
]
复制代码

Scope Hoisting

作用域提升,把几个函数合并成一个函数,减少执行每个新函数,产生的临时执行上下文,减少开销,mode 为 production 时默认开启。

压缩

JS 压缩

如果你使用的是 webpack 5 或以上版本,不需要安装这个插件。webpack 5 自带最新的 terser-webpack-plugin,并在mode 为 'production' 时自动使用。

npm i terser-webpack-plugin -D
复制代码
// // webpack.prod.js
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};
复制代码

HTML 压缩

1. html-webpack-plugin 的 minify

html-webpack-pluginminify 选项用于设置html文件的压缩,如果 mode 为 'production' 则会自动开启 minify。并使用 html-minifier-terser 进行压缩,默认配置如下,可参考 html-minifier-terser 的文档按需修改:

{
  collapseWhitespace: true,
  keepClosingSlash: true,
  removeComments: true,
  removeRedundantAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
  useShortDoctype: true
}
复制代码
2. html-minimizer-webpack-plugin

内部同样使用 html-minifier-terser 来压缩 HTML。webpack5 开始,像压缩类的插件,应该配置在 optimization.minimizer 数组中,方便统一管理。插件使用方式可以参考官方文档。

npm i html-minimizer-webpack-plugin -D
复制代码
// webpack.prod.js
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");

optimization: {
    minimize: true,
    minimizer: [
      // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
      // `...`,
      new HtmlMinimizerPlugin(),
    ],
},
复制代码

CSS 压缩

在 Webpack5 之前,通常使用 optimize-css-assets-webpack-plugin 这个插件来压缩 CSS 代码。但在其 NPM库,有一段话“For webpack v5 or above please use css-minimizer-webpack-plugin instead.”
所以,按照 css-minimizer-webpack-plugin 的文档,配置 Webpack 的optimization.minimize 即可开启css压缩。

npm i css-minimizer-webpack-plugin -D
复制代码
// webpack.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  optimization: {    // 告知 webpack 使用 TerserPlugin 或其它在 optimization.minimizer 定义的插件压缩 bundle。
    minimizer: [
      // `...`,        // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
      new CssMinimizerPlugin(),
    ],
  },
};
复制代码

拆分代码

splitChunks

在配置 splitChunks 之前,先来简单了解一下 module、chunk、bundle的概念

  • module:通过 import、require 语句引入的各类资源。
  • chunk:chunk 包含一个或多个 module,根据 webpack 的配置拆分出来,例如 splitChunks 就可以拆分出额外的 chunk。
  • bundle:对 chunk 打包压缩之后的最终文件,一般和 chunk 是一对一的关系,也可能会有多个chunk 中的公共部分被组合到一个bundle。

Webpack4 开始,用于处理公共模块的 splitChunks 功能是开箱即用的,默认配置如下:

module.exports = {
    // ...
    optimization: {
        splitChunks: {
            chunks: 'async', // "initial" | "all" | "async" ,默认async
            minSize: 20000, // 20K,当超过这个体积就会被抽离成公共部分。
            minRemainingSize: 0, // 确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块。
            minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。
            maxAsyncRequests: 30, // 按需加载时的最大并行请求数。例如 import('./page.js'),则引入page.js时,算上page.js本身在内的最大请求数量限制为30个。
            maxInitialRequests: 30, // entry入口的最大并行请求数。入口文件本身算一个请求,入口文件内的动态加载模块不计算请求数。
            enforceSizeThreshold: 50000,  // 强制执行拆分的体积阈值和其他限制(minRemainingSize,maxAsyncRequests,maxInitialRequests)将被忽略。
            cacheGroups: {
                defaultVendors: {
                    test: /[\\/]node_modules[\\/]/, // 符合规则则提取 chunk
                    priority: -10 // 优先级,当一个模块属于多个 chunkGroup,权重值高的优先
                    reuseExistingChunk: true, // 如果该chunk包含的modules都已经另一个被分割的chunk中存在,那么直接引用已存在的chunk,不会再重新产生一个
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                }
            }
        }
    }
};
复制代码

在实际项目中,通过 webpack-bundle-analyzer 等 bundle 分析工具,可以分析并将体积过大的公共模块拆分出来。例如发现下图的 moment 包过大,就可以添加配置,拆分出来(当然也可以使用体积更小的 day.js 或者使用 moment-locales-webpack-plugin 插件缩小 moment 体积)。

100878.png

// webpack.prod.js
optimization: {
    splitChunks: {
        cacheGroups: {
            moment: {
                test: /[\\/]node_modules[\\/]_moment@2.29.1@moment[\\/]/,
                name: 'moment',
                chunks: 'all',
            },
        },
    },
},
复制代码

如果使用了 html-webpack-plugin,需要添加 chunks 配置来自动引入拆分出的 chunk。

new HtmlWebpackPlugin({
    // ...
    chunks: ['moment','main'],
}),
复制代码

最后

好了,以上就是本篇文章的全部内容了,还有许多常用配置和 Webpack5 新特性没有使用到,提供的选项也存在很大的封装优化空间,当然你也可以编写自己的 loader 和 plugin 来解决遇到的实际问题。本文仅作为学习记录,起一个抛砖引玉的作用,希望文中内容能你有所帮助,谢谢。

文章分类
前端
文章标签