Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80657 views
1
/**
2
* Copyright 2013 Facebook, Inc.
3
*
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
7
*
8
* http://www.apache.org/licenses/LICENSE-2.0
9
*
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
15
*/
16
var EventEmitter = require('events').EventEmitter;
17
var inherits = require('util').inherits;
18
var loaders = require('./loaders');
19
var fs = require('fs');
20
21
var MapUpdateTask = require('./MapUpdateTask');
22
var ResourceMap = require('./ResourceMap');
23
var ResourceMapSerializer = require('./ResourceMapSerializer');
24
var FileFinder = require('./FileFinder');
25
26
var ProjectConfigurationLoader = loaders.ProjectConfigurationLoader;
27
28
/*
29
*
30
* ____________________________
31
* .OOo
32
* OOOOL
33
* __________________ .. JOOO?
34
* .eSSSSSS**'
35
* gSSSSSSSSSS 6.
36
* oo SSSSSSSs .G
37
* ___________ oo SSSSSSSSSgG
38
* *ISSSSSS **
39
* SSSSSSS
40
* .oYSSSSSY.
41
* OOOOOOOOOOOg
42
* .OOOOT**TOOOOO.
43
* ______________________ .OOOO' 'OOOOO.
44
* .OOOO' OOOO'
45
* .oOOOO* .OOOO*
46
* .OOOOO'' .OOOOO
47
* .JOOOO* .##OOO'
48
* C##?/ T##I
49
* Y#| 'V
50
* Node Haste ________________________ C
51
*
52
*/
53
54
/**
55
* @class Haste. A nice facade to node-haste system
56
*
57
* Running a node haste update task is a pretty complicated matter. You have
58
* to manually create a FileFinder, a MapUpdateTask, a ResourceMapSerializer.
59
* Haste class automates all of this providing a task oriented API with a
60
* broad set of configuration options. The only 2 required parameters are
61
* loaders and scanDirs
62
*
63
* @extends {EventEmitter}
64
*
65
* @example
66
* var Haste = require('node-haste/Haste');
67
* var loaders = require('node-haste/loaders');
68
*
69
* var haste = new Haste(
70
* [
71
* new loaders.JSLoader({ networkSize: true }),
72
* new loaders.CSSLoader({ networkSize: true }),
73
* new ProjectConfigurationLoader(),
74
* new ResourceLoader()
75
* ],
76
* ['html']);
77
*
78
* haste.update('.cache', function(map) {
79
* assert(map instanceof ResourceMap);
80
* });
81
*
82
*
83
* @param {Array.<Loader>} loaders Preconfigured Loader instances
84
* @param {Array.<String>} scanDirs
85
* @param {FileFinder|null} options.finder Custom finder instance
86
* @param {ResourceMapSerializer|null} options.serializer Custom serializer
87
* @param {Number|null} options.maxOpenFiles Maximum number of loaders
88
* MapUpdateTask can use
89
* @param {Number|null} options.maxProcesses Maximum number of loader forks
90
* MapUpdateTask can use
91
* @param {Boolean|null} options.useNativeFind Whether to use native shell
92
* find command (faster) or node
93
* implementation (safer)
94
* @param {function|null} options.ignorePaths Function to reject paths
95
* @param {String|null} options.version Version of the cache. If
96
* the version mismatches the
97
* cached on, cache will be
98
* ignored
99
*
100
*/
101
function Haste(loaders, scanDirs, options) {
102
EventEmitter.call(this);
103
104
this.loaders = loaders;
105
this.scanDirs = scanDirs;
106
this.options = options || {};
107
this.finder = this.options.finder || null;
108
this.serializer = this.options.serializer || null;
109
}
110
inherits(Haste, EventEmitter);
111
112
/**
113
* All in one function:
114
* 1) load cache if exists
115
* 2) compare to the existing files
116
* 3) analyze changes,
117
* 4) update map,
118
* 5) write cache back to disk
119
* 6) return map
120
*
121
* @param {String} path
122
* @param {Function} callback
123
*/
124
Haste.prototype.update = function(path, callback, options) {
125
var map, files;
126
var me = this;
127
128
var run = function() {
129
if (!map || !files) {
130
return;
131
}
132
var task = me.createUpdateTask(files, map).on('complete', function(map) {
133
// only store map if it's changed
134
var mapChanged = task.changed.length > task.skipped.length;
135
if (mapChanged) {
136
me.storeMap(path, map, function() {
137
me.emit('mapStored');
138
callback(map, task.messages);
139
});
140
} else {
141
callback(map, task.messages);
142
}
143
}).run();
144
}
145
146
this.getFinder().find(function(f) {
147
files = f;
148
me.emit('found', files);
149
run();
150
});
151
152
if (options && options.forceRescan) {
153
map = new ResourceMap();
154
} else {
155
this.loadOrCreateMap(path, function(m) {
156
map = m;
157
me.emit('mapLoaded');
158
run();
159
});
160
}
161
};
162
163
/**
164
* Same as update but will also rerun the update every time something changes
165
*
166
* TODO: (voloko) add support for inotify and FSEvent instead of constantly
167
* running finder
168
*
169
* @param {String} path
170
* @param {Function} callback
171
* @param {Number} options.timeout How often to rerun finder
172
* @param {Boolean} options.forceRescan
173
*/
174
Haste.prototype.watch = function(path, callback, options) {
175
var timeout = options && options.timeout || 1000;
176
var finder = this.getFinder();
177
var map, files, task;
178
var me = this;
179
var firstRun = true;
180
181
function find() {
182
finder.find(function(f) {
183
files = f;
184
if (map) {
185
update();
186
}
187
});
188
}
189
190
function updated(m) {
191
map = m;
192
var mapChanged = task.changed.length > task.skipped.length;
193
// if changed, store the map and only then callback and schedule next run
194
if (mapChanged) {
195
me.storeMap(path, map, function() {
196
callback(map, task.changed, task.messages);
197
setTimeout(find, timeout);
198
});
199
return;
200
}
201
202
// callback on the first run even if the map is unchanged
203
if (firstRun) {
204
firstRun = false;
205
callback(map, task.changed, task.messages);
206
}
207
setTimeout(find, timeout);
208
}
209
210
function update() {
211
task = me.createUpdateTask(files, map).on('complete', updated).run();
212
}
213
214
if (options && options.forceRescan) {
215
map = new ResourceMap();
216
} else {
217
this.loadOrCreateMap(path, function(m) {
218
map = m;
219
if (files) {
220
update();
221
}
222
});
223
}
224
225
find();
226
};
227
228
/**
229
* Updates a map using the configuration options from constructor
230
* @param {ResourceMap} map
231
* @param {Function} callback
232
*/
233
Haste.prototype.updateMap = function(map, callback) {
234
this.getFinder().find(function(files) {
235
this.createUpdateTask(files, map).on('complete', callback).run();
236
}.bind(this));
237
};
238
239
/**
240
* Loads map from a file
241
* @param {String} path
242
* @param {Function} callback
243
*/
244
Haste.prototype.loadMap = function(path, callback) {
245
this.getSerializer().loadFromPath(path, callback);
246
};
247
248
/**
249
* @param {String} path
250
* @return {ResourceMap|null}
251
*/
252
Haste.prototype.loadMapSync = function(path) {
253
return this.getSerializer().loadFromPathSync(path);
254
};
255
256
/**
257
* Loads map from a file or creates one if cache is not available
258
* @param {String} path
259
* @param {Function} callback
260
*/
261
Haste.prototype.loadOrCreateMap = function(path, callback) {
262
this.getSerializer().loadFromPath(path, function(err, map) {
263
callback(map || new ResourceMap());
264
});
265
};
266
267
/**
268
* @param {String} path
269
* @return {ResourceMap}
270
*/
271
Haste.prototype.loadOrCreateMapSync = function(path) {
272
return this.loadMapSync(path) || new ResourceMap();
273
};
274
275
/**
276
* Stores the map cache
277
* @param {String} path
278
* @param {ResourceMap} map
279
* @param {Function} callback
280
*/
281
Haste.prototype.storeMap = function(path, map, callback) {
282
this.getSerializer().storeToPath(path, map, callback);
283
};
284
285
286
287
/**
288
* @protected
289
* @param {ResourceMap} map
290
* @return {MapUpdateTask}
291
*/
292
Haste.prototype.createUpdateTask = function(files, map) {
293
var task = new MapUpdateTask(
294
files,
295
this.loaders,
296
map,
297
{
298
maxOpenFiles: this.options.maxOpenFiles,
299
maxProcesses: this.options.maxProcesses
300
});
301
302
var events =
303
['found', 'changed', 'analyzed', 'mapUpdated', 'postProcessed', 'complete'];
304
var me = this;
305
events.forEach(function(name) {
306
task.on(name, function(value) {
307
me.emit(name, value);
308
});
309
});
310
return task;
311
};
312
313
/**
314
* @protected
315
* @return {FileFinder}
316
*/
317
Haste.prototype.getFinder = function() {
318
if (!this.finder) {
319
var ext = {};
320
this.loaders.forEach(function(loader) {
321
loader.getExtensions().forEach(function(e) {
322
ext[e] = true;
323
});
324
});
325
this.finder = new FileFinder({
326
scanDirs: this.scanDirs,
327
extensions: Object.keys(ext),
328
useNative: this.options.useNativeFind,
329
ignore: this.options.ignorePaths
330
});
331
}
332
return this.finder;
333
};
334
335
/**
336
* @protected
337
* @return {ResourceMapSerializer}
338
*/
339
Haste.prototype.getSerializer = function() {
340
return this.serializer || new ResourceMapSerializer(
341
this.loaders,
342
{ version: this.options.version });
343
};
344
345
module.exports = Haste;
346
347