Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Babel transforms to the main process. #349

Closed
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5a66e9d
move babel to main thread
jamestalmage Dec 16, 2015
5507a7d
WIP - fix tests. one still skipped. not the prettiest solution either
jamestalmage Dec 16, 2015
8cb67d1
use latest nyc for proper require extension.
jamestalmage Dec 22, 2015
ce33e39
travis.yml: simplify piping coverage to coveralls
jamestalmage Dec 22, 2015
9abb688
add caching
jamestalmage Dec 22, 2015
0d88ba8
use `rimraf` so pretest is cross-platform
jamestalmage Dec 22, 2015
07f27e4
don't re-require babel plugins every time buildOptions is called.
jamestalmage Dec 23, 2015
4970e6b
swap `rimraf` for `del-cli`
jamestalmage Dec 23, 2015
be4497f
remove unnecessary nyc config
jamestalmage Dec 23, 2015
748acf4
rename `assign` -> `objectAssign` to match dependency name.
jamestalmage Dec 23, 2015
4db22da
delete pointless test
jamestalmage Dec 23, 2015
54a8051
remove unused dependencies
jamestalmage Dec 23, 2015
ef4730f
compute best location for `.cache` dir.
jamestalmage Dec 23, 2015
6c8f11d
test-transformer -> caching-precompiler
jamestalmage Dec 23, 2015
c710b64
move cacheDir logic to `caching-precompiler`
jamestalmage Dec 24, 2015
ca00ce9
add `--no-cache` CLI flag (squash this - always true)
jamestalmage Dec 24, 2015
9c893e8
actually enable `--no-cache` (squash with above).
jamestalmage Dec 24, 2015
d6a8f7a
use `classic` reporter in `test-win`
jamestalmage Dec 24, 2015
32df154
salt the hash
jamestalmage Dec 24, 2015
a3174cf
bump caching-transform
jamestalmage Dec 24, 2015
e80f8d3
fix npm@2: append ava's node_modules directory to _nodeModulePaths()
jamestalmage Dec 24, 2015
200d194
use md5hex now that is supports arrays of values.
jamestalmage Dec 24, 2015
fc48e56
`noCache` -> `cache`
jamestalmage Dec 24, 2015
ca9f7a4
bump caching-precompiler to 1.0.0, and strip-bom sources.
jamestalmage Dec 25, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ node_js:
before_install:
- 'npm i -g npm@latest'
after_success:
- '[ -z "$COVERALLS_REPO_TOKEN" ] && tap --coverage-report=text-lcov | ./node_modules/.bin/coveralls'
- 'cat ./coverage/lcov.info | ./node_modules/.bin/coveralls'
11 changes: 10 additions & 1 deletion api.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ var figures = require('figures');
var globby = require('globby');
var chalk = require('chalk');
var resolveCwd = require('resolve-cwd');
var objectAssign = require('object-assign');
var fork = require('./lib/fork');
var formatter = require('./lib/enhance-assert').formatter();
var CachingPrecompiler = require('./lib/caching-precompiler');

