Skip to content

Commit 2e977f4

Browse files
committed
Make it testacular plugin, allow external deps.js, removing files
1 parent 0181298 commit 2e977f4

9 files changed

+363
-211
lines changed

index.js

+14-109
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,11 @@ var chokidar = require('chokidar');
22
var fs = require('q-io/fs');
33

44
// inputs
5-
var WATCH = 'test-app/js';
5+
var WATCH = ['test-app/js', 'test-app/test'];
6+
var INCLUDE = ['test-app/test/main.js'];
67

7-
8-
var fileMap = Object.create(null);
9-
var provideMap = Object.create(null);
10-
11-
12-
var index = require('./lib/index');
13-
14-
var parseProvideRequire = index.parseProvideRequire;
15-
var diffSorted = index.diffSorted;
16-
17-
// DependencyResolver
18-
19-
// state:
20-
// - fileMap
21-
// - provideMap
22-
// - cacheFiles = cached resolved files
23-
// - cacheId = array of included files
24-
25-
var updateProvideMap = function(filepath, oldProvides, newProvides) {
26-
oldProvides.forEach(function(dep) {
27-
provideMap[dep] = null;
28-
});
29-
30-
newProvides.forEach(function(dep) {
31-
provideMap[dep] = filepath;
32-
});
33-
};
34-
35-
var checkFile = function(filepath) {
36-
return fs.read(filepath).then(function(content) {
37-
var parsed = parseProvideRequire(content);
38-
39-
if (!fileMap[filepath]) {
40-
console.log('New file', filepath, 'adding to the map.');
41-
console.log(parsed);
42-
updateProvideMap(filepath, [], parsed.provides);
43-
fileMap[filepath] = parsed;
44-
return;
45-
}
46-
47-
var diffProvides = diffSorted(fileMap[filepath].provides, parsed.provides);
48-
var diffRequires = diffSorted(fileMap[filepath].requires, parsed.requires);
49-
50-
if (diffProvides) {
51-
console.log('Provides change in', filepath);
52-
console.log('Added', diffProvides.added);
53-
console.log('Removed', diffProvides.removed);
54-
} else {
55-
console.log('No provides change in', filepath);
56-
}
57-
58-
if (diffRequires) {
59-
console.log('Requires change in', filepath);
60-
console.log('Added', diffRequires.added);
61-
console.log('Removed', diffRequires.removed);
62-
} else {
63-
console.log('No requires change in', filepath);
64-
}
65-
66-
updateProvideMap(filepath, fileMap[filepath].provides, parsed.provides);
67-
fileMap[filepath] = parsed;
68-
});
69-
};
70-
71-
// TODO(vojta): handle circular deps
72-
var resolveFile = function(filepath, files, alreadyResolvedMap) {
73-
// console.log('resolving', filepath);
74-
75-
files = files || [];
76-
alreadyResolvedMap = alreadyResolvedMap || Object.create(null);
77-
78-
// TODO(vojta): error if unknown file
79-
// resolve all dependencies first
80-
fileMap[filepath].requires.forEach(function(dep) {
81-
if (!alreadyResolvedMap[dep]) {
82-
// TODO(vojta): error if dep not provided
83-
resolveFile(provideMap[dep], files, alreadyResolvedMap);
84-
}
85-
});
86-
87-
files.push(filepath);
88-
fileMap[filepath].provides.forEach(function(dep) {
89-
alreadyResolvedMap[dep] = true;
90-
});
91-
92-
return files;
93-
};
8+
var DependencyResolver = require('./lib/resolver');
9+
var resolver = new DependencyResolver();
9410

