使用Webpack搭建React脚手架(上篇)

搭建React脚手架是开发React应用的重要一步,它可以帮助我们快速初始化项目结构,并且集成一些常用的开发工具和库,提高开发效率。本文将介绍如何来搭建一个React脚手架,并详细介绍每一步的步骤。

版本

 pnpm 版本

pnpm -v 7.27.1

# 初始化package.json文件

pnpm init

会在根目录生成一个 package.json 文件: 

{
"name": "fe",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

 基本项目结构

在根目录新建基本的项目结构: 

├── build
| ├── webpack.base.ts # 公共配置
| ├── webpack.dev.ts # 开发环境配置
| └── webpack.prod.ts # 打包环境配置
├── public
│ └── index.html # html模板
├── src
| ├── App.tsx
| ├── App.css
│ └── index.tsx # react应用入口页面
└── 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>webpack5-react-ts</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

 引入react

安装依赖: 

pnpm i react react-dom
# 声明依赖
pnpm i @types/react @types/react-dom -D

 接下来先将入口文件 src/index.tsx 写好:

import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const root = document.querySelector('#root')
if(root) {
createRoot(root).render(<App />)
}

App.tsx :

import React from 'react'
import './App.css'
function App() {
return <h2>Hello East_White</h2>
}
export default App

引入typescript

为什么要使用 typescript ?

当使用 JavaScript 编写大型项目时,可能会面临许多挑战,例如代码管理,可扩展性,协作和维护 等。 TypeScript 则是一种解决这些挑战的工具,它有如下的一些优点:

1. 更好的代码质量: TypeScript 的静态类型系统可以帮助开发人员在编写代码时捕获错误,这可以 提高代码质量和稳定性。

2. 更好的可读性和可维护性: TypeScript 的静态类型系统和类可以提高代码的可读性和可维护性, 特别是在大型项目中。这可以使代码更易于理解和修改。

3. 更好的IDE支持: TypeScript 具有出色的IDE支持,包括自动完成,语法突出显示和类型检查。这 可以提高开发人员的生产力和准确性。

4. 更好的可扩展性: TypeScript 支持面向对象编程,可以帮助开发人员创建复杂的数据类型和接 口,并使代码更易于扩展和维护。

5. 更好的协作: TypeScript 可以帮助开发团队更好地协作,因为代码的结构和类型是显式声明的。 这可以减少在协作开发中出现的潜在问题和错误。

6. 更好的性能:由于 TypeScript 可以在编译时捕获错误,因此可以减少运行时错误并提高性能。

总的来说, TypeScript 可以提高代码质量,可读性,可维护性,可扩展性和协作,并且可以提高 性能。这些优点使得 TypeScript 成为编写大型项目的优秀选择。

接下来,我们在项目中引入 typescript ,先安装依赖:

pnpm i typescript -D
pnpm i babel-loader ts-node 
@babel/core @babel/preset-react @babel/presettypescript @babel/preset-env core-js -D

初始化 tsconfig.json :

npx tsc --init
# 如果全局安装了typescript,也可以通过下面的命令创建
tsc --init

就会在根目录生成一个tsconfig.json文件:

{
"compilerOptions": {
"incremental": true, 
"target": "ESNEXT", 
"module": "commonjs", 
"lib": [], 
"allowJs": true, 
"checkJs": true, 
"jsx": "preserve", 
"declaration": true, 
"declarationMap": true, 
"sourceMap": true, 
"outFile": "./", 
"outDir": "./",
"rootDir": "./", 
"composite": true, 
"tsBuildInfoFile": "./", 
"removeComments": true, 
"noEmit": true,
"noEmitOnError": true, 
"importHelpers": true, 
"downlevelIteration": true, 
"isolatedModules": true, 
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true, 
"noImplicitThis": true,
"noImplicitAny": true, 
"noUnusedLocals": true,
"noUnusedParameters": true, 
"noImplicitReturns": true, 
"noFallthroughCasesInSwitch": true, 
"moduleResolution": "node",
"baseUrl": "./", 
"paths": {},
"rootDirs": [],
"typeRoots": [], 
"types": [], 
"allowSyntheticDefaultImports": true, 
"esModuleInterop": true, 
"preserveSymlinks": true, 
"allowUmdGlobalAccess": true, 
"sourceRoot": "", 
"mapRoot": "", 
"inlineSourceMap": true, 
"inlineSources": true, 
"experimentalDecorators": true, 
"emitDecoratorMetadata": true, 
"skipLibCheck": true, 
"forceConsistentCasingInFileNames": true 
}
}

webpack配置 

安装依赖:

pnpm i webpack webpack-cli -D
webpack.base.tswebpack.base.ts

 配置 webpack.base.ts 文件:

import { Configuration } from 'webpack';
const path = require("path");
const baseConfig: Configuration = {
entry: path.join(__dirname, "../src/index.tsx"), 
output: {
filename: "static/js/[name].js", 
path: path.join(__dirname, "../dist"), 
clean: true, 
publicPath: "/", 
},
module: {
rules: [],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"],
},
plugins: []
};

 另外因为我们在 App.tsx 中引入了 css 文件,所以还需要安装相关的 loader :

pnpm i style-loader css-loader html-webpack-plugin -D

 完善 webpack.base.ts :

import { Configuration } from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
const path = require("path");
const baseConfig: Configuration = {
entry: path.join(__dirname, "../src/index.tsx"), // 入口文件
// 打包出口文件
output: {
filename: "static/js/[name].js", // 每个输出js的名称
path: path.join(__dirname, "../dist"), // 打包结果输出路径
clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5
内置了
publicPath: "/", // 打包后文件的公共前缀路径
},
// loader 配置
module: {
rules: [
{
test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
use: {
loader: "babel-loader",
options: {
// 预设执行顺序由右往左,所以先处理ts,再处理tsx
presets: [
[
"@babel/preset-env",
{
// 设置兼容目标浏览器版本,也可以在根目录配置.browserslistrc文
件,babel-loader会自动寻找上面配置好的文件.browserslistrc
targets: { browsers: ["> 1%", "last 2 versions", "not ie
<= 8"] },
useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到
的api进行引入polyfill按需添加
corejs: 3, // 配置使用core-js使用的版本
loose: true,
},
],
// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime":
"automatic" 添加到配置中。
// 否则可能会出现错误:Uncaught ReferenceError: React is not
defined
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
},
},
},
{
test: /.css$/, //匹配 css 文件
use: ["style-loader", "css-loader"],
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"],
},
plugins: [
new HtmlWebpackPlugin({
// 复制 'index.html' 文件,并自动引入打包输出的所有资源(js/css)
template: path.join(__dirname, "../public/index.html"),
// 压缩html资源
minify: {
collapseWhitespace: true, //去空格
removeComments: true, // 去注释
},
}),
],
};
export default baseConfig

 因为 webpack.base.ts 文件承载了基本的配置,随着 webpack 做的事情越来越多,会逐渐变得很庞 大,我们可以将其中的 babel-loader 相关的配置抽离出来进行管理。在根目录新建 babel.config.js :

module.exports = {
// 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
presets: [
[
"@babel/preset-env",
{
// 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文
件.browserslistrc
// "targets": {
// "chrome": 35,
// "ie": 9
// },
targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入
polyfill按需添加
corejs: 3, // 配置使用core-js使用的版本
loose: true,
},
],
// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到
配置中。
// 否则可能会出现错误:Uncaught ReferenceError: React is not defined
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
};

 然后在 webpack.base.ts 文件中,就可以将 babel-loader 配置简化成:

module: {
rules: [
{
test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
use: "babel-loader"
},
],
},
 webpack.dev.ts

 接下来,我们需要通过 webpack-dev-server 来启动我们的项目,所以需要安装相关的依赖:

 pnpm i webpack-dev-server webpack-merge -D

 接着,配置开发环境配置: webpack.dev.ts

import path from "path";
import { merge } from "webpack-merge";
import { Configuration as WebpackConfiguration } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from "webpack-devserver";
import baseConfig from "./webpack.base";
interface Configuration extends WebpackConfiguration {
devServer?: WebpackDevServerConfiguration;
}
const host = "127.0.0.1";
const port = "8082";
// 合并公共配置,并添加开发环境配置
const devConfig: Configuration = merge(baseConfig, {
mode: "development", // 开发模式,打包更加快速,省了代码优化步骤
devtool: "eval-cheap-module-source-map",
devServer: {
host,
port,
open: true, // 是否自动打开
compress: false, // gzip压缩,开发环境不开启,提升热更新速度
hot: true, // 开启热更新
historyApiFallback: true, // 解决history路由404问题
setupExitSignals: true, // 允许在 SIGINT 和 SIGTERM 信号时关闭开发服务器和退出
进程。
static: {
directory: path.join(__dirname, "../public"), // 托管静态资源public文件夹
},
headers: { "Access-Control-Allow-Origin": "*" }, // HTTP响应头设置,允许任何
来源进行跨域请求
},
});
export default devConfig;

 package.json 中添加启动脚本:

"scripts": {
"dev": "webpack serve -c build/webpack.dev.ts"
},

正当我们准备启动项目的时候,发现还有一个错误;只需要在 tsconfig.json 中加入一行 "jsx": "react-jsx" 即可:

{
"compilerOptions": {
"target": "es2016",
"esModuleInterop": true,
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"jsx": "react-jsx" // 这里改成react-jsx,就不需要在tsx文件中手动引入React了
},
"include": ["./src"]
}

运行 pnpm run dev 脚本启动项目,就可以看到页面跑出来了!

webpack.prod.ts

配置 webpack.prod.ts :

import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import baseConfig from "./webpack.base";
const prodConfig: Configuration = merge(baseConfig, {
mode: "production", 
});
export default prodConfig;

 在 package.json 中添加:

"scripts": {
"build": "webpack -c build/webpack.prod.ts"
},

 运行 pnpm run build ,如果想看打包结果,可以通过一个小工具来查看:

# 如果之前使用npm,最简单的方法就是使用如下命令
npm i serve -g
# 如果是首次使用pnpm安装全局依赖,通过如下命令
pnpm setup
source ~/.zshrc
pnpm i serve -g
copy 静态资源 

一般 public 文件夹都会放一些静态资源,可以直接根据绝对路径引入,比如图片、 css 、 js 文件等,不 需要 webpack 进行解析,只需要打包的时候把 public 下内容复制到构建出口文件夹中,可以借助 copywebpack-plugin 插件,安装依赖:

pnpm i copy-webpack-plugin -D

 修改 webpack.base.ts :

const baseConfig: Configuration = {
// ...
plugins: [
new HtmlWebpackPlugin({
title: "webpack5-react-ts",
filename: "index.html",
// 复制 'index.html' 文件,并自动引入打包输出的所有资源(js/css)
template: path.join(__dirname, "../public/index.html"),
inject: true, // 自动注入静态资源
hash: true,
cache: false,
// 压缩html资源
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true, //去空格
removeComments: true, // 去注释
minifyJS: true, // 在脚本元素和事件属性中缩小JavaScript(使用UglifyJS)
minifyCSS: true, // 缩小CSS样式元素和样式属性
}
})
],
};
export default baseConfig;

 开发环境已经在 devServer 中配置了 static 托管了 public 文件夹,在开发环境使用绝对路径可以访问 到 public 下的文件,但打包构建时不做处理会访问不到,所以现在需要在打包配置文件 webpack.prod.ts 中新增 copy 插件配置。

import path from "path";
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import CopyPlugin from "copy-webpack-plugin";
import baseConfig from "./webpack.base";
const prodConfig: Configuration = merge(baseConfig, {
mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
plugins: [
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"), // 复制public下文件
to: path.resolve(__dirname, "../dist"), // 复制到dist目录中
filter: (source) => !source.includes("index.html"), // 忽略
index.html
},
],
}),
],
});
export default prodConfig;

测试一下,在 public 中新增一个 favicon.ico 图标文件,在 index.html 中引入:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- 绝对路径引入图标文件 -->
<link data-n-head="ssr" rel="icon" type="image/x-icon"
href="/favicon.ico">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack5-react-ts</title>
</head>
<body>
<!-- 容器节点 -->
<div id="root"></div>
</body>
</html>

 再执行 pnpm run build 打包,就可以看到 public 下的 favicon.ico 图标文件被复制到 dist 文件中 了。 有的可能会遇到 favicon 不显示的问题,提供以下思路进行处理:

1. 在head添加了 favicon 没生效,但是新开标签页直接访问图片可以访问到。

<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"

 2. 改图片尺寸格式依然没显示,直接复制其他网站正常 favaicon.ico ,排除格式的原因。

3. 语法问题,尝试各种写法。

<link rel="icon" href="/favicon.ico"">
<link rel="shortcut" href="/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

4. 添加版本号,favicon正常显示,排除语法原因。

<link rel="shortcut icon" href="/favicon.ico?v=1.0" type="image/x-icon">

 5. 继续深入查找问题, F12 查看 html 和 network ,发现 favicon.ico 的请求没有。

6. 没有 favicon 的请求,不会显示图片,怀疑是浏览器缓存问题,清除缓存后依然没有请求。

7. 打开其它浏览器,发现 favicon 正常显示,进一步确定是浏览器缓存。

8. 关闭标签页,重新打开浏览器,最后 favicon.ico 正常显示。

9. chrome 浏览器,前端资源经常会产生缓存问题,清空或禁用缓存也不一定有效。可以尝试重启浏 览器,或者重启电脑。

配置环境变量

 corss-env + DefinePlugin
环境变量按作用分为两种: 

1. 区分是开发模式还是打包构建模式

2. 区分项目业务环境,开发/测试/预测/正式环境 

安装 cross-env :

pnpm i cross-env -D

 修改 package.json 的 scripts :

"scripts": {
"dev:dev": "cross-env NODE_ENV=development BASE_ENV=development webpack
serve -c build/webpack.dev.ts",
"dev:test": "cross-env NODE_ENV=development BASE_ENV=test webpack serve
-c build/webpack.dev.ts",
"dev:pre": "cross-env NODE_ENV=development BASE_ENV=pre webpack serve -c
build/webpack.dev.ts",
"dev:prod": "cross-env NODE_ENV=development BASE_ENV=production webpack
serve -c build/webpack.dev.ts",
"build:dev": "cross-env NODE_ENV=production BASE_ENV=development webpack
-c build/webpack.prod.ts",
"build:test": "cross-env NODE_ENV=production BASE_ENV=test webpack -c
build/webpack.prod.ts",
"build:pre": "cross-env NODE_ENV=production BASE_ENV=pre webpack -c
build/webpack.prod.ts",
"build:prod": "cross-env NODE_ENV=production BASE_ENV=production webpack
-c build/webpack.prod.ts"
},

 在 webpack.base.ts 中打印一下设置的环境变量

console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)

 执行 pnpm run build:dev ,就可以在控制台打印出:

// NODE_ENV production
// BASE_ENV development

 当前是打包模式,业务环境是开发环境,这里需要把 process.env.BASE_ENV 注入到业务代码里面,就 可以通过该环境变量设置对应环境的接口地址和其他数据,要借助 webpack.DefinePlugin 插件。

修改 webpack.base.ts

import { DefinePlugin } from 'webpack'
module.export = {
// ...
plugins: [
// ...
new DefinePlugin({
'process.env': JSON.stringify(process.env)
})
]
}

 在根目录下新建 typings/global.d.ts 文件:

declare module 'process' {
global {
namespace NodeJS {
export interface ProcessEnv {
BASE_ENV: 'development' | 'test' | 'pre' | 'production'
NODE_ENV: 'development' | 'production'
}
}
}
}

 并在 tsconfig.json 中配置:

{
"compilerOptions": {
"target": "es2016", // 编译输出的JavaScript版本为ES2016
"esModuleInterop": true, // 允许更好的兼容性与ECMAScript模块导入
"module": "commonjs", // 指定生成哪个模块系统代码,这里是CommonJS
"forceConsistentCasingInFileNames": true, // 确保文件名大小写一致,有助于跨平
台开发
"strict": true, // 启用所有严格的类型检查选项
"skipLibCheck": true, // 跳过声明文件的类型检查,可以提高编译速度
"typeRoots": ["./typings/*.d.ts", "node_modules/@types"], // 指定类型声明文
件的路径
"jsx": "react-jsx" // react18这里改成react-jsx,就不需要在tsx文件中手动引入
React了
},
"include": ["./src", "./typings/*.d.ts"] // 指定哪些文件或目录应该被包含在编译范
围内
}

 配置后会把值注入到业务代码里面去, webpack 解析代码匹配到 process.env.BASE_ENV ,就会设置到 对应的值。测试一下,在 src/index.tsx 打印一下两个环境变量:

console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)

 执行 pnpm run dev:test ,可以在浏览器控制台看到打印的信息:

