Skip to main content

如何将webpack3升级至webpack4

背景#

原项目由vue-cli2快速构建, 几年过去了webpack5已经发布,迫于项目逐渐扩大且业务需求, webpack版本升级迫在眉睫。 以下记录了升级过程及问题解决方案

难点#

相关最新安装包不匹配造成无法按照预期运行

针对development#

升级webpack及周边#

⚠️webpack-dev-server需要与webpack匹配, 否则报错

"webpack": "^4.44.2", // 3.6.0 -> 4.44.2
"webpack-cli": "^3.3.12", // add
"webpack-dev-server": "^3.11.0", // 2.9.1 -> 3.11.0
npm i -D webpack-bundle-analyzer@latest // 2.9.0 -> 4.3.0

添加mode (v4新)#

mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',

参照官网指南, 移除废弃API#

https://v4.webpack.docschina.org/migrate/4/

  • 移除NamedModulesPlugin,NoEmitOnErrorsPlugin插件
// new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
// new webpack.NoEmitOnErrorsPlugin(),
  • 移除DefinePlugin
  • UglifyJsPlugin

升级html编译插件html-webpack-plugin#

错误提示:compilation.mainTemplate.applyPluginsWaterfall('asset-path', outputOptions.filename

npm i html-webpack-plugin@latest --dev // 2.30.1 -> 4.5.0

升级vue-loader,使用VueLoaderPlugin替换vueLoaderConfig#

错误提示: Cannot read property 'vue' of undefined

npm i -D vue-loader@latest // 13.3.0 -> 15.9.6

移除vueLoaderConfig, 增加VueLoaderPlugin ⚠️ options 字段改为 loader

//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()
]
}

移除webpack-dev-server/client#

错误提示:Cannot assign to read only property 'exports' of object '[object Object]' 移除resolve('node_modules/webpack-dev-server/client') ​

针对production#

废弃CommonsChunkPlugin由optimization 代替#

webpack.optimize.CommonsChunkPlugin has been removed

  • vendor
  • manifest
  • vendor-async
plugins: [
// 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
// }),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'app',
// async: 'vendor-async',
// children: true,
// minChunks: 3
// }),
]
optimization: {
// minimize: true,
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
chunks: 'initial',
name: 'vendors',
},
'async-vendors': {
test: /[\\/]node_modules[\\/]/,
minChunks: 3,
chunks: 'async',
name: 'async-vendors',
},
manifest: {
test: /[\\/]src[\\/]/,
minSize: 0,
minChunks: 2,
priority: 9
}
}
}
}

废弃extract-text-webpack-plugin由mini-css-extract-plugin代替#

Chunk.entrypoints: Use Chunks.groupsIterable and filter

extract-text-webpack-plugin // remove
npm i mini-css-extract-plugin@latest --dev

webpack.prod.js

// const ExtractTextPlugin = require('extract-text-webpack-plugin')
// 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
// }),
new MiniCssExtractPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
allChunks: true,
ignoreOrder: true
}),

告警: WARNING in chunk 1 [mini-css-extract-plugin] Conflicting order. 

ignoreOrder: true

utils.js

if (options.extract) {
// return ExtractTextPlugin.extract({
// use: loaders,
// fallback: 'vue-style-loader'
// })
return [MiniCssExtractPlugin.loader].concat(loaders)
}

修改chunksSortMode#

错误提示: Unhandled rejection Error: "dependency" is not a valid chunk sort mode

// HtmlWebpackPlugin 中配置
chunksSortMode: 'auto'

告警: Unhandled rejection Error: "dependency" is not a valid chunk sort mode#

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB). This can impact web performance. ​

升级成果: 打包时间提升 26.5%, 体积减小65.5%#

13831ms -> 10160ms 14.78s 16M -> 6M ​

影响不大的升级#

升级webpack-merge#

npm i webpack-merge@latest --dev // 4.1.0 -> 5.7.3
// error merge is not a function
const {merge} = reuqire('webpack-merge')

升级webpack-bundle-analyzer#

npm i -D webpack-bundle-analyzer@latest // ^2.9.0 -> ^4.3.0
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
"build:report": "npm_config_report=true node build_vue_script/build.js"

升级plugin#

vue-template-compiler@latest
friendly-errors-webpack-plugin@latest
copy-webpack-plugin@latest
optimize-css-assets-webpack-plugin@latest

升级css#

npm install --save-dev css-loader@latest
npm install --save-dev postcss-loader@latest
postcss-import@latest
postcss-import@latest
sass-loader@latest
npm install css-loader@latest
file-loader@latest
url-loader@latest
less-loader@latest
postcss-loader@latest
vue-loader@latest
vue-style-loader@latest -D

错误提示:Error: true is not a PostCSS plugin

升级eslint#

npm i -D eslint@latest
eslint-loader@latest 已被废弃 替换eslint-webpack-plugin
eslint-config-standard@latest
eslint-friendly-formatter@latest
eslint-plugin-import@latest
eslint-plugin-node@latest
eslint-plugin-promise@latest
eslint-plugin-standard@latest
eslint-plugin-vue@latest
eslint-plugin-html@latest
module.exports = {
// ...
module: {
rules: [
// {
// test: /\.js$/,
// exclude: /node_modules/,
// loader: 'eslint-loader',
// options: {
// // eslint options (if necessary)
// },
// },
],
},
// ...
};
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// ...
plugins: [new ESLintPlugin(options)],
// ...
};

