mirror of https://github.com/status-im/metro.git
module graph
Summary: This piece of code can assemble all transitive dependencies of a set of entry modules. It is supposed to replace `ResolutionRequest.getOrderedDependencies`. It has the following advantages: - has minimal API surface to other components of the system (uses two functions, exposes one function) - allows to separate concerns into loading + transforming files, resolving dependencies, gathering all modules belonging to a bundle (this code), and bundling - allows to specify multiple entry points - allows to use any kind of dependency ID as entry point (haste IDs, node module IDs, relative paths, absolute paths – depends on the resolver) - allows to skip files, which allows callers to incrementally update previously retrieved collections of modules Reviewed By: cpojer Differential Revision: D3627346 fbshipit-source-id: 84b7aa693ca6e89ba3c1ab2af9a004e2e0aaed3d
This commit is contained in:
parent
aad22c00c4
commit
0e06c0f74f
|
@ -0,0 +1,106 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const memoize = require('async/memoize');
|
||||||
|
const queue = require('async/queue');
|
||||||
|
const seq = require('async/seq');
|
||||||
|
const invariant = require('fbjs/lib/invariant');
|
||||||
|
|
||||||
|
import type {GraphFn, LoadFn, ResolveFn, File, Module} from './types.flow';
|
||||||
|
|
||||||
|
const createParentModule =
|
||||||
|
() => ({file: {path: '', ast: {}}, dependencies: []});
|
||||||
|
|
||||||
|
const noop = () => {};
|
||||||
|
|
||||||
|
exports.create = function create(resolve: ResolveFn, load: LoadFn): GraphFn {
|
||||||
|
function Graph(entryPoints, platform, options = {}, callback = noop) {
|
||||||
|
const {cwd = '', log = (console: any), optimize = false, skip} = options;
|
||||||
|
|
||||||
|
if (typeof platform !== 'string') {
|
||||||
|
log.error('`Graph`, called without a platform');
|
||||||
|
return callback(Error('The target platform has to be passed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const modules: Map<string | null, Module> = new Map();
|
||||||
|
modules.set(null, createParentModule());
|
||||||
|
|
||||||
|
const loadQueue = queue(seq(
|
||||||
|
({id, parent}, cb) => resolve(id, parent, platform, options, cb),
|
||||||
|
memoize((file, cb) => load(file, {log, optimize}, cb)),
|
||||||
|
), Number.MAX_SAFE_INTEGER);
|
||||||
|
|
||||||
|
const cleanup = () => (loadQueue.drain = noop);
|
||||||
|
loadQueue.drain = () => {
|
||||||
|
cleanup();
|
||||||
|
callback(null, collect(null, modules));
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadModule(id: string, parent: string | null, parentDependencyIndex) {
|
||||||
|
function onFileLoaded(
|
||||||
|
error: ?Error,
|
||||||
|
file: File,
|
||||||
|
dependencyIDs: Array<string>,
|
||||||
|
) {
|
||||||
|
if (error) {
|
||||||
|
cleanup();
|
||||||
|
callback(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentModule = modules.get(parent);
|
||||||
|
invariant(parentModule, 'Invalid parent module: ' + String(parent));
|
||||||
|
parentModule.dependencies[parentDependencyIndex] = {id, path: file.path};
|
||||||
|
|
||||||
|
if ((!skip || !skip.has(file.path)) && !modules.has(file.path)) {
|
||||||
|
const dependencies = Array(dependencyIDs.length);
|
||||||
|
modules.set(file.path, {file, dependencies});
|
||||||
|
dependencyIDs.forEach(
|
||||||
|
(dependencyID, j) => loadModule(dependencyID, file.path, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadQueue.push({id, parent: parent != null ? parent : cwd}, onFileLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
for (const entryPoint of entryPoints) {
|
||||||
|
loadModule(entryPoint, null, i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadQueue.idle()) {
|
||||||
|
log.error('`Graph` called without any entry points');
|
||||||
|
cleanup();
|
||||||
|
callback(Error('At least one entry point has to be passed.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Graph;
|
||||||
|
};
|
||||||
|
|
||||||
|
function collect(
|
||||||
|
path,
|
||||||
|
modules,
|
||||||
|
serialized = [],
|
||||||
|
seen: Set<string | null> = new Set(),
|
||||||
|
): Array<Module> {
|
||||||
|
const module = modules.get(path);
|
||||||
|
if (!module || seen.has(path)) { return serialized; }
|
||||||
|
|
||||||
|
if (path !== null) {
|
||||||
|
serialized.push(module);
|
||||||
|
seen.add(path);
|
||||||
|
}
|
||||||
|
module.dependencies.forEach(
|
||||||
|
dependency => collect(dependency.path, modules, serialized, seen));
|
||||||
|
|
||||||
|
return serialized;
|
||||||
|
}
|
|
@ -0,0 +1,315 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
jest
|
||||||
|
.disableAutomock()
|
||||||
|
.mock('console');
|
||||||
|
|
||||||
|
const {Console} = require('console');
|
||||||
|
const Graph = require('../Graph');
|
||||||
|
const {fn} = require('../test-helpers');
|
||||||
|
|
||||||
|
const {any, objectContaining} = jasmine;
|
||||||
|
const quiet = new Console();
|
||||||
|
|
||||||
|
describe('Graph:', () => {
|
||||||
|
const anyEntry = ['arbitrary/entry/point'];
|
||||||
|
const anyPlatform = 'arbitrary platform';
|
||||||
|
const noOpts = undefined;
|
||||||
|
|
||||||
|
let graph, load, resolve;
|
||||||
|
beforeEach(() => {
|
||||||
|
load = fn();
|
||||||
|
resolve = fn();
|
||||||
|
resolve.stub.yields(null, 'arbitrary file');
|
||||||
|
load.stub.yields(null, createFile('arbitrary file'), []);
|
||||||
|
|
||||||
|
graph = Graph.create(resolve, load);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls back an error when called without any entry point', done => {
|
||||||
|
graph([], anyPlatform, {log: quiet}, (error) => {
|
||||||
|
expect(error).toEqual(any(Error));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves the entry point with the passed-in `resolve` function', done => {
|
||||||
|
const entryPoint = '/arbitrary/path';
|
||||||
|
graph([entryPoint], anyPlatform, noOpts, () => {
|
||||||
|
expect(resolve).toBeCalledWith(
|
||||||
|
entryPoint, '', any(String), any(Object), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows to specify multiple entry points', done => {
|
||||||
|
const entryPoints = ['Arbitrary', '../entry.js'];
|
||||||
|
graph(entryPoints, anyPlatform, noOpts, () => {
|
||||||
|
expect(resolve).toBeCalledWith(
|
||||||
|
entryPoints[0], '', any(String), any(Object), any(Function));
|
||||||
|
expect(resolve).toBeCalledWith(
|
||||||
|
entryPoints[1], '', any(String), any(Object), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls back with an error when called without `platform` option', done => {
|
||||||
|
graph(anyEntry, undefined, {log: quiet}, error => {
|
||||||
|
expect(error).toEqual(any(Error));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards a passed-in `platform` to `resolve`', done => {
|
||||||
|
const platform = 'any';
|
||||||
|
graph(anyEntry, platform, noOpts, () => {
|
||||||
|
expect(resolve).toBeCalledWith(
|
||||||
|
any(String), '', platform, any(Object), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards a passed-in `log` option to `resolve`', done => {
|
||||||
|
const log = new Console();
|
||||||
|
graph(anyEntry, anyPlatform, {log}, () => {
|
||||||
|
expect(resolve).toBeCalledWith(
|
||||||
|
any(String), '', any(String), objectContaining({log}), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls back with every error produced by `resolve`', done => {
|
||||||
|
const error = Error();
|
||||||
|
resolve.stub.yields(error);
|
||||||
|
graph(anyEntry, anyPlatform, noOpts, e => {
|
||||||
|
expect(e).toBe(error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the files returned by `resolve` on to the `load` function', done => {
|
||||||
|
const modules = new Map([
|
||||||
|
['Arbitrary', '/absolute/path/to/Arbitrary.js'],
|
||||||
|
['../entry.js', '/whereever/is/entry.js'],
|
||||||
|
]);
|
||||||
|
for (const [id, file] of modules) {
|
||||||
|
resolve.stub.withArgs(id).yields(null, file);
|
||||||
|
}
|
||||||
|
const [file1, file2] = modules.values();
|
||||||
|
|
||||||
|
graph(modules.keys(), anyPlatform, noOpts, () => {
|
||||||
|
expect(load).toBeCalledWith(file1, any(Object), any(Function));
|
||||||
|
expect(load).toBeCalledWith(file2, any(Object), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the `optimize` flag on to `load`', done => {
|
||||||
|
graph(anyEntry, anyPlatform, {optimize: true}, () => {
|
||||||
|
expect(load).toBeCalledWith(
|
||||||
|
any(String), objectContaining({optimize: true}), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses `false` as the default for the `optimize` flag', done => {
|
||||||
|
graph(anyEntry, anyPlatform, noOpts, () => {
|
||||||
|
expect(load).toBeCalledWith(
|
||||||
|
any(String), objectContaining({optimize: false}), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards a passed-in `log` to `load`', done => {
|
||||||
|
const log = new Console();
|
||||||
|
graph(anyEntry, anyPlatform, {log}, () => {
|
||||||
|
expect(load)
|
||||||
|
.toBeCalledWith(any(String), objectContaining({log}), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls back with every error produced by `load`', done => {
|
||||||
|
const error = Error();
|
||||||
|
load.stub.yields(error);
|
||||||
|
graph(anyEntry, anyPlatform, noOpts, e => {
|
||||||
|
expect(e).toBe(error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves any dependencies provided by `load`', done => {
|
||||||
|
const entryPath = '/path/to/entry.js';
|
||||||
|
const id1 = 'required/id';
|
||||||
|
const id2 = './relative/import';
|
||||||
|
resolve.stub.withArgs('entry').yields(null, entryPath);
|
||||||
|
load.stub.withArgs(entryPath)
|
||||||
|
.yields(null, {path: entryPath}, [id1, id2]);
|
||||||
|
|
||||||
|
graph(['entry'], anyPlatform, noOpts, () => {
|
||||||
|
expect(resolve).toBeCalledWith(
|
||||||
|
id1, entryPath, any(String), any(Object), any(Function));
|
||||||
|
expect(resolve).toBeCalledWith(
|
||||||
|
id2, entryPath, any(String), any(Object), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads transitive dependencies', done => {
|
||||||
|
const entryPath = '/path/to/entry.js';
|
||||||
|
const id1 = 'required/id';
|
||||||
|
const id2 = './relative/import';
|
||||||
|
const path1 = '/path/to/dep/1';
|
||||||
|
const path2 = '/path/to/dep/2';
|
||||||
|
|
||||||
|
resolve.stub
|
||||||
|
.withArgs(id1).yields(null, path1)
|
||||||
|
.withArgs(id2).yields(null, path2)
|
||||||
|
.withArgs('entry').yields(null, entryPath);
|
||||||
|
load.stub
|
||||||
|
.withArgs(entryPath).yields(null, {path: entryPath}, [id1])
|
||||||
|
.withArgs(path1).yields(null, {path: path1}, [id2]);
|
||||||
|
|
||||||
|
graph(['entry'], anyPlatform, noOpts, () => {
|
||||||
|
expect(resolve).toBeCalledWith(id2, path1, any(String), any(Object), any(Function));
|
||||||
|
expect(load).toBeCalledWith(path1, any(Object), any(Function));
|
||||||
|
expect(load).toBeCalledWith(path2, any(Object), any(Function));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls back with an array of modules in depth-first traversal order, regardless of the order of resolution', done => {
|
||||||
|
load.stub.reset();
|
||||||
|
resolve.stub.reset();
|
||||||
|
|
||||||
|
const ids = [
|
||||||
|
'a',
|
||||||
|
'b',
|
||||||
|
'c', 'd',
|
||||||
|
'e',
|
||||||
|
'f', 'g',
|
||||||
|
'h',
|
||||||
|
];
|
||||||
|
ids.forEach(id => {
|
||||||
|
const path = idToPath(id);
|
||||||
|
resolve.stub.withArgs(id).yields(null, path);
|
||||||
|
load.stub.withArgs(path).yields(null, createFile(id), []);
|
||||||
|
});
|
||||||
|
load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b', 'e', 'h']);
|
||||||
|
load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), ['c', 'd']);
|
||||||
|
load.stub.withArgs(idToPath('e')).yields(null, createFile('e'), ['f', 'g']);
|
||||||
|
|
||||||
|
// load certain ids later
|
||||||
|
['b', 'e', 'h'].forEach(id => resolve.stub.withArgs(id).resetBehavior());
|
||||||
|
resolve.stub.withArgs('h').func = (a, b, c, d, callback) => {
|
||||||
|
callback(null, idToPath('h'));
|
||||||
|
['e', 'b'].forEach(
|
||||||
|
id => resolve.stub.withArgs(id).yield(null, idToPath(id)));
|
||||||
|
};
|
||||||
|
|
||||||
|
graph(['a'], anyPlatform, noOpts, (error, result) => {
|
||||||
|
expect(error).toEqual(null);
|
||||||
|
expect(result).toEqual([
|
||||||
|
createModule('a', ['b', 'e', 'h']),
|
||||||
|
createModule('b', ['c', 'd']),
|
||||||
|
createModule('c'),
|
||||||
|
createModule('d'),
|
||||||
|
createModule('e', ['f', 'g']),
|
||||||
|
createModule('f'),
|
||||||
|
createModule('g'),
|
||||||
|
createModule('h'),
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not include dependencies more than once', done => {
|
||||||
|
const ids = ['a', 'b', 'c', 'd'];
|
||||||
|
ids.forEach(id => {
|
||||||
|
const path = idToPath(id);
|
||||||
|
resolve.stub.withArgs(id).yields(null, path);
|
||||||
|
load.stub.withArgs(path).yields(null, createFile(id), []);
|
||||||
|
});
|
||||||
|
['a', 'd'].forEach(id =>
|
||||||
|
load.stub
|
||||||
|
.withArgs(idToPath(id)).yields(null, createFile(id), ['b', 'c']));
|
||||||
|
|
||||||
|
graph(['a', 'd', 'b'], anyPlatform, noOpts, (error, result) => {
|
||||||
|
expect(error).toEqual(null);
|
||||||
|
expect(result).toEqual([
|
||||||
|
createModule('a', ['b', 'c']),
|
||||||
|
createModule('b'),
|
||||||
|
createModule('c'),
|
||||||
|
createModule('d', ['b', 'c']),
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles dependency cycles', done => {
|
||||||
|
resolve.stub
|
||||||
|
.withArgs('a').yields(null, idToPath('a'))
|
||||||
|
.withArgs('b').yields(null, idToPath('b'))
|
||||||
|
.withArgs('c').yields(null, idToPath('c'));
|
||||||
|
load.stub
|
||||||
|
.withArgs(idToPath('a')).yields(null, createFile('a'), ['b'])
|
||||||
|
.withArgs(idToPath('b')).yields(null, createFile('b'), ['c'])
|
||||||
|
.withArgs(idToPath('c')).yields(null, createFile('c'), ['a']);
|
||||||
|
|
||||||
|
graph(['a'], anyPlatform, noOpts, (error, result) => {
|
||||||
|
expect(result).toEqual([
|
||||||
|
createModule('a', ['b']),
|
||||||
|
createModule('b', ['c']),
|
||||||
|
createModule('c', ['a']),
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can skip files', done => {
|
||||||
|
['a', 'b', 'c', 'd', 'e'].forEach(
|
||||||
|
id => resolve.stub.withArgs(id).yields(null, idToPath(id)));
|
||||||
|
load.stub
|
||||||
|
.withArgs(idToPath('a')).yields(null, createFile('a'), ['b', 'c', 'd'])
|
||||||
|
.withArgs(idToPath('b')).yields(null, createFile('b'), ['e']);
|
||||||
|
['c', 'd', 'e'].forEach(id =>
|
||||||
|
load.stub.withArgs(idToPath(id)).yields(null, createFile(id), []));
|
||||||
|
const skip = new Set([idToPath('b'), idToPath('c')]);
|
||||||
|
|
||||||
|
graph(['a'], anyPlatform, {skip}, (error, result) => {
|
||||||
|
expect(result).toEqual([
|
||||||
|
createModule('a', ['b', 'c', 'd']),
|
||||||
|
createModule('d', []),
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createDependency(id) {
|
||||||
|
return {id, path: idToPath(id)};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFile(id) {
|
||||||
|
return {ast: {}, path: idToPath(id)};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createModule(id, dependencies = []): Module {
|
||||||
|
return {
|
||||||
|
file: createFile(id),
|
||||||
|
dependencies: dependencies.map(createDependency)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function idToPath(id) {
|
||||||
|
return '/path/to/' + id;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {Console} = require('console');
|
||||||
|
const {Writable} = require('stream');
|
||||||
|
|
||||||
|
const write = (_, __, callback) => callback();
|
||||||
|
module.exports = new Console(new Writable({write, writev: write}));
|
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const stub = require('sinon/lib/sinon/stub');
|
||||||
|
|
||||||
|
exports.fn = () => {
|
||||||
|
const s = stub();
|
||||||
|
const f = jest.fn(s);
|
||||||
|
f.stub = s;
|
||||||
|
return f;
|
||||||
|
};
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import type {Console} from 'console';
|
||||||
|
|
||||||
|
type callback<T> = (error: ?Error, result?: T) => any;
|
||||||
|
type callback2<T, T1> = (error: ?Error, a?: T, b?: T1) => any;
|
||||||
|
|
||||||
|
type ResolveOptions = {
|
||||||
|
log?: Console,
|
||||||
|
};
|
||||||
|
|
||||||
|
type LoadOptions = {
|
||||||
|
log?: Console,
|
||||||
|
optimize?: boolean,
|
||||||
|
platform?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
type GraphOptions = {
|
||||||
|
cwd?: string,
|
||||||
|
log?: Console,
|
||||||
|
optimize?: boolean,
|
||||||
|
skip?: Set<string>,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Dependency = {
|
||||||
|
id: string,
|
||||||
|
path: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type File = {
|
||||||
|
path: string,
|
||||||
|
code?: string,
|
||||||
|
ast: Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Module = {
|
||||||
|
file: File,
|
||||||
|
dependencies: Array<Dependency>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GraphFn = (
|
||||||
|
entryPoints: Iterable<string>,
|
||||||
|
platform: string,
|
||||||
|
options?: GraphOptions,
|
||||||
|
callback?: callback<Array<Module>>,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type ResolveFn = (
|
||||||
|
id: string,
|
||||||
|
source: string,
|
||||||
|
platform: string,
|
||||||
|
options?: ResolveOptions,
|
||||||
|
callback: callback<string>,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type LoadFn = (
|
||||||
|
file: string,
|
||||||
|
options: LoadOptions,
|
||||||
|
callback: callback2<File, Array<string>>,
|
||||||
|
) => void;
|
Loading…
Reference in New Issue