教程:如何使用 React Native、react-native-web 和 monorepo 在 iOS、Android 和 Web 之间共享代码
让我们的react-native应用程序以正确的方式在浏览器中运行。
本教程是为 制作的
react-native <= 0.61。如果您使用的是较新版本,我建议您 fork 此仓库:brunolemos/react-native-web-monorepo,我会持续更新它 🙌
我为什么要写这个?
大家好👋我是Bruno Lemos 。我最近在 GitHub 上发布了一个名为 DevHub - TweetDeck的项目,它之所以受到大家的关注,是因为它是一个由一位开发者开发的应用,目前支持 6 个平台:Web(react-native-web)、iOS()react native、Android(react native)、macOS、Windows 和 Linux(electron),这些平台之间几乎 100% 的代码共享。它甚至与服务器共享部分代码!而几年前,这还需要 3 人以上的团队才能完成。
从那时起,我收到了数十条推文和私人消息,询问如何实现同样的目标,在本教程中我将引导您完成它。
啥react-native-web?
如果您不熟悉react-native-web ,它是Necolas(前 Twitter 工程师)开发的一个库,用于让您的React Native代码在浏览器中渲染。简单来说,您编写代码<View />,它就会渲染<div />,确保所有样式渲染的内容完全相同。它的功能远不止于此,但我们还是简单介绍一下。
新的 Twitter就是使用这项技术创建的,非常棒。
如果您已经了解react-native,则无需学习任何新语法。它们是相同的 API。
概括
- 开始新
React Native项目 - 将我们的文件夹结构转变为 monorepo
react-native在 monorepo 中工作- 在我们的 monorepo 包之间共享代码
create-react-app使用和创建新的 Web 项目react-native-web- 通过代码共享
CRA在我们的内部开展工作monorepo - ???
- 利润
分步教程
开始新React Native 项目
$ react-native init myprojectname$ cd myprojectname$ git init && git add . -A && git commit -m "Initial commit"
注意:从头开始创建跨平台应用程序比尝试移植现有的仅限移动设备(或更难:仅限网络)项目要容易得多,因为它们可能使用大量特定于平台的依赖项。
编辑:如果您使用 expo,看来他们很快就会内置对 web 的支持!
将我们的文件夹结构转变为 monorepo
Monorepo 意味着在单个仓库中拥有多个包,以便您可以轻松地在它们之间共享代码。这听起来可能有点复杂,因为两者都react-native需要create-react-app一些工作来支持 monorepo 项目。不过,至少这是可能的!
Yarn Workspaces我们将使用为此调用的功能。
要求:Node.js、Yarn和React Native。
- 确保您位于项目根文件夹
$ rm yarn.lock && rm -rf node_modules$ mkdir -p packages/components/src packages/mobile packages/web- 将所有文件(除
.git)移动到packages/mobile文件夹 - 编辑从到的
name字段packages/mobile/package.jsonpackagenamemobile package.json在根目录中创建此项以启用Yarn Workspaces:
{
"name": "myprojectname",
"private": true,
"workspaces": {
"packages": [
"packages/*"
],
"nohoist": []
}
"dependencies": {
"react-native": "0.61.3"
}
}
.gitignore在根目录下创建 :
.DS_Store
.vscode
node_modules/
yarn-error.log
$ yarn
使 React Native 在 Monorepo 中工作
-
检查
react-native安装位置。如果是在/node_modules/react-native,那就没问题。如果是在/packages/mobile/node_modules/react-native,那就有问题了。确保您拥有node和 的最新版本yarn。同时,请确保在 monorepo 包之间使用完全相同版本的依赖项,例如"react": "16.11.0"在 和 上mobile,components而不是在它们之间使用不同的版本。 -
打开您最喜欢的编辑器并使用该
Search & Replace功能将所有出现的 替换node_modules/react-native/为../../node_modules/react-native/。 -
对于 react-native <= 0.59 版本,请打开
packages/mobile/package.json。您的start脚本当前以 结尾/cli.js start。请将此内容附加到末尾:--projectRoot ../../。 -
打开
packages./mobile/metro.config.js并在其上设置projectRoot字段,使其看起来像这样:
const path = require('path')
module.exports = {
projectRoot: path.resolve(__dirname, '../../'),
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
}
- [解决方法] 您当前需要将
react-native依赖项添加到根目录package.json才能捆绑 JS:
"dependencies": {
"react-native": "0.61.3"
},
iOS 更改
$ open packages/mobile/ios/myprojectname.xcodeproj/- 打开
AppDelegate.m,查找jsBundleURLForBundleRoot:@"index"并替换index为packages/mobile/index - 仍在 Xcode 中,点击左侧的项目名称,然后转到
Build Phases>Bundle React Native code and Images。将其内容替换为以下内容:
export NODE_BINARY=node
export EXTRA_PACKAGER_ARGS="--entry-file packages/mobile/index.js"
../../../node_modules/react-native/scripts/react-native-xcode.sh
$ yarn workspace mobile start
现在您可以运行 iOS 应用了!💙 选择一个 iPhone 模拟器,然后按下 Xcode 中的“运行”三角形按钮。
Android 变更
$ studio packages/mobile/android/- 打开
packages/mobile/android/app/build.gradle。搜索文本project.ext.react = [...]。编辑它,使其看起来像这样:
project.ext.react = [
entryFile: "packages/mobile/index.js",
root: "../../../../"
]
- Android Studio 将显示“立即同步”弹出窗口。点击它。
- 打开
packages/mobile/android/app/src/main/java/com/myprojectname/MainApplication.java。搜索getJSMainModuleName方法。将其替换index为packages/mobile/index,如下所示:
@Override
protected String getJSMainModuleName() {
return "packages/mobile/index";
}
如果出现
Cannot get property 'packageName' on null object错误,请尝试禁用自动链接
现在您可以运行 Android 应用了!💙 按下 Android Studio 中的“运行”绿色三角形按钮,然后选择模拟器或设备。
在我们的 monorepo 包之间共享代码
我们在 monorepo 中创建了很多文件夹,但mobile目前只用过一次。让我们准备一下代码库,以便代码共享,然后将一些文件移到components包中,这样就可以在mobile、web以及我们决定将来支持的任何其他平台(例如desktop、server等)上重复使用。
packages/components/package.json创建包含以下内容的文件:
{
"name": "components",
"version": "0.0.1",
"private": true
}
-
[可选] 如果您决定在将来支持更多平台,您将为它们执行相同的操作:创建
packages/core/package.json、、等packages/desktop/package.json。packages/server/package.json每个平台的名称字段必须是唯一的。 -
打开
packages/mobile/package.json。添加所有要使用的 monorepo 包作为依赖项。在本教程中,mobile仅使用以下components包:
"dependencies": {
"components": "0.0.1",
...
}
- 如果 react-native 打包程序正在运行,请停止它
$ yarn$ mv packages/mobile/App.js packages/components/src/- 打开
packages/mobile/index.js。替换import App from './App'为import App from 'components/src/App'。这就是魔法的原理。现在一个包可以访问其他包了! - 编辑
packages/components/src/App.js,替换Welcome to React Native!为,Welcome to React Native monorepo!这样我们就知道我们正在渲染正确的文件。 $ yarn workspace mobile start
太棒了!现在您可以刷新正在运行的 iOS/Android 应用,并查看来自共享组件包的屏幕了。🎉
$ git add . -A && git commit -m "Monorepo"
Web 项目
注意:您可以复用多达 100% 的代码,但这并不意味着您应该这样做。建议在不同平台之间保留一些差异,以便用户感觉更自然。为此,您可以创建以 、 或 结尾的平台特定
.web.js文件.ios.js。.android.js请.native.js参阅示例。
使用 CRA 和 react-native-web 创建新的 Web 项目
$ cd packages/$ npx create-react-app web$ cd ./web(请留在此文件夹中以进行后续步骤)$ rm src/*(或者手动删除里面的所有文件packages/web/src)- 确保
package.json所有 monorepo 包中的依赖项完全相同。例如,将web和两个mobile包中的“react”版本更新为“16.9.0”(或任何其他版本)。 $ yarn add react-native-web react-art$ yarn add --dev babel-plugin-react-native-webpackages/web/src/index.js创建包含以下内容的文件:
import { AppRegistry } from 'react-native'
import App from 'components/src/App'
AppRegistry.registerComponent('myprojectname', () => App)
AppRegistry.runApplication('myprojectname', {
rootTag: document.getElementById('root'),
})
注意:当我们从项目
react-native内部导入时create-react-app,它的webpack配置会自动为我们将其别名化。react-native-web
packages/web/public/index.css创建包含以下内容的文件:
html,
body,
#root,
#root > div {
width: 100%;
height: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
- 在关闭标签之前进行编辑
packages/web/public/index.html以包含我们的 CSShead:
...
<title>React App</title>
<link rel="stylesheet" href="%PUBLIC_URL%/index.css" />
</head>
通过代码共享使 CRA 在我们的 monorepo 中工作
默认情况下, CRA 不会构建src文件夹外的文件。我们需要让它执行此操作,以便它能够理解来自 monorepo 包的代码,这些代码包含 JSX 和其他非纯 JS 代码。
- 留在室内
packages/web/进行下一步 - 创建一个 包含以下内容的
.env文件( ):packages/web/.env
SKIP_PREFLIGHT_CHECK=true
$ yarn add --dev react-app-rewired- 将里面的脚本替换
packages/web/package.json为:
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
- 创建
packages/web/config-overrides.js包含以下内容的文件:
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const appDirectory = fs.realpathSync(process.cwd())
const resolveApp = relativePath => path.resolve(appDirectory, relativePath)
// our packages that will now be included in the CRA build step
const appIncludes = [
resolveApp('src'),
resolveApp('../components/src'),
]
module.exports = function override(config, env) {
// allow importing from outside of src folder
config.resolve.plugins = config.resolve.plugins.filter(
plugin => plugin.constructor.name !== 'ModuleScopePlugin'
)
config.module.rules[0].include = appIncludes
config.module.rules[1] = null
config.module.rules[2].oneOf[1].include = appIncludes
config.module.rules[2].oneOf[1].options.plugins = [
require.resolve('babel-plugin-react-native-web'),
].concat(config.module.rules[2].oneOf[1].options.plugins)
config.module.rules = config.module.rules.filter(Boolean)
config.plugins.push(
new webpack.DefinePlugin({ __DEV__: env !== 'production' })
)
return config
}
上面的代码覆盖了一些
create-react-app配置,webpack因此它在 CRA 的构建步骤中包含了我们的 monorepo 包
$ git add . -A && git commit -m "Web project"
就这样!现在你可以yarn start在里面packages/web(或yarn workspace web start根目录)运行来启动 Web 项目,与我们的react-native mobile项目共享代码!🎉
一些陷阱
react-native-web支持大多数react-nativeAPI,但缺少一些部分,如Alert、和;ModalRefreshControlWebView- 如果您遇到与 monorepo 结构不能很好地兼容的依赖项,您可以将其添加到nohoist列表中;但尽可能避免这样做,因为它可能会导致其他问题,特别是对于 metro bundler。
一些建议
- 导航可能有点挑战;您可以使用最近添加了 Web 支持的react-navigation之类的东西,或者您可以尝试在和移动设备之间使用两个不同的导航器,以防您想通过牺牲一些代码共享来获得两全其美的效果;
- 如果您计划与服务器共享代码,我建议创建一个
core仅包含逻辑和辅助函数(没有与 UI 相关的代码)的包; - 对于 Next.js,你可以使用 react-native-web 查看其官方示例
- 对于原生windows,可以尝试react-native-windows;
- 对于原生 macOS,您可以使用新的 Apple Project Catalyst,但对它的支持尚未 100% 实现(请参阅我的推文);
- 要安装新的依赖项,请使用根目录中的命令
yarn workspace components add xxx。例如,要从某个包运行脚本,请运行yarn workspace web start;要从所有包运行脚本,请运行yarn workspaces run scriptname;
谢谢阅读!💙
如果您喜欢 React,请考虑在 Dev.to 和Twitter上关注我。
链接
- 源代码:react-native-web-monorepo
- DevHub:devhubapp/devhub(使用此结构的生产应用程序+桌面+TypeScript)
- 推特:@brunolemos
后端开发教程 - Java、Spring Boot 实战 - msg200.com


