Remove old Bundler/* logic

Reviewed By: mjesun

Differential Revision: D6259048

fbshipit-source-id: b39e4c602a0af9d2ad91b72a699b1369526ac9f2
This commit is contained in:
Rafael Oleza 2017-11-08 12:34:04 -08:00 committed by Facebook Github Bot
parent 2eefccc72a
commit 7b93876541
8 changed files with 95 additions and 2216 deletions

View File

@ -1,456 +0,0 @@
/**
* Copyright (c) 2015-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
* @format
*/
'use strict';
const BundleBase = require('./BundleBase');
const ModuleTransport = require('../lib/ModuleTransport');
const _ = require('lodash');
const crypto = require('crypto');
const debug = require('debug')('Metro:Bundle');
const invariant = require('fbjs/lib/invariant');
const {createRamBundleGroups} = require('./util');
const {fromRawMappings} = require('./source-map');
const {isMappingsMap} = require('../lib/SourceMap');
import type {IndexMap, MappingsMap, SourceMap} from '../lib/SourceMap';
import type {GetSourceOptions, FinalizeOptions} from './BundleBase';
import type {PostProcessBundleSourcemap} from './index.js';
export type Unbundle = {
startupModules: Array<*>,
lazyModules: Array<*>,
groups: Map<number, Set<number>>,
};
type SourceMapFormat = 'undetermined' | 'indexed' | 'flattened';
const SOURCEMAPPING_URL = '\n//# sourceMappingURL=';
class Bundle extends BundleBase {
_dev: boolean | void;
_inlineSourceMap: string | void;
_minify: boolean | void;
_numRequireCalls: number;
_ramBundle: Unbundle | null;
_ramGroups: ?Array<string>;
_sourceMap: string | null;
_sourceMapFormat: SourceMapFormat;
_sourceMapUrl: ?string;
postProcessBundleSourcemap: PostProcessBundleSourcemap;
constructor(
{
sourceMapUrl,
dev,
minify,
ramGroups,
postProcessBundleSourcemap,
}: {
sourceMapUrl: ?string,
dev?: boolean,
minify?: boolean,
ramGroups?: Array<string>,
postProcessBundleSourcemap: PostProcessBundleSourcemap,
} = {},
) {
super();
this._sourceMap = null;
this._sourceMapFormat = 'undetermined';
this._sourceMapUrl = sourceMapUrl;
this._numRequireCalls = 0;
this._dev = dev;
this._minify = minify;
this._ramGroups = ramGroups;
this._ramBundle = null; // cached RAM Bundle
this.postProcessBundleSourcemap = postProcessBundleSourcemap;
}
addModule(
/**
* $FlowFixMe: this code is inherently incorrect, because it modifies the
* signature of the base class function "addModule". That means callsites
* using an instance typed as the base class would be broken. This must be
* refactored.
*/
resolver: {
wrapModule: (options: any) => {code: any, map: any},
minifyModule: (
code: any,
map: any,
path: any,
) => Promise<{
code: any,
map: any,
}>,
},
resolutionResponse: any,
module: any,
/* $FlowFixMe: erroneous change of signature. */
moduleTransport: ModuleTransport,
/* $FlowFixMe: erroneous change of signature. */
): Promise<void> {
const index = super.addModule(moduleTransport);
const dependencyPairs = resolutionResponse.getResolvedDependencyPairs(
module,
);
const dependencyPairsMap = new Map();
for (const [relativePath, dependencyModule] of dependencyPairs) {
dependencyPairsMap.set(relativePath, dependencyModule.path);
}
return Promise.resolve(
resolver.wrapModule({
module,
getModuleId: resolutionResponse.getModuleId,
dependencyPairs: dependencyPairsMap,
name: moduleTransport.name,
code: moduleTransport.code,
map: moduleTransport.map,
dependencyOffsets: moduleTransport.meta
? moduleTransport.meta.dependencyOffsets
: undefined,
dev: this._dev,
}),
)
.then(({code, map}) => {
return this._minify
? resolver.minifyModule({code, map, path: module.path})
: {code, map};
})
.then(({code, map}) => {
// If we get a map from the transformer we'll switch to a mode
// were we're combining the source maps as opposed to
if (map) {
let usesRawMappings = isRawMappings(map);
// Transform the raw mappings into standard source maps so the RAM
// bundler for production can build the source maps correctly.
if (usesRawMappings) {
map = fromRawMappings(module).toMap(undefined, {});
usesRawMappings = false;
}
if (this._sourceMapFormat === 'undetermined') {
this._sourceMapFormat = usesRawMappings ? 'flattened' : 'indexed';
} else if (usesRawMappings && this._sourceMapFormat === 'indexed') {
throw new Error(
`Got at least one module with a full source map, but ${moduleTransport.sourcePath} has raw mappings`,
);
} else if (
!usesRawMappings &&
this._sourceMapFormat === 'flattened'
) {
throw new Error(
`Got at least one module with raw mappings, but ${moduleTransport.sourcePath} has a full source map`,
);
}
}
this.replaceModuleAt(
index,
new ModuleTransport({...moduleTransport, code, map}),
);
});
}
finalize(options: FinalizeOptions) {
options = options || {};
if (options.runModule) {
/* $FlowFixMe: this is unsound, as nothing enforces runBeforeMainModule
* to be available if `runModule` is true. Refactor. */
options.runBeforeMainModule.forEach(this._addRequireCall, this);
/* $FlowFixMe: this is unsound, as nothing enforces the module ID to have
* been set beforehand. */
this._addRequireCall(this.getMainModuleId());
}
super.finalize(options);
}
_addRequireCall(moduleId: string) {
const code = `;require(${JSON.stringify(moduleId)});`;
const name = 'require-' + moduleId;
super.addModule(
new ModuleTransport({
name,
id: -this._numRequireCalls - 1,
code,
virtual: true,
sourceCode: code,
sourcePath: name + '.js',
meta: {preloaded: true},
}),
);
this._numRequireCalls += 1;
}
_getInlineSourceMap(dev: ?boolean) {
if (this._inlineSourceMap == null) {
const sourceMap = this.getSourceMapString({excludeSource: true, dev});
/*eslint-env node*/
const encoded = new Buffer(sourceMap).toString('base64');
this._inlineSourceMap = 'data:application/json;base64,' + encoded;
}
return this._inlineSourceMap;
}
getSource(options: GetSourceOptions) {
this.assertFinalized();
options = options || {};
let source = super.getSource(options);
if (options.inlineSourceMap) {
source += SOURCEMAPPING_URL + this._getInlineSourceMap(options.dev);
} else if (this._sourceMapUrl) {
source += SOURCEMAPPING_URL + this._sourceMapUrl;
}
return source;
}
getUnbundle(): Unbundle {
this.assertFinalized();
if (!this._ramBundle) {
const modules = this.getModules().slice();
// separate modules we need to preload from the ones we don't
const [startupModules, lazyModules] = partition(modules, shouldPreload);
const ramGroups = this._ramGroups;
let groups;
this._ramBundle = {
startupModules,
lazyModules,
get groups() {
if (!groups) {
groups = createRamBundleGroups(
ramGroups || [],
lazyModules,
subtree,
);
}
return groups;
},
};
}
return this._ramBundle;
}
invalidateSource() {
debug('invalidating bundle');
super.invalidateSource();
this._sourceMap = null;
}
/**
* Combine each of the sourcemaps multiple modules have into a single big
* one. This works well thanks to a neat trick defined on the sourcemap spec
* that makes use of of the `sections` field to combine sourcemaps by adding
* an offset. This is supported only by Chrome for now.
*/
_getCombinedSourceMaps(options: {excludeSource?: boolean}): IndexMap {
const result = {
version: 3,
file: this._getSourceMapFile(),
sections: [],
};
let line = 0;
this.getModules().forEach(module => {
invariant(
!Array.isArray(module.map),
`Unexpected raw mappings for ${module.sourcePath}`,
);
let map: SourceMap =
module.map == null || module.virtual
? generateSourceMapForVirtualModule(module)
: module.map;
if (options.excludeSource && isMappingsMap(map)) {
map = {...map, sourcesContent: []};
}
result.sections.push({
offset: {line, column: 0},
map,
});
line += module.code.split('\n').length;
});
return result;
}
getSourceMap(options: {excludeSource?: boolean}): SourceMap {
this.assertFinalized();
return this._sourceMapFormat === 'indexed'
? this._getCombinedSourceMaps(options)
: this._fromRawMappings().toMap(undefined, options);
}
getSourceMapString(options: {excludeSource?: boolean}): string {
if (this._sourceMapFormat === 'indexed') {
return JSON.stringify(this.getSourceMap(options));
}
// The following code is an optimization specific to the development server:
// 1. generator.toSource() is faster than JSON.stringify(generator.toMap()).
// 2. caching the source map unless there are changes saves time in
// development settings.
let map = this._sourceMap;
if (map == null) {
debug('Start building flat source map');
map = this._sourceMap = this._fromRawMappings().toString(
undefined,
options,
);
debug('End building flat source map');
} else {
debug('Returning cached source map');
}
return map;
}
getEtag() {
var eTag = crypto
.createHash('md5')
/* $FlowFixMe: we must pass options, or rename the
* base `getSource` function, as it does not actually need options. */
.update(this.getSource())
.digest('hex');
return eTag;
}
_getSourceMapFile() {
return this._sourceMapUrl
? this._sourceMapUrl.replace('.map', '.bundle')
: 'bundle.js';
}
getJSModulePaths() {
return (
this.getModules()
// Filter out non-js files. Like images etc.
.filter(module => !module.virtual)
.map(module => module.sourcePath)
);
}
getDebugInfo() {
return [
/* $FlowFixMe: this is unsound as the module ID could be unset. */
'<div><h3>Main Module:</h3> ' + this.getMainModuleId() + '</div>',
'<style>',
'pre.collapsed {',
' height: 10px;',
' width: 100px;',
' display: block;',
' text-overflow: ellipsis;',
' overflow: hidden;',
' cursor: pointer;',
'}',
'</style>',
'<h3> Module paths and transformed code: </h3>',
this.getModules()
.map(function(m) {
return (
'<div> <h4> Path: </h4>' +
m.sourcePath +
'<br/> <h4> Source: </h4>' +
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
_.escape(m.code) +
'</pre></code></div>'
);
})
.join('\n'),
].join('\n');
}
setRamGroups(ramGroups: ?Array<string>) {
this._ramGroups = ramGroups;
}
_fromRawMappings() {
return fromRawMappings(
this.getModules().map(module => ({
map: Array.isArray(module.map) ? module.map : undefined,
path: module.sourcePath,
source: module.sourceCode,
code: module.code,
})),
);
}
}
function generateSourceMapForVirtualModule(module): MappingsMap {
// All lines map 1-to-1
let mappings = 'AAAA;';
for (let i = 1; i < module.code.split('\n').length; i++) {
mappings += 'AACA;';
}
return {
version: 3,
sources: [module.sourcePath],
names: [],
mappings,
file: module.sourcePath,
sourcesContent: [module.sourceCode],
};
}
function shouldPreload({meta}) {
return meta && meta.preloaded;
}
function partition(array, predicate) {
const included = [];
const excluded = [];
array.forEach(item => (predicate(item) ? included : excluded).push(item));
return [included, excluded];
}
function* subtree(
moduleTransport: ModuleTransport,
moduleTransportsByPath: Map<string, ModuleTransport>,
seen = new Set(),
) {
seen.add(moduleTransport.id);
const {meta} = moduleTransport;
invariant(
meta != null,
'Unexpected module transport without meta information: ' +
moduleTransport.sourcePath,
);
for (const [, {path}] of meta.dependencyPairs || []) {
const dependency = moduleTransportsByPath.get(path);
if (dependency && !seen.has(dependency.id)) {
yield dependency.id;
yield* subtree(dependency, moduleTransportsByPath, seen);
}
}
}
const isRawMappings = Array.isArray;
module.exports = Bundle;

