最近在做的项目因为相对较大(打包有100多个chunk),在build构建的时候速度一直上不去,甚是烦恼。由于用的是vue-cli的webpack2模板,一开始并没有想着要对其进行优化,一直觉得是webpack本身慢+硬件慢(在开发机上开发,内存和CPU都不是很强力)的原因。后来慢到实在受不了了,转移到本地(i7+16G)开发的时候,发现生产构建居然需要90s,实在太长了。所以开始着手Webpack2构建优化。

  • 优化webpack构建速度,总的来说有几个思路:
  1. 优化本身项目结构,模块的引入、拆分、共用
  2. 优化结构路径、让webpack接管的文件能够快速定位
  3. 优化uglify编译速度
  4. 优化webpack本身编译速度

    有些是在开发的时候代码层面上的,有些则是需要在webpack配置层面上的。对于开发层面上来说,按需引入是很重要的一点。通常为了方便我们可以直接引入一个echarts,但是实际上并不需要echarts的所有功能。而按需引入则能最大程度上让
  • 总得来说作用最大的有这几个:
  1. 开启webpack的cache
  2. 开启babel-loader的cache
  3. 指定modules以及配置项目相关的alias
  4. 配置loader的include和exclude
  5. 用CommonsChunkPlugin提取公用模块
  6. 使用DllPlugin和DllReferencePlugin预编译
  7. 换用happypack多进程构建
  8. css-loader换成0.14.5版本。
  9. 换用webpack-uglify-parallel并行压缩代码

以下的配置都是基于vue-cli的webpack模板进行的优化。

  • 开启webpack的cache

打开webpack.base.conf.js,在module.exports里加上cache: true:

module.exports = {
cache: true,
// ... 其他配置
}
  • 开启babel-loader的cache

    开启了cache的babel-loader,在下次编译的时候,遇到不变的部分可以直接拿取cache里的内容,能够较为明显地提高构建速度。在loader选项里只需要对babel-loader开启cacheDirectory=true即可。

    配置cacheDirectory后,babel-loader可以缓存处理过的模块,对于没有修改过的文件不会再重新编译。
// ... 其他配置
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory=true'
},
// ... 其他loader
]
}
  • 配置modules以及配置项目相关的alias

这个部分的配置实际上都是对webpack接管的文件路径的一些配置。通过这些配置,webpack可以不必自己遍历去搜索模块等,而可以通过我们定义的路径,快速定位。尤其是node_modules的位置,这个位置可以通过modules选项配置,节省webpack去查找的时间。

而alias是别名。通过编写alias,既能让webpack查找文件定位更快,在开发的时候,也能少些很多相对路径的../..,在引入模块的时候很方便。

同样是打开webpack.base.conf.js,在module.exports的resolve属性里配置modules和alias。其中vue-cli会自动配置一些默认的alias。

resolve: {
//... 其他配置
modules: [path.resolve(__dirname, '../../node_modules')], // node_modules文件夹所在的位置取决于跟webpack.base.conf.js相对的路径
alias: {
//... 其他配置
api: path.resolve(__dirname, '../../server/api') // api文件所在的位置取决于跟webpack.base.conf.js相对的路径,在项目中会自动转换跟项目文件的相对路径
//... 其他配置
}
}

如果配置了如上的alias,那么我们在项目里,要引用比如api.js这个模块,可以直接这样做了:

import * as api from 'api' // 'api'是个alias,webpack会直接去找`server/api`

而不用手动去根据项目文件和api所在路径的相对位置去书写import的路径了。

  • 配置loader的include和exclude

loader的include和exclude也就是需要loader接管编译的文件和不需要loader接管编译的文件。

这里我们举babel-loader为例。通常情况下,我们不需要loader去编译node_modules下的js文件,而我们只需要编译我们项目目录下的js就行了。这样可以通过配置这两个选项,能够最小范围的限制babel-loader需要编译的内容,能够有效提升构建速度。

同样打开webpack.base.conf.js,在rules的babel-loader那块加上include和exclude。

// ... 其他配置
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory=true'],
include: [resolve('src')], // src是项目开发的目录
exclude: [path.resolve('../../node_modules')] // 不需要编译node_modules下的js
},
// ... 其他loader
]
}
  • 使用CommonsChunkPlugin提取公用模块

我们经常会有这种场景:在a.vue组件里引入了a.js或者比如c.vue,在b.vue组件里也引入了a.js或者c.vue。这样,打包了之后将会把引入的模块重复打包。而CommonsChuncksPlugin就是把这样重复打包的模块给抽取出来单独打包的插件。这个能够显著降低最后打包的体积,也能提升一些打包速度。

在webpack.base.conf.js里的plugins可以加上这段:

plugins: [
new webpack.optimize.CommonsChunkPlugin({
async: 'shared-module',
minChunks: (module, count) => (
count >= 2 // 当一个模块被重复引用2次或以上的时候单独打包起来。
)
}),
//...
]
  • 使用DllPlugin和DllReferencePlugin预编译

