Это не просто эксперимент, поэтому он может не сработать для вас, потому что что-то могло измениться.
В этом мини-хакатоне я постараюсь проверить, можно ли объединить react-native, react-native-web и экспериментальный react-native-macos в единую базу кода. Если это сработает, это станет прекрасной возможностью для создания мультиплатформенных приложений, которые работают как на настольных компьютерах, так и в Интернете и на мобильных устройствах.
Я проверил response-native-web, и он выглядит многообещающим, потому что вы можете добавить префикс .web.js, чтобы сделать код специфичным для response-native-web. Моя гипотеза состоит в том, что есть похожая вещь, .macos.js для вещи react-native-macos.
Сначала я создам отдельный проект response-native-macos, чтобы я мог видеть, как работает структура исходного кода, и посмотреть, можно ли ее объединить с response-native-web и обычной экосистемой react-native. Мой проект начнется с моего проекта buddhalow, начатого с Exponent.
Alexanders-iMac:Apps buddhalow$ react-native-macos init buddhamac This will walk you through creating a new React Native for macOS project in /Users/buddhalow/Apps/buddhamac
После завершения процесса инициализации я проверю структуру папок.
Да, у него есть расширение .macos.js. Но, похоже, мне придется пройти через весь беспорядок с XCode, если я хочу его скомпилировать. В любом случае, теперь я создаю для начала отдельный файл index.macos.js.
Затем мне нужно скопировать файлы, относящиеся к macos, в мою новую папку. Теперь я добавляю сценарий macos ко вторым сценариям из package.json в моем проекте buddhamac в мой проект buddhalow.
package.json
...
"scripts": {
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "node node_modules/jest/bin/jest.js",
"macos": "react-native-macos run-macos"
},
...
Следующим шагом будет изменение префикса buddhamac на buddhalow в файлах проекта Xcode. Я открываю AppDelegate в папке проекта buddhamac, которую я скопировал из временного проекта buddhamac, который я создал и изменил. Нет, я передумал, зачем так лажать? Я позволю этому быть вместо этого и вернусь к «buddhamac» в моем index.macos.js, чтобы сэкономить время от неприятностей. Я слишком много раз был перфекционалистом и потратил слишком много часов на беспорядок из-за этого, и не делать этого было одним из моих новогодних обещаний на 2018 год.
В любом случае, теперь у меня есть файл index.macos.js, который выглядит так
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View } from 'react-native'; export default class buddhamac extends Component { render() { return ( <View style={styles.container}> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, }, }); AppRegistry.registerComponent('buddhamac', () => buddhamac);
Теперь я изменил файл на этот
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from 'react' import App from './App' import { AppRegistry, StyleSheet, Text, View } from 'react-native' export default class buddhamac extends Component { render() { return ( <App /> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, }, }); AppRegistry.registerComponent('buddhamac', () => buddhamac);
На сегодняшний день я создал две отдельные версии моего компонента IndexView, одну для Android и iOS. Теперь я собираюсь создать его для MacOS! Ура!

Я также создаю отдельную ветку для этого взлома в репозитории проекта, чтобы иметь возможность вернуться в случае, если что-то испортится.
Следствием этого является то, что мне приходится создавать отдельные версии моего DashboardView и других представлений в .macos.js, .web.js, .ios.js и .android.js соответственно, поскольку я считаю, что реагирующая навигация не будет работать должным образом. в macOS и в веб-ветке (мы должны использовать другие средства навигации). Пока что это большой недостаток у react-native, но если материалы react-native-web и -macos получат поддержку, я уверен, что появятся новые библиотеки, поддерживающие общие элементы навигации на всех трех платформах.
Итак, теперь я собираюсь разделить DashboardView на четыре версии (извините, я должен это сделать). Вот так выглядит DashboardView.js сегодня с помощью реакции-навигации.
import React from 'react'
import gql from 'graphql-tag'
import {graphql} from 'react-apollo'
import { StackNavigator } from 'react-navigation'
import OpportunityItem from '../components/OpportunityItem'
import { FlatList, Text, View } from 'react-native'
import I18n from '../i18n'
class Dashboard extends React.Component {
constructor(props) {
super(props)
}
static navigationOptions = {
title: I18n.t('dashboard')
}
render() {
return (
<View>
</View>
)
}
}
const query = gql`
{
allOpportunities {
name,
probability
}
}
`
let DashboardView = graphql(query)(Dashboard)
export default StackNavigator({
Dashboard: { screen: DashboardView }
})
Итак, теперь я разделил его на четыре разных версии, каждая для каждой платформы.