View File

@ -1,117 +0,0 @@
/**
* Copyright (c) 2015-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
* @format
*/
'use strict';
const ModuleTransport = require('../lib/ModuleTransport');
export type FinalizeOptions = {
allowUpdates?: boolean,
runBeforeMainModule?: Array<string>,
runModule?: boolean,
};
export type GetSourceOptions = {
inlineSourceMap?: boolean,
dev: boolean,
};
class BundleBase {
_assets: Array<mixed>;
_finalized: boolean;
_mainModuleId: number | void;
_source: ?string;
__modules: Array<ModuleTransport>;
constructor() {
this._finalized = false;
this.__modules = [];
this._assets = [];
this._mainModuleId = undefined;
}
isEmpty() {
return this.__modules.length === 0 && this._assets.length === 0;
}
getMainModuleId(): number | void {
return this._mainModuleId;
}
setMainModuleId(moduleId: number) {
this._mainModuleId = moduleId;
}
addModule(module: ModuleTransport): number {
if (!(module instanceof ModuleTransport)) {
throw new Error('Expected a ModuleTransport object');
}
return this.__modules.push(module) - 1;
}
replaceModuleAt(index: number, module: ModuleTransport) {
if (!(module instanceof ModuleTransport)) {
throw new Error('Expeceted a ModuleTransport object');
}
this.__modules[index] = module;
}
getModules() {
return this.__modules;
}
getAssets() {
return this._assets;
}
addAsset(asset: mixed) {
this._assets.push(asset);
}
finalize(options: FinalizeOptions) {
if (!options.allowUpdates) {
Object.freeze(this.__modules);
Object.freeze(this._assets);
}
this._finalized = true;
}
getSource(options: GetSourceOptions) {
this.assertFinalized();
if (this._source) {
return this._source;
}
this._source = this.__modules.map(module => module.code).join('\n');
return this._source;
}
invalidateSource() {
this._source = null;
}
assertFinalized(message?: string) {
if (!this._finalized) {
throw new Error(
message || 'Bundle needs to be finalized before getting any source',
);
}
}
setRamGroups(ramGroups: Array<string>) {}
}
module.exports = BundleBase;

View File

