跳转到内容

打包优化

刀刀

4/7/2025

0 字

0 分钟

项目的构建速度由两个方面决定:

  • 处理的内容数量
  • 进行的操作

优化操作一般有以下几点:

  1. 减少处理内容数量

    指定 includeexclude 排除掉一些不必要的处理; DLL 优化处理

  2. 提高处理效率

    多线程操作;缓存 sourcemap

  3. 减少操作的数量

    去掉一些没意义的操作

减少处理内容数量

排除不必要的处理

先来看看一段 vue.config.js 代码:

js
const path = require('path')
function resolve(dir) {
    return path.join(__dirname, dir)
}

module.exports = {
    publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
    chainWebpack: (config) => {
        config.resolve.alias.set('@', resolve('src'))
    },
    productionSourceMap: false,
    devServer: {
        open: false,
        port: 5003,
        overlay: {
            errors:true,
            warmimgs: true,
        },
        proxy: {
            '/api': {
                target: 'xxx',
                changeOrigin: true
            }
        }
    }
}

打包这个项目,打包的时间为 6.4s,接下来测试一下通过 exclude 排除后能减少多少时间。

需要注意的是,不能什么都能排除, js 文件 webpack 本来就认识,但是 tsvuehtml 等文件不能排除,需要处理,通过 babel-loader-es6-es5 转编译。

开发环境中排除掉 js 中的 node_modulessrc ,代码如下:

js
const path = require('path')
function resolve(dir) {
    return path.join(__dirname, dir)
}

module.exports = {
    publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
    chainWebpack: (config) => {
        if(config.module.rule('js').exclude
          .add(resolve('/node_modules'))
          .add(resolve('/src'))
        )
        config.resolve.alias.set('@', resolve('src'))
    },
    // ...
}

重新打包,打包时间缩短为 6.19s,这个项目用到的 js 不多,如果 js 文件多的话能减少更多的时间。

DLL处理

接下来做 DLL 处理,步骤为先把一些第三方库打包,后续打包时候不去打包这些第三方库,而是直接复用。

根目录下新建一个 webpack.dll.js 文件,代码如下:

js
const path = require('path')
const webpack = require('webpack')

// dll 文件存放的目录
const dllPath = 'public/vendor'
module.exports = {
    mode: 'production',
    entry: {
        // 需要提取的库文件
        vendor: ['vue', 'vuex', 'vue-router', 'element-ui', 'echarts']
    },
    // 定义出口
    output: {
        // 当前目录下的 public文件夹下的 vendor 
        path: path.join(__dirname, dllPath),
        filename: '[name].dll.js',
        // vendor.dll.js 中暴露出的全局变量名
        // 保持与 webpack.DllPlugin 中名称一致
        library: '[name]_[hash]'
    },
    plugins: [
        // manifest.json 描述动态链接库包含了哪些内容
        new webpack DllPlugin({
        	path: path.join(__dirname, dllPath, '[name]-manifest.json'),
    		// 保持与 output.library 中名称一致
    		name: '[name]_[hash]',
    		context:process.cwd()
        })
    ]
}

接着去到 package.json 文件重写打包命令:

json
{
    // ...
    "scripts": {
        "dll": "webpack --config webpack.dll.js",
        // ...
    }
}

通过新命令打包,会遇到一个坑,它会提示你是否要下载 wenpack-cli ,如果直接点 yes ,会有版本冲突问题,正确做法是先在全局下载 webpack-cli

npm i webpack-cli@3.3.12 --save-dev

然后再运行命令:

npm run dll

运行完毕, public 文件夹下已经有打包好的 vendor 文件夹,接下来回到 vue.config.js 文件,添加 webpack 配置,代码如下:

js
const path = require('path')
const webpack = require('webpack')
function resolve(dir) {
    return path.join(__dirname, dir)
}

