Skip to content

Commit 614eb12

Browse files
committed
Transpile test files in the main process:
Squashed commits: transpile in the main thread drop unused dependencies incorporate PR feedback add tests add unit tests
1 parent f696271 commit 614eb12

11 files changed

+293
-94
lines changed

api.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ var Promise = require('bluebird');
88
var figures = require('figures');
99
var globby = require('globby');
1010
var chalk = require('chalk');
11+
var objectAssign = require('object-assign');
1112
var commondir = require('commondir');
1213
var resolveCwd = require('resolve-cwd');
1314
var AvaError = require('./lib/ava-error');
1415
var fork = require('./lib/fork');
1516
var formatter = require('./lib/enhance-assert').formatter();
17+
var CachingPrecompiler = require('./lib/caching-precompiler');
18+
var uniqueTempDir = require('unique-temp-dir');
19+
var findCacheDir = require('find-cache-dir');
1620

1721
function Api(files, options) {
1822
if (!(this instanceof Api)) {
@@ -44,7 +48,10 @@ util.inherits(Api, EventEmitter);
4448
module.exports = Api;
4549

4650
Api.prototype._runFile = function (file) {
47-
return fork(file, this.options)
51+
var options = objectAssign({}, this.options, {
52+
precompiled: this.precompiler.generateHashForFile(file)
53+
});
54+
return fork(file, options)
4855
.on('stats', this._handleStats)
4956
.on('test', this._handleTest)
5057
.on('unhandledRejections', this._handleRejections)
@@ -137,6 +144,12 @@ Api.prototype.run = function () {
137144
return Promise.reject(new AvaError('Couldn\'t find any files to test'));
138145
}
139146

147+
var cacheEnabled = self.options.cacheEnabled !== false;
148+
var cacheDir = (cacheEnabled && findCacheDir({name: 'ava', files: files})) ||
149+
uniqueTempDir();
150+
self.options.cacheDir = cacheDir;
151+
self.precompiler = new CachingPrecompiler(cacheDir);
152+
140153
self.fileCount = files.length;
141154

142155
self.base = path.relative('.', commondir('.', files)) + path.sep;

cli.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var cli = meow([
4343
' --require Module to preload (Can be repeated)',
4444
' --tap Generate TAP output',
4545
' --verbose Enable verbose output',
46+
' --no-cache Disable the transpiler cache',
4647
'',
4748
'Examples',
4849
' ava',
@@ -77,7 +78,8 @@ if (cli.flags.init) {
7778
var api = new Api(cli.input, {
7879
failFast: cli.flags.failFast,
7980
serial: cli.flags.serial,
80-
require: arrify(cli.flags.require)
81+
require: arrify(cli.flags.require),
82+
cacheEnabled: cli.flags.cache !== false
8183
});
8284

8385
var logger = new Logger();

lib/caching-precompiler.js

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
var cachingTransform = require('caching-transform');
2+
var fs = require('fs');
3+
var path = require('path');
4+
var md5Hex = require('md5-hex');
5+
var stripBom = require('strip-bom');
6+
7+
module.exports = CachingPrecompiler;
8+
9+
function CachingPrecompiler(cacheDir) {
10+
if (!(this instanceof CachingPrecompiler)) {
11+
throw new Error('CachingPrecompiler must be called with new');
12+
}
13+
this.cacheDir = cacheDir;
14+
this.filenameToHash = {};
15+
this.transform = this._createTransform();
16+
}
17+
18+
CachingPrecompiler.prototype._factory = function (cacheDir) {
19+
// This factory method is only called once per process, and only as needed, to defer loading expensive dependencies.
20+
var babel = require('babel-core');
21+
var convertSourceMap = require('convert-source-map');
22+
var presetStage2 = require('babel-preset-stage-2');
23+
var presetES2015 = require('babel-preset-es2015');
24+
var transformRuntime = require('babel-plugin-transform-runtime');
25+
26+
var powerAssert = this._createEspowerPlugin(babel);
27+
28+
function buildOptions(filename, code) {
29+
// Extract existing source maps from the code.
30+
var sourceMap = convertSourceMap.fromSource(code) || convertSourceMap.fromMapFileSource(code, path.dirname(filename));
31+
32+
return {
33+
presets: [presetStage2, presetES2015],
34+
plugins: [powerAssert, transformRuntime],
35+
filename: filename,
36+
sourceMaps: true,
37+
ast: false,
38+
babelrc: false,
39+
inputSourceMap: sourceMap && sourceMap.toObject()
40+
};
41+
}
42+
43+
return function (code, filename, hash) {
44+
code = code.toString();
45+
var options = buildOptions(filename, code);
46+
var result = babel.transform(code, options);
47+
var mapFile = path.join(cacheDir, hash + '.map');
48+
fs.writeFileSync(mapFile, JSON.stringify(result.map));
49+
return result.code;
50+
};
51+
};
52+
53+
CachingPrecompiler.prototype._createEspowerPlugin = function (babel) {
54+
var createEspowerPlugin = require('babel-plugin-espower/create');
55+
var enhanceAssert = require('./enhance-assert');
56+
57+
// initialize power-assert
58+
return createEspowerPlugin(babel, {
59+
patterns: enhanceAssert.PATTERNS
60+
});
61+
};
62+
63+
CachingPrecompiler.prototype._createTransform = function () {
64+
return cachingTransform({
65+
factory: this._factory.bind(this),
66+
cacheDir: this.cacheDir,
67+
salt: new Buffer(JSON.stringify({
68+
'babel-plugin-espower': require('babel-plugin-espower/package.json').version,
69+
'ava': require('../package.json').version,
70+
'babel-core': require('babel-core/package.json').version
71+
})),
72+
ext: '.js',
73+
hash: this._hash.bind(this)
74+
});
75+
};
76+
77+
CachingPrecompiler.prototype._hash = function (code, filename, salt) {
78+
var hash = md5Hex([code, filename, salt]);
79+
this.filenameToHash[filename] = hash;
80+
return hash;
81+
};
82+
83+
CachingPrecompiler.prototype.precompileFile = function (filename) {
84+
if (!this.filenameToHash[filename]) {
85+
this.transform(stripBom(fs.readFileSync(filename)), filename);
86+
}
87+
return this.filenameToHash[filename];
88+
};
89+
90+
CachingPrecompiler.prototype.generateHashForFile = function (filename) {
91+
var hash = {};
92+
hash[filename] = this.precompileFile(filename);
93+
return hash;
94+
};

lib/test-worker.js

+21-71
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
'use strict';
22
var path = require('path');
3+
var fs = require('fs');
34
var debug = require('debug')('ava');
4-
var pkgDir = require('pkg-dir').sync;
5-
var hasha = require('hasha');
6-
var cacha = require('cacha');
75
var sourceMapSupport = require('source-map-support');
86

97
var opts = JSON.parse(process.argv[2]);
108
var testPath = opts.file;
119

12-
var cache = cacha(path.join(pkgDir(path.dirname(testPath)), 'node_modules', '.cache', 'ava'));
13-
1410
if (debug.enabled) {
1511
// Forward the `time-require` `--sorted` flag.
1612
// Intended for internal optimization tests only.
@@ -38,73 +34,40 @@ sourceMapSupport.install({
3834
if (sourceMapCache[source]) {
3935
return {
4036
url: source,
41-
map: sourceMapCache[source]
37+
map: fs.readFileSync(sourceMapCache[source], 'utf8')
4238
};
4339
}
4440
}
4541
});
4642

47-
var requireFromString = require('require-from-string');
4843
var loudRejection = require('loud-rejection/api')(process);
4944
var serializeError = require('serialize-error');
5045
var send = require('./send');
51-
52-
// if generators are not supported, use regenerator
53-
var options = {
54-
sourceMaps: true
55-
};
46+
var installPrecompiler = require('require-precompiled');
47+
var cacheDir = opts.cacheDir;
5648

5749
// check if test files required ava and show error, when they didn't
5850
exports.avaRequired = false;
5951

60-
// try to load an input source map for the test file, in case the file was
61-
// already compiled once by the user
62-
var inputSourceMap = sourceMapSupport.retrieveSourceMap(testPath);
63-
if (inputSourceMap) {
64-
// source-map-support returns the source map as a json-encoded string, but
65-
// babel requires an actual object
66-
options.inputSourceMap = JSON.parse(inputSourceMap.map);
67-
}
68-
69-
// include test file
70-
var cachePath = hasha(cacheKey(testPath));
71-
var hashPath = cachePath + '_hash';
72-
73-
var prevHash = cache.getSync(hashPath, {encoding: 'utf8'});
74-
var currHash = hasha.fromFileSync(testPath);
75-
76-
if (prevHash === currHash) {
77-
var cached = JSON.parse(cache.getSync(cachePath));
78-
79-
sourceMapCache[testPath] = cached.map;
80-
requireFromString(cached.code, testPath, {
81-
appendPaths: module.paths
82-
});
83-
} else {
84-
var createEspowerPlugin = require('babel-plugin-espower/create');
85-
var babel = require('babel-core');
86-
87-
// initialize power-assert
88-
var powerAssert = createEspowerPlugin(babel, {
89-
patterns: require('./enhance-assert').PATTERNS
90-
});
91-
92-
options.presets = [require('babel-preset-stage-2'), require('babel-preset-es2015')];
93-
options.plugins = [powerAssert, require('babel-plugin-transform-runtime')];
52+
installPrecompiler(function (filename) {
53+
var precompiled = opts.precompiled[filename];
54+
if (precompiled) {
55+
sourceMapCache[filename] = path.join(cacheDir, precompiled + '.map');
56+
return fs.readFileSync(path.join(cacheDir, precompiled + '.js'), 'utf8');
57+
}
58+
return null;
59+
});
9460

95-
var transpiled = babel.transformFileSync(testPath, options);
61+
// Modules need to be able to find `babel-runtime`, which is nested in our node_modules w/ npm@2
62+
var nodeModulesDir = path.join(__dirname, '../node_modules');
63+
var oldNodeModulesPaths = module.constructor._nodeModulePaths;
64+
module.constructor._nodeModulePaths = function () {
65+
var ret = oldNodeModulesPaths.apply(this, arguments);
66+
ret.push(nodeModulesDir);
67+
return ret;
68+
};
9669

97-
cache.setSync(hashPath, currHash);
98-
cache.setSync(cachePath, JSON.stringify({
99-
code: transpiled.code,
100-
map: transpiled.map
101-
}));
102-
103-
sourceMapCache[testPath] = transpiled.map;
104-
requireFromString(transpiled.code, testPath, {
105-
appendPaths: module.paths
106-
});
107-
}
70+
require(testPath);
10871

10972
process.on('uncaughtException', function (exception) {
11073
send('uncaughtException', {exception: serializeError(exception)});
@@ -151,16 +114,3 @@ process.on('ava-teardown', function () {
151114
function exit() {
152115
send('teardown');
153116
}
154-
155-
function cacheKey(path) {
156-
var key = path;
157-
158-
key += require('../package.json').version;
159-
key += require('babel-core/package.json').version;
160-
key += require('babel-plugin-espower/package.json').version;
161-
key += require('babel-plugin-transform-runtime/package.json').version;
162-
key += require('babel-preset-stage-2/package.json').version;
163-
key += require('babel-preset-es2015/package.json').version;
164-
165-
return hasha(key);
166-
}

package.json

+8-4
Original file line numberDiff line numberDiff line change
@@ -85,44 +85,48 @@
8585
"babel-preset-stage-2": "^6.3.13",
8686
"babel-runtime": "^6.3.19",
8787
"bluebird": "^3.0.0",
88-
"cacha": "^1.0.3",
88+
"caching-transform": "^1.0.0",
8989
"chalk": "^1.0.0",
9090
"co-with-promise": "^4.6.0",
9191
"commondir": "^1.0.1",
92+
"convert-source-map": "^1.1.2",
9293
"core-assert": "^0.1.0",
9394
"debug": "^2.2.0",
9495
"deeper": "^2.1.0",
9596
"empower-core": "^0.2.0",
9697
"figures": "^1.4.0",
98+
"find-cache-dir": "^0.1.1",
9799
"fn-name": "^2.0.0",
98100
"globby": "^4.0.0",
99-
"hasha": "^2.0.2",
100101
"is-generator-fn": "^1.0.0",
101102
"is-observable": "^0.1.0",
102103
"is-promise": "^2.1.0",
103104
"log-update": "^1.0.2",
104105
"loud-rejection": "^1.2.0",
105106
"max-timeout": "^1.0.0",
107+
"md5-hex": "^1.2.0",
106108
"meow": "^3.6.0",
107109
"object-assign": "^4.0.1",
108110
"observable-to-promise": "^0.1.0",
109-
"pkg-dir": "^1.0.0",
110111
"plur": "^2.0.0",
111112
"power-assert-formatter": "^1.3.0",
112113
"power-assert-renderers": "^0.1.0",
113114
"pretty-ms": "^2.0.0",
114-
"require-from-string": "^1.1.0",
115+
"require-precompiled": "^0.1.0",
115116
"resolve-cwd": "^1.0.0",
116117
"serialize-error": "^1.1.0",
117118
"set-immediate-shim": "^1.0.1",
118119
"source-map-support": "^0.4.0",
120+
"strip-bom": "^2.0.0",
119121
"time-require": "^0.1.2",
122+
"unique-temp-dir": "^1.0.0",
120123
"update-notifier": "^0.6.0"
121124
},
122125
"devDependencies": {
123126
"coveralls": "^2.11.4",
124127
"delay": "^1.3.0",
125128
"get-stream": "^1.1.0",
129+
"rimraf": "^2.5.0",
126130
"nyc": "^5.1.0",
127131
"signal-exit": "^2.1.2",
128132
"sinon": "^1.17.2",

0 commit comments

Comments
 (0)