不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

时间:2023-02-24 18:12:36

前言

工作中发现很多同事在接到一个新项目时,总是基于现有项目复制一份配置文件,然后写自己的组件及业务代码,以至于项目中存在一些冗余的依赖及配置信息。并且由于已有项目的依赖包及插件比较老,新项目也一直没有得到更新。即使是自己搭建,为了省时省力,大多会选择通过React提供的脚手架create-react-app创建项目,一行命令全部搞定。从来没有研究过各个模块是如何配置的,下面我跟搭建一起从0开始手动搭建一个React项目(PS:不要在乎我的项目路径之类的,因为我这边用的虚拟机并且只有一个磁盘)

基础环境准备

在开始搭建项目之前先安装好nodejs,安装包去node官网下载即可,下载安装都比较简单,这里不做赘述。安装好以后打开CMD命令行窗口,输入node -v及npm -v,如果正确显示版本信息,则表示安装成功。

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

项目初始化

node装好以后,在磁盘合适位置新建一个文件夹,比如my_react_app,进入该文件夹以后在目录行输入cmd回车使用命令行窗口打开
不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

在打开的窗口目录下输入命令:npm init,回车初始化基础项目框架

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

初始化完成后会在文件夹下面生成一个package.json文件,里面就是在创建项目时指定的基础信息,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

安装webpack

webpack是一个静态模块打包工具,可以将我们的代码打包成浏览器可以认识的文件。webpack一般配合webpack-cli一起使用,webpack命令的使用依赖webpack-cli

webpack安装分为全局安装跟局部安装:
全局安装,打包时用的全局的webpack,那么多人开发时可能由于电脑上版本不一样导致打包的版本不一致。
局部安装,每个项目版本固定,统一依赖,避免打包时版本不一致问题

npm install webpack webpack-cli -g  #全局安装
npm install webpack webpack-cli -D  #局部安装

命令行窗口进入项目目录,执行局部安装命令,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

安装完成以后,项目根目录下会生成package-lock.json文件以及node_modules文件夹。其中node_modules里面是各种依赖包,说一下package-lock.json跟package.json的区别:
package.json记录项目中所需的所有模块信息,但不会记录这些模块所依赖的子模块信息及版本。在npm5以前没有package-lock.json文件,所以要保存依赖信息需要在后面加上 --save 参数,而在npm5以后则不需要了,所以package-lock.json文件保存的是项目所需模块及其子依赖包的版本信息,相对较全,并且可以锁定版本,防止自动升级。所以当我们删了依赖包想要快速恢复时,可以直接执行npm install,此时node会从package.json中读取模块名称并且从package-lock.json中读取版本信息。如果我们想更新某个模块版本,需要执行npm install packagename或者npm install packagename@X.X.X,前者不指定版本号则更新为当前最新版本,后者按照指定版本号更新,package-lock.json文件也会自动更新记录当前版本信息。

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

创建目录结构,验证打包

在项目根目录建一个src文件夹,一个public文件夹。src下面用于存放我们的项目代码,在里面建一个index.js文件,作为入口文件,public下面建一个index.html,这是我们项目运行的主界面。再在项目根目录下建一个webpack.config.js文件,写我们的webpack配置代码,这就是当前基本的项目结构,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

OK,接下来我们写一点基础代码验证一下基础搭建是否成功:

在webpack.config.js中写入基础配置,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

安装一个html-webpack-plugin插件,该插件是可以让webpack按照指定模板生成主界面,安装好以后再在配置文件中添加进去,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

然后在index.js里随便写点代码验证打包,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

写好以后在终端输入打包命令:npm run build,可以看到根目录下生成了一个dist文件夹,里面有一个index.html文件及一个main.js文件,这就是按照配置文件生成的两个文件,查看main.js可以看到我们刚刚在index.js里写入的代码编译结果,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

JSX语法、ES5+语法及高级API支持

