背景
最近部门在进行 降本增笑
,我们面临的挑战是将团队内所有业务线的 Node 服务迁移到新搭建的统一 Node 服务上(项目整合)。在这个过程中,我们遇到了一个棘手的问题:静态页面(next.js
)的迁移。由于新的 Node 服务层暂时只能处理 ejs 模板,如果我们按照 ejs 模板的方案进行开发,无疑会导致开发效率和体验的下降。
方案
为了解决这个问题,我们提出了一个 折中
的解决方案:在 Node 层提供一个 ejs 文件
,该文件中包含一个 div 标签。然后,我们通过挂载 js 文件的方式,加载业务 UI。这样,我们既能充分利用新的 Node 服务层的能力,同时又能保持 React 开发的高效和优秀体验。(暂不考虑 SSR)
确保 ejs 文件中的 div 标签具有唯一的 ID,以便于我们在挂载 js 文件时准确地定位到该标签
ini
// ejs 文件
<div id='root'></div>
<script src="{cdn js 地址}">
这个方案对于原有采用 React 编写的页面来说非常可行,且开发成本相对较低。
要将 React 项目配置为打包输出一个 JS 文件,第一个想到的构建工具就是 Webpack
。Webpack 可以将所有的资源(如JavaScript、CSS、图片等)打包成一个或多个文件。从功能和生态上来讲,Webpack 都是首选
实施
从 0 开始搭建项目
本篇博客,将记录在 React 项目中,从 0 搭建一个多页面的开发环境。以下是如何使用 Webpack 配置 React 项目的步骤:
-
首先,确保你已经安装了 Node.js。你可以在这里下载:nodejs.org/en/download...
-
在项目根目录下创建一个
package.json
文件,用于管理项目的依赖。你可以使用npm init
命令来创建一个新的package.json
文件。sh# 以 Mac 电脑为例 # 创建项目目录 mkdir my-product # 创建 package.json npm init -y # 创建一个 src 目录(用于后续存放代码文件) mkdir src
-
安装 React 和 ReactDOM:
cssnpm install react react-dom --save
-
安装 Webpack 及其相关依赖:
cssnpm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
-
安装Babel,它是一个 JavaScript 编译器,用于将 ES6+ 代码转换为浏览器兼容的代码:
sqlnpm install @babel/core @babel/preset-env @babel/preset-react babel-loader --save-dev
-
在项目根目录下创建一个名为
webpack.config.js
的文件,这是 Webpack 的配置文件。在此文件中,你可以配置 Webpack 如何打包项目:javascriptconst HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { path: __dirname + '/dist', filename: 'bundle.js' }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'] } } } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ] };
在这个配置中,我们指定了入口文件(
./src/index.js
),输出文件(./dist/bundle.js
),以及如何处理 JSX 文件(使用 Babel 编译器)。 -
在
package.json
文件中的scripts
字段中,添加以下脚本,以便在开发过程中启动 Webpack 开发服务器,以及在生产环境中构建项目:json"scripts": { "start": "webpack serve --mode development --open", "build": "webpack --mode production" }
-
在
src
目录下创建一个名为index.html
的文件,它将作为项目的 HTML 模板:html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>React App</title> </head> <body> <div id="root"></div> </body> </html>
-
在
src
目录下创建一个名为index.js
的文件,它将作为项目的入口文件:javascriptimport React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render(<div>我是页面 A</div>, document.getElementById('root'));
-
现在你可以在开发环境中运行项目:
arduinonpm run start
这将启动一个开发服务器,并在浏览器中打开项目。
-
要构建项目并生成一个打包后的JS文件,请运行:
arduinonpm run build
这将在
dist
目录下生成一个名为bundle.js
的文件,你可以将其部署到 CDN。
如此一来,就可以在任何一个 ejs 模板页面上添加一个带有唯一 ID 的 div 标签。接下来,只需加载对应的 bundle.js 文件,即可轻松渲染出所需的 UI。
配置 TS
如果 React 项目使用了 TypeScript,需要额外配置 TypeScript。以下是如何使用 Webpack 配置 TypeScript 的步骤:
-
首先,安装 TypeScript 和 ts-loader。ts-loader 是一个 Webpack 插件,用于处理 TypeScript 文件:
sqlnpm install typescript ts-loader --save-dev npm install @types/react @types/react-dom --save-dev
-
在项目根目录下创建一个
tsconfig.json
文件,这是TypeScript的配置文件。在此文件中,你可以配置TypeScript的编译选项:json{ "compilerOptions": { "target": "ESNEXT", "outDir": "./dist/", "sourceMap": true, "noImplicitAny": true, "module": "commonjs", "jsx": "react", "esModuleInterop": true, "resolveJsonModule": true, "typeRoots": ["./node_modules/@types"], "baseUrl": "./src", "paths": { "@/*": ["*"] } }, "include": [ "./src/**/*" ], "exclude": [ "node_modules" ] }
在这个配置中,我们指定了输出目录(
./dist/
),启用了source map(用于调试),禁止了隐式的any
类型,设置了模块系统为commonjs
,设置了目标JavaScript版本为ES5,以及启用了JSX。 -
修改你的
webpack.config.js
文件,添加一个新的规则来处理TypeScript文件:javascriptconst HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.tsx', output: { path: __dirname + '/dist', filename: 'bundle.js' }, resolve: { extensions: ['.ts', '.tsx', '.js'] }, module: { rules: [ { test: /\.(ts|tsx)$/, exclude: /node_modules/, use: 'ts-loader' }, { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'] } } } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ] };
在这个配置中,我们添加了一个新的规则来处理
.ts
和.tsx
文件(使用 ts-loader),并修改了入口文件为./src/index.tsx
。我们还添加了一个resolve.extensions
选项,用于指定 Webpack 可以自动解析的文件扩展名。 -
现在你可以在开发环境中运行项目:
arduinonpm run start
配置 多语言
要在 React 项目中处理国际化(i18n),你可以使用 react-i18next
库。以下是如何配置 react-i18next
的步骤:
-
安装
react-i18next
和i18next
:cssnpm install react-i18next i18next --save
-
在
src
目录下创建一个名为i18n.js
的文件,用于配置i18n:javascriptimport i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; // 导入翻译资源 import enTranslations from './locales/en/translation.json'; import zhTranslations from './locales/zh/translation.json'; i18n .use(initReactI18next) .init({ resources: { en: { translation: enTranslations }, zh: { translation: zhTranslations } }, lng: 'en', // 默认语言 fallbackLng: 'en', // 如果找不到翻译资源,使用的备用语言 interpolation: { escapeValue: false } }); export default i18n;
在这个配置中,我们导入了两个翻译资源(英文和中文),并指定了默认语言和备用语言。你可以根据需要添加更多的翻译资源。
-
在
src/locales
目录下创建翻译资源文件。例如,你可以创建以下目录结构:csssrc └── locales ├── en │ └── translation.json └── zh └── translation.json
在这些JSON文件中,你可以添加键值对来表示翻译文本。例如:
json// src/locales/en/translation.json { "welcome": "Welcome to React" } // src/locales/zh/translation.json { "welcome": "欢迎来到React" }
-
在你的
src/index.js
(或src/index.tsx
,如果使用TypeScript)文件中,导入i18n.js
:javascriptimport React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import './i18n'; // 导入i18n配置 ReactDOM.render(<App />, document.getElementById('root'));
-
在你的 React 组件中,使用
react-i18next
提供的useTranslation
钩子来获取翻译函数:javascriptimport React from 'react'; import { useTranslation } from 'react-i18next'; function App() { const { t, i18n } = useTranslation(); // 切换语言的函数 const changeLanguage = (lng) => { i18n.changeLanguage(lng); }; return ( <div> <h1>{t('welcome')}</h1> <button onClick={() => changeLanguage('en')}>English</button> <button onClick={() => changeLanguage('zh')}>中文</button> </div> ); } export default App;
使用
useTranslation
钩子获取了翻译函数t
和i18n
实例。我们可以使用t
函数来获取翻译文本,例如{t('welcome')}
。我们还定义了一个changeLanguage
函数,用于切换当前语言。
多个页面,单独打包部署
如果你的 React 项目有两个页面,并且你想要分别打包它们,可以在 Webpack 配置中设置多个入口点。
-
首先,修改的
webpack.config.js
文件,设置多个入口点和输出:此处需注意 output 的修改,必须加上 [name]
javascriptconst HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { page1: './src/Page1.tsx', page2: './src/Page2.tsx' }, output: { path: __dirname + '/dist', filename: '[name].bundle.js' }, resolve: { extensions: ['.ts', '.tsx', '.js'] }, module: { rules: [ { test: /\.(ts|tsx)$/, exclude: /node_modules/, use: 'ts-loader' }, { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'] } } } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/page1.html', chunks: ['page1'], filename: 'page1.html' }), new HtmlWebpackPlugin({ template: './src/page2.html', chunks: ['page2'], filename: 'page2.html' }) ] };
在这个配置中,我们设置了两个入口点:
page1
和page2
,它们分别对应两个 React 组件(./src/Page1.tsx
和./src/Page2.tsx
)。我们还修改了输出文件名,现在它会根据入口点的名称生成不同的文件(例如,page1.bundle.js
和page2.bundle.js
)。我们还添加了两个
HtmlWebpackPlugin
实例,用于生成两个HTML文件。每个插件实例都有一个chunks
选项,用于指定包含哪些入口点的代码。 -
新建 page1 和 page2 的文件
-
现在你可以在开发环境中运行项目:
arduinonpm run start
你本地访问的 url,需要变成 http://localhost:8080/page1.html
-
要构建项目并生成打包后的JS文件,请运行:
arduinonpm run build
这将在
dist
目录下生成两个 HTML 文件和两个 JS 文件
输出文件携带 hash 值
要在输出的文件名中添加 hash 值,你可以在 webpack.config.js
的 output.filename
选项中使用[contenthash]
占位符。以下是如何修改webpack.config.js
文件的示例:
javascript
module.exports = {
entry: {
page1: './src/Page1.tsx',
page2: './src/Page2.tsx'
},
output: {
path: __dirname + '/dist',
filename: '[name].[contenthash].bundle.js'
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: 'ts-loader'
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
}
]
}
};
在这个配置中,将 output.filename
选项更改为 '[name].[contenthash].bundle.js'
。这将根据文件内容生成一个 hash 值,并将其添加到文件名中。当文件内容发生变化时,hash 值也会改变,这有助于解决浏览器缓存问题。
现在,当你运行 npm run build
命令时,Webpack将生成带有 hash 值的 JS 文件
编译 CSS
如果你的React项目中使用了CSS,你需要在Webpack配置中添加对CSS文件的处理。以下是如何配置Webpack的步骤:
-
首先,安装
style-loader
和css-loader
。style-loader
用于将CSS注入到DOM中,css-loader
用于解析CSS文件:cssnpm install style-loader css-loader --save-dev
-
修改你的
webpack.config.js
文件,添加一个新的规则来处理CSS文件:javascriptmodule.exports = { // ... module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, // ...其他规则 ] } // ... };
在这个配置中,我们添加了一个新的规则来处理
.css
文件。对于每个.css
文件,Webpack 会先使用css-loader
来解析CSS,然后使用style-loader
将CSS注入到DOM中。
现在你的 React 项目已经支持 CSS。你可以在 JS 或 TS 文件中导入 CSS 文件,例如:
javascript
import React from 'react';
import './App.css'; // 导入CSS文件
function App() {
return (
<div className="App">
{/* ... */}
</div>
);
}
export default App;
当你运行 npm run start
或 npm run build
命令时,Webpack 将处理你的 CSS 文件,并将其注入到 DOM 中。
优化
构建前删除上一次的打包文件
要在构建前删除 dist
文件夹,如果不进行这一步,你会发现你的 dist 目录中的文件会越来越多。可以使用 clean-webpack-plugin
插件。以下是如何配置 clean-webpack-plugin
的步骤:
-
安装
clean-webpack-plugin
:cssnpm install clean-webpack-plugin --save-dev
-
修改你的
webpack.config.js
文件,导入clean-webpack-plugin
并将其添加到plugins
数组中:javascriptconst { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { // ... plugins: [ new CleanWebpackPlugin(), // ...其他插件 ] // ... };
在这个配置中,我们导入了
CleanWebpackPlugin
并将其添加到plugins
数组中。默认情况下,CleanWebpackPlugin
会在每次构建前删除output.path
选项指定的文件夹(在这个例子中为dist
文件夹)。
现在,当你运行 npm run build
命令时,Webpack 将在构建前自动删除 dist
文件夹。
配置别名
要在Webpack和TypeScript中配置别名,你需要分别更新webpack.config.js
和tsconfig.json
文件。以下是如何配置别名的步骤:
-
修改你的
webpack.config.js
文件,添加resolve.alias
选项:javascriptconst path = require('path'); module.exports = { // ... resolve: { extensions: ['.ts', '.tsx', '.js'], alias: { '@components': path.resolve(__dirname, 'src/components'), '@styles': path.resolve(__dirname, 'src/styles') } }, // ... };
在这个配置中,我们添加了两个别名:
@components
和@styles
。它们分别指向src/components
和src/styles
文件夹。你可以根据需要添加更多的别名。 -
修改你的
tsconfig.json
文件,添加compilerOptions.paths
选项:json{ "compilerOptions": { // ... "baseUrl": "./src", "paths": { "@components/*": ["components/*"], "@styles/*": ["styles/*"] } }, "include": ["./src/**/*"] }
在这个配置中,我们添加了两个别名:
@components
和@styles
。它们分别指向src/components
和src/styles
文件夹。请注意,我们还设置了baseUrl
选项,这是TypeScript解析非相对模块名的基本目录。现在你可以在项目中使用配置的别名。例如:
javascriptimport React from 'react'; import styles from '@styles/test.css'; import HeaderComponent from '@components/HeaderComponent';
这将使得在项目中导入模块时更加简洁,同时避免了使用相对路径。
添加 SourceMap
如果不配置 SourceMap,你会发现在调试时,代码都是编译之后的,看着很别扭
要在调试过程中使用 SourceMap,你需要在 Webpack 配置中启用 SourceMap
-
修改你的
webpack.config.js
文件,添加devtool
选项:javascriptmodule.exports = (env, argv) => { const isProduction = argv.mode === 'production'; return { // ... devtool: isProduction ? 'source-map' : 'eval-source-map', // ... }; };
在这个配置中,我们使用了一个函数作为导出,以便根据
mode
参数(production
或development
)动态选择 sourceMap 类型。对于生产环境,我们选择了source-map
类型,它会生成独立的.map
文件。对于开发环境,我们选择了eval-source-map
类型,它会将 sourceMap 嵌入到每个模块的eval 中,以实现更快的构建速度。想要了解更多类型的 sourceMap,请参阅Webpack文档。
-
运行以下命令启动开发服务器:
sqlnpm run start -- --mode development
-
要构建生产环境的代码,请运行:
arduinonpm run build -- --mode production
这将在
dist
目录下生成 sourceMap 文件(例如main.[contenthash].js.map
)。请注意,在生产环境中,使用 sourceMap 可能会暴露源代码。如果不希望在生产环境中生成 sourceMap,你可以将devtool
选项设置为false
或移除devtool
选项。
结尾
在整个开发工程搭建完成后,你可以轻松地将原有的 React 页面代码移植过来。整个过程非常顺畅,基本无需进行任何修改,实现了高效的代码迁移。
通过配置 Webpack,我们能够在 React 项目中便捷地创建多页面开发环境,从而提高开发和调试多页面应用程序的效率。若你也遇到类似问题,不妨尝试采用这种方法进行处理。希望本文能为你在 React 项目中配置多页面开发环境提供一定帮助。