「React Build」之二集成 Webpack5/React17
在上一章节中我们已经使用 TypeScript/ESlint/Prettier/EditorConfig/stylelint,搭建好了前端规范的基础设施。这篇文章将会使用 webpack5 构建一个 React 应用。
在我们的 Webpack 构建的过程中将会包含
- 使用 Typescript 进行类型检查
- 使用 Eslint 进行代码规范检查
因为这些能帮助我们提高代码质量。我们还会
- 配置 webpack 热更新
- 配置 webpack 以区分开发/生产环境
以便提高我们的开发体验
创建一个基础项目
使用 npm init -y
初始化一个前端项目,这会自动生成package.json
文件。当我们安装项目依赖的时候,这个文件会自动更新
接下来我们创建以下文件目录
└── config/ // webpack配置文件
├── webpack.dev.js
├── webpack.pro.js
├── webpack.common.js
└── public/
├── index.html/ // html模板文件
└── src/
├── index.tsx // 项目入口文件
├── package.json
让我们在 index.html
中添加如下代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>react-app</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
这个 HTML 文件是 Webpack 构建过程中的模板文件。目的是告诉 Webpack 将 React 代码注入到 id="root"
的 div 元素中,并在 HTML 中自动引入打包好的 JavaScript 和 CSS。
添加 React
在 CLI 中安装 React 及其对应的类型库
yarn add react react-dom
yarn add @types/react @types/react-dom --dev
添加 React 根组件
创建一个 src/index.tsx
来编写 React 组件,此代码将会被展示到index.html
文件id="root"
的 div 元素下
import React from "react";
import ReactDOM from "react-dom";
const App = () => <h1>My React and TypeScript App!</h1>;
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
在上面的代码中,我们使用React.StrictMode 创建组件并插入到id="root"
的 div 元素下
添加 Babel
在项目中,我们需要使用 Babel 将 React 和 TypeScript 代码转换为 JavaScript。接下来我们安装一些 Babel 工具
yarn add @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @babel/plugin-transform-runtime @babel/runtime --dev
以下是一些 Babel 依赖的解释
@babel/core
:Babel 核心库@babel/preset-env
:让我们可以在不支持 JavaScript 最新特性的浏览器中使用 ES6+语法@babel/preset-react
:将 React 代码转换为 JavaScript@babel/preset-typescript
:将 TypeScript 代码转换为 JavaScript@babel/plugin-transform-runtime
和@babel/runtime
:支持在低版本浏览器使用 ES6+语法,如async/await
Babel 配置
我们通过.babelrc
文件来进行 Babel 配置,在根目录创建此文件并加入以下内容
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}
上面的配置是告诉 Babel 使用哪些插件(为了减少多文件配置麻烦,后面我们把此配置转移到 Webpack 中去)
添加 Webpack
Webpack 是目前最流行的前端模块打包工具
接下来我们开始安装 Webpack 依赖
yarn add webpack webpack-cli @types/webpack --dev
在开发环境中,我们还要使用 Webpack 为我们提供的 web server功能
yarn add webpack-dev-server @types/webpack-dev-server --dev
安装babel-loader
-通知 Babel 将 React 和 TypeScript 代码转换为 JavaScript
yarn add babel-loader --dev
安装 html-webpack-plugin
-用来生成 HTML 模板
yarn add html-webpack-plugin --dev
开发环境配置
我们需要为 Webpack 添加几个配置文件
- 公共配置
- 开发环境配置
- 生产环境配置
注: Webpack 配置文件的代码需要符合 CommonJs
规范。
让我们首先配置开发环境文件,在根目录创建 config/webpack.dev.js
并加入以下内容
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const config = {
mode: "development",
entry: {
main: path.resolve(__dirname, "../src/index.tsx"),
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "../build"),
},
module: {
rules: [
{
test: /\.(ts|js)x?$/i,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
regenerator: true,
},
],
],
},
},
},
],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "../src"),
},
extensions: [".tsx", ".ts", ".jsx", ".js"],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
}),
new webpack.HotModuleReplacementPlugin(),
],
devtool: "inline-source-map",
devServer: {
contentBase: path.join(__dirname, "../build"),
historyApiFallback: true,
port: 4000,
hot: true,
},
};
module.exports = config;
下面是一些相关配置的解释
mode
: 构建开发环境代码还是生产环境代码。在上面的配置中我们使用development
. Webpack 会自动将process.env.NODE_ENV
设置为development
output.public
:构建的根路径是什么。entry
:模块构建的入口文件.在我们的项目中,入口是src/index.tsx
module
: 用于处理不同的资源模块.在我们的项目中,用babel-loader
来处理.js
,.jsx
,.js
,.tsx
后缀的文件resolve.alias
: 可以让我们在引入模块路径时使用别名resolve.extensions
告诉 Webpack 在模块解析期间要按顺序查找哪些文件的后缀,以方便我们在在引入模块文件时不带后缀名。HtmlWebpackPlugin
:用来创建 HTML 文件.在上面的配置中,我们告诉此插件使用public/index.html
作为文件模板HotModuleReplacementPlugin
/devServer.hot
:修改业务代码后界面可以自动局部刷新,而不是整体刷新devtool
: 使用inline-source-map
,可以在让我们在谷歌开发工具中调试源代码devServer
: 启动 Webpack 开发服务器,我们告诉 Webpack web 服务的根路径是build
目录,并且在4000
端口上启动服务.historyApiFallback
对于多页面应用是比较有用的。最后,使用open
在服务启动后自动打开浏览器
为开发环境添加 NPM 脚本
为了方便以开发模式启动应用,可以利用 npm 脚本-将以下内容添加到package.json
中
...
"scripts": {
"start": "webpack serve --config config/webpack.dev.js",
}
...
以上脚本会启动一个 Webpack 下的开发环境服务器.并且使用 config
选项来引用开发环境配置文件
启动应用
yarn start
N 秒后,Webpack development server 将会启动,然后我们在浏览器中访问http://localhost:4000
注意:Webpack 并没有在 build 目录生成任何文件,这是因为 Webpack 服务启动后文件都在内存中
现在,我们修改 React 代码内容并观察变化.当我们保存代码后,浏览器会自动刷新
在 Webpack 中手动配置热更新插件
可能因为各种各样的原因导致 webpack 的 HMR 不生效。我们还可以手动配置热更新插件!
安装 React 热更新插件react-refresh-webpack-plugin
yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
修改config/webpack.dev.js
并加入以下内容
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const config = {
// 指定target为 web
target: "web",
module: {
rules: [
{
test: /\.(t|j)sx?$/i,
include: path.resolve(__dirname, "../src"),
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
regenerator: true,
},
],
// 热更新加载器
"react-refresh/babel",
],
},
},
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
// 热更新插件
new ReactRefreshWebpackPlugin({
exclude: [/node_modules/],
}),
],
};
如果热更新配置遇到问题,可以参考以下 issue
- Webpack 5 does not re render
- HMR/Live Reloading broken after Webpack 5
在 webpack 构建过程中添加类型检查
目前, Webpack 构建过程没有做任何类型检查,我们可以使用fork-ts-checker-webpack-plugin
让 Webpack 构建过程支持类型检查。这意味着 Webpack 会通知我们任何类型相关的错误。 接下来我们安装相关依赖
yarn add fork-ts-checker-webpack-plugin @types/fork-ts-checker-webpack-plugin --dev
在webpack.dev.js
添加如下配置
...
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const config = {
...,
plugins: [
...,
new ForkTsCheckerWebpackPlugin({
async: false
}),
],
};
我们使用 async
标志来告诉 Webpack 等待代码的类型检查结束,然后才提交代码进行编译
修改后,我们需要重新启动应用
让我们在 src/index.tsx
中做如下修改
...
const App = () => <h1>My React and TypeScript App!! {today}</h1>;
...
当然,控制台报错了,因为使用了一个未定义的变量today
。Webpack 将在终端中显示此类型错误
现在,可以修改为类似的如下代码来解决此问题
const App = () => (
<h1>My React and TypeScript App!! {new Date().toLocaleDateString()}</h1>
);
控制台的类型错误消失了,刷新浏览器界面后显示为正确的内容
在 webpack 构建过程中添加代码规范校验
目前,Webpack 构建流程不会执行代码规范校验。 我们可以使用ESLintPlugin
来使 Webpack 构建过程能够使用 ESLint 进行代码规范校验。 这意味着 Webpack 会通知我们任何代码规范校验的错误。 让我们安装这个依赖
yarn add eslint-webpack-plugin --dev
在 webpack.dev.js
修改如下内容
...
const ESLintPlugin = require('eslint-webpack-plugin')
const config = {
...,
plugins: [
...,
new ESLintPlugin({
extensions: ["js", "jsx", "ts", "tsx"],
}),
],
};
在 src/index.tsx
中,添加一个未使用的变量
const unused = "something";
Webpack 将会在控制台出现如下的代码校验警告
生产环境配置
Webpack 的生产环境配置与开发环境有些不同-我们需要项目代码被打包到文件目录中,并且做一定的优化
- 不需要热更新/代码规范校验等功能
- 为打包的文件名生成 hash 串
- 清空打
build
目录 - 压缩代码
- ......
让我们创建webpack.pro.js
并加入以下内容
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const config = {
mode: "production",
entry: {
main: path.resolve(__dirname, "../src/index.tsx"),
},
output: {
filename: "[name].[contenthash].js",
publicPath: "",
path: path.resolve(__dirname, "../build"),
// 打包前清空输出目录
clean: true,
},
module: {
rules: [
{
test: /\.(ts|js)x?$/i,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
},
},
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
}),
],
};
module.exports = config;
配置与开发环境很像,但是又有以下不同
-
我们将
mode
设置为 production. Webpack 会自动将process.env.NODE_ENV
设置为production
.这意味着打包后的代码中不会包含 React 开发者工具 -
output
告诉 Webpack 将打包后的资源放到哪里.在我们的项目中,是放在build
目录中.- 如果项目中做了代码分离(code split).我们使用
[name]
标志告诉 Webpack 分离后的文件名称 - 同时将
[contenthash]
标志加入到文件名称中.以便在代码内容更改后,打包以生成新的文件名称。这就可以避免浏览器缓存旧的文件 clean: true
用来在每次打包构建前清空build
目录,而不需要额外的插件,比如CleanWebpackPlugin
- 如果项目中做了代码分离(code split).我们使用
为生产环境添加 NPM 脚本
让我们为生产环境添加 NPM 脚本
...,
"scripts": {
"build": "webpack --config config/webpack.pro.js",
},
...
该脚本可以启动 Webpack 打包流程。 我们使用config
选项来引用我们刚刚创建的生产配置文件。
在终端运行以下命令:
npm run build
N 秒后,Webpack 将会在 build
目录生成打包后的文件
如果我们查看 JavaScript 文件,可以发现它是被压缩过的。因为 Webpack 在生产模式会使用TerserWebpackPlugin
来压缩代码。
打包后的 JavaScript 文件也包含了我们应用程序中的所有代码以及 react
和 react-dom
依赖包中的代码。
如果我们查看 html 文件,会发现所有空格/换行都已被删除。 如果仔细观察,我们会看到一个 script 元素,该元素是通过HtmlWebpackPlugin
自动插入的,以便引用打包后的 JavaScript 文件。
抽离 Webpack 的公共配置
虽然,我们将 生产环境 和 开发环境 做了区分,但是我们还是应该遵循不重复原则(Don't repeat yourself - DRY),保留一个 "common(通用)" 配置。为了将这些配置合并在一起,我们使用一个名为 webpack-merge
的工具。此工具会引用 "common" 配置,因此我们不必再在环境特定(environment-specific)的配置中编写重复代码。 参考文档
我们先从安装 webpack-merge
开始
yarn add webpack-merge --dev
添加 config/webpack.common.js
文件并加入以下配置
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
main: path.resolve(__dirname, "../src/index.tsx"),
},
module: {
rules: [
{
test: /\.(ts|js)x?$/i,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
regenerator: true,
},
],
],
},
},
},
],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "../src"),
},
extensions: [".tsx", ".ts", ".js"],
},
plugins: [
new HtmlWebpackPlugin({
title: "React Build",
template: "public/index.html",
}),
],
};
修改 config/webpack.dev.js
的配置
const path = require("path");
const webpack = require("webpack");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "development",
output: {
filename: "[name].js",
path: path.resolve(__dirname, "../build"),
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ForkTsCheckerWebpackPlugin({
async: false,
}),
new ESLintPlugin({
extensions: ["js", "jsx", "ts", "tsx"],
}),
],
devtool: "inline-source-map",
devServer: {
contentBase: path.join(__dirname, "../build"),
historyApiFallback: true,
port: 4000,
hot: true,
},
});
修改config/webpack.prod.js
的配置
const path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "production",
output: {
filename: "[name].[contenthash].js",
publicPath: "",
path: path.resolve(__dirname, "../build"),
// 打包前清空输出目录
clean: true,
},
});
在 webpack.common.js
中,我们设置了 entry
和 output
配置,并且在其中引入了开发/生产环境公用的全部插件。
在 webpack.dev.js
中,我们将 mode
设置为 development
,并且为此环境添加了推荐的 devtool
(强大的 source map)和简单的 devServer
配置。
在 webpack.prod.js
中,我们将 mode
设置为 production
随便运行下 NPM 脚本,然后查看输出结果的变化都能按预期所展示
完美! ? 现在我们的项目已经准备就绪,并可以有效地开发 React 和 TypeScript 应用程序了。通过 build 命令也可以轻松地将项目集成到 CI / CD 流程中
最后
下一篇文章是 「React Build」之三集成 css/less/sass/antd design.敬请期待
如果本文对你有帮助的话,给本文点个赞吧。
参考文档
- Webpack5 新特性业务落地实战-字节前端
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!