Skip to content

Commit

Permalink
Close PR #573: Added support for babel config under package.json/ava key
Browse files Browse the repository at this point in the history
Fixes #448
  • Loading branch information
Stephen Sorensen authored and novemberborn committed Mar 7, 2016
1 parent 3fcd492 commit a03f826
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 22 deletions.
2 changes: 1 addition & 1 deletion api.js
Expand Up @@ -187,7 +187,7 @@ Api.prototype.run = function (files) {
uniqueTempDir();

self.options.cacheDir = cacheDir;
self.precompiler = new CachingPrecompiler(cacheDir);
self.precompiler = new CachingPrecompiler(cacheDir, self.options.babelConfig);
self.fileCount = files.length;
self.base = path.relative('.', commonPathPrefix(files)) + path.sep;

Expand Down
9 changes: 7 additions & 2 deletions cli.js
Expand Up @@ -35,7 +35,11 @@ var Api = require('./api');
// Bluebird specific
Promise.longStackTraces();

var conf = pkgConf.sync('ava');
var conf = pkgConf.sync('ava', {
defaults: {
babel: 'default'
}
});

var cli = meow([
'Usage',
Expand Down Expand Up @@ -102,7 +106,8 @@ var api = new Api({
require: arrify(cli.flags.require),
cacheEnabled: cli.flags.cache !== false,
explicitTitles: cli.flags.watch,
match: arrify(cli.flags.match)
match: arrify(cli.flags.match),
babelConfig: conf.babel
});

var reporter;
Expand Down
40 changes: 27 additions & 13 deletions lib/caching-precompiler.js
Expand Up @@ -3,20 +3,21 @@ var path = require('path');
var cachingTransform = require('caching-transform');
var md5Hex = require('md5-hex');
var stripBom = require('strip-bom');
var objectAssign = require('object-assign');

module.exports = CachingPrecompiler;

function CachingPrecompiler(cacheDir) {
function CachingPrecompiler(cacheDir, babelConfig) {
if (!(this instanceof CachingPrecompiler)) {
throw new TypeError('Class constructor CachingPrecompiler cannot be invoked without \'new\'');
}

this.cacheDir = cacheDir;
this.filenameToHash = {};
this.transform = this._createTransform();
this.transform = this._createTransform(babelConfig);
}

CachingPrecompiler.prototype._factory = function (cacheDir) {
CachingPrecompiler.prototype._factory = function (babelConfig, 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');
Expand All @@ -30,15 +31,27 @@ CachingPrecompiler.prototype._factory = function (cacheDir) {
// 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],
var options = {babelrc: false};

if (!babelConfig || babelConfig === 'default') {
objectAssign(options, {presets: [presetStage2, presetES2015]});
} else if (babelConfig === 'inherit') {
objectAssign(options, {babelrc: true});
} else {
objectAssign(options, babelConfig);
}

objectAssign(options, {
inputSourceMap: sourceMap && sourceMap.toObject(),
filename: filename,
sourceMaps: true,
ast: false,
babelrc: false,
inputSourceMap: sourceMap && sourceMap.toObject()
};
ast: false
});

options.plugins = options.plugins || [];
options.plugins.push(powerAssert, transformRuntime);

return options;
}

return function (code, filename, hash) {
Expand All @@ -61,14 +74,15 @@ CachingPrecompiler.prototype._createEspowerPlugin = function (babel) {
});
};

CachingPrecompiler.prototype._createTransform = function () {
CachingPrecompiler.prototype._createTransform = function (babelConfig) {
return cachingTransform({
factory: this._factory.bind(this),
factory: this._factory.bind(this, babelConfig),
cacheDir: this.cacheDir,
salt: new Buffer(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
'babel-core': require('babel-core/package.json').version,
'babelConfig': babelConfig
})),
ext: '.js',
hash: this._hash.bind(this)
Expand Down
8 changes: 6 additions & 2 deletions profile.js
Expand Up @@ -20,7 +20,11 @@ globals.setTimeout = setTimeout.bind(null);
globals.clearTimeout = clearTimeout.bind(null);

Promise.longStackTraces();
var conf = pkgConf.sync('ava');
var conf = pkgConf.sync('ava', {
defaults: {
babel: 'default'
}
});

// Define a minimal set of options from the main CLI.
var cli = meow([
Expand Down Expand Up @@ -63,7 +67,7 @@ var opts = {
require: arrify(cli.flags.require),
tty: false,
cacheDir: cacheDir,
precompiled: new CachingPrecompiler(cacheDir).generateHashForFile(file)
precompiled: new CachingPrecompiler(cacheDir, conf.babel).generateHashForFile(file)
};

var events = new EventEmitter();
Expand Down
58 changes: 57 additions & 1 deletion readme.md
Expand Up @@ -154,7 +154,8 @@ All of the CLI options can be configured in the `ava` section of your `package.j
"tap": true,
"require": [
"babel-register"
]
],
"babel": "inherit"
}
}
```
Expand Down Expand Up @@ -423,6 +424,61 @@ AVA comes with builtin support for ES2015 through [Babel 6](https://babeljs.io).

AVA includes typings for TypeScript. You have to setup transpilation yourself. When you set `module` to `commonjs` in your `tsconfig.json` file, TypeScript will automatically find the type definitions for AVA. You should set `target` to `es2015` to use Promises and async functions.

### Babel Configuration for Test Scripts

If you want to customize the babel transpiler for test files, you can do so by adding a `"babel"` key to the `ava` section in your `package.json` file.

```json
{
"ava": {
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
]
}
},
}
```

In addition to specifying a custom Babel config, you can also use the special `"inherit"` keyword. When you do this, AVA will allow tests to be transpiled using the configuration defined in your `.babelrc` file or in package.json/babel. This way, your test files will be transpiled using the same options as your source files, but you won't have to define the options twice.

```json
{
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
]
},
"ava": {
"babel": "inherit",
},
}
```

Note: When configuring Babel for tests manually, the espower and transform-runtime plugins will be
added for you.

## Default Babel Configuration for Test Scripts

If you don't explicitly configure Babel for your tests using the `"babel"` key in package.json, your tests will be transpiled using AVA's default Babel configuration, which is as follows:

```json
{
"presets": [
"es2015",
"stage-0",
],
"plugins": [
"espower",
"transform-runtime"
]
}
```

#### Transpiling Imported Modules

AVA currently only transpiles the tests you ask it to run. *It will not transpile modules you ```import``` from outside of the test.* While there are valid reasons for taking this approach, it may not be what you expect!
Expand Down
31 changes: 31 additions & 0 deletions test/api.js
Expand Up @@ -5,6 +5,7 @@ var rimraf = require('rimraf');
var fs = require('fs');
var test = require('tap').test;
var Api = require('../api');
var testDoublerPlugin = require('./fixture/babel-plugin-test-doubler');

test('must be called with new', function (t) {
t.throws(function () {
Expand Down Expand Up @@ -684,3 +685,33 @@ test('verify test count', function (t) {
t.is(api.todoCount, 1);
});
});

test('Custom Babel Plugin Support', function (t) {
t.plan(1);

var api = new Api({
babelConfig: {
presets: ['es2015', 'stage-2'],
plugins: [testDoublerPlugin]
}
});

api.run([path.join(__dirname, 'fixture/es2015.js')])
.then(
function () {
t.is(api.passCount, 2);
},
t.threw
);
});

test('Default babel config doesn\'t use .babelrc', function (t) {
t.plan(1);

var api = new Api();

return api.run([path.join(__dirname, 'fixture/babelrc/test.js')])
.then(function () {
t.is(api.passCount, 1);
});
});
75 changes: 72 additions & 3 deletions test/caching-precompiler.js
Expand Up @@ -3,6 +3,9 @@ var fs = require('fs');
var path = require('path');
var test = require('tap').test;
var uniqueTempDir = require('unique-temp-dir');
var sinon = require('sinon');
var babel = require('babel-core');
var transformRuntime = require('babel-plugin-transform-runtime');

var CachingPrecompiler = require('../lib/caching-precompiler');

Expand All @@ -18,24 +21,26 @@ function endsWithMap(filename) {
return /\.js$/.test(filename);
}

sinon.spy(babel, 'transform');

test('creation with new', function (t) {
var tempDir = uniqueTempDir();
var precompiler = new CachingPrecompiler(tempDir);
var precompiler = new CachingPrecompiler(tempDir, null);
t.is(precompiler.cacheDir, tempDir);
t.end();
});

test('must be called with new', function (t) {
t.throws(function () {
var cachingPrecompiler = CachingPrecompiler;
cachingPrecompiler(uniqueTempDir());
cachingPrecompiler(uniqueTempDir(), null);
}, {message: 'Class constructor CachingPrecompiler cannot be invoked without \'new\''});
t.end();
});

test('adds files and source maps to the cache directory as needed', function (t) {
var tempDir = uniqueTempDir();
var precompiler = new CachingPrecompiler(tempDir);
var precompiler = new CachingPrecompiler(tempDir, null);

t.false(fs.existsSync(tempDir), 'cache directory is not created before it is needed');

Expand All @@ -48,3 +53,67 @@ test('adds files and source maps to the cache directory as needed', function (t)
t.is(files.filter(endsWithMap).length, 1, 'one .map file is saved to the cache');
t.end();
});

test('uses default babel options when babelConfig === "default"', function (t) {
var tempDir = uniqueTempDir();
var precompiler = new CachingPrecompiler(tempDir, 'default');
babel.transform.reset();

precompiler.precompileFile(fixture('es2015.js'));

t.true(babel.transform.calledOnce);
var options = babel.transform.firstCall.args[1];

t.true('filename' in options);
t.true(options.sourceMaps);
t.false(options.ast);
t.true('inputSourceMap' in options);
t.false(options.babelrc);
t.true(Array.isArray(options.presets));
t.true(Array.isArray(options.plugins));
t.end();
});

test('allows babel config from package.json/babel when babelConfig === "inherit"', function (t) {
var tempDir = uniqueTempDir();
var precompiler = new CachingPrecompiler(tempDir, 'inherit');
babel.transform.reset();

precompiler.precompileFile(fixture('es2015.js'));

t.true(babel.transform.calledOnce);
var options = babel.transform.firstCall.args[1];

t.true('filename' in options);
t.true(options.sourceMaps);
t.false(options.ast);
t.true('inputSourceMap' in options);
t.true(options.babelrc);
t.end();
});

test('uses babelConfig for babel options when babelConfig is an object', function (t) {
var tempDir = uniqueTempDir();
var customPlugin = sinon.stub().returns({visitor: {}});
var powerAssert = sinon.stub().returns({visitor: {}});
var precompiler = new CachingPrecompiler(tempDir, {
presets: ['stage-2', 'es2015'],
plugins: [customPlugin]
});
sinon.stub(precompiler, '_createEspowerPlugin').returns(powerAssert);
babel.transform.reset();

precompiler.precompileFile(fixture('es2015.js'));

t.true(babel.transform.calledOnce);
var options = babel.transform.firstCall.args[1];

t.true('filename' in options);
t.true(options.sourceMaps);
t.false(options.ast);
t.true('inputSourceMap' in options);
t.false(options.babelrc);
t.same(options.presets, ['stage-2', 'es2015']);
t.same(options.plugins, [customPlugin, powerAssert, transformRuntime]);
t.end();
});

0 comments on commit a03f826

Please sign in to comment.