Это не просто эксперимент, поэтому он может не сработать для вас, потому что что-то могло измениться.
В этом мини-хакатоне я постараюсь проверить, можно ли объединить 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. Следующая часть будет позже. Будьте на связи!