UNPKG

4.23 kBJavaScriptView Raw
1/**
2 * Module dependencies.
3 */
4var fs = require('fs')
5 , debug = require('debug')('koa-liveload')
6 , path = require('path');
7
8/**
9 * Utility functions to synchronously test whether the giving path
10 * is a file or a directory.
11 */
12var is = (function(ret) {
13 ['file', 'dir'].forEach(function(method) {
14 ret[method] = function(fpath) {
15 var suffix = ({file: 'File', dir: 'Directory'})[method];
16 if (fs.existsSync(fpath)) {
17 return fs.statSync(fpath)['is' + suffix]();
18 }
19 return false;
20 }
21 });
22 return ret;
23}({}));
24
25
26/**
27 * Get sub-directories in a directory.
28 */
29var sub = function(parent, cb) {
30 if (is.dir(parent)) {
31 fs.readdir(parent, function(err, all) {
32 all && all.forEach(function(f) {
33 var sdir = path.join(parent, f)
34 if (is.dir(sdir)) {
35 cb.call(null, sdir)
36 }
37 });
38 });
39 }
40};
41
42
43/**
44 * A container for memorizing names of files or directories.
45 */
46var memo = (function(memo) {
47 return {
48 push: function(name, type) {
49 memo[name] = type;
50 },
51 has: function(name) {
52 return memo.hasOwnProperty(name) ? true : false;
53 },
54 update: function(name) {
55 if (!is.file(name) && !is.dir(name)) {
56 delete memo[name];
57 }
58 return true;
59 }
60 };
61}({}));
62
63
64/**
65 * A Container for storing unique and valid filenames.
66 */
67var fileNameCache = (function(cache) {
68 return {
69 push: function(name) {
70 cache[name] = 1;
71 return this;
72 },
73 each: function() {
74 var temp = Object.keys(cache).filter(function(name){
75 if (memo.has(name)) {
76 memo.update(name);
77 }
78 return true;
79 });
80 temp.forEach.apply(temp, arguments);
81 return this;
82 },
83 clear: function(){
84 cache = {};
85 return this;
86 }
87 };
88}({}));
89
90
91/**
92 * Abstracting the way of avoiding duplicate function call.
93 */
94var worker = (function() {
95 var free = true;
96 return {
97 busydoing: function(cb) {
98 if (free) {
99 free = false;
100 cb.call();
101 }
102 },
103 free: function() {
104 free = true;
105 }
106 }
107}());
108
109
110
111/**
112 * Watch a file or a directory recursively.
113 *
114 * @param {String} fpath
115 * @param {Object} options includes ['html', 'js', 'css'] excludes ['node_modules', 'components']
116 * @param {Function} cb
117 *
118 * watch('fpath', function(file) {
119 * console.log(file, ' changed');
120 * });
121 */
122
123function watchDir(fpath, opts, cb){
124 if(typeof(opts) == "function"){
125 cb = opts;
126 opts = {};
127 }
128 opts.includes = opts.includes || ['js'];
129 opts.excludes = opts.excludes || ['node_modules'];
130 opts.ignoreHidden = opts.ignoreHidden || true;
131 watch(fpath, cb);
132
133 var normalizeCall = function(fname, cb) {
134 debug('changed file: ' + fname);
135 // Store each name of the modifying or temporary files generated by an editor.
136 fileNameCache.push(fname);
137
138 worker.busydoing(function() {
139 // A heuristic delay of the write-to-file process.
140 setTimeout(function() {
141
142 // When the write-to-file process is done, send all filtered filenames
143 // to the callback function and call it.
144 fileNameCache
145 .each(function(f) {
146 // Watch new created directory.
147 if (!memo.has(f) && is.dir(f)) {
148 watch(f, cb);
149 } else if (is.file(f)) {
150 cb.call(null, f);
151 }
152 }).clear();
153
154 worker.free();
155
156 }, 100);
157 })
158 }
159
160 function watch(fpath, cb) {
161 var basename = path.basename(fpath);
162 if(!is.dir(fpath)) return;
163 if(opts.ignoreHidden && /^\./.test(basename)) return;
164 if(opts.excludes && opts.excludes.indexOf(basename) !== -1) return;
165
166 debug('watching directory: ' + fpath);
167 memo.push(fpath, 'dir');
168 fs.watch(fpath, function(err, fname) {
169 // Windows "delete" operations do not pass the filename that was deleted
170 if(!fname) return;
171 var ext = path.extname(fname).slice(1);
172 var f = path.join(fpath, fname);
173 if (is.file(f) && opts.includes.indexOf(ext) === -1) return;
174 normalizeCall(f, cb);
175 });
176 // Recursively watch its sub-directories.
177 sub(fpath, function(dir) {
178 watch(dir, cb);
179 });
180 }
181}
182
183
184// Expose.
185module.exports = watchDir;
\No newline at end of file