Files
react-native-web/packages/website/guides/getting-started.md
Ben Milman b84e3b938a Fix typo in docs
Close #1049
2018-08-17 10:56:54 -07:00

8.8 KiB

Getting started

This guide will help you render components and applications with React Native for Web.

Your application may need to polyfill Promise, Object.assign, Array.from, and ResizeObserver as necessary for your desired browser support.

If you're not familiar with setting up a new React web project, please follow the recommendations in the React documentation.

Install

yarn add react react-dom react-native-web

And if you need to use ART:

yarn add react-art

Starter kits

Web: create-react-app includes built-in support for aliasing react-native-web to react-native.

create-react-app my-app

Multi-platform: create-react-native-app includes experimental support for Web.

create-react-native-app my-app --with-web-support

Configuring a module bundler

If you have a custom setup, you may choose to configure your module bundler to alias the package to react-native.

For example, modify your webpack configuration as follows:

// webpack.config.js
module.exports = {
  // ...the rest of your config

  resolve: {
    alias: {
      'react-native$': 'react-native-web'
    }
  }
}

Now you can create your components and applications with the React Native API.

Configuring babel

If you need to do the aliasing with Babel you can use babel-plugin-module-resolver

{
  "plugins": [
    ["module-resolver", {
      "alias": {
        "^react-native$": "react-native-web"
      }
    }]
  ]
}

Client-side rendering

Render apps using AppRegistry:

// index.web.js

import App from './src/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')
});

Or render individual components:

import AppHeader from './src/AppHeader';
import React from 'react';
import { render } from 'react-native';

render(<AppHeader />, document.getElementById('react-app-header'))

(Components will also be rendered within a tree produced by calling ReactDOM.render (i.e., an existing web app), but otherwise it is not recommended.)

You might need to adjust the styles of the HTML document's root elements for your app to fill the viewport.

<html style="height:100%">
<body style="height:100%">
<div id="react-root" style="display:flex;height:100%"></div>

Server-side rendering

Server-side rendering to HTML is supported using AppRegistry:

import App from './src/App';
import ReactDOMServer from 'react-dom/server';
import { AppRegistry } from 'react-native-web';

// register the app
AppRegistry.registerComponent('App', () => App);

// prerender the app
const { element, getStyleElement } = AppRegistry.getApplication('App', { initialProps });
// first the element
const html = ReactDOMServer.renderToString(element);
// then the styles (optionally include a nonce if your CSP policy requires it)
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement({ nonce }));

// example HTML document string
const document = `
<!DOCTYPE html>
<html style="height:100%">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
${css}
<body style="height:100%; overflow-y:hidden">
<div id="root" style="display:flex; height: 100%">
${html}
</div>
<script nonce="${nonce}" src="${bundlePath}"></script>
`

Testing with Jest

Jest can be configured using the provided preset. This will map react-native to react-native-web and provide appropriate mocks:

{
  "preset": "react-native-web"
}

Please refer to the Jest documentation for more information.

Using Flow

Flow can be configured to understand the aliased module:

[options]
module.name_mapper='^react-native$' -> 'react-native-web'

You may also need to include a custom libdef (example) in your config.

Multi-platform applications

Web-specific code

Minor platform differences can use the Platform module.

import { Platform } from 'react-native';

const styles = StyleSheet.create({
  height: (Platform.OS === 'web') ? 200 : 100,
});

More significant platform differences should use platform-specific files (see the webpack configuration below for resolving *.web.js files):

For example, with the following files in your project:

MyComponent.android.js
MyComponent.ios.js
MyComponent.web.js

And the following import:

import MyComponent from './MyComponent';

React Native will automatically import the correct variant for each specific target platform.

Web packaging for existing React Native apps

What follows is merely an example of one basic way to package a web app using Webpack and Babel. (You can also the React Native bundler, Metro, to build web apps.)

Packaging web apps is subtly different to packaging React Native apps and is complicated by the need to tree-shake and code-split non-trivial apps.

Install webpack-related dependencies, for example:

yarn add --dev babel-loader url-loader webpack webpack-cli webpack-dev-server

React Native's Babel preset rewrites ES modules to CommonJS modules, preventing bundlers from automatically performing "tree-shaking" to remove unused modules from your web app build. To help with this, you can install the following Babel plugin:

yarn install --dev babel-plugin-react-native-web

Create a web/webpack.config.js file:

// 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,
      // The 'react-native' preset is recommended to match React Native's packager
      presets: ['react-native'],
      // Re-write paths to import only the modules needed by the app
      plugins: ['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]'
    }
  }
};

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' ]
  }
}

To run in development from the root of your application:

./node_modules/.bin/webpack-dev-server -d --config ./web/webpack.config.js --inline --hot --colors

To build for production:

./node_modules/.bin/webpack -p --config ./web/webpack.config.js

Please refer to the Webpack documentation for more information on configuration.

Other notes

Safari flexbox performance

Safari prior to version 10.1 can suffer from extremely poor flexbox performance. The recommended way to work around this issue (as used on mobile.twitter.com) is to set display:block on Views in your element hierarchy that you know don't need flexbox layout.

Platform-specific component props

There are properties that do not work across all platforms. All web-specific props are annotated with (web) in the documentation.