2015-03-23 18:48:02 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
2015-02-20 04:10:52 +00:00
|
|
|
'use strict';
|
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
const ModuleTransport = require('../lib/ModuleTransport');
|
|
|
|
const Promise = require('promise');
|
|
|
|
const declareOpts = require('../lib/declareOpts');
|
|
|
|
const fs = require('fs');
|
2016-01-08 16:34:52 +00:00
|
|
|
const temp = require('temp');
|
2015-08-13 02:04:54 +00:00
|
|
|
const util = require('util');
|
|
|
|
const workerFarm = require('worker-farm');
|
2015-09-29 23:06:42 +00:00
|
|
|
const debug = require('debug')('ReactNativePackager:JStransformer');
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
const readFile = Promise.denodeify(fs.readFile);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-08-19 23:35:52 +00:00
|
|
|
// Avoid memory leaks caused in workers. This number seems to be a good enough number
|
|
|
|
// to avoid any memory leak while not slowing down initial builds.
|
|
|
|
// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more.
|
|
|
|
const MAX_CALLS_PER_WORKER = 600;
|
|
|
|
|
|
|
|
// Worker will timeout if one of the callers timeout.
|
2016-01-06 15:14:20 +00:00
|
|
|
const DEFAULT_MAX_CALL_TIME = 300000;
|
2015-08-19 23:35:52 +00:00
|
|
|
|
2015-08-31 16:40:46 +00:00
|
|
|
// How may times can we tolerate failures from the worker.
|
2016-01-06 15:14:20 +00:00
|
|
|
const MAX_RETRIES = 2;
|
2015-08-31 16:40:46 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
const validateOpts = declareOpts({
|
2015-02-24 22:22:13 +00:00
|
|
|
projectRoots: {
|
|
|
|
type: 'array',
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
blacklistRE: {
|
|
|
|
type: 'object', // typeof regex is object
|
|
|
|
},
|
|
|
|
polyfillModuleNames: {
|
|
|
|
type: 'array',
|
|
|
|
default: [],
|
|
|
|
},
|
|
|
|
transformModulePath: {
|
|
|
|
type:'string',
|
2015-02-27 18:23:54 +00:00
|
|
|
required: false,
|
2015-02-24 22:22:13 +00:00
|
|
|
},
|
2015-08-10 23:00:16 +00:00
|
|
|
cache: {
|
|
|
|
type: 'object',
|
|
|
|
required: true,
|
2015-02-24 22:22:13 +00:00
|
|
|
},
|
2015-08-19 23:35:52 +00:00
|
|
|
transformTimeoutInterval: {
|
|
|
|
type: 'number',
|
|
|
|
default: DEFAULT_MAX_CALL_TIME,
|
2016-01-04 19:31:48 +00:00
|
|
|
},
|
2016-01-08 19:09:17 +00:00
|
|
|
disableInternalTransforms: {
|
|
|
|
type: 'boolean',
|
|
|
|
default: false,
|
|
|
|
},
|
2015-02-24 22:22:13 +00:00
|
|
|
});
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
class Transformer {
|
|
|
|
constructor(options) {
|
2015-08-19 23:35:52 +00:00
|
|
|
const opts = this._opts = validateOpts(options);
|
2015-02-24 22:22:13 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
this._cache = opts.cache;
|
2016-01-04 19:31:48 +00:00
|
|
|
this._transformModulePath = opts.transformModulePath;
|
2015-04-01 03:23:33 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
if (opts.transformModulePath != null) {
|
2016-01-08 19:09:17 +00:00
|
|
|
let transformer;
|
|
|
|
|
|
|
|
if (opts.disableInternalTransforms) {
|
|
|
|
transformer = opts.transformModulePath;
|
|
|
|
} else {
|
|
|
|
transformer = this._workerWrapperPath = temp.path();
|
|
|
|
fs.writeFileSync(
|
|
|
|
this._workerWrapperPath,
|
|
|
|
`
|
|
|
|
module.exports = require(${JSON.stringify(require.resolve('./worker'))});
|
|
|
|
require(${JSON.stringify(String(opts.transformModulePath))});
|
|
|
|
`
|
|
|
|
);
|
|
|
|
}
|
2016-01-08 16:34:52 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
this._workers = workerFarm({
|
|
|
|
autoStart: true,
|
|
|
|
maxConcurrentCallsPerWorker: 1,
|
|
|
|
maxCallsPerWorker: MAX_CALLS_PER_WORKER,
|
2015-08-19 23:35:52 +00:00
|
|
|
maxCallTime: opts.transformTimeoutInterval,
|
2015-08-31 16:40:46 +00:00
|
|
|
maxRetries: MAX_RETRIES,
|
2016-01-08 19:09:17 +00:00
|
|
|
}, transformer);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
this._transform = Promise.denodeify(this._workers);
|
|
|
|
}
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
kill() {
|
|
|
|
this._workers && workerFarm.end(this._workers);
|
2016-01-08 19:09:17 +00:00
|
|
|
if (this._workerWrapperPath &&
|
|
|
|
typeof this._workerWrapperPath === 'string') {
|
2016-01-08 16:34:52 +00:00
|
|
|
fs.unlink(this._workerWrapperPath, () => {}); // we don't care about potential errors here
|
|
|
|
}
|
2015-08-13 02:04:54 +00:00
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
invalidateFile(filePath) {
|
|
|
|
this._cache.invalidate(filePath);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-12-21 20:14:33 +00:00
|
|
|
loadFileAndTransform(filePath, options) {
|
2015-08-13 02:04:54 +00:00
|
|
|
if (this._transform == null) {
|
|
|
|
return Promise.reject(new Error('No transfrom module'));
|
|
|
|
}
|
|
|
|
|
2015-09-29 23:06:42 +00:00
|
|
|
debug('transforming file', filePath);
|
|
|
|
|
2015-12-21 20:14:33 +00:00
|
|
|
const optionsJSON = JSON.stringify(options);
|
|
|
|
|
2015-08-19 23:35:52 +00:00
|
|
|
return this._cache.get(
|
|
|
|
filePath,
|
2015-12-21 20:14:33 +00:00
|
|
|
'transformedSource-' + optionsJSON,
|
2015-08-13 02:04:54 +00:00
|
|
|
// TODO: use fastfs to avoid reading file from disk again
|
2015-08-19 23:35:52 +00:00
|
|
|
() => readFile(filePath).then(
|
|
|
|
buffer => {
|
|
|
|
const sourceCode = buffer.toString('utf8');
|
|
|
|
|
|
|
|
return this._transform({
|
|
|
|
sourceCode,
|
|
|
|
filename: filePath,
|
2016-01-04 19:31:48 +00:00
|
|
|
options: {
|
|
|
|
...options,
|
|
|
|
externalTransformModulePath: this._transformModulePath,
|
|
|
|
},
|
2015-08-19 23:35:52 +00:00
|
|
|
}).then(res => {
|
2015-02-20 04:10:52 +00:00
|
|
|
if (res.error) {
|
2015-05-22 17:17:43 +00:00
|
|
|
console.warn(
|
2015-08-19 23:35:52 +00:00
|
|
|
'Error property on the result value from the transformer',
|
2015-05-22 17:17:43 +00:00
|
|
|
'module is deprecated and will be removed in future versions.',
|
|
|
|
'Please pass an error object as the first argument to the callback'
|
|
|
|
);
|
|
|
|
throw formatError(res.error, filePath);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-09-29 23:06:42 +00:00
|
|
|
debug('done transforming file', filePath);
|
|
|
|
|
2015-05-02 00:05:24 +00:00
|
|
|
return new ModuleTransport({
|
2015-02-20 04:10:52 +00:00
|
|
|
code: res.code,
|
2015-05-02 00:05:24 +00:00
|
|
|
map: res.map,
|
2015-02-20 04:10:52 +00:00
|
|
|
sourcePath: filePath,
|
2015-05-02 00:05:24 +00:00
|
|
|
sourceCode: sourceCode,
|
|
|
|
});
|
2015-08-19 23:35:52 +00:00
|
|
|
}).catch(err => {
|
|
|
|
if (err.type === 'TimeoutError') {
|
|
|
|
const timeoutErr = new Error(
|
2015-08-20 21:39:43 +00:00
|
|
|
`TimeoutError: transforming ${filePath} took longer than ` +
|
2015-08-19 23:35:52 +00:00
|
|
|
`${this._opts.transformTimeoutInterval / 1000} seconds.\n` +
|
|
|
|
`You can adjust timeout via the 'transformTimeoutInterval' option`
|
|
|
|
);
|
|
|
|
timeoutErr.type = 'TimeoutError';
|
|
|
|
throw timeoutErr;
|
2015-08-31 16:40:46 +00:00
|
|
|
} else if (err.type === 'ProcessTerminatedError') {
|
|
|
|
const uncaughtError = new Error(
|
|
|
|
'Uncaught error in the transformer worker: ' +
|
|
|
|
this._opts.transformModulePath
|
|
|
|
);
|
|
|
|
uncaughtError.type = 'ProcessTerminatedError';
|
|
|
|
throw uncaughtError;
|
2015-08-19 23:35:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
throw formatError(err, filePath);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
2015-08-13 02:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-19 23:35:52 +00:00
|
|
|
|
2015-08-13 02:04:54 +00:00
|
|
|
module.exports = Transformer;
|
|
|
|
|
|
|
|
Transformer.TransformError = TransformError;
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-08-05 19:31:51 +00:00
|
|
|
function TransformError() {
|
|
|
|
Error.captureStackTrace && Error.captureStackTrace(this, TransformError);
|
|
|
|
}
|
2015-02-26 04:29:14 +00:00
|
|
|
util.inherits(TransformError, SyntaxError);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-03-01 01:13:18 +00:00
|
|
|
function formatError(err, filename, source) {
|
2015-05-22 17:17:43 +00:00
|
|
|
if (err.loc) {
|
|
|
|
return formatBabelError(err, filename, source);
|
2015-03-01 01:13:18 +00:00
|
|
|
} else {
|
|
|
|
return formatGenericError(err, filename, source);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-03-01 01:13:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function formatGenericError(err, filename) {
|
|
|
|
var msg = 'TransformError: ' + filename + ': ' + err.message;
|
|
|
|
var error = new TransformError();
|
2015-04-08 21:28:44 +00:00
|
|
|
var stack = (err.stack || '').split('\n').slice(0, -1);
|
2015-03-01 01:13:18 +00:00
|
|
|
stack.push(msg);
|
|
|
|
error.stack = stack.join('\n');
|
|
|
|
error.message = msg;
|
|
|
|
error.type = 'TransformError';
|
|
|
|
return error;
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-05-22 17:17:43 +00:00
|
|
|
function formatBabelError(err, filename) {
|
2015-02-20 04:10:52 +00:00
|
|
|
var error = new TransformError();
|
|
|
|
error.type = 'TransformError';
|
2015-05-22 17:17:43 +00:00
|
|
|
error.message = (err.type || error.type) + ' ' + err.message;
|
|
|
|
error.stack = err.stack;
|
|
|
|
error.snippet = err.codeFrame;
|
|
|
|
error.lineNumber = err.loc.line;
|
|
|
|
error.column = err.loc.column;
|
2015-02-20 04:10:52 +00:00
|
|
|
error.filename = filename;
|
2015-05-22 17:17:43 +00:00
|
|
|
error.description = err.message;
|
2015-02-20 04:10:52 +00:00
|
|
|
return error;
|
|
|
|
}
|