我们在react项目中写的JSX语法代码以及ES6等各种ES5+的代码,浏览器是无法解析的,所以我们需要将这些转换为浏览器认识的东西,这个事情是由babel插件去完成。
执行以下命令安装插件:

npm install babel-loader @babel/core @babel/preset-env @babel/preset-react --save-dev

其中babel-loader是webpack的loader,用来预处理文件,告诉webpack当遇到js文件时交给babel处理,至于怎么处理跟webpack无关,取决于babel的配置。babel/core是babel的核心库,提供转换的API,babel/preset开头的代表预设,@babel/preset-env用来将ES5+的高级语法转化为es5,babel/preset-react用来解析jsx语法,具体的可以看babel官网

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

下载完以后,package.json配置文件中可以看到对应模块信息,package-lock.json也会同步更新进去。然后需要配置babel,可以通过添加babel配置文件的形式也可以直接在webpack配置文件中作为某条规则的optionsi写入。我这里使用官网推荐的方式,在项目根目录下新建一个叫babel.config.json的配置文件(需要 v7.8.0 或更高版本,如果是旧版本的babel,则命名为babel.config.js),并写入如下配置信息:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

然后在webpack.config.js文件中添加babel-loader插件的配置信息,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

这个时候我们就可以在index.js中写点ES6语法的代码,验证下打包后是否成功转换为ES5代码,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

执行打包命令后,可以看到箭头函数已经成功转换为ES5语法代码,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

接下来我们写点使用新API的代码,比如熟悉的promise,再次打包查看结果如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

可以看到有报错,因为@babel/preset-env 只能转换ES6等高级语法,并不能转换例如promise、async等新的API,然后我们在上面配置了polyfill的按需引入以及corejs参数,但是还没有安装插件,执行以下命令安装 core-js regenerator-runtime(7.4版本以后弃用@babel/polyfill,推荐分开安装),如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

然后再次打包看打包成功的结果,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

接下来安装 @babel/plugin-transform-runtime、@babel/runtime、@babel/runtime-corejs3,用于自动去除语法转换后的内敛函数以优化体积及避免全局变量污染,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

为了可以更明显的看到打包体积变小的效果,新建3个文件,在index.js下引入,执行打包命令,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

再把@babel/plugin-transform-runtime配置加上,查看打包体积,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

可以看到体积大小从294变成了168,明显减少。体积减少的原理就是,babel在转换的时候有时候需要用一些辅助函数,如果多个文件都有相同的语法,那每个文件都会注入相同的一份辅助函数,而针对这种情况 @babel/plugin-transform-runtime 会自动将需要引入的 helpers 函数替换为从 @babel/runtime 中的引用,这样就只使用了一份,从而减少体积。

Less支持,Antd按需引入

由于antd组件是基于less方式开发的,所以我们也得配置一下支持less预处理器编写的代码转换为css,当前支持的方式主要有三种:
第一种:react-app-rewired + customize-cra,antd@3X版本提出的方案
第二种:antd@4.19.4文档提出的craco方案
第三种:安装loader插件

我们这里使用第三种,安装需要的几种loader,执行以下命令:

npm install less less-loader css-loader style-loader -D

npm install babel-plugin-import

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

其中css-loader是将css文件编译为webpack能识别的模块,less-loader负责将css文件编译为css文件,style-loader会动态生成style标签并将css内容放进去进行渲染,less是常用的预处理器,而babel-plugin-import是用于配置antd按需引入。

顺便提一句,我们在安装依赖或者插件的时候可能会出现下面的错误(没有报错或者不使用该方式请忽略),比如我在使用craco方式的时候,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