参考:

webpack4-documnet

如何使用parcel实现零配置打包

简介#

parcel,是一款完全零配置的前端打包器。 Parcel 是 2017 年发布, 解决了Webpack配置过于繁琐的问题。

parcel核心特点: • 零配置自动安装依赖构建速度更快

快速上手#

yarn init -y
yarn add parcel-bundler --dev
yarn parcel src/index.html
yarn parcel build src/index.html // 打包

虽然 Parcel 跟 Webpack 一样都支持以任意类型文件作为打包入口,不过 Parcel 官方还是建议我们使用 HTML 文件作为入口。官方的理由是 HTML 是应用在浏览器端运行时的入口。

<!-- ./src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>use Parcel</title>
</head>
<body>
</body>
<script src="main.js"></script>
</html>

ES Modules 模块的打包#

// ./src/logger.js
const log = (data) => {
console.log(data)
}
module.exports = {
log
}

js入口文件

// ./src/main.js
import { log } from './logger'
log('hello')

模块热替换(HMR)#

Parcel 提供的 accept 只需要接收一个回调参数,作用就是当前模块更新或者所依赖的模块更新过后自动执行传入的回调函数。Webpack 中的 accept 方法支持接收两个参数,用来处理指定的模块更新后的逻辑。

// ./src/main.js
if (module.hot) {
module.hot.accept(() => {
console.log('has HMR')
})
}

自动安装依赖#

// ./src/main.js
import $ from 'jquery';

保存后, 会自动安装并添加到package.json 中。

其他类型资源加载#

// ./src/main.css
body{
color: red;
}
// ./src/main.js
import './main.css'

 动态导入#

// ./src/main.js
import('jquery').then($ => {
$(document.body).append('<h2>panda od chengdu</h2>')
})

生产模式打包#

相同体量的项目打包,Parcel 的构建速度会比 Webpack 快很多。因为 Parcel 内部使用的是多进程同时工作,充分发挥了多核 CPU 的性能。

代码

如何实现vue多页面

vue-cli3 配置多页面#

多个页面时, 目录结构

├── src
├── pages
├── home
| ├── home.html
| └── home.js
├── about
| ├── about.html
| └── about.js

vue.config.js 添加

pages: {
home : {
entry: 'src/pages/home/home.js',
template: 'public/home.html',
filename: 'home.html'
},
}

动态配置

pages: getPages(),
// 辅助函数
var path = require("path");
var glob = require("glob");
function getPages() {
var PAGE_PATH = path.resolve(__dirname, "./src/pages");
var entryFiles = glob.sync(PAGE_PATH + "/*/*.js");
var map = {};
entryFiles.forEach((filePath) => {
const filename = filePath.substring(
filePath.lastIndexOf("/") + 1,
filePath.lastIndexOf(".")
);
map[filename] = {
entry: "src/pages/" + filename + "/" + filename + ".js",
template: "public/" + filename + ".html",
filename: filename + ".html",
};
});
return map;
}

vue-cli2配置多页面#

借助glob第三方库, 使用shell模式匹配文件。(glob是webpack安装时依赖的一个第三方模块) cli2中需要在build/webpack.base.conf.js中修改entry

entry: entries()
// 辅助函数
function entries () {
const PAGE_PATH = path.resolve(__dirname, './src/pages')
const entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
let map = {};
entryFiles.forEach(filePath => {
const filename = filePath.substring(
filePath.lastIndexOf("/") +1,
filePath.lastIndexOf(".")
)
map[filename] = filePath;
})
return map;
}

分别在build/webpack.dev.conf.js和build/webpack.prod.conf.js中修改plugins

plugins: [].concat(htmls())
// 辅助函数
function htmls () {
const PAGE_PATH = path.resolve(__dirname, './src/pages')
let entryHtml = glob.sync(PAGE_PATH + "/*/*.html");
let arr = [];
entryHtml.forEach(filePath => {
let filename = filePath.substring(
filePath.lastIndexOf("/") + 1,
filePath.lastIndexOf(".")
);
let conf = {
template: filePath, // 模板来源
filename: filename + ".html", // 文件名称
chunks: ["manifest", "vendor", filename], // 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本
inject: true
};
if (process.env.NODE_ENV === "production") {
conf = merge(conf, {
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: "dependency"
});
}
arr.push(new HtmlWebpackPlugin(conf));
});
return arr;
};

webpack配置多页面#

webpack项目, entry作为入口, output作为出口, 借助htmlwebpackplugin插件 把静态资源引入到html中。 普通单页面配置:

var HtmlWebpackPlugin = require('html-webpack-plugin');
var path = require('path');
var webpackConfig = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
})]
};

配置文件

var webpackConfig = {
entry: {
home: "./pages/home/home.js",
about: "./pages/about/about.js",
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].js'
},
plugins: [new HtmlWebpackPlugin({
template: './src/pages/home/home.html',
filename: 'home.html',
}), new HtmlWebpackPlugin({
template: './src/pages/about/about.html',
filename: 'about.html',
})]
};

参考文献: https://cli.vuejs.org/zh/config/#pages https://www.webpackjs.com/configuration/