From 85b74ab63966a74296f8d42f2dad22287e5a9db2 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 5 Oct 2016 09:15:41 -0700 Subject: [PATCH] Add `js_file` rule Reviewed By: matryoshcow Differential Revision: D3900455 fbshipit-source-id: 61384ade035db978ef3ca258e4c0109bcb450692 --- react-packager/src/JSTransformer/index.js | 49 ++++++--- .../worker/collect-dependencies.js | 51 +++++++++ react-packager/src/ModuleGraph/worker.js | 104 ++++++++++++++++++ 3 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 react-packager/src/JSTransformer/worker/collect-dependencies.js create mode 100644 react-packager/src/ModuleGraph/worker.js diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js index b0bda78f..9a5619e5 100644 --- a/react-packager/src/JSTransformer/index.js +++ b/react-packager/src/JSTransformer/index.js @@ -36,6 +36,13 @@ const validateOpts = declareOpts({ type: 'number', default: DEFAULT_MAX_CALL_TIME, }, + worker: { + type: 'string', + }, + methods: { + type: 'array', + default: [], + }, }); const maxConcurrentWorkers = ((cores, override) => { @@ -50,33 +57,47 @@ const maxConcurrentWorkers = ((cores, override) => { return Math.floor(cores * 0.75); } if (cores < 24) { - return Math.floor(3/8 * cores + 3); // between cores *.75 and cores / 2 + return Math.floor(3 / 8 * cores + 3); // between cores *.75 and cores / 2 } return cores / 2; })(os.cpus().length, process.env.REACT_NATIVE_MAX_WORKERS); +function makeFarm(worker, methods, timeout) { + return workerFarm( + { + autoStart: true, + maxConcurrentCallsPerWorker: 1, + maxConcurrentWorkers: maxConcurrentWorkers, + maxCallsPerWorker: MAX_CALLS_PER_WORKER, + maxCallTime: timeout, + maxRetries: MAX_RETRIES, + }, + worker, + methods, + ); +} + class Transformer { constructor(options) { const opts = this._opts = validateOpts(options); const {transformModulePath} = opts; - if (transformModulePath) { + if (opts.worker) { + this._workers = + makeFarm(opts.worker, opts.methods, opts.transformTimeoutInterval); + opts.methods.forEach(name => { + this[name] = this._workers[name]; + }); + } + else if (transformModulePath) { this._transformModulePath = require.resolve(transformModulePath); - this._workers = workerFarm( - { - autoStart: true, - maxConcurrentCallsPerWorker: 1, - maxConcurrentWorkers: maxConcurrentWorkers, - maxCallsPerWorker: MAX_CALLS_PER_WORKER, - maxCallTime: opts.transformTimeoutInterval, - maxRetries: MAX_RETRIES, - }, + this._workers = makeFarm( require.resolve('./worker'), - ['minify', 'transformAndExtractDependencies'] + ['minify', 'transformAndExtractDependencies'], + opts.transformTimeoutInterval, ); - this._transform = Promise.denodeify(this._workers.transformAndExtractDependencies); this.minify = Promise.denodeify(this._workers.minify); } @@ -111,7 +132,7 @@ class Transformer { const timeoutErr = new Error( `TimeoutError: transforming ${fileName} took longer than ` + `${this._opts.transformTimeoutInterval / 1000} seconds.\n` + - `You can adjust timeout via the 'transformTimeoutInterval' option` + 'You can adjust timeout via the \'transformTimeoutInterval\' option' ); timeoutErr.type = 'TimeoutError'; throw timeoutErr; diff --git a/react-packager/src/JSTransformer/worker/collect-dependencies.js b/react-packager/src/JSTransformer/worker/collect-dependencies.js new file mode 100644 index 00000000..1a9eac1c --- /dev/null +++ b/react-packager/src/JSTransformer/worker/collect-dependencies.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2016-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. + */ +'use strict'; + +// RUNS UNTRANSFORMED IN A WORKER PROCESS. ONLY USE NODE 4 COMPATIBLE FEATURES! + +const {traverse, types} = require('babel-core'); + +const isRequireCall = (callee, firstArg) => + callee.type !== 'Identifier' || + callee.name !== 'require' || + !firstArg || + firstArg.type !== 'StringLiteral'; + +function collectDependencies(ast, code) { + let nextIndex = 0; + const dependencyIndexes = new Map(); + + function getIndex(depencyId) { + let index = dependencyIndexes.get(depencyId); + if (index !== undefined) { + return index; + } + + index = nextIndex++; + dependencyIndexes.set(depencyId, index); + return index; + } + + traverse(ast, { + CallExpression(path) { + const node = path.node; + const arg = node.arguments[0]; + if (isRequireCall(node.callee, arg)) { + return; + } + + node.arguments[0] = types.numericLiteral(getIndex(arg.value)); + } + }); + + return Array.from(dependencyIndexes.keys()); +} + +module.exports = collectDependencies; diff --git a/react-packager/src/ModuleGraph/worker.js b/react-packager/src/ModuleGraph/worker.js new file mode 100644 index 00000000..7b7f7b53 --- /dev/null +++ b/react-packager/src/ModuleGraph/worker.js @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2016-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. + */ +'use strict'; + +// RUNS UNTRANSFORMED IN A WORKER PROCESS. ONLY USE NODE 4 COMPATIBLE FEATURES! + +const fs = require('fs'); +const dirname = require('path').dirname; + +const babel = require('babel-core'); +const generate = require('babel-generator').default; +const series = require('async/series'); +const mkdirp = require('mkdirp'); + +const collectDependencies = require('../JSTransformer/worker/collect-dependencies'); +const docblock = require('../node-haste/DependencyGraph/docblock'); + +function transformModule(infile, options, outfile, callback) { + let code, transform; + try { + transform = require(options.transform); + code = fs.readFileSync(infile, 'utf8'); + } catch (readError) { + callback(readError); + return; + } + + const filename = options.filename || infile; + const variants = options.variants || {default: {}}; + + const tasks = {}; + Object.keys(variants).forEach(name => { + tasks[name] = cb => transform({ + filename, + sourceCode: code, + options: variants[name], + }, cb); + }); + + series(tasks, (error, transformed) => { + if (error) { + callback(error); + return; + } + + Object.keys(transformed).forEach(key => { + transformed[key] = makeResult(transformed[key].ast, filename, code); + }); + + const annotations = docblock.parseAsObject(docblock.extract(code)); + + const result = { + file: filename, + code, + transformed, + hasteID: annotations.providesModule || annotations.provide || null, + }; + + try { + mkdirp.sync(dirname(outfile)); + fs.writeFileSync(outfile, JSON.stringify(result), 'utf8'); + } catch (writeError) { + callback(writeError); + return; + } + callback(null); + }); +} + +function makeResult(ast, filename, sourceCode) { + const dependencies = collectDependencies(ast); + const file = wrapModule(ast); + + const gen = generate(file, { + comments: false, + compact: true, + filename, + sourceMaps: true, + sourceMapTarget: filename, + sourceFileName: filename, + }, sourceCode); + return {code: gen.code, map: gen.map, dependencies}; +} + +function wrapModule(file) { + const p = file.program; + const t = babel.types; + const factory = t.functionExpression(t.identifier(''), [ + t.identifier('require'), + t.identifier('module'), + t.identifier('global'), + t.identifier('exports') + ], t.blockStatement(p.body, p.directives)); + const def = t.callExpression(t.identifier('__d'), [factory]); + return t.file(t.program([t.expressionStatement(def)])); +} + +exports.transformModule = transformModule;