将react工程从 webpack3升级到webpack5

449 阅读3分钟

我正在参加「掘金·启航计划」

前言

由于我司的工程久远且代码量庞大, 平时开发编译和生产编译都很慢,差不多需要20-30分钟,之前看到说webpack5的编译方式优化,可以加快编译速度,就想试着开始升级一下。我们原来用的是webpack3.5.1的版本,不用管什么的直接升级到最新的,本次我升级的是webpack5.74.0。

具体思路

一、基于webpack5.x搭建react工程

1、先把node版本升级到14.x ,因为经测试好像有些依赖低于14的装不上,这里就不说怎么安装了,大家自行百度可以的^_^。

2、找个干净的目录,依次执行一下命令

  • (1)初始化目录,生成一个package.json文件

npm init -y

  • (2)安装webpack相关的包

npm install -D webpack webpack-cli

  • (3)安装html-webpack-plugin插件

npm install -D html-webpack-plugin

  • (4)安装react和babel插件

npm install react react-dom

npm install @babel/core

npm install -D @babel/preset-env @babel/preset-react babel-loader

  • (5)安装css-loader、sass、sass-loader、style-loader插件

npm install -D css-loader sass sass-loader style-loader

  • (6)在根目录新建一个文件webpack.config.js,内容如下:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require("copy-webpack-plugin");

const config = require('./config.js');
let pages = config.pages;
const VERSION = Date.now();
let hbsHtmlWebpackPlugins = [
    new HtmlWebpackPlugin({
        template: __dirname + '/template.html',
        filename: './page/' + pages[i].name + '.html',
        title: pages[i].title,
        inject: 'body',
        chunks: [pages[i].name]
    })
];

module.exports = {
    // 打包模式: 'production' or development'
    mode: 'development',
    entry: {
       index: './index.js',
    },
    // output为项目打包后的输出位置
    output: {
        path: __dirname + '/dist',
        filename: 'busi/[name]-' + VERSION + '.js',
        publicPath: '../'
    },

    plugins: hbsHtmlWebpackPlugins.concat([
        new MiniCssExtractPlugin({ filename: 'css/[name]-' + VERSION + '.css', ignoreOrder: true }),
        new CopyWebpackPlugin(
            {
                patterns: [
                    {
                        from: __dirname + '/lib',
                        to: __dirname + '/dist/lib'
                    }
                ]
            }
        )
    ]),

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react']
                    }
                }
            },
            {
                test: /\.(css|scss)$/i,
                use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
            },
            {
                test: /\.(png|jpe?g|gif)$/i,
                type: "asset",
                parser: {
                    dataUrlCondition: {
                        maxSize: 8 * 1024
                    }
                },
                generator: {
                    filename: "images/[name].[ext]"
                }
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)$/,
                type: "asset",
                generator: {
                    filename: "fonts/[name].[ext]"
                }
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: 'eslint-loader',
                enforce: 'pre'
            }
        ]
    }
};

  • (7)在根目录新建一个文件index.js,内容如下:

import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
  return (
    <div>
      <span>我是文字</span>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));

  • (8)在根目录新建一个文件template.html,内容如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>测试</title>
</head>
<body>
    <div id="root" ></div>
</body>
</html>

  • (9)在package.json里面增加一下代码,然后运行npm run build,就可以完成简单的编译了。

"scripts":{ 
    "build": "webpack --config ./scripts/webpack.config.js"
},

二、把原来的工程代码迁移进来,注意修改webpack.config.js里面的output和entry成自己工程的。

  1. 把原工程package.json里面的依赖包放到新的里面,然后看哪个是重复的,取最新的。
  2. 然后把旧的依赖包版本去掉,重新install一下,安装最新的。
  3. 然后根据对应的报错,查找对应的问题,一种错误是版本不对应,安装对应版本即可;另一种是接口过时,跟进文档使用最新的方法即可。

三、我遇到的问题及解决方式

1、# Invalid options object. Copy Plugin has been initialized using an options object that does not match:

这是copy-webpack-plugin插件报的,原因是新版本的入参已经修改了,原来是

     new CopyWebpackPlugin([{
        from: __dirname + '/lib',
        to: __dirname + '/dist/lib'
    })

现在需要改成这样:

  new CopyWebpackPlugin(
    {
        patterns: [
            {
                from: __dirname + '/lib',
                to: __dirname + '/dist/lib'
            }
        ]
    }
)

2、# Uncaught Error: ES Modules may not assign module.exports or exports.:同时使用了module.exports 和export default ,需要使用插件。安装babel-plugin-add-module-exportsbabel-plugin-dynamic-import-webpack插件,然后再loader里面使用

// ...
    {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env', '@babel/preset-react'],
                plugins: ["add-module-exports", "dynamic-import-webpack"]
            }
        }
    },
// ...

3、IE兼容问题,在IE打不开,报: SCRIPT1002: 语法错误

解决方法:安装babel-polyfill插件,然后在主入口引入即可,如果不行,可以删除node_module然后再重新安装一下。

import "babel-polyfill";

4、最新的eslint-loader 和最新的eslint并不匹配,编译时会报错:Cannot read property 'getFormatter' of undefined,看了代码,最新的eslint并没有把cliEngine方法暴露出去,所以拿不到getFormatter

解决方法:安装低于8.x版本的eslint,例如:7.32.0

5、打包出来后出现两份图片和字体的问题,因为webpack5自带了静态资源的处理,所以不用额外使用url-loader或者file-loader。

应该按照以下配置即可:

// ...
  {
    test: /\.(png|jpe?g|gif)$/i,
    type: "asset",
    parser: {
        dataUrlCondition: {
            maxSize: 8 * 1024
        }
    },
    generator: {
        filename: "images/[name].[ext]"
    }
},
{
    test: /\.(svg|eot|ttf|woff|woff2)$/,
    type: "asset",
    generator: {
        filename: "fonts/[name].[ext]"
    }
},
// ...

设置type: "asset"即为按照静态资源处理,其他参数可参考对应文档。

6、另外还有一个插件不兼容webpack5的,一般都有提示或者报错,按照报错搜一下就有替代的方案。例如extract-text-webpack-plugin可以换成mini-css-extract-pluginuglifyjs-webpack-plugin可以换成terser-webpack-plugin,不过据我测试terser-webpack-plugin用了好像也没有进行压缩。可以直接改config 的mode为“development”就会进行压缩打包。

结语

升级之后,编译时间只需7分钟,减少了2/3的时间。由于开始迁移的时候,没有过多的记录报错及解决方法,有一些报错还是没有收集到,因为每个工程的代码、环境都不太一样,因此仅供参考。