examples-web-app 更新 2016/5
akabekobeko/examples-web-app にある front-end-starter と front-end-starter-with-gulp を更新した。ここ最近の開発で得られた知見や方針を反映している。それらについては Twitter でもつぶやいていたのだけど、せっかくなので記事にまとめておく。
静的リソース用フォルダの変更
従来のフォルダ構成は
.
├── package.json
├── src/
│ ├── index.html
│ ├── fonts/
│ ├── js/
│ └── stylus/
└── test/
となっていた。src/
直下と Web サイトのルートを一致させていたのだが JavaScript と Stylus の
開発用リソースと index.html
や fonts
のような静的リソースを区別しにくかった。そこで構成を以下のように変更。
.
├── package.json
├── src/
│ ├── assets/
│ │ ├── fonts/
│ │ └── index.html
│ ├── js/
│ └── stylus/
└── test/
静的リソースは assets
に置かれる。JavaScript や Stylus のコンパイル結果は assets
に出力。今後、例えば画像を静的リソースとして追加する場合は src
直下ではなく assets
配下に置く。静的なものと開発用フォルダを分けたことで見通しがよくなった。
browser-sync
Stylus の Source Maps 参照は元ファイルの相対パス指定が必要。そのため browser-sync の対象フォルダ内にそれらが含まれていなければならない。しかし assets
をルートすると stylus
フォルダが見えないので Source Maps を参照できなくなる。
この問題を解決するためにルートを src
に変更すると、npm としてインストール & 参照している normalize.css が含まれない。よってプロジェクト全体のルートになる ./
を指定。
この状態で browser-sync を起動すると Web ブラウザで初期表示される階層が ./
になる。そのため src/assets
を表示するには手動で URL を修正しなければならない。これは面倒なので初期表示するページも同時に指定しておく。
{
"scripts": {
"watch:server": "browser-sync start --server ./ --startPath src/assets/"
}
}
--server
に指定されたパスが Web サイトのルート。初期表示するページはルートからの相対パスとして --startPath
へ指定すればよい。index.html
はデフォルトの表示対象なので省略可能。別のページにしたいなら明示的に指定すること。これでプロジェクト配下にある全てのファイルとフォルダを参照できるようになった。
Stylus 関連
これまで Stylus の CLI 設定は npm-scripts で以下のように定義していた。
{
"scripts": {
"build:css": "stylus -c ./src/stylus/App.styl -o ./src/html/bundle.css -m --sourcemap-root ../stylus",
"watch:css": "stylus -c -w ./src/stylus/App.styl -o ./src/html/bundle.css -m --sourcemap-root ../stylus",
"release:css": "stylus -c ./src/stylus/App.styl -o ./dist/bundle.css"
}
}
Source Maps におけるソース参照は --sourcemap-root
で指定していたのだが、いつからかこの方法だと Not Found になっていた。改めて Executable — Stylus を見直したら Source Maps 関連のオプションに --sourcemap-base
というものがある。相対パスで指定するならこれを利用するのが正しいので修正。
それと Normalize.css を npm で管理して Stylus に組み込むに書いた内容を反映した npm-scripts は以下。
{
"scripts": {
"build:css": "stylus -c --include-css ./src/stylus/App.styl -o ./src/html/bundle.css -m --sourcemap-base ../stylus",
"watch:css": "stylus -c -w --include-css ./src/stylus/App.styl -o ./src/html/bundle.css -m --sourcemap-base ../stylus",
"release:css": "stylus -c --include-css ./src/stylus/App.styl -o ./dist/bundle.css"
}
}
Source Maps の相対パスが ./stylus
ではなく ../stylus
になっている。これは前述の静的リソース用フォルダ変更への対応。
JavaScript 関連
Babel の設定を .babelrc
から package.json の babel
プロパティへ移動。mocha についても espower-babel から babel-preset-power-assert への移行で書いたようにしばらく mocha.opts
で運用していたのだが、Babel にあわせて package.json の npm-scripts へ定義する方法に戻した。
{
"babel": {
"presets": [
"es2015"
],
"env": {
"development": {
"presets": [
"power-assert"
]
}
}
},
"browserify" : {
"transform": [
"babelify"
]
},
"scripts": {
"test": "mocha --compilers js:babel-register test/**/*.test.js",
"build:js": "browserify ./src/js/App.js -d | exorcist ./src/assets/bundle.js.map > ./src/assets/bundle.js",
"watch:js": "watchify -v -t [ babelify ] ./src/js/App.js -o \"exorcist ./src/assets/bundle.js.map > ./src/assets/bundle.js\" -d",
"release:js": "cross-env NODE_ENV=production browserify ./src/js/App.js | uglifyjs > ./dist/bundle.js"
}
}
Browserify の transform 設定も package.json
の browserify.transform
に定義して CLI オプションから -t [ babelify ]
を削除。しかし watchify はこれを無視するらしく watch
が落ちてしまう。コンパイルも失敗しているようで bundle.js
の実処理は空だ。仕方ないので watchify だけオプションを残している。
もうひとつ、production ビルドについて。
front-end-starter では View や Flux 系の npm は組み込まない方針だが React などを追加した場合、そのまま require/import
するとデバッグ用の処理が大量に残る。それらは
if (process.env.NODE_ENV !== 'production') {
}
のようになっているため残っても実害はない。しかしサイズは巨大だし React のリリース用イメージである react.min.js
からは除去されているものだから、自前で bundle する場合もそうしたい。
これを実現するには Babel のビルド時に NODE_ENV=production
を指定すればよい。npm-scripts で実行するなら環境変数の設定だけでなく記法もクロスプラットフォーム対応させたいので cross-env を利用。前述のサンプルから当該部分だけ抜き出すと
{
"babel": {
// Babel 設定
},
"browserify" : {
// browserify 設定
},
"scripts": {
"release:js": "cross-env NODE_ENV=production browserify ./src/js/App.js | uglifyjs > ./dist/bundle.js"
}
}
のようになる。この話と除去の原理については browserify + npmでReactを使う場合はNODE_ENVを設定するとよい - Qiita を参照のこと。Downloads | React の Note でも production と mishoo/UglifyJS2 について言及されている。
React v15.1.0 を bundle する場合、この処理の有無でファイルサイズが 8KB ぐらい縮小された。
gulpfile を ES2015 対応させる
いまは gulp を利用していないのだけど、何気なく front-end-starter-with-gulp の npm を更新してみたら gulp-watchify が更新されていて最新 Browserify に対応しているようだ。なのでこれも最新構成へ修正してみた。
gulp v4 を目前に控えており、そちらでは gulp-load-plugins で実現していた処理が標準化されるなどタスク実装を改善するレベルの機能追加がある。それを待つつもりだったが、そう考えてから数ヶ月すぎて未だ v3.9 のままだから現時点で可能な ES2015 対応のみを反映することにした。
しかし 2016/5/25 時点の gulpjs/gulp を babel や ES2015 で検索しても gulp/exports-as-tasks.md とか README、CHANGELOG ぐらいしか情報がない。機能としては実装されているが公式リファレンスはこれからなのだろうか。
gulpfile ES2015
とかでググると gulpfileをES2015(ES6)で書く や Using ES6 with gulp などが見つかった。後者の記事では Babel 6 以降の変更を反映しているため、主にこちらを参考にする。
ES2015 対応を試すにあたり、問題が起きたときの切り分けを簡単にするため最小のプロジェクトを用意することにした。npm init
して package.json だけ存在する状態から開始。
{
"name": "gulp-es2015",
"version": "1.0.0",
"description": "gulp-es2015",
"author": "akabeko",
"license": "MIT",
"main": "index.js",
"scripts": {
"test": ""
}
}
はじめに必要な npm を揃える。参考記事では gulp、babel-core、babel-register、babel-preset-es2015 を利用しているので、まとめてインストール。
$ npm i -D gulp babel-core babel-register babel-preset-es2015
babel-core は babel-register をインストールすると依存で入る。そのためか明示的にインストールしなくても ES2015 版の gulpfile を処理できるのだが公式リファレンスの言及がないので参考記事に従い、すべて入れておいた。
Babel の preset 設定は package.json
に定義。依存や設定は可能な限りこのファイルで管理する方針。
gulp の default
タスクを npm-scripts から呼び出すように定義。こうすれば npm start
でプロジェクトのローカルにある gulp を使用するのでグローバルにインストールしなくても済む。
これらをすべて反映した状態の package.json
。
{
"name": "gulp-es2015",
"version": "1.0.0",
"description": "gulp-es2015",
"author": "akabeko",
"license": "MIT",
"main": "index.js",
"babel": {
"presets": [
"es2015"
]
},
"scripts": {
"start": "gulp"
},
"devDependencies": {
"babel-core": "^6.9.0",
"babel-preset-es2015": "^6.9.0",
"babel-register": "^6.9.0",
"gulp": "^3.9.1"
}
}
環境が整ったので gulpfile を実装。ES2015 で書く場合はファイル名を gulpfile.babel.js
にしておく。すると gulp が babel-register 経由で babel-preset-es2015 を呼び出して ES2015 で記述されたファイルをコンパイル & 実行という流れで処理される仕組みのようだ。
gulpfile.babel.js
を試す。console.log
するだけの簡素なタスクを定義。
import gulp from 'gulp';
gulp.task( 'default', () => {
console.log( 'test' );
} );
実行してみる。
$ npm start
> gulp-es2015@1.0.0 start .../sample
> gulp
[16:20:03] Requiring external module babel-register
(node:8531) fs: re-evaluating native module sources is not supported. If you are using the graceful-fs module, please update it to a more recent version.
[16:20:04] Using gulpfile .../sample/gulpfile.babel.js
[16:20:04] Starting 'default'...
test
[16:20:04] Finished 'default' after 208 μs
タスクは正常に実行された。しかし gulp の参照している graceful-fs が古いためだろうか、常に警告が表示される。なんとかして欲しいものだ。
gulp-stylus の Source Maps 修正
gulp 版の browser-sync も npm-scripts と同様に server
と startPath
を分けて見たのだが Stylus の Source Maps がうまく参照できなかった。調査したところ gulp.src
で base
オプションを指定すれば適切に参照できることが判明。
また front-end-starter と同じく normalize.css を npm で管理して組み込むように修正してみた。それら全てを反映した css
タスクは以下となる。
gulp.task( 'css', () => {
return gulp.src( [ config.dir.stylus + 'App.styl' ], { base: config.dir.root } )
.pipe( $.plumber() )
.pipe( $.if( !( config.isRelease ), $.sourcemaps.init() ) )
.pipe( $.stylus( { 'include css': true } ) )
.pipe( $.rename( 'bundle.css' ) )
.pipe( $.if( config.isRelease, $.cleanCss() ) )
.pipe( $.if( !( config.isRelease ), $.sourcemaps.write( './' ) ) )
.pipe( $.if( config.isRelease, gulp.dest( config.dir.dist ), gulp.dest( config.dir.assets ) ) );
} );
import
や config
定義については examples-web-app/gulpfile.babel.js を参照のこと。
修正を反映した後に npm start
して browser-sync が適切にページを表示すること、その状態から Source Maps を参照できることを確認済み。