前言:由于項(xiàng)目是基于vue-cli2搭建的,使用的是webpack3.x版本。隨著時(shí)間的遷移,打包的速度越來(lái)越慢,痛下決定將webpack升級(jí)到4.0版本。
安裝依賴
首先先貼出升級(jí)完成后的package.json
"devDependencies": {
"assets-webpack-plugin": "^3.9.10",
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^7.1.4",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.7.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"chalk": "^2.0.1",
"copy-webpack-plugin": "^5.0.4",
"css-loader": "^3.2.0",
"file-loader": "^4.2.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0",
"kity": "^2.0.4",
"less": "^3.0.1",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.8.0",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^3.0.0",
"postcss-url": "^7.2.1",
"progress-bar-webpack-plugin": "^1.11.0",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^2.1.0",
"vue-loader": "^15.7.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.5.21",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.8",
"webpack-dev-middleware": "^3.7.1",
"webpack-dev-server": "^3.8.0",
"webpack-merge": "^4.2.2",
},
第一步
npm i -D webpack@4.39.0
出現(xiàn)報(bào)錯(cuò):connot find module 'webpack/bin/config-yargs'

報(bào)錯(cuò)原因:提示缺少依賴
webpack-cli解決方案:安裝依賴
npm i -D webpack-cli@3.3.8
第二步
在安裝完webpack-cli之后,在繼續(xù)npm run dev
出現(xiàn)報(bào)錯(cuò):
var outputName = compilation.mainTemplate.applyPluginsWaterfall('asset-path', outputOptions.filename, {
^
TypeEorror: compilation.mainTemplate.applyPluginsWaterfall is not function

報(bào)錯(cuò)原因:
html-webpack-plugin版本過(guò)低解決方案:安裝依賴
npm i -D html-webpack-plugin@3.2.0
第三步
在升級(jí)完html-webpack-plugin之后,在繼續(xù)npm run dev
出現(xiàn)報(bào)錯(cuò): Module build failed (from ./node_module/vue-loader/index.js):
TypeError: Connot read property 'vue' of undefined.

根據(jù)上面描述,很明顯是vue-loader的依賴出了問(wèn)題,想要了解詳情的請(qǐng)點(diǎn)擊從v14遷移 | vue-loader
報(bào)錯(cuò)原因:
vue-loader版本過(guò)低解決方案:
安裝依賴
npm i -D vue-loader@15.7.1在
webpack.base.conf.js下修改-- const vueLoaderConfig = require('./vue-loader.conf')
-- options: vueLoaderConfig
++ const { VueLoaderPlugin } = require('vue-loader')
++ new VueLoaderPlugin()
具體操作
//const vueLoaderConfig = require('./vue-loader.conf')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
...,
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
// options: vueLoaderConfig
},
]
},
plugins: [
new VueLoaderPlugin()
]
}
第四步
升級(jí)完后繼續(xù)dev
出現(xiàn)報(bào)錯(cuò):
Module parse failed: Unexpected token
File was process with loaders:
*./node_modules/vue-loaders/lib/index.js
You may need an additional loader to handle the result of these loaders.
.ht-Head-Search{
...
}

這時(shí)候會(huì)感到困惑,我剛剛升級(jí)了
vue-loader到v15版本,怎么還是提示vue-loader的錯(cuò)誤,難道是升級(jí)的方式不對(duì)?繼續(xù)往下看,發(fā)現(xiàn)多了點(diǎn)
css樣式的提示,明白了可能是css-loader和vue-style-loader也需要跟著升級(jí)報(bào)錯(cuò)原因:
css-loader,vue-style-loader版本過(guò)低解決方案:安裝依賴
npm i -D css-loader@3.2.0 vue-style-loader@4.1.2
第五步
升級(jí)是考驗(yàn)?zāi)托牡氖虑?,不著急一個(gè)坑一個(gè)坑的解決,在升級(jí)完css-loader和vue-style-loade后,繼續(xù)dev
出現(xiàn)報(bào)錯(cuò):
Module Warning (from ./node_module/postcss-loader/lib/index):
(Emitted value instead of an instace of Error)
PostCSS Loader
Previous source map found,but options.sourceMap isn't set.

