-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Transpile in main thread #390
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
var cachingTransform = require('caching-transform'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var md5Hex = require('md5-hex'); | ||
var stripBom = require('strip-bom'); | ||
|
||
module.exports = CachingPrecompiler; | ||
|
||
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, | ||
babelrc: false, | ||
inputSourceMap: sourceMap && sourceMap.toObject() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sure to include the |
||
}; | ||
} | ||
|
||
return function (code, filename, hash) { | ||
code = code.toString(); | ||
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 () { | ||
return cachingTransform({ | ||
factory: this._factory.bind(this), | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should probably salt with the Babel plugins too: https://github.com/sindresorhus/ava/pull/390/files#diff-b72e3e25f31d6b3c3e5f40b5991dc5f6L157 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those are just presets, and don't reflect the actual versions of the underlying plugins (they use caret ranges). While it won't break anything to salt with preset versions, it may be providing a false sense of security. @thejameskyle - Any thoughts on this? Last time I browsed the babel source, it seemed you were only including the version of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've replied to that thread: https://phabricator.babeljs.io/T6709#70444 @thejameskyle - Happy to contribute a PR to babel if you want. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's really no good way of caching based on plugins, your solution is full of holes. The current recommendation is for users to wipe out their cache when they change their config. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should default to caching off, and just use the "compile in main thread" optimization. That appears to give us 95% of the benefit anyways. |
||
})), | ||
ext: '.js', | ||
hash: this._hash.bind(this) | ||
}); | ||
}; | ||
|
||
CachingPrecompiler.prototype._hash = function (code, filename, salt) { | ||
var hash = md5Hex([code, filename, salt]); | ||
this.filenameToHash[filename] = hash; | ||
return hash; | ||
}; | ||
|
||
CachingPrecompiler.prototype.precompileFile = function (filename) { | ||
if (!this.filenameToHash[filename]) { | ||
this.transform(stripBom(fs.readFileSync(filename)), filename); | ||
} | ||
return this.filenameToHash[filename]; | ||
}; | ||
|
||
CachingPrecompiler.prototype.generateHashForFile = function (filename) { | ||
var hash = {}; | ||
hash[filename] = this.precompileFile(filename); | ||
return hash; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,12 @@ | ||
'use strict'; | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
var debug = require('debug')('ava'); | ||
var pkgDir = require('pkg-dir').sync; | ||
var hasha = require('hasha'); | ||
var cacha = require('cacha'); | ||
var sourceMapSupport = require('source-map-support'); | ||
|
||
var opts = JSON.parse(process.argv[2]); | ||
var testPath = opts.file; | ||
|
||
var cache = cacha(path.join(pkgDir(path.dirname(testPath)), 'node_modules', '.cache', 'ava')); | ||
|
||
if (debug.enabled) { | ||
// Forward the `time-require` `--sorted` flag. | ||
// Intended for internal optimization tests only. | ||
|
@@ -38,73 +34,40 @@ sourceMapSupport.install({ | |
if (sourceMapCache[source]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't modify this line, the only modification I made was the one discussed below (saving to disk in a different thread and loading here). |
||
return { | ||
url: source, | ||
map: sourceMapCache[source] | ||
map: fs.readFileSync(sourceMapCache[source], 'utf8') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
EDIT: Never mind. It's a diffrent process. But this feels like the wrong place for this logic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where should it go? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... I'm not sure. I guess it's fine. On Wed, Dec 30, 2015 at 7:03 PM James Talmage [email protected]
|
||
}; | ||
} | ||
} | ||
}); | ||
|
||
var requireFromString = require('require-from-string'); | ||
var loudRejection = require('loud-rejection/api')(process); | ||
var serializeError = require('serialize-error'); | ||
var send = require('./send'); | ||
|
||
// if generators are not supported, use regenerator | ||
var options = { | ||
sourceMaps: true | ||
}; | ||
var installPrecompiler = require('require-precompiled'); | ||
var cacheDir = opts.cacheDir; | ||
|
||
// 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 cachePath = hasha(cacheKey(testPath)); | ||
var hashPath = cachePath + '_hash'; | ||
|
||
var prevHash = cache.getSync(hashPath, {encoding: 'utf8'}); | ||
var currHash = hasha.fromFileSync(testPath); | ||
|
||
if (prevHash === currHash) { | ||
var cached = JSON.parse(cache.getSync(cachePath)); | ||
|
||
sourceMapCache[testPath] = cached.map; | ||
requireFromString(cached.code, testPath, { | ||
appendPaths: module.paths | ||
}); | ||
} else { | ||
var createEspowerPlugin = require('babel-plugin-espower/create'); | ||
var babel = require('babel-core'); | ||
|
||
// initialize power-assert | ||
var powerAssert = createEspowerPlugin(babel, { | ||
patterns: require('./enhance-assert').PATTERNS | ||
}); | ||
|
||
options.presets = [require('babel-preset-stage-2'), require('babel-preset-es2015')]; | ||
options.plugins = [powerAssert, require('babel-plugin-transform-runtime')]; | ||
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; | ||
}); | ||
|
||
var transpiled = babel.transformFileSync(testPath, options); | ||
// 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; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jamestalmage: Why not just modify Or, you know what? This is probably fine. But the bikeshed is to be blue. |
||
|
||
cache.setSync(hashPath, currHash); | ||
cache.setSync(cachePath, JSON.stringify({ | ||
code: transpiled.code, | ||
map: transpiled.map | ||
})); | ||
|
||
sourceMapCache[testPath] = transpiled.map; | ||
requireFromString(transpiled.code, testPath, { | ||
appendPaths: module.paths | ||
}); | ||
} | ||
require(testPath); | ||
|
||
process.on('uncaughtException', function (exception) { | ||
send('uncaughtException', {exception: serializeError(exception)}); | ||
|
@@ -151,16 +114,3 @@ process.on('ava-teardown', function () { | |
function exit() { | ||
send('teardown'); | ||
} | ||
|
||
function cacheKey(path) { | ||
var key = path; | ||
|
||
key += require('../package.json').version; | ||
key += require('babel-core/package.json').version; | ||
key += require('babel-plugin-espower/package.json').version; | ||
key += require('babel-plugin-transform-runtime/package.json').version; | ||
key += require('babel-preset-stage-2/package.json').version; | ||
key += require('babel-preset-es2015/package.json').version; | ||
|
||
return hasha(key); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jamestalmage: What we do everywhere else is just invoke ourself with
new
and return it. Maybe switch to that?:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah sure. Probably worth matching style.
That said, after doing it the way you suggest for a long time, I am starting to think it's a mistake. ES2015 classes throw when function called, so magic auto-
new
ing is not future proof. Also, it introduces an easy to overlook failure point when refactoring the function signature. I know I have forgotten to update that statement inside the if condition before.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer being explicit and use
new
for classes. ES2015 seems to agree with me.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, changed it back to the original behavior (throw if they forget to use new).