项目在发布测试和生产环境前需要打包,而webpack默认使用commonChunkPlugin,该插件虽然可以将第三方库提取到vendor中,但每次打包时都会重新被打包一次,而这些第三方库一般是不会产生变化的,这也就意味着每次打包时都会花费一部分时间来生成相同的vendor.js文件,这无异于是一种资源浪费。
好在webpack中提供了DllPlugin和DllReferencePlugin来提升打包效率。
我们先来看package.json中"dependencies"
部分的组件[1]:
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuex": "^3.1.1"
},
dependencies
中第三方插件没有变化的话,使用commonChunkPlugin插件打包出来的vendor.js文件基本不会有变化,我们完全可以把此处使用到的代码提取出来,这样打包就能减少很多时间。
在此之前,我们先执行下npm run build
看看打包出的文件大小和时长。
可以看出,使用commonChunkPlugin打包项目代码花了大概8.3s。
接下来我们对项目进行改造,使用DllPlugin和DllReferencePlugin。
在build
目录下新建webpack.dll.conf.js
文件,里面内容如下:
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
vendor: [
'vue/dist/vue.esm.js',
'vuex/dist/vuex.esm.js',
'vue-router/dist/vue-router.esm.js',
'axios',
]
},
output: {
path: path.resolve('./static/js'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DllPlugin({
path: path.resolve('.', '[name]-manifest.json'),
name: '[name]_library'
})
]
}
细心的朋友可能会发现entry中引用vue、vuex等插件引用的都是esm版本,这是因为像import Vue from 'vue'
这类import引用的就是vue/dist/vue.esm.js
文件,但此文件中若直接引用vue
的话无法被正确映射为vue.esm.js
,所以此处做手动指定处理。如果有朋友最后发现项目代码莫名的比改造前大了许多,可以回过头来看此处,看看是不是因为第三方插件引用版本不统一导致打包两个版本代码到同一项目中了。
package.json文件添加npm run build:dll
命令如下:
"build:dll": "webpack --config build/webpack.dll.conf.js"
运行npm run build:dll
命令结果如下:
可以看到,static文件夹下的js文件夹有个vendor.dll.js文件,根目录下有一个vendor-mainfest.json文件。
不过我们发现这个vendor.dll.js似乎有些大,482kb,这大小后期在添加提取几个第三方插件的话这个文件岂不是轻轻松松的有几兆大小。我们都知道正常情况下发布生产环境的代码都是经过压缩的,我们再次也可以对代码进行压缩。
参照webpack.prod.conf.js
,使用UglifyJsPlugin
对代码进行压缩,此时webpack.dll.conf.js
的内容如下:
const path = require('path')
const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const env = require('../config/prod.env')
module.exports = {
entry: {
vendor: [
'vue/dist/vue.esm.js',
'vuex/dist/vuex.esm.js',
'vue-router/dist/vue-router.esm.js',
'axios',
]
},
output: {
path: path.resolve('./static/js'),
filename: '[name].dll.js',
library: '[name]_library'
},
plugins: [
new webpack.DefinePlugin({
'process.env': env
}),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: false,
parallel: true
}),
new webpack.DllPlugin({
path: path.resolve('.', '[name]-manifest.json'),
name: '[name]_library'
})
]
}
再次执行npm run build:dll
后发现,新生成的vendor.dll.js的大小仅为146kb,这个算是正常大小了。
接下来,开始改造打包生产环境代码用的webpack.prod.conf.js
文件,将CommonChunkPlugin生成vendor.js和manifest.js文件的配置注释或者删除。
// new webpack.optimize.CommonsChunkPlugin({
// name: 'vendor',
// minChunks (module) {
// // any required modules inside node_modules are extracted to vendor
// return (
// module.resource &&
// /\.js$/.test(module.resource) &&
// module.resource.indexOf(
// path.join(__dirname, '../node_modules')
// ) === 0
// )
// }
// }),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'manifest',
// minChunks: Infinity
// }),
然后添加plugins:
new webpack.DllReferencePlugin({
manifest: require(path.resolve(__dirname, '../vendor-manifest.json'))
}),
index.html模板添加script标签,引入vendor.dll.js文件,具体位置如下:
<div id="app"></div>
<!-- built files will be auto injected -->
<script type='text/javascript' src='./static/js/vendor.dll.js'></script>
执行npm run build
后发现,此次打包的时间只花了大概4.5s,比之前几乎少了一半,且生成的文件大小都很正常。
dist目录下开启静态资源服务,如http-server,访问后发现正常。
至此,将第三方插件提取出来防止重复打包的目的算是圆满达成。
但细想一下,还是存在一个小问题,那就是开发环境访问项目的话也会存在一个引用vendor.dll.js的script标签,我们应该只在构建生产环境的HTML时才需要引入此资源。
解决这个问题的方法有很多种,下面介绍几种:
- 使用ejs模板语法。
执行npm install -S -D ejs
安装ejs,然后将index.html
的扩展名改为.ejs
。
打开webpack.prod.conf.js
,找到HtmlWebpackPlugin
的配置项,添加自定义参数insertJs: ['/static/js/vendor.dll.js']
。
然后对index.ejs做如下处理:new HtmlWebpackPlugin({ filename: config.build.index, template: 'index.ejs', inject: true, insertJs: ['/static/js/vendor.dll.js'], minify: { removeComments: true,
再执行<div id="app"></div> <!-- built files will be auto injected --> <script type='text/javascript' src='./static/js/vendor.dll.js'></script> <% if (htmlWebpackPlugin.options.insertJs && htmlWebpackPlugin.options.insertJs.length > 0) { %> <% htmlWebpackPlugin.options.insertJs.forEach(function(item){ %> <script type='text/javascript' src='<%= item %>'></script> <% }); %> <% } %>
npm run build
会发现生产环境index.html中成功的引入了vendor.dll.js,而开发环境并没有此js。
此方法其实可以引申出其他的一些用法,比如说区分环境加载cdn资源。 - 使用插件add-asset-html-webpack-plugin(NOTE: This plugin requires html-webpack-plugin@^2.10.0)
webpack3.0版本的环境貌似用这个插件老是报错,原因未知,具体使用方法可以参照add-asset-html-webpack-plugin
再次强调一下,有些朋友可能做到最后发现vendor.dll.js中指定的第三方插件是被打包进来了,但这些插件在改造后的npm run build
下,又一遍被打包进了app.js之类的文件中。为了清楚此类情况,需要仔细排查项目中对第三方插件引用的版本是否统一。
-
为了突出改造前后的对比效果,项目暂且只安装这几个插件。 ↩