// NODE_ENV development
// BASE_ENV test
配置多环境运行配置

安装依赖:

pnpm i dotenv

 在根目录下新建一个多文件配置文件夹 env :

├── env

├── .env.development # 开发环境

├── .env.test # 测试环境

├── .env.pre # 预发布环境

└── .env.production # 生产环境

文件中可以配置任意我们需要的变量:

// env/.env.development
REACT_APP_API_URL=https://api-dev.com
// env/.env.test
REACT_APP_API_URL=https://api-test.com
// env/.env.pre
REACT_APP_API_URL=https://api-pre.com
// env/.env.production
REACT_APP_API_URL=https://api-prod.com

 然后再 webpack.base.ts 中引入,然后解析对应环境配置,最后通过 DefinePlugin 进行注入:

import path from "path";
import { Configuration, DefinePlugin } from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import * as dotenv from "dotenv";
// 加载配置文件
const envConfig = dotenv.config({
path: path.resolve(__dirname, "../env/.env." + process.env.BASE_ENV),
});
// console.log("process.env", process.env);
// console.log("NODE_ENV", process.env.BASE_ENV);
// console.log("REACT_APP_API_URL", process.env.REACT_APP_API_URL);
const baseConfig: Configuration = {
// ...
plugins: [
// 注入到业务
new DefinePlugin({
"process.env": JSON.stringify(envConfig.parsed),
"process.env.BASE_ENV": JSON.stringify(process.env.BASE_ENV),
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
}),
].filter(Boolean), // // 去除数组中的假值(例如,如果某些插件的条件不满足而导致未定
义)
};
export default baseConfig;

业务代码中使用:

import { createRoot } from 'react-dom/client';
import App from './App';
// const root = document.getElementById('root');
const root = document.querySelector('#root')
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
console.log("process.env", process.env);
if(root) {
createRoot(root).render(<App />)
}å

然后重启项目: pnpm run dev:dev ,就可以在控制台

还可以验证一下环境配置是否正确,启动打包: pnpm run build:prod ,通过 serve -s dist ,启动 项目

文件别名

 先在 webpack.base.ts 中配置:

resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".less", ".css"],
// 别名需要配置两个地方,这里和 tsconfig.json
alias: {
"@": path.join(__dirname, "../src")
},
},

然后还需要在 tsconfig.json 中配置:

{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
},
}

 然后就可以在项目中使用了

import '@/App.css'
function App() {
return <h2>webpack5-react-ts</h2>
}
export default App

到这里搭建react脚手架第上篇就结束了,通过配置webpack,环境变量,来一步步的搭建,雏型渐显出来。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值