metro-bundler: Resolver @format

Reviewed By: cpojer

Differential Revision: D5514920

fbshipit-source-id: 59b8ab3555aca1703b22049382f39d1d67401c4f
This commit is contained in:
Jean Lauliac 2017-07-28 12:45:25 -07:00 committed by Facebook Github Bot
parent 2dda50893f
commit f332dbee47
5 changed files with 259 additions and 175 deletions

View File

@ -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);

View File

@ -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<string>,
@ -55,7 +62,6 @@ type Options = {|
|};
class Resolver {
_depGraph: DependencyGraph;
_getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>;
_minifyCode: MinifyCode;
@ -119,51 +125,52 @@ class Resolver {
getModuleId: mixed,
): Promise<ResolutionResponse<Module, T>> {
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<Module> {
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<Module> {
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<T: ContainsTransformerOptions>({
@ -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);
}

View File

@ -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();

View File

@ -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();

View File

@ -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;
}