Why I Built This Framework
Our company’s legacy PC projects were all built on an older tech stack: jekyll + ruby-sass. However, jekyll’s template inheritance, coupled with sass’s powerful pre-processing capabilities and Grunt for task management, made everything quite manageable.
However, as project scales rapidly increased, this stack became incredibly slow. It got to the point where I’d dread making any changes to these projects. My headache wasn’t about figuring out how to implement a feature, but rather the fact that a simple file modification—from jekyll detecting the change, to sass compiling, to the browser automatically refreshing—would typically take over 40 seconds. This was completely unacceptable.
So, I had always intended to set up a new development environment. Coincidentally, a new project came up recently, which I took as an opportunity to build it. I’ve now used it for two projects, and after making some modifications based on the initial setup, it can completely replace the old stack.
Performance
Thanks to node-sass’s overwhelming compilation speed advantage over ruby-sass, and jekyll being completely outclassed by jade, the entire process in the current project—from file change to compilation complete to browser auto-refresh—now takes about 1 second. This is a speed improvement of dozens of times. Furthermore, the entire process is fully automated. Whether you modify JavaScript, CSS, or HTML files, the browser will automatically refresh once recompilation is complete. 
File Structure
Here’s the file structure during project development:

public, router, and views are all default Express file structures, so I won’t go into detail about them here. submodule is our shared code library across different projects, which helps us reduce redundant common code.
Technical Details
The entire project’s tech stack is jade + node-sass + JavaScript, enabling fully automated development. First, define the jade template files under views, then begin adding new pages.
Dependencies
This environment is primarily built upon Express, Gulp, and a series of Gulp plugins. Here’s the dependency relationship for the development environment:
{
"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"
}
}Among these, Express is primarily used to render the jade template engine and provide routing functionality, while also running a local server. Some might say, “I can render jade templates directly with Gulp.” Yes, Gulp can indeed render jade templates directly, but there’s a problem: if you only modify a single jade file locally, Gulp still needs to render all jade files, which clearly wastes a lot of time.
That’s why I introduced Express, which only renders jade on demand, saving a lot of time. Furthermore, an efficient development environment must have browser auto-refresh, but Express doesn’t offer this feature. So, I integrated browser-sync to proxy the local service started by Express, monitor local file changes, and achieve automatic browser refreshing.
Some might ask, “What if I modify an Express-related JavaScript file, or what if a jade file compilation fails?” In such scenarios, the Express server would crash and require a restart. That’s why I integrated nodemon to provide automatic restart functionality for the Express server.
Within this environment, all changes I make to jade, scss, and JavaScript files during development are detected, and appropriate actions are taken, all at a very high speed. This allows me to spend more time on developing business logic, rather than constantly restarting services and refreshing the browser.
Finally
This project has been uploaded to GitHub. You can find it here.
Additionally, the full gulpfile is attached for your reference:
"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"]);
});