Refactor production serializers to use directly the graph information

Reviewed By: mjesun

Differential Revision: D7158632

fbshipit-source-id: 91fad2e3ca617ea5f3b283e335c0d70edbb6ff3b
This commit is contained in:
Rafael Oleza 2018-03-19 10:04:55 -07:00 committed by Facebook Github Bot
parent 3ac0bb47d9
commit 6be6b0805d
14 changed files with 479 additions and 94 deletions

View File

@ -34,7 +34,7 @@ function withSourceMap(
function minify(inputCode: string, inputMap: ?BabelSourceMap) {
const result = uglify.minify(inputCode, {
mangle: {toplevel: true},
mangle: {toplevel: false},
output: {
ascii_only: true,
quote_style: 3,
@ -44,7 +44,7 @@ function minify(inputCode: string, inputMap: ?BabelSourceMap) {
content: inputMap,
includeSources: false,
},
toplevel: true,
toplevel: false,
compress: {
// reduce_funcs inlines single-use function, which cause perf regressions.
reduce_funcs: false,

View File

@ -444,7 +444,7 @@ class DeltaTransformer extends EventEmitter {
const name = this._dependencyGraph.getHasteName(edge.path);
const wrappedCode = wrapModule(edge, {
createModuleIdFn: this._getModuleId,
createModuleId: this._getModuleId,
dev: transformOptions.dev,
});

View File

@ -42,7 +42,7 @@ it('should serialize a very simple bundle', () => {
entryPoints: ['foo'],
},
{
createModuleIdFn: path => path,
createModuleId: path => path,
dev: true,
runBeforeMainModule: [],
runModule: true,
@ -70,7 +70,7 @@ it('should add runBeforeMainModule statements if found in the graph', () => {
entryPoints: ['foo'],
},
{
createModuleIdFn: path => path,
createModuleId: path => path,
dev: true,
runBeforeMainModule: ['bar', 'non-existant'],
runModule: true,
@ -99,7 +99,7 @@ it('should handle numeric module ids', () => {
entryPoints: ['foo'],
},
{
createModuleIdFn: createModuleIdFactory(),
createModuleId: createModuleIdFactory(),
dev: true,
runBeforeMainModule: ['bar', 'non-existant'],
runModule: true,

View File

@ -35,7 +35,7 @@ describe('wrapModule()', () => {
it('Should wrap a module in nondev mode', () => {
expect(
wrapModule(myModule, {
createModuleIdFn: createModuleIdFactory(),
createModuleId: createModuleIdFactory(),
dev: false,
}),
).toEqual('__d(function() { console.log("foo") },0,[1,2]);');
@ -44,7 +44,7 @@ describe('wrapModule()', () => {
it('Should wrap a module in dev mode', () => {
expect(
wrapModule(myModule, {
createModuleIdFn: createModuleIdFactory(),
createModuleId: createModuleIdFactory(),
dev: true,
}),
).toEqual('__d(function() { console.log("foo") },0,[1,2],"foo.js");');
@ -55,17 +55,17 @@ describe('wrapModule()', () => {
expect(
wrapModule(myModule, {
createModuleIdFn: createModuleIdFactory(),
createModuleId: createModuleIdFactory(),
dev: true,
}),
).toEqual(myModule.output.code);
});
it('should use custom createModuleIdFn param', () => {
// Just use a createModuleIdFn that returns the same path.
it('should use custom createModuleId param', () => {
// Just use a createModuleId that returns the same path.
expect(
wrapModule(myModule, {
createModuleIdFn: path => path,
createModuleId: path => path,
dev: false,
}),
).toEqual(

View File

@ -16,7 +16,7 @@ const path = require('path');
import type {DependencyEdge} from '../../traverseDependencies';
export type Options = {
+createModuleIdFn: string => number | string,
+createModuleId: string => number | string,
+dev: boolean,
};
@ -25,10 +25,10 @@ function wrapModule(module: DependencyEdge, options: Options) {
return module.output.code;
}
const moduleId = options.createModuleIdFn(module.path);
const moduleId = options.createModuleId(module.path);
const params = [
moduleId,
Array.from(module.dependencies.values()).map(options.createModuleIdFn),
Array.from(module.dependencies.values()).map(options.createModuleId),
];
// Add the module name as the last parameter (to make it easier to do

View File

@ -10,13 +10,15 @@
'use strict';
const getAppendScripts = require('../../lib/getAppendScripts');
const {wrapModule} = require('./helpers/js');
import type {Graph} from '../DeltaCalculator';
import type {DependencyEdge} from '../traverseDependencies';
type Options = {|
createModuleIdFn: string => number | string,
createModuleId: string => number | string,
+dev: boolean,
+runBeforeMainModule: $ReadOnlyArray<string>,
+runModule: boolean,
@ -29,35 +31,17 @@ function plainJSBundle(
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));
options.createModuleId(module.path);
}
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');
return [
...pre,
...graph.dependencies.values(),
...getAppendScripts(entryPoint, graph, options),
]
.map(module => wrapModule(module, options))
.join('\n');
}
module.exports = plainJSBundle;

View File

@ -10,10 +10,16 @@
'use strict';
const DeltaCalculator = require('./DeltaCalculator');
const DeltaTransformer = require('./DeltaTransformer');
import type Bundler from '../Bundler';
import type {BundleOptions} from '../shared/types.flow';
import type {
DeltaResult,
Graph as CalculatorGraph,
Options,
} from './DeltaCalculator';
import type {DeltaEntry} from './DeltaTransformer';
export type PostProcessModules = (
@ -27,6 +33,9 @@ export type MainOptions = {|
postProcessModules?: PostProcessModules,
|};
export type Delta = DeltaResult;
export type Graph = CalculatorGraph;
/**
* `DeltaBundler` uses the `DeltaTransformer` to build bundle deltas. This
* module handles all the transformer instances so it can support multiple
@ -38,6 +47,7 @@ class DeltaBundler {
_options: MainOptions;
_deltaTransformers: Map<string, DeltaTransformer> = new Map();
_currentId: number = 0;
_deltaCalculators: Map<Graph, DeltaCalculator> = new Map();
constructor(bundler: Bundler, options: MainOptions) {
this._bundler = bundler;
@ -47,6 +57,9 @@ class DeltaBundler {
end() {
this._deltaTransformers.forEach(DeltaTransformer => DeltaTransformer.end());
this._deltaTransformers = new Map();
this._deltaCalculators.forEach(deltaCalculator => deltaCalculator.end());
this._deltaCalculators = new Map();
}
endTransformer(clientId: string) {
@ -78,6 +91,55 @@ class DeltaBundler {
return deltaTransformer;
}
async buildGraph(options: Options): Promise<Graph> {
const depGraph = await this._bundler.getDependencyGraph();
const deltaCalculator = new DeltaCalculator(
this._bundler,
depGraph,
options,
);
await deltaCalculator.getDelta({reset: true});
const graph = deltaCalculator.getGraph();
this._deltaCalculators.set(graph, deltaCalculator);
return graph;
}
async getDelta(graph: Graph, {reset}: {reset: boolean}): Promise<Delta> {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error('Graph not found');
}
return await deltaCalculator.getDelta({reset});
}
listen(graph: Graph, callback: () => mixed) {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error('Graph not found');
}
deltaCalculator.on('change', callback);
}
endGraph(graph: Graph) {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error('Graph not found');
}
deltaCalculator.end();
this._deltaCalculators.delete(graph);
}
getPostProcessModulesFn(
entryPoint: string,
): (modules: $ReadOnlyArray<DeltaEntry>) => $ReadOnlyArray<DeltaEntry> {

View File

@ -19,6 +19,7 @@ jest
}))
.mock('../../Bundler')
.mock('../../Assets')
.mock('../../lib/getPrependedScripts')
.mock('../../node-haste/DependencyGraph')
.mock('metro-core/src/Logger')
.mock('../../lib/getAbsolutePath')
@ -29,6 +30,7 @@ describe('processRequest', () => {
let Bundler;
let Server;
let getAsset;
let getPrependedScripts;
let symbolicate;
let Serializers;
let DeltaBundler;
@ -40,6 +42,7 @@ describe('processRequest', () => {
Bundler = require('../../Bundler');
Server = require('../');
getAsset = require('../../Assets').getAsset;
getPrependedScripts = require('../../lib/getPrependedScripts');
symbolicate = require('../symbolicate/symbolicate');
Serializers = require('../../DeltaBundler/Serializers/Serializers');
DeltaBundler = require('../../DeltaBundler');
@ -85,6 +88,15 @@ describe('processRequest', () => {
let requestHandler;
beforeEach(() => {
DeltaBundler.prototype.buildGraph = jest.fn().mockReturnValue(
Promise.resolve({
entryPoints: [''],
dependencies: new Map(),
}),
);
getPrependedScripts.mockReturnValue(Promise.resolve([]));
Serializers.fullBundle.mockReturnValue(
Promise.resolve({
bundle: 'this is the source',
@ -445,28 +457,16 @@ describe('processRequest', () => {
entryFile: 'foo file',
})
.then(() =>
expect(Serializers.fullBundle).toBeCalledWith(
expect.any(DeltaBundler),
{
assetPlugins: [],
customTransformOptions: {},
dev: true,
entryFile: '/root/foo file',
entryModuleOnly: false,
excludeSource: false,
hot: false,
inlineSourceMap: false,
isolateModuleIDs: false,
minify: false,
onProgress: null,
platform: undefined,
resolutionResponse: null,
runBeforeMainModule: ['InitializeCore'],
runModule: true,
sourceMapUrl: null,
unbundle: false,
},
),
expect(DeltaBundler.prototype.buildGraph).toBeCalledWith({
assetPlugins: [],
customTransformOptions: {},
dev: true,
entryPoints: ['/root/foo file'],
hot: false,
minify: false,
onProgress: null,
platform: undefined,
}),
);
});
});

View File

@ -14,13 +14,19 @@ const Bundler = require('../Bundler');
const DeltaBundler = require('../DeltaBundler');
const MultipartResponse = require('./MultipartResponse');
const Serializers = require('../DeltaBundler/Serializers/Serializers');
const defaultCreateModuleIdFactory = require('../lib/createModuleIdFactory');
const plainJSBundle = require('../DeltaBundler/Serializers/plainJSBundle');
const sourceMapString = require('../DeltaBundler/Serializers/sourceMapString');
const debug = require('debug')('Metro:Server');
const defaults = require('../defaults');
const formatBundlingError = require('../lib/formatBundlingError');
const getAbsolutePath = require('../lib/getAbsolutePath');
const getMaxWorkers = require('../lib/getMaxWorkers');
const getOrderedDependencyPaths = require('../lib/getOrderedDependencyPaths');
const getPrependedScripts = require('../lib/getPrependedScripts');
const mime = require('mime-types');
const mapGraph = require('../lib/mapGraph');
const nullthrows = require('fbjs/lib/nullthrows');
const parseCustomTransformOptions = require('../lib/parseCustomTransformOptions');
const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath');
@ -32,6 +38,7 @@ const {getAsset} = require('../Assets');
const resolveSync: ResolveSync = require('resolve').sync;
import type {CustomError} from '../lib/formatBundlingError';
import type {DependencyEdge} from '../DeltaBundler/traverseDependencies';
import type {IncomingMessage, ServerResponse} from 'http';
import type {Reporter} from '../lib/reporting';
import type {
@ -73,7 +80,7 @@ class Server {
blacklistRE: void | RegExp,
cacheStores: $ReadOnlyArray<CacheStore<TransformedCode>>,
cacheVersion: string,
createModuleIdFactory?: () => (path: string) => number,
createModuleId: (path: string) => number,
enableBabelRCLookup: boolean,
extraNodeModules: {},
getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>,
@ -119,6 +126,9 @@ class Server {
const assetExts = options.assetExts || defaults.assetExts;
const sourceExts = options.sourceExts || defaults.sourceExts;
const _createModuleId =
options.createModuleId || defaultCreateModuleIdFactory();
this._opts = {
assetExts: options.assetTransforms ? [] : assetExts,
assetRegistryPath: options.assetRegistryPath,
@ -126,7 +136,7 @@ class Server {
cacheStores: options.cacheStores,
cacheVersion: options.cacheVersion,
dynamicDepsInPackages: options.dynamicDepsInPackages || 'throwAtRuntime',
createModuleIdFactory: options.createModuleIdFactory,
createModuleId: _createModuleId,
enableBabelRCLookup:
options.enableBabelRCLookup != null
? options.enableBabelRCLookup
@ -175,7 +185,7 @@ class Server {
// This slices out options that are not part of the strict BundlerOptions
/* eslint-disable no-unused-vars */
const {
createModuleIdFactory,
createModuleId,
getModulesRunBeforeMainModule,
moduleFormat,
silent,
@ -230,27 +240,48 @@ class Server {
}
async build(options: BundleOptions): Promise<{code: string, map: string}> {
options = {
...options,
runBeforeMainModule: this._opts.getModulesRunBeforeMainModule(
options.entryFile,
),
entryFile: getAbsolutePath(options.entryFile, this._opts.projectRoots),
};
const fullBundle = await Serializers.fullBundle(
this._deltaBundler,
options,
const entryPoint = getAbsolutePath(
options.entryFile,
this._opts.projectRoots,
);
const fullMap = await Serializers.fullSourceMap(
this._deltaBundler,
let graph = await this._deltaBundler.buildGraph({
assetPlugins: options.assetPlugins,
customTransformOptions: options.customTransformOptions,
dev: options.dev,
entryPoints: [entryPoint],
hot: options.hot,
minify: options.minify,
onProgress: options.onProgress,
platform: options.platform,
});
let prependScripts = await getPrependedScripts(
this._opts,
options,
this._bundler,
);
if (options.minify) {
prependScripts = await Promise.all(
prependScripts.map(script => this._minifyModule(script)),
);
graph = await mapGraph(graph, module => this._minifyModule(module));
}
return {
code: fullBundle.bundle,
map: fullMap,
code: plainJSBundle(entryPoint, prependScripts, graph, {
createModuleId: this._opts.createModuleId,
dev: options.dev,
runBeforeMainModule: this._opts.getModulesRunBeforeMainModule(
options.entryFile,
),
runModule: options.runModule,
sourceMapUrl: options.sourceMapUrl,
}),
map: sourceMapString(prependScripts, graph, {
excludeSource: options.excludeSource,
}),
};
}
@ -299,6 +330,24 @@ class Server {
return await getOrderedDependencyPaths(this._deltaBundler, bundleOptions);
}
async _minifyModule(module: DependencyEdge): Promise<DependencyEdge> {
const {code, map} = await this._bundler.minifyModule(
module.path,
module.output.code,
module.output.map,
);
// $FlowIssue #16581373 spread of an exact object should be exact
return {
...module,
output: {
...module.output,
code,
map,
},
};
}
onFileChange(type: string, filePath: string) {
Promise.all(
this._fileChangeListeners.map(listener => listener(filePath)),

View File

@ -149,7 +149,7 @@ __d(function (global, _require, module, exports, _dependencyMap) {
Foo: Foo,
Bar: Bar
};
},4,[5,6]);
},0,[1,2]);
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
@ -159,7 +159,7 @@ __d(function (global, _require, module, exports, _dependencyMap) {
type: 'bar',
foo: Foo.type
};
},5,[6]);
},1,[2]);
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
@ -169,7 +169,7 @@ __d(function (global, _require, module, exports, _dependencyMap) {
type: 'foo',
asset: asset
};
},6,[7]);
},2,[3]);
__d(function (global, _require, module, exports, _dependencyMap) {
module.exports = _require(_dependencyMap[0]).registerAsset({
\\"__packager_asset\\": true,
@ -181,11 +181,11 @@ __d(function (global, _require, module, exports, _dependencyMap) {
\\"name\\": \\"test\\",
\\"type\\": \\"png\\"
});
},7,[8]);
},3,[4]);
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
},8,[]);
require(4);"
},4,[]);
require(0);"
`;
exports[`basic_bundle bundles package without polyfills 1`] = `
@ -323,7 +323,7 @@ __d(function (global, _require, module, exports, _dependencyMap) {
Foo: Foo,
Bar: Bar
};
},2,[3,4]);
},0,[1,2]);
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
@ -333,7 +333,7 @@ __d(function (global, _require, module, exports, _dependencyMap) {
type: 'bar',
foo: Foo.type
};
},3,[4]);
},1,[2]);
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
@ -343,7 +343,7 @@ __d(function (global, _require, module, exports, _dependencyMap) {
type: 'foo',
asset: asset
};
},4,[5]);
},2,[3]);
__d(function (global, _require, module, exports, _dependencyMap) {
module.exports = _require(_dependencyMap[0]).registerAsset({
\\"__packager_asset\\": true,
@ -355,9 +355,9 @@ __d(function (global, _require, module, exports, _dependencyMap) {
\\"name\\": \\"test\\",
\\"type\\": \\"png\\"
});
},5,[6]);
},3,[4]);
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
},6,[]);
require(2);"
},4,[]);
require(0);"
`;

View File

@ -0,0 +1,58 @@
/**
* 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 mapGraph = require('../mapGraph');
let graph;
beforeEach(() => {
graph = {
dependencies: new Map([
['/entryPoint', {name: 'entryPoint', id: '1'}],
['/foo', {name: 'foo', id: '2'}],
['/baz', {name: 'baz', id: '3'}],
]),
entryPoints: ['/entryPoint'],
};
});
it('should map the passed graph when a sync function is passed', async () => {
const mapped = await mapGraph(graph, element => ({
name: '-' + element.name + '-',
id: parseInt(element.id, 10),
}));
expect(mapped.dependencies).toEqual(
new Map([
['/entryPoint', {name: '-entryPoint-', id: 1}],
['/foo', {name: '-foo-', id: 2}],
['/baz', {name: '-baz-', id: 3}],
]),
);
expect(mapped.entryPoints).toEqual(['/entryPoint']);
});
it('should map the passed graph when an async function is passed', async () => {
const mapped = await mapGraph(graph, async element => ({
name: '-' + element.name + '-',
id: parseInt(element.id, 10),
}));
expect(mapped.dependencies).toEqual(
new Map([
['/entryPoint', {name: '-entryPoint-', id: 1}],
['/foo', {name: '-foo-', id: 2}],
['/baz', {name: '-baz-', id: 3}],
]),
);
expect(mapped.entryPoints).toEqual(['/entryPoint']);
});

View File

@ -0,0 +1,67 @@
/**
* 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';
import type {Graph} from '../DeltaBundler/DeltaCalculator';
import type {DependencyEdge} from '../DeltaBundler/traverseDependencies';
type Options = {
+createModuleId: string => number | string,
+runBeforeMainModule: $ReadOnlyArray<string>,
+runModule: boolean,
+sourceMapUrl: ?string,
};
function getAppendScripts(
entryPoint: string,
graph: Graph,
options: Options,
): $ReadOnlyArray<DependencyEdge> {
const output = [];
if (options.runModule) {
const paths = [...options.runBeforeMainModule, entryPoint];
for (const path of paths) {
if (graph.dependencies.has(path)) {
output.push({
path: `require-${path}`,
dependencies: new Map(),
inverseDependencies: new Set(),
output: {
code: `require(${JSON.stringify(options.createModuleId(path))});`,
source: '',
map: [],
type: 'script',
},
});
}
}
}
if (options.sourceMapUrl) {
output.push({
path: 'source-map',
dependencies: new Map(),
inverseDependencies: new Set(),
output: {
code: `//# sourceMappingURL=${options.sourceMapUrl}`,
source: '',
map: [],
type: 'script',
},
});
}
return output;
}
module.exports = getAppendScripts;