可以看到报错了,错误提示为无法解析依赖树,简单来说就是依赖冲突了,这是因为在npm@7.X中,默认安装peerDependencies,这在很多情况下都会导致版本冲突,从而中断安装过程。可以通过降npm版本到7以前(比如npm@6)解决,也可以通过npm7引入的--legacy-peer-deps解决,该参数的目的是告诉npm忽略项目中引入的各个modules之间的相同modules但不同版本的问题并继续安装,保证各个引入的依赖之间对自身所使用的不同版本modules共存(原文链接)。我这里选择在后面加上该参数重新安装,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

接下来安装antd,由于antd@5X版本变动较大(比如:去除了less),还没仔细研究~,我这里暂时用4X版本了,各位可以根据具体项目选择,执行如下命令:

npm install antd@4 --legacy-peer-deps

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

安装完以后在babel.config.json配置文件中配置antd按需引入,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

接下来在src目录下新建一个index.less文件,写点less语法代码,并在index.js中引入,验证打包,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

执行打包命令,可以看到已经成功解析,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

另外我们在开发过程中经常会需要兼容各种浏览器,如果手动添加前缀会很麻烦,所以我们可以通过配置postcss-loader来自动添加前缀,执行安装:

npm install --save-dev postcss-loader postcss postcss-preset-env

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

安装完以后在webpack.config.js中加入插件配置,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

然后我们可以在package.json文件中添加 browserslist 来控制样式的兼容性做到什么程度,比如:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

集成React

要使用react,首先需要安装react跟react-dom,如下:

npm install react react-dom

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

安装好以后,我们就可以写react独特的JSX语法代码了,还记得在上面的时候,我已经安装过@babel/preset-react,这个就是实现将jsx转换为ES5代码的插件,下面我们在index.js里面写点代码,然后在index.html中添加一个div承载需要渲染的组件。如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

执行打包命令看下效果,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

热加载

到这一步基本能用了,但是我们会发现每次改点什么都得手动重新编译,而且每次重新编译之前都要手动删除dist文件夹及里面的内容,很不方便。我们引入webpack-dev-server及clean-webpack-plugin,其中webpack-dev-server相当于提供一个本地的web服务,同时具有live reloading(实时重新加载)功能,而clean-webpack-plugin插件则是每次打包前自动删除上次打包的东西,而之前安装的html-webpack-plugin插件可以将我们编译好的js及css文件动态添加到html中。执行安装命令,如下:

npm install -D clean-webpack-plugin webpack-dev-server

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

安装完以后,在webpack配置文件中添加配置,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

然后再package.json中添加启动命令,如下:

"start": "webpack-dev-server --open-app-name chrome" --后面参数代表启动后自动打开谷歌浏览器

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

配置好以后,输入npm start启动,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

可以看到项目成功运行,之前写的那点字体为红色的样式也渲染到了html中,不过控制台有两个警告报错,虽然不影响正常运行,但作为前端开发人员,看到控制台有红色就难受~~,一起看一下:
第一个报错大概意思是ReactDOM.render在react18中已经不支持了,让我们用createRoot替代,第二个错误是说LocaleProvider已弃用,请将“locale”与“ConfigProvider”一起使用。我们来试一下,修改index.js文件如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

我们配置了热加载,修改完点保存后,webpack会自动重新编译且浏览器自动更新修改的地方,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

另外。为了方便开发过程中定位问题,可以配置在开发环境时,生成源码,如下:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

具体项目开发时,可以把webpack配置文件进行拆分,分别给生产环境及开发环境进行不同的配置,具体的可以参考其他资料,这里不做说明,重新启动看下效果:

不借助脚手架手动搭建react项目(webpack5 + Antd4 + React18)

OK,到这里就结束了,另外还有很多可以优化的配置,比如css提前,图片资源解析等等,可以参考官网进行相应配置。说一下我在搭建过程中遇到认为比较需要注意的一点就是,要注意版本差异。很多地方会由于webpack版本差异存在写法不同,也可能依赖包之间会存在冲突。然后npm下载依赖的时候应不应该带参数,建议去npm官网package下看推荐,愿我们都在踩坑的过程中越战越勇,一起进步!