From 6a0efc0853afbf8b316bdd794473d52c55206774 Mon Sep 17 00:00:00 2001 From: Jean Lauliac Date: Mon, 21 Aug 2017 04:23:15 -0700 Subject: [PATCH] metro-bundler: ModuleGraph worker: @format Reviewed By: davidaurelio Differential Revision: D5658384 fbshipit-source-id: 918017a117e5fb574b3f6801104b4db2708a0eff --- .../src/ModuleGraph/worker/JsFileWrapping.js | 13 ++- .../__tests__/collect-dependencies-test.js | 48 ++++++----- .../worker/__tests__/optimize-module-test.js | 42 +++++---- .../worker/__tests__/transform-module-test.js | 2 + .../worker/__tests__/wrap-worker-fn-test.js | 22 +++-- .../worker/collect-dependencies.js | 85 +++++++++++-------- .../src/ModuleGraph/worker/generate.js | 31 +++++-- .../src/ModuleGraph/worker/optimize-module.js | 46 +++++----- .../src/ModuleGraph/worker/wrap-worker-fn.js | 2 + 9 files changed, 182 insertions(+), 109 deletions(-) diff --git a/packages/metro-bundler/src/ModuleGraph/worker/JsFileWrapping.js b/packages/metro-bundler/src/ModuleGraph/worker/JsFileWrapping.js index 37021a31..80edd425 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/JsFileWrapping.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/JsFileWrapping.js @@ -6,11 +6,14 @@ * 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. * + * @format * @flow */ 'use strict'; +/* eslint-disable no-unclear-flowtypes */ + const babel = require('babel-core'); const MODULE_FACTORY_PARAMETERS = ['global', 'require', 'module', 'exports']; @@ -26,12 +29,18 @@ function wrapModule(fileAst: Object, dependencyMapName: string): Object { function wrapPolyfill(fileAst: Object): Object { const t = babel.types; - const factory = functionFromProgram(fileAst.program, POLYFILL_FACTORY_PARAMETERS); + const factory = functionFromProgram( + fileAst.program, + POLYFILL_FACTORY_PARAMETERS, + ); const iife = t.callExpression(factory, [t.identifier('this')]); return t.file(t.program([t.expressionStatement(iife)])); } -function functionFromProgram(program: Object, parameters: Array): Object { +function functionFromProgram( + program: Object, + parameters: Array, +): Object { const t = babel.types; return t.functionExpression( t.identifier(''), diff --git a/packages/metro-bundler/src/ModuleGraph/worker/__tests__/collect-dependencies-test.js b/packages/metro-bundler/src/ModuleGraph/worker/__tests__/collect-dependencies-test.js index 7fa577fa..131f6f98 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/__tests__/collect-dependencies-test.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/__tests__/collect-dependencies-test.js @@ -5,7 +5,11 @@ * 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. + * + * @format + * @emails oncall+javascript_tools */ + 'use strict'; const collectDependencies = require('../collect-dependencies'); @@ -24,46 +28,43 @@ describe('dependency collection from ASTs:', () => { } `); - expect(collectDependencies(ast).dependencies) - .toEqual(['b/lib/a', 'do', 'setup/something']); + expect(collectDependencies(ast).dependencies).toEqual([ + 'b/lib/a', + 'do', + 'setup/something', + ]); }); it('supports template literals as arguments', () => { const ast = astFromCode('require(`left-pad`)'); - expect(collectDependencies(ast).dependencies) - .toEqual(['left-pad']); + expect(collectDependencies(ast).dependencies).toEqual(['left-pad']); }); it('ignores template literals with interpolations', () => { const ast = astFromCode('require(`left${"-"}pad`)'); - expect(collectDependencies(ast).dependencies) - .toEqual([]); + expect(collectDependencies(ast).dependencies).toEqual([]); }); it('ignores tagged template literals', () => { const ast = astFromCode('require(tag`left-pad`)'); - expect(collectDependencies(ast).dependencies) - .toEqual([]); + expect(collectDependencies(ast).dependencies).toEqual([]); }); it('exposes a string as `dependencyMapName`', () => { const ast = astFromCode('require("arbitrary")'); - expect(collectDependencies(ast).dependencyMapName) - .toEqual(any(String)); + expect(collectDependencies(ast).dependencyMapName).toEqual(any(String)); }); it('exposes a string as `dependencyMapName` even without collecting dependencies', () => { const ast = astFromCode(''); - expect(collectDependencies(ast).dependencyMapName) - .toEqual(any(String)); + expect(collectDependencies(ast).dependencyMapName).toEqual(any(String)); }); - it('replaces all required module ID strings with array lookups, keeps the ID as second argument', - () => { - const ast = astFromCode(` + it('replaces all required module ID strings with array lookups, keeps the ID as second argument', () => { + const ast = astFromCode(` const a = require('b/lib/a'); const b = require(123); exports.do = () => require("do"); @@ -72,18 +73,19 @@ describe('dependency collection from ASTs:', () => { } `); - const {dependencyMapName} = collectDependencies(ast); + const {dependencyMapName} = collectDependencies(ast); - expect(codeFromAst(ast)).toEqual(comparableCode(` + expect(codeFromAst(ast)).toEqual( + comparableCode(` const a = require(${dependencyMapName}[0], 'b/lib/a'); const b = require(123); exports.do = () => require(${dependencyMapName}[1], "do"); if (!something) { require(${dependencyMapName}[2], "setup/something"); } - `)); - }, - ); + `), + ); + }); }); describe('Dependency collection from optimized ASTs:', () => { @@ -121,13 +123,15 @@ describe('Dependency collection from optimized ASTs:', () => { it('replaces all call signatures inserted by a prior call to `collectDependencies`', () => { forOptimization(ast, names, dependencyMapName); - expect(codeFromAst(ast)).toEqual(comparableCode(` + expect(codeFromAst(ast)).toEqual( + comparableCode(` const a = require(${dependencyMapName}[0]); const b = require(123); exports.do = () => require(${dependencyMapName}[1]); if (!something) { require(${dependencyMapName}[2]); } - `)); + `), + ); }); }); 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 eeebb5da..aa431355 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 @@ -5,7 +5,11 @@ * 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. + * + * @format + * @emails oncall+javascript_tools */ + 'use strict'; const optimizeModule = require('../optimize-module'); @@ -23,8 +27,7 @@ describe('optimizing JS modules', () => { platform: 'android', postMinifyProcess: x => x, }; - const originalCode = - `if (Platform.OS !== 'android') { + const originalCode = `if (Platform.OS !== 'android') { require('arbitrary-dev'); } else { __DEV__ ? require('arbitrary-android-dev') : require('arbitrary-android-prod'); @@ -55,12 +58,13 @@ describe('optimizing JS modules', () => { const result = optimizeModule(transformResult, optimizationOptions); optimized = result.details.transformed.default; injectedVars = optimized.code.match(/function\(([^)]*)/)[1].split(','); - [, requireName,,, dependencyMapName] = injectedVars; + [, requireName, , , dependencyMapName] = injectedVars; }); it('optimizes code', () => { - expect(optimized.code) - .toEqual(`__d(function(${injectedVars}){${requireName}(${dependencyMapName}[0])});`); + expect(optimized.code).toEqual( + `__d(function(${injectedVars}){${requireName}(${dependencyMapName}[0])});`, + ); }); it('extracts dependencies', () => { @@ -72,15 +76,16 @@ describe('optimizing JS modules', () => { const column = optimized.code.lastIndexOf(requireName + '('); const loc = findLast(originalCode, 'require'); - expect(consumer.originalPositionFor({line: 1, column})) - .toEqual(objectContaining(loc)); + expect(consumer.originalPositionFor({line: 1, column})).toEqual( + objectContaining(loc), + ); }); it('does not extract dependencies for polyfills', () => { - const result = optimizeModule( - transformResult, - {...optimizationOptions, isPolyfill: true}, - ).details; + const result = optimizeModule(transformResult, { + ...optimizationOptions, + isPolyfill: true, + }).details; expect(result.transformed.default.dependencies).toEqual([]); }); }); @@ -90,7 +95,10 @@ describe('optimizing JS modules', () => { beforeEach(() => { postMinifyProcess = fn(); optimize = () => - optimizeModule(transformResult, {...optimizationOptions, postMinifyProcess}); + optimizeModule(transformResult, { + ...optimizationOptions, + postMinifyProcess, + }); }); it('passes the result to the provided postprocessing function', () => { @@ -104,15 +112,17 @@ describe('optimizing JS modules', () => { const code = 'var postprocessed = "code";'; const map = {version: 3, mappings: 'postprocessed'}; postMinifyProcess.stub.returns({code, map}); - expect(optimize().details.transformed.default) - .toEqual(objectContaining({code, map})); + expect(optimize().details.transformed.default).toEqual( + objectContaining({code, map}), + ); }); }); it('passes through non-code data unmodified', () => { const data = {type: 'asset', details: {arbitrary: 'data'}}; - expect(optimizeModule(JSON.stringify(data), {dev: true, platform: ''})) - .toEqual(data); + expect( + optimizeModule(JSON.stringify(data), {dev: true, platform: ''}), + ).toEqual(data); }); }); 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 22b1bb58..6c45d62d 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 @@ -7,7 +7,9 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @format + * @emails oncall+javascript_tools */ + 'use strict'; const transformModule = require('../transform-module'); diff --git a/packages/metro-bundler/src/ModuleGraph/worker/__tests__/wrap-worker-fn-test.js b/packages/metro-bundler/src/ModuleGraph/worker/__tests__/wrap-worker-fn-test.js index 233a809c..130f8d6b 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/__tests__/wrap-worker-fn-test.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/__tests__/wrap-worker-fn-test.js @@ -5,12 +5,14 @@ * 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. + * + * @format + * @emails oncall+javascript_tools */ + 'use strict'; -jest - .mock('fs', () => jest.genMockFromModule('fs')) - .mock('mkdirp'); +jest.mock('fs', () => jest.genMockFromModule('fs')).mock('mkdirp'); const wrapWorkerFn = require('../wrap-worker-fn'); const {dirname} = require('path'); @@ -63,14 +65,20 @@ describe('wrapWorkerFn:', () => { workerFn.stub.yields(null, result); wrapped(infile, outfile, {}, () => { expect(mkdirp.sync).toBeCalledWith(dirname(outfile)); - expect(fs.writeFileSync).toBeCalledWith(outfile, JSON.stringify(result), 'utf8'); + expect(fs.writeFileSync).toBeCalledWith( + outfile, + JSON.stringify(result), + 'utf8', + ); done(); }); }); it('calls back with any error thrown by `mkdirp.sync`', done => { const error = new Error(); - mkdirp.sync.mockImplementationOnce(() => { throw error; }); + mkdirp.sync.mockImplementationOnce(() => { + throw error; + }); wrapped(infile, outfile, {}, e => { expect(e).toBe(error); done(); @@ -79,7 +87,9 @@ describe('wrapWorkerFn:', () => { it('calls back with any error thrown by `fs.writeFileSync`', done => { const error = new Error(); - fs.writeFileSync.mockImplementationOnce(() => { throw error; }); + fs.writeFileSync.mockImplementationOnce(() => { + throw error; + }); wrapped(infile, outfile, {}, e => { expect(e).toBe(error); done(); diff --git a/packages/metro-bundler/src/ModuleGraph/worker/collect-dependencies.js b/packages/metro-bundler/src/ModuleGraph/worker/collect-dependencies.js index b17c8c9d..03639fc8 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/collect-dependencies.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/collect-dependencies.js @@ -6,8 +6,10 @@ * 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. * + * @format * @flow */ + 'use strict'; const nullthrows = require('fbjs/lib/nullthrows'); @@ -27,8 +29,10 @@ class Replacement { isRequireCall(callee, firstArg) { return ( - callee.type === 'Identifier' && callee.name === 'require' && - firstArg && isLiteralString(firstArg) + callee.type === 'Identifier' && + callee.name === 'require' && + firstArg && + isLiteralString(firstArg) ); } @@ -82,8 +86,9 @@ class ProdReplacement { } throw new Error( - `${id} is not a known module ID. Existing mappings: ${ - this.names.map((n, i) => `${i} => ${n}`).join(', ')}` + `${id} is not a known module ID. Existing mappings: ${this.names + .map((n, i) => `${i} => ${n}`) + .join(', ')}`, ); } @@ -107,26 +112,32 @@ function createMapLookup(dependencyMapIdentifier, propertyIdentifier) { function collectDependencies(ast, replacement, dependencyMapIdentifier) { const traversalState = {dependencyMapIdentifier}; - traverse(ast, { - Program(path, state) { - if (!state.dependencyMapIdentifier) { - state.dependencyMapIdentifier = - path.scope.generateUidIdentifier('dependencyMap'); - } + traverse( + ast, + { + Program(path, state) { + if (!state.dependencyMapIdentifier) { + state.dependencyMapIdentifier = path.scope.generateUidIdentifier( + 'dependencyMap', + ); + } + }, + CallExpression(path, state) { + const node = path.node; + const arg = node.arguments[0]; + if (replacement.isRequireCall(node.callee, arg)) { + const index = replacement.getIndex(arg); + node.arguments = replacement.makeArgs( + types.numericLiteral(index), + arg, + state.dependencyMapIdentifier, + ); + } + }, }, - CallExpression(path, state) { - const node = path.node; - const arg = node.arguments[0]; - if (replacement.isRequireCall(node.callee, arg)) { - const index = replacement.getIndex(arg); - node.arguments = replacement.makeArgs( - types.numericLiteral(index), - arg, - state.dependencyMapIdentifier, - ); - } - }, - }, null, traversalState); + null, + traversalState, + ); return { dependencies: replacement.getNames(), @@ -135,16 +146,22 @@ function collectDependencies(ast, replacement, dependencyMapIdentifier) { } function isLiteralString(node) { - return node.type === 'StringLiteral' || - node.type === 'TemplateLiteral' && node.quasis.length === 1; + return ( + node.type === 'StringLiteral' || + (node.type === 'TemplateLiteral' && node.quasis.length === 1) + ); } -exports = module.exports = - (ast: AST) => collectDependencies(ast, new Replacement()); -exports.forOptimization = - (ast: AST, names: Array, dependencyMapName?: string) => - collectDependencies( - ast, - new ProdReplacement(names), - dependencyMapName ? types.identifier(dependencyMapName) : undefined, - ); +const xp = (module.exports = (ast: AST) => + collectDependencies(ast, new Replacement())); + +xp.forOptimization = ( + ast: AST, + names: Array, + dependencyMapName?: string, +) => + collectDependencies( + ast, + new ProdReplacement(names), + dependencyMapName ? types.identifier(dependencyMapName) : undefined, + ); diff --git a/packages/metro-bundler/src/ModuleGraph/worker/generate.js b/packages/metro-bundler/src/ModuleGraph/worker/generate.js index 321ad2b4..c18825ed 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/generate.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/generate.js @@ -6,21 +6,34 @@ * 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. * + * @format * @flow */ + 'use strict'; +/* eslint-disable no-unclear-flowtypes */ + const babelGenerate = require('babel-generator').default; -function generate(ast: Object, filename: string, sourceCode: string, compact: boolean) { - return babelGenerate(ast, { - comments: false, - compact, - filename, - sourceFileName: filename, - sourceMaps: true, - sourceMapTarget: filename, - }, sourceCode); +function generate( + ast: Object, + filename: string, + sourceCode: string, + compact: boolean, +) { + return babelGenerate( + ast, + { + comments: false, + compact, + filename, + sourceFileName: filename, + sourceMaps: true, + sourceMapTarget: filename, + }, + sourceCode, + ); } module.exports = generate; diff --git a/packages/metro-bundler/src/ModuleGraph/worker/optimize-module.js b/packages/metro-bundler/src/ModuleGraph/worker/optimize-module.js index ca4f7214..ee807efb 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/optimize-module.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/optimize-module.js @@ -6,13 +6,16 @@ * 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. * + * @format * @flow */ + 'use strict'; const babel = require('babel-core'); const collectDependencies = require('./collect-dependencies'); -const constantFolding = require('../../JSTransformer/worker/constant-folding').plugin; +const constantFolding = require('../../JSTransformer/worker/constant-folding') + .plugin; const generate = require('./generate'); const inline = require('../../JSTransformer/worker/inline').plugin; const minify = require('../../JSTransformer/worker/minify'); @@ -22,7 +25,6 @@ import type {TransformedSourceFile, TransformResult} from '../types.flow'; import type {MappingsMap, SourceMap} from '../../lib/SourceMap'; import type {PostMinifyProcess} from '../../Bundler/index.js'; - export type OptimizationOptions = {| dev: boolean, isPolyfill?: boolean, @@ -46,9 +48,14 @@ function optimizeModule( const {postMinifyProcess} = optimizationOptions; //$FlowIssue #14545724 - Object.entries(transformed).forEach(([k, t: TransformResult]: [*, TransformResult]) => { + Object.entries( + transformed, + ).forEach(([k, t: TransformResult]: [*, TransformResult]) => { const optimized = optimize(t, file, code, optimizationOptions); - const processed = postMinifyProcess({code: optimized.code, map: optimized.map}); + const processed = postMinifyProcess({ + code: optimized.code, + map: optimized.map, + }); optimized.code = processed.code; optimized.map = processed.map; result.transformed[k] = optimized; @@ -102,23 +109,22 @@ function mergeSourceMaps( ): MappingsMap { const merged = new sourceMap.SourceMapGenerator(); const inputMap = new sourceMap.SourceMapConsumer(originalMap); - new sourceMap.SourceMapConsumer(secondMap) - .eachMapping(mapping => { - const original = inputMap.originalPositionFor({ - line: mapping.originalLine, - column: mapping.originalColumn, - }); - if (original.line == null) { - return; - } - - merged.addMapping({ - generated: {line: mapping.generatedLine, column: mapping.generatedColumn}, - original: {line: original.line, column: original.column || 0}, - source: file, - name: original.name || mapping.name, - }); + new sourceMap.SourceMapConsumer(secondMap).eachMapping(mapping => { + const original = inputMap.originalPositionFor({ + line: mapping.originalLine, + column: mapping.originalColumn, }); + if (original.line == null) { + return; + } + + merged.addMapping({ + generated: {line: mapping.generatedLine, column: mapping.generatedColumn}, + original: {line: original.line, column: original.column || 0}, + source: file, + name: original.name || mapping.name, + }); + }); return merged.toJSON(); } diff --git a/packages/metro-bundler/src/ModuleGraph/worker/wrap-worker-fn.js b/packages/metro-bundler/src/ModuleGraph/worker/wrap-worker-fn.js index 8d3df8c5..0b43d779 100644 --- a/packages/metro-bundler/src/ModuleGraph/worker/wrap-worker-fn.js +++ b/packages/metro-bundler/src/ModuleGraph/worker/wrap-worker-fn.js @@ -6,8 +6,10 @@ * 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. * + * @format * @flow */ + 'use strict'; const fs = require('fs');