Uncaught ReferenceError: require is not defined 対処法まとめ

f:id:uraway:20160124171042j:plain

前にも記事を書きましたが、少し勉強した中で他にも解決法を見つけたのでまとめておきます。

さて、クライアントサイドのJavaScriptではrequireを使ってNode.jsのモジュールを読み込むことはできません。したがって以下の様なツールを組み込む必要があります。Reactライブラリを例に使用していきましょう。

  1. CDN
  2. Browserify
  3. Webpack
  4. Rollup

CDN

主要なライブラリはCDNから<script>タグを使って読み込むことができます。また、cdnjsjsdelivrも同じように利用可能です。cdnjsを使用すると次のようになります。

<!-- Reactのコアライブラリ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>
<!-- ReactDOMライブラリ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.js"></script>

ただ、<script>タグの挿入先には注意が必要です。

<!-- index.html1 -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/babel">

        class HelloMessage extends React.Component {
          render() {
              return <h1>Hello {this.props.name}</h1>;
          }
        }

        ReactDOM.render(
          <HelloMessage name="React!" />,
          document.getElementById('content')
        );

    </script>
  </body>
</html>

上記HTMLのように<head>タグ内で読み込むと、完了するまで以降のHTMLのレンダリングを停止します。

<!-- index.html2 -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/babel">

        class HelloMessage extends React.Component {
          render() {
              return <h1>Hello {this.props.name}</h1>;
          }
        }

        ReactDOM.render(
          <HelloMessage name="React!" />,
          document.getElementById('content')
        );

    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
  </body>
</html>

上記HTMLでは、<body>タグ内のレンダリングが終わってからライブラリを読み込むためレンダリングの停止が発生しません。

今回は<body>タグ内にReactライブラリを使用する必要があり、予期しないエラーを防ぐために<head>タグ内で読み込むことにします。

Browserify

上記CDNはajax通信を通してReactライブラリの読み込み、Babelのコンパイルを実現しているため、シンプルではありますが速度的に問題があります。Browserifyを使えば、オフラインでライブラリを使用し、コンパイルすることができます。

まずはライブラリとbrowserifyのプラグインであるbabelifyとプリセットをインストールします。

$ npm install --save react react-dom browserify babelify babel-preset-react babel-preset-es2015

main.jsを用意します。

// main.js
import React from 'react';
import ReactDOM from 'react-dom';

class HelloMessage extends React.Component {
  render() {
    return <h1>Hello {this.props.name}</h1>;
  }
}

ReactDOM.render(
  <HelloMessage name="React!" />,
    document.getElementById('content')
);

次のコマンドでmain.jsファイルに使われているモジュールをバンドルし、bundle.jsファイルに書き出します。

$ browserify -t [ babelify --presets [ react es2015 ] ] main.js -o bundle.js

このbundle.jsファイルをHTMLから読みこませればOKです。

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
  </head>
  <body>
    <div id="content"></div>
    <script src="bundle.js"></script>
  </body>
</html>

ただし、このままでは、main.jsファイルに変更があるたびにコマンドを叩く必要があるので、gulpなどのビルドツールに組み込みましょう。

uraway.hatenablog.com

Webpack

コンフィグファイルに設定を指定することでバンドルのみならず、トランスフォーム、パッケージングも可能にしてくれるバンドラーです。個人的にはBrowserifyより好きです。

webpackと、トランスフォームに必要なローダーとプリセットをインストールします。

$ npm install webpack babel-loader babel-core babel-preset-es2015 babel-preset-react --save-dev

webpack.config.jsファイルを用意して、以下のように読み込み先、出力先、ローダーを設定します。

// webpack.config.js
var path = require('path');
var webpack = require('webpack');

module.exports = {
  entry: 'scripts/main.js',
  output: { path: __dirname, filename: 'bundle.js' },
  module: {
    loaders: [
      {
        test: /.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react'],
        },
      },
    ],
  },
};

コマンドを叩くと、コンフィグファイルで指定したバンドルが実行されます。

$ webpack

Webpack + React + ES6の最小構成を考えてみる。

Rollup

Rollupは次世代のバンドラーです。上記Browserify, Webpackと異なる点は、使用していないコードをバンドルから削除する点にあります。従来のバンドラーではライブラリの全体を読み込むため、ライブラリ内の必要でないコードをもバンドルしています。結果、Rollupとその他のバンドラーが出力するファイルには大きな容量差が生まれます。(参考: nolanlawson/rollup-comparison)

この機能はWebpack2でも実装されるようです。

Rollupは、デフォルトではES6モジュールのみをバンドルしますが、プラグインを使うことで、CommonJSのモジュールも処理してくれます。

 React + Rollup

早速試してみましょう。まずは必要なものをインストールします。

rollupのコアとプラグインであるrollup-plugin-npm, rollup-plugin-commonjs, rollup-plugin-babel, rollup-plugin-replaceを読み込みます。

$ npm install --save-dev rollup rollup-plugin-npm rollup-plugin-commonjs rollup-plugin-babel rollup-plugin-replace

バベル関係(babel-preset-es2015-rollup, babel-preset-react)も読み込んでおきます。

$ npm install --save-dev babel-preset-es2015-rollup babel-preset-react

最終的にはpackage.jsonはこうなります。

{
  "dependencies": {
    "react": "^0.14.3",
    "react-dom": "^0.14.6"
  },
  "devDependencies": {
    "babel-preset-es2015-rollup": "^1.1.1",
    "babel-preset-react": "^6.3.13",
    "rollup": "^0.21.2",
    "rollup-plugin-babel": "^2.3.9",
    "rollup-plugin-commonjs": "^1.4.0",
    "rollup-plugin-npm": "^1.1.0",
    "rollup-plugin-replace": "^1.1.0"
  },
  "scripts": {
    "build": "rollup -c && open index.html"
  }
}

ルートディレクトリにrollup.config.jsを作成します。

// rollup.config.js
import npm from 'rollup-plugin-npm';
import commonjs from 'rollup-plugin-commonjs';
import replace from 'rollup-plugin-replace';
import babel from 'rollup-plugin-babel';

export default {
  // エントリ
    entry: 'scripts/index.js',
  // 出力先
    dest: 'bundle.js',
   // プラグイン
    plugins: [
    // 文字列の置換
        replace({
            'process.env.NODE_ENV': '"production"'
        }),
    // npmモジュールのmainを参照
        npm({
            main: true
        }),
    // CommonJSモジュールをES6に変換
        commonjs({
            include: 'node_modules/**'
        }),
    // ES5に変換
        babel({
            exclude: 'node_modules/**'
        }),
    ],
    format: 'iife'
};

.babelrcにバベルの設定を入れます。

{
  "presets": ["es2015-rollup" , "react"]
}

npm run buildコマンドでrollup.config.jsにしたがってコンパイルが実行され、htmlファイルが開きます。

サンプル

ドキュメントが結構書きかけなので、これからのツールですね。

(参考: http://qiita.com/cognitom/items/e3ac0da00241f427dad6)

個人的にはWebpackですら持て余してる感があるので、こっちはもう少しWebpackを勉強してからかな。