やること
webpackとその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の設定
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]
ソース
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>
↓↓↓↓↓↓↓↓↓↓ あと参考までに ↓↓↓↓↓↓↓↓↓↓
webpack-dev-serverかbrowser-syncのどちらを選択するか
BrowerとのSyncにはwebpack-dev-serverという選択肢もありますが、HTMLが1枚しか対応していないのでSPAを開発する場合にはよいが
SPAではなく、複数のHTMLで構成されてて、そのHTML間を遷移させて開発する場合にはbrowser-syncを使うしかないかなと思います。
webpack起動時に環境変数の内容でHTMLの中身を書き換えたい時
string-replace-loaderが便利でした。
webpack.config.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を使うと簡単に実現できます。
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
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