diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js index 63db60c687..1f208fb56e 100644 --- a/local-cli/bundle/buildBundle.js +++ b/local-cli/bundle/buildBundle.js @@ -95,6 +95,7 @@ function buildBundle( transformCache: TransformCaching.useTempDir(), transformModulePath: transformModulePath, watch: false, + workerPath: config.getWorkerPath && config.getWorkerPath(), }; packagerInstance = new Server(options); diff --git a/local-cli/server/runServer.js b/local-cli/server/runServer.js index 179e32bb86..fc9616d1e6 100644 --- a/local-cli/server/runServer.js +++ b/local-cli/server/runServer.js @@ -157,6 +157,7 @@ function getPackagerServer(args, config) { transformModulePath: transformModulePath, verbose: args.verbose, watch: !args.nonPersistent, + workerPath: config.getWorkerPath(), }); } diff --git a/local-cli/util/Config.js b/local-cli/util/Config.js index 1e33995e56..a99cf6a99a 100644 --- a/local-cli/util/Config.js +++ b/local-cli/util/Config.js @@ -78,6 +78,11 @@ export type ConfigT = { getTransformModulePath: () => string, getTransformOptions: GetTransformOptions, + /** + * Returns the path to the worker that is used for transformation. + */ + getWorkerPath: () => string, + /** * An optional function that can modify the code and source map of bundle * after the minifaction took place. @@ -121,6 +126,7 @@ const defaultConfig: ConfigT = { postProcessModules: modules => modules, postProcessModulesForBuck: modules => modules, transformVariants: () => ({default: {}}), + getWorkerPath: () => require.resolve('./worker.js'), }; /** diff --git a/local-cli/util/worker.js b/local-cli/util/worker.js new file mode 100644 index 0000000000..31086105f0 --- /dev/null +++ b/local-cli/util/worker.js @@ -0,0 +1,13 @@ +/** + * 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'; + +require('../../setupBabel')(); +module.exports = require('../../packager/src/JSTransformer/worker'); diff --git a/package.json b/package.json index b455e51780..2e8b6e9ffc 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "Libraries/Renderer", "/node_modules/", "/website/", - "local-cli/templates/" + "local-cli/templates/", + "packager/integration_tests/__tests__/basic_bundle-test.js" ], "haste": { "defaultPlatform": "ios", diff --git a/packager/index.js b/packager/index.js index ee3a0fe1dc..bac1468d58 100644 --- a/packager/index.js +++ b/packager/index.js @@ -39,6 +39,7 @@ type Options = { +transformCache: TransformCache, +transformModulePath: string, watch?: boolean, + workerPath: ?string, }; type StrictOptions = {...Options, reporter: Reporter}; diff --git a/packager/src/Bundler/index.js b/packager/src/Bundler/index.js index 4068b027b5..fe436b0e4d 100644 --- a/packager/src/Bundler/index.js +++ b/packager/src/Bundler/index.js @@ -44,7 +44,7 @@ import type AssetServer from '../AssetServer'; import type Module, {HasteImpl} from '../node-haste/Module'; import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse'; import type {MappingsMap} from '../lib/SourceMap'; -import type {Options as JSTransformerOptions} from '../JSTransformer/worker/worker'; +import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type {Reporter} from '../lib/reporting'; import type {TransformCache} from '../lib/TransformCaching'; import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; @@ -126,8 +126,8 @@ type Options = {| +hasteImpl?: HasteImpl, +platforms: Array, +polyfillModuleNames: Array, - +postProcessModules?: PostProcessModules, +postMinifyProcess: PostMinifyProcess, + +postProcessModules?: PostProcessModules, +projectRoots: $ReadOnlyArray, +providesModuleNodeModules?: Array, +reporter: Reporter, @@ -137,6 +137,7 @@ type Options = {| +transformModulePath: string, +transformTimeoutInterval: ?number, +watch: boolean, + +workerPath: ?string, |}; const {hasOwnProperty} = Object; @@ -197,7 +198,8 @@ class Bundler { { stdoutChunk: chunk => opts.reporter.update({type: 'worker_stdout_chunk', chunk}), stderrChunk: chunk => opts.reporter.update({type: 'worker_stderr_chunk', chunk}), - } + }, + opts.workerPath, ); const getTransformCacheKey = options => { diff --git a/packager/src/JSTransformer/index.js b/packager/src/JSTransformer/index.js index 66a96b8f1c..4cf2a99388 100644 --- a/packager/src/JSTransformer/index.js +++ b/packager/src/JSTransformer/index.js @@ -20,10 +20,10 @@ const path = require('path'); const util = require('util'); const workerFarm = require('../worker-farm'); -import type {Data as TransformData, Options as WorkerOptions} from './worker/worker'; +import type {Data as TransformData, Options as WorkerOptions} from './worker'; import type {LocalPath} from '../node-haste/lib/toLocalPath'; import type {MappingsMap} from '../lib/SourceMap'; -import typeof {minify as Minify, transformAndExtractDependencies as TransformAndExtractDependencies} from './worker/worker'; +import typeof {minify as Minify, transformAndExtractDependencies as TransformAndExtractDependencies} from './worker'; type CB = (?Error, ?T) => mixed; type Denodeify = @@ -83,12 +83,13 @@ class Transformer { transformModulePath: string, maxWorkerCount: number, reporters: Reporters, + workerPath: ?string, ) { invariant(path.isAbsolute(transformModulePath), 'transform module path should be absolute'); this._transformModulePath = transformModulePath; const farm = makeFarm( - require.resolve('./worker'), + workerPath || require.resolve('./worker'), ['minify', 'transformAndExtractDependencies'], TRANSFORM_TIMEOUT_INTERVAL, maxWorkerCount, diff --git a/packager/src/JSTransformer/worker/index.js b/packager/src/JSTransformer/worker/index.js index b549763800..65e3b8922a 100644 --- a/packager/src/JSTransformer/worker/index.js +++ b/packager/src/JSTransformer/worker/index.js @@ -5,9 +5,187 @@ * 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. + * + * @flow */ 'use strict'; -require('../../../../setupBabel')(); -module.exports = require('./worker'); +const babelRegisterOnly = require('../../../babelRegisterOnly'); +const constantFolding = require('./constant-folding'); +const extractDependencies = require('./extract-dependencies'); +const inline = require('./inline'); +const invariant = require('fbjs/lib/invariant'); +const minify = require('./minify'); + +import type {LogEntry} from '../../Logger/Types'; +import type {MappingsMap} from '../../lib/SourceMap'; +import type {LocalPath} from '../../node-haste/lib/toLocalPath'; +import type {Ast, Plugins as BabelPlugins} from 'babel-core'; + +export type TransformedCode = { + code: string, + dependencies: Array, + dependencyOffsets: Array, + map?: ?MappingsMap, +}; + +export type Transformer = { + transform: ({| + filename: string, + localPath: string, + options: ExtraOptions & TransformOptions, + plugins?: BabelPlugins, + src: string, + |}) => {ast: ?Ast, code: string, map: ?MappingsMap}, + getCacheKey: () => string, +}; + + +export type TransformOptionsStrict = {| + +dev: boolean, + +generateSourceMaps: boolean, + +hot: boolean, + +inlineRequires: {+blacklist: {[string]: true}} | boolean, + +platform: ?string, + +projectRoot: string, +|}; + +export type TransformOptions = { + +dev?: boolean, + +generateSourceMaps?: boolean, + +hot?: boolean, + +inlineRequires?: {+blacklist: {[string]: true}} | boolean, + +platform: ?string, + +projectRoot: string, +}; + +export type Options = {| + +dev: boolean, + +minify: boolean, + +platform: ?string, + +transform: TransformOptionsStrict, +|}; + +export type Data = { + result: TransformedCode, + transformFileStartLogEntry: LogEntry, + transformFileEndLogEntry: LogEntry, +}; + +type Callback = ( + error: ?Error, + data: ?T, +) => mixed; + +function transformCode( + transformer: Transformer<*>, + filename: string, + localPath: LocalPath, + sourceCode: string, + options: Options, + callback: Callback, +) { + invariant( + !options.minify || options.transform.generateSourceMaps, + 'Minifying source code requires the `generateSourceMaps` option to be `true`', + ); + + const isJson = filename.endsWith('.json'); + if (isJson) { + sourceCode = 'module.exports=' + sourceCode; + } + + const transformFileStartLogEntry = { + action_name: 'Transforming file', + action_phase: 'start', + file_name: filename, + log_entry_label: 'Transforming file', + start_timestamp: process.hrtime(), + }; + + let transformed; + try { + transformed = transformer.transform({ + filename, + localPath, + options: options.transform, + src: sourceCode, + }); + } catch (error) { + callback(error); + return; + } + + invariant( + transformed != null, + 'Missing transform results despite having no error.', + ); + + var code, map; + if (options.minify) { + ({code, map} = + constantFolding(filename, inline(filename, transformed, options))); + invariant(code != null, 'Missing code from constant-folding transform.'); + } else { + ({code, map} = transformed); + } + + if (isJson) { + code = code.replace(/^\w+\.exports=/, ''); + } else { + // Remove shebang + code = code.replace(/^#!.*/, ''); + } + + const depsResult = isJson + ? {dependencies: [], dependencyOffsets: []} + : extractDependencies(code); + + const timeDelta = process.hrtime(transformFileStartLogEntry.start_timestamp); + const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6); + const transformFileEndLogEntry = { + action_name: 'Transforming file', + action_phase: 'end', + file_name: filename, + duration_ms, + log_entry_label: 'Transforming file', + }; + + callback(null, { + result: {...depsResult, code, map}, + transformFileStartLogEntry, + transformFileEndLogEntry, + }); +} + +exports.transformAndExtractDependencies = ( + transform: string, + filename: string, + localPath: LocalPath, + sourceCode: string, + options: Options, + callback: Callback, +) => { + babelRegisterOnly([transform]); + /* $FlowFixMe: impossible to type a dynamic require */ + const transformModule: Transformer<*> = require(transform); + transformCode(transformModule, filename, localPath, sourceCode, options, callback); +}; + +exports.minify = ( + filename: string, + code: string, + sourceMap: MappingsMap, + callback: Callback<{code: string, map: MappingsMap}>, +) => { + var result; + try { + result = minify(filename, code, sourceMap); + } catch (error) { + callback(error); + } + callback(null, result); +}; + +exports.transformCode = transformCode; // for easier testing diff --git a/packager/src/JSTransformer/worker/worker.js b/packager/src/JSTransformer/worker/worker.js deleted file mode 100644 index 65e3b8922a..0000000000 --- a/packager/src/JSTransformer/worker/worker.js +++ /dev/null @@ -1,191 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const babelRegisterOnly = require('../../../babelRegisterOnly'); -const constantFolding = require('./constant-folding'); -const extractDependencies = require('./extract-dependencies'); -const inline = require('./inline'); -const invariant = require('fbjs/lib/invariant'); -const minify = require('./minify'); - -import type {LogEntry} from '../../Logger/Types'; -import type {MappingsMap} from '../../lib/SourceMap'; -import type {LocalPath} from '../../node-haste/lib/toLocalPath'; -import type {Ast, Plugins as BabelPlugins} from 'babel-core'; - -export type TransformedCode = { - code: string, - dependencies: Array, - dependencyOffsets: Array, - map?: ?MappingsMap, -}; - -export type Transformer = { - transform: ({| - filename: string, - localPath: string, - options: ExtraOptions & TransformOptions, - plugins?: BabelPlugins, - src: string, - |}) => {ast: ?Ast, code: string, map: ?MappingsMap}, - getCacheKey: () => string, -}; - - -export type TransformOptionsStrict = {| - +dev: boolean, - +generateSourceMaps: boolean, - +hot: boolean, - +inlineRequires: {+blacklist: {[string]: true}} | boolean, - +platform: ?string, - +projectRoot: string, -|}; - -export type TransformOptions = { - +dev?: boolean, - +generateSourceMaps?: boolean, - +hot?: boolean, - +inlineRequires?: {+blacklist: {[string]: true}} | boolean, - +platform: ?string, - +projectRoot: string, -}; - -export type Options = {| - +dev: boolean, - +minify: boolean, - +platform: ?string, - +transform: TransformOptionsStrict, -|}; - -export type Data = { - result: TransformedCode, - transformFileStartLogEntry: LogEntry, - transformFileEndLogEntry: LogEntry, -}; - -type Callback = ( - error: ?Error, - data: ?T, -) => mixed; - -function transformCode( - transformer: Transformer<*>, - filename: string, - localPath: LocalPath, - sourceCode: string, - options: Options, - callback: Callback, -) { - invariant( - !options.minify || options.transform.generateSourceMaps, - 'Minifying source code requires the `generateSourceMaps` option to be `true`', - ); - - const isJson = filename.endsWith('.json'); - if (isJson) { - sourceCode = 'module.exports=' + sourceCode; - } - - const transformFileStartLogEntry = { - action_name: 'Transforming file', - action_phase: 'start', - file_name: filename, - log_entry_label: 'Transforming file', - start_timestamp: process.hrtime(), - }; - - let transformed; - try { - transformed = transformer.transform({ - filename, - localPath, - options: options.transform, - src: sourceCode, - }); - } catch (error) { - callback(error); - return; - } - - invariant( - transformed != null, - 'Missing transform results despite having no error.', - ); - - var code, map; - if (options.minify) { - ({code, map} = - constantFolding(filename, inline(filename, transformed, options))); - invariant(code != null, 'Missing code from constant-folding transform.'); - } else { - ({code, map} = transformed); - } - - if (isJson) { - code = code.replace(/^\w+\.exports=/, ''); - } else { - // Remove shebang - code = code.replace(/^#!.*/, ''); - } - - const depsResult = isJson - ? {dependencies: [], dependencyOffsets: []} - : extractDependencies(code); - - const timeDelta = process.hrtime(transformFileStartLogEntry.start_timestamp); - const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6); - const transformFileEndLogEntry = { - action_name: 'Transforming file', - action_phase: 'end', - file_name: filename, - duration_ms, - log_entry_label: 'Transforming file', - }; - - callback(null, { - result: {...depsResult, code, map}, - transformFileStartLogEntry, - transformFileEndLogEntry, - }); -} - -exports.transformAndExtractDependencies = ( - transform: string, - filename: string, - localPath: LocalPath, - sourceCode: string, - options: Options, - callback: Callback, -) => { - babelRegisterOnly([transform]); - /* $FlowFixMe: impossible to type a dynamic require */ - const transformModule: Transformer<*> = require(transform); - transformCode(transformModule, filename, localPath, sourceCode, options, callback); -}; - -exports.minify = ( - filename: string, - code: string, - sourceMap: MappingsMap, - callback: Callback<{code: string, map: MappingsMap}>, -) => { - var result; - try { - result = minify(filename, code, sourceMap); - } catch (error) { - callback(error); - } - callback(null, result); -}; - -exports.transformCode = transformCode; // for easier testing diff --git a/packager/src/ModuleGraph/types.flow.js b/packager/src/ModuleGraph/types.flow.js index b75b6655ff..6db31c26e0 100644 --- a/packager/src/ModuleGraph/types.flow.js +++ b/packager/src/ModuleGraph/types.flow.js @@ -13,7 +13,7 @@ import type {FBSourceMap, MappingsMap, SourceMap} from '../lib/SourceMap'; import type {Ast} from 'babel-core'; import type {Console} from 'console'; -export type {Transformer} from '../JSTransformer/worker/worker.js'; +export type {Transformer} from '../JSTransformer/worker'; export type BuildResult = {| ...GraphResult, diff --git a/packager/src/Resolver/index.js b/packager/src/Resolver/index.js index b855d33675..b28d0bd9fd 100644 --- a/packager/src/Resolver/index.js +++ b/packager/src/Resolver/index.js @@ -20,7 +20,7 @@ import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionRes import type Module, {HasteImpl, TransformCode} from '../node-haste/Module'; import type {MappingsMap} from '../lib/SourceMap'; import type {PostMinifyProcess} from '../Bundler'; -import type {Options as JSTransformerOptions} from '../JSTransformer/worker/worker'; +import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type {Reporter} from '../lib/reporting'; import type {TransformCache, GetTransformCacheKey} from '../lib/TransformCaching'; import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; diff --git a/packager/src/Server/index.js b/packager/src/Server/index.js index 7f20607d98..88b7bfcc3d 100644 --- a/packager/src/Server/index.js +++ b/packager/src/Server/index.js @@ -80,6 +80,7 @@ type Options = { +transformModulePath: string, transformTimeoutInterval?: number, watch?: boolean, + workerPath: ?string, }; export type BundleOptions = { @@ -137,6 +138,7 @@ class Server { +transformModulePath: string, transformTimeoutInterval: ?number, watch: boolean, + workerPath: ?string, }; _projectRoots: $ReadOnlyArray; _bundles: {}; @@ -177,7 +179,9 @@ class Server { transformModulePath: options.transformModulePath, transformTimeoutInterval: options.transformTimeoutInterval, watch: options.watch || false, + workerPath: options.workerPath, }; + const processFileChange = ({type, filePath}) => this.onFileChange(type, filePath); diff --git a/packager/src/lib/GlobalTransformCache.js b/packager/src/lib/GlobalTransformCache.js index 4f8f683459..e8798dd140 100644 --- a/packager/src/lib/GlobalTransformCache.js +++ b/packager/src/lib/GlobalTransformCache.js @@ -25,7 +25,7 @@ const throat = require('throat'); import type { Options as TransformWorkerOptions, TransformOptionsStrict, -} from '../JSTransformer/worker/worker'; +} from '../JSTransformer/worker'; import type {LocalPath} from '../node-haste/lib/toLocalPath'; import type {CachedResult, GetTransformCacheKey} from './TransformCaching'; diff --git a/packager/src/lib/TransformCaching.js b/packager/src/lib/TransformCaching.js index 57eaaba58a..fe63310704 100644 --- a/packager/src/lib/TransformCaching.js +++ b/packager/src/lib/TransformCaching.js @@ -22,7 +22,7 @@ const rimraf = require('rimraf'); const terminal = require('../lib/terminal'); const writeFileAtomicSync = require('write-file-atomic').sync; -import type {Options as WorkerOptions} from '../JSTransformer/worker/worker'; +import type {Options as WorkerOptions} from '../JSTransformer/worker'; import type {MappingsMap} from './SourceMap'; import type {Reporter} from './reporting'; import type {LocalPath} from '../node-haste/lib/toLocalPath'; diff --git a/packager/src/node-haste/DependencyGraph.js b/packager/src/node-haste/DependencyGraph.js index d71ebbcb40..39410a0363 100644 --- a/packager/src/node-haste/DependencyGraph.js +++ b/packager/src/node-haste/DependencyGraph.js @@ -35,9 +35,7 @@ const { } = require('../Logger'); const {EventEmitter} = require('events'); -import type { - Options as JSTransformerOptions, -} from '../JSTransformer/worker/worker'; +import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; import type {GetTransformCacheKey} from '../lib/TransformCaching'; import type {Reporter} from '../lib/reporting'; diff --git a/packager/src/node-haste/DependencyGraph/ResolutionRequest.js b/packager/src/node-haste/DependencyGraph/ResolutionRequest.js index 4196c3788a..f8cc3c74ca 100644 --- a/packager/src/node-haste/DependencyGraph/ResolutionRequest.js +++ b/packager/src/node-haste/DependencyGraph/ResolutionRequest.js @@ -27,7 +27,7 @@ import type DependencyGraphHelpers from './DependencyGraphHelpers'; import type ResolutionResponse from './ResolutionResponse'; import type { Options as TransformWorkerOptions, -} from '../../JSTransformer/worker/worker'; +} from '../../JSTransformer/worker'; import type {ReadResult, CachedReadResult} from '../Module'; type DirExistsFn = (filePath: string) => boolean; diff --git a/packager/src/node-haste/Module.js b/packager/src/node-haste/Module.js index c8080665e6..670f69023b 100644 --- a/packager/src/node-haste/Module.js +++ b/packager/src/node-haste/Module.js @@ -25,7 +25,7 @@ const {join: joinPath, relative: relativePath, extname} = require('path'); import type { TransformedCode, Options as WorkerOptions, -} from '../JSTransformer/worker/worker'; +} from '../JSTransformer/worker'; import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; import type {MappingsMap} from '../lib/SourceMap'; import type { diff --git a/packager/transformer.js b/packager/transformer.js index 8adb523761..04360af93a 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -26,7 +26,7 @@ const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins'); const {compactMapping} = require('./src/Bundler/source-map'); import type {Plugins as BabelPlugins} from 'babel-core'; -import type {Transformer, TransformOptions} from './src/JSTransformer/worker/worker'; +import type {Transformer, TransformOptions} from './src/JSTransformer/worker'; const cacheKeyParts = [ fs.readFileSync(__filename),