Add new plainJSSerializer that uses directly the Graph object

Reviewed By: mjesun

Differential Revision: D7211546

fbshipit-source-id: 7e6a0a0123f46a9037ecae845d29daea43e1ebed
This commit is contained in:
Rafael Oleza 2018-03-19 10:04:53 -07:00 committed by Facebook Github Bot
parent a4afdcb2e8
commit 3ac0bb47d9
6 changed files with 346 additions and 52 deletions

View File

@ -12,13 +12,12 @@
const DeltaCalculator = require('./DeltaCalculator');
const addParamsToDefineCall = require('../lib/addParamsToDefineCall');
const createModuleIdFactory = require('../lib/createModuleIdFactory');
const crypto = require('crypto');
const defaults = require('../defaults');
const getPreludeCode = require('../lib/getPreludeCode');
const nullthrows = require('fbjs/lib/nullthrows');
const {wrapModule} = require('./Serializers/helpers/js');
const {EventEmitter} = require('events');
import type Bundler from '../Bundler';
@ -444,27 +443,10 @@ class DeltaTransformer extends EventEmitter {
): Promise<[number, ?DeltaEntry]> {
const name = this._dependencyGraph.getHasteName(edge.path);
const dependencyPairs = edge ? edge.dependencies : new Map();
let wrappedCode;
// Get the absolute path of each of the module dependencies from the
// dependency edges. The module dependencies ensure correct order, while
// the dependency edges do not ensure the same order between rebuilds.
const dependencies = Array.from(edge.dependencies.keys()).map(dependency =>
nullthrows(dependencyPairs.get(dependency)),
);
if (edge.output.type !== 'script') {
wrappedCode = this._addDependencyMap({
code: edge.output.code,
dependencies,
name,
path: edge.path,
const wrappedCode = wrapModule(edge, {
createModuleIdFn: this._getModuleId,
dev: transformOptions.dev,
});
} else {
wrappedCode = edge.output.code;
}
const {code, map} = transformOptions.minify
? await this._bundler.minifyModule(
@ -490,35 +472,6 @@ class DeltaTransformer extends EventEmitter {
];
}
/**
* Function to add the mapping object between local module ids and
* actual bundle module ids for dependencies. This way, we can do the path
* replacements on require() calls on transformers (since local ids do not
* change between bundles).
*/
_addDependencyMap({
code,
dependencies,
name,
path,
}: {
code: string,
dependencies: $ReadOnlyArray<string>,
name: string,
path: string,
}): string {
const moduleId = this._getModuleId(path);
const params = [moduleId, dependencies.map(this._getModuleId)];
// Add the module name as the last parameter (to make it easier to do
// requires by name when debugging).
if (this._bundleOptions.dev) {
params.push(name);
}
return addParamsToDefineCall(code, ...params);
}
_onFileChange = () => {
this.emit('change');
};

View File

@ -0,0 +1,119 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+javascript_foundation
* @format
*/
'use strict';
const createModuleIdFactory = require('../../../lib/createModuleIdFactory');
const plainJSBundle = require('../plainJSBundle');
const polyfill = {
output: {
type: 'script',
code: '__d(function() {/* code for polyfill */});',
},
};
const fooModule = {
path: 'foo',
dependencies: new Map([['./bar', 'bar']]),
output: {code: '__d(function() {/* code for foo */});'},
};
const barModule = {
path: 'bar',
dependencies: new Map(),
output: {code: '__d(function() {/* code for bar */});'},
};
it('should serialize a very simple bundle', () => {
expect(
plainJSBundle(
'foo',
[polyfill],
{
dependencies: new Map([['foo', fooModule], ['bar', barModule]]),
entryPoints: ['foo'],
},
{
createModuleIdFn: path => path,
dev: true,
runBeforeMainModule: [],
runModule: true,
sourceMapUrl: 'http://localhost/bundle.map',
},
),
).toEqual(
[
'__d(function() {/* code for polyfill */});',
'__d(function() {/* code for foo */},"foo",["bar"],"foo");',
'__d(function() {/* code for bar */},"bar",[],"bar");',
'require("foo");',
'//# sourceMappingURL=http://localhost/bundle.map',
].join('\n'),
);
});
it('should add runBeforeMainModule statements if found in the graph', () => {
expect(
plainJSBundle(
'foo',
[polyfill],
{
dependencies: new Map([['foo', fooModule], ['bar', barModule]]),
entryPoints: ['foo'],
},
{
createModuleIdFn: path => path,
dev: true,
runBeforeMainModule: ['bar', 'non-existant'],
runModule: true,
sourceMapUrl: 'http://localhost/bundle.map',
},
),
).toEqual(
[
'__d(function() {/* code for polyfill */});',
'__d(function() {/* code for foo */},"foo",["bar"],"foo");',
'__d(function() {/* code for bar */},"bar",[],"bar");',
'require("bar");',
'require("foo");',
'//# sourceMappingURL=http://localhost/bundle.map',
].join('\n'),
);
});
it('should handle numeric module ids', () => {
expect(
plainJSBundle(
'foo',
[polyfill],
{
dependencies: new Map([['foo', fooModule], ['bar', barModule]]),
entryPoints: ['foo'],
},
{
createModuleIdFn: createModuleIdFactory(),
dev: true,
runBeforeMainModule: ['bar', 'non-existant'],
runModule: true,
sourceMapUrl: 'http://localhost/bundle.map',
},
),
).toEqual(
[
'__d(function() {/* code for polyfill */});',
'__d(function() {/* code for foo */},0,[1],"foo");',
'__d(function() {/* code for bar */},1,[],"bar");',
'require(1);',
'require(0);',
'//# sourceMappingURL=http://localhost/bundle.map',
].join('\n'),
);
});

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails oncall+javascript_foundation
* @flow
* @format
*/
'use strict';
const createModuleIdFactory = require('../../../../lib/createModuleIdFactory');
const {wrapModule} = require('../js');
let myModule;
beforeEach(() => {
myModule = {
path: '/root/foo.js',
dependencies: new Map([['bar', '/bar'], ['baz', '/baz']]),
inverseDependencies: new Set(),
output: {
code: '__d(function() { console.log("foo") });',
map: [],
source: '',
type: 'module',
},
};
});
describe('wrapModule()', () => {
it('Should wrap a module in nondev mode', () => {
expect(
wrapModule(myModule, {
createModuleIdFn: createModuleIdFactory(),
dev: false,
}),
).toEqual('__d(function() { console.log("foo") },0,[1,2]);');
});
it('Should wrap a module in dev mode', () => {
expect(
wrapModule(myModule, {
createModuleIdFn: createModuleIdFactory(),
dev: true,
}),
).toEqual('__d(function() { console.log("foo") },0,[1,2],"foo.js");');
});
it('should not wrap a script', () => {
myModule.output.type = 'script';
expect(
wrapModule(myModule, {
createModuleIdFn: createModuleIdFactory(),
dev: true,
}),
).toEqual(myModule.output.code);
});
it('should use custom createModuleIdFn param', () => {
// Just use a createModuleIdFn that returns the same path.
expect(
wrapModule(myModule, {
createModuleIdFn: path => path,
dev: false,
}),
).toEqual(
'__d(function() { console.log("foo") },"/root/foo.js",["/bar","/baz"]);',
);
});
});

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
const addParamsToDefineCall = require('../../../lib/addParamsToDefineCall');
const path = require('path');
import type {DependencyEdge} from '../../traverseDependencies';
export type Options = {
+createModuleIdFn: string => number | string,
+dev: boolean,
};
function wrapModule(module: DependencyEdge, options: Options) {
if (module.output.type === 'script') {
return module.output.code;
}
const moduleId = options.createModuleIdFn(module.path);
const params = [
moduleId,
Array.from(module.dependencies.values()).map(options.createModuleIdFn),
];
// Add the module name as the last parameter (to make it easier to do
// requires by name when debugging).
// TODO (t26853986): Switch this to use the relative file path (once we have
// as single project root).
if (options.dev) {
params.push(path.basename(module.path));
}
return addParamsToDefineCall(module.output.code, ...params);
}
module.exports = {
wrapModule,
};

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
const {wrapModule} = require('./helpers/js');
import type {Graph} from '../DeltaCalculator';
import type {DependencyEdge} from '../traverseDependencies';
type Options = {|
createModuleIdFn: string => number | string,
+dev: boolean,
+runBeforeMainModule: $ReadOnlyArray<string>,
+runModule: boolean,
+sourceMapUrl: ?string,
|};
function plainJSBundle(
entryPoint: string,
pre: $ReadOnlyArray<DependencyEdge>,
graph: Graph,
options: Options,
): string {
const output = [];
for (const module of pre) {
output.push(wrapModule(module, options));
}
for (const module of graph.dependencies.values()) {
output.push(wrapModule(module, options));
}
for (const path of options.runBeforeMainModule) {
if (graph.dependencies.has(path)) {
output.push(
`require(${JSON.stringify(options.createModuleIdFn(path))});`,
);
}
}
if (options.runModule && graph.dependencies.has(entryPoint)) {
output.push(
`require(${JSON.stringify(options.createModuleIdFn(entryPoint))});`,
);
}
if (options.sourceMapUrl) {
output.push(`//# sourceMappingURL=${options.sourceMapUrl}`);
}
return output.join('\n');
}
module.exports = plainJSBundle;

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
const {fromRawMappings} = require('metro-source-map');
import type {Graph} from '../DeltaCalculator';
import type {DependencyEdge} from '../traverseDependencies';
function fullSourceMap(
pre: $ReadOnlyArray<DependencyEdge>,
graph: Graph,
options: {|+excludeSource: boolean|},
): string {
const modules = pre.concat(...graph.dependencies.values());
const modulesWithMaps = modules.map(module => {
return {
...module.output,
path: module.path,
};
});
return fromRawMappings(modulesWithMaps).toString(undefined, {
excludeSource: options.excludeSource,
});
}
module.exports = fullSourceMap;