这个也是一个大杀器。将一些全局都要用到的依赖抽离出来,预编译一遍,然后引入项目中,作为依赖项。而webpack在正式编译的时候就可以放过他们了。能够很明显地提升webpack的构建速度。类似于Windows的dll文件的设计理念。dll资源能够有效的解决资源循环依赖的问题。能够大大减少项目里重复依赖的问题。

在webpack.base.conf.js所在的文件夹里建立一个webpack.dll.conf.js,我们将一些常用的依赖打包成dll。

首先配置一下DllPlugin的资源列表。

const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
vendor: ['vue/dist/vue.esm.js','vue-router','axios','vuex'] // 需要打包起来的依赖
},
output: {
path: path.join(__dirname, '../../public/js'), // 输出的路径
filename: '[name].dll.js', // 输出的文件,将会根据entry命名为vendor.dll.js
library: '[name]_library' // 暴露出的全局变量名
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, '../../public/js/', '[name]-mainfest.json'), // 描述依赖对应关系的json文件
name: '[name]_library',
context: __dirname // 执行的上下文环境,对之后DllReferencePlugin有用
}),
new webpack.optimize.UglifyJsPlugin({ // uglifjs压缩
compress: {
warnings: false
}
})
]
}

为了方便之后构建,可以在package.json里加上这么一句scripts:

scripts: {
//... 其他scripts
"build:dll": "webpack --config ./webpack/build/webpack.dll.conf.js" // 填写你项目中webpack.dll.conf.js的路径
}

然后执行一下npm run build:dll,就可以在输出的目录里输出一个vendor.dll.js和vendor-mainfest.json两个文件。

之后打开webpack.base.conf.js。在plugins一项里加上DllReferencePlugin。这个plugin是用于引入上一层里生成的json的。

module.exports = {
//... 其他配置 plugins: [
// ... 其他插件
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('../../public/js/vendor-mainfest.json') // 指向这个json
})
]
}

最后,在项目输出的index.html里,最先引入这个js:

<script type="text/javascript" src="public/js/vendor.dll.js"></script>

这样,webpack将不会再解析dll里的资源了。构建速度将会有质的提高。

  • 换用happypack多进程构建

webpack的构建毕竟还是单进程的。采用happypack可以改为多进程构建。而对于小文件而言,happypack效果并不明显。而对于babel-loader编译的庞大的js文件群来说,则是一大利器。

首先安装:npm install happypack --save-dev或者yarn add happypack

然后修改webpack.base.conf.js的配置如下:

const os = require('os');
const HappyPack = require('happypack');
const happThreadPool = HappyPack.ThreadPool({size: os.cpus().length}); // 采用多进程,进程数由CPU核数决定
//...
module.exports = {
plugins: [
// ...
new HappyPack({
id: 'js',
cache: true,
loaders: ['babel-loader?cacheDirectory=true'],
threadPool: happThreadPool
})
],
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
js: 'happypack/loader?id=js' // 将loader换成happypack
}
}
},
{
test: /\.js$/,
loader: ['happypack/loader?id=js'], // 将loader换成happypack
include: [resolve('src')], // src是项目开发的目录
exclude: [path.resolve('../../node_modules')] // 不需要编译node_modules下的js
},
//...
]
}
}

提速还是比较明显的。

  • css-loader换成0.14.5版本

可以查看这个issue,说是该版本之上的版本会拖慢webpack的构建速度。我自己实验了之后确实能快几秒钟。

  • 换用webpack-uglify-parallel并行压缩代码

webpack自带的uglifyjs插件效果确实不错。只不过由于受限于单线程,所以压缩速度不够高。换成webpack-uglify-parallel这个插件之后能够有效减少压缩的时间。

首先安装:npm install webpack-uglify-parallel --save-dev 或者 yarn add webpack-uglify-parallel

找到webpack.prod.conf.js(由于开发模式不需要进行uglify压缩),将原本的:

plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
})
// ... 其他配置
]

替换为:

const UglifyJsparallelPlugin = require('webpack-uglify-parallel');
const os = require('os');
// ... 其他配置
plugins: [
new UglifyJsparallelPlugin({
workers: os.cpus().length,
mangle: true,
compressor: {
warnings: false,
drop_console: true,
drop_debugger: true
}
})
]
  • 效果综述

经过以上优化,原本90+s的构建时间,优化到30+秒效果还是很明显的。原本HMR模式初始化需要50+秒,也优化到了20+秒的程度。不过,还是有一个至今我自己还无法解决的问题,那就是HMR模式,rebuild时间会越来越长,直到超过初始化的20+秒。此时只能重新开关一次HMR模式。这一点我还无法找到具体原因是什么。不过,至少生产的构建时间得到了60%的优化,效果还是挺好的。

来源:https://molunerfinn.com/Webpack-Optimize/

