如何在没有 Expo 的情况下创建适用于 Android、iOS 和 Web 的 React Native 应用
包名的别名
默认将流程指向“模块”字段
注意:本文于 2021 年 8 月 8 日更新,以解决此 StackOverflow 问题。本次更新
.babelrc
移除了 的创建,babel.config.js
并更新了 。这些更改与本次提交 的webpack.config.js
效果相同。
在本文中,我们将了解如何创建一个可在 Android、iOS 和 Web 浏览器上运行的 React Native 应用。此过程中我们将不使用Expo。为了支持 Web 应用,我们将使用react-native-web包。
我使用的是 Windows 系统,因此我将仅展示在 Android 和 Web 上运行的项目。我假设您已经下载并设置了 Node、NPM、Android SDK、Java 以及用于构建和调试的模拟器/设备。如果没有,请继续关注本文。
我的环境:
- 操作系统: Windows 10(64位)
- 节点: 16.3.0
- NPM: 7.17
如果您想进一步了解选择哪种混合应用程序开发框架,那么您可以查看这篇文章:React Native vs Ionic vs Flutter
步骤 1:初始化 React Native 应用程序:
此步骤与官方 React Native 文档相同。因此对于init
React Native 应用:
- 打开命令提示符并转到您想要创建项目的路径,在我的情况下该路径是
C:\Users\shivam\Desktop\React
。 - 初始化应用程序:
npx react-native init AwesomeProject
- 您的文件夹将看起来像这样提交。
AwesomeProject
您会在当前目录中找到一个新文件夹,现在使用任何编辑器打开此文件夹,我正在使用Visual Studio Code。
第 2 步:在 Android 中运行此新应用(可选)
如果您已完成 Android 设置并连接了模拟器或设备,那么您只需在文件夹中使用命令提示符运行以下命令即可在 Android 中运行该应用程序AwesomeProject
。
npx react-native run-android
步骤3:Web设置
如前所述,我们将使用react-native-web包来提供 Web 支持。因此,您可以在该包的官方文档中找到简短的设置说明。
1. 添加Web包
react-native-web
添加用于 Web API 和react-dom
浏览器的包。
npm install react-dom react-native-web
如果您看到任何错误,unable to resolve dependency tree
那么您可以使用--legacy-peer-deps
如下所示的选项。
npm install react-dom react-native-web --legacy-peer-deps
2. Babel 插件用于构建时间优化
根据官方文档的建议,使用babel插件babel-plugin-react-native-web
。
npm install --save-dev babel-plugin-react-native-web
3. Babel 模块别名
由于我们要使用babel-plugin-module-resolver来设置别名,react-native
以便Babel支持模块别名,react-native-web
所以我们将使用它。
npm install --save-dev babel-plugin-module-resolver
现在,为了设置别名,我们将在 webpack 配置中使用此包。(这些设置将在 文件中web/webpack.config.js
,我们将在本文后面介绍此文件)
4. 嘲笑
您可以使用提供的预设配置Jestreact-native
。这将映射到react-native-web
并提供适当的模拟。
为此,在/package.json
文件中,将键的值"jest"
从"react-native"
更新为"react-native-web"
。最终值:
/package.json
{
// Other Settings
"jest": {
"preset": "react-native-web"
}
}
5.配置流程
Flow是一个类似TypeScript的 JavaScript 静态类型检查器。如果你没有使用 TypeScript 模板初始化项目,React-Native 会默认使用它。
它可以配置为理解别名模块。为此,我们需要在[options]
文件内的 key下添加一些配置文本/.flowconfig
。
/.flowconfig
[options]
# Alias the package name
module.name_mapper='^react-native$' -> 'react-native-web'
可以将 Flow 配置为从 React Native for Web 的源代码中提取类型。为此,请在[options]
键中添加以下配置文本。
/.flowconfig
[options]
# Point flow to the 'module' field by default
module.system.node.main_field=module
module.system.node.main_field=main
6. 包装优化
我们在步骤 3 中添加了babel-plugin-module-resolver包,建议使用它来进行构建时优化,并修剪应用程序未使用的模块。为了配置它,我们将使用 webpack 配置,因此您的/babel.config.js
文件应该如下所示。
/babel.config.js
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};
7.创建入口文件
对于网页版,我们需要创建2个入口文件,第一个是index.html
,第二个是index.web.js
,都需要放在根路径下。
/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Testing React Native on the Web!</title>
<meta content="initial-scale=1,width=device-width" name="viewport" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<style>
/* These styles make the body full-height */
html,
body,
#root {
height: 100%;
}
/* These styles disable body scrolling if you are using <ScrollView> */
body {
overflow: hidden;
}
/* These styles make the root element flex and column wise filling */
#root {
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<div id="react-native-web-app"></div>
<script type="text/javascript" src="/bundle.web.js"></script>
</body>
</html>
注意脚本名称src="/bundle.web.js"
,我们将在配置webpack时使用这个文件名。
/index.web.js
import React from 'react';
import {AppRegistry} from 'react-native';
import App from './src/components/App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
AppRegistry.runApplication(appName, {
rootTag: document.getElementById('react-native-web-app'),
});
如果你注意到,除了最后一行之外,其他部分几乎相同index.js
。如上所示,我们正在使用一个 App 组件,但它从何而来?它就是复制的同一个文件,只不过路径中App.js
包含了名称。这只是为了演示一个重要的概念,我们将在本文后面学习,因为这个文件会产生一些问题。所以看起来就像下面这样:App.jsx
/src/components/
/src/components/App.jsx
注意:与许多编辑器/IDE 一样,同一个 App.js 文件可能会显示错误,因为该文件使用 Flow 语法进行类型定义,而您的 IDE 可能不支持此语法。为了解决这个问题,您可以通过扩展添加 Flow 语言支持,或者删除特定于 Flow 的代码。为了节省您的故障排除时间,我在下面的示例中删除了特定于 Flow 的代码。
/src/components/App.jsx
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React from 'react';
import {Node} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';
import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
const Section = ({children, title}) => {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
);
};
const App = () => {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<Header />
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
<Section title="Step One">
Edit <Text style={styles.highlight}>App.js</Text> to change this
screen and then come back to see your edits.
</Section>
<Section title="See Your Changes">
<ReloadInstructions />
</Section>
<Section title="Debug">
<DebugInstructions />
</Section>
<Section title="Learn More">
Read the docs to discover what to do next:
</Section>
<LearnMoreLinks />
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});
export default App;
8. 配置和绑定
我们将使用Webpack进行捆绑并使用Babel进行转译babel-loader
。
安装 Webpack 和相关依赖项:在终端中运行以下命令来安装开发环境的包。
npm install --save-dev babel-loader url-loader webpack webpack-cli webpack-dev-server
摇树优化: React Native 的 Babel 预设将 ES 模块重写为 CommonJS 模块,从而防止打包器自动执行“摇树优化”来从 Web 应用构建中删除未使用的模块。为了解决这个问题,您可以安装以下 Babel 插件:
npm install --save-dev babel-plugin-react-native-web
Webpack 配置:
此配置取自官方文档,并略作修改以添加.jsx
支持,module-resolver
我们通过 添加了上述配置babel-plugin-module-resolver
。因此,要配置 Webpack,请在 处创建一个文件/web/webpack.config.js
。我们将使用它webpack-cli
来区分开发版本和生产版本,如果您想通过脚本进行管理,则可以使用本指南。
/web/webpack.config.js
const path = require('path');
const webpack = require('webpack');
const appDirectory = path.resolve(__dirname, '../');
// This is needed for webpack to compile JavaScript.
// Many OSS React Native packages are not compiled to ES5 before being
// published. If you depend on uncompiled packages they may cause webpack build
// errors. To fix this webpack can be configured to compile to the necessary
// `node_module`.
const babelLoaderConfiguration = {
test: /\.(js)|(jsx)$/,
// Add every directory that needs to be compiled by Babel during the build.
include: [
path.resolve(appDirectory, 'index.web.js'),
path.resolve(appDirectory, 'src'),
path.resolve(appDirectory, 'node_modules/react-native-uncompiled'),
],
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
// The 'metro-react-native-babel-preset' preset is recommended to match React Native's packager
presets: ['module:metro-react-native-babel-preset'],
// Re-write paths to import only the modules needed by the app
plugins: [
'react-native-web',
[
'module-resolver',
{
alias: {
'^react-native$': 'react-native-web',
},
},
],
],
},
},
};
// This is needed for webpack to import static images in JavaScript files.
const imageLoaderConfiguration = {
test: /\.(gif|jpe?g|png|svg)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
esModule: false,
},
},
};
module.exports = {
entry: [
// load any web API polyfills
// path.resolve(appDirectory, 'polyfills-web.js'),
// your web-specific entry file
path.resolve(appDirectory, 'index.web.js'),
],
// configures where the build ends up
output: {
filename: 'bundle.web.js',
path: path.resolve(appDirectory, 'dist'),
},
// ...the rest of your config
module: {
rules: [babelLoaderConfiguration, imageLoaderConfiguration],
},
resolve: {
// This will only alias the exact import "react-native"
alias: {
'react-native$': 'react-native-web',
},
// If you're working on a multi-platform React Native app, web-specific
// module implementations should be written in files using the extension
// `.web.js`.
extensions: ['.web.js', '.js', '.jsx'],
},
};
9. 在 Web 上运行的脚本
现在,我们将添加一些脚本,以便使用简短的命令(而不是完整的 webpack-cli 命令)来运行我们的 Web 应用。为此,我们需要在文件 key 中添加以下两个/package.json
选项"scripts"
。
要了解有关webpack-cli
webpack-5 选项的更多信息,请访问此处;要了解有关 dev-tool 的更多信息,请访问此处
/package.json
{
"scripts": {
"web": "webpack serve -d source-map --mode development --config \"./web/webpack.config.js\" --inline --color --hot",
"build:web": "webpack --mode production --config \"./web/webpack.config.js\" --hot"
}
}
10. 运行我们的网页应用程序
所以,我们终于到了这里,因为我们已经在package.json
脚本中设置了快捷方式,所以现在我们可以简单地运行下面的命令在浏览器中启动我们的 webapp。
npm run web
等一下!我收到错误,如下所示:
ERROR in ./node_modules/react-native/Libraries/NewAppScreen/components/DebugInstructions.js 11:12
Module parse failed: Unexpected token (11:12)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| */
|
> import type {Node} from 'react';
| import {Platform, StyleSheet, Text} from 'react-native';
| import React from 'react';
@ ./node_modules/react-native/Libraries/NewAppScreen/index.js 17:0-63 20:0-27:2
@ ./src/components/App.jsx 1:864-910
@ ./index.web.js 1:261-292
这就是我们在步骤 7中提到的错误。我花了 3-4 天的时间才找到解决方案。后来,该react-native-web
软件包的创建者和维护者Nicolas Gallagher通过这个讨论帮助了我。
所以问题出import
在第 21 行的语句中src/components/App.jsx
,我们尝试执行如下操作:
堆栈跟踪中的这一行告诉我们,我们正在尝试将 RN 内部代码捆绑在我们的 Web 包中:
node_modules/react-native/Libraries/NewAppScreen/index.js
。首先,我们不应该在 Web 上加载任何 RN 包,尤其是那些不属于公共 API 的部分。其次,正如 config 的内联注释中提到的
web/webpack.config.js
,我们需要明确列出所有node_modules
需要编译的内容。import { Colors, DebugInstructions, Header, LearnMoreLinks, ReloadInstructions, } from 'react-native/Libraries/NewAppScreen';
所有这些代码都存在问题。我们不应该从库中导入。这不属于公共 API 的一部分。
为了解决这个问题,请删除对库的依赖:react-native/Libraries
,为此更新代码/src/components/App.jsx
如下:
/src/components/App.jsx
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React from 'react';
import {Node} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';
// import {
// Colors,
// DebugInstructions,
// Header,
// LearnMoreLinks,
// ReloadInstructions,
// } from 'react-native/Libraries/NewAppScreen';
const Colors = {
white: '#fff',
black: '#000',
light: '#ddd',
dark: '#333',
lighter: '#eee',
darker: '#111',
};
const Section = ({children, title}) => {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
);
};
const App = () => {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
{/* <Header /> */}
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
<Section title="Step One">
Edit <Text style={styles.highlight}>App.js</Text> to change this
screen and then come back to see your edits.
</Section>
<Section title="See Your Changes">
{/* <ReloadInstructions /> */}
<Text>Reload Instruction</Text>
</Section>
<Section title="Debug">
{/* <DebugInstructions /> */}
<Text>Debug Instruction</Text>
</Section>
<Section title="Learn More">
Read the docs to discover what to do next:
</Section>
{/* <LearnMoreLinks /> */}
<Text>Learn More Links</Text>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});
export default App;
11. 故障排除后最终运行
如果它之前已停止,我们可以在终端中运行以下命令来在浏览器中启动我们的 web 应用程序。
npm run web
您应该获得类似于以下屏幕截图的输出,并且您可以转到http://localhost:8080查看您的 webapp 正在运行。
我相信这肯定会对某些人有所帮助,如果我之前知道的话,至少可以节省我4天的时间。所有为添加Web支持所做的更改都可以在这个更新前的提交和这个更新后的提交中找到。
我也为此创建了一个版本。
我在这个虚拟项目上使用了相同的配置,更新后没有遇到任何问题。
如果您发现配置中的任何问题并得到解决,请毫不犹豫地为repo做出贡献。
文章来源:https://dev.to/shivams136/create-react-native-app-for-android-ios-and-web-without-expo-48lc