Webpack 打包速度优化和多入口配置

318 阅读2分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第n篇文章,点击查看活动详情

webpack 是一个非常复杂的构建工具,包含的知识点极多,本文主要讲解两个部分:

  1. 通过 DllPlugin 和 DllReferencePlugin 来进行打包速度的优化
  2. 详细讲解如何使用 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,查看打包速度

image.png

  • 从上图中可以看到,最终打包时候是 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 再次进行打包

image.png

  • 可以看到打包速度提高到了 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 是个很复杂的构建系统,我们还需要许多的探索才能了解它