metro-bundler: moar @format

Reviewed By: mjesun

Differential Revision: D5484885

fbshipit-source-id: f9ae126931f0c9f611ee5b5b96243656e86f4ba4
This commit is contained in:
Jean Lauliac 2017-07-24 22:28:32 -07:00 committed by Facebook Github Bot
parent 9f1cce4e89
commit ce0da03a05
15 changed files with 865 additions and 618 deletions

View File

@ -5,6 +5,8 @@
* 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';
@ -26,7 +28,7 @@ describe('AssetServer', () => {
});
fs.__setMockFilesystem({
'root': {
root: {
imgs: {
'b.png': 'b image',
'b@2x.png': 'b2 image',
@ -37,11 +39,7 @@ describe('AssetServer', () => {
return Promise.all([
server.get('imgs/b.png'),
server.get('imgs/b@1x.png'),
]).then(resp =>
resp.forEach(data =>
expect(data).toBe('b image')
)
);
]).then(resp => resp.forEach(data => expect(data).toBe('b image')));
});
it('should work for the simple case with platform ext', () => {
@ -51,7 +49,7 @@ describe('AssetServer', () => {
});
fs.__setMockFilesystem({
'root': {
root: {
imgs: {
'b.ios.png': 'b ios image',
'b.android.png': 'b android image',
@ -62,25 +60,24 @@ describe('AssetServer', () => {
});
return Promise.all([
server.get('imgs/b.png', 'ios').then(
data => expect(data).toBe('b ios image')
),
server.get('imgs/b.png', 'android').then(
data => expect(data).toBe('b android image')
),
server.get('imgs/c.png', 'android').then(
data => expect(data).toBe('c android image')
),
server.get('imgs/c.png', 'ios').then(
data => expect(data).toBe('c general image')
),
server.get('imgs/c.png').then(
data => expect(data).toBe('c general image')
),
server
.get('imgs/b.png', 'ios')
.then(data => expect(data).toBe('b ios image')),
server
.get('imgs/b.png', 'android')
.then(data => expect(data).toBe('b android image')),
server
.get('imgs/c.png', 'android')
.then(data => expect(data).toBe('c android image')),
server
.get('imgs/c.png', 'ios')
.then(data => expect(data).toBe('c general image')),
server
.get('imgs/c.png')
.then(data => expect(data).toBe('c general image')),
]);
});
it('should work for the simple case with jpg', () => {
const server = new AssetServer({
projectRoots: ['/root'],
@ -88,7 +85,7 @@ describe('AssetServer', () => {
});
fs.__setMockFilesystem({
'root': {
root: {
imgs: {
'b.png': 'png image',
'b.jpg': 'jpeg image',
@ -99,12 +96,7 @@ describe('AssetServer', () => {
return Promise.all([
server.get('imgs/b.jpg'),
server.get('imgs/b.png'),
]).then(data =>
expect(data).toEqual([
'jpeg image',
'png image',
])
);
]).then(data => expect(data).toEqual(['jpeg image', 'png image']));
});
it('should pick the bigger one', () => {
@ -114,7 +106,7 @@ describe('AssetServer', () => {
});
fs.__setMockFilesystem({
'root': {
root: {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
@ -124,9 +116,9 @@ describe('AssetServer', () => {
},
});
return server.get('imgs/b@3x.png').then(data =>
expect(data).toBe('b4 image')
);
return server
.get('imgs/b@3x.png')
.then(data => expect(data).toBe('b4 image'));
});
it('should pick the bigger one with platform ext', () => {
@ -136,7 +128,7 @@ describe('AssetServer', () => {
});
fs.__setMockFilesystem({
'root': {
root: {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
@ -151,12 +143,10 @@ describe('AssetServer', () => {
});
return Promise.all([
server.get('imgs/b@3x.png').then(data =>
expect(data).toBe('b4 image')
),
server.get('imgs/b@3x.png', 'ios').then(data =>
expect(data).toBe('b4 ios image')
),
server.get('imgs/b@3x.png').then(data => expect(data).toBe('b4 image')),
server
.get('imgs/b@3x.png', 'ios')
.then(data => expect(data).toBe('b4 ios image')),
]);
});
@ -167,23 +157,23 @@ describe('AssetServer', () => {
});
fs.__setMockFilesystem({
'root': {
root: {
imgs: {
'b.png': 'b image',
},
},
'root2': {
'newImages': {
'imgs': {
root2: {
newImages: {
imgs: {
'b@1x.png': 'b1 image',
},
},
},
});
return server.get('newImages/imgs/b.png').then(data =>
expect(data).toBe('b1 image')
);
return server
.get('newImages/imgs/b.png')
.then(data => expect(data).toBe('b1 image'));
});
});
@ -195,7 +185,7 @@ describe('AssetServer', () => {
});
fs.__setMockFilesystem({
'root': {
root: {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
@ -206,17 +196,19 @@ describe('AssetServer', () => {
});
return server.getAssetData('imgs/b.png').then(data => {
expect(data).toEqual(objectContaining({
type: 'png',
name: 'b',
scales: [1, 2, 4, 4.5],
files: [
'/root/imgs/b@1x.png',
'/root/imgs/b@2x.png',
'/root/imgs/b@4x.png',
'/root/imgs/b@4.5x.png',
],
}));
expect(data).toEqual(
objectContaining({
type: 'png',
name: 'b',
scales: [1, 2, 4, 4.5],
files: [
'/root/imgs/b@1x.png',
'/root/imgs/b@2x.png',
'/root/imgs/b@4x.png',
'/root/imgs/b@4.5x.png',
],
}),
);
});
});
@ -227,7 +219,7 @@ describe('AssetServer', () => {
});
fs.__setMockFilesystem({
'root': {
root: {
imgs: {
'b@1x.jpg': 'b1 image',
'b@2x.jpg': 'b2 image',
@ -238,17 +230,19 @@ describe('AssetServer', () => {
});
return server.getAssetData('imgs/b.jpg').then(data => {
expect(data).toEqual(objectContaining({
type: 'jpg',
name: 'b',
scales: [1, 2, 4, 4.5],
files: [
'/root/imgs/b@1x.jpg',
'/root/imgs/b@2x.jpg',
'/root/imgs/b@4x.jpg',
'/root/imgs/b@4.5x.jpg',
],
}));
expect(data).toEqual(
objectContaining({
type: 'jpg',
name: 'b',
scales: [1, 2, 4, 4.5],
files: [
'/root/imgs/b@1x.jpg',
'/root/imgs/b@2x.jpg',
'/root/imgs/b@4x.jpg',
'/root/imgs/b@4.5x.jpg',
],
}),
);
});
});
@ -261,7 +255,7 @@ describe('AssetServer', () => {
});
mockFS = {
'root': {
root: {
imgs: {
'b@1x.jpg': 'b1 image',
'b@2x.jpg': 'b2 image',
@ -280,18 +274,20 @@ describe('AssetServer', () => {
hash.update(mockFS.root.imgs[name]);
}
return server.getAssetData('imgs/b.jpg').then(data =>
expect(data).toEqual(objectContaining({hash: hash.digest('hex')}))
);
return server
.getAssetData('imgs/b.jpg')
.then(data =>
expect(data).toEqual(objectContaining({hash: hash.digest('hex')})),
);
});
it('changes the hash when the passed-in file watcher emits an `all` event', () => {
return server.getAssetData('imgs/b.jpg').then(initialData => {
mockFS.root.imgs['b@4x.jpg'] = 'updated data';
server.onFileChange('all', '/root/imgs/b@4x.jpg');
return server.getAssetData('imgs/b.jpg').then(data =>
expect(data.hash).not.toEqual(initialData.hash)
);
return server
.getAssetData('imgs/b.jpg')
.then(data => expect(data.hash).not.toEqual(initialData.hash));
});
});
});

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -25,7 +26,6 @@ const readDir = denodeify(fs.readdir);
const readFile = denodeify(fs.readFile);
class AssetServer {
_roots: $ReadOnlyArray<string>;
_assetExts: $ReadOnlyArray<string>;
_hashes: Map<?string, string>;
@ -57,7 +57,10 @@ class AssetServer {
});
}
getAssetData(assetPath: string, platform: ?string = null): Promise<{|
getAssetData(
assetPath: string,
platform: ?string = null,
): Promise<{|
files: Array<string>,
hash: string,
name: string,
@ -109,22 +112,17 @@ class AssetServer {
* 4. Then try to pick platform-specific asset records
* 5. Then pick the closest resolution (rounding up) to the requested one
*/
_getAssetRecord(assetPath: string, platform: ?string = null): Promise<{|
_getAssetRecord(
assetPath: string,
platform: ?string = null,
): Promise<{|
files: Array<string>,
scales: Array<number>,
|}> {
const filename = path.basename(assetPath);
return (
this._findRoot(
this._roots,
path.dirname(assetPath),
assetPath,
)
.then(dir => Promise.all([
dir,
readDir(dir),
]))
return this._findRoot(this._roots, path.dirname(assetPath), assetPath)
.then(dir => Promise.all([dir, readDir(dir)]))
.then(res => {
const dir = res[0];
const files = res[1];
@ -137,8 +135,9 @@ class AssetServer {
let record;
if (platform != null) {
record = map.get(getAssetKey(assetData.assetName, platform)) ||
map.get(assetData.assetName);
record =
map.get(getAssetKey(assetData.assetName, platform)) ||
map.get(assetData.assetName);
} else {
record = map.get(assetData.assetName);
}
@ -146,33 +145,39 @@ class AssetServer {
if (!record) {
throw new Error(
/* $FlowFixMe: platform can be null */
`Asset not found: ${assetPath} for platform: ${platform}`
`Asset not found: ${assetPath} for platform: ${platform}`,
);
}
return record;
})
);
});
}
_findRoot(roots: $ReadOnlyArray<string>, dir: string, debugInfoFile: string): Promise<string> {
_findRoot(
roots: $ReadOnlyArray<string>,
dir: string,
debugInfoFile: string,
): Promise<string> {
return Promise.all(
roots.map(root => {
const absRoot = path.resolve(root);
// important: we want to resolve root + dir
// to ensure the requested path doesn't traverse beyond root
const absPath = path.resolve(root, dir);
return stat(absPath).then(fstat => {
// keep asset requests from traversing files
// up from the root (e.g. ../../../etc/hosts)
if (!absPath.startsWith(absRoot)) {
return stat(absPath).then(
fstat => {
// keep asset requests from traversing files
// up from the root (e.g. ../../../etc/hosts)
if (!absPath.startsWith(absRoot)) {
return {path: absPath, isValid: false};
}
return {path: absPath, isValid: fstat.isDirectory()};
},
_ => {
return {path: absPath, isValid: false};
}
return {path: absPath, isValid: fstat.isDirectory()};
}, _ => {
return {path: absPath, isValid: false};
});
})
},
);
}),
).then(stats => {
for (let i = 0; i < stats.length; i++) {
if (stats[i].isValid) {
@ -183,15 +188,22 @@ class AssetServer {
const rootsString = roots.map(s => `'${s}'`).join(', ');
throw new Error(
`'${debugInfoFile}' could not be found, because '${dir}' is not a ` +
`subdirectory of any of the roots (${rootsString})`,
`subdirectory of any of the roots (${rootsString})`,
);
});
}
_buildAssetMap(dir: string, files: $ReadOnlyArray<string>, platform: ?string): Map<string, {|
files: Array<string>,
scales: Array<number>,
|}> {
_buildAssetMap(
dir: string,
files: $ReadOnlyArray<string>,
platform: ?string,
): Map<
string,
{|
files: Array<string>,
scales: Array<number>,
|},
> {
const platforms = new Set(platform != null ? [platform] : []);
const assets = files.map(this._getAssetDataFromName.bind(this, platforms));
const map = new Map();
@ -214,7 +226,7 @@ class AssetServer {
const length = record.scales.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.scales[insertIndex]) {
if (asset.resolution < record.scales[insertIndex]) {
break;
}
}
@ -244,7 +256,8 @@ function hashFiles(files, hash, callback) {
return;
}
fs.createReadStream(files.shift())
fs
.createReadStream(files.shift())
.on('data', data => hash.update(data))
.once('end', () => hashFiles(files, hash, callback))
.once('error', error => callback(error));

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -36,10 +37,9 @@ export type Unbundle = {
type SourceMapFormat = 'undetermined' | 'indexed' | 'flattened';
const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL=';
const SOURCEMAPPING_URL = '\n//# sourceMappingURL=';
class Bundle extends BundleBase {
_dev: boolean | void;
_inlineSourceMap: string | void;
_minify: boolean | void;
@ -51,13 +51,21 @@ class Bundle extends BundleBase {
_sourceMapUrl: ?string;
postProcessBundleSourcemap: PostProcessBundleSourcemap;
constructor({sourceMapUrl, dev, minify, ramGroups, postProcessBundleSourcemap}: {
sourceMapUrl: ?string,
dev?: boolean,
minify?: boolean,
ramGroups?: Array<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';
@ -86,39 +94,44 @@ class Bundle extends BundleBase {
/* $FlowFixMe: erroneous change of signature. */
): Promise<void> {
const index = super.addModule(moduleTransport);
return resolver.wrapModule({
resolutionResponse,
module,
name: moduleTransport.name,
code: moduleTransport.code,
map: moduleTransport.map,
meta: moduleTransport.meta,
minify: this._minify,
dev: this._dev,
}).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) {
const usesRawMappings = isRawMappings(map);
return resolver
.wrapModule({
resolutionResponse,
module,
name: moduleTransport.name,
code: moduleTransport.code,
map: moduleTransport.map,
meta: moduleTransport.meta,
minify: this._minify,
dev: this._dev,
})
.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) {
const usesRawMappings = isRawMappings(map);
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`
);
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}));
});
this.replaceModuleAt(
index,
new ModuleTransport({...moduleTransport, code, map}),
);
});
}
finalize(options: FinalizeOptions) {
@ -138,15 +151,17 @@ class Bundle extends BundleBase {
_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},
}));
super.addModule(
new ModuleTransport({
name,
id: -this._numRequireCalls - 1,
code,
virtual: true,
sourceCode: code,
sourcePath: name + '.js',
meta: {preloaded: true},
}),
);
this._numRequireCalls += 1;
}
@ -191,7 +206,11 @@ class Bundle extends BundleBase {
lazyModules,
get groups() {
if (!groups) {
groups = createRamBundleGroups(ramGroups || [], lazyModules, subtree);
groups = createRamBundleGroups(
ramGroups || [],
lazyModules,
subtree,
);
}
return groups;
},
@ -226,10 +245,10 @@ class Bundle extends BundleBase {
!Array.isArray(module.map),
`Unexpected raw mappings for ${module.sourcePath}`,
);
let map: SourceMap = module.map == null || module.virtual
? generateSourceMapForVirtualModule(module)
: module.map;
let map: SourceMap =
module.map == null || module.virtual
? generateSourceMapForVirtualModule(module)
: module.map;
if (options.excludeSource && isMappingsMap(map)) {
map = {...map, sourcesContent: []};
@ -287,10 +306,12 @@ class Bundle extends BundleBase {
}
getJSModulePaths() {
return this.getModules()
// Filter out non-js files. Like images etc.
.filter(module => !module.virtual)
.map(module => module.sourcePath);
return (
this.getModules()
// Filter out non-js files. Like images etc.
.filter(module => !module.virtual)
.map(module => module.sourcePath)
);
}
getDebugInfo() {
@ -308,11 +329,18 @@ class Bundle extends BundleBase {
'}',
'</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'),
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');
}
@ -326,7 +354,7 @@ function generateSourceMapForVirtualModule(module): MappingsMap {
let mappings = 'AAAA;';
for (let i = 1; i < module.code.split('\n').length; i++) {
mappings += 'AACA;';
mappings += 'AACA;';
}
return {
@ -350,7 +378,7 @@ function partition(array, predicate) {
return [included, excluded];
}
function * subtree(
function* subtree(
moduleTransport: ModuleTransport,
moduleTransportsByPath: Map<string, ModuleTransport>,
seen = new Set(),
@ -359,13 +387,14 @@ function * subtree(
const {meta} = moduleTransport;
invariant(
meta != null,
'Unexpected module transport without meta information: ' + moduleTransport.sourcePath,
'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);
yield* subtree(dependency, moduleTransportsByPath, seen);
}
}
}

View File

@ -7,7 +7,9 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
const ModuleTransport = require('../lib/ModuleTransport');
@ -24,7 +26,6 @@ export type GetSourceOptions = {
};
class BundleBase {
_assets: Array<mixed>;
_finalized: boolean;
_mainModuleId: number | void;
@ -104,7 +105,9 @@ class BundleBase {
assertFinalized(message?: string) {
if (!this._finalized) {
throw new Error(message || 'Bundle needs to be finalized before getting any source');
throw new Error(
message || 'Bundle needs to be finalized before getting any source',
);
}
}

View File

@ -8,6 +8,7 @@
*
* @format
*/
'use strict';
const Bundle = require('../Bundle');

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
@ -23,8 +26,7 @@ jest
.mock('../Bundle')
.mock('../HMRBundle')
.mock('../../Logger')
.mock('/path/to/transformer.js', () => ({}), {virtual: true})
;
.mock('/path/to/transformer.js', () => ({}), {virtual: true});
var Bundler = require('../');
var Resolver = require('../../Resolver');
@ -36,7 +38,6 @@ const path = require('path');
const {any, objectContaining} = expect;
var commonOptions = {
allowBundleUpdates: false,
assetExts: defaults.assetExts,
@ -52,7 +53,6 @@ var commonOptions = {
};
describe('Bundler', function() {
function createModule({
path,
id,
@ -100,10 +100,12 @@ describe('Bundler', function() {
getModuleSystemDependencies,
};
});
Resolver.load = jest.fn().mockImplementation(opts => Promise.resolve(new Resolver(opts)));
Resolver.load = jest
.fn()
.mockImplementation(opts => Promise.resolve(new Resolver(opts)));
fs.__setMockFilesystem({
'path': {'to': {'transformer.js': ''}},
path: {to: {'transformer.js': ''}},
});
fs.statSync.mockImplementation(function() {
@ -151,7 +153,7 @@ describe('Bundler', function() {
options: transformOptions,
getModuleId: () => 123,
getResolvedDependencyPairs: () => [],
})
}),
);
getModuleSystemDependencies.mockImplementation(function() {
@ -188,12 +190,17 @@ describe('Bundler', function() {
},
},
},
])
]),
);
});
it('allows overriding the platforms array', () => {
expect(bundler._opts.platforms).toEqual(['ios', 'android', 'windows', 'web']);
expect(bundler._opts.platforms).toEqual([
'ios',
'android',
'windows',
'web',
]);
const b = new Bundler({
...commonOptions,
projectRoots,
@ -217,96 +224,115 @@ describe('Bundler', function() {
};
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;
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,
return bundler
.bundle({
entryFile: '/root/foo.js',
runBeforeMainModule: [],
allowUpdates: false,
}]);
runModule: true,
sourceMapUrl: 'source_map_url',
})
.then(bundle => {
const ithAddedModule = i => bundle.addModule.mock.calls[i][2].path;
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',
}]);
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');
// TODO(amasad) This fails with 0 != 5 in OSS
//expect(ProgressBar.prototype.tick.mock.calls.length).toEqual(modules.length);
});
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('loads and runs asset plugins', function() {
jest.mock('mockPlugin1', () => {
return asset => {
asset.extraReverseHash = asset.hash.split('').reverse().join('');
return asset;
};
}, {virtual: true});
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});
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,
}]);
});
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', () => {
@ -324,34 +350,39 @@ describe('Bundler', function() {
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(Number),
code: any(String),
sourceCode: any(String),
sourcePath: x.path,
meta: any(Object),
polyfill: !!x.isPolyfill(),
})),
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(Number),
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 postProcessModules = jest
.fn()
.mockImplementation((ms, e) => ms.reverse());
const b = new Bundler({
...commonOptions,
@ -361,21 +392,23 @@ describe('Bundler', function() {
});
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;
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));
});
[
'/root/file.json',
'/root/img/new_image.png',
'/root/bar.js',
'/root/foo.js',
].forEach((path, ix) => expect(ithAddedModule(ix)).toEqual(path));
});
});
});
@ -423,18 +456,21 @@ describe('Bundler', function() {
}),
);
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',
]));
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',
]),
);
});
});
});

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -68,7 +69,7 @@ export type GetTransformOptionsOpts = {|
export type GetTransformOptions = (
mainModuleName: string,
options: GetTransformOptionsOpts,
getDependenciesOf: string => Promise<Array<string>>,
getDependenciesOf: (string) => Promise<Array<string>>,
) => Promise<ExtraTransformOptions>;
export type AssetDescriptor = {
@ -152,7 +153,6 @@ type Options = {|
const {hasOwnProperty} = Object;
class Bundler {
_opts: Options;
_getModuleId: (opts: Module) => number;
_transformer: Transformer;
@ -167,14 +167,16 @@ class Bundler {
opts.projectRoots.forEach(verifyRootExists);
const transformModuleStr = fs.readFileSync(opts.transformModulePath);
const transformModuleHash =
crypto.createHash('sha1').update(transformModuleStr).digest('hex');
const transformModuleHash = crypto
.createHash('sha1')
.update(transformModuleStr)
.digest('hex');
const stableProjectRoots = opts.projectRoots.map(p => {
return path.relative(path.join(__dirname, '../../../..'), p);
});
const cacheKeyParts = [
const cacheKeyParts = [
'react-packager-cache',
VERSION,
opts.cacheVersion,
@ -193,17 +195,20 @@ class Bundler {
}
}
const transformCacheKey = crypto.createHash('sha1').update(
cacheKeyParts.join('$'),
).digest('hex');
const transformCacheKey = crypto
.createHash('sha1')
.update(cacheKeyParts.join('$'))
.digest('hex');
debug(`Using transform cache key "${transformCacheKey}"`);
this._transformer = new Transformer(
opts.transformModulePath,
opts.maxWorkers,
{
stdoutChunk: chunk => opts.reporter.update({type: 'worker_stdout_chunk', chunk}),
stderrChunk: chunk => opts.reporter.update({type: 'worker_stderr_chunk', chunk}),
stdoutChunk: chunk =>
opts.reporter.update({type: 'worker_stdout_chunk', chunk}),
stderrChunk: chunk =>
opts.reporter.update({type: 'worker_stderr_chunk', chunk}),
},
opts.workerPath,
);
@ -232,8 +237,8 @@ class Bundler {
reporter: opts.reporter,
resetCache: opts.resetCache,
sourceExts: opts.sourceExts,
transformCode:
(module, code, transformCodeOptions) => this._transformer.transformFile(
transformCode: (module, code, transformCodeOptions) =>
this._transformer.transformFile(
module.path,
module.localPath,
code,
@ -251,8 +256,8 @@ class Bundler {
end() {
this._transformer.kill();
return this._resolverPromise.then(
resolver => resolver.getDependencyGraph().getWatcher().end(),
return this._resolverPromise.then(resolver =>
resolver.getDependencyGraph().getWatcher().end(),
);
}
@ -264,41 +269,40 @@ class Bundler {
}): 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,
}));
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,
);
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,
);
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));
_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);
@ -316,13 +320,22 @@ class Bundler {
);
return (
prefix + resource +
'.' + extensionOverride + '?' +
'platform=' + (platform || '') + '&runModule=false&entryModuleOnly=true&hot=true'
prefix +
resource +
'.' +
extensionOverride +
'?' +
'platform=' +
(platform || '') +
'&runModule=false&entryModuleOnly=true&hot=true'
);
}
hmrBundle(options: {platform: ?string}, host: string, port: number): Promise<HMRBundle> {
hmrBundle(
options: {platform: ?string},
host: string,
port: number,
): Promise<HMRBundle> {
return this._bundle({
...options,
bundle: new HMRBundle({
@ -372,43 +385,54 @@ class Bundler {
runModule?: boolean,
unbundle?: boolean,
}) {
const onResolutionResponse = (response: ResolutionResponse<Module, BundlingOptions>) => {
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 || '')
module.path.endsWith(entryFile || ''),
);
} else {
response.dependencies = moduleSystemDeps.concat(response.dependencies);
}
};
const finalizeBundle = ({bundle: finalBundle, transformedModules, response, modulesByName}: {
const finalizeBundle = ({
bundle: finalBundle,
transformedModules,
response,
modulesByName,
}: {
bundle: Bundle,
transformedModules: Array<{module: Module, transformed: ModuleTransport}>,
response: ResolutionResponse<Module, BundlingOptions>,
modulesByName: {[name: string]: Module},
}) =>
this._resolverPromise.then(resolver => Promise.all(
transformedModules.map(({module, transformed}) =>
finalBundle.addModule(resolver, response, module, transformed)
this._resolverPromise
.then(resolver =>
Promise.all(
transformedModules.map(({module, transformed}) =>
finalBundle.addModule(resolver, response, module, transformed),
),
),
)
)).then(() => {
const runBeforeMainModuleIds = Array.isArray(runBeforeMainModule)
? runBeforeMainModule
.map(name => modulesByName[name])
.filter(Boolean)
.map(response.getModuleId)
: undefined;
.then(() => {
const runBeforeMainModuleIds = Array.isArray(runBeforeMainModule)
? runBeforeMainModule
.map(name => modulesByName[name])
.filter(Boolean)
.map(response.getModuleId)
: undefined;
finalBundle.finalize({
runModule,
runBeforeMainModule: runBeforeMainModuleIds,
allowUpdates: this._opts.allowBundleUpdates,
finalBundle.finalize({
runModule,
runBeforeMainModule: runBeforeMainModuleIds,
allowUpdates: this._opts.allowBundleUpdates,
});
return finalBundle;
});
return finalBundle;
});
return this._buildBundle({
entryFile,
@ -445,12 +469,13 @@ class Bundler {
finalizeBundle = emptyFunction,
onProgress = emptyFunction,
}: *) {
const transformingFilesLogEntry =
log(createActionStartEntry({
const transformingFilesLogEntry = log(
createActionStartEntry({
action_name: 'Transforming files',
entry_point: entryFile,
environment: dev ? 'dev' : 'prod',
}));
}),
);
const modulesByName = Object.create(null);
@ -467,9 +492,10 @@ class Bundler {
});
}
return Promise.all(
[this._resolverPromise, resolutionResponse],
).then(([resolver, response]) => {
return Promise.all([
this._resolverPromise,
resolutionResponse,
]).then(([resolver, response]) => {
bundle.setRamGroups(response.options.ramGroups);
log(createActionEndEntry(transformingFilesLogEntry));
@ -477,12 +503,15 @@ class Bundler {
// 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;
if (response.dependencies.length > 1) {
// skip HMR requests
const numModuleSystemDependencies = resolver.getModuleSystemDependencies(
{dev, unbundle},
).length;
const dependencyIndex =
(response.numPrependedDependencies || 0) + numModuleSystemDependencies;
(response.numPrependedDependencies || 0) +
numModuleSystemDependencies;
if (dependencyIndex in response.dependencies) {
entryFilePath = response.dependencies[dependencyIndex].path;
@ -490,28 +519,27 @@ class Bundler {
}
const modulesByTransport: Map<ModuleTransport, Module> = new Map();
const toModuleTransport: Module => Promise<ModuleTransport> =
module =>
this._toModuleTransport({
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);
modulesByName[transformed.name] = module;
onModuleTransformed({
module,
response,
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);
modulesByName[transformed.name] = module;
onModuleTransformed({
module,
response,
bundle,
transformed,
});
return transformed;
transformed,
});
return transformed;
});
const p = this._opts.postProcessModules;
const postProcess = p
@ -525,8 +553,14 @@ class Bundler {
module: modulesByTransport.get(transformed),
transformed,
}));
return finalizeBundle({bundle, transformedModules, response, modulesByName});
}).then(() => bundle);
return finalizeBundle({
bundle,
transformedModules,
response,
modulesByName,
});
})
.then(() => bundle);
});
}
@ -545,26 +579,25 @@ class Bundler {
hot?: boolean,
generateSourceMaps?: boolean,
}): Promise<Array<Module>> {
return this.getTransformOptions(
entryFile,
{
enableBabelRCLookup: this._opts.enableBabelRCLookup,
dev,
generateSourceMaps,
hot,
minify,
platform,
projectRoots: this._projectRoots,
},
).then(bundlingOptions =>
return this.getTransformOptions(entryFile, {
enableBabelRCLookup: this._opts.enableBabelRCLookup,
dev,
generateSourceMaps,
hot,
minify,
platform,
projectRoots: this._projectRoots,
}).then(bundlingOptions =>
this._resolverPromise.then(resolver =>
resolver.getShallowDependencies(entryFile, bundlingOptions.transformer),
)
),
);
}
getModuleForPath(entryFile: string): Promise<Module> {
return this._resolverPromise.then(resolver => resolver.getModuleForPath(entryFile));
return this._resolverPromise.then(resolver =>
resolver.getModuleForPath(entryFile),
);
}
async getDependencies({
@ -612,42 +645,47 @@ class Bundler {
return response;
}
getOrderedDependencyPaths({entryFile, dev, platform, minify, generateSourceMaps}: {
getOrderedDependencyPaths({
entryFile,
dev,
platform,
minify,
generateSourceMaps,
}: {
+entryFile: string,
+dev: boolean,
+platform: string,
+minify: boolean,
+generateSourceMaps: boolean,
}) {
return this.getDependencies({entryFile, dev, platform, minify, generateSourceMaps}).then(
({dependencies}) => {
const ret = [];
const promises = [];
const placeHolder = {};
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 this.getDependencies({
entryFile,
dev,
platform,
minify,
generateSourceMaps,
}).then(({dependencies}) => {
const ret = [];
const promises = [];
const placeHolder = {};
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;
return Promise.all(promises).then(assetsData => {
assetsData.forEach(({files}) => {
const index = ret.indexOf(placeHolder);
ret.splice(index, 1, ...files);
});
}
);
return ret;
});
});
}
_toModuleTransport({
@ -673,7 +711,12 @@ class Bundler {
if (module.isAsset()) {
moduleTransport = this._generateAssetModule(
bundle, module, moduleId, assetPlugins, transformOptions.platform);
bundle,
module,
moduleId,
assetPlugins,
transformOptions.platform,
);
}
if (moduleTransport) {
@ -683,15 +726,14 @@ class Bundler {
return Promise.all([
module.getName(),
module.read(transformOptions),
]).then((
[name, {code, dependencies, dependencyOffsets, map, source}]
) => {
]).then(([name, {code, dependencies, dependencyOffsets, map, source}]) => {
const {preloadedModules} = options;
const isPolyfill = module.isPolyfill();
const preloaded =
module.path === entryFilePath ||
isPolyfill ||
preloadedModules && hasOwnProperty.call(preloadedModules, module.path);
(preloadedModules &&
hasOwnProperty.call(preloadedModules, module.path));
return new ModuleTransport({
name,
@ -721,35 +763,45 @@ class Bundler {
const isImage = isAssetTypeAnImage(extname(module.path).slice(1));
return this._assetServer.getAssetData(localPath, platform).then(assetData => {
return Promise.all([isImage ? sizeOf(assetData.files[0]) : null, assetData]);
}).then(res => {
const dimensions = res[0];
const assetData = res[1];
const scale = assetData.scales[0];
const asset = {
__packager_asset: true,
fileSystemLocation: pathDirname(module.path),
httpServerLocation: assetUrlPath,
width: dimensions ? dimensions.width / scale : undefined,
height: dimensions ? dimensions.height / scale : undefined,
scales: assetData.scales,
files: assetData.files,
hash: assetData.hash,
name: assetData.name,
type: assetData.type,
};
return this._assetServer
.getAssetData(localPath, platform)
.then(assetData => {
return Promise.all([
isImage ? sizeOf(assetData.files[0]) : null,
assetData,
]);
})
.then(res => {
const dimensions = res[0];
const assetData = res[1];
const scale = assetData.scales[0];
const asset = {
__packager_asset: true,
fileSystemLocation: pathDirname(module.path),
httpServerLocation: assetUrlPath,
width: dimensions ? dimensions.width / scale : undefined,
height: dimensions ? dimensions.height / scale : undefined,
scales: assetData.scales,
files: assetData.files,
hash: assetData.hash,
name: assetData.name,
type: assetData.type,
};
return this._applyAssetPlugins(assetPlugins, asset);
}).then(asset => {
const {code, dependencies, dependencyOffsets} =
generateAssetTransformResult(this._opts.assetRegistryPath, asset);
return {
asset,
code,
meta: {dependencies, dependencyOffsets, preloaded: null},
};
});
return this._applyAssetPlugins(assetPlugins, asset);
})
.then(asset => {
const {
code,
dependencies,
dependencyOffsets,
} = generateAssetTransformResult(this._opts.assetRegistryPath, asset);
return {
asset,
code,
meta: {dependencies, dependencyOffsets, preloaded: null},
};
});
}
_applyAssetPlugins(
@ -769,7 +821,7 @@ class Bundler {
// applying the remaining plugins
if (typeof result.then === 'function') {
return result.then(resultAsset =>
this._applyAssetPlugins(remainingAssetPlugins, resultAsset)
this._applyAssetPlugins(remainingAssetPlugins, resultAsset),
);
} else {
return this._applyAssetPlugins(remainingAssetPlugins, result);
@ -811,14 +863,19 @@ class Bundler {
platform: ?string,
projectRoots: $ReadOnlyArray<string>,
|},
): Promise<BundlingOptions> {
): Promise<BundlingOptions> {
const getDependencies = (entryFile: string) =>
this.getDependencies({...options, entryFile})
.then(r => r.dependencies.map(d => d.path));
this.getDependencies({...options, entryFile}).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)
? await this._getTransformOptions(
mainModuleName,
{dev, hot, platform},
getDependencies,
)
: {};
const {transform = {}} = extraOptions;
@ -846,7 +903,6 @@ class Bundler {
getResolver(): Promise<Resolver> {
return this._resolverPromise;
}
}
function verifyRootExists(root) {

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -77,13 +78,16 @@ class Generator {
*/
addSimpleMapping(generatedLine: number, generatedColumn: number): void {
const last = this.last;
if (this.source === -1 ||
generatedLine === last.generatedLine &&
generatedColumn < last.generatedColumn ||
generatedLine < last.generatedLine) {
const msg = this.source === -1
? 'Cannot add mapping before starting a file with `addFile()`'
: 'Mapping is for a position preceding an earlier mapping';
if (
this.source === -1 ||
(generatedLine === last.generatedLine &&
generatedColumn < last.generatedColumn) ||
generatedLine < last.generatedLine
) {
const msg =
this.source === -1
? 'Cannot add mapping before starting a file with `addFile()`'
: 'Mapping is for a position preceding an earlier mapping';
throw new Error(msg);
}
@ -130,7 +134,11 @@ class Generator {
name: string,
): void {
this.addSourceMapping(
generatedLine, generatedColumn, sourceLine, sourceColumn);
generatedLine,
generatedColumn,
sourceLine,
sourceColumn,
);
const last = this.last;
const nameIndex = this.names.indexFor(name);
@ -158,14 +166,16 @@ class Generator {
* This is ~2.5x faster than calling `JSON.stringify(generator.toMap())`
*/
toString(file?: string): string {
return ('{' +
return (
'{' +
'"version":3,' +
(file ? `"file":${JSON.stringify(file)},` : '') +
`"sources":${JSON.stringify(this.sources)},` +
`"sourcesContent":${JSON.stringify(this.sourcesContent)},` +
`"names":${JSON.stringify(this.names.items())},` +
`"mappings":"${this.builder.toString()}"` +
'}');
'}'
);
}
}

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';
const B64Builder = require('../B64Builder');

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';
const Generator = require('../Generator');
@ -26,11 +29,12 @@ it('adds file name and source code when starting a file', () => {
generator.startFile(file1, source1);
generator.startFile(file2, source2);
expect(generator.toMap())
.toEqual(objectContaining({
expect(generator.toMap()).toEqual(
objectContaining({
sources: [file1, file2],
sourcesContent: [source1, source2],
}));
}),
);
});
it('throws when adding a mapping without starting a file', () => {
@ -46,29 +50,32 @@ it('throws when adding a mapping after ending a file', () => {
it('can add a mapping for generated code without corresponding original source', () => {
generator.startFile('apples', 'pears');
generator.addSimpleMapping(12, 87);
expect(generator.toMap())
.toEqual(objectContaining({
expect(generator.toMap()).toEqual(
objectContaining({
mappings: ';;;;;;;;;;;uF',
}));
}),
);
});
it('can add a mapping with corresponding location in the original source', () => {
generator.startFile('apples', 'pears');
generator.addSourceMapping(2, 3, 456, 7);
expect(generator.toMap())
.toEqual(objectContaining({
expect(generator.toMap()).toEqual(
objectContaining({
mappings: ';GAucO',
}));
}),
);
});
it('can add a mapping with source location and symbol name', () => {
generator.startFile('apples', 'pears');
generator.addNamedSourceMapping(9, 876, 54, 3, 'arbitrary');
expect(generator.toMap())
.toEqual(objectContaining({
expect(generator.toMap()).toEqual(
objectContaining({
mappings: ';;;;;;;;42BAqDGA',
names: ['arbitrary'],
}));
}),
);
});
describe('full map generation', () => {
@ -94,10 +101,11 @@ describe('full map generation', () => {
});
it('can add a `file` property to the map', () => {
expect(generator.toMap('arbitrary'))
.toEqual(objectContaining({
expect(generator.toMap('arbitrary')).toEqual(
objectContaining({
file: 'arbitrary',
}));
}),
);
});
it('supports direct JSON serialization', () => {

View File

@ -1,11 +1,14 @@
/**
/**
* Copyright (c) 2017-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.
*
* @format
*/
'use strict';
const Generator = require('../Generator');
@ -13,23 +16,29 @@ const {compactMapping, fromRawMappings} = require('..');
describe('flattening mappings / compacting', () => {
it('flattens simple mappings', () => {
expect(compactMapping({generated: {line: 12, column: 34}}))
.toEqual([12, 34]);
expect(compactMapping({generated: {line: 12, column: 34}})).toEqual([
12,
34,
]);
});
it('flattens mappings with a source location', () => {
expect(compactMapping({
generated: {column: 34, line: 12},
original: {column: 78, line: 56},
})).toEqual([12, 34, 56, 78]);
expect(
compactMapping({
generated: {column: 34, line: 12},
original: {column: 78, line: 56},
}),
).toEqual([12, 34, 56, 78]);
});
it('flattens mappings with a source location and a symbol name', () => {
expect(compactMapping({
generated: {column: 34, line: 12},
name: 'arbitrary',
original: {column: 78, line: 56},
})).toEqual([12, 34, 56, 78, 'arbitrary']);
expect(
compactMapping({
generated: {column: 34, line: 12},
name: 'arbitrary',
original: {column: 78, line: 56},
}),
).toEqual([12, 34, 56, 78, 'arbitrary']);
});
});
@ -39,44 +48,45 @@ describe('build map from raw mappings', () => {
});
it('returns a working source map containing all mappings', () => {
const input = [{
code: lines(11),
map: [
[1, 2],
[3, 4, 5, 6, 'apples'],
[7, 8, 9, 10],
[11, 12, 13, 14, 'pears'],
],
sourceCode: 'code1',
sourcePath: 'path1',
}, {
code: lines(3),
map: [
[1, 2],
[3, 4, 15, 16, 'bananas'],
],
sourceCode: 'code2',
sourcePath: 'path2',
}, {
code: lines(23),
map: [
[11, 12],
[13, 14, 15, 16, 'bananas'],
[17, 18, 19, 110],
[21, 112, 113, 114, 'pears'],
],
sourceCode: 'code3',
sourcePath: 'path3',
}];
const input = [
{
code: lines(11),
map: [
[1, 2],
[3, 4, 5, 6, 'apples'],
[7, 8, 9, 10],
[11, 12, 13, 14, 'pears'],
],
sourceCode: 'code1',
sourcePath: 'path1',
},
{
code: lines(3),
map: [[1, 2], [3, 4, 15, 16, 'bananas']],
sourceCode: 'code2',
sourcePath: 'path2',
},
{
code: lines(23),
map: [
[11, 12],
[13, 14, 15, 16, 'bananas'],
[17, 18, 19, 110],
[21, 112, 113, 114, 'pears'],
],
sourceCode: 'code3',
sourcePath: 'path3',
},
];
expect(fromRawMappings(input).toMap())
.toEqual({
mappings: 'E;;IAIMA;;;;QAII;;;;YAIIC;E;;ICEEC;;;;;;;;;;;Y;;cCAAA;;;;kBAI8F;;;;gHA8FID',
names: ['apples', 'pears', 'bananas'],
sources: ['path1', 'path2', 'path3'],
sourcesContent: ['code1', 'code2', 'code3'],
version: 3,
});
expect(fromRawMappings(input).toMap()).toEqual({
mappings:
'E;;IAIMA;;;;QAII;;;;YAIIC;E;;ICEEC;;;;;;;;;;;Y;;cCAAA;;;;kBAI8F;;;;gHA8FID',
names: ['apples', 'pears', 'bananas'],
sources: ['path1', 'path2', 'path3'],
sourcesContent: ['code1', 'code2', 'code3'],
version: 3,
});
});
});

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
/**
@ -53,14 +54,70 @@
// A map of values to characters for the b64 encoding
const CHAR_MAP = [
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f,
0x41,
0x42,
0x43,
0x44,
0x45,
0x46,
0x47,
0x48,
0x49,
0x4a,
0x4b,
0x4c,
0x4d,
0x4e,
0x4f,
0x50,
0x51,
0x52,
0x53,
0x54,
0x55,
0x56,
0x57,
0x58,
0x59,
0x5a,
0x61,
0x62,
0x63,
0x64,
0x65,
0x66,
0x67,
0x68,
0x69,
0x6a,
0x6b,
0x6c,
0x6d,
0x6e,
0x6f,
0x70,
0x71,
0x72,
0x73,
0x74,
0x75,
0x76,
0x77,
0x78,
0x79,
0x7a,
0x30,
0x31,
0x32,
0x33,
0x34,
0x35,
0x36,
0x37,
0x38,
0x39,
0x2b,
0x2f,
];
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
@ -93,9 +150,7 @@ const VLQ_CONTINUATION_BIT = VLQ_BASE;
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
function toVLQSigned(value) {
return value < 0
? ((-value) << 1) + 1
: (value << 1) + 0;
return value < 0 ? (-value << 1) + 1 : (value << 1) + 0;
}
/**

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -18,10 +19,12 @@ import type {RawMapping as BabelRawMapping} from 'babel-generator';
type GeneratedCodeMapping = [number, number];
type SourceMapping = [number, number, number, number];
type SourceMappingWithName = [number, number, number, number, string];
type SourceMappingWithName = [number, number, number, number, string];
export type RawMapping =
SourceMappingWithName | SourceMapping | GeneratedCodeMapping;
| SourceMappingWithName
| SourceMapping
| GeneratedCodeMapping;
/**
* Creates a source map from modules with "raw mappings", i.e. an array of
@ -40,7 +43,7 @@ function fromRawMappings(modules: Array<ModuleTransport>): Generator {
addMappingsForFile(generator, map, module, carryOver);
} else if (map != null) {
throw new Error(
`Unexpected module with full source map found: ${module.sourcePath}`
`Unexpected module with full source map found: ${module.sourcePath}`,
);
}
@ -74,7 +77,6 @@ function addMappingsForFile(generator, mappings, module, carryOver) {
}
generator.endFile();
}
function addMapping(generator, mapping, carryOver, columnOffset) {
@ -89,8 +91,15 @@ function addMapping(generator, mapping, carryOver, columnOffset) {
generator.addSourceMapping(line, column, mapping[2], mapping[3]);
} else if (n === 5) {
generator.addNamedSourceMapping(
line,
column,
// $FlowIssue #15579526
line, column, mapping[2], mapping[3], mapping[4]);
mapping[2],
// $FlowIssue #15579526
mapping[3],
// $FlowIssue #15579526
mapping[4],
);
} else {
throw new Error(`Invalid mapping: [${mapping.join(', ')}]`);
}

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -23,27 +24,41 @@ type SubTree<T: ModuleTransportLike> = (
moduleTransportsByPath: Map<string, T>,
) => Generator<number, void, void>;
const assetPropertyBlacklist = new Set([
'files',
'fileSystemLocation',
'path',
]);
const assetPropertyBlacklist = new Set(['files', 'fileSystemLocation', 'path']);
function generateAssetCodeFileAst(
assetRegistryPath: string,
assetDescriptor: AssetDescriptor,
): Object {
const properDescriptor = filterObject(assetDescriptor, assetPropertyBlacklist);
const descriptorAst = babylon.parseExpression(JSON.stringify(properDescriptor));
const properDescriptor = filterObject(
assetDescriptor,
assetPropertyBlacklist,
);
const descriptorAst = babylon.parseExpression(
JSON.stringify(properDescriptor),
);
const t = babel.types;
const moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports'));
const requireCall =
t.callExpression(t.identifier('require'), [t.stringLiteral(assetRegistryPath)]);
const registerAssetFunction = t.memberExpression(requireCall, t.identifier('registerAsset'));
const registerAssetCall = t.callExpression(registerAssetFunction, [descriptorAst]);
return t.file(t.program([
t.expressionStatement(t.assignmentExpression('=', moduleExports, registerAssetCall)),
]));
const moduleExports = t.memberExpression(
t.identifier('module'),
t.identifier('exports'),
);
const requireCall = t.callExpression(t.identifier('require'), [
t.stringLiteral(assetRegistryPath),
]);
const registerAssetFunction = t.memberExpression(
requireCall,
t.identifier('registerAsset'),
);
const registerAssetCall = t.callExpression(registerAssetFunction, [
descriptorAst,
]);
return t.file(
t.program([
t.expressionStatement(
t.assignmentExpression('=', moduleExports, registerAssetCall),
),
]),
);
}
function generateAssetTransformResult(
@ -66,9 +81,11 @@ function generateAssetTransformResult(
// Test extension against all types supported by image-size module.
// If it's not one of these, we won't treat it as an image.
function isAssetTypeAnImage(type: string): boolean {
return [
'png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff',
].indexOf(type) !== -1;
return (
['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff'].indexOf(
type,
) !== -1
);
}
function filterObject(object, blacklist) {
@ -95,18 +112,17 @@ function createRamBundleGroups<T: ModuleTransportLike>(
// build a map of group root IDs to an array of module IDs in the group
const result: Map<number, Set<number>> = new Map(
ramGroups
.map(modulePath => {
const root = byPath.get(modulePath);
if (root == null) {
throw Error(`Group root ${modulePath} is not part of the bundle`);
}
return [
root.id,
// `subtree` yields the IDs of all transitive dependencies of a module
new Set(subtree(root, byPath)),
];
})
ramGroups.map(modulePath => {
const root = byPath.get(modulePath);
if (root == null) {
throw Error(`Group root ${modulePath} is not part of the bundle`);
}
return [
root.id,
// `subtree` yields the IDs of all transitive dependencies of a module
new Set(subtree(root, byPath)),
];
}),
);
if (ramGroups.length > 1) {
@ -124,9 +140,10 @@ function createRamBundleGroups<T: ModuleTransportLike>(
const parentNames = parents.map(byId.get, byId);
const lastName = parentNames.pop();
throw new Error(
`Module ${byId.get(moduleId) || moduleId} belongs to groups ${
parentNames.join(', ')}, and ${String(lastName)
}. Ensure that each module is only part of one group.`
`Module ${byId.get(moduleId) ||
moduleId} belongs to groups ${parentNames.join(', ')}, and ${String(
lastName,
)}. Ensure that each module is only part of one group.`,
);
}
}
@ -134,7 +151,7 @@ function createRamBundleGroups<T: ModuleTransportLike>(
return result;
}
function * filter(iterator, predicate) {
function* filter(iterator, predicate) {
for (const value of iterator) {
if (predicate(value)) {
yield value;