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

Added support for babel config under package.json/ava key (Fixes #448) #573

Closed
wants to merge 12 commits into from
2 changes: 1 addition & 1 deletion api.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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') {
Copy link
Member

Choose a reason for hiding this comment

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

Rather than checking for a falsy value here you could define the default in cli.js. Change this line to:

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

This will also save you a test 😎

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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"update-notifier": "^0.6.0"
},
"devDependencies": {
"babel-cli": "^6.6.4",
Copy link
Member

Choose a reason for hiding this comment

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

Why this dependency?

"cli-table2": "^0.1.9",
"coveralls": "^2.11.4",
"delay": "^1.3.0",
Expand Down
8 changes: 6 additions & 2 deletions profile.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Why transform-runtime?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we don't include transform-runtime, users will have to import 'babel-polyfill' at the top of each test file if they want to use async functions. This has not changed in the pull request. It was like this before. This is only a documentation change.

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
Copy link
Member

Choose a reason for hiding this comment

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

Should pass 'default' here (and below).

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();
});
Loading