webpack 4 + Babel 7 の環境で React + MobX アプリを動かす

今年は2月にwebpack 4がリリースされ、4月にNode ver.10がリリース、Babel 7もリリースを間近に控えるなど、フロントエンド周りのバージョンアップが相次いでいます。ここでは webpack 4 + Babel 7 の開発環境で React + Mobx を動かすための設定をまとめておきます。Mac + node v10.3.0で動作確認をしています。

パッケージのインストール

まずは適当なフォルダに移動してpackage.jsonを作成します:

yarn init

ついでReact関連をインストールします:

yarn add react react-dom mobx mobx-react

開発環境のインストールはこんな感じです:

yarn add @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env @babel/preset-react babel-loader@^8.0.0-beta css-loader sass-loader style-loader webpack webpack-cli@2.1.5 webpack-dev-server -D

いくつか注意点があります。



まず @babel/... ですが、これはBabel 7からインストールするパッケージ名が変更されました。angularやionicもそうですが、Babelもパッケージ名の先頭に@がついてスコープ化されるようになりました。



@babel/plugin-proposal-class-properties@babel/plugin-proposal-decorators はMobXでデコレーターを使用するために必要になります。この二つに関しては次のwebpackの設定で、プラグインに指定するオプションについて説明します。



また webpack-cli のバージョンが2.1.5で指定されていますが、これは記事公開当日に公開された webpack-cli@3.0.0webpack-dev-server@3.1.4 との相性が悪いようで、2系の最新版をインストールするようにしています。



インストールが終わった package.json はこんな感じになります:

{
  "name": "CounterApp",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "webpack-dev-server",
    "build": "yarn run webpack"
  },
  "dependencies": {
    "mobx": "^4.3.0",
    "mobx-react": "^5.1.2",
    "react": "^16.4.0",
    "react-dom": "^16.4.0"
  },
  "devDependencies": {
    "@babel/core": "^7.0.0-beta.49",
    "@babel/plugin-proposal-class-properties": "^7.0.0-beta.49",
    "@babel/plugin-proposal-decorators": "^7.0.0-beta.49",
    "@babel/preset-env": "^7.0.0-beta.49",
    "@babel/preset-react": "^7.0.0-beta.49",
    "babel-loader": "^8.0.0-beta",
    "css-loader": "^0.28.11",
    "sass-loader": "^7.0.2",
    "style-loader": "^0.21.0",
    "webpack": "^4.10.2",
    "webpack-cli": "2.1.5",
    "webpack-dev-server": "^3.1.4",
    "webpack-merge": "^4.1.2"
  }
}

webpackの設定

パッケージがインストールできたらwebpackの設定を行います。ここではBabelの設定を .babelrc ではなく webpack.config.jsで行うため、jsxmodule.rulesquery.babelrcfalse にセットしています。

// webpack.confing.js

const path = require('path')

module.exports = {
  mode: 'development',
  entry: './app.jsx',
  output: {
    path: path.resolve(__dirname, './'),
    filename: 'app.min.js'
  },
  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          babelrc: false,
          presets: [
            '@babel/preset-env',
            '@babel/preset-react'
          ],
          plugins: [
            ['@babel/plugin-proposal-decorators', {
              legacy: true
            }],
            ['@babel/plugin-proposal-class-properties', {
              loose: true
            }]
          ]
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader?modules']
      },
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx']
  },
  devServer: {
    contentBase: path.join(__dirname, './'),
    compress: true,
    port: 9000
  }
}

module.rules の中の query.plugins ではまず @babel/plugin-proposal-decorators を指定しています。これがないと、MobXで @observable などのデコレーターを使用すると、コンパイル時に

ERROR in ...
Module build failed: SyntaxError: ...: Support for the experimental syntax 'decorators-legacy' isn't currently enabled

というシンタックスエラーがターミナルに表示されます。



さらに @babel/plugin-proposal-decorators に対して {legacy: true} というオプションを渡してやらないと、コンパイル時に

ERROR in ...
Module build failed: Error: [BABEL] ...: The new decorators proposal is not supported yet. You must pass the `"legacy": true` option to @babel/plugin-proposal-decorators

というコンパイルエラーが表示されます。



@babel/plugin-proposal-class-properties では {loose: true} というオプションを指定しています。これがないと、

Uncaught Error: Decorating class property failed. Please ensure that proposal-class-properties is enabled and set to use loose mode. To use proposal-class-properties in spec mode with decorators, wait for the next major version of decorators in stage 2.

というランタイムエラーがブラウザのコンソールに出力されます。



この二つのプラグインの順番も大切です。もし @babel/plugin-proposal-decorators を一番最初に記述せずに

['@babel/plugin-proposal-class-properties', {
  loose: true
}],
['@babel/plugin-proposal-decorators', {
  legacy: true
}]

のようにすると、

ERROR in ...
Module build failed: SyntaxError: .....: Decorators transform is necessary.

というシンタックスエラーがターミナルに表示されます。

単純なカウンターアプリを動かす

環境設定が終わったので、単純なカウンターアプリを動かします。用意するファイルは index.html, style.scss, app.jsx です:

<!DOCTYPE html>
<html lang="ja">
<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>Document</title>
</head>
<body>
<div id="root"></div>
<script src="app.min.js"></script>
</body>
</html>
body {
  margin: 0;
  line-height: 1;
}
span {
  display: inline-block;
  width: 3em;
  font-size: 30px;
  text-align: center;
}

button {
  margin: 1em;
  padding: .5em 1em;
  border: 0;
  font-size: 20px;
  background-color: #fafafa;
}
import React from 'react'
import { render } from 'react-dom'
import { observable } from 'mobx'
import { observer } from 'mobx-react'
import './style.scss'

class CounterStore {
  @observable counter = 0

  increment() { this.counter++ }

  decrement() { this.counter-- }
}

@observer
class App extends React.Component {
  render() {
    return (
      <div className="wrapper">
        <button onClick={() => this.props.data.increment()}>+1</button>
        <span>{this.props.data.counter}</span>
        <button onClick={() => this.props.data.decrement()}>-1</button>
      </div>
    )
  }
}

const counterStore = new CounterStore()
render(<App data={counterStore} />, document.getElementById('root'))

スタイルは sass-loader が動くのを確かめるために適当に書いてあります。



最後に

webpack-dev-server

で開発サーバーhttp://localhost:9000を立ち上げて、こんな感じのカウンターが表示されて動作すればOKです。(下のカウンターは画像ではなくamp-iframeで読み込んでいるため、ボタンを押すとカウンターが動作します。)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です