diff --git a/packages/metro-bundler/src/ModuleGraph/worker.js b/packages/metro-bundler/src/ModuleGraph/worker.js index 6a1a41f3..16ee3cf6 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker.js +++ b/packages/metro-bundler/src/ModuleGraph/worker.js @@ -26,4 +26,4 @@ type Asyncify = ((A, B) => C) => (A, B, Callback) => void; exports.optimizeModule = (wrapWorkerFn(asyncify(optimizeModule)): WorkerFnWithIO); exports.transformModule = - (wrapWorkerFn(transformModule): WorkerFnWithIO); + (wrapWorkerFn(asyncify(transformModule)): WorkerFnWithIO); diff --git a/packages/metro-bundler/src/ModuleGraph/worker/__tests__/optimize-module-test.js b/packages/metro-bundler/src/ModuleGraph/worker/__tests__/optimize-module-test.js index 9e8a1c65..40305106 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/__tests__/optimize-module-test.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/__tests__/optimize-module-test.js @@ -34,14 +34,9 @@ describe('optimizing JS modules', () => { }`; let transformResult; - beforeAll(done => { - transformModule(originalCode, {filename, transformer}, (error, result) => { - if (error) { - throw error; - } - transformResult = JSON.stringify({type: 'code', details: result.details}); - done(); - }); + beforeAll(() => { + const result = transformModule(originalCode, {filename, transformer}); + transformResult = JSON.stringify({type: 'code', details: result.details}); }); it('copies everything from the transformed file, except for transform results', () => { diff --git a/packages/metro-bundler/src/ModuleGraph/worker/__tests__/transform-module-test.js b/packages/metro-bundler/src/ModuleGraph/worker/__tests__/transform-module-test.js index 607a3075..6757e09e 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/__tests__/transform-module-test.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/__tests__/transform-module-test.js @@ -45,49 +45,35 @@ describe('transforming JS modules:', () => { ast: t.file(t.program(body)), }); - it('passes through file name and code', done => { - transformModule(sourceCode, options(), (error, result) => { - expect(result.type).toBe('code'); - expect(result.details).toEqual( - expect.objectContaining({ - code: sourceCode, - file: filename, - }), - ); - done(); - }); + it('passes through file name and code', () => { + const result = transformModule(sourceCode, options()); + expect(result.type).toBe('code'); + expect(result.details).toEqual( + expect.objectContaining({ + code: sourceCode, + file: filename, + }), + ); }); - it('exposes a haste ID if present', done => { + it('exposes a haste ID if present', () => { const hasteID = 'TheModule'; const codeWithHasteID = `/** @providesModule ${hasteID} */`; - transformModule(codeWithHasteID, options(), (error, result) => { - expect(result.type).toBe('code'); - expect(result.details).toEqual(expect.objectContaining({hasteID})); - done(); - }); + const result = transformModule(codeWithHasteID, options()); + expect(result.type).toBe('code'); + expect(result.details).toEqual(expect.objectContaining({hasteID})); }); - it('sets `type` to `"module"` by default', done => { - transformModule(sourceCode, options(), (error, result) => { - expect(result.type).toBe('code'); - expect(result.details).toEqual(expect.objectContaining({type: 'module'})); - done(); - }); + it('sets `type` to `"module"` by default', () => { + const result = transformModule(sourceCode, options()); + expect(result.type).toBe('code'); + expect(result.details).toEqual(expect.objectContaining({type: 'module'})); }); - it('sets `type` to `"script"` if the input is a polyfill', done => { - transformModule( - sourceCode, - {...options(), polyfill: true}, - (error, result) => { - expect(result.type).toBe('code'); - expect(result.details).toEqual( - expect.objectContaining({type: 'script'}), - ); - done(); - }, - ); + it('sets `type` to `"script"` if the input is a polyfill', () => { + const result = transformModule(sourceCode, {...options(), polyfill: true}); + expect(result.type).toBe('code'); + expect(result.details).toEqual(expect.objectContaining({type: 'script'})); }); const defaults = { @@ -102,95 +88,79 @@ describe('transforming JS modules:', () => { it( 'calls the passed-in transform function with code, file name, and options ' + 'for all passed in variants', - done => { + () => { const variants = {dev: {dev: true}, prod: {dev: false}}; - transformModule(sourceCode, options(variants), () => { - expect(transformer.transform).toBeCalledWith({ - filename, - localPath: filename, - options: {...defaults, ...variants.dev}, - src: sourceCode, - }); - expect(transformer.transform).toBeCalledWith({ - filename, - localPath: filename, - options: {...defaults, ...variants.prod}, - src: sourceCode, - }); - done(); + transformModule(sourceCode, options(variants)); + expect(transformer.transform).toBeCalledWith({ + filename, + localPath: filename, + options: {...defaults, ...variants.dev}, + src: sourceCode, + }); + expect(transformer.transform).toBeCalledWith({ + filename, + localPath: filename, + options: {...defaults, ...variants.prod}, + src: sourceCode, }); }, ); - it('calls back with any error yielded by the transform function', done => { + it('calls back with any error yielded by the transform function', () => { const error = new Error(); transformer.transform.stub.throws(error); - - transformModule(sourceCode, options(), e => { + try { + transformModule(sourceCode, options()); + throw new Error('should not be reached'); + } catch (e) { expect(e).toBe(error); - done(); - }); + } }); - it('wraps the code produced by the transform function into a module factory', done => { - transformModule(sourceCode, options(), (error, result) => { - expect(error).toEqual(null); + it('wraps the code produced by the transform function into a module factory', () => { + const result = transformModule(sourceCode, options()); - const {code, dependencyMapName} = result.details.transformed.default; - expect(code.replace(/\s+/g, '')).toEqual( - `__d(function(global,require,module,exports,${dependencyMapName}){${transformedCode}});`, - ); - done(); - }); - }); - - it('wraps the code produced by the transform function into an IIFE for polyfills', done => { - transformModule( - sourceCode, - {...options(), polyfill: true}, - (error, result) => { - expect(error).toEqual(null); - - const {code} = result.details.transformed.default; - expect(code.replace(/\s+/g, '')).toEqual( - `(function(global){${transformedCode}})(this);`, - ); - done(); - }, + const {code, dependencyMapName} = result.details.transformed.default; + expect(code.replace(/\s+/g, '')).toEqual( + `__d(function(global,require,module,exports,${dependencyMapName}){${transformedCode}});`, ); }); - it('creates source maps', done => { - transformModule(sourceCode, options(), (error, result) => { - const {code, map} = result.details.transformed.default; - const position = findColumnAndLine(code, 'code'); - expect(position).not.toBeNull(); - - const consumer = new SourceMapConsumer(map); - expect(consumer.originalPositionFor(position)).toEqual( - expect.objectContaining({line: 1, column: sourceCode.indexOf('code')}), - ); - done(); - }); + it('wraps the code produced by the transform function into an IIFE for polyfills', () => { + const result = transformModule(sourceCode, {...options(), polyfill: true}); + const {code} = result.details.transformed.default; + expect(code.replace(/\s+/g, '')).toEqual( + `(function(global){${transformedCode}})(this);`, + ); }); - it('extracts dependencies (require calls)', done => { + it('creates source maps', () => { + const result = transformModule(sourceCode, options()); + const {code, map} = result.details.transformed.default; + const position = findColumnAndLine(code, 'code'); + expect(position).not.toBeNull(); + + const consumer = new SourceMapConsumer(map); + expect(consumer.originalPositionFor(position)).toEqual( + expect.objectContaining({line: 1, column: sourceCode.indexOf('code')}), + ); + }); + + it('extracts dependencies (require calls)', () => { const dep1 = 'foo'; const dep2 = 'bar'; const code = `require('${dep1}'),require('${dep2}')`; const {body} = parse(code).program; transformer.transform.stub.returns(transformResult(body)); - transformModule(code, options(), (error, result) => { - expect(result.details.transformed.default).toEqual( - expect.objectContaining({dependencies: [dep1, dep2]}), - ); - done(); - }); + const result = transformModule(code, options()); + expect(result.details.transformed.default).toEqual( + expect.objectContaining({dependencies: [dep1, dep2]}), + ); }); - it('transforms for all variants', done => { + it('transforms for all variants', () => { const variants = {dev: {dev: true}, prod: {dev: false}}; transformer.transform.stub .withArgs(filename, sourceCode, variants.dev) @@ -198,49 +168,35 @@ describe('transforming JS modules:', () => { .withArgs(filename, sourceCode, variants.prod) .returns(transformResult([])); - transformModule(sourceCode, options(variants), (error, result) => { - const {dev, prod} = result.details.transformed; - expect(dev.code.replace(/\s+/g, '')).toEqual( - `__d(function(global,require,module,exports,${dev.dependencyMapName}){arbitrary(code);});`, - ); - expect(prod.code.replace(/\s+/g, '')).toEqual( - `__d(function(global,require,module,exports,${prod.dependencyMapName}){arbitrary(code);});`, - ); - done(); - }); + const result = transformModule(sourceCode, options(variants)); + const {dev, prod} = result.details.transformed; + expect(dev.code.replace(/\s+/g, '')).toEqual( + `__d(function(global,require,module,exports,${dev.dependencyMapName}){arbitrary(code);});`, + ); + expect(prod.code.replace(/\s+/g, '')).toEqual( + `__d(function(global,require,module,exports,${prod.dependencyMapName}){arbitrary(code);});`, + ); }); - it('prefixes JSON files with `module.exports = `', done => { + it('prefixes JSON files with `module.exports = `', () => { const json = '{"foo":"bar"}'; - transformModule( - json, - {...options(), filename: 'some.json'}, - (error, result) => { - const {code} = result.details.transformed.default; - expect(code.replace(/\s+/g, '')).toEqual( - '__d(function(global,require,module,exports){' + - `module.exports=${json}});`, - ); - done(); - }, + const result = transformModule(json, {...options(), filename: 'some.json'}); + const {code} = result.details.transformed.default; + expect(code.replace(/\s+/g, '')).toEqual( + '__d(function(global,require,module,exports){' + + `module.exports=${json}});`, ); }); - it('does not create source maps for JSON files', done => { - transformModule( - '{}', - {...options(), filename: 'some.json'}, - (error, result) => { - expect(result.details.transformed.default).toEqual( - expect.objectContaining({map: null}), - ); - done(); - }, + it('does not create source maps for JSON files', () => { + const result = transformModule('{}', {...options(), filename: 'some.json'}); + expect(result.details.transformed.default).toEqual( + expect.objectContaining({map: null}), ); }); - it('adds package data for `package.json` files', done => { + it('adds package data for `package.json` files', () => { const pkg = { name: 'package-name', main: 'package/main', @@ -248,14 +204,11 @@ describe('transforming JS modules:', () => { 'react-native': {'react-native': 'defs'}, }; - transformModule( - JSON.stringify(pkg), - {...options(), filename: 'arbitrary/package.json'}, - (error, result) => { - expect(result.details.package).toEqual(pkg); - done(); - }, - ); + const result = transformModule(JSON.stringify(pkg), { + ...options(), + filename: 'arbitrary/package.json', + }); + expect(result.details.package).toEqual(pkg); }); }); diff --git a/packages/metro-bundler/src/ModuleGraph/worker/transform-module.js b/packages/metro-bundler/src/ModuleGraph/worker/transform-module.js index 0719f7e9..66681d31 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/transform-module.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/transform-module.js @@ -9,29 +9,28 @@ * @flow * @format */ + 'use strict'; const JsFileWrapping = require('./JsFileWrapping'); -const asyncify = require('async/asyncify'); const collectDependencies = require('./collect-dependencies'); const defaults = require('../../defaults'); const docblock = require('jest-docblock'); const generate = require('./generate'); +const invariant = require('fbjs/lib/invariant'); const path = require('path'); -const series = require('async/series'); const {basename} = require('path'); import type { - Callback, TransformedCodeFile, TransformedSourceFile, Transformer, - TransformerResult, TransformResult, TransformVariants, } from '../types.flow'; +import type {Ast} from 'babel-core'; export type TransformOptions = {| filename: string, @@ -55,68 +54,46 @@ const ASSET_EXTENSIONS = new Set(defaults.assetExts); function transformModule( content: Buffer, options: TransformOptions, - callback: Callback, -): void { +): TransformedSourceFile { if (ASSET_EXTENSIONS.has(path.extname(options.filename).substr(1))) { - transformAsset(content, options, callback); - return; + return transformAsset(content, options); } const code = content.toString('utf8'); if (options.filename.endsWith('.json')) { - transformJSON(code, options, callback); - return; + return transformJSON(code, options); } - const {filename, transformer, variants = defaultVariants} = options; - const tasks = {}; - Object.keys(variants).forEach(name => { - tasks[name] = asyncify(() => - transformer.transform({ - filename, - localPath: filename, - options: {...defaultTransformOptions, ...variants[name]}, - src: code, - }), - ); - }); + const {filename, transformer, polyfill, variants = defaultVariants} = options; + const transformed: {[key: string]: TransformResult} = {}; - series(tasks, (error, results: {[key: string]: TransformerResult}) => { - if (error) { - callback(error); - return; - } - - const transformed: {[key: string]: TransformResult} = {}; - - //$FlowIssue #14545724 - Object.entries(results).forEach(([key, value]: [*, TransformFnResult]) => { - transformed[key] = makeResult( - value.ast, - filename, - code, - options.polyfill, - ); + for (const variantName of Object.keys(variants)) { + const {ast} = transformer.transform({ + filename, + localPath: filename, + options: {...defaultTransformOptions, ...variants[variantName]}, + src: code, }); + invariant(ast != null, 'ast required from the transform results'); + transformed[variantName] = makeResult(ast, filename, code, polyfill); + } - const annotations = docblock.parse(docblock.extract(code)); + const annotations = docblock.parse(docblock.extract(code)); - callback(null, { - type: 'code', - details: { - assetContent: null, - code, - file: filename, - hasteID: annotations.providesModule || null, - transformed, - type: options.polyfill ? 'script' : 'module', - }, - }); - }); - return; + return { + details: { + assetContent: null, + code, + file: filename, + hasteID: annotations.providesModule || null, + transformed, + type: options.polyfill ? 'script' : 'module', + }, + type: 'code', + }; } -function transformJSON(json, options, callback) { +function transformJSON(json, options): TransformedSourceFile { const value = JSON.parse(json); const {filename} = options; const code = `__d(function(${JsFileWrapping.MODULE_FACTORY_PARAMETERS.join( @@ -151,24 +128,23 @@ function transformJSON(json, options, callback) { 'react-native': value['react-native'], }; } - callback(null, {type: 'code', details: result}); + return {type: 'code', details: result}; } function transformAsset( content: Buffer, options: TransformOptions, - callback: Callback, -) { - callback(null, { +): TransformedSourceFile { + return { details: { assetContentBase64: content.toString('base64'), filePath: options.filename, }, type: 'asset', - }); + }; } -function makeResult(ast, filename, sourceCode, isPolyfill = false) { +function makeResult(ast: Ast, filename, sourceCode, isPolyfill = false) { let dependencies, dependencyMapName, file; if (isPolyfill) { dependencies = [];