なぜこのようなフレームワークを構築したのか
当社のPC向けレガシープロジェクトは、以前はJekyllとRuby-Sassという比較的古い技術スタックに基づいて構築されていました。しかし、Jekyllのテンプレート継承とSassの強力なプリプロセス能力、そしてGruntを使ったタスク管理を組み合わせることで、すべてが非常に快適に機能していました。
しかし、プロジェクト規模が急激に増大するにつれて、この環境の速度はあまりにも遅すぎました。その結果、このプロジェクトで要件変更が必要になるたびに頭を悩ませるようになりました。要件の変更方法に悩むのではなく、ファイルを一つ変更するだけで、Jekyllが変更を検知してからSassのコンパイルが完了し、ブラウザが自動更新されるまでに、基本的に40秒以上かかり、これは全く受け入れられませんでした。
そこで、ずっと新しい開発環境を構築しようと考えていました。ちょうど先日新しいプロジェクトがあったので、それを機会に構築に着手しました。すでに2つのプロジェクトで利用しており、既存の環境にいくつかの変更を加えた後、以前の環境を完全に置き換えることができるようになりました。
パフォーマンス
Node-SassがRuby-Sassのコンパイル速度を圧倒的な差で凌駕し、JekyllがJadeに完全に凌駕されたおかげで、現在のプロジェクトではファイルの変更 => コンパイル完了 => ブラウザの自動更新というプロセス全体が約1秒で完了します。これは速度が数十倍に向上したと言えます。
しかも、このプロセス全体は完全に自動化されており、JavaScript、CSS、HTMLファイルのいずれを変更しても、ブラウザは再コンパイルが完了すると自動的に更新されます。
ファイル構造
これはプロジェクト開発時のファイル構造です。
public
、router
、views
はExpressのデフォルトのファイル構造なので、ここでは説明を省略します。
submodule
は異なるプロジェクト間で共通して利用するコードライブラリです。これにより、多くの共通コードを書く手間を省くことができます。
技術詳細
プロジェクト全体の技術スタックはJade + Node-Sass + JavaScriptで、完全自動開発を実現しています。
まずviews
ディレクトリ以下にJadeテンプレートファイルを定義し、新しいページの追加を開始します。
依存関係
この環境は、主にExpress、Gulp、および一連のGulpプラグインに基づいて構築されています。以下は開発環境の依存関係です:
{
"dependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0"
},
"devDependencies": {
"browser-sync": "^2.9.1",
"del": "^2.0.2",
"gulp": "^3.9.0",
"gulp-autoprefixer": "^3.0.1",
"gulp-jade": "^1.1.0",
"gulp-nodemon": "^2.0.4",
"gulp-sass": "^2.0.4",
"gulp-sourcemaps": "^1.5.2",
"jade": "^1.11.0"
}
}
Expressは主にJadeテンプレートエンジンのレンダリングとルーティング機能を提供し、同時にローカルサーバーを起動します。「JadeテンプレートのレンダリングはGulpでも直接できるのでは?」と言う人もいるかもしれません。確かにGulpでも直接Jadeテンプレートをレンダリングできますが、問題が一つあります。それは、ローカルでJadeファイルを一つだけ変更した場合でも、GulpはすべてのJadeファイルを再レンダリングする必要があるため、明らかに多くの時間を無駄にしてしまうことです。
そこでExpressを導入し、必要に応じてJadeをレンダリングするようにしました。これにより多くの時間を節約できます。また、効率的な開発環境にはブラウザの自動更新が必須ですが、Expressにはこの機能がありません。そのため、browser-syncを導入し、Expressが起動したローカルサービスをプロキシし、ローカルファイルの変更を監視することで、ブラウザの自動更新を実現しています。
「Express関連のJavaScriptファイルを変更した場合や、Jadeファイルのコンパイルでエラーが発生した場合はどうなるのか?」と疑問に思う人もいるかもしれません。これらの場合、Expressサーバーはクラッシュし、再起動が必要になります。そこで、nodemonを導入し、Expressサーバーの自動再起動機能を実現しました。
この環境では、開発中に発生するすべてのJade、SCSS、JavaScriptファイルの変更が検知され、適切なアクションが非常に高速に実行されます。これにより、サービスを繰り返し再起動したりブラウザを更新したりするのではなく、ビジネスロジックのコード開発により多くの時間を費やすことができます。
最後に
これはGitHubに公開しています。こちらです。
参考までに、gulpfileの全文を以下に添付します:
"use strict";
var gulp = require("gulp");
var browserSync = require("browser-sync");
var reload = browserSync.reload;
var sass = require("gulp-sass");
var prefix = require("gulp-autoprefixer");
var nodemon = require("gulp-nodemon");
var sourcemaps = require("gulp-sourcemaps");
var jade = require("gulp-jade");
var stylus = require("gulp-stylus");
var rename = require("gulp-rename");
var del = require("del");
//dev task start
//DONE can not compile the sass or less file
gulp.task("sass", function () {
return gulp
.src(["./sass/personal.scss"])
.pipe(sourcemaps.init())
.pipe(sass({ errLogToConsole: true }).on("error", sass.logError))
.pipe(prefix("last 2 versions", "> 1%", "ie 8", "Android 2"))
.pipe(sourcemaps.write())
.pipe(gulp.dest("./public/css"))
.pipe(reload({ stream: true }));
});
gulp.task("browser-sync", ["nodemon"], function () {
browserSync.init(null, {
proxy: "http://localhost:3000",
files: ["public/**/*.*", "views/**/*.*", "submodule/**/*.*"],
browser: "google chrome",
notify: false,
port: 5000,
});
});
gulp.task("movesub", function () {
return gulp
.src(["./submodule/images/**/*.*"], { base: "./submodule" })
.pipe(gulp.dest("./public"));
});
gulp.task("stylus", function () {
return gulp
.src("submodule/stylus/public.styl")
.pipe(stylus())
.pipe(
rename({
extname: ".scss",
}),
)
.pipe(gulp.dest("submodule/stylus/"));
});
gulp.task("nodemon", function (cb) {
del(["./public/*.html"]);
var called = false;
return nodemon({
script: "bin/www",
}).on("start", function () {
if (!called) {
cb();
called = true;
}
});
});
//dev task end
gulp.task("clean", function (cb) {
del(["./dist/*"], cb);
});
gulp.task("copy", function () {
return gulp
.src(
[
"public/css/**/*",
"public/images/**/*",
"public/js/**/*",
"public/pageScripts/**/*",
],
{ base: "./public" },
)
.pipe(gulp.dest("./dist"));
});
//build task start
//DONE add build task
gulp.task("jade", function () {
return gulp
.src([
"views/**/*.jade",
"!views/layout/**/*.jade",
"!views/includes/**/*.jade",
])
.pipe(jade({ pretty: true }))
.pipe(gulp.dest("./dist"));
});
//build task end
gulp.task("dist", ["clean", "copy", "jade"]);
gulp.task("default", ["browser-sync", "sass", "movesub"], function () {
gulp.watch(["sass/**/*.*", ".submodule/stylus/**/*.*"], ["sass"]);
});
この記事は 2015年11月3日 に公開され、2015年11月3日 に最終更新されました。3625 日が経過しており、内容が古くなっている可能性があります。