创建一个新的目录,用于搭建React
项目。
mkdir react-cli
cd react-cli
初始化项目生成package.json
文件。
npm init -y
新建config
目录文件夹,在创建开发配置文件,并定义好初始配置结构。
文件名:webpack.dev.js
module.exports = {
// 入口
entry: '',
// 输出
output: '',
// 存放loader的module
module: {
rules: []
},
// 需要加载的插件plugins
plugins: [],
// 关于压缩的配置项
optimization: {},
// 开发模式mode
mode: 'development'
}
entry: './src/main.js'
output: {
path: undefined, // 开发环境不需要打包,可以设置路径为undefined
filename: 'static/js/[name].js', // 指定bundle资源的路径以及命名
chunkFilename: 'static/js/[name].chunk.js', // 一般是动态导入的一些资源
assetModuleFilename: 'static/media/[hash:10][ext][query]', // 一些静态资源如图片等
},
需要处理的有css资源、图片资源、js资源。
主要的loader有:style-loader
、css-loader
、less-loader
、sass-loader
、stylus-loader
。
处理样式的兼容性:postcss-loader
配置兼容的程度:需要在package.json
里面配置browserslist
属性
1、封装通用样式处理loader函数
由于样式文件有多种(.css|.less|.sass|.scss|.styl),一个个配置会代码冗余,所以使用通用函数来复用代码,减少配置代码的冗余。
// 处理样式loader的通用函数
const getStyleLoader = (pre) => {
return [
'style-loader','css-loader',{
// 处理css兼容性
// 需要配合package.json的browserlist属性来决定兼容性
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['postcss-preset-env']
}
}
},
pre
].filter(Boolean)
}
2、结合通用函数配置样式loader
module: {
rules: [
{
test: /\.css$/i, // 以css结尾的文件
use: getStyleLoader()
},
{
test: /\.less$/i, // 以less结尾的文件
use:getStyleLoader('less-loader')
},
{
test: /\.s[ac]ss$/i, // 以sass/scss结尾的文件
use:getStyleLoader('sass-loader')
},
{
test: /\.styl$/i, // 以styl结尾的文件
use:getStyleLoader('stylus-loader')
}
]
},
3、配置browserlist
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
]
// 处理图片资源
{
test: /\.(jpe?g|png|webp|svg|gif)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 当图片小于10kb时转为base64
}
}
},
// 处理其他资源
{
test: /\.(woff2?|ttf|mp3|mp4)$/,
type: 'asset/resource'
}
// webpack.dev.js
const path = require('path')
//...省略
{
test: /\.(jsx|js)$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
// "@babel/plugin-transform-runtime", // presets中包含了
"react-refresh/babel", // 开启js的HMR功能
],
},
},
// babel.config.js
module.exports = {
// 使用react官方规则
presets: ["react-app"],
};
// webpack.dev.js
const path = require('path')
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
//...省略
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", // 排除的目录
cache: true, // 开启缓存
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
}),
]
// .eslintrc.js
module.exports = {
extends: ["react-app"], // 继承 react 官方规则
parserOptions: {
babelOptions: {
presets: [
// 解决页面报错问题
["babel-preset-react-app", false],
"babel-preset-react-app/prod",
],
},
},
};
// webpack.dev.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
//...省略
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
]
// webpack.dev.js
// 开发模式mode
mode: 'development'
// webpack.dev.js
devtool: "cheap-module-source-map"
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
devServer: {
open: true,
host: "localhost",
port: 3000,
hot: true,
compress: true,
historyApiFallback: true, // 解决react-router刷新404问题
},
src/main.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("app"));
root.render(
<App />
);
src/App.jsx
import React from "react";
function App() {
return (
<div>
<h1>App</h1>
</div>
);
}
export default App;
public/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</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
npm i webpack webpack-cli webpack-dev-server -D
npm i eslint-webpack-plugin -D
npm i html-webpack-plugin -D
npm i style-loader css-loader less-loader sass-loader sass stylus-loader -D
npm i postcss-loader postcss-preset-env -D
npm i babel-loader @babel/core babel-preset-react-app eslint-config-react-app -D
npm i react react-dom
"scripts": {
"start": "npm run dev",
"dev": "webpack serve --config ./config/webpack.dev.js"
},
npm start
Error: [BABEL] D:\MyWorkSpace\VUE3_WORKSPACE\react-cli\node_modules\webpack-dev-server\client\index.js: Using `babel-preset-react-app` requires that you specify `NODE_ENV` or `BABEL_ENV` environment variables. Valid values are "development", "test", and "production". Instead, received: undefined.
1、安装cross-env
npm i cross-env -D
2、修改启动命令
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js"
},
将开发环境变量传给配置
npm start
ERROR in ./src/main.js 4:0-24
Module not found: Error: Can't resolve './App' in 'D:\MyWorkSpace\VUE3_WORKSPACE\react-cli\src'
resolve './App' in 'D:\MyWorkSpace\VUE3_WORKSPACE\react-cli\src'
原因是后缀为jsx的文件,webpack无法识别。
resolve: {
extensions: [".jsx", ".js", ".json"], // 自动补全文件扩展名,让jsx可以使用
},
再次启动就可以正常运行了。
css默认在style-loader里面就已经激活了HMR功能,我们只需要激活js的HMR功能就好。
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
1、配置babel-loader
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
// "@babel/plugin-transform-runtime", // presets中包含了
require.resolve('react-refresh/babel'), // 开启js的HMR功能
],
},
}
2、开启插件
plugins: [
// ...省略
new ReactRefreshWebpackPlugin(), // 解决js的HMR功能运行时全局变量的问题
]
1、src/main.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import {BrowserRouter} from 'react-router-dom'
import App from './App'
//..
const root = ReactDOM.createRoot(document.getElementById('app'))
root.render(<div>
<BrowserRouter>
<App />
</BrowserRouter>
</div>)
2、src/App.jsx
import React from "react";
import {Link, Route, Routes} from "react-router-dom"
import Home from "./pages/home"
import About from "./pages/about"
function App() {
return (
<div>
<h1>App1</h1>
<ul>
<li><Link to="/home">Home</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
);
}
export default App;
3、src/pages/home/index.jsx
import React from "react";
export default function Home() {
return <h1>Home~~~</h1>;
}
4、src/pages/about/index.jsx
import React from "react";
export default function Home() {
return <h1 >about~~~</h1>;
}
npm i react-router-dom
npm run dev
在浏览器可以切换路由,但是在路由下刷新页面会出现404错误。
GET http://localhost:3000/home 404 (Not Found)
devServer: {
//...
historyApiFallback: true, // 解决react-router刷新404问题
}
重新启动项目即可。
1、src/App.jsx
import React, { Suspense, lazy } from 'react'
import { Link, Route, Routes } from 'react-router-dom'
// import Home from './pages/home'
// import About from './pages/about'
const Home = lazy(()=>import(/*webpackChunkName: 'Home'*/"./pages/home"))
const About = lazy(()=>import(/*webpackChunkName: 'About'*/"./pages/about"))
function App() {
return (
<div>
<h1>App1</h1>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<Suspense fallback={<div>loading...</div>}>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</div>
)
}
export default App
npm run dev
开启懒加载后,其路由代码会被单独打包到一个chunk里面,实现按需加载。
const path = require('path')
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// 处理样式loader的通用函数
const getStyleLoader = (pre) => {
return [
'style-loader','css-loader',{
// 处理css兼容性
// 需要配合package.json的browserlist属性来决定兼容性
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: ['postcss-preset-env']
}
}
},
pre
].filter(Boolean)
}
module.exports = {
// 入口
entry: './src/main.js',
// 输出
output: {
path: undefined, // 开发环境不需要打包,可以设置路径为undefined
filename: 'static/js/[name].js', // 指定bundle资源的路径以及命名
chunkFilename: 'static/js/[name].chunk.js', // 一般是动态导入的一些资源
assetModuleFilename: 'static/media/[hash:10][ext][query]', // 一些静态资源如图片等
},
// 存放loader的module
module: {
rules: [
{
test: /\.css$/i, // 以css结尾的文件
use: getStyleLoader()
},
{
test: /\.less$/i, // 以less结尾的文件
use:getStyleLoader('less-loader')
},
{
test: /\.s[ac]ss$/i, // 以sass/scss结尾的文件
use:getStyleLoader('sass-loader')
},
{
test: /\.styl$/i, // 以styl结尾的文件
use:getStyleLoader('stylus-loader')
},
// 处理图片资源
{
test: /\.(jpe?g|png|webp|svg|gif)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 当图片小于10kb时转为base64
}
}
},
// 处理其他资源
{
test: /\.(woff2?|ttf|mp3|mp4)$/,
type: 'asset/resource'
},
// babel处理js|jsx资源
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
// "@babel/plugin-transform-runtime", // presets中包含了
'react-refresh/babel', // 开启js的HMR功能
],
},
}
]
},
// 需要加载的插件plugins
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
new ReactRefreshWebpackPlugin()
],
// 关于压缩的配置项
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
resolve: {
extensions: [".jsx", ".js", ".json"], // 自动补全文件扩展名,让jsx可以使用
},
devServer: {
open: false,
host: "localhost",
port: 3000,
hot: true,
historyApiFallback: true, // 解决react-router刷新404问题
// static: {
// directory: path.join(__dirname, "../"),
// },
},
// 开发模式mode
mode: 'development',
devtool: "cheap-module-source-map"
}