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"]);
});
This article was published on November 3, 2015 and last updated on November 3, 2015, 3625 days ago. The content may be outdated.