module.exports = {
    publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
    configureWebpack: {
        plugins: [
            new webpack.DllReferencePlugin({
                context: process.cwd(),
                // 关键设置,引用的文件地址
                manifest: require('./public/vendor/vendor-manifest.json')
            })
        ]
    },
    // ...
}

改完配置文件后因为没有缓存了,所以第一次打包速度会比较慢,以后续打包时间为准。打包后发现时间已经缩短为 3.15s 。

不过现在还没结束,打包完毕后此时项目如果在浏览器上打开,页面一片空白。这是因为前面打包的时候把这些资源排除掉不打包了,解决方法为去到 index.html 文件中,把那些排除出去的 js 文件手动引入。

html
<script src="<%= BASE_URL %>vendor/vendor.dll.js"></script>

提高处理效率

多线程处理

多线程打包的收益在项目较小的时候其实不是很明显,因为开启多线程也需要额外的操作。

想要使用多线程需要下载一个 loader

npm i thread-loader --save

下载同时来写代码,在 vue.config.js 文件中引入使用:

js
const os = require('os')
const path = require('path')
function resolve(dir) {
    return path.join(__dirname, dir)
}

module.exports = {
    chainWebpack: (config) => {
        if(config.module.rule('js').exclude
          	.add(resolve('/node_modules'))
          	.add(resolve('/src'))
           config.module.rule('vue').use('thread-loader')
            .loader('thread-loader').tap(() =>{
            	return {
                	// 根据当前cpu数量
                	worker: os.cpus().length
            	}
        	}).before('vue-loader') // 在vue-loader处理完后再处理
        )
        config.resolve.alias.set('@', resolve('src'))
    },
    // ...
}

但是这样可能不会生效,甚至会有反效果,这是因为优化之后也有可能会增多处理。

上方开多线程这个举动也是需要时间,多线程处理的时间比开启多线程的时间要少,因此最后打包时间也会增多,不太推荐使用该方法。

sourceMap

举个例子,我在 login.vue 页面 console.log() 输出一行代码,开启 sourceMap 后可以根据这个输出定位到是哪个文件哪行代码输出的。在生产模式时没必要打开。

Vue-cli 中配置的默认 evalsourceMap ,可以关闭,代码如下:

js
module.exports = {
    configureWebpack: {
        devtool: 'none',
        plugins: [
            // ...
        ]
    },
}

关闭之后打包时间确实有减少,但是打开项目之后查看控制台的打印,会定位到打包好的文件,不利于我们排错,因此不推荐关闭。

注意

CSS 的 sourceMap 默认是关闭的,可以打开,但是最后打包时间会增加,因为增加了要处理的内容。开启方式为在 vue.config.js 文件设置以下代码:

js
module.exports = {
    // ...
    css: {
        sourceMap: true
    }
}

完整代码

vue.config.js 完整代码如下:

js
const os = require('os')
const path = require('path')
function resolve(dir) {
    return path.join(__dirname, dir)
}

module.exports = {
    publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
    chainWebpack: (config) => {
        if(config.module.rule('js').exclude
          	.add(resolve('/node_modules'))
          	.add(resolve('/src'))
           
           /** 
           * 多线程处理模块start,不太推荐使用
           */
           config.module.rule('vue').use('thread-loader')
            .loader('thread-loader').tap(() =>{
            	return {
                	// 根据当前cpu数量
                	worker: os.cpus().length
            	}
        	}).before('vue-loader') // 在vue-loader处理完后再处理
           /** 
           * 多线程处理模块end
           */
        )
        config.resolve.alias.set('@', resolve('src'))
    },
    configureWebpack: {
        plugins: [
            new webpack.DllReferencePlugin({
                context: process.cwd(),
                // 关键设置,引用的文件地址
                manifest: require('./public/vendor/vendor-manifest.json')
            })
        ]
    },
    productionSourceMap: false,
    devServer: {
        open: false,
        port: 5003,
        overlay: {
            errors:true,
            warmimgs: true,
        },
        proxy: {
            '/api': {
                target: 'xxx',
                changeOrigin: true
            }
        }
    }
}