ECMAScript6対応 webpackを最低限実務で使う為の設定とRailsとwebpackの共存方法

# やること

[webpack](https://webpack.github.io)とそのPluginを使って、実務で使えるレベルにするには、情報が断片的だったのでまとめてみました。

[追記]
Railsとwebpackの共存方法についても記述しました。

## 以下の事を実現する

### ES6をES5に変換
   
- babel-core
- babel-loader
- babel-preset-es2015

### SCSSのコンパイル

- css-loader
- style-loader
- extract-text-webpack-plugin
- sass-loader
- node-sass

### JS内にHTMLファイルを読み込む

- html-loader

### Font Awesomeをnpmでインストール

- file-loader
- url-loader

### ブラウザのLive Reload

- browser-sync
- browser-sync-webpack-plugin

### .envでの設定値をセットし、process.envから取得できるようにする

- dotenv

### Productionビルド用にuglifyする

### Productionビルド用にファイル名にハッシュをつける

- assets-webpack-plugin

### JSのシンタックスチェック

- eslint
- eslint-loader

## npm package Install

```
npm install --save-dev babel-core babel-loader babel-preset-es2015 browser-sync browser-sync-webpack-plugin css-loader dotenv eslint eslint-loader extract-text-webpack-plugin file-loader node-sass sass-loader style-loader url-loader html-loader assets-webpack-plugin webpack
```

## Babelの設定

```:.babelrc
{
  "presets": ["es2015"]
}
```

## ESLintの設定

```:.eslintrc
{
    "env": {
        "browser": true,
        "es6": true,
        "jquery": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "sourceType": "module"
    },
    "globals": {
        "process": true
    },
    "rules": {
        "comma-dangle": [
            1,
            "only-multiline"
        ],
        "indent": [
            "error",
            2
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "never"
        ]
    }
}
```

## webpackの設定

```js:webpack.config.js
var path = require('path')
var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var BrowserSyncPlugin = require('browser-sync-webpack-plugin')
var AssetsPlugin = require('assets-webpack-plugin')

require('dotenv').config()

const isProduction = (process.env.NODE_ENV === 'production')

var assets = new AssetsPlugin({
  filename: 'webpack-asset-manifest.json',
  includeManifest: 'manifest',
  prettyPrint: true
})
var defineEnv = new webpack.DefinePlugin({
  'process.env': {
    'NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
    'HELLO_WORLD': JSON.stringify(process.env.HELLO_WORLD)
  }
})
var uglify = new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } })
var extractText = new ExtractTextPlugin('style-bundle.css')
var browserSync = new BrowserSyncPlugin({
  host: 'localhost',
  port: 3001,
  server: { baseDir: ['public'] },
  files: ['src/*.css', 'src/*.js', 'public/*.html']
}, { reload: true })
var jsPlugins = isProduction ? [defineEnv, uglify, assets] : [defineEnv, browserSync, assets]
var cssPlugins = isProduction ? [extractText, uglify, assets] : [extractText, browserSync, assets]

var build_path = {
  js: 'public/assets',
  css: 'public/assets'
}

var devTool = isProduction ? '' : 'source-map'

const config_for_js = {
  entry: {
    'app': './main.js'
  },

  output: {
    path: path.join(__dirname, build_path.js),
    filename: isProduction ? '[name]-bundle-[hash].js' : '[name]-bundle.js'
  },

  context: path.resolve(__dirname, 'src'),

  module: {
    preLoaders: [
      { test: /\.js$/, loader: "eslint-loader", exclude: /node_modules/ }
    ],
    loaders: [
      { test: /\.js$/, exclude: /node_modules/, loaders: ['babel'] },
      { test: /\.html$/, loader: 'html' }
    ]
  },

  resolve: {
    root: [ path.resolve(__dirname, 'src') ]
  },

  devtool: devTool,

  plugins: jsPlugins
}

const config_for_static = {
  entry: [
    './src/scss/style.scss',
    'font-awesome/scss/font-awesome.scss'
  ],

  output: {
    path: path.join(__dirname, build_path.css),
    filename: isProduction ? '[name]-bundle-[hash].css' : '[name]-bundle.css'
  },

  module: {
    loaders: [
      { test: /\.css$/, loaders: [ 'style', 'css' ] },
      { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css!sass') },
      { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
      { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream' },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml' }
    ]
  },

  plugins: cssPlugins,

  devtool: devTool
}

module.exports = [config_for_js, config_for_static]
```

## ソース

```js:main.js
import HelloWorld from 'templates/hello_world.html'
import _ from 'lodash'
import $ from 'jquery'

const template = _.template(HelloWorld)
$('body').append(template({ title: process.env.HELLO_WORLD }))
```

```:templates/hello_world.html
<h1><a href="/another_page.html"><%= title %></a></h1>
```

- 詳細なソースは[https://github.com/rneuo/webpack-browser-sync](https://github.com/rneuo/webpack-browser-sync)

↓↓↓↓↓↓↓↓↓↓ あと参考までに ↓↓↓↓↓↓↓↓↓↓

# webpack-dev-serverかbrowser-syncのどちらを選択するか

BrowerとのSyncには[webpack-dev-server](https://webpack.github.io/docs/webpack-dev-server.html)という選択肢もありますが、HTMLが1枚しか対応していないのでSPAを開発する場合にはよいが

SPAではなく、複数のHTMLで構成されてて、そのHTML間を遷移させて開発する場合には[browser-sync](https://www.browsersync.io/)を使うしかないかなと思います。

# webpack起動時に環境変数の内容でHTMLの中身を書き換えたい時

[string-replace-loader](https://github.com/Va1/string-replace-loader)が便利でした。

webpack.config.jsには以下の設定をして

```js:webpack.config.js
module.exports = {
  // ...
  module: {
    loaders: [
      { test: /index\.html$/, 
        loader: 'string-replace', 
        query: { 
          multiple:[
            { search: '$REPLACE_STRING', 
              replace: process.env.REPLACE_STRING 
            }
          ]
        } 
      }
    ]
  }
}
```

HTML内には設定で指定した文字列を埋め込んでおくと置換してくれる

```:index.html
<div class="$REPLACE_STRING">
```

# Railsとwebpackの共存方法

css, javascriptのコンパイルなどはwebpackで完結したい場合、[assets-webpack-plugin](https://github.com/kossnocorp/assets-webpack-plugin)を使うと簡単に実現できます。

```rb:config/initializers/webpack.rb
Rails.configuration.webpack = {
  asset_manifest: {},
  asset_host: 'http://localhost:3000' # public/assets以下はs3に置く事を想定
}

asset_manifest = Rails.root.join('webpack-asset-manifest.json')
if File.exist?(asset_manifest)
  Rails.configuration.webpack[:asset_manifest] = JSON.parse(File.read(asset_manifest),).with_indifferent_access
end
```

```rb:app/helpers/application_helpers.rb
module ApplicationHelper
  def webpack_stylesheet_link_tag(name)
    stylesheet_link_tag(webpack_assets('css', name))
  end

  def webpack_javascript_include_tag(name)
    javascript_include_tag(webpack_assets('js', name))
  end

  private
    def webpack_assets(type, name)
      @manifest ||= Rails.configuration.webpack[:asset_manifest]
      host = Rails.configuration.webpack[:asset_host]
      return "#{host}/assets/#{@manifest[name][type]}"
    end
end
```


## モダンなJavaScript開発を学べるおすすめの本

[![](https://images-fe.ssl-images-amazon.com/images/I/41mIjfhe6IL._AC_US160_.jpg)](http://amzn.to/2n3aNut)

[![](https://images-fe.ssl-images-amazon.com/images/I/51tDpXBLD7L._AC_US160_.jpg)](http://amzn.to/2n38jwa)

[![](https://images-fe.ssl-images-amazon.com/images/I/41ZzWbhlaNL._AC_US160_.jpg)](http://amzn.to/2nhJjTX)