webpack
を使っていたプロジェクトをFuseBox
に移行してみました。
バンドル時間が超速くなり、バンドルファイルサイズが超小さくなりました。
package.json
package.json
の比較です。
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.23.1",
"babel-eslint": "^8.0.0",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.22.0",
"babel-preset-flow": "^6.23.0",
"babel-preset-react": "^6.23.0",
"css-loader": "^0.28.7",
"eslint": "^4.6.1",
"eslint-loader": "^1.6.3",
"eslint-plugin-flowtype": "^2.34.1",
"eslint-plugin-react": "^7.3.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^0.11.2",
"flow-bin": "^0.54.1",
"node-sass": "^4.5.0",
"sass-loader": "^6.0.3",
"style-loader": "^0.18.2",
"url-loader": "^0.5.8",
"webpack": "^3.4.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.23.1",
"babel-eslint": "^8.0.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-flow": "^6.23.0",
"babel-preset-react": "^6.23.0",
"eslint": "^4.6.1",
"eslint-plugin-flowtype": "^2.34.1",
"eslint-plugin-react": "^7.3.0",
"flow-bin": "^0.54.1",
"fuse-box": "^2.2.31",
"node-sass": "^4.5.0",
"uglify-js": "^3.1.1"
},
FuseBoxは自パッケージ内のプラグインで大体のバンドルを済ませることができます。
webpackのようになんとかloader系をいちいちインストールしなくてもよいので、package.json
はかなりスッキリしました。
設定ファイル
もともとのwebpackの設定webpack.config.dev.js
と、FuseBoxの設定fuse.js
です。
だいたい同じことを実現できていると思いますが、ESLint関連だけはnpm scriptsからの実行に切り替えました。
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
module.exports = {
devtool: 'inline-source-map',
entry: {
bundle: './src/javascripts/index.js',
},
output: {
path: path.join(__dirname, '../../public/javascripts'),
filename: '[name].js',
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: ['eslint-loader'],
enforce: 'pre'
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: ['react', 'es2015', 'flow'],
}
},
{
test: /\.(css|scss)$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.(jpg|png)$/,
use: ['url-loader']
}
]
},
resolve: {
extensions: ['.js', '.jsx']
},
plugins: [
new ExtractTextPlugin('[name].css'),
new webpack.LoaderOptionsPlugin({
options: {
eslint: {
test: /\.jsx?$/,
configFile: path.join(__dirname, '../../.eslintrc'),
cache: false
}
}
})
],
};
const {
FuseBox,
CSSPlugin,
SassPlugin,
BabelPlugin,
QuantumPlugin,
WebIndexPlugin,
ImageBase64Plugin,
JSONPlugin,
Sparky
} = require('fuse-box')
let fuse, app, vendor, isProduction
Sparky.task('config', () => {
fuse = new FuseBox({
homeDir: 'src/',
sourceMaps: !isProduction,
hash: isProduction,
output: 'public/$name.js',
target: 'browser',
experimentalFeatures: true,
plugins: [
BabelPlugin({
sourceMaps: !isProduction,
presets: ['es2015', 'react', 'flow'],
}),
CSSPlugin(),
[SassPlugin(), CSSPlugin()],
ImageBase64Plugin(),
JSONPlugin(),
WebIndexPlugin({
template: 'src/index.html'
}),
isProduction && QuantumPlugin({
bakeApiIntoBundle: 'bundle',
treeshake: true,
uglify: true,
}),
],
})
app = fuse.bundle('bundle').instructions('> javascripts/index.js')
})
Sparky.task('clean', () => Sparky.src('public/').clean('public/'))
Sparky.task('copy', () => Sparky.src('index.html', {base: 'public/'}).dest('./'))
Sparky.task('prod-env', () => { isProduction = true })
Sparky.task('build', ['prod-env', 'clean', 'config'], () => fuse.run())
Sparky.task('release', ['build', 'copy'], () => {})
Sparky.task('default', ['clean', 'config'], () => {
fuse.dev({
port: 8000,
})
app.watch().hmr()
return fuse.run()
})
fuse.js
は、fuse-box/react-exampleを参考にして作りました。
FuseBox付属のタスクランナーSparkyを使用しています。
詳しい使い方は参考サイトに譲ります。
リリース用にビルドするときのみprod-env
タスクを噛ませてisProduction
フラグを立てることにより、sourceMaps
を消したりuglifyしたりできて、かなり気持ちいいです。
知見
fuse.js
を書くときにいろいろと迷ったので、個人的なポイントやハマったところを挙げていきます。
デフォルトのトランスパイラがTypeScript
README.mdに
FuseBox loves typescript, and does not require any additional configuration.
とある通り、特に何も指定しなければソースコードをTypeScriptと解釈してトランスパイルします。
既存のプロジェクトでBabelを使用している場合、plugins
にBabelPlugin
を指定する必要があります。
.babelrc
に記載していたようなpresetsの設定等はこの中に書きます。
QuantumPlugin
バンドルしたソースコードをminifyしたりtreeshakeしたりuglifyしたりできます。
このプラグインを追加すると、バンドルファイルと別にapi.js
というファイルが吐かれるようになります。
それも含めて一つのバンドルファイルにしたい場合、bakeApiIntoBundle
オプションにバンドル名を指定します。
plugin chaining
[SassPlugin(), CSSPlugin()]
こんな感じでプラグインを配列で指定すると、まずSassPlugin
を通してからCSSPlugin
に渡す、といったことができます。
plugin chaining という機能名らしいです。
homeDir
フォルダ配下のソースしか読んでくれない
例えば以下のようなフォルダ構成でindex.js
がエントリポイントであるとき、
Sparky.task('config', () => {
fuse = new FuseBox({
homeDir: 'src/javascripts/',
...
app = fuse.bundle('bundle').instructions('> index.js')
})
と設定すると、homeDir
に指定したsrc/javascripts
配下のhoge.js
やhage.js
は読み込めますが、その他のsrc/stylesheets
やsrc/assets
配下にあるファイルは読み込めません。(バンドルは正常に終了するが、ブラウザで実行時エラーが発生する)
この場合、
Sparky.task('config', () => {
fuse = new FuseBox({
homeDir: 'src/',
...
app = fuse.bundle('bundle').instructions('> javascripts/index.js')
})
このように、homeDir
に指定するのはsrc
までとすることで、ブラウザで正常に読み込めるようになります。
私はこの問題で三時間くらい悩みました……。