1.前言
起初,这是很久之前优化的一个vue SPA项目,最近在总结时看到了记录的externals和Dll,
二者都是为了分离模块诞生,那么在效率上各自如何呢,还有分别适合于什么场景呢?
针对这两个疑惑,写下这篇笔记,顺便记录一下SPA项目的优化方案。
首先看数据
- 优化前
- 优化后
优化后数据如下
dom树构建完毕时间 | 页面完全加载时间 | 打包文件大小 | |
---|---|---|---|
优化前 | 1.79s | 2.01s | 1.15MB |
优化后 | 0.484s | 1.70s | 225.1KB |
注意:DOMContentLoaded事件,即dom树构建完毕,通常这时候我们已经能看到页面了,这个等待时间直接决定了用户的初体验,这里可以看到优化前后明显的速度提升。
2.开始优化
2.1. 第三方模块按需引入
取消全局引入,在组件中import {xxx, xxx} from 'XXXXXX';
2.2. 路由按需加载
export default new VueRouter({
routes: [
{
path: '/',
name: 'Home',
component: () => import('../pages/Home'),
},
...
]
})
2.3. 提取第三方模块
在我们的项目中,大部分依赖库是不会改变的
比如vue、Ant、echarts等等,
对于这些基本不会改变的模块,我们希望他们不要影响我们的打包时间和打包后文件的加载速度。
2.3.1 externals
官网解释:
防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
简单讲,就是把我们引入的三方模块声明为外部依赖,webpack不会对其进行打包,最后我们在打包的项目中通过script外链引入即可。
vue-cli 2.x的配置文件
- webpack.base.conf.js: 基础公共配置
- webpack.dev.conf.js: 开发环境配置
- webpack.prod.conf.js: 生产环境配置
我们在选择配置文件时,注意不要选择base和dev,因为我们的开发环境需要实时编译,如果这时候编译忽略了第三方包,那么程序在运行时就会报错,xxx is not defined,所以下面的操作我们都选择webpack.prod.conf.js
具体操作:
- 在webpack.prod.conf.js中声明externals
externals: {
'vue': 'Vue',
'echarts': 'echarts',
...
}
- 在打包后的dist/index.html中手动添加、或者写脚本(比如node)自动添加
<script src=...></script>
<script src=...></script>
<script src=...></script>
-
在webpack.prod.conf.js中声明libraryTarget
打包时,webpack不会将external中声明的第三方模块记录到chunk信息中,所以我们需要通过libraryTarget来告知打包后的文件,当读到了externals中的key时,需要以umd的方式去获取资源名,否则可能出现找不到module的问题。
output: {
libraryTarget: 'umd',
...
},
2.3.2 Dll
包括DllPlugin和DllReferencePlugin
官网解释:
使用于将项目依赖的基础模块(第三方模块)抽离出来,然后打包到一个个单独的动态链接库中。当下一次打包时,通过webpackReferencePlugin,如果打包过程中发现需要导入的模块存在于某个动态链接库中,就不能再次被打包,而是去动态链接库中get到。
简单讲,就是将第三依赖单独打包,通过引用 manifest.json来把依赖映射到模块上,相当于把原本的一个文件拆分成多个。
比如说,现在有一个1M的app.js文件,
<script src="/static/app.js"></script> <!-- 1M -->
优化后变成这种形式
<script src="/static/app.js"></script> <!-- 500KB -->
<script src="/static/dll.js"></script> <!-- 524KB -->
这样做有三个优点
- 异步加载文件,减少了页面的白屏时间
- Dll打包以后是独立存在的,只要其包含的库没有修改,其hash也不会变化,因此线上的dll文件基本不需要随着频繁更新。
- 如果多个项目使用了相同的依赖库,可以共用一个文件。
我们准备让优化后的文件结构如下
- 我们首先需要在项目的build目录下创建一个 webpack.dll.config.js 文件。然后配置代码如下:
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
vendor: [
"vue-router",
"vuex",
"echarts"
...
]
},
output: {
path: path.resolve('./dist/dll/'),
// publicPath: '.',
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve('./dist/dll/', '[name]-manifest.json'),
name: '[name]_library'
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}
- 然后在webpack.prod.conf.js使用
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: require('../dist/dll/vendor-manifest.json')
})
...
],
...
}
3.在package.json中添加打包命令
"scripts": {
...
"build:dll": "webpack --config build/webpack.dll.config.js",
},
4.在index.html中引入
<script src="./dll/vendor.dll.js"></script>
2.3.3 二者的对比与选取
-
打包效率
- externals:每次更新第三方模块时,只需修改webpack的externals属性,然后在打包以后的文件中引入外链。
- Dll:每次更新第三方模块时,需要修改webpack.dll.config.js,然后单独打包成dll。
- 结论:externals更方便些
-
项目的配置管理
- externals:上文的三个步骤
- Dll:修改配置文件、重新打包
- 结论:Dll更方便
-
页面效率
- 都是资源并行引入,没有本质区别,只是同一域名下的资源有并发数限制,而使用externals方案,唯一担心的问题是cdn服务器宕机了,所以可以考虑部署到自己专门的资源服务器上。
-
总结
- 二者看似各有千秋,但是考虑到 【项目的配置管理】 这一项我们只需配置一次,所以在二者皆可的其概况下,我们优先选用externals。
其他优化方案(探索中,后续记录)
-
公共组件上传npm
在项目中作为第三方模块引入,然后通过dll打包引入页面,以减少打包时间。
-
HappyPack开启多进程任务
因为nodeJs是单线程,非阻塞模型,所以可以开启多进程来提高效率,HappyPack就是利用这一点,可以大大缩短打包时间。