報(bào)錯(cuò)原因:
postcss-loader版本過(guò)低解決方案:安裝依賴
npm i -D postcss-loader@3.0.0
解決development環(huán)境下的配置問(wèn)題
首先在webpack.dev.conf.js 文件下添加mode: 'development',
然后移除NamedModulesPlugin,NoEmitOnErrorsPlugin插件,webpack4已修改為內(nèi)置插件
module.exports = {
...,
mode: 'development,
...,
plugins: {
//new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
//new webpack.NoEmitOnErrorsPlugin(),
}
}
在這里終于看到了我們熟悉的localhost:8080,說(shuō)明我們的項(xiàng)目可以成功的在dev環(huán)境下運(yùn)行了,心想升級(jí)到webpack4,也是挺簡(jiǎn)單的嘛,迫不及待的在網(wǎng)頁(yè)打開(kāi)地址,想和項(xiàng)目進(jìn)一步的親近的時(shí)候。卻在控制臺(tái)看見(jiàn)了報(bào)錯(cuò),不說(shuō)了重新打開(kāi)編譯繼續(xù)回到正題。
出現(xiàn)報(bào)錯(cuò):
Uncaught TypeError:Connot assign to read only property 'exports' of object '#<Object>'
at Module.eval (BaseClient.js)
at Module../node_module/webpack-dev-srever/client/clients/BaseClient.js

報(bào)錯(cuò)原因:說(shuō)是
es6的import 和es5的module.export不能一起使用,項(xiàng)目全局查詢 module.export發(fā)現(xiàn)沒(méi)沒(méi)有一起使用,排除掉這個(gè)可能,真相只有一個(gè),那就是配置出了問(wèn)題解決方案:
一共有兩種解決方案
- 第一種方法 移除
resolve('node_modules/webpack-dev-server/client')
webpack.base.conf.js
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader'
// include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
include: [resolve('src'),resolve('test')],
exclude: /node_modules/
},
]
}
}
- 第二種方法 移除
transform-runtime
.babelrc
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
// "plugins": ["transform-vue-jsx", "transform-runtime"]
"plugins": ["transform-vue-jsx"]
}
重新dev,打開(kāi)控制臺(tái)沒(méi)有報(bào)錯(cuò),在這里,終于可以和項(xiàng)目來(lái)一波深入親近了,一波功能亂點(diǎn)確認(rèn)無(wú)可疑報(bào)錯(cuò),開(kāi)始幻想如何build,生成我們最愛(ài)的dist文件
解決production環(huán)境下配置問(wèn)題
第一步
首先在webpack.prod.conf.js文件下,添加mode: 'production'
const webpackConfig = merge(baseWebpackConfig, {
...,
mode: 'production'
})
然后選擇build
果然,build的過(guò)程沒(méi)有想象的那么一帆風(fēng)順,通過(guò)npm run build,
項(xiàng)目還未跑動(dòng)就被扼殺在搖籃
出現(xiàn)報(bào)錯(cuò):
thorw new RemovedPluginError(errorMessage);
Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead.

報(bào)錯(cuò)原因:
webpack4移除了
webpack.optimize.CommonsChunkPlugin解決方案:
在
webpack.prod.conf.js文件下將以下方法刪除
- webpack.optimize.CommonsChunkPlugin
- webpack.optimize.CommonsChunkPlugin
- webpack.optimize.CommonsChunkPlugin
- webpack.optimize.ModuleConcatenationPlugin
- OptimizeCSSPlugin
- UglifyJsPlugin
const webpackConfig = merge(baseWebpackConfig, {
...,
plugins: [
//new webpack.optimize.CommonsChunkPlugin(),
//new webpack.optimize.CommonsChunkPlugin({}),
//new webpack.optimize.CommonsChunkPlugin({}),
//new webpack.optimize.ModuleConcatenationPlugin(),
//new OptimizeCSSPlugin({}),
//new UglifyJsPlugin({})
],
})
然后在webpack.prod.conf.js文件下添加以下代碼:
const webpackConfig = merge(baseWebpackConfig, {
...,
optimization: {
//取代 new UglifyJsPlugin
minimizer: [
// 壓縮代碼
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
drop_debugger: true,//關(guān)閉debug
drop_console: true,//關(guān)閉console
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
// 可自己配置,建議第一次升級(jí)先不配置
new OptimizeCSSPlugin({
// cssProcessorOptions: config.build.productionSourceMap
// ? {safe: true, map: {inline: false}, autoprefixer: false}
// : {safe: true}
}),
],
// 識(shí)別package.json中的sideEffects以剔除無(wú)用的模塊,用來(lái)做tree-shake
// 依賴于optimization.providedExports和optimization.usedExports
sideEffects: true,
// 取代 new webpack.optimize.ModuleConcatenationPlugin()
concatenateModules: true,
// 取代 new webpack.NoEmitOnErrorsPlugin(),編譯錯(cuò)誤時(shí)不打印輸出資源。
noEmitOnErrors: true,
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
chunks: 'initial',
name: 'vendors',
},
'async-vendors': {
test: /[\\/]node_modules[\\/]/,
minChunks: 2,
chunks: 'async',
name: 'async-vendors'
}
}
},
runtimeChunk: { name: 'runtime' }
},
})
第二步
上面操作主要是廢除和遷移了一些插件,由于webpack4將其內(nèi)置,所以無(wú)需在plugins下進(jìn)行實(shí)例。
繼續(xù)build
出現(xiàn)報(bào)錯(cuò):
Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint insted
at .../node_module/extract-text-webpack-plugin/dist/index.js