9511
var pendingTimer = null;
9612
var scheduleResolving = function() {
@@ -101,32 +17,21 @@ var scheduleResolving = function() {
10117
pendingTimer = setTimeout(function() {
10218
pendingTimer = null;
10319
console.log('RESOLVED FILES');
104-
console.log(resolveFile('test-app/test/main.js'));
20+
console.log(resolver.resolveFiles(INCLUDE));
10521
}, 500);
10622
};
10723

24+
// kick off watching
25+
var watcher = chokidar.watch(WATCH, {persistent: true});
10826

109-
chokidar.watch(WATCH, {persistent: true}).add('test-app/test')
110-
.on('change', function(path) {
111-
console.log('change', path);
112-
checkFile(path).then(function() {
113-
scheduleResolving();
114-
// console.log('RESOLVED FILES');
115-
// console.log(resolveFile('test-app/test/main.js'));
27+
['change', 'add'].forEach(function(eventName) {
28+
watcher.on(eventName, function(path) {
29+
console.log(eventName, path);
30+
fs.read(path).then(function(content) {
31+
resolver.updateFile(path, content);
32+
scheduleResolving();
33+
});
11634
}, function(e) {
117-
console.error(e);
118-
console.error(e.stack);
119-
});
120-
})
121-
.on('add', function(path) {
122-
console.log('add', path);
123-
checkFile(path).then(function() {
124-
scheduleResolving();
125-
// console.log('RESOLVED FILES');
126-
// console.log(resolveFile('test-app/test/main.js'));
127-
}, function(e) {
128-
console.error(e);
12935
console.error(e.stack);
13036
});
13137
});
132-

lib/goog.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
var vm = require('vm');
2+
var path = require('path');
3+
4+
5+
// TODO(vojta): improve, this is lame
6+
var parseProvideRequire = function(str) {
7+
var provides = [];
8+
var requires = [];
9+
var match;
10+
11+
str.split('\n').forEach(function(line) {
12+
match = line.match(/goog\.provide\([\"\'](.*)[\"\']\)/);
13+
if (match) {
14+
provides.push(match[1]);
15+
}
16+
17+
match = line.match(/goog\.require\([\"\'](.*)[\"\']\)/);
18+
if (match) {
19+
requires.push(match[1]);
20+
}
21+
});
22+
23+
return {
24+
provides: provides.sort(),
25+
requires: requires.sort()
26+
};
27+
};
28+
29+
var parseDepsJs = function(filepath, content) {
30+
var parsed = {
31+
fileMap: Object.create(null),
32+
provideMap: Object.create(null)
33+
};
34+
var sandbox = {
35+
parsed: parsed,
36+
goog: {
37+
addDependency: function(relativePath, provides, requires) {
38+
var absolutePath = path.resolve(path.dirname(filepath), relativePath);
39+
40+
parsed.fileMap[absolutePath] = {provides: provides, requires: requires};
41+
provides.forEach(function(dep) {
42+
parsed.provideMap[dep] = absolutePath;
43+
});
44+
}
45+
}
46+
};
47+
48+
vm.runInNewContext(content, sandbox, filepath);
49+
50+
return parsed;
51+
};
52+
53+
exports.parseDepsJs = parseDepsJs;
54+
exports.parseProvideRequire = parseProvideRequire;

lib/plugin.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
var DependencyResolver = require('./resolver');
2+
3+
// TODO(vojta): cache resolved
4+
// TODO(vojta): filter included files by iit/ddescribe
5+
6+
var initClosureDependencyResolver = function(emitter, fileList) {
7+
var resolver = new DependencyResolver();
8+
9+
// whenever file list changes, resolve the deps - update files.included
10+
emitter.on('file_list_modified', function(promise) {
11+
promise.then(function(files) {
12+
var pathsToResolve = files.included.map(function(file) {
13+
return file.originalPath;
14+
});
15+
var resolvedPaths = resolver.resolveFiles(pathsToResolve);
16+
var servedFiles = files.served;
17+
18+
// TODO(vojta): change files.served to be a map, rather than an array
19+
files.included = resolvedPaths.map(function(filepath) {
20+
for (var i = 0, length = servedFiles.length; i < length; i++) {
21+
if (servedFiles[i].originalPath === filepath) {
22+
return servedFiles[i];
23+
}
24+
}
25+
26+
console.error('NOT SERVED FILE', filepath);
27+
var externalFile = {
28+
path: filepath,
29+
originalPath: filepath,
30+
contentPath: filepath,
31+
isUrl: false,
32+
// TODO(vojta): cache mtime and restat when deps.js changes ?
33+
// TODO(votja): use the last synced CL number in google3 ?
34+
mtime: new Date()
35+
};
36+
37+
files.served.push(externalFile);
38+
return externalFile;
39+
});
40+
});
41+
});
42+
43+
// monkey-patch fileList to get notified when file is removed
44+
var originalRemoveFile = fileList.removeFile;
45+
fileList.removeFile = function(filepath, done) {
46+
resolver.removeFile(filepath);
47+
return originalRemoveFile.call(fileList, filepath, done);
48+
};
49+
50+
return resolver;
51+
};
52+
53+
initClosureDependencyResolver.$inject = ['emitter', 'fileList'];
54+
55+
56+
var createPreprocesor = function(resolver) {
57+
return function(content, file, done) {
58+
resolver.updateFile(file.originalPath, content);
59+
done(content);
60+
};
61+
};
62+
63+
createPreprocesor.$inject = ['framework:closure'];
64+
65+
66+
var createDepsPreprocesor = function(resolver) {
67+
return function(content, file, done) {
68+
resolver.loadExternalDeps(file.originalPath, content);
69+
done(content);
70+
};
71+
};
72+
73+
createDepsPreprocesor.$inject = ['framework:closure'];
74+
75+
76+
// PUBLISH DI MODULE
77+
module.exports = {
78+
'framework:closure': ['factory', initClosureDependencyResolver],
79+
'preprocessor:closure': ['factory', createPreprocesor],
80+
'preprocessor:closure-deps': ['factory', createDepsPreprocesor]
81+
};

lib/resolver.js

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
var utils = require('./utils');
2+
var goog = require('./goog');
3+
4+
// TODO(vojta): can we handle provide "same thing provided multiple times" ?
5+
var DependencyResolver = function() {
6+
// the state
7+
var fileMap = Object.create(null);
8+
var provideMap = Object.create(null);
9+
10+
var updateProvideMap = function(filepath, oldProvides, newProvides) {
11+
oldProvides.forEach(function(dep) {
12+
if (provideMap[dep] === filepath) {
13+
provideMap[dep] = null;
14+
}
15+
});
16+
17+
newProvides.forEach(function(dep) {
18+
provideMap[dep] = filepath;
19+
});
20+
};
21+
22+
var resolveFile = function(filepath, files, alreadyResolvedMap) {
23+
if (!fileMap[filepath]) {
24+
// console.log('IGORED', filepath);
25+
files.push(filepath);
26+
return;
27+
}
28+
29+
// resolve all dependencies first
30+
fileMap[filepath].requires.forEach(function(dep) {
31+
if (!alreadyResolvedMap[dep]) {
32+
// TODO(vojta): error if dep not provided
33+
resolveFile(provideMap[dep], files, alreadyResolvedMap);
34+
}
35+
});
36+
37+
files.push(filepath);
38+
fileMap[filepath].provides.forEach(function(dep) {
39+
alreadyResolvedMap[dep] = true;
40+
});
41+
};
42+
43+
this.removeFile = function(filepath) {
44+
// TODO(vojta): handle if unknown file
45+
fileMap[filepath].provides.forEach(function(dep) {
46+
if (provideMap[dep] === filepath) {
47+
provideMap[dep] = null;
48+
}
49+
});
50+
fileMap[filepath] = null;
51+
};
52+
53+
this.updateFile = function(filepath, content) {
54+
var parsed = goog.parseProvideRequire(content);
55+
56+
if (!fileMap[filepath]) {
57+
// console.log('New file', filepath, 'adding to the map.');
58+
// console.log(parsed);
59+
updateProvideMap(filepath, [], parsed.provides);
60+
fileMap[filepath] = parsed;
61+
return;
62+
}
63+
64+
var diffProvides = utils.diffSorted(fileMap[filepath].provides, parsed.provides);
65+
var diffRequires = utils.diffSorted(fileMap[filepath].requires, parsed.requires);
66+
67+
if (diffProvides) {
68+
// console.log('Provides change in', filepath);
69+
// console.log('Added', diffProvides.added);
70+
// console.log('Removed', diffProvides.removed);
71+
} else {
72+
// console.log('No provides change in', filepath);
73+
}
74+
75+
if (diffRequires) {
76+
// console.log('Requires change in', filepath);
77+
// console.log('Added', diffRequires.added);
78+
// console.log('Removed', diffRequires.removed);
79+
} else {
80+
// console.log('No requires change in', filepath);
81+
}
82+
83+
updateProvideMap(filepath, fileMap[filepath].provides, parsed.provides);
84+
fileMap[filepath] = parsed;
85+
};
86+
87+
this.resolveFiles = function(files) {
88+
// console.log('RESOLVING', files);
89+
// console.log(fileMap);
90+
91+
var resolvedFiles = [];
92+
var alreadyResolvedMap = Object.create(null);
93+
94+
files.forEach(function(file) {
95+
resolveFile(file, resolvedFiles, alreadyResolvedMap);
96+
});
97+
98+
return resolvedFiles;
99+
};
100+
101+
this.loadExternalDeps = function(filepath, content) {
102+
var parsed = goog.parseDepsJs(filepath, content);
103+
104+
fileMap.__proto__ = parsed.fileMap;
105+
provideMap.__proto__ = parsed.provideMap;
106+
};
107+
};
108+
109+
module.exports = DependencyResolver;

0 commit comments

Comments
 (0)