Теперь я должен делать следующее. Я просто скопировал один и тот же код во все четыре версии.
Я уже сейчас считаю, что это не совсем оптимально, потому что из-за этого приходится поддерживать много кода и нарушать принцип DRY, поскольку мне приходится писать код несколько раз для каждой платформы, но такова ситуация сегодня.
Я позволю Dashboard.android.js и Dashboard.ios.js оставаться такими же, но я бы предпочел, чтобы был дополнительный префикс .mobile, который нацелен как на iOS, так и на Android в проектах, подобных этому, например Dashboard.mobile.js, это было бы приемлемо, поскольку в противном случае я считаю, что macos и веб должны немного отличаться, но иметь мобильную версию js было бы здорово.
В любом случае, сейчас я займусь веб-версией этого кода.
Следующее, что нужно сделать, это создать веб-версию представления IndexView. Сначала добавьте response-router-dom. В терминал ввожу
yarn add react-router-dom
Так добавлены response-router-dom. Этот компонент следует использовать только в представлениях, предназначенных только для .web.js.
Итак, теперь я изменил IndexView.web.js на это:
import React from 'react'
import DashboardView from './DashboardView'
import OpportunitiesView from './OpportunitiesView'
import OpportunityView from './OpportunityView'
import {
Route,
Link,
BrowserRouter
} from 'react-router-dom'
export default class IndexView extends React.Component {
render() {
return (
<BrowserRouter>
<div>
<nav>
<ul>
<li>
<Link to="/dashboard">Dashboard</Link>
<Link to="/opportunities">Opportunities</Link>
</li>
</ul>
</nav>
<main>
<Route path="/dashboard" component={DashboardView} />
<Route path="/opportunity" component={OpportunitiesView} />
<Route path="/opportunity/:id" component={OpportunityView} />
</main>
</div>
</BrowserRouter>
)
}
}
На данный момент я удалил импорт других представлений, кроме DashboardView, потому что я просто проверяю, работает ли это. В противном случае при компиляции возникнут ошибки, потому что другие компоненты используют библиотеки, не подходящие для .web.
Но если серьезно, то сейчас я буду использовать тот же подход, что и с Dashboard.js, с Opportunities.js.
Итак, я создал OpportunitiesView.web.js с этим контентом.
import React from 'react'
import gql from 'graphql-tag'
import {graphql} from 'react-apollo'
import OpportunityItem from '../components/OpportunityItem'
import OpportunityView from './OpportunityView'
import I18n from '../i18n'
import {
Link
} from 'react-router-dom'
class Opportunities extends React.Component {
constructor(props) {
super(props)
}
static navigationOptions = ({navigation}) => {
return {
headerTitle: (
<Text>{I18n.t('opportunities')}</Text>
),
headerRight: (
<Button title={I18n.t('add')} onPress={() => navigation.navigate('Opportunity')} />
)
}
}
render() {
return (
<View>
{this.props.allOpportunities.map(item => {
return (
<Link to={´/opportunity/{item.id}´}>
<OpportunityItem style={{padding: 12}} data={item} />
</Link>
)
})}
</View>
)
}
}
const query = gql`
{
allOpportunities {
name,
probability
}
}
`
let OpportunitiesView = graphql(query)(Opportunities)
export default OpportunitiesView
А теперь сделайте то же самое с OpportunityView.js
(Опять же, мне очень грустно, что я должен это сделать, но я надеюсь, что по мере того, как этот подход набирает обороты, держу пари, что найдутся вещи, решающие эту проблему)
В данный момент основное внимание уделяется тому, чтобы кроссплатформенный подход заработал, и ничего больше.
Мне также нужно создать версию моего пользовательского компонента ProgressBar для веб-подразделения моего проекта.
ProgressBar.web.js
import React from 'react'
export default class ProgressBar extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<progress progress={this.props.progress} />
)
}
}
Хорошо, давай посмотрим, пока это сработает.
Сейчас я попытался войти
yarn start web
В моем терминале, но, похоже, он запускает сценарии реагирования, что не было предполагаемым случаем. Я надеялся, что это оказалось непросто, но давайте протестируем другой подход.
Я читал в документации для response-native-web, что для тестирования запуска вы можете ввести
./node_modules/.bin/webpack-dev-server -d --config ./web/webpack.config.js --inline --hot --colors
в корневой папке. Поэтому я добавил это как «веб-запись» в раздел сценариев файла package.json в моем проекте.
"scripts": {
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "node node_modules/jest/bin/jest.js",
"macos": "react-native-macos run-macos",
"web": "./node_modules/.bin/webpack-dev-server -d --config ./web/webpack.config.js --inline --hot --colors"
},
Посмотрим, что происходит, когда я запускаю пряжу в паутине. Скрещивая пальцы!
К сожалению, веб-пакет не настроен.
Alexanders-iMac:buddhalow buddhalow$ yarn web yarn run v1.3.2 $ ./node_modules/.bin/webpack-dev-server -d --config ./web/webpack.config.js --inline --hot --colors /bin/sh: ./node_modules/.bin/webpack-dev-server: No such file or directory error Command failed with exit code 127. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Давай исправим это.
Теперь я прочитал в том же документе, что вам нужно создать сценарий веб-упаковки.
Итак, в директории проекта я ввожу yarn add --dev babel-loader url-loader webpack webpack-dev-server
Я предполагаю, что это будет работать лучше, если только в компиляторе не будет задействован какой-либо JavaScript, который имеет дело с файлами JavScript, которые включают компоненты для мобильных устройств и специфические для MacOS вещи (например, реагирование-навигацию и т. Д.)
Затем я должен следовать инструкциям и создать новую папку web и поместить файл webpack.config.js со следующим содержимым (взято из https://github.com/necolas/react-native-web/blob/ master / website / guides / getting-started.md )
// 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$/,
// 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,
// Babel configuration (or use .babelrc)
// This aliases 'react-native' to 'react-native-web' and includes only
// the modules needed by the app.
plugins: ['react-native-web'],
// The 'react-native' preset is recommended to match React Native's packager
presets: ['react-native']
}
}
};
// 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]'
}
}
};
module.exports = {
// your web-specific entry file
entry: 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
]
},
plugins: [
// `process.env.NODE_ENV === 'production'` must be `true` for production
// builds to eliminate development checks and reduce build size. You may
// wish to include additional optimizations.
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
__DEV__: process.env.NODE_ENV === 'production' || true
})
],
resolve: {
// 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' ]
}
}
Хорошо, попробуем еще раз. Скрещиваю пальцы! Компиляция запустила веб-сервер, но не удалась:
ERROR in ./index.web.js Module build failed: ReferenceError: document is not defined at hasCanvas (/Users/buddhalow/Apps/buddhalow/node_modules/art/modes/fast-noSideEffects.js:3:16) at Object.<anonymous> (/Users/buddhalow/Apps/buddhalow/node_modules/art/modes/fast-noSideEffects.js:8:5) at Module._compile (module.js:643:30) at Object.Module._extensions..js (module.js:654:10) at Module.load (module.js:556:32) at tryModuleLoad (module.js:499:12) at Function.Module._load (module.js:491:3) at Module.require (module.js:587:17) at require (internal/module.js:11:18) at /Users/buddhalow/Apps/buddhalow/node_modules/react-art/cjs/react-art.development.js:27:25 @ multi (webpack)-dev-server/client?https://localhost:8080 webpack/hot/dev-server ./index.web.js
Эта ошибка отмечена в этой проблеме GitHub: https://github.com/necolas/react-native-web/issues/737
Насколько я понимаю из комментариев, похоже, что это какие-то компоненты, которые невозможно использовать в коде рендеринга на стороне сервера.
Я могу сказать, что проблема заключается в том, что он загрузил App.js, который имеет зависимости только от собственных мобильных компонентов, поэтому я разделил его на три версии, причем версии для Android и iOS остались нетронутыми, а третья версия для Интернета со следующим кодом .
import React from 'react'
import client from './client'
import IndexView from './views/IndexView'
import './i18n' // import this before RootContainer as RootContainer is using react-native-i18n, and I18n.js ne
class App extends React.Component {
componentWillMount() {
}
render() {
return (
<ApolloProvider client={client} style={{backgroundColor: 'white'}}>
<IndexView />
</ApolloProvider>
)
}
}
export default App
// TODO Look at React Native navigation https://wix.github.io/react-native-navigation/#/usage
Опять тестирование.
К сожалению, та же ошибка произошла снова. Похоже, что есть конфликт с компонентом, предоставляющим какой-то элемент холста. Ошибка находится в модуле узла под названием «искусство».
Я поискал искусство в реестре NPM и узнал, что «искусство» - это модуль, используемый для рендеринга векторной графики, и он использует режим холста.
Кстати обнаружил, что не установил вавилонскую штуку:
yarn add — dev babel-plugin-react-native-web
После запуска этого я снова протестировал сеть пряжи, и она кажется лучше, хотя были некоторые другие проблемы, по моей ошибке такие синтаксические ошибки, но это было легко исправлено.
index.web.js
import App from './App'
import React from 'react'
import { AppRegistry } from 'react-native'
// register the app
AppRegistry.registerComponent('App', () => App)
AppRegistry.runApplication('App', {
initialProps: {},
rootTag: document.getElementById('react-app')
});
yarn start теперь выдает эту ошибку
ERROR in ./App.web.js
Module parse failed: Unexpected token (13:6)
You may need an appropriate loader to handle this file type.
| render() {
| return (
| <ApolloProvider client={client} style={{backgroundColor: 'white'}}>
| <IndexView />
| </ApolloProvider>
@ ./index.web.js 1:9-25
@ multi (webpack)-dev-server/client?https://localhost:8080 webpack/hot/dev-server ./index.web.js
Это было довольно интересно, проблема с типом файла. Похоже, поиск в Google подсказывает мне, что в моем файле веб-пакета babel отсутствует пресет:
Итак, я установил пресет es2015
пряжа добавить babel-preset-es2015
а затем обновил предустановку файла webpack.config.js
use: {
loader: ‘babel-loader’,
options: {
cacheDirectory: true,
// Babel configuration (or use .babelrc)
// This aliases ‘react-native’ to ‘react-native-web’ and includes only
// the modules needed by the app.
plugins: [‘react-native-web’],
// The ‘react-native’ preset is recommended to match React Native’s packager
presets: [‘react-native’, ‘es2015’]
}
}
Затем снова тестирование, но с той же ошибкой. Теперь я тестирую, чтобы добавить babel-preset-jsx
пряжа добавить babel-preset-react
Затем снова измените файл на следующий
options: {
cacheDirectory: true,
// Babel configuration (or use .babelrc)
// This aliases ‘react-native’ to ‘react-native-web’ and includes only
// the modules needed by the app.
plugins: [‘react-native-web’],
// The ‘react-native’ preset is recommended to match React Native’s packager
presets: [‘react-native’, ‘es2015’, 'react']
}
}
Похоже, есть проблемы с webpack.config.js, потому что пресеты и плагины не работают должным образом. На этой странице я получил подсказку,
Возможно, это связано с тем, что я опускаю расширение .js при импорте некоторых файлов. Пытаюсь добавить "" в массив расширений.
Оказалось, что это проблема пути, поэтому я добавил
path.resolve (appDirectory), но теперь возникает другая ошибка. И это потому, что он случайно включает в себя react-native, поскольку я использую библиотеку react-native-i18n.
Мое решение - сделать его специфичным для iOS и Android и создать макет для других платформ (пока).
Итак, я создал i18n.web.js
class I18n {
constructor () {
}
t(str) {
return this.translations.en[str]
}
}
let i18n = new I18n();
i18n.fallbacks = true;
// I18n.js
i18n.translations = {
en: require('./lang/en.json'),
sv: require('./lang/sv.json')
}
export default i18n
К сожалению, конфигурация webpack-dev-server, предоставленная react-native-web, не работала должным образом, поэтому мне пришлось собрать для продакшена.
./node_modules/.bin/webpack -p --config ./web/webpack.config.js
А затем добавьте файл index.html в папку dist
<!DOCTYPE html>
<html>
<head>
<title>Buddhalow</title>
</head>
<body>
<div id="react-app"></div>
<script src="bundle.web.js"></script>
</body>
</html>
А затем протестируйте его оттуда, запустив сеть пряжи, а затем перейдите к
Http: // localhost: 8080 / dist / index.html
Это была часть 1. Следующая часть будет позже. Будьте на связи!