[devs: Gulp] gulp note (attention old contents). #js #gulp
node.js
環境下で駆動させるCUIアプリケーション。
JavaScriptで動作する「タスクランナー」と呼ばれるタスク自動化ツールで、フロントエンド開発時にビルドツールのように扱う。
具体的には Sass(CSS), Markdown(HTML), ES2015(JavaScript) などのコンパイル(トランスパイル)に用いる。
Gulpはローカル(グローバル)にインストールされたGulpの他に、以下の3つの構成で駆動する。また、パッケージ管理は node.js
の npm
で行う。
gulpfile.js
package.json
node_modules
ディレクトリ以下は初回の環境導入時の手順(notプロジェクト毎)。 ruby
node.js(npm)
は必ず環境変数にパスを通すこと。
node.js
に必要な ruby
を公式サイトよりインストールnode.js
を公式サイトよりインストールgulp
を npm
経由でインストール $ npm install -g gulp
Gulpはプロジェクト毎の「必要パッケージのバージョンへの依存性」を解消するために、Gulp自身を含むプラグイン(パッケージ)を必ずプロジェックトディレクトリへ個別に導入する必要がある。 package.json
に必要パッケージがリスト化され、 $ npm install
で必要パッケージ群のインストールを自動化できるため、Gitなどでソース管理していればさほど面倒ではないが、 node_modules
はプロジェクト毎に必要なので不要であればシンボリックリンクなどで対応するのも手。
以下はプロジェクトフォルダに初めてGulpを導入する際の手順。
$ npm init
$ npm install --save-dev gulp
$ gulp watch
など監視コマンドの実行以下は既に開発リーダによって gulpfile.js
や package.json
がGitなどでソース共有されている場合の導入手順。
$ npm install
: 既存の package.json
より依存パッケージを全てインストール$ gulp watch
: 既存の gulpfile.js
に用意されたコマンド実行(大抵は gulp
か gulp watch
)http://qiita.com/cognitom/items/c6d821d72664f90f1e11
watchが立ち上がらないときは、大体監視対象ファイル(拡張子)がないディレクトリを探索しにいる。必ずファイルが存在するディレクトリを限定するなど工夫補が必要。
/**
* [gulpfile.js]
* working in vagrant(192.168.33.10).
* @autor yano3
* @version4.3 - 2016.12.23
*/
/**
* [gulp-plugins]
* input after '$ npm install --save-dev'.
* common
* gulp
* browser-sync
* gulp-plumber
* js
* riot
* babel babel-core babel-loader babel-cli
* babel-preset-es2015 babel-preset-es2015-riot
* babelify riotify
* browserify vinyl-source-stream vinyl-buffer
* css
* gulp-sass
* gulp-pleeease
* markdown
* gulp-markdown
*
* ----
*
* lib,fw
* riot, superagent, (jQuery)
*/
var assets = "./html/assets";
var entJs = assets+"/js/src/app.js";
var dirTag = assets+"/js/src/*.tag";
var dirJs = assets+"/js/**/*.js";
var distJs = assets+"/js/";
var dirCss = assets+"/css/**/*.css";
var dirSass = assets+"/css/sass/**/*.scss";
var distSass = assets+"/css/";
var dirMd = "./stmt/md/**/*.md";
var distMd = "./stmt/";
var watchList = [
"!node_modules",
"./App/**/*.php",
// "./App/**/*.html",
// "./App/**/*.ctp",
"./html/**/*.php",
dirJs,
dirTag,
dirCss,
dirSass,
dirMd
];
console.log('Watch-files: ');
console.dir(watchList);
/**
* [Watch]
* live-reload & compile
*/
var gulp = require('gulp');
var plumber = require('gulp-plumber');
gulp.task('watch',['server'], function(){
var gaze_opt = {
debounceDelay: 5000 // wait 5 sec after the last run
};
gulp.watch([dirTag,dirJs], gaze_opt, ['js']);
gulp.watch(dirSass, gaze_opt, ['sass']);
gulp.watch(dirMd, gaze_opt, ['md']);
gulp.watch(watchList, gaze_opt, ['reload']);
});
/**
* [ Common ]
* - gulp: main
* - browser-sync: live-reload (*notice: require <body>)
* - plumber: keep running
*/
var browserSync = require('browser-sync').create();
gulp.task('server',function(){
browserSync.init({
notify: true,
logPrefix: "SYNC",
proxy: "192.168.33.10", // vagrant-proxy
});
});
gulp.task('reload', function(){
browserSync.reload();
});
/**
* [ js ]
* browserify + babel(es6) + riot.js
*/
var browserify = require('browserify');
var riotify = require('riotify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
gulp.task('js', function () {
var b = browserify({
entries: [entJs],
debug: true // source map
})
b.transform(riotify,{type:"babel"})
b.transform(babelify,{presets: ["es2015"]})
b.bundle()
.on('error', function(err){
console.log(err.message);
console.log(err.stack);
})
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(gulp.dest(distJs));
return b;
});
/**
* [ Sass ]
* - gulp-sass: compile scss->css
* - gulp-pleeease: prefix, minify
*/
var sass = require('gulp-sass');
var pleeease = require('gulp-pleeease');
gulp.task('sass', function() {
gulp.src(dirSass)
.pipe(plumber())
.pipe(sass({
style: "expanded"
}))
.pipe(pleeease({
autoprefixer: {
browsers: ['last 4 versions']
},
minifier: true
}))
.pipe(gulp.dest(distSass));
});
/**
* [ Markdown ]
* - gulp-markdown: compile
* @notice: don't use </body>-tag in .md file
*/
var markdown = require('gulp-markdown');
gulp.task('md', function(){
gulp.src(dirMd)
.pipe(plumber())
.pipe(markdown())
.pipe(gulp.dest(distMd));
});
````````````````````````````````````````````
### package.json
````````````````````````````````````````````
{
"name": "amplify",
"version": "3.0.0",
"description": "Sass+Riot(Browserify+Babel)",
"main": "gulpfile.js",
"dependencies": {
"jquery": "^2.2.4",
"riot": "^3.4.4"
},
"devDependencies": {
"babel": "^6.5.2",
"babel-cli": "^6.24.1",
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2015-riot": "^1.1.0",
"babelify": "^7.3.0",
"browser-sync": "^2.18.1",
"browserify": "^13.3.0",
"gulp": "^3.9.1",
"gulp-markdown": "^1.2.0",
"gulp-pleeease": "^2.0.2",
"gulp-plumber": "^1.1.0",
"gulp-sass": "^2.3.2",
"riotify": "^1.0.1",
"superagent": "^2.3.0",
"superagent-jsonp": "^0.1.1",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"webpack": "^2.2.0"
},
"babel": {
"presets": [
"es2015-riot"
]
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "yano3nora",
"license": "MIT"
}
````````````````````````````
----------------------------------------------
## Manual
### ライブリロード
`BrowserSync` を使用。Vagrantにプロキシ経由で接続し、ホストOS側ソースに更新があった場合にブラウザリロード。
メモリ消費などの関係でディレイを数秒設けているので注意。
### Sass
Sassのコンパイルgulp-sassとgulp-pleeaseでCSSの「Sass利用・コンパイル」と「プレフィクス補完・minify」を行う。ディレクトリ構成は以下を想定。
``````
/* FLOCSS設計 */
/css
- style.css(style.scssをコンパイルしたもの)
/sass
- style.scss(/modules以下を@import)
- /modules
- _00_conf.scss
- _01_foundation.scss
- ....
``````
### Markdown
アプリケーションの仕様書などをmarkdownで記述し、ソース管理を行うケースで使用する。.mdファイルをhtmlへコンバートする。ディレクトリ構成は以下を想定。
既知の問題として、mdファイル内でbodyタグを明示しないとライブリロードされないので注意。また、bodyタグで全体を囲うと今度はmd->htmlのコンパイルがきれいに通らない。なので<body>開始タグだけ書き、閉じタグ(</body>)を記入しない方法で対処する。
````````
/stmt
- index.html
- md/
- index.md
`````````
### JavaScript
- ロジック・ステートレスなプラグインJS群(jQuery,jQuery-plugin)
- ロジック・ステートフルなモジュールJS群(主にAjaxで用いるDOM群)
サーバサイドMVCでアプリの大枠を作る場合、フロントエンドに欲しい機能はだいたい上記の2つになるはず。ロジックのないプラグインはゆくゆくはレガシーな技術的負債として残るが、現段階でクリティカルな解決策がない。アプリ・WEBサイトなど全体に用いるプラグイン的なJSはある程度許容できるとして、アプリケーションの機能・ロジックに密接に結びつくモジュールの方はある程度のフレームワークが必要になる。
そこで、フロントエンドのモジュール開発ではJSを軸に開発→ビルド→デプロイをタスクランナーで自動化・構造化し、アプリのフロントサイドのあちこちにJSロジック・Ajax・HTMLをばらまかないようにする必要がある(ついでにSASSやMarkDownもコンパイルさせたり、ライブリロードもさせたりする)。
参考 : http://blog.lebe.jp/post/150338847590/modern-javascript-riotjs
``````````````````
//directory
|- /app (serverMVC)
|- /assets (staticFiles)
|- /css
|- /img
|- /js
|- /src
|- app.js //entry js
|- ~~~.tag //riot tags
|- bundle.js //es6 modules
|- plugin.js //script for legacy js, jQuery-plugins
|- jQuery.plugins ... //legacy jQuery-plugins source
``````````````````
gulp で ローカルに取り込んだ各種プラグインは、依存性(バージョンなど)を管理された状態で node_modules に格納される。gulpを走らせてこれらをビルドすると、import や require で クラスを取得できるようになる。( riot.min.js とかをDLして `<script src="">` で読み込んで利用... ではなく、gulp でビルド中に app.js 内で import riot form 'riot'; で node_modules から riot.min.js をクラスとしてとってきている。よって js ディレクトリ配下にごちゃごちゃプラグインを入れて...みたいなことはしなくなる )
以下は全てGulpのプラグインで用意する「フロントエンド開発のためのツール群」である。ちなみにタスクランナーにはGruntもあるが、JSが軸なのでnode.jsで動作するGulpを採用する。
#### コンパイラ
__生JS(ES6)+Babel(JSトランスパイラ)
現在EcmaScript2015(ES6)が策定されクラスが使えるようになったJSだが、コンパイル(ソースをソースに変換するのでトランスパイルとも)してES5出力しないとブラウザ対応ができない。それを解決する。
#### モジュール管理
__Browserify
ES7からモジュール管理機能(要はrequire)が使えるようになる予定のJSだが、現状モジュール機能は別途ツールの補助がないと行えない。WebPackやRequireJSなど有名だが、リファレンスの多さからBrowserifyを採用。
#### ビューフレームワーク
__Riot.js
Angular2(MVC), React.js, Vue.js, など無数にある。これらJSフレームワークは基本的に上記の開発環境が揃っていないと導入は厳しい。フルスタックものから機能を絞ったシンプルなものまである。