webpack4迁移webpack5

# webpack4迁移webpack5

# 准备

  • 当前项目webpack版本为 v4.29.6webpack-cliv3.3.0
  • vue版本仍然还是v2vue3webpack配置,不在这篇博文里讨论~

# 升级

为减少升级成本,这里有个工具可以一键升级~

# 一键迁移

  1. 安装工具:cnpm install -g npm-check-updates
  2. 项目根目录下执行命令:npm-check-updates,检查更新:
boss-creator-web git:(feature/editor) ✗ npm-check-updates
Checking /Users/admin/work/boss-creator-web/package.json
[====================] 74/74 100%
 
@babel/core ^7.4.0 → ^7.15.8
@babel/preset-env ^7.4.2 → ^7.15.8
@vue/babel-preset-app ^3.6.0 → ^4.5.14
@vue/test-utils ^1.0.0-beta.29 → ^1.2.2
...
webpack ^4.29.6 → ^5.59.1
webpack-bundle-analyzer ^3.7.0 → ^4.5.0
webpack-cli ^3.3.0 → ^4.9.1
webpack-dashboard ^3.3.3 → ^3.3.6
webpack-dev-server ^3.2.1 → ^4.3.1
webpack-merge ^4.2.1 → ^5.8.0
...
  1. 检查完成后会输出 packge.json 中插件当前版本和最新版本的对比,接下来输入 ncu -u,会看到 package.json中版本号已经更新为最新版了;
  2. 最后删除 node_modules ,执行 npm install,安装~

# 升级

安装完成后,先试下 npm run build ,不出意外,应该是会报错的~

接下来挨个解决各个报错~

# 插件报错

  • HashedModuleIdsPlugin 插件报错,报错信息如下:
TypeError: webpack.HashedModuleIdsPlugin is not a constructor

HashedModuleIdsPluginwebpack v4 提供的插件,会根据模块的相对路径生成一个四位数的hash作为模块idv4中用法是:

// webpack.config.base.js
 
new webpack.HashedModuleIdsPlugin({
  // 选项……
});

v5 中用法为:

new webpack.ids.HashedModuleIdsPlugin({
  // Options...
});

解决方法: 将配置文件中 v4 用法改为 v5 用法即可~

参考:hashed-module-ids-plugin v4用法 (opens new window)hashed-module-ids-plugin v5用法 (opens new window)

  • CleanWebpackPlugin 插件报错,报错信息为:
TypeError: CleanWebpackPlugin is not a constructor

这个也是插件升级引入方式的的改变造成的报错,该插件由 v2.0.1 升级为了 v4.0.0 ,修改引入方式即可:

// webpack.config.prod.js
 
// const CleanWebpackPlugin = require('clean-webpack-plugin'); // 旧用法
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); // 新用法
  • webpack-merge 插件报错:
[webpack-cli] Failed to load '/Users/admin/work/boss-youle-web/build/webpack.config.prod.js' config
[webpack-cli] TypeError: merge is not a function

webpack-mergev4.2.1 升级到 v.5.8.0,引入方式也改变了,修改即可:

// const merge = require('webpack-merge'); // 旧用法
const {merge} = require('webpack-merge'); // 新用法
  • optimize-css-assets-webpack-plugin 插件警告:
(node:3251) [DEP_WEBPACK_COMPILATION_OPTIMIZE_CHUNK_ASSETS] DeprecationWarning: optimizeChunkAssets is deprecated (use Compilation.hooks.processAssets instead and use one of Compilation.PROCESS_ASSETS_STAGE_* as stage option)

optimize-css-assets-webpack-pluginwebpack4 主要是用来压缩css的插件,webpack5 中则 可以通过 css-minimizer-webpack-plugin 来替换:

  1. 安装:npm install css-minimizer-webpack-plugin --save-dev
  2. 配置:
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 引入
 
optimization: {
        minimize: true, // 开发环境下启用 CSS 优化
        minimizer: [
            // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
            `...`,
            new CssMinimizerPlugin(),
        ],
}
  1. 最后删掉 optimize-css-assets-webpack-plugin 的使用即可~

参考 (opens new window)

  • mini-css-extract-plugin 警告:
WARNING in chunk commons [mini-css-extract-plugin]
Conflicting order. Following module has been added:

可以通过将插件的 ignoreOrder 选项设置为 true 来禁用 css order 警告:

// webpack.config.prod.js
 
plugins: [
    new MiniCssExtractPlugin({
      ignoreOrder: true,
    }),
  ],

具体设置可参考 (opens new window)