@ -1,96 +0,0 @@
/**
* Copyright (c) 2015-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
* @format
*/
'use strict';
const BundleBase = require('./BundleBase');
const ModuleTransport = require('../lib/ModuleTransport');
import type Resolver from '../Resolver';
import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse';
import type Module from '../node-haste/Module';
class HMRBundle extends BundleBase {
_sourceMappingURLFn: (hmrpath: string) => mixed;
_sourceMappingURLs: Array<mixed>;
_sourceURLFn: (hmrpath: string) => mixed;
_sourceURLs: Array<mixed>;
constructor({
sourceURLFn,
sourceMappingURLFn,
}: {
sourceURLFn: (hmrpath: string) => mixed,
sourceMappingURLFn: (hmrpath: string) => mixed,
}) {
super();
this._sourceURLFn = sourceURLFn;
this._sourceMappingURLFn = sourceMappingURLFn;
this._sourceURLs = [];
this._sourceMappingURLs = [];
}
addModule(
/* $FlowFixMe: broken OOP design: function signature should be the same */
resolver: Resolver,
/* $FlowFixMe: broken OOP design: function signature should be the same */
response: ResolutionResponse<Module, {}>,
/* $FlowFixMe: broken OOP design: function signature should be the same */
module: Module,
/* $FlowFixMe: broken OOP design: function signature should be the same */
moduleTransport: ModuleTransport,
) {
const dependencyPairs = response.getResolvedDependencyPairs(module);
const dependencyPairsMap = new Map();
for (const [relativePath, module] of dependencyPairs) {
dependencyPairsMap.set(relativePath, module.path);
}
const code = resolver.resolveRequires(
module,
/* $FlowFixMe: `getModuleId` is monkey-patched so may not exist */
response.getModuleId,
moduleTransport.code,
dependencyPairsMap,
/* $FlowFixMe: may not exist */
moduleTransport.meta.dependencyOffsets,
);
super.addModule(new ModuleTransport({...moduleTransport, code}));
this._sourceMappingURLs.push(
this._sourceMappingURLFn(moduleTransport.sourcePath),
);
this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath));
// inconsistent with parent class return type
return (Promise.resolve(): any);
}
getModulesIdsAndCode(): Array<{id: string, code: string}> {
return this.__modules.map(module => {
return {
id: JSON.stringify(module.id),
code: module.code,
};
});
}
getSourceURLs() {
return this._sourceURLs;
}
getSourceMappingURLs() {
return this._sourceMappingURLs;
}
}
module.exports = HMRBundle;

View File

