1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Reactの環境構築を段階的にやってみる

Last updated at Posted at 2020-11-09

はじめに

勉強目的なのでCreate React App相当の設定を実施するわけではないです!
参考にされる際はこの点ご留意下さい。

モチベーション

Create React AppはサクッとReactアプリの雛形が作成できて便利ですよね。
Reactの環境構築が複雑だからこそ、このようなツールが提供されているのだと思います。
ただ、処理過程を理解しないのは些か不安でもあります。
段階的に実施すればそこまで難しくないのでは?と思い立ち、勉強を兼ねて0からの構築を試してみました。

STEP 0 : Reactをインストールする

プロジェクトの初期化が済んでいない場合は、まずnpm initコマンドで初期化しておいて下さい。

npm i react react-dom

STEP 1 : Webpackでのバンドルを試す

まずはWebpackでのバンドルを試してみます。

npm i -D webpack webpack-cli

JSファイルのバンドルであればローダーは必要ありません。
そして、React自体はJSで記述できるので、この段階でReactアプリがバンドルできます。
エントリーポイントのindex.jsとReactコンポーネントのApp.jsをsrc配下に作成していきます。

src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

const rootEl = document.getElementById('root')
const rootComponent = React.createElement(App)

ReactDOM.render(rootComponent, rootEl)

src/App.js
import React from 'react'

const App = () => React.createElement('h1', null, 'React App')

export default App

package.jsonにビルド用のスクリプトを書きます

package.json
"scripts": {
  "build": "webpack --mode=production"
}

npm run buildを実行すると、distディレクトリ配下にmain.jsが作成されます。
distディレクトリにindex.htmlを作成してmain.jsを読み込んでみます。

dist/index.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React App</title>
</head>

<body>
  <div id="root"></div>
  <script src="main.js"></script>
</body>

</html>

そしてindex.htmlを直接ブラウザで開き、React Appと表示されていればOKです。

エントリーポイントがsrc/index.js出力先がdist/main.jsというのはwebpackのデフォルト設定です。なので、webpackの設定ファイルを作成せずともバンドルができました。
とはいえ、流石にJSXなしは辛いのでJSXで記述できるようにします。

STEP 2 : JSXでの記述を試す

JSXは標準のJSではないので、Babelを通して変換します。
まずは必要なパッケージをインストール

npm i -D @babel/core @babel/preset-env @babel/preset-react babel-loader

Babelの設定ファイルを作成し、使用するプリセットを明記します。
後ろに記載したプリセットから処理されるので、配列の順番に意味がある点に注意します。

babel.config.js
module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-react']
}

Webpackの設定ファイルを作成し、Babelを実行するタイミング(babel-loader)を明記します。
また、jsx拡張子はデフォルトで省略できないので、省略できるようにこちらも明記します。

webpack.config.js
const path = require('path')

// 出力先は絶対パスで記載する
const outputPath = path.resolve(__dirname, 'dist')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: outputPath
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: ['babel-loader']
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx']
  }
}

App.jsだけJSXファイルにしてみます。拡張子をjsxにしてから編集します。

src/App.jsx
import React from 'react'

const App = () => <h1>React App JSX</h1>

export default App

npm run buildを実行して、React App JSXと表示されればOKです。

JSXファイルで記述でき、かなりReactっぽくなりました。ただindex.htmlを直接ブラウザで参照してしまっているので、サーバー経由で参照するようにします。

STEP 3 : 開発サーバーを試す

webpack-dev-serverをインストールして開発サーバーを導入します。

npm i -D webpack-dev-server

Webpackの設定ファイルに、開発サーバーの設定を追記します。

webpack.config.js
const path = require('path')

// 出力先は絶対パスで記載する
const outputPath = path.resolve(__dirname, 'dist')

module.exports = {
  entry: './src/index.js',
  output: { /* 省略 */ },
  module: { /* 省略 */ },
  resolve: { /* 省略 */ },
  devServer: {
    contentBase: outputPath
  }
}

contentBaseにdistディレクトリを指定しているので、distディレクトリをルートにサーバーが起動します。

package.jsonにwebpack-dev-serverの実行コマンドを追記します。
(最新版だとwebpack-dev-serverでなくwebpack serveで起動するようです)

package.json
"scripts": {
  "build": "webpack --mode=production",
  "dev": "webpack serve --mode=development"
}

npm run devでサーバーを起動し http://localhost:8080 にアクセスしてSTEP2と同様の結果になればOKです。
ちなみに、webpack-dev-serverはファイルの変更を検知してブラウザを自動リロードしてくれます。便利ですね。

ところで、dist配下に直接index.htmlを作成してしまったのですが、基本的にdist配下はバージョン管理しないはずです。
HTMLファイルの生成も、Webpack経由で処理するようにします。

STEP 4 : HTML Webpack Pluginを試す

HTML用のプラグインをインストールします。

npm i -D html-webpack-plugin@next

src配下にindex.htmlを作成します。
基本的にはSTEP1と同じですが、scriptタグはプラグインで自動挿入されるので削除しています。

src/index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React App</title>
</head>

<body>
  <div id="root"></div>
</body>

</html>

Webpackの設定ファイルを修正します。
HTMLファイルのテンプレートを指定しつつ、devServerのcontentBaseもdistディレクトリに依存しなくなったので、コメントアウトしています。

