背景
当前, 我们使用 React Native 开发页面, 以支持 Android 和 iOS 平台. 现在我们在探索是否能够让我们的 React Native 项目能够直接运行在 Web 端, 经过调研, 我们计划使用 React Native Web 来达成这一目的.
但是, 由于 React Native 的开发模式, 使得 React Native 项目的开发和构建过程与传统的 Web 开发有很大的不同. 例如, React Native 项目使用 Metro 作为打包工具, 而 Web 项目使用 Webpack 或者 Vite 作为打包工具. 为了提高开发效率, 我们希望能够使用 Vite 来打包 React Native Web 项目.
Tips: 本文暂不关注 React Native 社区诸多原生能力在 Web 端的可能性, 假定你使用的 React Native 的 API 已经被 React Native Web 支持, React Native 开源库亦然.
不过还好, 很多知名且非常重要的库, 都有考虑到 Web 端的支持, 比如:
react-navigation
react-native-screens
react-native-gesture-handler
react-native-svg
react-native-reanimated
react-native-vector-icons
调研
babel-plugin-react-native-web
首先, 我们深入的阅读了 React Native Web 的文档, 并重点分析了其提供的 babel-plugin-react-native-web
插件的源码.
因为 React Native Web 提供了和 React Native 一致的 API , 所以该插件的最主要工作就是将项目代码以及 node_modules
依赖包中的 react-native
依赖替换为 react-native-web
.
另外, 建议该插件还可以保证只打包你用到的 react-native-web
包中的组件代码, 以减少打包体积.
集成步骤
为了简单起见, 让我们先抛开 React Native 原生开发所需要的繁琐细节, 从一个干净的 Vite 项目开始
shell
pnpm create vite my-react-native-web --template react-ts
安装依赖包
对于 React 18 版本
shell
pnpm add react@18 react-dom@18
pnpm add react-native@0.73.6 react-native-web@0.19.10
# 若需要支持 TypeScript
pnpm add -D @types/react@18 @types/react-dom@18
如果你的项目使用的是 React 17 的版本, 需要降低 React Native 和 React Native Web 的版本
shell
pnpm add react@17 react-dom@17
pnpm add react-native@0.68.7 react-native-web@0.18.12
# 若需要支持 TypeScript
pnpm add -D @types/react@17 @types/react-dom@17
pnpm add -D @types/react-native@0.68.7
注意 1: 若需要其他版本的 React 或者 React Native, 请自行按照如上逻辑分析并安装对应版本.
注意 2: 新版本的 React Native 内置 TypeScript 类型文件, 老版本需要额外安装对应版本的 @types/react-native
.
配置 Vite
首先, 使用 @vitejs/plugin-react
插件, 并将 react-native
映射为 react-native-web
.
javascript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
alias: {
"react-native": "react-native-web",
},
});
但是如果我们想使用社区内为 React Native 或者 React Native Web 提供的开源库, 按照 React Native 的设定, 我们还需要通过如下配置, 实现平台特定文件的查找功能, 以及 JSX 相关支持.
javascript
const extensions = [
".web.tsx",
".tsx",
".web.ts",
".ts",
".web.jsx",
".jsx",
".web.js",
".js",
".css",
".json",
".mjs",
];
export default defineConfig({
resolve: {
extensions,
}
optimizeDeps: {
esbuildOptions: {
loader: { ".js": "jsx" },
resolveExtensions: extensions,
jsx: "automatic",
}
}
})
另外, 我们还需要将 React Native 中使用的一些变量进行替换:
javascript
const development = process.env.NODE_ENV === "development";
export default defineConfig({
define: {
global: "window",
__DEV__: JSON.stringify(development),
DEV: JSON.stringify(development),
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
},
});
详情请参阅
VSCode 配置
安装 React Native Tools 插件
开发
入口代码
index.html
非常简单, 和普通的 Vite 项目没有什么区别.
html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
src/main.tsx
使用 React Native 的 AppRegistry API .
tsx
import React from "react";
// import ReactDOM from 'react-dom/client'
import { AppRegistry } from "react-native";
import App from "./App.tsx";
import "./index.css";
const Root = () => (
<React.StrictMode>
<App />
</React.StrictMode>
);
const rootTag = document.getElementById("root");
// ReactDOM.createRoot(rootTag!).render(<Root />);
AppRegistry.registerComponent("App", () => Root);
AppRegistry.runApplication("App", {
initialProps: {},
rootTag,
});
Tips 1: 如何你只是想单独使用一些组件, 可以不使用 AppRegistry
相关的 API, 直接使用 React Dom 即可.
Tips 2: 尽管可以直接使用 ReactDOM.render
, 但是会缺少 AppRegistry
提供给组件的全局上下文
src/App.tsx
直接使用 React Native 的组件即可
tsx
import React from "react";
import { View, Text } from "react-native";
export default function App() {
return (
<View>
<Text>Hello World</Text>
</View>
);
}
src/index.css
根据 React Native Web 的文档, React Native 通常是一个全屏页面, 我们会使用一个 ScrollView
作为根元素.
对于 Web 端, 我们需要在 HTML 文档中添加如下样式:
css
/* These styles make the body full-height */
html,
body {
height: 100%;
}
/* These styles disable body scrolling if you are using <ScrollView> */
body {
overflow: hidden;
}
/* These styles make the root element full-height */
#root {
display: flex;
height: 100%;
}
注意: 如何你只是想单独使用一些组件, 可以不添加如上样式.
功能开发
接下来, 我们就可以按照 React Native 的开发模式, 开发我们的页面. 相关文档请参阅 React Native 和 React Native Web.
总结
现在, 我们已经可以使用 Vite 打包 React Native Web 项目了. 但是, 由于 React Native 和 Web 的开发模式不同, 所以在开发过程中, 你可能会遇到一些问题, 例如:
- React Native 的 API 或者组件在 Web 端不支持
- React Native 的样式在 Web 端不支持
但是, 我们已经在路上了, 后续会有更多的文章来讨论跨端的一些问题.