概述
本文旨在通过从0到1搭建一套完整的React开发框架来掌握如Webpack
、React
、Typescript
、 CSS preprocessor
等各部分是如何协同进行编译开发的,进而能去了解如creat-react-app
之类的框架都做了哪些事情。
同时本文还提供了业务层面的如路由react-router
、状态管理器redux-toolkit
的配置和使用方式。
TIPS: 本文中所有包版本均采用当前最新版本,注意区别于如webpack、react、react-router等模块的旧版本使用方式。
一、初始化项目
yarn init
yarn init或npm init都是OK的
二、构建核心打包环境
- 添加依赖
yarn add webpack webpack-cli webpack-dev-server -D
- 依赖模块说明
模块名 | 描述 | 版本 |
webpack | 模块化打包工具,打包代码时的核心依赖 | ^5.72.1 |
webpack-cli | 支持在命令行中执行webpack的工具 | ^4.9.2 |
webpack-dev-server | 本地开发服务器,便于开发 | ^4.9.0 |
webpack-merge | 用于合并webpack-config文件 | ^5.8.0 |
- webpack.config.js 配置文件
webpack.config.js是webpack的默认配置文件,也可以在命令行中通过--config指定配置文件。
一般在项目中会区分production、development环境的配置文件,这里我们设计配置文件为:公共配置文件,开发环境配置文件,产品环境配置文件,目录结构如下
config
-- webpack.config.base.js
-- webpack.config.dev.js
-- webpack.config.prod.js
webpack.config.base.js 公共配置文件
const path = require("path");
/**
* @method resolve
* @description 从根路径开始查找文件
*/
const resolve = (targetPath) => {
return path.resolve(__dirname, "..", targetPath);
};
module.exports = {
target: "web",
// 入口文件
entry: {
main: resolve("./src/index.js"),
},
// 输出
output: {
// 文件名称
filename: "[name].[contenthash].js",
// 输出目录
path: resolve("./dist"),
// 每次编译输出的时候,清空dist目录 - 这里就不需要clean-webpack-plugin了
clean: true,
// 所有URL访问的前缀路径
publicPath: "/",
},
resolve: {
// 定义了扩展名之后,在import文件时就可以不用写后缀名了,会按循序依次查找
extensions: [".js", ".jsx", ".ts", ".tsx", ".json", ".css", ".less"],
// 设置链接
alias: {
// 注意resolve方法开始的查找的路径是/
"@": resolve("./src"),
},
},
};
webpack.config.dev.js 开发环境配置文件
// merge,合并两个或多个webpack配置文件
const { merge } = require("webpack-merge");
// 导入公共配置文件
const webpackConfigBase = require("./webpack.config.base");
// dev环境下相关配置
module.exports = merge(webpackConfigBase, {
// 指定环境
mode: "development",
// 输出source-map的方式,增加调试。eval是默认推荐的选择,build fast and rebuild fast!
devtool: "eval",
// 本地服务器配置
devServer: {
// 启动GZIP压缩
compress: true,
// 设置端口号
port: 3000,
// 代理请求设置
proxy: {
"/api": {
// 目标域名
target: "http://xxxx.com:8080",
// 允许跨域了
changeOrigin: true,
// 重写路径 - 根据自己的实际需要处理,不需要直接忽略该项设置即可
pathRewrite: {
// 该处理是代码中使用/api开头的请求,如/api/userinfo,实际转发对应服务器的路径是/userinfo
"^/api": "",
},
// https服务的地址,忽略证书相关
secure: false,
},
},
},
});
weback.config.build.js 线上产品环境配置文件
const { merge } = require("webpack-merge");
const webpackConfigBase = require("./webpack.config.base");
module.exports = merge(webpackConfigBase, {
// 指定打包环境
mode: "production",
});
- package.json命令行
在package.json的scripts中添加一下命令:
scripts: {
"dev": "webpack serve --config config/webpack.config.dev",
"build": "webpack build --config config/webpack.config.prod"
}
开发环境运行:yarn dev
生产环境运行:yarn build
三、添加HTML模板文件
- 添加依赖
yarn add html-webpack-plugin -D
- 创建模板文件
目录结构如下
public
-- index.html
-- logo.ico
- 更新webpack配置
在webpack.config.dev.js中添加如下配置:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
// HTML模板文件
template: resolve("./public/index.html"),
// 收藏夹图标
favicon: resolve("./public/logo.ico"),
}),
]
// ...
}
四、解析React
- 添加依赖
# 添加react、react-dom
yarn add react react-dom
# 添加babel等loader
yarn add babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/preset-react core-js@3 -D
- 依赖模块说明
模块名 | 描述 | 版本 |
react | 还用说是啥吗? 核心代码 | ^18.1.0 |
react-dom | 还用说是啥吗? 浏览器端实现 | ^18.1.0 |
babel-loader | 识别ES6语法,编译js | ^8.2.5 |
@babel/core | babel处理的核心逻辑 | ^7.18.2 |
@babel/preset-env | 根据一些预设的目标值转换js语法,会打包一些polyfill | ^7.18.2 |
@babel/preset-react | 帮助识别jsx语法,解析react | ^7.17.12 |
@babel/plugin-transform-runtime | 优化解决preset-env打包的polyfill会污染全局的问题 | ^7.18.2 |
core-js | polyfill的核心实现,选择3版本 | 3 |
- 添加webpack的loader配置
loader是webpack的文件处理器,让webpack能够处理其他类型的文件,并转为有效的模块,以供程序使用,以及被添加到依赖图中。
webpack.config.base.js文件
module.exports = {
// ...
module: {
rules: [
{
// 匹配js/jsx
test: /\.jsx?$/,
// 排除node_modules
exclude: /node_modules/,
use: {
// 确定使用的loader
loader: "babel-loader",
// 参数配置
options: {
presets: [
[
// 预设polyfill
"@babel/preset-env",
{
// polyfill 只加载使用的部分
useBuiltIns: "usage",
// 使用corejs解析,模块化
corejs: "3",
},
],
// 解析react
"@babel/preset-react",
],
// 使用transform-runtime,避免全局污染,注入helper
plugins: ["@babel/plugin-transform-runtime"],
},
},
}
]
}
// ...
}
- 创建React组件
创建App.jsx文件
import React, { useState } from "react";
export default function App () {
return <div className="app">
<h1>Hello Webpack-React</h1>
</div>;
}
入口文件src/index.js
import React from "react";
// 注意这里最新版的ReactDOM是从client中导出的
import ReactDOM from "react-dom/client";
// 因为设置了extensions,所以可以不加扩展名
import App from './App';
// 创建app根节点
const appEl = document.createElement("div");
// 设置id
appEl.id = "app";
// 追加节点到body中
document.body.appendChild(appEl);
// 最新版本使用的是ReactDOM.createRoot
// 如果使用ReactDOM.render()控制台会报warnning错误
const root = ReactDOM.createRoot(appEl);
// 渲染
root.render(<App />);
页面效果图:
效果都不用说,肯定是
五、解析CSS以及CSS预处理器
CSS在webpack中也是作为一个资源被识别的,需要配置相关的loader。CSS预处理器如less/sass/stylus/postcss都可以被相关的loader识别。
- 添加依赖
yarn add css-loader less less-loader style-loader postcss postcss-loader mini-css-extract-plugin cross-env autoprefixer css-minimizer-webpack-plugin -D
- 依赖模块说明
模块名 | 描述 | 版本 |
css-loader | 解析css | ^6.7.1 |
less | 支持less语法 | ^4.1.2 |
less-loader | 解析less | ^11.0.0 |
style-loader | 将解析的css内容追加到head中 | ^3.3.1 |
postcss | 好用好玩的css插件,压缩、自动补全 | ^8.4.14 |
postcss-loader | 解析postcss设置 | ^7.0.0 |
mini-css-extract-plugin | 分离CSS | ^2.6.0 |
cross-env | 好用的环境配置 | ^7.0.3 |
autoprefixer | 自动补全css属性前缀 | ^10.4.7 |
css-minimizer-webpack-plugin | 生产环境,压缩css | ^4.0.0 |
- webpack相关配置
webpack.config.dev.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const isProd = process.env.NODE_ENV === "prod";
module.exports = {
// ...
plugins: [
new MiniCssExtractPlugin({
// 输出的每个css文件名称
filename: isProd ? "[name].[contenthash].css" : "[name].css",
// 非入口的chunk文件名 - 通过import()加载异步组件中样式
chunkFilename: isProd ? "[id].[contenthash].css" : "[id].css",
}),
],
module: {
rules: [
{
test: /\.(css|less)$/,
use: [
// 生产环境下直接分离打包css
isProd ? MiniCssExtractPlugin.loader : "style-loader",
{
loader: "css-loader",
},
"less-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
// 浏览器前缀自动补全
plugins: ["autoprefixer"],
},
},
},
],
},
]
}
// ...
}
在webpack的loader中,加载顺序是从右向左依次处理,css/less文件的处理顺序是:postcss-loader -> less-loader -> css-loader -> (style-loader | MiniCssExtractPlugin.loader)
webpack.config.prod.js
在生产环境中,将打包的css文件进行压缩
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = merge(webpackConfigBase, {
// ...
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
},
// ...
})
- package.json更新scripts,增加环境变量
通过cross-env配置环境变量参数,跨平台好用!
"scripts": {
"dev": "cross-env NODE_ENV=dev webpack serve --config config/webpack.config.dev",
"build": "cross-env NODE_ENV=prod webpack build --config config/webpack.config.prod"
},
传递参数NODE_ENV,在webpack的配置文件中可以使用process.env.NODE_ENV
获取值。
判断是否是生产环境
const isProd = process.env.NODE_ENV === 'prod';