# 配置优化

  • optimization.splitChunks 报错:
Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
 - configuration.optimization.splitChunks should be one of these:
   false | object { automaticNameDelimiter?, cacheGroups?, chunks?, defaultSizeTypes?, enforceSizeThreshold?, fallbackCacheGroup?, filename?, hidePathInfo?, maxAsyncRequests?, maxAsyncSize?, maxInitialRequests?, maxInitialSize?, maxSize?, minChunks?, minRemainingSize?, minSize?, name?, usedExports? }
   -> Optimize duplication and caching by splitting chunks by shared modules and cache group.
configuration.optimization.splitChunks.name should be one of these:
      false | string | function

optimization.splitChunks.name 设置拆分chunk的名称,webpack4 中设置为 true 将自动生成基于块和缓存组键的名称;

v5 中不能传 true,只能传 false | string | function;设为 false 将保持 chunk 的相同名称,因此不会不必要地更改名称。这是生产环境下构建的建议值。

// webpack.config.prod.js
 
optimization: {
    splitChunks: {
//      name: true  // v4写法
        name: false // 新写法,设置为false,或者注释掉也可以
    }
}

参考 (opens new window)

  • DefinePlugin 警告:
WARNING in DefinePlugin
Conflicting values for 'process.env.NODE_ENV'

webpack5process.env.NODE_ENV 默认值取决于 mode,也可在 optimization.nodeEnv 中设置;取消 DefinePlugin 中的配置即可解除警告:

// webpack.config.base.js
 
plugins: [
    new webpack.DefinePlugin({
            'process.env': {
                // NODE_ENV: JSON.stringify(`${process.env.NODE_ENV}`), // webpack5中 process.env.NODE_ENV 默认值取决于 mode
                PUBLIC_PATH: JSON.stringify(`${argv.publicPath}`),
                PATH_PREFIX: JSON.stringify(`${argv.pathPrefix}`),
                RUN_ENV: JSON.stringify(`${process.env.RUN_ENV}`)
            }
        }),
]

具体设置可参考optimization.nodeEnv (opens new window)

2021-11-20更新:不用注释,在脚本中添加NODE_ENV全局变量即可解除警告:

"dev": "cross-env NODE_ENV=development RUN_ENV=dev webpack serve --progress --config build/webpack.config.dev.js",
  • hash 警告:
(node:99549) [DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_HASH] DeprecationWarning: [hash] is now [fullhash] (also consider using [chunkhash] or [contenthash], see documentation for details)

这个警告提示我们要把项目中之前在 webpack4 中使用 hash 配置的改成 chunkhash 或者 contenthashwebpack 的官方迁移指南里有说明:

// webpack.config.prod.js
 
output: {
        path: path.resolve(__dirname, '../dist'),
        publicPath: utils.getPublicPath(),
        filename: 'js/[name]_[chunkhash:8].js', // hash更改为 chunkhash or contenthash
        chunkFilename: 'js/[name]_[chunkhash:8]_chunk.js',
        libraryTarget: 'umd',
        umdNamedDefine: true
    },
  • devtool 报错:
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$".

这是因为 devtoolwebpack4webpack5 上也是有区别的,修改如下:

// webpack.config.dev.js
 
// devtool: 'cheap-module-eval-source-map', // webpack4写法
devtool: 'eval-source-map',  // webpack5写法

具体配置可参考 (opens new window)

  • hard-source-webpack-plugin 报错:
TypeError: Cannot read property 'tap' of undefined
    at Object.exports.tap (/Users/admin/work/boss-youle-web/node_modules/hard-source-webpack-plugin/lib/util/plugin-compat.js:118:25)
    at new CacheSerializerFactory (/Users/admin/work/boss-youle-web/node_modules/hard-source-webpack-plugin/lib/CacheSerializerFactory.js:94:18)
    at HardSourceWebpackPlugin.apply (/Users/admin/work/boss-youle-web/node_modules/hard-source-webpack-plugin/index.js:219:36)

webpack4 的开发环境中,我们使用 hard-source-webpack-plugin 来实现启动缓存,从而实现启动提速;但在 webpack5 中内置了 cache 缓存机制,直接配置即可,hard-source-webpack-plugin 就直接被废弃掉了。

// webpack.config.dev.js
 
cache: {     
        // 将缓存类型设置为文件系统     
        type: "filesystem",      
        buildDependencies: {       
            /* 将你的 config 添加为 buildDependency,          
               以便在改变 config 时获得缓存无效*/       
            config: [__filename],       
            /* 如果有其他的东西被构建依赖,          
               你可以在这里添加它们*/       
            /* 注意,webpack.config,          
               加载器和所有从你的配置中引用的模块都会被自动添加*/     
        },     
        // 指定缓存的版本     
        version: '1.0'    
    }