Vuejs项目的Webpack2构建优化的更多相关文章

  1. 【Devops】【docker】【CI/CD】Jenkins源码管理,设置gitlab上项目的clone地址 + jenkins构建报错:Please make sure you have the correct access rights and the repository exists.

    注意,如果 jenkins构建报错:Please make sure you have the correct access rights and the repository exists. 而此时 ...

  2. 学习笔记——Maven实战(三)多模块项目的POM重构

    重复,还是重复 程序员应该有狗一般的嗅觉,要能嗅到重复这一最常见的坏味道,不管重复披着怎样的外衣,一旦发现,都应该毫不留情地彻底地将其干掉.不要因为POM不是产品代码而纵容重复在这里发酵,例如这样一段 ...

  3. Maven实战(三)——多模块项目的POM重构

    在本专栏的上一篇文章POM重构之增还是删中.我们讨论了一些简单有用的POM重构技巧,包含重构的前提--持续集成,以及怎样通过加入或者删除内容来提高POM的可读性和构建的稳定性.但在实际的项目中,这些技 ...

  4. spring-boot项目的docker集成化部署(一)

    目录 spring-boot项目的docker集成化部署 前言 基本思路与方案 基本步骤 准备源码 服务器和基础环境 结语 1. 本文总结: 2. 后期优化: spring-boot项目的docker ...

  5. 搜刮一些开源项目的APP

    iOS完整App资源收集 <iOS完整app资源收集>  <GitHub 上有哪些完整的 iOS-App 源码值得参考?> <GitHub 上有哪些完整的 iOS-App ...

  6. 老项目的#iPhone6与iPhone6Plus适配#iOS8无法开启定位问题和#解决方案#

    本文永久地址为 http://www.cnblogs.com/ChenYilong/p/4020359.html,转载请注明出处. iOS8的定位和推送的访问都发生了变化, 下面是iOS7和iOS8申 ...

  7. 老项目的#iPhone6于iPhone6Plus适配#iPhone6分辨率与适配

    技术博客http://www.cnblogs.com/ChenYilong/    本文永久地址为http://www.cnblogs.com/ChenYilong/p/4011744.html ,转 ...

  8. 干货|人人都是翻译项目的Master

    在平时的工作中,我们都会经常查阅一些英文文档来解决平时遇到的问题和拓宽视野.看到好的文章或者书籍有没有想要和小伙伴分享的冲动,那么我们一起来翻译吧- 翻译主张 "信 达 雅" .& ...

  9. Python两步实现关联规则Apriori算法,参考机器学习实战,包括频繁项集的构建以及关联规则的挖掘

    .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...

随机推荐

  1. CoderForce 180D-Name (构造+回溯)

    题目大意:给两个字符串s,t,用s中的字符重新组合构造出按字典序最小的但比t大的新字符串. 题目分析:先统计s中各个字母出现的次数,然后从t的左端向右端依次构造出新串的每一位上的字母.这个过程我是用回 ...

  2. NOJ-1581 筷子 (线性DP)

    题目大意:有n支筷子,已知长度,定义一双筷子的质量等于长度的平方差,问能否分成k双?若能,输出所有筷子的最小质量和. 题目分析:先将筷子按长度从小到大排序,定义状态dp(i,j)表示将前 i 支筷子分 ...

  3. 一、重写(覆盖)override

    一.重写(覆盖)override 子类可以继承父类对象的方法,在继承后,重复提供该方法,就叫做方法的重写,又叫做覆盖override package property; //父类对象 public c ...

  4. iOS UI-常用控件

    #import "ViewController.h" @interface ViewController ()<UITextFieldDelegate> // 标题标签 ...

  5. js获取当前点击元素的索引

    以ul下的li元素为例:获取li的索引,代码如下: <ul id="list"> <li></li> <li></li> ...

  6. POJ 2488 DFS

    DES:给一个n行m列的棋盘.马以L型走.问能否从某一位置开始走完棋盘上的每个位置.若能继续输出字典序最小的一条路径. 很典型的dfs.搜的时候就按照字典序从小到大的顺序.搜到第一条路径时停止搜索输出 ...

  7. cas AssertionThreadLocalFilter

    AssertionThreadLocalFilter AssertionThreadLocalFilter作用很简单,就是将Assertion绑定到ThreadLocal. ThreadLocal 无 ...

  8. EntityFramework 6

    3.EF6 3.1初步目录及说明 下面是用VS2013开发环境创建的项目: 说明:控制台项目类型,安装 EF版本为6.1.3 , 数据库连接字符串配置: 隐藏代码 <connectionStri ...

  9. vue + element-ui Table的数据多选,多页选择数据回显,分页记录保存选中的数据。

    业务的需要:我要对与会人员勾选,记录所选的与会人员,并且点击到别的页面上时也要记录所勾选的.第一次尝试,每次点击下一页数据都会清空.然后我就去element ui官网查看了api.实现如下: 在tab ...

  10. STL_容器_删除

    1.erase()函数 用于删除由迭代器指定的元素,或者一个区间. 2.clear()函数 用于删除容器中所有元素 3.remove()函数 用于删除指定的元素,无需知道元素在容器的哪个位置,会删除掉 ...