報(bào)錯(cuò)原因:
extract-text-webpack-plugin:3.0.0并不兼容webpack4解決方案:
官方推薦使用
mini-css-extract-plugin來(lái)替換extract-text-webpack-plugin,另一種方法是升級(jí)extract-text-webpack-plugin至xtract-text-webpack-plugin@4.0.0-beta.0安裝依賴
npm i -D mini-css-extract-plugin@0.8.0安裝完成之后,打開(kāi)
utils.js 和 webpack.prod.conf.js文件
utils.js
// const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
exports.cssLoaders = function (options) {
...
// Extract CSS when that option is specified
// (which is the case during production build)
// if (options.extract) {
// return ExtractTextPlugin.extract({
// use: loaders,
// fallback: 'vue-style-loader',
// publicPath: '../../', //注意: 此處根據(jù)路徑, 自動(dòng)更改,添加publicPath,可以在css中使用背景圖
// })
// } else {
// return ['vue-style-loader'].concat(loaders)
// }
return [
options.extract ? MiniCssExtractPlugin.loader : 'vue-style-loader',
].concat(loaders)
}
-
webpack.prod.conf.js
添加mini-css-extract-plugin,移除ExtractTextPlugin
//const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const webpackConfig = merge(baseWebpackConfig, {
...,
plugins: [
new MiniCssExtractPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
allChunks: true,
}),
// new ExtractTextPlugin({
// filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
// allChunks: true,
// }),
]
})
在build,終于看到可愛(ài)的 dist文件了,吃了dev的教訓(xùn),首先我們還是檢查下dist,是否正常的,要是隔壁老王打出了包豈不是一片青青草原。
打開(kāi)網(wǎng)頁(yè),發(fā)現(xiàn)所有的背景圖全部失效,找到utils.js下的配置看看有什么不對(duì)的地方。
發(fā)現(xiàn)webpack3中的設(shè)置
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader',
publicPath: '../../', //注意: 此處根據(jù)路徑, 自動(dòng)更改,添加publicPath,可以在css中使用背景圖
})
} else {
return ['vue-style-loader'].concat(loaders)
}
webpack4中的設(shè)置
return [
options.extract ? MiniCssExtractPlugin.loader : 'vue-style-loader',
].concat(loaders)
這明顯不對(duì),人家webpack3有配置背景圖路徑,咱webpack4可不能丟下,修改配置如下即可
return [
options.extract ? {loader:MiniCssExtractPlugin.loader, options: {publicPath: '../../'}} : 'vue-style-loader',
].concat(loaders)
如果有本文章沒(méi)有出現(xiàn)的報(bào)錯(cuò),請(qǐng)下面留言我會(huì)試著幫你解決?。?!
如果文章對(duì)你有所幫助,請(qǐng)留下你的小愛(ài)心
就這樣這次webpack3遷移到webpack4的踏坑之旅快走到尾聲了,不過(guò)這里還有一些bug不一定發(fā)生,我簡(jiǎn)單列出來(lái)。
拓展-(可能你的項(xiàng)目還有其他報(bào)錯(cuò))
- 如果你的項(xiàng)目中使用了
web worker,那么你在升級(jí)到webpack4運(yùn)行dev的時(shí)候,會(huì)出現(xiàn)報(bào)錯(cuò),如下:
Uncaught ReferenceError: window is not defined
其實(shí)在worker-loader插件的issues已經(jīng)有了解決辦法,想要了解更多,請(qǐng)戳我
解決辦法: 在webpack.base.conf.js文件下加入globalObject: 'this'
module.exports = {
...
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath,
globalObject: 'this'
}
}
2.如果你的項(xiàng)目出現(xiàn)這個(gè)錯(cuò)誤
Vue packages version mismatch:
- vue@2.5.21
- vue-template-compiler@2.5.16
This may cause things to work incorrectly. Make sure to use the same version for both.
If you are using vue-loader@>=10.0, simply update vue-template-compiler.
If you are using vue-loader@<10.0 or vueify, re-installing vue-loader/vueify should bump vue-template-compiler to the latest.
@ ./resources/assets/js/app.js 12:13-46
@ multi ./resources/assets/js/app.js ./resources/assets/sass/app.scss
ERROR in ./resources/assets/js/components/steps.vue
Module build failed (from ./node_modules/vue-loader/lib/index.js):
TypeError: Cannot read property 'parseComponent' of undefined
不要擔(dān)心,這只是你的vue版本和vue-template-compiler不一致導(dǎo)致的,
統(tǒng)一下兩個(gè)的版本就可以了
- 如果你的項(xiàng)目使用了
eslint報(bào)錯(cuò)
TypeError: Cannot read property 'eslint' of undefined
請(qǐng)升級(jí)npm i -D eslint-loader@2.2.1
打包優(yōu)化
一.使用happypack多線程打包
1.安裝依賴npm i -D happypack@5.0.1
2.導(dǎo)入插件
webpack.base.conf.js
const HappyPack = require('happypack')
const os = require('os')
// 創(chuàng)建 happypack 共享進(jìn)程池
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().length})
module.exports = {
...
module: {
rules: [
...
{
test: /\.js$/,
//loader: 'babel-loader',
use: ['happypack/loader?id=babel'],
// include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
include: [resolve('src'),resolve('test')],
exclude: /node_modules/
}
]
},
plugins: [
new HappyPack({
/*
* 必須配置項(xiàng)
*/
// id 標(biāo)識(shí)符,要和 rules 中指定的 id 對(duì)應(yīng)起來(lái)
id: 'babel',
// 需要使用的 loader,用法和 rules 中 Loader 配置一樣
// 可以直接是字符串,也可以是對(duì)象形式
loaders: ['babel-loader?cacheDirectory'],
// 使用共享進(jìn)程池中的進(jìn)程處理任務(wù)
threadPool: happyThreadPool,
verbose: true
})
]
}
二.使用DllReferencePlugin打包公共方法
1.新建文件dll.js和 webpack.dll.conf.js,兩個(gè)文件和webpack.base.conf.js同級(jí)。
2.在dll.js文件下添加以下代碼
const path = require('path');
const webpack = require('webpack');
const dllConfig = require('./webpack.dll.conf');
const chalk = require('chalk')
const rm = require('rimraf')
const ora = require('ora')
const spinner = ora({
color: 'green',
text: '正為生產(chǎn)環(huán)境打包dll包中...'
})
spinner.start()
rm(path.resolve(__dirname, '../dll'), err => {
if (err) throw err
webpack(dllConfig,function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' dll打包已完成啦!\n'))
})
});
3.在webpack.dll.conf.js文件下添加以下代碼
const path = require('path')
const webpack = require('webpack')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const AssetsPlugin = require('assets-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
vendor: [
'vue/dist/vue.esm.js'
]
},
output: {
path: path.join(__dirname, '../static/js/'), // 生成的文件存放路徑
filename: 'dll.[name].[chunkhash].js', // 生成的文件名字(默認(rèn)為dll.vendor.[hash].js)
library: '[name]_[chunkhash]' // 生成文件的映射關(guān)系,與下面DllPlugin中配置對(duì)應(yīng)
},
plugins: [
new CleanWebpackPlugin(['dll','../static/js']),
new webpack.DllPlugin({
// 會(huì)生成一個(gè)json文件,里面是關(guān)于dll.js的一些配置信息
path:path.join(__dirname, '../dll/[name]-manifest.json'),
name: '[name]_[chunkhash]', // 與上面output中配置對(duì)應(yīng)
context: __dirname // 上下文環(huán)境路徑(必填,為了與DllReferencePlugin存在與同一上下文中)
}),
new AssetsPlugin({ //
filename: 'bundle-conf.json',
path: './dll'
})
]
}
- 安裝依賴
npm i -D clean-webpack-plugin@latest assets-webpack-plugin@latest
5.在webpack.prod.conf.js文件下
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const boundConf = require('../dll/bundle-conf.json') //bundle-confi.json目錄結(jié)構(gòu)要正確
const webpackConfig = merge(baseWebpackConfig, {
...
plugins: [
new CleanWebpackPlugin(['dist']),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('../dll/vendor-manifest.json')
}),
//這個(gè)主要是將生成的vendor.dll.js文件加上hash值插入到頁(yè)面中。
new AddAssetHtmlPlugin([{
filepath: path.resolve(`./static/js/${boundConf.vendor.js}`),
outputPath: utils.assetsPath('js'),
publicPath: path.posix.join(config.build.assetsPublicPath, 'static/js'),
includeSourcemap: false,
hash: true,
}]),
]
})
- 在
package.sjon下添加配置"build:dll": "node build/dll.js",
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js",
"build:dll": "node build/dll.js"
},
6 只需第一次執(zhí)行npm run build:dll,打包生成文件之后,以后就不需要在npm run build之前再npm run build:dll了
