从零开始搭建 React + TypeScript + webpack 应用,无需使用 create-react-app
艺术作品:https://code-art.pictures/
艺术作品中的代码:React JS
既然有 create-react-app,为什么还要费这个劲呢?
问得好!其实,如果你对它满意create-react-app,就尽管用吧🙂 不过,如果你想了解所有部件是如何协同工作的,那我们来自己把它们组合起来吧!
完整代码
本示例的完整代码可在此处获取。您可以直接复制粘贴使用,或者参考这篇博文进行操作。
关于保持其时效性的说明
亲爱的读者和开发者同行,我力求保持这篇文章的时效性。但是,如果您发现任何不准确或过时的信息,请随时留言。谢谢!
我们将要创建的项目的结构
/hello-react
/dist
index.html
main.da363aed60cf1cf68088.css
main.db8733ab04253d079b1c.js
main.db8733ab04253d079b1c.js.LICENSE.txt
/src
index.css
index.tsx
index.html
package.json
tsconfig.json
webpack.config.mjs
1. 安装 Node.js 和 npm
Node.js 的安装步骤取决于您的操作系统。请访问下载页面并按照提供的说明进行操作。
npm不需要单独安装,因为它已与 Node.js 捆绑在一起。要验证所有内容是否已正确安装在您的系统上,请参考这些说明。
附注: Node.js 和 npm 并非唯二的选择。还有Deno(Node.js 的替代方案)和Yarn(npm 的替代方案)。如果您不确定,我建议您暂时先使用 Node.js 和 npm。
2. 创建项目
创建项目根目录,hello-react并npm init在该目录下运行向导:
mkdir hello-react
cd hello-react
npm init
向导会通过逐一提问的方式引导您创建一个空项目。要自动接受所有默认答案,您可以将-y参数添加到npm init命令中。
向导完成后,会创建以下文件:
package.json
{
"name": "hello-react",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
虽然不多,但这已经是一个有效的 Node.js 项目了!🎊
3. 安装 TypeScript
在项目根目录下,运行以下命令:
npm i --save-dev typescript
4. 创建tsconfig.json
此文件包含项目的 TypeScript 配置。tsconfig.json在项目根目录下创建一个文件,并插入以下内容:
tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true,
"jsx": "react-jsx",
"module": "esnext",
"moduleResolution": "bundler",
"lib": [
"dom",
"esnext"
],
"strict": true,
"sourceMap": true,
"target": "esnext",
},
"exclude": [
"node_modules"
]
}
这些选项是什么意思?我们一起来看看!
compilerOptions
esModuleInterop修复了从 CommonJS 到 TypeScript 的默认导入和命名空间导入问题。这通常是为了兼容性而必须做的。jsx:指定 TypeScript 应该如何转译 JSX 文件(例如,react-jsx用于 React)。module:决定 TypeScript 如何转译 ES6 的导入和导出。如果设置为 false,esnext则不会更改它们,我建议这样做,以便 webpack 可以处理这些转换。moduleResolution:指定 TypeScript 如何解析模块,这取决于目标运行时。由于我们的应用使用 webpack 打包,bundler因此是最佳选择。早期版本使用过另一个node选项,该选项也适用于我们的示例。lib:指定目标环境中存在的库,以便 TypeScript 隐式包含它们的类型。注意:TypeScript 无法在运行时验证这些库是否实际可用——这是你的承诺。更多详情稍后介绍。strict启用 TypeScript 中的所有严格类型检查选项,以实现最大的类型安全性。sourceMap启用 TypeScript 生成源映射。我们将配置 webpack,使其在生产构建中排除这些源映射。target配置目标 ECMAScript 版本,该版本取决于用户的环境。稍后将详细介绍。
exclude
exclude:将某些库排除在类型检查和转译之外。但是,您的代码仍然会根据这些库提供的类型定义进行检查。
完整tsconfig.json参考信息请查看官方文档。
5. 安装 webpack、插件和加载器
请在项目根目录下运行以下命令。该命令很长,请确保您已滚动到足够远的位置并复制整行内容!
npm i --save-dev webpack webpack-cli webpack-dev-server css-loader html-webpack-plugin mini-css-extract-plugin esbuild-loader
6. 创建webpack.config.mjs
webpack.config.mjs在项目根目录下创建一个名为 `.htm` 的文件,并插入以下内容:
webpack.config.mjs
import HtmlWebpackPlugin from 'html-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
const prod = process.env.NODE_ENV === 'production'
export default {
mode: prod ? 'production' : 'development',
devtool: prod ? undefined : 'source-map',
entry: './src/index.tsx',
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
loader: 'esbuild-loader',
options: {
target: 'esnext',
jsx: 'automatic',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
},
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
]
},
output: {
filename: '[name].[contenthash].js',
path: import.meta.dirname + '/dist/',
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
}
这里涉及的内容很多!Webpack 配置可以说是整个设置中最复杂的部分。让我们一步一步来:
- 设置
NODE_ENV变量:这是区分开发模式和生产模式的常用方法。稍后,您将看到如何在脚本中设置它。 HtmlWebpackPlugin:dist/index.html根据模板生成文件index.html,我们稍后将创建该模板。MiniCssExtractPlugin:将样式提取到单独的文件中。如果没有这一步,样式将保留在代码中index.html。mode:指定构建是用于开发环境还是生产环境。在生产模式下,Webpack 会自动压缩包。devtool配置源映射以便于调试。entry:定义应用程序在客户端加载后首先执行的模块。它作为启动应用程序的引导程序。module.rules:描述如何将不同类型的文件加载(导入)到捆绑包中。test: /\.(ts|tsx)$/:使用 . 处理 TypeScript 文件。请在此处esbuild-loader查看示例。test: /\.css$/处理 CSS 文件。
output:filename设置文件名包含内容哈希值。这种技术被称为“缓存清除”。path:指定已编译文件的目标目录。
plugins列出所有插件及其设置。
6.1. 替代方案:将 webpack 配置作为 CJS 文件
我们在 ESM 文件中定义了 webpack 配置。这从4.5版本开始就可行,并且在新版本中也应该有效node。但是,如果这种方法对您无效,您可以将配置切换到 CJS。
webpack.config.js
首先,将文件扩展名从 . 更改为
.mjs..js然后,按如下方式更新内容:
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const prod = process.env.NODE_ENV === 'production'
module.exports = {
// (no changes)
output: {
// (no changes)
// The only change:
// replace import.meta.dirname with __dirname
path: __dirname + '/dist/',
},
// (no changes)
}
6.2. 替代方案:使用ts-loader代替esbuild-loader
我们使用esbuild-loader来加载 TypeScript 文件。截至 2025 年,它很可能是 webpack 中最快的 TypeScript 加载器。
然而,根据 webpack文档, `typescript`ts-loader是默认推荐的 TypeScript 加载器。与esbuild-loader相对较新的 `typescript` 不同,ts-loader`typescript` 更成熟、更可靠。如果您有更复杂的配置,`typescript`ts-loader更有可能提供可靠的支持。
切换到 ts-loader
npm uninstall esbuild-loader
npm i -D ts-loader
webpack.config.mjs
// ...
export default {
// ...
module: {
rules: [
// {
// test: /\.(ts|tsx)$/,
// exclude: /node_modules/,
// loader: 'esbuild-loader',
// options: {
// target: 'esnext',
// jsx: 'automatic',
// },
// resolve: {
// extensions: ['.ts', '.tsx', '.js', '.json']
// },
// },
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
},
use: 'ts-loader',
},
// ...
],
},
// ...
}
7. 将脚本添加到 package.json 文件中
将以下脚本添加start到build您的文件中package.json:
package.json(Linux、OS X)
{
...
"scripts": {
"start": "webpack serve --port 3000",
"build": "NODE_ENV=production webpack"
}
...
}
package.json(Windows PowerShell)
命令build必须不同:
{
...
"scripts": {
"start": "webpack serve --port 3000",
"build": "set NODE_ENV=production && webpack"
}
...
}
start:在端口 3000 上启动一个开发服务器。开发服务器会自动监视您的文件,并在需要时重新构建应用程序。build:构建用于生产环境的应用程序。它NODE_ENV=production会设置NODE_ENV变量,该变量会在第一行进行检查webpack.config.mjs。
8. 创建 index.html 模板
HtmlWebpackPlugin即使没有模板,也可以生成 HTML 文件。不过,您可能还是需要一个模板,所以我们在项目根目录下创建一个。这就是我们在webpack.config.mjs插件部分提到的文件。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="initial-scale=1, width=device-width"/>
<title>Hello React</title>
</head>
<body>
<div id="app-root">App is loading...</div>
</body>
</html>
笔记:
lang="en"如果应用程序的实际语言与应用程序的语言不同,请记得更新为应用程序的实际语言。- 该
<meta name="viewport" .../>标签对于响应式设计至关重要,它可以让您的布局适应各种设备尺寸。 <meta http-equiv="x-ua-compatible" content="ie=edge"/>如果您的目标浏览器是 IE,请务必在“也”<head>部分添加。
9. 安装 React
在项目根目录下,运行以下命令:
npm i react react-dom
进而:
npm i --save-dev @types/react @types/react-dom
10. 创建 src/index.tsx
这是您的应用程序的入口点,我们在[此处应插入参考文献]中引用过webpack.config.mjs。您也可以更新[main此处应插入参考文献]中的字段package.json以指向同一个文件,但这并非绝对必要。
src/index.tsx
import React from 'react'
import { createRoot } from 'react-dom/client'
const container = document.getElementById('app-root')!
const root = createRoot(container)
root.render(<h1>Hello React!</h1>)
注意:此createRoot()API 是 React 18 的新增功能。如果您使用的是旧版本的 React,可以参考这篇博客文章获取指导,并使用以下代码:
React 17 中的 src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.render(
<h1>Hello React!</h1>,
document.getElementById('app-root'),
)
11. 创建src/index.css并导入到src/index.tsx
为了确保我们的 CSS 插件正常工作,让我们应用一些简单的样式。
src/index.css
body {
color: blue;
}
src/index.tsx
import './index.css'
// The rest app remains the same
// ...
12. 运行开发服务器
一路走来实属不易,但我们即将抵达终点!让我们运行开发服务器吧:
npm start
现在在浏览器中打开http://localhost:3000/ — 你应该会看到彩色的欢迎信息:
你好 React!
现在尝试修改src/index.tsx,例如更改消息。应用程序应该会重新加载并显示更新后的文本。您还可以尝试更改样式——这些更改应该无需重启服务器即可生效。
13. 构建用于生产环境的应用
在项目根目录下,运行以下命令:
npm run build
这将生成一个dist包含打包文件的文件夹。为了使这些文件在生产环境中呈现,您需要一个名为 `<utility_name>` 的小工具serve和一个相应的脚本。添加依赖项:
npm i --save-dev serve
接下来,请更新您的脚本package.json:
package.json
{
...
"scripts": {
...
"preview": "serve dist -p 3000"
},
...
}
然后运行npm run preview。打开http://localhost:3000/ — 你应该会看到欢迎信息!
14. 针对较旧的环境
这部分内容稍微高级一些,所以等你熟悉了基本设置之后再回来学习吧。
14.1. 目标 ES 版本
目标 ES 版本在两个地方设置:
tsconfig.json: 在下面compilerOptions.targetwebpack.config.mjs在esbuild-loader配置中
这取决于你的应用的目标用户群体。那么,你的目标用户是谁呢?
- 你和你的团队——如果你们使用的是现代工具,那么保留默认设置是安全的
esnext。你们可能不需要任何过时的东西🙂 - 普通互联网用户——我建议以 2021 年为目标
es<currentYear-3>。例如,在撰写本文的年份(2021 年),您应该以 2021 年为目标es2018。为什么不呢esnext?有时,即使是看似最新的浏览器也会缺少对某些功能的支持。例如,小米 MIUI 浏览器 12.10.5-go(2021 年 5 月发布)就不支持空值合并运算符。这里有一个示例程序,可以在该浏览器中进行测试。您的测试结果如何? - IE 用户— 如果您要支持 Internet Explorer,则目标必须为 ES5
es5。请注意,某些 ES6+ 功能在转译为 ES5 时可能会变得臃肿。
14.2. 选择目标库
库设置在tsconfig.json下compilerOptions.lib,此选项也取决于您对目标用户的猜测。
您可能会用到的典型库:
dom— 包括浏览器提供的所有 API。es...例如,es2018— 包括带有相应 ES 规范的 JavaScript 内置函数。
重要提示:与 Babel 不同,这些选项不会自动添加任何 polyfill。因此,如果您的目标环境较旧,则需要手动添加 polyfill,如下一节所述。
14.3. 添加 polyfill
根据应用程序所需的 API,可能需要使用 Polyfill。
以下是一些常用的填充材料:
- core-js — 用于缺失的
Set、、等Map。Array.flatMap - raf — 缺失
requestAnimationFrame。 - whatwg-fetch — 用于查找缺失的资源
fetch。注意:这不包含Promisepolyfill,它已包含在core-js上述内容中。
鉴于我们决定全部使用它们,设置如下:
npm i core-js raf whatwg-fetch
index.tsx
import 'core-js/features/array/flat-map'
import 'core-js/features/map'
import 'core-js/features/promise'
import 'core-js/features/set'
import 'raf/polyfill'
import 'whatwg-fetch'
// The rest app remains the same
// ...
14.4. 调整webpack运行时
或许令人惊讶,即使编译成 ES5,webpack代码中仍然可能包含 ES6+ 的结构。因此,如果您的目标浏览器是 Internet Explorer 等旧版浏览器,则可能需要禁用这些功能。更多详情,请查看此文档页面。
添加这么多填充元素是否合理?
不,这样做并非总是合理的,因为大多数用户都使用现代浏览器。添加不必要的 polyfill 会浪费运行时资源和带宽。最佳方案是创建两个独立的 bundle:一个用于现代环境,另一个用于旧环境,然后根据用户环境加载相应的 bundle。不过,这种方法超出了本教程的范围。
你完成了!
我知道这并不容易😅 但我相信这些概念对你来说已经不再是难题了。感谢你一路陪伴我!
接下来会发生什么?
完成设置和应用的基本逻辑后,您可能需要某种形式的服务器端渲染 (SSR)。本系列的下一篇文章将探讨一种便捷实用的方法,无需服务器端 Worker 即可预渲染您的应用。祝您使用愉快!
文章来源:https://dev.to/alekseiberezkin/setting-up-react-typescript-app-without-create-react-app-oph