アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

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.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.jsonbrowserify.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 に定義。依存や設定は可能な限りこのファイルで管理する方針。

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 と同様に serverstartPath を分けて見たのだが Stylus の Source Maps がうまく参照できなかった。調査したところ gulp.srcbase オプションを指定すれば適切に参照できることが判明。

また 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 ) ) );
} );

importconfig 定義については examples-web-app/gulpfile.babel.js を参照のこと。

修正を反映した後に npm start して browser-sync が適切にページを表示すること、その状態から Source Maps を参照できることを確認済み。

Copyright © 2009 - 2023 akabeko.me All Rights Reserved.