webpack4项目升级webpack5实践
本文是继上篇webpack3项目升级webpack4实践的第二篇,本着循序渐进的原则,对老项目一步步升级。
老规矩,先复制一份原始依赖。如下:
"dependencies": {
"@xkeshi/vue-qrcode": "^1.0.0",
"axios": "^0.19.0",
"blueimp-md5": "^2.12.0",
"china-area-data": "^5.0.1",
"easemob-websdk": "^3.6.3",
"echarts": "^4.6.0",
"el-tree-transfer": "^2.3.0",
"element-china-area-data": "^5.0.0",
"element-ui": "^2.12.0",
"file-saver": "^2.0.2",
"html2canvas": "^1.0.0-rc.5",
"js-cookie": "^2.2.1",
"jsbarcode": "^3.11.0",
"jwt-js": "^0.5.0",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"throttle-debounce": "^2.1.0",
"v-charts": "^1.19.0",
"v-distpicker": "^1.2.2",
"vant": "^2.12.19",
"vue": "^2.5.2",
"vue-amap": "^0.5.10",
"vue-clipboard2": "^0.3.1",
"vue-drag-resize": "^1.3.2",
"vue-quill-editor": "^3.0.6",
"vue-router": "^3.0.1",
"vuedraggable": "^2.24.0",
"vuex": "^3.1.1",
"xlsx": "^0.15.6"
},
"devDependencies": {
"@babel/core": "^7.7.5",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/polyfill": "^7.7.0",
"@babel/preset-env": "^7.7.6",
"@babel/runtime": "^7.7.6",
"autoprefixer": "^7.1.2",
"babel-eslint": "^10.0.3",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^8.0.6",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-vue-jsx": "^3.7.0",
"body-parser": "^1.19.0",
"chalk": "^2.0.1",
"chokidar": "^3.2.2",
"concurrently": "^5.0.0",
"copy-webpack-plugin": "^6.4.1",
"core-js": "^2.6.11",
"css-loader": "^5.2.7",
"el-tree-transfer": "^2.3.0",
"eslint": "^4.15.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-plugin-vue": "^4.0.0",
"execa": "^5.0.0",
"express": "^4.17.1",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^4.4.0",
"http-proxy-middleware": "^0.20.0",
"http-server": "^0.11.1",
"inquirer": "^7.3.3",
"mini-css-extract-plugin": "^1.6.2",
"node-notifier": "^5.1.2",
"node-sass": "^4.12.0",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"os": "^0.1.1",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"sass-loader": "^7.3.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"socket.io": "^3.0.4",
"terser-webpack-plugin": "^4.2.3",
"thread-loader": "^2.1.3",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^4.1.1",
"vue-loader": "^15.9.8",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.3",
"webpack-merge": "^4.1.0"
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
本次升级主要升级webpack,并附带升级相关webpack插件。
# 过程
先调试开发。
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --env.pageName",
# 升级webpack5
npm i webpack@5 webpack-cli@4 -D
运行报错,如下
internal/modules/cjs/loader.js:892
throw err;
^
Error: Cannot find module 'webpack-cli/bin/config-yargs'
Require stack:
- F:\h5\node_modules\webpack-dev-server\bin\webpack-dev-server.js
2
3
4
5
6
7
是webpack-dev-server
版本偏低
升级webpack-dev-server
npm i webpack-dev-server@4 -D
报错,如下
[webpack-cli] Error: Unknown option '--inline'
[webpack-cli] Run 'webpack --help' to see available commands and options
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! s2b2c@1.0.0 dev: `webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --env.pageName "goodsCenter"`
npm ERR! Exit status 2
2
3
4
5
6
是因为新版本的webpack-dev-server不支持--inline参数,去除。
运行报错,如下
[webpack-cli] Error: Unknown option '--env.pageName'
[webpack-cli] Run 'webpack --help' to see available commands and options
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! s2b2c@1.0.0 dev: `webpack-dev-server --progress --config build/webpack.dev.conf.js --env.pageName "goodsCenter"`
npm ERR! Exit status 2
2
3
4
5
6
参考github文档 (opens new window),发现webpack5不支持用户传入自定义参数了,不过依旧支持给env对象增加属性(见webpack文档 (opens new window))。这句话可能有点难理解,以当前项目为例:
项目开发命令为
npm run dev goodsCenter
,只编译goodsCenter页面。原webpack config文件可以通过env.pageName来获取goodsCenter的值
webpack5对于--env的写法有变,它支持的写法如下
webpack serve --env pageName=goodsCenter
1但这里有个问题,就是goodsCenter实际是开发者作为命令行参数输入的,而webpack这里必须固定设置。
解决方案是使用node命令行参数,作为中转。
更改dev命令
"dev": "node build/dev.js",
1中转dev.js
// build/dev.js const chalk = require('chalk') const cp = require('child_process') async function run() { const cliParams = process.argv.slice(2) if (cliParams.length > 2) { console.log(chalk.yellow('仅支持传入一个页面参数,多余页面会被忽略')) } const page = cliParams[0] || '' const pageParam = page ? `--env pageName=${page}` : '' const command = `webpack serve --config build/webpack.dev.conf.js --color ${pageParam}` const child = cp.exec(command) child.stdout.pipe(process.stdout) // 输出子进程信息 child.stderr.pipe(process.stderr) } run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
done,原有处理pageName的逻辑不需要改变。
# 处理报错
继续运行,报错。
[webpack-cli] Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.devtool should match pattern "^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$".
BREAKING CHANGE since webpack 5: The devtool option is more strict.
Please strictly follow the order of the keywords in the pattern.
2
3
4
之前的devtool如下
devtool: 'cheap-module-eval-source-map',
改写为
devtool: 'eval-cheap-module-source-map',
继续运行,报错。
[webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'watchOptions'.
2
提示watchOptions已废弃,去除。
// watchOptions: {
// poll: config.dev.poll
// }
2
3
继续运行,报错。
[webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'quiet'.
2
提示quiet属性已废弃,去除。
// quiet: true // necessary for FriendlyErrorsPlugin
继续运行,报错。
[webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'publicPath'.
2
提示publicPath已废弃,改写之,文档-publicPath (opens new window)。
// 之前
devServer: {
publicPath: config.dev.assetsPublicPath
}
// 之后
devServer: {
static: {
publicPath: config.dev.assetsPublicPath
},
}
2
3
4
5
6
7
8
9
10
11
继续运行,报错。
[webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'overlay'.
2
提示overlay属性不存在,根据文档-overlay (opens new window)修改。
// 之前
devServer: {
overlay: {
warnings: false,
errors: true
}
}
// 之后
devServer: {
client: {
overlay: {
warnings: false,
errors: true
}
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
继续运行,报错。
Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'contentBase'.
2
webpack5修改了字段。
// 之前
devServer: {
contentBase: path.resolve(__dirname, '../client'),
}
// 之后
devServer: {
directory: path.resolve(__dirname, '../client'),
}
2
3
4
5
6
7
8
9
继续运行,报错。
Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'clientLogLevel'.
2
webpack5已删除该属性,去除。
继续运行,报错。
[webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'disableHostCheck'.
2
webpack5修改了字段。
// 之前
devServer: {
disableHostCheck: true,
}
// 之后
devServer: {
allowedHosts: 'all',
}
2
3
4
5
6
7
8
9
好的,没报错了,项目正常启动。
# 打包
开发过程正常,接下来是构建部署包过程。
运行npm run build
,报错
h5\build\webpack.prod.conf.js:136
new webpack.HashedModuleIdsPlugin(),
^
TypeError: webpack.HashedModuleIdsPlugin is not a constructor
2
3
4
5
参考文档:hashed-module-ids-plugin (opens new window),改为
new webpack.ids.HashedModuleIdsPlugin(),
插一句,官网不推荐使用这种方式,移除最好,见文档 (opens new window)。
继续运行,又报错
h5\build\build-all.js:35
if (err) throw err
^
TypeError: compiler.plugin is not a function
at LastCallWebpackPlugin.apply (F:\h5\node_modules\last-call-webpack-plugin\index.js:190:12)
at OptimizeCssAssetsPlugin.apply (F:\h5\node_modules\optimize-css-assets-webpack-plugin\index.js:73:32)
at createCompiler (F:\h5\node_modules\webpack\lib\webpack.js:73:12)
at create (F:\h5\node_modules\webpack\lib\webpack.js:134:16)
at webpack (F:\h5\node_modules\webpack\lib\webpack.js:142:47)
at f (F:\h5\node_modules\webpack\lib\index.js:55:16)
at buildPage (F:\h5\build\build-all.js:33:5)
at F:\h5\build\build-all.js:22:9
at Array.forEach (<anonymous>)
at F:\h5\build\build-all.js:21:14
at next (F:\h5\node_modules\rimraf\rimraf.js:83:7)
at CB (F:\h5\node_modules\rimraf\rimraf.js:119:9)
at F:\h5\node_modules\rimraf\rimraf.js:145:14
at FSReqCallback.oncomplete (fs.js:192:21)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
因为optimize-css-assets-webpack-plugin
插件已过时(参考文档 (opens new window)),webpack5使用css-minimizer-webpack-plugin替代。
安装插件npm i ss -D
,运行,报错。
xx/css/776.0442ce6947f070cf7f1f.css from Css Minimizer
Error: [object Object] is not a PostCSS plugin
at Processor.normalize (F:\h5\node_modules\postcss\lib\processor.js:145:15)
at new Processor (F:\h5\node_modules\postcss\lib\processor.js:51:25)
at postcss (F:\h5\node_modules\postcss\lib\postcss.js:73:10)
at cssnanoPlugin (F:\h5\node_modules\cssnano\src\index.js:156:10)
at cssnanoMinify (eval at transform (F:\h5\node_modules\css-minimizer-webpack-plugin\dist\minify.js:34:28), <anonymous>:51:33)
at minify (F:\h5\node_modules\css-minimizer-webpack-plugin\dist\minify.js:15:32)
at Object.transform (F:\h5\node_modules\css-minimizer-webpack-plugin\dist\minify.js:35:24)
at execFunction (F:\h5\node_modules\jest-worker\build\workers\threadChild.js:158:17)
at execHelper (F:\h5\node_modules\jest-worker\build\workers\threadChild.js:137:5)
at execMethod (F:\h5\node_modules\jest-worker\build\workers\threadChild.js:141:5)
2
3
4
5
6
7
8
9
10
11
12
见issue (opens new window),需要重新安装postcss
。
npm i postcss -D
# 升级依赖
升级
html-webpack-plugin
npm i html-webpack-plugin@5
1升级
mini-css-extract-plugin
npm i mini-css-extract-plugin@2
1升级
thread-loader
npm i thread-loader@3
1升级
copy-webpack-plugin
npm i copy-webpack-plugin@10
1升级
css-loader
npm i css-loader@6
1升级
file-loader
npm i file-loader@6
1
# 其他问题
# 端口占用
F:\h5\node_modules\webpack-dev-server\lib\Server.js:1751
throw error;
^
Error: listen EADDRINUSE: address already in use 0.0.0.0:8088
at Server.setupListenHandle [as _listen2] (net.js:1320:16)
at listenInCluster (net.js:1368:12)
at doListen (net.js:1505:7)
at processTicksAndRejections (internal/process/task_queues.js:83:21) {
errno: -4091,
syscall: 'listen',
address: '0.0.0.0',
port: 8088
}
2
3
4
5
6
7
8
9
10
11
12
13
14
查找开启服务的端口
netstat -ano|findstr 8088
发现的确存在
TCP 0.0.0.0:8088 0.0.0.0:0 LISTENING 2968
TCP 127.0.0.1:8088 127.0.0.1:52191 ESTABLISHED 2968
TCP 127.0.0.1:52191 127.0.0.1:8088 ESTABLISHED 13156
TCP 127.0.0.1:52222 127.0.0.1:8088 TIME_WAIT 0
2
3
4
干掉它,即可。
tskill 2968
# 字体文件损坏(css-loader@6)
部署后,发现element-ui的字体图标失效,控制台报警告错误:
Failed to decode downloaded font: https://example.com/d3cc9e4763d2a7c851ef.woff
查找对应的打包文件,发现这个woff文件里面是js代码。
export default __webpack_public_path__ + "element-ui/fonts/element-icons.313f7da.woff";
明显是打包错了。
查看提交记录,发现是升级了css-loader
(v5.2.7->v6.6.0)导致,于是去github查询issue。
相关issue有1338 (opens new window),1360 (opens new window),作者对于webpack5 做了 breaking change,建议开发者使用资源模块 (opens new window)来加载资源,并废弃raw-loader
,url-loader
,file-loader
。前后对比如下:
之前
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: path.posix.join('element-ui/fonts/[name].[hash:7].[ext]')
}
}
2
3
4
5
6
7
8
之后
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'asset',
generator: {
filename: path.posix.join('element-ui/fonts/[name].[hash:7].[ext]')
}
}
2
3
4
5
6
7
注:options选项是作为参数传入loader的,采用asset-modules
后,就不需要了,webpack默认小于8kb就会使用Base64编码字符串注入,可以修改maxSize。
# 整理webpack控制台信息
正常情况下,webpack构建时会输出一大堆信息,如下:
多余的信息会让开发者变得焦躁,清理方式很简单:
module.export = {
infrastructureLogging: {
// 禁用日志
// https://webpack.docschina.org/configuration/other-options/#level
level: 'none'
},
stats: 'errors-only' // 编译错误时输出
}
2
3
4
5
6
7
8
不过,顺利的话是没有任何输出的,开发者还以为“卡死”了,优化如下:
module.export = {
plugins: [
compiler => {
console.log('Start compiling...')
compiler.hooks.done.tap({ name: 'myPlugin' }, stats => {
const hasErrors = stats.hasErrors()
if (!hasErrors) {
console.log()
console.log(` App running at:`)
console.log(` - Local: ${chalk.cyan(`http://localhost:8088`)}`)
}
})
}
],
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
另外,还可以使用成熟的解决方案,即friendly-errors-webpack-plugin (opens new window)。
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
module.exports = env => {
const devWebpackConfig = {
}
return new Promise((resolve, reject) => {
console.log(`${chalk.bgBlue.black(' I ')} Start compiling...`)
portfinder.basePort = config.dev.port || '8088'
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(
new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`${new Date().toLocaleTimeString()} update`, `App running at: http://localhost:${port}`]
},
onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined // 参考官方文档介绍
})
)
resolve(devWebpackConfig)
}
})
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28