View File

@ -0,0 +1,125 @@
/**
* 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 defaults = require('../defaults');
const getPreludeCode = require('./getPreludeCode');
import type Bundler from '../Bundler';
import type {DependencyEdge} from '../DeltaBundler/traverseDependencies';
import type Module from '../node-haste/Module';
type Options = {
enableBabelRCLookup: boolean,
getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>,
polyfillModuleNames: Array<string>,
projectRoots: $ReadOnlyArray<string>,
};
type BundleOptions = {
+dev: boolean,
+hot: boolean,
+platform: ?string,
};
async function getPrependedScripts(
options: Options,
bundleOptions: BundleOptions,
bundler: Bundler,
): Promise<Array<DependencyEdge>> {
// Get all the polyfills from the relevant option params (the
// `getPolyfills()` method and the `polyfillModuleNames` variable).
const polyfillModuleNames = options
.getPolyfills({
platform: bundleOptions.platform,
})
.concat(options.polyfillModuleNames);
const dependencyGraph = await bundler.getDependencyGraph();
// Build the module system dependencies (scripts that need to
// be included at the very beginning of the bundle) + any polifyll.
const modules = [defaults.moduleSystem]
.concat(polyfillModuleNames)
.map(polyfillModuleName =>
dependencyGraph.createPolyfill({
file: polyfillModuleName,
}),
);
const transformOptions = {
dev: bundleOptions.dev,
enableBabelRCLookup: options.enableBabelRCLookup,
hot: bundleOptions.hot,
projectRoot: options.projectRoots[0],
};
const out = await Promise.all(
modules.map(module => _createEdgeFromScript(module, transformOptions)),
);
out.unshift(_getPrelude({dev: bundleOptions.dev}));
return out;
}
function _getPrelude({dev}: {dev: boolean}): DependencyEdge {
const code = getPreludeCode({isDev: dev});
const name = '__prelude__';
return {
dependencies: new Map(),
inverseDependencies: new Set(),
path: name,
output: {
code,
map: [],
source: code,
type: 'script',
},
};
}
async function _createEdgeFromScript(
module: Module,
options: {
dev: boolean,
enableBabelRCLookup: boolean,
hot: boolean,
projectRoot: string,
},
): Promise<DependencyEdge> {
const result = await module.read({
assetDataPlugins: [],
customTransformOptions: {},
dev: options.dev,
enableBabelRCLookup: options.enableBabelRCLookup,
hot: options.hot,
inlineRequires: false,
minify: false,
platform: undefined,
projectRoot: options.projectRoot,
});
return {
dependencies: new Map(),
inverseDependencies: new Set(),
path: module.path,
output: {
code: result.code,
map: result.map,
source: result.source,
type: 'script',
},
};
}
module.exports = getPrependedScripts;

View File

@ -0,0 +1,40 @@
/**
* 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';
import type {DependencyEdge} from '../DeltaBundler/traverseDependencies';
import type {Graph} from '../DeltaBundler';
/**
* Generates a new Graph object, which has all the dependencies returned by the
* mapping function (similar to Array.prototype.map).
**/
async function mapGraph(
graph: Graph,
mappingFn: DependencyEdge => Promise<DependencyEdge>,
): Promise<Graph> {
const dependencies = new Map(
await Promise.all(
Array.from(graph.dependencies.entries()).map(async ([path, module]) => {
const mutated = await mappingFn(module);
return [path, mutated];
}),
),
);
return {
dependencies,
entryPoints: graph.entryPoints,
};
}
module.exports = mapGraph;