cache 具体配置可参考 (opens new window)

  • devServer 报错:
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'. These properties are valid:
   object { allowedHosts?, bonjour?, client?, compress?, devMiddleware?, headers?, historyApiFallback?, host?, hot?, http2?, https?, ipc?, liveReload?, magicHtml?, onAfterSetupMiddleware?, onBeforeSetupMiddleware?, onListening?, open?, port?, proxy?, setupExitSignals?, static?, watchFiles?, webSocketServer? }

这是因为 devServerwebpack5 中的配置相较 v4 有些变化,修改如下:

// webpack.config.dev.js
 
devServer: {
        hot: true,
        // contentBase: path.join(__dirname, 'dist'), // webpack4 写法
        compress: true,
        host: '127.0.0.1',
        port: 8098,
        open: true,
        // webpack5新增 static 字段,里面的 directory 替换原来 contentBase 的配置;publicPath 也放在了里面
        static: {
            directory: path.join(__dirname, 'dist'),
            publicPath: '/',
        },
        // publicPath: '/', // webpack4写法
        // quiet: true, // webpack5取消了这个参数
        ...
    },

具体配置参数可参考: v4配置 (opens new window)v5配置 (opens new window)

还有一个地方需要注意的是, webpack-cli4之后执行脚本上有了变化:

"scripts": {
    "dev": "cross-env RUN_ENV=dev webpack-dev-server --progress --config build/webpack.config.dev.js", // 旧写法
	"dev": "cross-env RUN_ENV=dev webpack serve --progress --config build/webpack.config.dev.js", // 新写法
}

# 资源模块

webpack5 之前,加载图片、字体等资源需要我们使用 url-loader、 file-loader 等来处理; 从 webpack5 开始,我们可以使用内置的资源模块来替代这些 loader 的工作。

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader参考 (opens new window)

以图片静态资源解析为例:

// webpack.config.base.js

{
    test: /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/, // 解析图片等静态资源
    dependency: { not: ['url'] }, // 排除来自新 URL 处理的 asset
    exclude: [ path.resolve('src/assets/svg')], // 排除
    // use: {
    //     loader: 'url-loader',
    //     options: {
    //         limit: 1024, // 字节数,意味着当图片大小小于1k的话,打包时会进行base64转换
    //         name: utils.assetsPath('images/[name].[hash:7].[ext]')
    //     }
    // },
    type: "asset/resource",
    generator: {
        // 这里的 [ext] 扩展名通配符包含了 . ,我们不需要额外再写,跟之前的 loader 有所区别
        filename: utils.assetsPath('images/[name]_[hash:7][ext][query]')
    },
},

# 其他问题

  • eslint-loader 报错:
ERROR in ./src/main.js
Module build failed (from ./node_modules/eslint-loader/index.js):
Error: Cannot find module '@eslint/eslintrc/universal'

升级后的版本不稳定,降至之前的稳定版本即可:

// eslint相关插件退至升级前版本
 
    "eslint": "^5.16.0",
    "eslint-config-standard": "^12.0.0",
    "eslint-friendly-formatter": "^4.0.1",
    "eslint-loader": "^2.1.2",
    "eslint-plugin-html": "^5.0.3",
    "eslint-plugin-import": "^2.16.0",
    "eslint-plugin-node": "^8.0.1",
    "eslint-plugin-promise": "^4.0.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^5.2.2",
  • babel-eslint 报错
ERROR in ./src/plugins/global/video-player/index.vue
Module build failed (from ./node_modules/_eslint-loader@2.2.1@eslint-loader/index.js):
TypeError: token.type.endsWith is not a function

原因同上,把 babel-eslint 退至 v8.2.6 稳定版即可~

以上就是我在升级过程中遇到的问题,都解决后,执行 npm run dev,成功启动;npm run build,成功打包~

# 区别

  • devServer的参数配置有变化
  • 图片等静态资源的解析有变化,Webpack5 提供了内置的静态资源构建能力,我们不需要安装额外的 loader(url-loader,file-loader,raw-loader)
  • webpack5 中内置了 Cache 来实现启动缓存,实现了二次构建的提速, v4 需要引入插件hard-source-webpack-plugin
  • js压缩:webpack v5 开箱即带有最新版本的 terser-webpack-plugin。如果希望自定义配置,那么仍需要安装 terser-webpack-plugin。v4 则必须安装 terser-webpack-plugin v4 的版本
  • 启动服务差别:v4 通过 webpack-dev-server 启动服务,v5 内置使用 webpack serve 启动
  • tree-shaking的优化:v5 能够处理对嵌套模块的 tree shaking,也能处理对 Commonjstree shaking

https://github.com/webpack/changelog-v5 (opens new window)

# 参考

上次更新: 1/24/2022, 5:38:42 PM
最近更新
01
taro开发实操笔记
09-29
02
前端跨端技术调研报告
07-28
03
Flutter学习笔记
07-15
更多文章>