-
Notifications
You must be signed in to change notification settings - Fork 1
/
middleware.js
155 lines (135 loc) · 3.91 KB
/
middleware.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Shamelessly copied from the stylus middleware
var imports = {};
module.exports = function(options){
var browserify = require('browserify');
var mkdirp = require('mkdirp');
var join = require('path').join;
var dirname = require('path').dirname;
var url = require('url');
var fs = require('fs');
options = options || {};
// Accept src/dest dir
if ('string' == typeof options) {
options = { src: options };
}
// Force compilation
var force = options.force;
// Source dir required
var src = options.src;
if (!src) throw new Error('browserify.middleware() requires "src" directory');
// Default dest dir to source
var dest = options.dest
? options.dest
: src;
// Default compile callback
options.compile = options.compile || function(path){
return browserify({
entry: path
});
};
// Middleware
return function browserify(req, res, next){
if ('GET' != req.method && 'HEAD' != req.method) return next();
var path = url.parse(req.url).pathname;
if (/\.js$/.test(path)) {
var jsPath = join(dest, path)
, coffeePath = join(src, path.replace('.js', '.coffee'))
, origPath = join(src, path);
// Ignore ENOENT to fall through as 404
function error(err) {
next('ENOENT' == err.code
? null
: err);
}
// Force
if (force) return compile();
// Compile to jsPath
function compile() {
fs.exists(coffeePath, function(isCoffee){
if (!isCoffee) {
fs.exists(origPath, function(exists) {
if (!exists) {
next();
}
else {
commonCompile(origPath);
}
});
}
else {
commonCompile(coffeePath);
}
});
}
function commonCompile(path) {
var compiler = options.compile(path);
delete imports[path];
var js = compiler.bundle();
mkdirp(dirname(jsPath), 0700, function(err){
if (err) return error(err);
fs.writeFile(jsPath, js, 'utf8', next);
});
};
// Re-compile on server restart, disregarding
// mtimes since we need to map imports
if (!imports[origPath] && !imports[coffeePath]) return compile();
// Compare mtimes
fs.stat(coffeePath, function(err, coffeeStats){
fs.stat(origPath, function(err, origStats){
if (err) return error(err);
commonCompare(origPath, origStats);
});
commonCompare(coffeePath, coffeeStats);
});
function commonCompare(path, stats) {
fs.stat(jsPath, function(err, jsStats){
// JS has not been compiled, compile it!
if (err) {
if ('ENOENT' == err.code) {
compile();
} else {
next(err);
}
} else {
console.log(stats);
// Source has changed, compile it
if (stats.mtime > jsStats.mtime) {
compile();
imports[path]=[{path:path,mtime:stats.mtime}];
// Already compiled, check imports
} else {
checkImports(path, function(changed){
changed && changed.length ? compile() : next();
});
}
}
});
};
} else {
next();
}
}
};
/**
* Check `path`'s imports to see if they have been altered.
*
* @param {String} path
* @param {Function} fn
* @api private
*/
function checkImports(path, fn) {
var nodes = imports[path];
if (!nodes) return fn();
if (!nodes.length) return fn();
var pending = nodes.length
, changed = [];
nodes.forEach(function(imported){
fs.stat(imported.path, function(err, stat){
// error or newer mtime
if (err || !imported.mtime || stat.mtime > imported.mtime) {
changed.push(imported.path);
}
--pending || fn(changed);
});
});
}