@ -1,510 +0,0 @@
/**
* Copyright (c) 2015-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.
*
* @emails oncall+javascript_foundation
* @format
*/
'use strict';
const Bundle = require('../Bundle');
const ModuleTransport = require('../../lib/ModuleTransport');
const crypto = require('crypto');
const resolutionResponse = {
getModuleId() {},
getResolvedDependencyPairs() {
return [];
},
};
describe('Bundle', () => {
var bundle;
beforeEach(() => {
bundle = new Bundle({sourceMapUrl: 'test_url'});
bundle.getSourceMap = jest.fn(() => {
return 'test-source-map';
});
});
describe('source bundle', () => {
it('should create a bundle and get the source', () => {
return Promise.resolve()
.then(() => {
return addModule({
bundle,
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle,
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
});
})
.then(() => {
bundle.finalize({});
expect(bundle.getSource({dev: true})).toBe(
[
'transformed foo;',
'transformed bar;',
'//# sourceMappingURL=test_url',
].join('\n'),
);
});
});
it('should be ok to leave out the source map url', () => {
const otherBundle = new Bundle();
return Promise.resolve()
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
});
})
.then(() => {
otherBundle.finalize({});
expect(otherBundle.getSource({dev: true})).toBe(
['transformed foo;', 'transformed bar;'].join('\n'),
);
});
});
it('should create a bundle and add run module code', () => {
return Promise.resolve()
.then(() => {
return addModule({
bundle,
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle,
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
});
})
.then(() => {
bundle.setMainModuleId('foo');
bundle.finalize({
runBeforeMainModule: ['bar'],
runModule: true,
});
expect(bundle.getSource({dev: true})).toBe(
[
'transformed foo;',
'transformed bar;',
';require("bar");',
';require("foo");',
'//# sourceMappingURL=test_url',
].join('\n'),
);
});
});
it('inserts modules in a deterministic order, independent of timing of the wrapper process', () => {
const moduleTransports = [
createModuleTransport({name: 'module1'}),
createModuleTransport({name: 'module2'}),
createModuleTransport({name: 'module3'}),
];
const resolves = {};
const resolver = {
wrapModule({name}) {
return new Promise(resolve => {
resolves[name] = resolve;
});
},
};
const promise = Promise.all(
moduleTransports.map(m =>
bundle.addModule(
resolver,
resolutionResponse,
{isPolyfill: () => false},
m,
),
),
).then(() => {
expect(bundle.getModules()).toEqual(moduleTransports);
});
resolves.module2({code: ''});
resolves.module3({code: ''});
resolves.module1({code: ''});
return promise;
});
});
describe('sourcemap bundle', () => {
it('should create sourcemap', () => {
//TODO: #15357872 add a meaningful test here
});
it('should combine sourcemaps', () => {
const otherBundle = new Bundle({sourceMapUrl: 'test_url'});
return Promise.resolve()
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed foo;\n',
sourceCode: 'source foo',
map: {name: 'sourcemap foo'},
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed bar;\n',
sourceCode: 'source bar',
map: {name: 'sourcemap bar'},
sourcePath: 'bar path',
});
})
.then(() => {
return addModule({
bundle: otherBundle,
code: 'image module;\nimage module;',
virtual: true,
sourceCode: 'image module;\nimage module;',
sourcePath: 'image.png',
});
})
.then(() => {
otherBundle.setMainModuleId('foo');
otherBundle.finalize({
runBeforeMainModule: ['InitializeCore'],
runModule: true,
});
const sourceMap = otherBundle.getSourceMap({dev: true});
expect(sourceMap).toEqual({
file: 'test_url',
version: 3,
sections: [
{offset: {line: 0, column: 0}, map: {name: 'sourcemap foo'}},
{offset: {line: 2, column: 0}, map: {name: 'sourcemap bar'}},
{
offset: {
column: 0,
line: 4,
},
map: {
file: 'image.png',
mappings: 'AAAA;AACA;',
names: [],
sources: ['image.png'],
sourcesContent: ['image module;\nimage module;'],
version: 3,
},
},
{
offset: {
column: 0,
line: 6,
},
map: {
file: 'require-InitializeCore.js',
mappings: 'AAAA;',
names: [],
sources: ['require-InitializeCore.js'],
sourcesContent: [';require("InitializeCore");'],
version: 3,
},
},
{
offset: {
column: 0,
line: 7,
},
map: {
file: 'require-foo.js',
mappings: 'AAAA;',
names: [],
sources: ['require-foo.js'],
sourcesContent: [';require("foo");'],
version: 3,
},
},
],
});
});
});
});
describe('getAssets()', () => {
it('should save and return asset objects', () => {
var p = new Bundle({sourceMapUrl: 'test_url'});
var asset1 = {};
var asset2 = {};
p.addAsset(asset1);
p.addAsset(asset2);
p.finalize();
expect(p.getAssets()).toEqual([asset1, asset2]);
});
});
describe('getJSModulePaths()', () => {
it('should return module paths', () => {
var otherBundle = new Bundle({sourceMapUrl: 'test_url'});
return Promise.resolve()
.then(() => {
return addModule({
bundle: otherBundle,
code: 'transformed foo;\n',
sourceCode: 'source foo',
sourcePath: 'foo path',
});
})
.then(() => {
return addModule({
bundle: otherBundle,
code: 'image module;\nimage module;',
virtual: true,
sourceCode: 'image module;\nimage module;',
sourcePath: 'image.png',
});
})
.then(() => {
expect(otherBundle.getJSModulePaths()).toEqual(['foo path']);
});
});
});
describe('getEtag()', function() {
it('should return an etag', function() {
bundle.finalize({});
var eTag = crypto
.createHash('md5')
.update(bundle.getSource())
.digest('hex');
expect(bundle.getEtag()).toEqual(eTag);
});
});
describe('main module id:', function() {
it('can save a main module ID', function() {
const id = 'arbitrary module ID';
bundle.setMainModuleId(id);
expect(bundle.getMainModuleId()).toEqual(id);
});
});
describe('random access bundle groups:', () => {
let moduleTransports;
beforeEach(() => {
moduleTransports = [
transport('Product1', ['React', 'Relay']),
transport('React', ['ReactFoo', 'ReactBar']),
transport('ReactFoo', ['invariant']),
transport('invariant', []),
transport('ReactBar', ['cx']),
transport('cx', []),
transport('OtherFramework', ['OtherFrameworkFoo', 'OtherFrameworkBar']),
transport('OtherFrameworkFoo', ['invariant']),
transport('OtherFrameworkBar', ['crc32']),
transport('crc32', ['OtherFrameworkBar']),
];
});
it('can create a single group', () => {
bundle = createBundle([fsLocation('React')]);
const {groups} = bundle.getUnbundle();
expect(groups).toEqual(
new Map([
[
idFor('React'),
new Set(['ReactFoo', 'invariant', 'ReactBar', 'cx'].map(idFor)),
],
]),
);
});
it('can create two groups', () => {
bundle = createBundle([fsLocation('ReactFoo'), fsLocation('ReactBar')]);
const {groups} = bundle.getUnbundle();
expect(groups).toEqual(
new Map([
[idFor('ReactFoo'), new Set([idFor('invariant')])],
[idFor('ReactBar'), new Set([idFor('cx')])],
]),
);
});
it('can handle circular dependencies', () => {
bundle = createBundle([fsLocation('OtherFramework')]);
const {groups} = bundle.getUnbundle();
expect(groups).toEqual(
new Map([
[
idFor('OtherFramework'),
new Set(
[
'OtherFrameworkFoo',
'invariant',
'OtherFrameworkBar',
'crc32',
].map(idFor),
),
],
]),
);
});
it('omits modules that are contained by more than one group', () => {
bundle = createBundle([
fsLocation('React'),
fsLocation('OtherFramework'),
]);
expect(() => {
const {groups} = bundle.getUnbundle(); //eslint-disable-line no-unused-vars
}).toThrow(
new Error(
`Module ${fsLocation('invariant')} belongs to groups ${fsLocation(
'React',
)}` +
`, and ${fsLocation(
'OtherFramework',
)}. Ensure that each module is only part of one group.`,
),
);
});
it('ignores missing dependencies', () => {
bundle = createBundle([fsLocation('Product1')]);
const {groups} = bundle.getUnbundle();
expect(groups).toEqual(
new Map([
[
idFor('Product1'),
new Set(
['React', 'ReactFoo', 'invariant', 'ReactBar', 'cx'].map(idFor),
),
],
]),
);
});
it('throws for group roots that do not exist', () => {
bundle = createBundle([fsLocation('DoesNotExist')]);
expect(() => {
const {groups} = bundle.getUnbundle(); //eslint-disable-line no-unused-vars
}).toThrow(
new Error(
`Group root ${fsLocation('DoesNotExist')} is not part of the bundle`,
),
);
});
function idFor(name) {
const {map} = idFor;
if (!map) {
idFor.map = new Map([[name, 0]]);
idFor.next = 1;
return 0;
}
if (map.has(name)) {
return map.get(name);
}
const id = idFor.next++;
map.set(name, id);
return id;
}
function createBundle(ramGroups, options = {}) {
const b = new Bundle(Object.assign(options, {ramGroups}));
moduleTransports.forEach(t => addModule({bundle: b, ...t}));
b.finalize();
return b;
}
function fsLocation(name) {
return `/fs/${name}.js`;
}
function module(name) {
return {path: fsLocation(name)};
}
function transport(name, deps) {
return createModuleTransport({
name,
id: idFor(name),
sourcePath: fsLocation(name),
meta: {dependencyPairs: deps.map(d => [d, module(d)])},
});
}
});
});
function resolverFor(code, map) {
return {
wrapModule: () => Promise.resolve({code, map}),
};
}
function addModule({
bundle,
code,
sourceCode,
sourcePath,
map,
virtual,
polyfill,
meta,
id = '',
}) {
return bundle.addModule(
resolverFor(code, map),
resolutionResponse,
{isPolyfill: () => polyfill},
createModuleTransport({
code,
sourceCode,
sourcePath,
id,
map,
meta,
virtual,
polyfill,
}),
);
}
function createModuleTransport(data) {
return new ModuleTransport({
code: '',
sourceCode: '',
sourcePath: '',
id: 'id' in data ? data.id : '',
...data,
});
}

View File