function Api(files, options) {
if (!(this instanceof Api)) {
Expand Down Expand Up @@ -42,7 +44,10 @@ util.inherits(Api, EventEmitter);
module.exports = Api;

Api.prototype._runFile = function (file) {
return fork(file, this.options)
var options = objectAssign({}, this.options, {
precompiled: this.precompiler.generateHashForFile(file)
});
return fork(file, options)
.on('stats', this._handleStats)
.on('test', this._handleTest)
.on('unhandledRejections', this._handleRejections)
Expand Down Expand Up @@ -135,6 +140,10 @@ Api.prototype.run = function () {
if (files.length === 0) {
return Promise.reject(new Error('Couldn\'t find any files to test'));
}
var noCache = self.options.noCache;
var cacheDir = (!noCache && CachingPrecompiler.findCacheDir(files)) || CachingPrecompiler.findUniqueTempDir();
self.options.cacheDir = cacheDir;
self.precompiler = new CachingPrecompiler(cacheDir);

self.fileCount = files.length;

Expand Down
4 changes: 3 additions & 1 deletion cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var cli = meow([
' --init Add AVA to your project',
' --fail-fast Stop after first test failure',
' --serial Run tests serially',
' --no-cache Disable the transpiler cache',
' --require Module to preload (Can be repeated)',
' --tap Generate TAP output',
'',
Expand Down Expand Up @@ -77,7 +78,8 @@ if (cli.flags.tap) {
var api = new Api(cli.input, {
failFast: cli.flags.failFast,
serial: cli.flags.serial,
require: arrify(cli.flags.require)
require: arrify(cli.flags.require),
noCache: cli.flags.cache === false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noCache => cache. I don't like double negatives.

});

api.on('test', function (test) {
Expand Down
53 changes: 23 additions & 30 deletions lib/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,59 +20,52 @@ var globals = require('./globals');

var sourceMapCache = Object.create(null);

var fs = require('fs');
var sourceMapSupport = require('source-map-support');
sourceMapSupport.install({
handleUncaughtExceptions: false,
retrieveSourceMap: function (source) {
if (sourceMapCache[source]) {
return {
url: source,
map: sourceMapCache[source]
map: fs.readFileSync(sourceMapCache[source], 'utf8')
};
}
}
});

var createEspowerPlugin = require('babel-plugin-espower/create');
var requireFromString = require('require-from-string');
var loudRejection = require('loud-rejection/api')(process);
var serializeError = require('serialize-error');
var babel = require('babel-core');
var send = require('./send');
var installPrecompiler = require('require-precompiled');
var path = require('path');
var cacheDir = opts.cacheDir;

var testPath = opts.file;

// initialize power-assert
var powerAssert = createEspowerPlugin(babel, {
patterns: require('./enhance-assert').PATTERNS
});

// if generators are not supported, use regenerator
var options = {
presets: [require('babel-preset-stage-2'), require('babel-preset-es2015')],
plugins: [powerAssert, require('babel-plugin-transform-runtime')],
sourceMaps: true
};

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

// try to load an input source map for the test file, in case the file was
// already compiled once by the user
var inputSourceMap = sourceMapSupport.retrieveSourceMap(testPath);
if (inputSourceMap) {
// source-map-support returns the source map as a json-encoded string, but
// babel requires an actual object
options.inputSourceMap = JSON.parse(inputSourceMap.map);
}

// include test file
var transpiled = babel.transformFileSync(testPath, options);
sourceMapCache[testPath] = transpiled.map;
requireFromString(transpiled.code, testPath, {
appendPaths: module.paths
installPrecompiler(function (filename) {
var precompiled = opts.precompiled[filename];
if (precompiled) {
sourceMapCache[filename] = path.join(cacheDir, precompiled + '.map');
return fs.readFileSync(path.join(cacheDir, precompiled + '.js'), 'utf8');
}
return null;
});

// Modules need to be able to find `babel-runtime`, which is nested in our node_modules w/ npm@2
var nodeModulesDir = path.join(__dirname, '../node_modules');
var oldNodeModulesPaths = module.constructor._nodeModulePaths;
module.constructor._nodeModulePaths = function () {
var ret = oldNodeModulesPaths.apply(this, arguments);
ret.push(nodeModulesDir);
return ret;
};

require(testPath);

process.on('uncaughtException', function (exception) {
send('uncaughtException', {exception: serializeError(exception)});
});
Expand Down
117 changes: 117 additions & 0 deletions lib/caching-precompiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
var cachingTransform = require('caching-transform');
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var commonDir = require('commondir');
var pkgDir = require('pkg-dir');
var osTmpdir = require('os-tmpdir');

module.exports = CachingPrecompiler;
module.exports.findCacheDir = findCacheDir;
module.exports.findUniqueTempDir = findUniqueTempDir;

function findCacheDir(files) {
var projectBaseDirectory = pkgDir.sync(commonDir(files));
return path.join(projectBaseDirectory, 'node_modules', '.cache', 'ava');
}

function findUniqueTempDir() {
return path.join(
osTmpdir(),
Math.random().toString(35).substr(2, 30)
);
}

function CachingPrecompiler(cacheDir) {
if (!(this instanceof CachingPrecompiler)) {
throw new Error('CachingPrecompiler must be called with new');
}
this.cacheDir = cacheDir;
this.filenameToHash = {};
this.transform = this._createTransform();
}

CachingPrecompiler.prototype._factory = function (cacheDir) {
// This factory method is only called once per process, and only as needed, to defer loading expensive dependencies.
var babel = require('babel-core');
var convertSourceMap = require('convert-source-map');
var presetStage2 = require('babel-preset-stage-2');
var presetES2015 = require('babel-preset-es2015');
var transformRuntime = require('babel-plugin-transform-runtime');

var powerAssert = this._createEspowerPlugin(babel);

function buildOptions(filename, code) {
// Extract existing source maps from the code.
var sourceMap = convertSourceMap.fromSource(code) || convertSourceMap.fromMapFileSource(code, path.dirname(filename));

return {
presets: [presetStage2, presetES2015],
plugins: [powerAssert, transformRuntime],
filename: filename,
sourceMaps: true,
ast: false,
inputSourceMap: sourceMap && sourceMap.toObject()
};
}

return function (code, filename, hash) {
var options = buildOptions(filename, code);
var result = babel.transform(code, options);
var mapFile = path.join(cacheDir, hash + '.map');
fs.writeFileSync(mapFile, JSON.stringify(result.map));
return result.code;
};
};

CachingPrecompiler.prototype._createEspowerPlugin = function (babel) {
var createEspowerPlugin = require('babel-plugin-espower/create');
var enhanceAssert = require('./enhance-assert');

// initialize power-assert
return createEspowerPlugin(babel, {
patterns: enhanceAssert.PATTERNS
});
};

CachingPrecompiler.prototype._createTransform = function () {
var self = this;

return cachingTransform({
factory: this._factory.bind(this),
cacheDir: self.cacheDir,
salt: JSON.stringify({
'babel-plugin-espower': require('babel-plugin-espower/package.json').version,
'ava': require('../package.json').version,
'babel-core': require('babel-core/package.json').version
}),
ext: '.js',
hash: this._hash.bind(this)
});
};

CachingPrecompiler.prototype._hash = function (code, filename, salt) {
var hash = crypto
.createHash('md5')
.update(code, 'utf8')
.update(filename || 'unknown file', 'utf8')
.update(salt || '', 'utf8')
.digest('hex');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


this.filenameToHash[filename] = hash;

return hash;
};

CachingPrecompiler.prototype.precompileFile = function (filename) {
if (!this.filenameToHash[filename]) {
this.transform(fs.readFileSync(filename, 'utf8'), filename);
}
return this.filenameToHash[filename];
};

CachingPrecompiler.prototype.generateHashForFile = function (filename) {
var hash = {};
hash[filename] = this.precompileFile(filename);
return hash;
};
23 changes: 11 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
"node": ">=0.10.0"
},
"scripts": {
"test": "xo && tap --coverage --reporter=spec --timeout=150 test/*.js",
"test-win": "tap --reporter=spec --timeout=150 test/*.js",
"coverage": "tap --coverage-report=lcov"
"pretest": "de ./node_modules/.cache",
"test": "xo && nyc --reporter=lcov ./node_modules/.bin/tap --no-cov --timeout=150 test/*.js",
"test-win": "npm run pretest && tap -b --no-cov --timeout=150 --reporter=classic test/*.js"
},
"files": [
"index.js",
Expand Down Expand Up @@ -88,8 +88,11 @@
"babel-preset-stage-2": "^6.3.13",
"babel-runtime": "^6.3.19",
"bluebird": "^3.0.0",
"caching-transform": "0.0.4",
"chalk": "^1.0.0",
"co-with-promise": "^4.6.0",
"commondir": "^1.0.1",
"convert-source-map": "^1.1.2",
"core-assert": "^0.1.0",
"debug": "^2.2.0",
"deeper": "^2.1.0",
Expand All @@ -105,11 +108,13 @@
"meow": "^3.6.0",
"object-assign": "^4.0.1",
"observable-to-promise": "^0.1.0",
"os-tmpdir": "^1.0.1",
"pkg-dir": "^1.0.0",
"plur": "^2.0.0",
"power-assert-formatter": "^1.3.0",
"power-assert-renderers": "^0.1.0",
"pretty-ms": "^2.0.0",
"require-from-string": "^1.1.0",
"require-precompiled": "^0.1.0",
"resolve-cwd": "^1.0.0",
"serialize-error": "^1.1.0",
"set-immediate-shim": "^1.0.1",
Expand All @@ -120,22 +125,16 @@
},
"devDependencies": {
"coveralls": "^2.11.4",
"del-cli": "^0.1.2",
"hook-std": "^0.1.0",
"nyc": "^5.0.1",
"signal-exit": "^2.1.2",
"sinon": "^1.17.2",
"source-map-fixtures": "^0.3.0",
"tap": "^2.2.1",
"xo": "*",
"zen-observable": "^0.1.6"
},
"config": {
"nyc": {
"exclude": [
"node_modules[/\\\\]",
"test[/\\\\]"
]
}
},
"xo": {
"ignore": [
"cli.js"
Expand Down
24 changes: 11 additions & 13 deletions test/fork.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
'use strict';
var path = require('path');
var test = require('tap').test;
var fork = require('../lib/fork.js');
var _fork = require('../lib/fork.js');
var CachingPrecompiler = require('../lib/caching-precompiler');
var cacheDir = path.join(__dirname, '../node_modules/.cache/ava');
var precompiler = new CachingPrecompiler(cacheDir);

function fixture(name) {
return path.join(__dirname, 'fixture', name);
}

function fork(testPath) {
return _fork(testPath, {
cacheDir: cacheDir,
precompiled: precompiler.generateHashForFile(testPath)
});
}

test('emits test event', function (t) {
t.plan(1);

Expand All @@ -33,18 +43,6 @@ test('resolves promise with tests info', function (t) {
});
});

test('rejects on error and streams output', function (t) {
t.plan(2);

fork(fixture('broken.js'))
.run()
.catch(function (err) {
t.ok(err);
t.match(err.message, /exited with a non-zero exit code: \d/);
t.end();
});
});

test('exit after tests are finished', function (t) {
t.plan(2);

Expand Down
12 changes: 11 additions & 1 deletion test/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@
var path = require('path');
var test = require('tap').test;
var Runner = require('../lib/runner');
var fork = require('../lib/fork');
var _fork = require('../lib/fork');
var CachingPrecompiler = require('../lib/caching-precompiler');
var cacheDir = path.join(__dirname, '../node_modules/.cache/ava');
var precompiler = new CachingPrecompiler(cacheDir);

function fork(testPath) {
return _fork(testPath, {
cacheDir: cacheDir,
precompiled: precompiler.generateHashForFile(testPath)
});
}

test('before', function (t) {
t.plan(1);
Expand Down