跳转到内容

打包优化

刀刀

7/14/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'))
  },
  productionSourceMap: false,
  devServer: {
    open: false,
    port: 5003,
    overlay: {
      errors:true,
      warmimgs: true,
    },
    proxy: {
      '/api': {
        target: 'xxx',
        changeOrigin: true
      }
    }
  }
}

重新打包,打包时间缩短为 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 文件重写打包命令,新增一条 dll 打包命令:

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

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

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

然后再运行命令:

sh
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') 
      }) 
    ] 
  }, 
  chainWebpack: (config) => {
    if(config.module.rule('js').exclude
      .add(resolve('/node_modules'))
      .add(resolve('/src'))
    )
    config.resolve.alias.set('@', resolve('src'))
  },
  productionSourceMap: false,
  devServer: {
    open: false,
    port: 5003,
    overlay: {
      errors:true,
      warmimgs: true,
    },
    proxy: {
      '/api': {
        target: 'xxx',
        changeOrigin: true
      }
    }
  }
}

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

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

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

提高处理效率

多线程处理

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

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

sh
npm i thread-loader --save

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

js
const os = require('os') 
const webpack = require('webpack')
const path = require('path')
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')
      })
    ]
  },
  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'))
  },
  productionSourceMap: false,
  devServer: {
    open: false,
    port: 5003,
    overlay: {
      errors:true,
      warmimgs: true,
    },
    proxy: {
      '/api': {
        target: 'xxx',
        changeOrigin: true
      }
    }
  }
}

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

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

sourceMap

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

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

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

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

注意

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

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

完整代码

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

js
const os = require('os') 
const webpack = require('webpack')
const path = require('path')
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')
      })
    ]
  },
  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'))
  },
  css: {
    sourceMap: false
  },
  productionSourceMap: false,
  devServer: {
    open: false,
    port: 5003,
    overlay: {
      errors:true,
      warmimgs: true,
    },
    proxy: {
      '/api': {
        target: 'xxx',
        changeOrigin: true
      }
    }
  }
}