examples-web-app 更新 2016/5

2016年5月25日 0 開発 , , , ,

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.htmlfonts のような静的リソースを区別しにくかった。そこで構成を以下のように変更。

.
├── 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 を揃える。参考記事では gulpbabel-corebabel-registerbabel-preset-es2015 を利用しているので、まとめてインストール。

$ npm i -D gulp babel-core babel-register babel-preset-es2015

なお babel-core は babel-register をインストールすると依存で入る。そのためか明示的にインストールしなくても ES2015 版の gulpfile を処理できるのだが、公式リファレンスの言及がないので参考記事に従い、すべて入れておいた。

Babel の preset 設定は package.json に定義。依存や設定は可能な限り 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 を参照できることを確認済み。


REPLY

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です