跨端系列: 使用 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 端不支持

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

参考资料

相关推荐
茶茶只知道学习4 分钟前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
蒟蒻的贤7 分钟前
Web APIs 第二天
开发语言·前端·javascript
清灵xmf11 分钟前
揭开 Vue 3 中大量使用 ref 的隐藏危机
前端·javascript·vue.js·ref
su1ka11116 分钟前
re题(35)BUUCTF-[FlareOn4]IgniteMe
前端
测试界柠檬18 分钟前
面试真题 | web自动化关闭浏览器,quit()和close()的区别
前端·自动化测试·软件测试·功能测试·程序人生·面试·自动化
多多*19 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
2301_8010741519 分钟前
TypeScript异常处理
前端·javascript·typescript
小阿飞_20 分钟前
报错合计-1
前端
caperxi22 分钟前
前端开发中的防抖与节流
前端·javascript·html
霸气小男22 分钟前
react + antDesign封装图片预览组件(支持多张图片)
前端·react.js