跨端系列: 使用 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 [email protected] [email protected]

# 若需要支持 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 [email protected] [email protected]

# 若需要支持 TypeScript
pnpm add -D @types/react@17 @types/react-dom@17
pnpm add -D @types/[email protected]

注意 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 端不支持

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

参考资料

相关推荐
GISer_Jing42 分钟前
前端性能指标及优化策略——从加载、渲染和交互阶段分别解读详解并以Webpack+Vue项目为例进行解读
前端·javascript·vue
不知几秋44 分钟前
数字取证-内存取证(volatility)
java·linux·前端
水银嘻嘻2 小时前
08 web 自动化之 PO 设计模式详解
前端·自动化
Zero1017134 小时前
【详解pnpm、npm、yarn区别】
前端·react.js·前端框架
&白帝&4 小时前
vue右键显示菜单
前端·javascript·vue.js
Wannaer4 小时前
从 Vue3 回望 Vue2:事件总线的前世今生
前端·javascript·vue.js
羽球知道5 小时前
在Spark搭建YARN
前端·javascript·ajax
光影少年5 小时前
vue中,created和mounted两个钩子之间调用时差值受什么影响
前端·javascript·vue.js
青苔猿猿5 小时前
node版本.node版本、npm版本和pnpm版本对应
前端·npm·node.js·pnpm
一只码代码的章鱼6 小时前
Spring的 @Validate注解详细分析
前端·spring boot·算法