初始化环境
mkdir react-project
cd react-project
npm init
执行命令后react-project目录下出现一个package.json文件
{
"name": "react-project",
"version": "1.0.0",
"private": true,
"license": "ISC"
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "achirsh",
}
这时文件目录结构为:
创建基础目录
安装typescript
npm install typescript --save-dev
在根目录下创建tsconfig.json文件
{
"compilerOptions": {
"target": "esnext", // 生成代码的语言版本
"module": "esnext", // 生成代码的模块化标准
"allowJs": true, // 允许ts编译器编译js文件
"lib": [ // 指定要包含在编译中的library
"dom",
"dom.iterable",
"esnext"
],
"skipLibCheck": true, // 跳过声明文件的类型检查
"esModuleInterop": true, // es模块互操作,屏蔽ESModule和CommonJS之间的差异
"allowSyntheticDefaultImports": true, // 允许通过import x from 'y' 即使模块没有显式制定default导出
"strict": true, // 开启严格模式
"forceConsistentCasingInFileNames": true, // 对文件名称强制区分大小写
"noFallthroughCasesInSwitch": true, // 对switch语句启用错误报告
"moduleResolution": "node", // 模块解析(查找)策略
"resolveJsonModule": true, // 允许导入拓展名为.json的模块
"jsx": "react-jsx", // 指定将JSX编译成什么形式
"sourceMap": true,
"noImplicitAny": true,
"paths": {
"@src/*": [
"./src/*"
]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"node_modules",
"build"
]
}
public目录
public文件夹用于存放index.html和ico图标文件
1、在根目录下创建public文件夹,在public下创建index.html
<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
2、在public文件夹下添加favicon.ico文件
scripts目录
scripts文件夹用于存放webpack的一些配置文件
在根目录下创建scripts文件夹,进入scripts,创建tsconfig.json、config.ts、build.ts、start.ts文件
src目录
src文件夹用于存放一些开发的页面、配置文件等
1、安装react、react-dom react-router-dom
npm install react react-dom react-router-dom --save
npm install @types/react @types/react-dom --save-dev
2、在根目录下创建src文件夹,进入src,创建pages、config文件夹
2.1、在src创建root.tsx
import React from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import Index from './pages'
const rootElement: any = document.getElementById('root')
const root = createRoot(rootElement);
root.render(
<BrowserRouter>
<Index />
</BrowserRouter>
)
2.2、进入pages,创建index.tsx、home/index.tsx
安装@loadable/component
npm install @loadable/component --save
npm install @types/loadable__component --save-dev
index.tsx
import React from 'react'
import * as config from "../config/route"
import loadable from "@loadable/component"
import { useLocation, Routes, Route } from "react-router-dom"
const PageElement = loadable((props: { page: string }) => import(`../pages/${props.page}`), { cacheKey: (props) => props.page });
export default function PageIndex(): JSX.Element {
const location = useLocation()
return (
<Routes location={location}>
<Route path={"/"}>
{config.routes.map((item) => {
return (
<Route
path={item.page}
key={`route-${item.page}`}
element={
<PageElement page={item.page} />
}
/>
);
})}
<Route path="*" element={<div>404</div>} />
</Route>
</Routes>
)
}
home/index.tsx
import React from 'react'
export default function Home(): JSX.Element {
return <div>Hello World</div>
}
2.2、进入config,创建route.ts
interface IRoutes {
page: string;
}
export const routes: IRoutes[] = [
{
page: "home"
}
];
此时的目录结构为:
配置webpack
这里使用的是webpack-merge来对开发、生产环境进行拆分与合并。
打开scripts目录下的config.ts文件
安装webpack
npm install webpack webpack-merge --save-dev
配置entry、output
import merge from "webpack-merge"
import { Configuration } from "webpack"
export default function config(config: Configuration): Configuration {
return merge({
mode: config.mode,
devtool: config.devtool,
entry: ["./src/root.tsx"],
output: {
clean: true,
path: resolvePath(__dirname, "../build"),
filename: config.mode === 'production' ? "js/[name].[contenthash:8].js" : "js/bundle.js",
chunkFilename: config.mode === 'production' ? 'js/[name].[contenthash:8].chunk.js' : 'js/[name].chunk.js',
publicPath: process.env.PUBLIC_PATH || "/",
},
})
}
这里我们将production环境的filename与chunkFilename做了hash处理
配置resolve
在上面内容中,page/index.html有一行代码是import * as config from "../config/route",这里我们可以通过配置resolve中的alias来简化引用
import { resolve as resolvePath } from "path"
return merge({
...
resolve: {
alias: {
"@src": resolvePath(__dirname, "../src")
},
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"]
}
})
配置好以后就可以import * as config from "@src/config"引用了
配置module
这里使用了swc去处理javascript编译,如果对babel感兴趣的也可以去了解下
安装swc、@swc/core
npm install @swc/core swc-loader --save-dev
配置处理js、jsx
modlue: {
strictExportPresence: true,
rules: [
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "swc-loader"
}
}
]
}
配置处理css、module.css
安装style-loader、css-loader、postcss-loader、sass-loader、postcss-preset-env、postcss-normalize、mini-css-extract-plugin
npm install style-loader css-loader postcss-loader postcss-preset-env postcss-normalize sass-loader mini-css-extract-plugin --save-dev
import MiniCssExtractPlugin from "mini-css-extract-plugin"
const styleOptions = (config: Configuration, otherOptions?: any) => {
const options = [
{
loader: config?.mode === "development" ? require.resolve("style-loader") : MiniCssExtractPlugin.loader
},
{
loader: require.resolve("css-loader"),
options: {
sourceMap: true,
modules: true
}
},
{
loader: require.resolve("postcss-loader"),
options: {
postcssOptions: {
ident: "postcss",
config: false,
plugins: [
[
"postcss-preset-env",
{
autoprefixer: { flexbox: "no-2009" },
stage: 3,
},
],
"postcss-normalize"
]
}
}
}
]
if (otherOptions) {
options.push({
loader: require.resolve(otherOptions)
})
}
return options
}
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
module: {
...
rules: [
...
{
test: cssRegex,
exclude: cssModuleRegex,
use: styleOptions(config),
},
{
test: cssModuleRegex,
use: styleOptions(config),
},
{
test: sassRegex,
exclude: sassModuleRegex,
use: styleOptions(config, "sass-loader")
},
{
test: sassModuleRegex,
use: styleOptions(config, "sass-loader")
},
]
}
配置图片资源
module: {
...
rules: [
...
{
test: [/\.gif$/, /\.jpe?g$/, /\.png$/],
type: "asset/resource",
},
{
test: /\.svg$/i,
type: "asset/inline",
},
]
}
配置plugins
提取css到单独的文件
import MiniCssExtractPlugin from "mini-css-extract-plugin"
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:8].css",
})
]
配置html页面
安装html-webpack-plugin
npm install html-webpack-plugin --save-dev
import HtmlWebpackPlugin from "html-webpack-plugin"
plugins: [
...
new HtmlWebpackPlugin({
hash: true,
template: "./public/index.html",
favicon: "./public/favicon.ico",
}),
]
将manifest数据提取为一个 json 文件
安装webpack-manifest-plugin
npm install webpack-manifest-plugin --save-dev
import { WebpackManifestPlugin } from "webpack-manifest-plugin"
plugins: [
...
new WebpackManifestPlugin({
fileName: "asset-manifest.json"
}),
]
配置optimization
添加压缩bundle的插件,css-minimizer-webpack-plugin、terser-webpack-plugin
npm install css-minimizer-webpack-plugin terser-webpack-plugin --save-dev
import CssMinimizerPlugin from "css-minimizer-webpack-plugin"
import TerserPlugin from "terser-webpack-plugin"
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin()
]
},
配置开发、生产环境
配置开发环境配置
1、进入scripts/start.ts配置开发环境配置
安装open、webpack-dev-middleware @types/webpack-dev-middleware webpack-hot-middleware @types/webpack-hot-middleware connect-history-api-fallback、express
npm install open @types/open webpack-dev-middleware webpack-hot-middleware connect-history-api-fallback express @types/webpack-dev-middleware @types/webpack-hot-middleware --save-dev
import open from "open"
import webpack from "webpack"
import devMiddleware from "webpack-dev-middleware"
import hotMiddleware from "webpack-hot-middleware"
import fallback from "connect-history-api-fallback"
import express from "express"
import config from "./config"
const publicPath = process.env.PUBLIC_PATH || "/";
const compiler = webpack(
config({
mode: "development",
devtool: "eval-source-map",
output: { publicPath },
entry: ["webpack-hot-middleware/client?reload=true"],
plugins: [new webpack.HotModuleReplacementPlugin()],
cache: {
type: "filesystem",
buildDependencies: {
config: [__filename],
},
},
})
);
express()
.use(fallback({ index: `${publicPath}index.html` }))
.use(devMiddleware(compiler, { publicPath }))
.use(hotMiddleware(compiler))
.listen(3002, () => {
console.info("Listening on :3002");
open(`http://localhost:3002${publicPath}`);
});
2、在根目录package.json配置scripts
安装ts-node
npm install ts-node --save-dev
配置scripts
"scripts": {
"start": "ts-node ./scripts/start.ts",
},
此时执行npm run start可运行项目,会自动在浏览器打开 http://localhost:3002 地址
配置生产配置文件
1、进入scripts/build.ts配置生产环境配置
import webpack from "webpack"
import config from "./config"
const compiler = webpack(
config({
mode: "production",
devtool: false,
cache: false
})
);
compiler.run((err?: Error | null, stats?: webpack.Stats) => {
if (err) {
throw err;
}
if (stats) {
process.stdout.write(
stats.toString({
colors: true,
})
);
process.stdout.write("\n");
}
});
2、在根目录package.json配置scripts
"scripts": {
"build": "ts-node ./scripts/build.ts",
},
此时执行npm run build会将项目打包到根目录的build目录下,如下图