-
-
Notifications
You must be signed in to change notification settings - Fork 73
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
Parallel babel transpilation #114
Conversation
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.
after reviewing this and the babel-persistent-filter pr, i feel this is the right direction.
Some open questions:
- where do we do throttling?
- how is the workflow \w errors (is the output mangled?)
module.exports = { | ||
name: 'amd-name-resolver', | ||
|
||
build: function(options) { |
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.
build(options) {
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
module.exports = { | ||
name: 'transform-es2015-block-scoping', | ||
|
||
build: function(options) { |
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.
^
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
module.exports = { | ||
name: 'transform-strict-mode', | ||
|
||
build: function(options) { |
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.
^
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
index.js
Outdated
@@ -25,16 +33,44 @@ function replaceExtensions(extensionsRegex, name) { | |||
return name; | |||
} | |||
|
|||
function pluginCanBeParallelized(plugin) { |
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.
lets add a unit test for each of these helper methods
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 - I moved this stuff to a separate file as well to keep index.js a little cleaner
index.js
Outdated
return transpiler.transform(string, options); | ||
if (transformIsParallelizable(options)) { | ||
// send the job to the worker pool | ||
return new Promise(function(resolve, reject) { |
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.
Can we extract this to its own file, so we can test this branch in isolation? Parallel/worker stuff is
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 I will extract this to a separate file
index.js
Outdated
return transpiler.transform(string, options); | ||
if (transformIsParallelizable(options)) { | ||
// send the job to the worker pool | ||
return new Promise(function(resolve, reject) { |
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.
this promise wrapper is not required.
return pool.exec('transform', [string, options]).catch(err) {
if (err.name === 'Error' && (err.message === 'Worker terminated unexpectedly' ||
err.message === 'Worker is terminated')) {
// retry one time if it's a worker error
resolve(pool.exec('transform', [string, options]));
} else {
throw err;
}
});
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.
good call - simplifying that
index.js
Outdated
resolve(result); | ||
}, | ||
function onRejected(err) { | ||
if (err.name === 'Error' && (err.message === 'Worker terminated unexpectedly' || |
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.
err
may be undefined or null or a number you should likely add a guard.
typeof err === 'object' && err !== null
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.
good catch
lib/parallel-api.js
Outdated
|
||
|
||
module.exports = { | ||
pluginCanBeParallelized: function(plugin) { |
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.
concise function pluginCanBeParallelized() {
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
lib/parallel-api.js
Outdated
module.exports = { | ||
pluginCanBeParallelized: function(plugin) { | ||
return typeof plugin === 'string' || | ||
(Object.prototype.toString.call(plugin) === '[object Array]' && |
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.
Array.isArray(plugin)
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.
awesome, I knew there had to be a better way to check that
lib/parallel-api.js
Outdated
|
||
resolveModuleIsParallelizable: function(resolveModule) { | ||
return resolveModule === undefined || | ||
(Object.prototype.toString.call(resolveModule) === '[object Array]' && |
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.
Array.isArray(resolveModule)
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
lib/parallel-api.js
Outdated
|
||
if (options.resolveModuleSource !== undefined) { | ||
// convert to work with Babel, if needed | ||
if (Object.prototype.toString.call(options.resolveModuleSource) === '[object Array]') { |
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.
Array.isArray(options.resolveModuleSource)
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
lib/parallel-api.js
Outdated
transformOptions: function(options) { | ||
var newOptions = options; | ||
|
||
if (options.resolveModuleSource !== undefined) { |
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.
nested if seems redundant.
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
lib/parallel-api.js
Outdated
if (options.plugins !== undefined) { | ||
// convert plugins to work with Babel, if needed | ||
newOptions.plugins = options.plugins.map(function(plugin) { | ||
if (Object.prototype.toString.call(plugin) === '[object Array]') { |
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.
Array.isArray(plugin)
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
lib/parallel-api.js
Outdated
if (typeof err === 'object' && err !== null && | ||
(err.message === 'Worker terminated unexpectedly' || err.message === 'Worker is terminated')) { | ||
// retry one time if it's a worker error | ||
return pool.exec('transform', [string, options]); |
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 don't believe we should retry (this will just slow things down). The work we doing isn't reliant on external resources that may be a partial failure, we should just assume one attempt === fatal.
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.
Sounds good to me, and this makes things simpler
lib/parallel-api.js
Outdated
}); | ||
} | ||
else { | ||
return Promise.resolve(transpiler.transform(string, this.transformOptions(options))); |
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.
new Promise(resolve => resolve(transpiler.transform(string, this.transformOptions(options));
Otherwise if transpiler.transform
throws synchronously, we will break the transformString
signature by also throwing synchronously.
In general, if you function returns a promise it should always return a promise. Otherwise every caller must wrap in try/catch or promise constructor etc. So by wrapping at the "leaves" of a program, when we invoke external code that is not promise aware, we hide the rest of our program from these inconsistencies.
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.
Good point
based on our other discussion broccolijs/broccoli-persistent-filter#111 (comment) it seems like this PR will require throttling. I helped @2cflor with this for broccoli-eyeglass and created a wrapper around |
due to concurrency changes in babel-persistent-filter, the order of file processing and errors is non-deterministic also, fix pluralization in error message :)
concise methods Array.isArray() remove redundant nested if consistently return Promise
I added throttling/concurrency using There is already some throttling with this PR, due to how the |
} | ||
|
||
function transformString(string, options) { | ||
if (transformIsParallelizable(options)) { |
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.
we should debug log which branch we took.
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, adding debug logging like in broccoli-persistent-filter
lib/worker.js
Outdated
// transpile the input string, using the input options | ||
function transform(string, options) { | ||
return new Promise(function(resolve) { | ||
var result = transpiler.transform(string, ParallelApi.transformOptions(options)); |
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.
would it make more sense to name this ParallelApi.deserializeOptions
?
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 think that makes sense, to match the other renaming
lib/parallel-api.js
Outdated
|
||
function transformString(string, options) { | ||
if (transformIsParallelizable(options)) { | ||
return pool.exec('transform', [string, objectifyCallbacks(options)]); |
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.
would it make more sense to name objectifyCallbacks
as serializeOptions
?
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 I think that's clearer
can we also document the JOBS env var |
To test a against a decent sized project, I linked this into https://github.com/TryGhost/Ghost (which is using babel 6) and ran it there with no problems, same output as before. |
released as v6.1.0 🎉 |
This modifies the
options.resolveModuleSource
andoptions.plugins
APIs to be able to run babel transpilation in parallel using a worker pool. This API looks like:That file that is the 2nd item in the array is required in the worker process. That file must define a function
build()
that returns the plugin or resolver to use. Thebuild()
function will be passed the options object that is the 3rd item from that array. Like so:For these operations to actually run in parallel requires broccolijs/broccoli-persistent-filter#111 to be committed, so that the transpilations can happen concurrently.