webpack.config.js

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

const outputPath = path.resolve(__dirname, 'dist')

module.exports = {
  entry: './src/index.js',
  output: { /* 省略 */ },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  module: { /* 省略 */ },
  resolve: { /* 省略 */ },
  devServer: {
    // contentBase: outputPath
  }
}

distディレクトリを削除し、開発サーバーを再起動します。distディレクトリは存在しませんが、STEP2と同様の内容が表示されるはずです。
npm run buildを実行すると、distディレクトリが作成され、STEP2と同様のファイルが生成されます。

HTMLファイルが処理できたので、次はCSSファイルもWebpack経由で処理するようにします。

STEP 5 : CSSローダーを試す

基本的には「JSでCSSをimportし、バンドル後のファイルに含める」という考え方です。
ただ、JSに含めたCSSをどのようにしてHTMLに反映させるかについては、選択肢があります。
そのため
 ①JSでCSSをimportするためのローダー
 ②JS内のCSSをHTMLに反映させる方法
それぞれ別のパッケージを使用する必要があります。
今回は、①css-loaderstyle-loaderを使用します。
style-loaderは取り込んだCSSをheadタグ内のstyleタグとして展開してくれます。

npm i -D css-loader style-loader

Webpackの設定ファイルを修正します。

webpack.config.js

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

const outputPath = path.resolve(__dirname, 'dist')

module.exports = {
  entry: './src/index.js',
  output: { /* 省略 */ },
  plugins: [/* 省略 */],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: ['babel-loader']
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  resolve: { /* 省略 */ },
  devServer: { /* 省略 */ }
}

useでは最後に定義したものから順番に処理されるため、必ずcss-loaderを後に記載する必要があります。
では、簡単なCSSファイルを作成し、index.jsで読み込んでみます。

styles.css
.tomato-title {
  color: tomato;
}
src/App.jsx
import React from 'react'

const App = () => <h1 className="tomato-title">React App JSX</h1>

export default App

src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

import './styles.css'

const rootEl = document.getElementById('root')
const rootComponent = React.createElement(App)

ReactDOM.render(rootComponent, rootEl)

タイトルがトマト色に変わっていたらOKです。

次は静的ファイルもWebpack経由で処理するようにします。

STEP 6 : Asset Modulesを試す

静的ファイルの読み込みはurl-loaderfile-loaderが使用できますが、Webpack5系から標準で静的ファイル用のtypeプロパティが追加されたので、そちらを使用してみます。

webpack.config.js

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

const outputPath = path.resolve(__dirname, 'dist')

module.exports = {
  entry: './src/index.js',
  output: { /* 省略 */ },
  plugins: [/* 省略 */],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: ['babel-loader']
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|svg|jpg)$/, // 他に必要な拡張子があれば追加
        type: 'asset'
      }
    ]
  },
  resolve: { /* 省略 */ },
  devServer: { /* 省略 */ }
}

標準だと、8kb以下のファイルはバンドルファイルに同梱(inline)、それ以上は別ファイルとしてdist配下に配置(resource)されるようです。

適当な画像をバンドルしてみます。

src/App.jsx
import React from 'react'

import image from './image.png'

const App = () => (
  <>
    <h1 className="tomato-title">React App</h1>
    <img src={image} width="150" height="150" />
  </>
)

export default App

指定した画像が表示されればOKです。

以上で、Webpack経由でのバンドルは一通り設定できました。
最後に、このアプリをテストしてみたいと思います。

STEP 7 : テストを試す

テストランナーは多くの種類がありますが、React公式が推奨しているJestを導入します。(Reactと同じFacebook製なので相性が良いです)

npm i -D jest

Jestはnode環境で動作する関係上、ESModules(import/export)が標準では使用できません。
しかし、Babelの設定ファイルを検出すると自動でBabelを通して実行してくれます。
STEP2でBabelの設定ファイルは作成しているので、簡単なテストを作って実行してみます。

src/App.test.jsx
import React from 'react'
import { render } from 'react-dom'
import { act } from 'react-dom/test-utils'

import App from './App'

test('render content', () => {
  const container = document.createElement('div')
  document.body.appendChild(container)
  act(() => { render(<App />, container) })
  expect(container.textContent).toBe('React App')
})

App.jsx内で画像をimportしていますが、これはWebpackによって解決されるものです。Jestは画像importの処理方法を知らないので、このままだとエラーになります。
画像(ついでにCSSも)をimportするときはモックを使用するように、Jestの設定ファイルで指定します。

jest.config.js
module.exports = {
  moduleNameMapper: {
    "\\.(png|svg|jpg)$": "<rootDir>/__mocks__/fileMock.js",
    "\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
  }
}

モックを作成します。

__mocks__/fileMock.js
module.exports = 'test-file-stub'
__mocks__/styleMock.js
module.exports = {}

package.jsonにスクリプトを追加し、npm testで実行します。

package.json
"scripts": {
  "build": "webpack --mode=production",
  "dev": "webpack serve --mode=development",
  "test": "jest"
}

テスト成功で終了すればOKです。

おわりに

以上で、Reactの基本的な環境構築ができました。
各ステップでの細かい設定に関しては調査不足なところがあるので、今後の課題にしていきたいですが、全体の流れが俯瞰できて良い勉強になりました。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?