跨端系列: 使用 Vite 打包 React Native Web 项目

背景

当前, 我们使用 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 NativeReact Native Web.

总结

现在, 我们已经可以使用 Vite 打包 React Native Web 项目了. 但是, 由于 React Native 和 Web 的开发模式不同, 所以在开发过程中, 你可能会遇到一些问题, 例如:

  • React Native 的 API 或者组件在 Web 端不支持
  • React Native 的样式在 Web 端不支持

但是, 我们已经在路上了, 后续会有更多的文章来讨论跨端的一些问题.

参考资料

相关推荐
学不会•32 分钟前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜3 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点3 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow3 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o3 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic4 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā4 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年5 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder5 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727576 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架