@ -23,8 +23,6 @@ jest
.mock('../../node-haste/DependencyGraph')
.mock('../../JSTransformer')
.mock('../../Resolver')
.mock('../Bundle')
.mock('../HMRBundle')
.mock('../../Logger')
.mock('/path/to/transformer.js', () => ({}), {virtual: true});
@ -36,8 +34,6 @@ var fs = require('fs');
const os = require('os');
const path = require('path');
const {any, objectContaining} = expect;
var commonOptions = {
allowBundleUpdates: false,
assetExts: defaults.assetExts,
@ -53,36 +49,9 @@ var commonOptions = {
};
describe('Bundler', function() {
function createModule({
path,
id,
dependencies,
isAsset,
isJSON,
isPolyfill,
resolution,
}) {
return {
path,
resolution,
getDependencies: () => Promise.resolve(dependencies),
getName: () => id,
isJSON: () => isJSON,
isAsset: () => isAsset,
isPolyfill: () => isPolyfill,
read: async () => ({
code: 'arbitrary',
source: 'arbitrary',
}),
};
}
var getDependencies;
var getModuleSystemDependencies;
var bundler;
var assetServer;
var modules;
var projectRoots;
let bundler;
let assetServer;
let projectRoots;
beforeEach(function() {
os.cpus.mockReturnValue({length: 1});
@ -90,19 +59,8 @@ describe('Bundler', function() {
// anything to the disk during a unit test!
os.tmpDir.mockReturnValue(path.join(__dirname));
getDependencies = jest.fn();
getModuleSystemDependencies = jest.fn();
projectRoots = ['/root'];
Resolver.mockImplementation(function() {
return {
getDependencies,
getModuleSystemDependencies,
getModuleForPath(path) {
return {path};
},
};
});
Resolver.load = jest
.fn()
.mockImplementation(opts => Promise.resolve(new Resolver(opts)));
@ -117,10 +75,6 @@ describe('Bundler', function() {
};
});
fs.readFile.mockImplementation(function(file, callback) {
callback(null, '{"json":true}');
});
assetServer = {
getAssetData: jest.fn(),
};
@ -131,84 +85,11 @@ describe('Bundler', function() {
assetServer,
});
modules = [
createModule({id: 'foo', path: '/root/foo.js', dependencies: []}),
createModule({id: 'bar', path: '/root/bar.js', dependencies: []}),
createModule({
id: 'new_image.png',
path: '/root/img/new_image.png',
isAsset: true,
resolution: 2,
dependencies: [],
}),
createModule({
id: 'package/file.json',
path: '/root/file.json',
isJSON: true,
dependencies: [],
}),
];
getDependencies.mockImplementation((main, options, transformOptions) =>
Promise.resolve({
mainModuleId: 'foo',
dependencies: modules,
options: transformOptions,
getModuleId: ({path}) => path,
getResolvedDependencyPairs: () => [],
}),
);
getModuleSystemDependencies.mockImplementation(function() {
return [];
});
sizeOf.mockImplementation(function(path, cb) {
cb(null, {width: 50, height: 100});
});
});
it('gets the list of dependencies from the resolver', function() {
const entryFile = '/root/foo.js';
return bundler
.getDependencies({
entryFile,
rootEntryFile: entryFile,
recursive: true,
prependPolyfills: true,
})
.then(() =>
// jest calledWith does not support jasmine.any
expect(getDependencies.mock.calls[0].slice(0, -2)).toEqual([
'/root/foo.js',
{
dev: true,
platform: undefined,
recursive: true,
prependPolyfills: true,
},
{
preloadedModules: undefined,
ramGroups: undefined,
transformer: {
dev: true,
minify: false,
platform: undefined,
transform: {
enableBabelRCLookup: true,
dev: true,
generateSourceMaps: false,
hot: false,
inlineRequires: false,
platform: undefined,
projectRoot: projectRoots[0],
},
},
},
]),
);
});
it('allows overriding the platforms array', () => {
expect(bundler._opts.platforms).toEqual([
'ios',
@ -225,7 +106,7 @@ describe('Bundler', function() {
expect(b._opts.platforms).toEqual(['android', 'vr']);
});
describe('.bundle', () => {
it('.generateAssetObjAndCode', async () => {
const mockAsset = {
__packager_asset: true,
fileSystemLocation: '/root/img',
@ -243,295 +124,44 @@ describe('Bundler', function() {
width: 50,
};
beforeEach(() => {
assetServer.getAssetData.mockImplementation(() =>
Promise.resolve(mockAsset),
);
});
assetServer.getAssetData.mockImplementation(() =>
Promise.resolve(mockAsset),
);
it('creates a bundle', function() {
return bundler
.bundle({
entryFile: '/root/foo.js',
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'source_map_url',
})
.then(bundle => {
const ithAddedModule = i => bundle.addModule.mock.calls[i][2].path;
jest.mock(
'mockPlugin1',
() => {
return asset => {
asset.extraReverseHash = asset.hash
.split('')
.reverse()
.join('');
return asset;
};
},
{virtual: true},
);
expect(ithAddedModule(0)).toEqual('/root/foo.js');
expect(ithAddedModule(1)).toEqual('/root/bar.js');
expect(ithAddedModule(2)).toEqual('/root/img/new_image.png');
expect(ithAddedModule(3)).toEqual('/root/file.json');
expect(bundle.finalize.mock.calls[0]).toEqual([
{
runModule: true,
runBeforeMainModule: [],
allowUpdates: false,
},
]);
expect(bundle.addAsset.mock.calls[0]).toEqual([
{
__packager_asset: true,
fileSystemLocation: '/root/img',
httpServerLocation: '/assets/img',
width: 50,
height: 100,
scales: [1, 2, 3],
files: [
'/root/img/img.png',
'/root/img/img@2x.png',
'/root/img/img@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
},
]);
// TODO(amasad) This fails with 0 != 5 in OSS
//expect(ProgressBar.prototype.tick.mock.calls.length).toEqual(modules.length);
});
});
it('passes runBeforeMainModule correctly to finalize', function() {
return bundler
.bundle({
entryFile: '/root/foo.js',
runBeforeMainModule: ['/root/bar.js'],
runModule: true,
sourceMapUrl: 'source_map_url',
})
.then(bundle => {
expect(bundle.finalize.mock.calls[0]).toEqual([
{
runModule: true,
runBeforeMainModule: ['/root/bar.js'],
allowUpdates: false,
},
]);
});
});
it('ignores runBeforeMainModule when it is not part of the bundle', function() {
return bundler
.bundle({
entryFile: '/root/foo.js',
runBeforeMainModule: ['/root/not-valid.js'],
runModule: true,
sourceMapUrl: 'source_map_url',
})
.then(bundle => {
expect(bundle.finalize.mock.calls[0]).toEqual([
{
runModule: true,
runBeforeMainModule: [],
allowUpdates: false,
},
]);
});
});
it('loads and runs asset plugins', function() {
jest.mock(
'mockPlugin1',
() => {
return asset => {
asset.extraReverseHash = asset.hash
.split('')
.reverse()
.join('');
return asset;
};
},
{virtual: true},
);
jest.mock(
'asyncMockPlugin2',
() => {
return asset => {
expect(asset.extraReverseHash).toBeDefined();
return new Promise(resolve => {
asset.extraPixelCount = asset.width * asset.height;
resolve(asset);
});
};
},
{virtual: true},
);
return bundler
.bundle({
entryFile: '/root/foo.js',
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'source_map_url',
assetPlugins: ['mockPlugin1', 'asyncMockPlugin2'],
})
.then(bundle => {
expect(bundle.addAsset.mock.calls[0]).toEqual([
{
__packager_asset: true,
fileSystemLocation: '/root/img',
httpServerLocation: '/assets/img',
width: 50,
height: 100,
scales: [1, 2, 3],
files: [
'/root/img/img.png',
'/root/img/img@2x.png',
'/root/img/img@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
extraReverseHash: 'hsah a ma i',
extraPixelCount: 5000,
},
]);
});
});
it('calls the module post-processing function', () => {
const postProcessModules = jest.fn().mockImplementation((ms, e) => ms);
const b = new Bundler({
...commonOptions,
postProcessModules,
projectRoots,
assetServer,
});
const dev = false;
const minify = true;
const platform = 'arbitrary';
const entryFile = '/root/foo.js';
return b
.bundle({
dev,
entryFile,
minify,
platform,
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'source_map_url',
})
.then(() => {
expect(postProcessModules).toBeCalledWith(
modules.map(x =>
objectContaining({
name: any(String),
id: any(String), // Our mock of getModuleId returns a string
code: any(String),
sourceCode: any(String),
sourcePath: x.path,
meta: any(Object),
polyfill: !!x.isPolyfill(),
}),
),
entryFile,
{dev, minify, platform},
);
});
});
it('respects the order of modules returned by the post-processing function', () => {
const postProcessModules = jest
.fn()
.mockImplementation((ms, e) => ms.reverse());
const b = new Bundler({
...commonOptions,
postProcessModules,
projectRoots,
assetServer,
});
const entryFile = '/root/foo.js';
return b
.bundle({
entryFile,
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'source_map_url',
})
.then(bundle => {
const ithAddedModule = i => bundle.addModule.mock.calls[i][2].path;
[
'/root/file.json',
'/root/img/new_image.png',
'/root/bar.js',
'/root/foo.js',
].forEach((path, ix) => expect(ithAddedModule(ix)).toEqual(path));
});
});
});
describe('.getOrderedDependencyPaths', () => {
beforeEach(() => {
assetServer.getAssetData.mockImplementation(function(relPath) {
if (relPath === 'img/new_image.png') {
return Promise.resolve({
scales: [1, 2, 3],
files: [
'/root/img/new_image.png',
'/root/img/new_image@2x.png',
'/root/img/new_image@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
jest.mock(
'asyncMockPlugin2',
() => {
return asset => {
expect(asset.extraReverseHash).toBeDefined();
return new Promise(resolve => {
asset.extraPixelCount = asset.width * asset.height;
resolve(asset);
});
} else if (relPath === 'img/new_image2.png') {
return Promise.resolve({
scales: [1, 2, 3],
files: [
'/root/img/new_image2.png',
'/root/img/new_image2@2x.png',
'/root/img/new_image2@3x.png',
],
hash: 'i am a hash',
name: 'img',
type: 'png',
});
}
};
},
{virtual: true},
);
throw new Error('unknown image ' + relPath);
});
});
it('should get the concrete list of all dependency files', () => {
modules.push(
createModule({
id: 'new_image2.png',
path: '/root/img/new_image2.png',
isAsset: true,
resolution: 2,
dependencies: [],
}),
);
return bundler
.getOrderedDependencyPaths('/root/foo.js', true)
.then(paths =>
expect(paths).toEqual([
'/root/foo.js',
'/root/bar.js',
'/root/img/new_image.png',
'/root/img/new_image@2x.png',
'/root/img/new_image@3x.png',
'/root/file.json',
'/root/img/new_image2.png',
'/root/img/new_image2@2x.png',
'/root/img/new_image2@3x.png',
]),
);
});
expect(
await bundler.generateAssetObjAndCode(
{},
['mockPlugin1', 'asyncMockPlugin2'],
'ios',
),
).toMatchSnapshot();
});
});

View File

@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Bundler .generateAssetObjAndCode 1`] = `
Object {
"asset": Object {
"__packager_asset": true,
"extraPixelCount": 5000,
"extraReverseHash": "hsah a ma i",
"fileSystemLocation": "/root/img",
"files": Array [
"/root/img/img.png",
"/root/img/img@2x.png",
"/root/img/img@3x.png",
],
"hash": "i am a hash",
"height": 100,
"httpServerLocation": "/assets/img",
"name": "img",
"scales": Array [
1,
2,
3,
],
"type": "png",
"width": 50,
},
"code": "module.exports=require(\\"/AssetRegistry.js\\").registerAsset({\\"__packager_asset\\":true,\\"scales\\":[1,2,3],\\"hash\\":\\"i am a hash\\",\\"height\\":100,\\"httpServerLocation\\":\\"/assets/img\\",\\"name\\":\\"img\\",\\"type\\":\\"png\\",\\"width\\":50,\\"extraReverseHash\\":\\"hsah a ma i\\",\\"extraPixelCount\\":5000});",
"meta": Object {
"dependencies": Array [
"/AssetRegistry.js",
],
"dependencyOffsets": Array [
23,
],
"preloaded": null,
},
}
`;

View File

@ -15,16 +15,12 @@
const assert = require('assert');
const crypto = require('crypto');
const debug = require('debug')('Metro:Bundler');
const emptyFunction = require('fbjs/lib/emptyFunction');
const fs = require('fs');
const Transformer = require('../JSTransformer');
const Resolver = require('../Resolver');
const Bundle = require('./Bundle');
const HMRBundle = require('./HMRBundle');
const ModuleTransport = require('../lib/ModuleTransport');
const path = require('path');
const defaults = require('../defaults');
const toLocalPath = require('../node-haste/lib/toLocalPath');
const createModuleIdFactory = require('../lib/createModuleIdFactory');
const {generateAssetTransformResult} = require('./util');
@ -35,7 +31,6 @@ const VERSION = require('../../package.json').version;
import type AssetServer from '../AssetServer';
import type Module, {HasteImpl} from '../node-haste/Module';
import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse';
import type {MappingsMap, SourceMap} from '../lib/SourceMap';
import type {Options as JSTransformerOptions} from '../JSTransformer/worker';
import type {Reporter} from '../lib/reporting';
@ -85,12 +80,6 @@ export type ExtendedAssetDescriptor = AssetDescriptor & {
+fileSystemLocation: string,
};
const {
createActionStartEntry,
createActionEndEntry,
log,
} = require('../Logger');
export type PostProcessModulesOptions = {|
dev: boolean,
minify: boolean,
@ -115,7 +104,6 @@ export type PostProcessBundleSourcemap = ({
}) => {code: Buffer | string, map: SourceMap | string};
type Options = {|
+allowBundleUpdates: boolean,
+assetExts: Array<string>,
+assetRegistryPath: string,
+assetServer: AssetServer,
@ -144,8 +132,6 @@ type Options = {|
+workerPath: ?string,
|};
const {hasOwnProperty} = Object;
class Bundler {
_opts: Options;
_getModuleId: ({path: string}) => number;
@ -265,542 +251,34 @@ class Bundler {
);
}
bundle(options: {
dev: boolean,
minify: boolean,
unbundle: boolean,
sourceMapUrl: ?string,
}): Promise<Bundle> {
const {dev, minify, unbundle} = options;
const postProcessBundleSourcemap = this._opts.postProcessBundleSourcemap;
return this._resolverPromise
.then(resolver => resolver.getModuleSystemDependencies({dev, unbundle}))
.then(moduleSystemDeps =>
this._bundle({
...options,
bundle: new Bundle({
dev,
minify,
sourceMapUrl: options.sourceMapUrl,
postProcessBundleSourcemap,
}),
moduleSystemDeps,
}),
);
}
_sourceHMRURL(platform: ?string, hmrpath: string) {
return this._hmrURL('', platform, 'bundle', hmrpath);
}
_sourceMappingHMRURL(platform: ?string, hmrpath: string) {
// Chrome expects `sourceURL` when eval'ing code
return this._hmrURL('//# sourceURL=', platform, 'map', hmrpath);
}
_hmrURL(
prefix: string,
platform: ?string,
extensionOverride: string,
filePath: string,
) {
const matchingRoot = this._projectRoots.find(root =>
filePath.startsWith(root),
);
if (!matchingRoot) {
throw new Error('No matching project root for ' + filePath);
}
// Replaces '\' with '/' for Windows paths.
if (pathSeparator === '\\') {
filePath = filePath.replace(/\\/g, '/');
}
const extensionStart = filePath.lastIndexOf('.');
const resource = filePath.substring(
matchingRoot.length,
extensionStart !== -1 ? extensionStart : undefined,
);
return (
prefix +
resource +
'.' +
extensionOverride +
'?' +
'platform=' +
(platform || '') +
'&runModule=false&entryModuleOnly=true'
);
}
hmrBundle(
options: {platform: ?string},
host: string,
port: number,
): Promise<HMRBundle> {
return this._bundle({
...options,
bundle: new HMRBundle({
sourceURLFn: this._sourceHMRURL.bind(this, options.platform),
sourceMappingURLFn: this._sourceMappingHMRURL.bind(
this,
options.platform,
),
}),
hot: true,
dev: true,
});
}
_bundle<T: Bundle | HMRBundle>({
assetPlugins,
bundle,
dev,
entryFile,
entryModuleOnly,
generateSourceMaps,
hot,
isolateModuleIDs,
minify,
moduleSystemDeps = [],
onProgress,
platform,
resolutionResponse,
runBeforeMainModule,
runModule,
unbundle,
}: {
assetPlugins?: Array<string>,
bundle: T,
dev: boolean,
entryFile?: string,
entryModuleOnly?: boolean,
generateSourceMaps?: boolean,
hot?: boolean,
isolateModuleIDs?: boolean,
minify?: boolean,
moduleSystemDeps?: Array<Module>,
onProgress?: () => void,
platform?: ?string,
resolutionResponse?: ResolutionResponse<Module, BundlingOptions>,
runBeforeMainModule?: Array<string>,
runModule?: boolean,
unbundle?: boolean,
}): Promise<T> {
const onResolutionResponse = (
response: ResolutionResponse<Module, BundlingOptions>,
) => {
/* $FlowFixMe: looks like ResolutionResponse is monkey-patched
* with `getModuleId`. */
bundle.setMainModuleId(response.getModuleId(getMainModule(response)));
if (entryModuleOnly && entryFile) {
response.dependencies = response.dependencies.filter(module =>
module.path.endsWith(entryFile || ''),
);
} else {
response.dependencies = moduleSystemDeps.concat(response.dependencies);
}
};
const finalizeBundle = ({
bundle: finalBundle,
transformedModules,
response,
modulesByPath,
}: {
bundle: Bundle,
transformedModules: Array<{module: Module, transformed: ModuleTransport}>,
response: ResolutionResponse<Module, BundlingOptions>,
modulesByPath: {[path: string]: Module},
}) =>
this._resolverPromise
.then(resolver =>
Promise.all(
transformedModules.map(({module, transformed}) =>
finalBundle.addModule(resolver, response, module, transformed),
),
),
)
.then(() => {
return Promise.all(
runBeforeMainModule
? runBeforeMainModule.map(path => this.getModuleForPath(path))
: [],
);
})
.then(runBeforeMainModules => {
runBeforeMainModules = runBeforeMainModules
.map(module => modulesByPath[module.path])
.filter(Boolean);
finalBundle.finalize({
runModule,
runBeforeMainModule: runBeforeMainModules.map(module =>
/* $FlowFixMe: looks like ResolutionResponse is monkey-patched
* with `getModuleId`. */
response.getModuleId(module),
),
allowUpdates: this._opts.allowBundleUpdates,
});
return finalBundle;
});
return this._buildBundle({
entryFile,
dev,
minify,
platform,
bundle,
hot,
unbundle,
resolutionResponse,
onResolutionResponse,
finalizeBundle,
isolateModuleIDs,
generateSourceMaps,
assetPlugins,
onProgress,
});
}
_buildBundle<T: Bundle | HMRBundle>({
entryFile,
dev,
minify,
platform,
bundle,
hot,
unbundle,
resolutionResponse,
isolateModuleIDs,
generateSourceMaps,
assetPlugins,
onResolutionResponse = emptyFunction,
onModuleTransformed = emptyFunction,
finalizeBundle = emptyFunction,
onProgress = emptyFunction,
}: *): Promise<T> {
const transformingFilesLogEntry = log(
createActionStartEntry({
action_name: 'Transforming files',
entry_point: entryFile,
environment: dev ? 'dev' : 'prod',
}),
);
const modulesByPath = Object.create(null);
if (!resolutionResponse) {
resolutionResponse = this.getDependencies({
entryFile,
rootEntryFile: entryFile,
dev,
platform,
hot,
onProgress,
minify,
isolateModuleIDs,
generateSourceMaps: unbundle || minify || generateSourceMaps,
prependPolyfills: true,
});
}
return Promise.all([
this._resolverPromise,
resolutionResponse,
]).then(([resolver, response]) => {
bundle.setRamGroups(response.options.ramGroups);
log(createActionEndEntry(transformingFilesLogEntry));
onResolutionResponse(response);
// get entry file complete path (`entryFile` is a local path, i.e. relative to roots)
let entryFilePath;
if (response.dependencies.length > 1) {
// skip HMR requests
const numModuleSystemDependencies = resolver.getModuleSystemDependencies(
{dev, unbundle},
).length;
const dependencyIndex =
(response.numPrependedDependencies || 0) +
numModuleSystemDependencies;
if (dependencyIndex in response.dependencies) {
entryFilePath = response.dependencies[dependencyIndex].path;
}
}
const modulesByTransport: Map<ModuleTransport, Module> = new Map();
const toModuleTransport: Module => Promise<ModuleTransport> = module =>
this._toModuleTransport({
module,
bundle,
entryFilePath,
assetPlugins,
options: response.options,
/* $FlowFixMe: `getModuleId` is monkey-patched */
getModuleId: (response.getModuleId: () => number),
dependencyPairs: response.getResolvedDependencyPairs(module),
}).then(transformed => {
modulesByTransport.set(transformed, module);
modulesByPath[module.path] = module;
onModuleTransformed({
module,
response,
bundle,
transformed,
});
return transformed;
});
const p = this._opts.postProcessModules;
const postProcess = p
? modules => p(modules, entryFile, {dev, minify, platform})
: null;
return Promise.all(response.dependencies.map(toModuleTransport))
.then(postProcess)
.then(moduleTransports => {
const transformedModules = moduleTransports.map(transformed => ({
module: modulesByTransport.get(transformed),
transformed,
}));
return finalizeBundle({
bundle,
transformedModules,
response,
modulesByPath,
});
})
.then(() => bundle);
});
}
async getShallowDependencies({
entryFile,
rootEntryFile,
platform,
dev = true,
minify = !dev,
hot = false,
generateSourceMaps = false,
transformerOptions,
}: {
entryFile: string,
+rootEntryFile: string,
platform: ?string,
dev?: boolean,
minify?: boolean,
hot?: boolean,
generateSourceMaps?: boolean,
transformerOptions?: JSTransformerOptions,
}): Promise<Array<string>> {
if (!transformerOptions) {
transformerOptions = (await this._getLegacyTransformOptions_Do_Not_Use(
rootEntryFile,
{
dev,
generateSourceMaps,
hot,
minify,
platform,
prependPolyfills: false,
},
)).transformer;
}
const notNullOptions = transformerOptions;
return this._resolverPromise.then(resolver =>
resolver.getShallowDependencies(entryFile, notNullOptions),
);
}
getModuleForPath(entryFile: string): Promise<Module> {
return this._resolverPromise.then(resolver =>
resolver.getModuleForPath(entryFile),
);
}
async getDependencies({
entryFile,
platform,
dev = true,
minify = !dev,
hot = false,
recursive = true,
generateSourceMaps = false,
isolateModuleIDs = false,
rootEntryFile,
prependPolyfills,
onProgress,
}: {
entryFile: string,
platform: ?string,
dev?: boolean,
minify?: boolean,
hot?: boolean,
recursive?: boolean,
generateSourceMaps?: boolean,
isolateModuleIDs?: boolean,
+rootEntryFile: string,
+prependPolyfills: boolean,
onProgress?: ?(finishedModules: number, totalModules: number) => mixed,
}): Promise<ResolutionResponse<Module, BundlingOptions>> {
const bundlingOptions: BundlingOptions = await this._getLegacyTransformOptions_Do_Not_Use(
rootEntryFile,
{
dev,
platform,
hot,
generateSourceMaps,
minify,
prependPolyfills,
},
);
const resolver = await this._resolverPromise;
const response = await resolver.getDependencies(
entryFile,
{dev, platform, recursive, prependPolyfills},
bundlingOptions,
onProgress,
isolateModuleIDs ? createModuleIdFactory() : this._getModuleId,
);
return response;
}
getOrderedDependencyPaths({
entryFile,
dev,
platform,
minify,
generateSourceMaps,
}: {
+entryFile: string,
+dev: boolean,
+platform: string,
+minify: boolean,
+generateSourceMaps: boolean,
}): Promise<Array<string>> {
return this.getDependencies({
entryFile,
rootEntryFile: entryFile,
dev,
platform,
minify,
generateSourceMaps,
prependPolyfills: true,
}).then(({dependencies}) => {
const ret = [];
const promises = [];
/* $FlowFixMe: these are always removed */
const placeHolder: string = {};
dependencies.forEach(dep => {
if (dep.isAsset()) {
const localPath = toLocalPath(this._projectRoots, dep.path);
promises.push(this._assetServer.getAssetData(localPath, platform));
ret.push(placeHolder);
} else {
ret.push(dep.path);
}
});
return Promise.all(promises).then(assetsData => {
assetsData.forEach(({files}) => {
const index = ret.indexOf(placeHolder);
ret.splice(index, 1, ...files);
});
return ret;
});
});
}
_toModuleTransport({
module,
bundle,
entryFilePath,
options,
getModuleId,
dependencyPairs,
assetPlugins,
}: {
module: Module,
bundle: Bundle,
entryFilePath: string,
options: BundlingOptions,
getModuleId: (module: Module) => number,
dependencyPairs: Array<[string, Module]>,
assetPlugins: Array<string>,
}): Promise<ModuleTransport> {
let moduleTransport;
const moduleId = getModuleId(module);
const transformOptions = options.transformer;
if (module.isAsset()) {
moduleTransport = this._generateAssetModule(
bundle,
module,
moduleId,
assetPlugins,
transformOptions.platform,
);
}
if (moduleTransport) {
return Promise.resolve(moduleTransport);
}
return module
.read(transformOptions)
.then(({code, dependencies, dependencyOffsets, map, source}) => {
const name = module.getName();
const {preloadedModules} = options;
const isPolyfill = module.isPolyfill();
const preloaded =
module.path === entryFilePath ||
isPolyfill ||
(preloadedModules &&
hasOwnProperty.call(preloadedModules, module.path));
return new ModuleTransport({
name,
id: moduleId,
code,
map,
meta: {dependencies, dependencyOffsets, preloaded, dependencyPairs},
polyfill: isPolyfill,
sourceCode: source,
sourcePath: module.path,
});
});
}
generateAssetObjAndCode(
async generateAssetObjAndCode(
module: Module,
assetPlugins: Array<string>,
platform: ?string = null,
) {
return this._assetServer
.getAssetData(module.path, platform)
.then(asset => {
return this._applyAssetPlugins(assetPlugins, asset);
})
.then(asset => {
const {
code,
dependencies,
dependencyOffsets,
} = generateAssetTransformResult(this._opts.assetRegistryPath, asset);
return {
asset,
code,
meta: {dependencies, dependencyOffsets, preloaded: null},
};
});
const assetData = await this._assetServer.getAssetData(
module.path,
platform,
);
const asset = await this._applyAssetPlugins(assetPlugins, assetData);
const {
code,
dependencies,
dependencyOffsets,
} = generateAssetTransformResult(this._opts.assetRegistryPath, asset);
return {
asset,
code,
meta: {dependencies, dependencyOffsets, preloaded: null},
};
}
_applyAssetPlugins(
@ -827,31 +305,6 @@ class Bundler {
}
}
_generateAssetModule(
bundle: Bundle,
module: Module,
moduleId: number,
assetPlugins: Array<string> = [],
platform: ?string = null,
) {
return this.generateAssetObjAndCode(
module,
assetPlugins,
platform,
).then(({asset, code, meta}) => {
bundle.addAsset(asset);
return new ModuleTransport({
name: module.getName(),
id: moduleId,
code,
meta,
sourceCode: code,
sourcePath: module.path,
virtual: true,
});
});
}
/**
* Returns the transform options related to a specific entry file, by calling
* the config parameter getTransformOptions().
@ -920,64 +373,6 @@ class Bundler {
};
}
/*
* Old logic to get the transform options, which automatically calculates
* the dependencies of the inlineRequires and preloadedModules params.
*
* TODO: Remove this.
*/
async _getLegacyTransformOptions_Do_Not_Use(
mainModuleName: string,
options: {|
dev: boolean,
generateSourceMaps: boolean,
hot: boolean,
minify: boolean,
platform: ?string,
+prependPolyfills: boolean,
|},
): Promise<BundlingOptions> {
const getDependencies = (entryFile: string) =>
this.getDependencies({
...options,
enableBabelRCLookup: this._opts.enableBabelRCLookup,
entryFile,
projectRoots: this._projectRoots,
rootEntryFile: entryFile,
prependPolyfills: false,
}).then(r => r.dependencies.map(d => d.path));
const {dev, hot, platform} = options;
const extraOptions: ExtraTransformOptions = this._getTransformOptions
? await this._getTransformOptions(
[mainModuleName],
{dev, hot, platform},
getDependencies,
)
: {};
const {transform = {}} = extraOptions;
return {
transformer: {
dev,
minify: options.minify,
platform,
transform: {
enableBabelRCLookup: this._opts.enableBabelRCLookup,
dev,
generateSourceMaps: options.generateSourceMaps,
hot,
inlineRequires: transform.inlineRequires || false,
platform,
projectRoot: this._projectRoots[0],
},
},
preloadedModules: extraOptions.preloadedModules,
ramGroups: extraOptions.ramGroups,
};
}
getResolver(): Promise<Resolver> {
return this._resolverPromise;
}
@ -988,8 +383,4 @@ function verifyRootExists(root) {
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
}
function getMainModule({dependencies, numPrependedDependencies = 0}) {
return dependencies[numPrependedDependencies];
}
module.exports = Bundler;

View File

@ -217,7 +217,6 @@ class Server {
const bundlerOpts = Object.create(this._opts);
bundlerOpts.assetServer = this._assetServer;
bundlerOpts.allowBundleUpdates = this._opts.watch;
bundlerOpts.globalTransformCache = options.globalTransformCache;
bundlerOpts.watch = this._opts.watch;
bundlerOpts.reporter = reporter;