紆余曲折の果てにたどり着いた私の開発環境をご紹介します。
あ、私は業務ではもっぱらNode.jsでexpressを使ったアプリを作ってますので、そのつもりで読んでください。
ファイルのパスとかは適宜お使いの環境に読み替えていただけると幸いです。
基本構成
実装に使っているのは下記になります
- TypeScript(AltJS 使い始めるともう生のjavascriptには戻れない)
- SCSS (CSSのメタ言語)
- Pug (テンプレートエンジン 商標の関係でJadeから改名された模様)
これらで実装し、ファイルに変更があれば自動でコンパイル、TypeScriptに関してはWebpackでバンドル、
そして自動でブラウザリロードといった感じです。
Pugのウォッチ
ウォッチはgulpで行います。
htmlへの変換とかはexpressのお仕事ですので、ここでは.pugの変更を検知してブラウザをリロードします。
gulpのタスクとして記述していくわけですが、なんでもかんでもgulpfile.jsに詰め込むとえらいことになるので、
require-dirを使ってファイルを分割します。
const gulp = require('gulp');
const requireDir = require('require-dir');
requireDir('./gulp/tasks', {recurse: true});
const gulp = require('gulp');
const browserSync = require('browser-sync');
gulp.task('serve',['browser-sync'],() => {
gulp.watch(['views/**/*.pug'], ['bs-reload']);
});
gulp.task('browser-sync', () => {
//browser-syncの起動
browserSync({
port: 3200,
notify: false,
proxy: {
target: 'http://127.0.0.1:3000/',
ws: true //websocketを使うならtrue、そうじゃないならfalse
},
https: false
});
});
//ブラウザリロード
gulp.task('bs-reload', () => {
browserSync.reload();
});
gulp.task('default', ['serve']);
SCSSのウォッチ
さて、次に.scssの変更を検知してcssにコンパイルします。
ベンダープレフィックスの付与とか最小化とかもgulpのプラグインでサクッとできるなんて、便利な世の中です。
コンパイルのタスクはこんな感じ
const gulp = require('gulp');
const autoprefixer = require('autoprefixer'); //ベンダープレフィックス
const cssnano = require('cssnano'); //cssの最小化
const plumber = require('gulp-plumber'); //エラーが発生しても落ちないように
const sass = require('gulp-sass');
const postCss = require('gulp-postcss');
gulp.task('style', function(){
return gulp.src('path/to/scssFile')
.pipe(plumber())
.pipe(sass())
.on('error', function(err) {
console.log(err.message);
})
.pipe(postCss([
autoprefixer({browsers: ['last 1 version']}),
cssnano()
]))
.pipe(gulp.dest(config.dest));
});
先ほどのserveタスクにscssのウォッチを追加します。
// 省略
gulp.task('serve',['browser-sync', 'style'],() => {
gulp.watch(['views/**/*.pug'], ['bs-reload']);
gulp.watch(['src/scss/**/*.scss'], ['style-reload']);
});
gulp.task('style-reload', ['style'], () => {
browserSync.reload();
});
// 省略
TypeScriptのウォッチ
次にTypeScriptのウォッチを行うわけですが、
gulpで.tsをウォッチして変更があったらWebpackでコンパイル...なんてことをしたらWebpackがメモリを食いつぶしてそのうち死にます。
なので、ここはWebpackのウォッチ機能を使います。
Webpackの設定はこんな感じ。
各ページごとにバンドルしたjsを生成します。
const webpack = require('webpack');
const BowerWebpackPlugin = require("bower-webpack-plugin");
const entryPoints = {
index: './src/ts/index/main.ts',
user: './src/ts/user/main.ts',
};
module.exports = {
// エントリーポイント
entry: entryPoints,
// 出力先
dest: './public/javascripts/',
// 出力するファイル名
// [name]とすることでentryPointsのキー名に応じたjsファイルが生成される
// この場合 public/javascripts以下にindex.jsとuser.jsが生成される
output: {
filename: '[name].js'
},
// 依存関係
resolve: {
root:[path.join(current,'bower_components')],
moduleDirectories: ["bower_components"],
extensions:['', '.webpack.js', 'web.js', '.js', '.ts', 'css']
},
// bowerで取得したライブラリの読み込み用プラグイン
plugins: [
new BowerWebpackPlugin()
],
// 各種ファイルを読み込むためのloader
module: {
loaders: [
{ test: /\.ts$/, loader: 'ts-loader' },
{ test: /\.css$/, loader: "style!css" },
{ test: /\.(jpg|png)$/, loader: 'file?name=[path][name].[ext]' }
]
},
watch: true //ウォッチ有効可
};
タスクはこんな感じ
const gulp = require('gulp');
const plumber = require('gulp-plumber');
const ts = require('gulp-typescript');
const tsConfig = require('path/to/tsconfig.json');
const webpack = require('webpack-stream');
gulp.task('webpack', function() {
return gulp.src(['./src/ts/**/*.ts'])
.pipe(plumber())
.pipe(ts(tsConfig.compilerOptions))
.pipe(webpack(require('../webpack.config')))
.pipe(gulp.dest('public/javascripts'));
});
serve.jsに追記したいところですが、gulpからwebpackを呼んでウォッチさせると、webpackがウォッチしている間ずっとタスクは続いている扱いになります。
つまり
gulp.task('serve',['browser-sync', 'style', 'webpack'], () => { //webpackが終わらない!!
gulp.watch(['views/**/*.pug'], ['bs-reload']);
gulp.watch(['src/scss/**/*.scss'], ['style-reload']);
});
とするとserveタスクには永遠にたどり着けません。
serveタスクとwebpackタスクを別個に立ち上げるのは面倒ですが、やむを得ません。
(いい方法あったら教えてください...)
Node.jsの再起動
さて、ここまでもっぱらフロントエンドの話でしたが当然サーバー側のソースをいじることもあります。
Node.jsはNode.jsを再起動しないとソースの変更が反映されないのですが、毎回手動でやるのはだるいのでnodemonに助けてもらいます。
gulp.task('serve',['browser-sync', 'style'], () => {
const nodemon = require('gulp-nodemon');
nodemon({
script: './bin/www',
ext: 'js json', //.jsや.jsonに変更があったらnodeを再起動
ignore: ['views', 'public','test', 'bower_components'], //フロントエンドのソースは変更されても無視
env: {
'NODE_ENV': 'development'
},
stdout: false
}).on('readable', () => {
//再起動完了したらブラウザリロード
setTimeout(() => {
browserSync.reload();
}, 1000);
});
gulp.watch(['views/**/*.pug', 'public/javascripts/**/*.js'], ['bs-reload']);
gulp.watch(['src/scss/**/*.scss'], ['style-reload']);
});