Это не просто эксперимент, поэтому он может не сработать для вас, потому что что-то могло измениться.

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