From f332dbee470722c84b957d28b673fca6ed839c58 Mon Sep 17 00:00:00 2001 From: Jean Lauliac Date: Fri, 28 Jul 2017 12:45:25 -0700 Subject: [PATCH] metro-bundler: Resolver @format Reviewed By: cpojer Differential Revision: D5514920 fbshipit-source-id: 59b8ab3555aca1703b22049382f39d1d67401c4f --- .../src/Resolver/__tests__/Resolver-test.js | 280 +++++++++++------- packages/metro-bundler/src/Resolver/index.js | 110 ++++--- .../src/Resolver/polyfills/prelude.js | 5 +- .../src/Resolver/polyfills/prelude_dev.js | 5 +- .../src/Resolver/polyfills/require.js | 34 ++- 5 files changed, 259 insertions(+), 175 deletions(-) diff --git a/packages/metro-bundler/src/Resolver/__tests__/Resolver-test.js b/packages/metro-bundler/src/Resolver/__tests__/Resolver-test.js index bcb20a53..331ddb00 100644 --- a/packages/metro-bundler/src/Resolver/__tests__/Resolver-test.js +++ b/packages/metro-bundler/src/Resolver/__tests__/Resolver-test.js @@ -5,7 +5,10 @@ * 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 */ + 'use strict'; jest.useRealTimers(); @@ -37,9 +40,9 @@ describe('Resolver', function() { return polyfill; }); - DependencyGraph.load = jest.fn().mockImplementation( - opts => Promise.resolve(new DependencyGraph(opts)), - ); + DependencyGraph.load = jest + .fn() + .mockImplementation(opts => Promise.resolve(new DependencyGraph(opts))); DependencyGraph.prototype.createPolyfill = jest.fn(); DependencyGraph.prototype.getDependencies = jest.fn(); @@ -73,7 +76,9 @@ describe('Resolver', function() { var module = new Module({}); module.path = id; module.getName.mockImplementation(() => Promise.resolve(id)); - module.getDependencies.mockImplementation(() => Promise.resolve(dependencies)); + module.getDependencies.mockImplementation(() => + Promise.resolve(dependencies), + ); return module; } @@ -86,8 +91,7 @@ describe('Resolver', function() { function createPolyfill(id, dependencies) { var polyfill = new Polyfill({}); polyfill.getName = jest.fn(() => Promise.resolve(id)); - polyfill.getDependencies = - jest.fn(() => Promise.resolve(dependencies)); + polyfill.getDependencies = jest.fn(() => Promise.resolve(dependencies)); return polyfill; } @@ -98,8 +102,9 @@ describe('Resolver', function() { const platform = 'ios'; const entry = '/root/index.js'; - DependencyGraph.prototype.getDependencies.mockImplementation( - () => Promise.reject()); + DependencyGraph.prototype.getDependencies.mockImplementation(() => + Promise.reject(), + ); return Resolver.load({projectRoot: '/root'}) .then(r => r.getDependencies(entry, {platform}, transformOptions)) .catch(() => { @@ -114,7 +119,8 @@ describe('Resolver', function() { it('passes custom platforms to the dependency graph', function() { expect.assertions(1); - return Resolver.load({ // eslint-disable-line no-new + return Resolver.load({ + // eslint-disable-line no-new projectRoot: '/root', platforms: ['ios', 'windows', 'vr'], }).then(() => { @@ -135,36 +141,45 @@ describe('Resolver', function() { }); DependencyGraph.prototype.getDependencies.mockImplementation(function() { - return Promise.resolve(new ResolutionResponseMock({ - dependencies: deps, - mainModuleId: 'index', - })); + return Promise.resolve( + new ResolutionResponseMock({ + dependencies: deps, + mainModuleId: 'index', + }), + ); }); return depResolverPromise - .then(r => r.getDependencies( - '/root/index.js', - {dev: false}, - undefined, - undefined, - createGetModuleId() - )).then(result => { + .then(r => + r.getDependencies( + '/root/index.js', + {dev: false}, + undefined, + undefined, + createGetModuleId(), + ), + ) + .then(result => { expect(result.mainModuleId).toEqual('index'); const calls = DependencyGraph.prototype.createPolyfill.mock.calls; const callPolyfill1 = calls[result.dependencies.length - 3]; const callPolyfill2 = calls[result.dependencies.length - 2]; - expect(callPolyfill1).toEqual([{ - file: 'custom-polyfill-1', - id: 'custom-polyfill-1', - dependencies: [], - }]); + expect(callPolyfill1).toEqual([ + { + file: 'custom-polyfill-1', + id: 'custom-polyfill-1', + dependencies: [], + }, + ]); - expect(callPolyfill2).toEqual([{ - file: 'custom-polyfill-2', - id: 'custom-polyfill-2', - dependencies: ['custom-polyfill-1'], - }]); + expect(callPolyfill2).toEqual([ + { + file: 'custom-polyfill-2', + id: 'custom-polyfill-2', + dependencies: ['custom-polyfill-1'], + }, + ]); }); }); }); @@ -174,7 +189,9 @@ describe('Resolver', function() { beforeEach(() => { return Resolver.load({ projectRoot: '/root', - }).then(r => { depResolver = r; }); + }).then(r => { + depResolver = r; + }); }); it('should resolve modules', function() { @@ -185,13 +202,13 @@ describe('Resolver', function() { // require 'require("x")', 'require("y");require(\'abc\');', - 'require( \'z\' )', + "require( 'z' )", 'require( "a")', 'require("b" )', ].join('\n'); /*eslint-disable */ - function *findDependencyOffsets() { + function* findDependencyOffsets() { const re = /(['"']).*?\1/g; let match; while ((match = re.exec(code))) { @@ -206,67 +223,85 @@ describe('Resolver', function() { mainModuleId: 'test module', }); - resolutionResponse.getResolvedDependencyPairs = (module) => { + resolutionResponse.getResolvedDependencyPairs = module => { return [ ['x', createModule('changed')], ['y', createModule('Y')], - ['abc', createModule('abc')] + ['abc', createModule('abc')], ]; - } + }; const moduleIds = new Map( resolutionResponse .getResolvedDependencyPairs() .map(([importId, module]) => [ importId, - padRight(resolutionResponse.getModuleId(module), importId.length + 2), - ]) + padRight( + resolutionResponse.getModuleId(module), + importId.length + 2, + ), + ]), ); - return depResolver.wrapModule({ - resolutionResponse, - module: module, - name: 'test module', - code, - meta: {dependencyOffsets}, - dev: false, - }).then(({code: processedCode}) => { - expect(processedCode).toEqual([ - '__d(/* test module */function(global, require, module, exports) {' + - // require - `require(${moduleIds.get('x')}) // ${moduleIds.get('x').trim()} = x`, - `require(${moduleIds.get('y')});require(${moduleIds.get('abc') - }); // ${moduleIds.get('abc').trim()} = abc // ${moduleIds.get('y').trim()} = y`, - 'require( \'z\' )', - 'require( "a")', - 'require("b" )', - `}, ${resolutionResponse.getModuleId(module)});`, - ].join('\n')); - }); + return depResolver + .wrapModule({ + resolutionResponse, + module: module, + name: 'test module', + code, + meta: {dependencyOffsets}, + dev: false, + }) + .then(({code: processedCode}) => { + expect(processedCode).toEqual( + [ + '__d(/* test module */function(global, require, module, exports) {' + + // require + `require(${moduleIds.get('x')}) // ${moduleIds + .get('x') + .trim()} = x`, + `require(${moduleIds.get('y')});require(${moduleIds.get( + 'abc', + )}); // ${moduleIds.get('abc').trim()} = abc // ${moduleIds + .get('y') + .trim()} = y`, + "require( 'z' )", + 'require( "a")', + 'require("b" )', + `}, ${resolutionResponse.getModuleId(module)});`, + ].join('\n'), + ); + }); }); it('should add module transport names as fourth argument to `__d`', () => { expect.assertions(1); const module = createModule('test module'); - const code = 'arbitrary(code)' + const code = 'arbitrary(code)'; const resolutionResponse = new ResolutionResponseMock({ dependencies: [module], mainModuleId: 'test module', }); - return depResolver.wrapModule({ - resolutionResponse, - code, - module, - name: 'test module', - dev: true, - }).then(({code: processedCode}) => - expect(processedCode).toEqual([ - '__d(/* test module */function(global, require, module, exports) {' + - code, - `}, ${resolutionResponse.getModuleId(module)}, null, "test module");` - ].join('\n')) - ); + return depResolver + .wrapModule({ + resolutionResponse, + code, + module, + name: 'test module', + dev: true, + }) + .then(({code: processedCode}) => + expect(processedCode).toEqual( + [ + '__d(/* test module */function(global, require, module, exports) {' + + code, + `}, ${resolutionResponse.getModuleId( + module, + )}, null, "test module");`, + ].join('\n'), + ), + ); }); it('should pass through passed-in source maps', () => { @@ -277,40 +312,44 @@ describe('Resolver', function() { mainModuleId: 'test module', }); const inputMap = {version: 3, mappings: 'ARBITRARY'}; - return depResolver.wrapModule({ - resolutionResponse, - module, - name: 'test module', - code: 'arbitrary(code)', - map: inputMap, - }).then(({map}) => expect(map).toBe(inputMap)); + return depResolver + .wrapModule({ + resolutionResponse, + module, + name: 'test module', + code: 'arbitrary(code)', + map: inputMap, + }) + .then(({map}) => expect(map).toBe(inputMap)); }); - it('should resolve polyfills', function () { + it('should resolve polyfills', function() { expect.assertions(1); return Resolver.load({ projectRoot: '/root', - }).then(depResolver => {; + }).then(depResolver => { const polyfill = createPolyfill('test polyfill', []); - const code = [ - 'global.fetch = () => 1;', - ].join(''); - return depResolver.wrapModule({ - module: polyfill, - code - }).then(({code: processedCode}) => { - expect(processedCode).toEqual([ - '(function(global) {', - 'global.fetch = () => 1;', - '\n})' + - "(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);", - ].join('')); - }); + const code = ['global.fetch = () => 1;'].join(''); + return depResolver + .wrapModule({ + module: polyfill, + code, + }) + .then(({code: processedCode}) => { + expect(processedCode).toEqual( + [ + '(function(global) {', + 'global.fetch = () => 1;', + '\n})' + + "(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);", + ].join(''), + ); + }); }); }); describe('JSON files:', () => { - const code = JSON.stringify({arbitrary: "data"}); + const code = JSON.stringify({arbitrary: 'data'}); const id = 'arbitrary.json'; let depResolver, module, resolutionResponse; @@ -330,21 +369,27 @@ describe('Resolver', function() { return depResolver .wrapModule({resolutionResponse, module, name: id, code, dev: false}) .then(({code: processedCode}) => - expect(processedCode).toEqual([ - `__d(/* ${id} */function(global, require, module, exports) {`, - `module.exports = ${code}\n}, ${resolutionResponse.getModuleId(module)});`, - ].join(''))); + expect(processedCode).toEqual( + [ + `__d(/* ${id} */function(global, require, module, exports) {`, + `module.exports = ${code}\n}, ${resolutionResponse.getModuleId( + module, + )});`, + ].join(''), + ), + ); }); }); describe('minification:', () => { - const code ='arbitrary(code)'; + const code = 'arbitrary(code)'; const id = 'arbitrary.js'; let depResolver, minifyCode, module, resolutionResponse, sourceMap; beforeEach(() => { minifyCode = jest.fn((filename, code, map) => - Promise.resolve({code, map})); + Promise.resolve({code, map}), + ); module = createModule(id); module.path = '/arbitrary/path.js'; resolutionResponse = new ResolutionResponseMock({ @@ -355,14 +400,16 @@ describe('Resolver', function() { return Resolver.load({ projectRoot: '/root', minifyCode, - }).then(r => { depResolver = r; }); + }).then(r => { + depResolver = r; + }); }); it('should invoke the minifier with the wrapped code', () => { expect.assertions(1); - const wrappedCode = - `__d(/* ${id} */function(global, require, module, exports) {${ - code}\n}, ${resolutionResponse.getModuleId(module)});` + const wrappedCode = `__d(/* ${id} */function(global, require, module, exports) {${code}\n}, ${resolutionResponse.getModuleId( + module, + )});`; return depResolver .wrapModule({ resolutionResponse, @@ -372,8 +419,13 @@ describe('Resolver', function() { map: sourceMap, minify: true, dev: false, - }).then(() => { - expect(minifyCode).toBeCalledWith(module.path, wrappedCode, sourceMap); + }) + .then(() => { + expect(minifyCode).toBeCalledWith( + module.path, + wrappedCode, + sourceMap, + ); }); }); @@ -381,9 +433,17 @@ describe('Resolver', function() { expect.assertions(2); const minifiedCode = 'minified(code)'; const minifiedMap = {version: 3, file: ['minified']}; - minifyCode.mockReturnValue(Promise.resolve({code: minifiedCode, map: minifiedMap})); + minifyCode.mockReturnValue( + Promise.resolve({code: minifiedCode, map: minifiedMap}), + ); return depResolver - .wrapModule({resolutionResponse, module, name: id, code, minify: true}) + .wrapModule({ + resolutionResponse, + module, + name: id, + code, + minify: true, + }) .then(({code, map}) => { expect(code).toEqual(minifiedCode); expect(map).toEqual(minifiedMap); diff --git a/packages/metro-bundler/src/Resolver/index.js b/packages/metro-bundler/src/Resolver/index.js index d045ed87..e2b72aed 100644 --- a/packages/metro-bundler/src/Resolver/index.js +++ b/packages/metro-bundler/src/Resolver/index.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @flow + * @format */ 'use strict'; @@ -22,13 +23,19 @@ import type {MappingsMap} from '../lib/SourceMap'; import type {PostMinifyProcess} from '../Bundler'; import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type {Reporter} from '../lib/reporting'; -import type {TransformCache, GetTransformCacheKey} from '../lib/TransformCaching'; +import type { + TransformCache, + GetTransformCacheKey, +} from '../lib/TransformCaching'; import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; -type MinifyCode = (filePath: string, code: string, map: MappingsMap) => - Promise<{code: string, map: MappingsMap}>; +type MinifyCode = ( + filePath: string, + code: string, + map: MappingsMap, +) => Promise<{code: string, map: MappingsMap}>; -type ContainsTransformerOptions = {+transformer: JSTransformerOptions} +type ContainsTransformerOptions = {+transformer: JSTransformerOptions}; type Options = {| +assetExts: Array, @@ -55,7 +62,6 @@ type Options = {| |}; class Resolver { - _depGraph: DependencyGraph; _getPolyfills: ({platform: ?string}) => $ReadOnlyArray; _minifyCode: MinifyCode; @@ -119,51 +125,52 @@ class Resolver { getModuleId: mixed, ): Promise> { const {platform, recursive = true} = options; - return this._depGraph.getDependencies({ - entryPath, - platform, - options: bundlingOptions, - recursive, - onProgress, - }).then(resolutionResponse => { - this._getPolyfillDependencies(platform).reverse().forEach( - polyfill => resolutionResponse.prependDependency(polyfill) - ); + return this._depGraph + .getDependencies({ + entryPath, + platform, + options: bundlingOptions, + recursive, + onProgress, + }) + .then(resolutionResponse => { + this._getPolyfillDependencies(platform) + .reverse() + .forEach(polyfill => resolutionResponse.prependDependency(polyfill)); - /* $FlowFixMe: monkey patching */ - resolutionResponse.getModuleId = getModuleId; - return resolutionResponse.finalize(); - }); + /* $FlowFixMe: monkey patching */ + resolutionResponse.getModuleId = getModuleId; + return resolutionResponse.finalize(); + }); } getModuleSystemDependencies({dev = true}: {dev?: boolean}): Array { - const prelude = dev - ? pathJoin(__dirname, 'polyfills/prelude_dev.js') - : pathJoin(__dirname, 'polyfills/prelude.js'); + ? pathJoin(__dirname, 'polyfills/prelude_dev.js') + : pathJoin(__dirname, 'polyfills/prelude.js'); const moduleSystem = defaults.moduleSystem; - return [ - prelude, - moduleSystem, - ].map(moduleName => this._depGraph.createPolyfill({ - file: moduleName, - id: moduleName, - dependencies: [], - })); + return [prelude, moduleSystem].map(moduleName => + this._depGraph.createPolyfill({ + file: moduleName, + id: moduleName, + dependencies: [], + }), + ); } _getPolyfillDependencies(platform: ?string): Array { - const polyfillModuleNames = this._getPolyfills({platform}) - .concat(this._polyfillModuleNames); + const polyfillModuleNames = this._getPolyfills({platform}).concat( + this._polyfillModuleNames, + ); - return polyfillModuleNames.map( - (polyfillModuleName, idx) => this._depGraph.createPolyfill({ + return polyfillModuleNames.map((polyfillModuleName, idx) => + this._depGraph.createPolyfill({ file: polyfillModuleName, id: polyfillModuleName, dependencies: polyfillModuleNames.slice(0, idx), - }) + }), ); } @@ -177,7 +184,8 @@ class Resolver { // here, we build a map of all require strings (relative and absolute) // to the canonical ID of the module they reference - resolutionResponse.getResolvedDependencyPairs(module) + resolutionResponse + .getResolvedDependencyPairs(module) .forEach(([depName, depModule]) => { if (depModule) { /* $FlowFixMe: `getModuleId` is monkey-patched so may not exist */ @@ -193,13 +201,15 @@ class Resolver { // require('./c') => require(3); // -- in b/index.js: // require('../a/c') => require(3); - return dependencyOffsets.reduceRight( - ([unhandled, handled], offset) => [ - unhandled.slice(0, offset), - replaceDependencyID(unhandled.slice(offset) + handled, resolvedDeps), - ], - [code, ''], - ).join(''); + return dependencyOffsets + .reduceRight( + ([unhandled, handled], offset) => [ + unhandled.slice(0, offset), + replaceDependencyID(unhandled.slice(offset) + handled, resolvedDeps), + ], + [code, ''], + ) + .join(''); } wrapModule({ @@ -236,7 +246,7 @@ class Resolver { resolutionResponse, module, code, - meta.dependencyOffsets + meta.dependencyOffsets, ); code = defineModuleCode(moduleId, code, name, dev); } @@ -246,9 +256,15 @@ class Resolver { : Promise.resolve({code, map}); } - minifyModule( - {path, code, map}: {path: string, code: string, map: MappingsMap}, - ): Promise<{code: string, map: MappingsMap}> { + minifyModule({ + path, + code, + map, + }: { + path: string, + code: string, + map: MappingsMap, + }): Promise<{code: string, map: MappingsMap}> { return this._minifyCode(path, code, map); } diff --git a/packages/metro-bundler/src/Resolver/polyfills/prelude.js b/packages/metro-bundler/src/Resolver/polyfills/prelude.js index 38170fa1..0959142a 100644 --- a/packages/metro-bundler/src/Resolver/polyfills/prelude.js +++ b/packages/metro-bundler/src/Resolver/polyfills/prelude.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @polyfill + * @format */ /* eslint-disable strict */ @@ -14,5 +15,5 @@ global.__DEV__ = false; global.__BUNDLE_START_TIME__ = global.nativePerformanceNow - ? global.nativePerformanceNow() - : Date.now(); + ? global.nativePerformanceNow() + : Date.now(); diff --git a/packages/metro-bundler/src/Resolver/polyfills/prelude_dev.js b/packages/metro-bundler/src/Resolver/polyfills/prelude_dev.js index caf05c28..4b3e5ecd 100644 --- a/packages/metro-bundler/src/Resolver/polyfills/prelude_dev.js +++ b/packages/metro-bundler/src/Resolver/polyfills/prelude_dev.js @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @polyfill + * @format */ /* eslint-disable strict */ @@ -14,5 +15,5 @@ global.__DEV__ = true; global.__BUNDLE_START_TIME__ = global.nativePerformanceNow - ? global.nativePerformanceNow() - : Date.now(); + ? global.nativePerformanceNow() + : Date.now(); diff --git a/packages/metro-bundler/src/Resolver/polyfills/require.js b/packages/metro-bundler/src/Resolver/polyfills/require.js index eac13011..b2382a2b 100644 --- a/packages/metro-bundler/src/Resolver/polyfills/require.js +++ b/packages/metro-bundler/src/Resolver/polyfills/require.js @@ -8,6 +8,7 @@ * * @polyfill * @flow + * @format */ 'use strict'; @@ -43,8 +44,7 @@ type ModuleDefinition = {| isInitialized: boolean, verboseName?: string, |}; -type ModuleMap = - {[key: ModuleID]: (ModuleDefinition)}; +type ModuleMap = {[key: ModuleID]: ModuleDefinition}; type RequireFn = (id: ModuleID | VerboseModuleNameForDev) => Exports; type VerboseModuleNameForDev = string; @@ -97,7 +97,7 @@ function require(moduleId: ModuleID | VerboseModuleNameForDev) { } else { console.warn( `Requiring module '${verboseName}' by name is only supported for ` + - 'debugging purposes and will BREAK IN PRODUCTION!' + 'debugging purposes and will BREAK IN PRODUCTION!', ); } } @@ -155,7 +155,7 @@ function loadModuleImplementation(moduleId, module) { // factory to keep any require cycles inside the factory from causing an // infinite require loop. module.isInitialized = true; - const exports = module.exports = {}; + const exports = (module.exports = {}); const {factory, dependencyMap} = module; try { if (__DEV__) { @@ -205,8 +205,13 @@ function unknownModuleError(id) { } function moduleThrewError(id, error: any) { - const displayName = __DEV__ && modules[id] && modules[id].verboseName || id; - return Error('Requiring module "' + displayName + '", which threw an exception: ' + error); + const displayName = (__DEV__ && modules[id] && modules[id].verboseName) || id; + return Error( + 'Requiring module "' + + displayName + + '", which threw an exception: ' + + error, + ); } if (__DEV__) { @@ -216,21 +221,21 @@ if (__DEV__) { var createHotReloadingObject = function() { const hot: HotModuleReloadingData = { acceptCallback: null, - accept: callback => { hot.acceptCallback = callback; }, + accept: callback => { + hot.acceptCallback = callback; + }, }; return hot; }; - const acceptAll = function( - dependentModules, - inverseDependencies, - ) { + const acceptAll = function(dependentModules, inverseDependencies) { if (!dependentModules || dependentModules.length === 0) { return true; } const notAccepted = dependentModules.filter( - module => !accept(module, /*factory*/ undefined, inverseDependencies)); + module => !accept(module, /*factory*/ undefined, inverseDependencies), + ); const parents = []; for (let i = 0; i < notAccepted.length; i++) { @@ -252,7 +257,8 @@ if (__DEV__) { ) { const mod = modules[id]; - if (!mod && factory) { // new modules need a factory + if (!mod && factory) { + // new modules need a factory define(factory, id); return true; // new modules don't need to be accepted } @@ -261,7 +267,7 @@ if (__DEV__) { if (!hot) { console.warn( 'Cannot accept module because Hot Module Replacement ' + - 'API was not installed.' + 'API was not installed.', ); return false; }