Webpack
也在不断的优化迭代;截至目前,已经更新至 v4.16.0;在Webpack4
这个版本,它在原有基础上,作了不少优化,也引入了颇多的新特性。在新的版本中,将得到更多模块类型及对.mjs
的支持,更好的默认值、更为简洁的模式设置、更加智能的来分割Chunk
,还新增的splitChunks
来自定义分割代码块,诸此等等。在升级至新版Webpack
的项目中,在包的构建速度
、代码块体积&数量
、以及运行效率
,都会有一个质的飞跃。javascript
所以面对Webpack4
优越的功能,将本地项目中从原先的2.7.0
一步到位升级至4.16.0
,而且相关依赖包以及配置文件须要作相应的修改。css
再也不支持Node4
,建议使用高版本node
,如下作升级使用的是node v8.11.1
和npm v5.6.0
html
webpack 4以前,js 是 webpack 中的惟一模块类型,于是不能有效地打包其它类型的文件。而 webpack 4 则提供了 5 种模块类型:前端
此外,webpack 4 中会默认解析 .wasm, .mjs, .js 和 .json 为后缀的文件。vue
升级完webpack4
而后直接运行项目打包命令npm run build
,会提示你须要安装webpack-cli/webpack-command
,能够根据本身的须要选择安装,本人选择的是webpack-cli
。java
webpack4
默认是经过mode
来设置是生产环境
仍是开发环境
,因此须要在webpack.dev.conf.js
和webpack.prod.conf.js
增长相应的mode
配置项,而且删除以前设置环境变量的代码process.env.NODE_ENV = 'production'
以及插件配置中设置环境变量的方法,片断代码以下:node
// webpack.dev.conf.js
module.exports = merge(baseWebpackConfig, {
mode: 'development',
// 省略
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
]
}
// webpack.prod.conf.js
var webpackConfig = merge(baseWebpackConfig, {
mode: 'production',
// 省略
plugins: [
new webpack.DefinePlugin({
'process.env': env
}),
]
}
复制代码
注意:new webpack.DefinePlugin是保证浏览器脚本中可以访问
process.env
变量,以便作相应的逻辑操做webpack
development 模式:git
production 模式:github
由于extract-text-webpack-plugin
的最新正式版尚未对webpack4.x进行支持,即便是使用extract-text-webpack-plugin@next
版本依然会出现报contenthash
错误,因此仍是建议使用mini-css-extract-plugin
,固然这也是官方推荐的。
主要须要修改webpack.prod.conf.js
中的插件配置以及loaders
加载的工具函数utils.js
,修改片断代码以下:
// webpack.dev.conf.js
module.exports = merge(baseWebpackConfig, {
// 省略
plugins: [
new MiniCssExtractPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
]
}
// utils.js
if (options.extract) {
return [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../../'
}
}
].concat(loaders)
} else {
return ['vue-style-loader'].concat(loaders)
}
复制代码
注意:其中
utils.js
中配置publicPath
主要解决css中引用图片出现路径错误问题。
再次运行相关打包命令你会发现有以下提示错误,片断代码以下:
Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead.
at Object.get [as CommonsChunkPlugin] (/data/test/node_modules/webpack/lib/webpack.js:159:10)
复制代码
主要是由于webpack4
中删除了webpack.optimize.CommonsChunkPlugin
,而且使用optimization
中的splitChunk
来替代
主要须要修改webpack.prod.conf.js
文件,而且删除全部webpack.optimize.CommonsChunkPlugin
相关代码,片断代码以下:
var webpackConfig = merge(baseWebpackConfig, {
mode: 'production',
entry: {
charts: ['echarts'],
vendors: ['vue', 'vuex', 'vue-router', 'moment'],
iconfonts: ['ga-iconfont']
},
// 省略
optimization: {
// minimizer: true, // [new UglifyJsPlugin({...})]
providedExports: true,
usedExports: true,
//识别package.json中的sideEffects以剔除无用的模块,用来作tree-shake
//依赖于optimization.providedExports和optimization.usedExports
sideEffects: true,
//取代 new webpack.optimize.ModuleConcatenationPlugin()
concatenateModules: true,
//取代 new webpack.NoEmitOnErrorsPlugin(),编译错误时不打印输出资源。
noEmitOnErrors: true,
splitChunks: {
// maxAsyncRequests: 1, // 最大异步请求数, 默认1
// maxInitialRequests: 1, // 最大初始化请求数,默认1
cacheGroups: {
// 抽离第三方插件
commons: {
// test: path.resolve(__dirname, '../node_modules'),
chunks: 'all',
minChunks: 2,
maxInitialRequests: 5, // The default limit is too small to showcase the effect
minSize: 0, // This is example is too small to create commons chunks
name: 'common'
}
}
},
}
复制代码
test
主要是经过正则来匹配entry
中配置第三方库,固然这里也能够写成path.resolve(__dirname, '../node_modules')
来匹配项目中node_modules
引入的库文件。
chunks
形式有三种取值(若是配置了entry
,那么默认从入口文件中抽离,若是没有配置entry
配置了test
,默认按照test
中的正则去匹配)我的比较推荐使用all
或者async
:
当取值all
的时候,效果是不论是异步仍是同步,都会将入口entry
配置的包公共部分抽离出来,好处就是其余文件很小,公共文件会只加载一次,不优雅的就是若是enrty
配置的包过多会致使一个文件很大。效果基本以下:
当取值async
的时候,效果是将入口entry
配置的包抽离异步的公共部分,主要是看entry
中包的引入方式是否是异步的。效果基本以下:
当取值initial
的时候,其实效果不如all
和async
,就是在初始化的时候,将每一个页面涉及到的包从各自页面中的js中抽离出来,而且会根据页面加载这些分离出来的js文件,对于各页面公共的js会打包多份。效果基本以下:
sideEffects
开启时能够剔除无用的模块,用来作tree-shake
。当模块的package.json
中添加该字段时,代表该模块没有反作用,也就意味着webpack
能够安全地清除被用于重复导出(re-exports)的代码。
concatenateModules
取代了webpack.optimize.ModuleConcatenationPlugin()
插件
noEmitOnErrors
取代了new webpack.NoEmitOnErrorsPlugin()
插件。
minChunks
是split
前,有共享模块的chunks
的最小数目 ,默认值是1,但示例里的代码在default
里把它重写成2了,从常理上讲,minChunks = 2
应该是一个比较合理的选择吧
注意:
webpack.optimize.UglifyJsPlugin
如今也不须要了,只须要使用optimization.minimize
为true
就行,production mode
下面自动为true
,固然若是想使用第三方的压缩插件也能够在optimization.minimizer
的数组列表中进行配置
建议升级至最新版本@4.0.0-alpha
,这里须要把默认的chunksSortMode: dependency
删除,主要是由于webpack4
已经删除相关的CommonsChunkPlugin
API了。
其实这个能够不须要升级,可是若是升级至15.x版本以上,在使用中须要执行VueLoaderPlugin
插件方法,其余用法跟以前保持一致,片断代码以下:
// webpack.prod.conf.js
const { VueLoaderPlugin } = require('vue-loader')
// 省略
plugins: [
new VueLoaderPlugin(),
]
复制代码
webpack4
会默认提示须要开启sourceMap
,所以只要在相关loader
配置中的options
配置sourceMap:true
便可。
建议把相关loader统一作一次升级,基本升级以下:
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
baseWebpackConfig.output.chunkFilename = '[name].[chunkhash].js'; // 路由js命名 这个拆分路由 模块依赖脚本文件
module.exports = merge(baseWebpackConfig, {
mode: 'development',
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
devtool: '#cheap-module-eval-source-map',
optimization: {
// minimizer: true,
providedExports: true,
usedExports: true,
//识别package.json中的sideEffects以剔除无用的模块,用来作tree-shake
//依赖于optimization.providedExports和optimization.usedExports
sideEffects: true,
//取代 new webpack.optimize.ModuleConcatenationPlugin()
concatenateModules: true,
//取代 new webpack.NoEmitOnErrorsPlugin(),编译错误时不打印输出资源。
noEmitOnErrors: true,
splitChunks: {
chunks: 'initial', //'all'|'async'|'initial'(所有|按需加载|初始加载)的chunks
},
//提取webpack运行时的代码
runtimeChunk: {
name: 'manifest'
}
},
plugins: [
new VueLoaderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})
复制代码
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')
var webpackConfig = merge(baseWebpackConfig, {
mode: 'production',
entry: {
charts: ['echarts'],
vendors: ['vue', 'vuex', 'vue-router', 'moment'],
iconfonts: ['ga-iconfont']
},
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js'),
publicPath: './'
},
optimization: {
// minimizer: true,
providedExports: true,
usedExports: true,
//识别package.json中的sideEffects以剔除无用的模块,用来作tree-shake
//依赖于optimization.providedExports和optimization.usedExports
sideEffects: true,
//取代 new webpack.optimize.ModuleConcatenationPlugin()
concatenateModules: true,
//取代 new webpack.NoEmitOnErrorsPlugin(),编译错误时不打印输出资源。
noEmitOnErrors: true,
splitChunks: {
// maxAsyncRequests: 1, // 最大异步请求数, 默认1
// maxInitialRequests: 1, // 最大初始化请求书,默认1
cacheGroups: {
// test: path.resolve(__dirname, '../node_modules'),
commons: {
chunks: 'all',
minChunks: 2,
maxInitialRequests: 5, // The default limit is too small to showcase the effect
minSize: 0, // This is example is too small to create commons chunks
name: 'common'
}
}
},
//提取webpack运行时的代码
runtimeChunk: {
name: 'manifest'
}
},
plugins: [
new VueLoaderPlugin(),
// 解决moment语言包问题
new webpack.ContextReplacementPlugin(
/moment[\\\/]locale$/,
/^\.\/(zh-cn)$/
),
new MiniCssExtractPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true,
discardComments: { removeAll: true }
}
}),
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
hash:true,// 防止缓存
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
}),
new webpack.HashedModuleIdsPlugin(),
new CopyWebpackPlugin([{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}])
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
复制代码
若是在项目中使用了preload-webpack-plugin
插件,必须升级至3.0.0-beta.1
版本,能够运行如下命令:
npm i preload-webpack-plugin@next -D
复制代码
同时须要把html-webpack-plugin
插件版本回退到3.2.0
才行,而后在配置文件按照如下前后顺序添加,片断代码以下:
// 省略
plugins: [
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
}),
new PreloadWebpackPlugin({
rel: 'prefetch',
}),
new PreloadWebpackPlugin({
rel: 'preload'
}),
// 省略
]
复制代码
升级webpack4
以后,在dev
环境下,你会发现修改任何代码会致使整个网页刷新,并且会报cb is not a function
,形成这个缘由是html-webpack-plugin-after-emit
插件针对高版本的webpack4
和html-webpack-plugin3.2.0
已经被弃用了,暂时没找到替代的插件,能够暂时先注释掉这段代码,代码在build/dev-server.js
中,片断代码以下:
compiler.plugin('compilation', function(compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function(data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
复制代码
按照以上修改基本能够完成webpack4
的升级,升级完以后,我的感受配置更加简单,删除了之前不少繁琐的插件配置,不少功能webpack4
默认就是自带,通过测试打包速度有了50%多的提高,修改以前打包时间为143894ms
左右,升级完以后,用时基本在58080ms
左右,效果基本以下:
再次打包的话,用时基本在27534ms
左右,效果基本以下:
上文介绍了如何在optimization
经过定义配合entry
进行打包,若是按照以上配置最终打包的确会生成charts
、vendors
、iconfonts
三个js文件,可是对于js大小会有所怀疑,由于大小基本在199 bytes
如下,这彷佛有点奇怪,直接打开这三个js看看,代码以下:
// charts.js
(window.webpackJsonp=window.webpackJsonp||[]).push([[24],{21:function(n,o,p){n.exports=p("K8M1")}},[[21,1,0]]]);
//# sourceMappingURL=charts.2e5cbbfa2a894d2bb5aa.js.map
// iconfonts.js
(window.webpackJsonp=window.webpackJsonp||[]).push([[22],{19:function(n,o,p){n.exports=p("t+cQ")}},[[19,1,0]]]);
//# sourceMappingURL=iconfonts.e90fd0507d501ef81b69.js.map
// vendors.js
(window.webpackJsonp=window.webpackJsonp||[]).push([[23],{20:function(n,o,w){w("oCYn"),w("L2JU"),w("jE9Z"),n.exports=w("wd/R")}},[[20,1,0]]]);
//# sourceMappingURL=vendors.5c535f00ba89522ba93b.js.map
复制代码
其实这三段js都被一块儿打成common.js
了,因此从项目加载资源的角度来讲,以上这三段彷佛是多余js,那么该如何删除这没用的js呢,其实能够把entry
配置的代码所有注释掉,基本以下:
entry: {
// charts: ['echarts'],
// vendors: ['vue', 'vuex', 'vue-router', 'moment'],
// iconfonts: ['ga-iconfont']
}
复制代码
按照以上修改,就能少打包三个彷佛没用的js,这个问题感谢咱们前端组的小伙伴发现的。
那么问题来了,如何才能按照入口文件配置的那样打成三个js包呢,能够遵循如下配置,片断代码以下:
// 前提是不注释entry中的代码
// 省略
optimization: {
// 省略
splitChunks: {
cacheGroups: {
charts: {
chunks: 'async',
minChunks: 2,
maxInitialRequests: 5,
minSize: 0,
name: 'charts'
},
vendors: {
chunks: 'async',
minChunks: 2,
maxInitialRequests: 5,
minSize: 0,
name: 'vendors'
},
iconfonts: {
chunks: 'async',
minChunks: 2,
maxInitialRequests: 5,
minSize: 0,
name: 'iconfonts'
}
}
},
// 省略
}
// 省略
复制代码
重点看cacheGroups
里面的配置,将原先的commons改为了三个了,同时chunks
须要改为async
,按照这样打包结果基本以下:
如下测试速度的项目页面数是26个页面,十多个业务组件,一整套组内开发的ui组件库,以及多个第三方库。实际时间会受各自项目文件多少影响。
使用HappyPack
插件可以提升打包编译速度,能够参照如下修改,基本修改以下:
// webpack.base.conf.js
// 在rules中的babel-loader改用happypack中的loader
// 省略
module: {
rules: [{
test: /\.js$/,
loader: 'happypack/loader', // 增长新的HappyPack构建loader
include: [resolve('src')],
exclude: /node_modules/,
options: {
sourceMap: true,
}
}
}
// 省略
// webpack.prod.conf.js
// 省略
plugins: [
new HappyPack({
loaders: [{
loader: 'babel-loader',
options: {
babelrc: true,
cacheDirectory: true
}
}],
threadPool: happyThreadPool
})
]
// 省略
复制代码
如下是未修改以前打包的所需的时间是26831ms
:
修改以后打包速度是20387ms
,相比以前减小了近6.5s
左右:
经过设置babel-loader
中的cacheDirectory
属性也能提升编译速度,网上不少都是以下设置,可是会报错,片断代码以下:
// 这是错误用法,我实测发现报错
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory=true', // 或者loader: 'babel-loader?cacheDirectory'
include: [resolve('src')],
exclude: /node_modules/,
options: {
sourceMap: true,
}
}
复制代码
其实能够把cacheDirectory
看成一个属性配置在options
中,基本代码以下:
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src')],
exclude: /node_modules/,
options: {
sourceMap: true,
cacheDirectory: true
}
}
复制代码
增长如上修改,打包速度是20593ms
,彷佛跟以前没有多大变化:
最终通过测试,对于本人项目而言,HappyPack
和cacheDirectory
效果并不能叠加,使用任意其一,均可以能达到20s
全部的时间。
以上就是所有内容,若是有什么不对的地方,欢迎提issues