サイト名変更・お引越しのお知らせ

React NativeアプリをReact Native for Web化する方法について解説

React Nativeで作成したアプリをReact Native for Web化する方法を解説します。

React Native for Webについてはこちらで解説していますので、ご参照ください。

React Native for Webとは何かについて解説

この記事でわかること
  • React NativeアプリをReact Native for Web化する方法がわかる
  • react-native-webの基本の使い方がわかる

バージョン
  • React 17.0.2
  • React Native 0.65.1
  • react-native-web 0.17.5

React Nativeアプリを作成する

まずは、React Nativeでアプリを作成しましょう。

モグモグさん

すでに作成済みの方はスキップでOKです!

ReactNative 【ReactNative入門 】Macで開発環境を作って始める

react-native-web関連パッケージを追加

続いて、react-native-webとwebpack等のパッケージを追加しましょう。

$ yarn add react-native-web

// npmの方
$ npm install react-native-web
$ yarn add -D babel-plugin-react-native-web webpack webpack-cli webpack-dev-server html-webpack-plugin react-dom babel-loader url-loader @svgr/webpack

// npmの方
$ npm install --save-dev babel-plugin-react-native-web webpack webpack-cli webpack-dev-server html-webpack-plugin react-dom babel-loader url-loader @svgr/webpack

詳細なドキュメントはこちらを参照ください。

index.htmlファイルを追加

ルートファイルとなるindex.htmlを追加します。

ディレクトリは任意ですが、/web/index.htmlとして作成しています。

<!DOCTYPE html>
<html>
  <head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>RN Web example</title>
    <style>
      #app-root {
          display: flex;
          flex: 1 1 100%;
          height: 100vh;
      }
    </style>
  </head>
  <body>
    <div id="app-root"></div>
  </body>
</html>

index.web.jsを作成

すでに、index.jsがあると思いますが、React Native for Web用にindex.web.jsを作成します。

プロジェクトルートに配置しましょう。

import React from 'react';
import {AppRegistry} from 'react-native';
import App from './App';

AppRegistry.registerComponent('App', () => App);

AppRegistry.runApplication('App', {
  rootTag: document.getElementById('app-root'),
});

webpack.config.jsを作成

続いてwebpack.config.jsを追加します。こちらもプロジェクトルートに配置です。

const path = require('path');

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const appDirectory = path.resolve(__dirname);
const {presets} = require(`${appDirectory}/babel.config.js`);

const compileNodeModules = [
  // Add every react-native package that needs compiling
  // e.g. 'react-native-gesture-handler',
].map(moduleName => path.resolve(appDirectory, `node_modules/${moduleName}`));

const babelLoaderConfiguration = {
  test: /\.js$|tsx?$/,
  // Add every directory that needs to be compiled by Babel during the build.
  include: [
    path.resolve(appDirectory, 'index.web.js'),
    path.resolve(appDirectory, 'App.js'), // TypeScriptを使用している場合はApp.tsx
    path.resolve(appDirectory, 'src'),
    ...compileNodeModules,
  ],
  use: {
    loader: 'babel-loader',
    options: {
      cacheDirectory: true,
      presets,
      plugins: ['react-native-web'],
    },
  },
};

const svgLoaderConfiguration = {
  test: /\.svg$/,
  use: [
    {
      loader: '@svgr/webpack',
    },
  ],
};

const imageLoaderConfiguration = {
  test: /\.(gif|jpe?g|png)$/,
  use: {
    loader: 'url-loader',
    options: {
      name: '[name].[ext]',
    },
  },
};

module.exports = {
  entry: {
    app: path.join(appDirectory, 'index.web.js'),
  },
  output: {
    path: path.resolve(appDirectory, 'dist'),
    filename: 'bundle.web.js',
  },
  resolve: {
    extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.js', '.js'],
    alias: {
      'react-native$': 'react-native-web',
    },
  },
  module: {
    rules: [
      babelLoaderConfiguration,
      imageLoaderConfiguration,
      svgLoaderConfiguration,
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(appDirectory, './web/index.html'),
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({
      // See: https://github.com/necolas/react-native-web/issues/349
      __DEV__: JSON.stringify(true),
    }),
  ],
};

ポイント

大きなポイントは、aliasの箇所です。

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

これで、import {} from react-nativeの箇所をreact-native-webに変更しています。

補足

imageLoaderConfigurationsvgLoaderConfigurationは必須ではないです。

compileNodeModulesはこの例では空ですが、必要に応じて利用しているパッケージを追加してください。

App.js (App.tsx)を修正

例を簡単にするために余計なApp.js(もしくはApp.tsx)をシンプルにします。

モグモグさん

興味がある方や既存のアプリに組み込んでいる方は、いろいろなUIがReact Native for Webでどうなるか試してみてください!

import React from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  Text,
  useColorScheme,
  View,
} from 'react-native';

const App = () => {
  const isDarkMode = useColorScheme() === 'dark';

  return (
    <SafeAreaView>
      <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
      <ScrollView contentInsetAdjustmentBehavior="automatic">
        <View>
          <Text>Hello react native web</Text>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

export default App;

package.jsonにScriptを追加

react-native-webをビルドするためにスクリプトを追加します。

{
  ...
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
    "web-build": "rm -rf dist/ && webpack --mode=production --config webpack.config.js",
    "web": "webpack serve --mode=development --config webpack.config.js"
  },
  ...
}

追加されたスクリプト

$ yarn web / npm web

ローカルサーバーが立ち上がります。

$ yarn web-build / npm web-build

本番用にビルドされ、distディレクトリにファイルが配置されます。

ビルド

最後に動作確認をしましょう。

$ yarn web / npm web

localhost:8080を開くと画面が表示されます。

まとめ

モグモグさん

お疲れ様でした!

React Nativeを開発しつつ、Webにも適用できる可能性があるのでユースケースによっては非常に便利だと思います。

画像や他のComponentsなどのUIを試してみてください。

ReactアプリをReact Native for Web化する方法についてはこちらで解説しています。

Create React AppしたReactアプリをReact Native for Web化する方法を解説

2 COMMENTS

匿名

おそらく、webpack.config.jsのbabelLoaderConfigurationのところに、1行追加した方が良いように思いますが、いかがでしょうか?

const babelLoaderConfiguration = {
test: /\.js$|tsx?$/,
// Add every directory that needs to be compiled by Babel during the build.
include: [
path.resolve(appDirectory, ‘index.web.js’),
path.resolve(appDirectory, ‘App.tsx’),
path.resolve(appDirectory, ‘App.js’), // この行追加
path.resolve(appDirectory, ‘src’),
…compileNodeModules,
],

mogu-san

ご指摘ありがとうございます。

TypeScriptを使用する形と混合しておりましたので、該当箇所を修正して更新いたしました。
ありがとうございます!

現在コメントは受け付けておりません。