“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第n篇文章,点击查看活动详情”
webpack 是一个非常复杂的构建工具,包含的知识点极多,本文主要讲解两个部分:
- 通过 DllPlugin 和 DllReferencePlugin 来进行打包速度的优化
- 详细讲解如何使用 webpack 进行多入口文件配置
环境准备
我们要通过使用 webpack,babel 工具配置一个可以运行 react 代码的环境
说明: 本文所使用的 webpack 版本为:5.74.0,react 版本为:18.2.0
- 新建项目:
1. 新建一个空的文件夹
2. 通过 npm init -y 进行初始化
3. 在根目录下创建 src 文件夹,并创建 index.html 和 index.js 文件
// 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>html 模版</title>
</head>
<body>
<div id='root'></div>
</body>
</html>
// react 测试文件
import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
import _ from 'lodash';
class App extends Component {
render() {
return <div>
<div>{_.join(['This', 'is', 'App'], ' ')}</div>
</div>
}
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
- 安装依赖:
webpack 相关
npm i webpack
npm i webpack-cli -D
npm i webpack-merge -D
npm i webpack-dev-server -D
webpack plugin
npm i html-webpack-plugin -D
npm i clean-webpack-plugin -D
npm i add-asset-html-webpack-plugin -D
babel 相关
npm i babel-loader -D
npm i @babel/preset-env -D
npm i @babel/preset-react -D
npm i @babel/plugin-syntax-dynamic-import -D
react 相关
npm i react
npm i react-dom
测试第三方模块相关
npm i jquery
npm i lodash
- 依赖安装好后,我们需要开始编写 webpack 的配置文件,在根目录下创建一个 build 文件夹,新建三个文件分别为:webpack.common.js, webpack.dev.js, webpack.prod.js
// webpack 公用配置文件
const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
index: './src/index.js'
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /.jsx?$/,
include: path.resolve(__dirname, '../src'),
use: [
{
loader: "babel-loader"
}
]
}
]
},
output: {
path: path.resolve(__dirname, '../dist')
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({template: 'src/index.html'}
]
}
// webpack 开发环境配置文件: 包括了 webpack dev server 的配置
const path = require('path')
const webpack = require('webpack');
const {merge} = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const devConfig = {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
},
open: true,
port: 8080,
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].js'
}
}
module.exports = merge(commonConfig, devConfig)
// 生产环境配置文件
const {merge} = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
output: {
filename: '[name].[contenthash].js'
}
}
module.exports = merge(commonConfig, prodConfig)
- package.json 文件脚本配置
build:dev 作用:打包开发环境代码,这个命令主要是运行 webpack dev server 时我们的打包生成文件都放到了内存里,我们没法看到,所以设置了这个命令
dev 作用:运行开发环境代码,启动 webpack dev server
build 作用:打包生产环境代码
"scripts": {
"build:dev": "webpack --config ./build/webpack.dev.js",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
}
- 以上操作都配置好后,可以尝试运行三个打包命令,正常打包并且生成文件可以在浏览器中打开即可
打包速度优化
- 此时我们没有做任何的打包速度优化,运行一个 npm run build,查看打包速度
- 从上图中可以看到,最终打包时候是 3155ms,多运行了几次,平均时间大概在 3000ms 左右
- 那么接下来我们就通过 DllPlugin 和 DllReferencePlugin 这两个插件来提升打包速度
- 首先,在 build 目录中新建一个文件:webpack.dll.js
// 打包优化配置文件
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry: {
vendors: ['react', 'react-dom', 'lodash'],
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, '../dll'),
// 打包生成的文件通过 name 作为全局变量暴露出来
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, '../dll/[name].manifest.json'),
})
]
}
- 上面的 webpack 配置文件就是通过 DllPlugin 这个插件来生成对应的库文件,并且把生成的文件放到根目录下的 dll 文件夹中,除了对应的库文件以外,还生成了描述库文件与 node_modules 文件映射关系的 manifest 文件,这有点类似于 sourcemap 的作用
- 那么 DllPlugin 这个插件是怎么做到打包优化的呢,其实就是我们每次打包,第三方的文件每次都会参与打包静态分析等等,然后实际上那些第三方的文件没有变动,不需要每次都参与打包,那么这个插件就是将第三方文件打包一次,然后再通过 manifest 文件建立的映射关系来为我们的代码使用
- 当然,生成了这些 js 库文件该如何使用呢,我们还需要使用插件将文件挂在到最终打包生成的 index.html 中
- 接下来,在 webpack.common.js 中做出修改:
//----新增----
const plugins = [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({template: 'src/index.html'})
]
const files = fs.readdirSync(path.resolve(__dirname, '../dll'))
files.forEach((file) => {
if (/.*.dll.js/.test(file)) {
plugins.push(new AddAssetHtmlPlugin({
outputPath: './auto',
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if (/.*.manifest.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
})
//----新增----
//----修改----
plugins
//----修改----
- 在原来的代码中,新增了两个插件 AddAssetHtmlPlugin 和 DllReferencePlugin
- AddAssetHtmlPlugin 的作用是将我们上一步生成的 dll 文件夹中的库文件挂在到最终打包生成的 index.html 上
- DllReferencePlugin 插件是通过传递的 manifest 文件,webpack 会分析如果使用的第三方模块在库文件中那就直接用了,不再进行打包了,这样就能很大的提高打包速度
- 配置完后,我们先运行 npm run build:dll 生成 dll 文件
- 然后运行 npm run build 再次进行打包
- 可以看到打包速度提高到了 1568ms,比之前快了一倍左右
多入口配置
在我们日常开发中,有的时候可能需要通过 webpack 实现多个入口文件,而不是只有一个 index.html,那么该怎么通过 webpack 进行配置呢,其实很简单,我们先在 src 目录中新增两个入口文件:
- list.js
import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
import _ from 'lodash';
class App extends Component {
render() {
return <div>
<div>{_.join(['This', 'is', 'List'], ' ')}</div>
</div>
}
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
- detail.js
import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
import _ from 'lodash';
class App extends Component {
render() {
return <div>
<div>{_.join(['This', 'is', 'Detail'], ' ')}</div>
</div>
}
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
- 接下来,我们修改一下 webpack.common.js 这个文件
// 通用配置文件
const path = require('path')
const fs = require('fs')
const webpack = require('webpack')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const makePlugin = (config) => {
const plugins = [
new CleanWebpackPlugin()
]
// 循环添加入口文件
Object.keys(config.entry).forEach((item) => {
plugins.push(new HtmlWebpackPlugin({
template: 'src/index.html',
filename: `${item}.html`,
chunks: ['runtime', 'vendors', item]
}))
})
const files = fs.readdirSync(path.resolve(__dirname, '../dll'))
files.forEach((file) => {
if (/.*.dll.js/.test(file)) {
plugins.push(new AddAssetHtmlPlugin({
outputPath: './auto',
filepath: path.resolve(__dirname, '../dll', file)
}))
}
if (/.*.manifest.json/.test(file)) {
plugins.push(new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, '../dll', file)
}))
}
})
return plugins
}
const config = {
entry: {
index: './src/index.js',
detail: './src/detail.js',
list: './src/list.js'
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /.jsx?$/,
include: path.resolve(__dirname, '../src'),
use: [
{
loader: "babel-loader"
}
]
}
]
},
output: {
path: path.resolve(__dirname, '../dist')
}
}
config.plugins = makePlugin(config)
module.exports = config
- 最后运行 npm run build:dll 和 npm run build 命令,可以看到 dist 目录下会生成三个入口文件:index.html,detail.html,list.html
总结
这次通过使用 webpack 的插件,优化了打包速度,并且通过配置实现了构建多入口文件,当然 webpack 是个很复杂的构建系统,我们还需要许多的探索才能了解它