Remove old bundler code from Server class
Reviewed By: mjesun Differential Revision: D6255868 fbshipit-source-id: 74695129199c8be43e13c97f2ae2ca2c03b801eb
This commit is contained in:
parent
354e1cb508
commit
e9393f694d
|
@ -1,57 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2015-present, Facebook, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the BSD-style license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
*
|
|
||||||
* @emails oncall+javascript_foundation
|
|
||||||
*/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
jest.dontMock('../getInverseDependencies');
|
|
||||||
|
|
||||||
const getInverseDependencies = require('../getInverseDependencies');
|
|
||||||
|
|
||||||
describe('getInverseDependencies', () => {
|
|
||||||
it('', () => {
|
|
||||||
const module1 = createModule('module1', ['module2', 'module3']);
|
|
||||||
const module2 = createModule('module2', ['module3', 'module4']);
|
|
||||||
const module3 = createModule('module3', ['module4']);
|
|
||||||
const module4 = createModule('module4', []);
|
|
||||||
|
|
||||||
const modulePairs = {
|
|
||||||
'module1': [['module2', module2], ['module3', module3]],
|
|
||||||
'module2': [['module3', module3], ['module4', module4]],
|
|
||||||
'module3': [['module4', module4]],
|
|
||||||
'module4': [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolutionResponse = {
|
|
||||||
dependencies: [module1, module2, module3, module4],
|
|
||||||
getResolvedDependencyPairs: module => {
|
|
||||||
return modulePairs[module.hash()];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const dependencies = getInverseDependencies(resolutionResponse);
|
|
||||||
const actual = // jest can't compare maps and sets
|
|
||||||
Array.from(dependencies.entries())
|
|
||||||
.map(([key, value]) => [key, Array.from(value)]);
|
|
||||||
|
|
||||||
expect(actual).toEqual([
|
|
||||||
[module2, [module1]],
|
|
||||||
[module3, [module1, module2]],
|
|
||||||
[module4, [module2, module3]],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function createModule(name, dependencies) {
|
|
||||||
return {
|
|
||||||
hash: () => name,
|
|
||||||
getName: () => Promise.resolve(name),
|
|
||||||
getDependencies: () => Promise.resolve(dependencies),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,459 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2015-present, Facebook, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the BSD-style license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const getInverseDependencies = require('./getInverseDependencies');
|
|
||||||
const querystring = require('querystring');
|
|
||||||
const url = require('url');
|
|
||||||
|
|
||||||
import type {ResolutionResponse} from './getInverseDependencies';
|
|
||||||
import type {Server as HTTPServer} from 'http';
|
|
||||||
import type {Server as HTTPSServer} from 'https';
|
|
||||||
import type {Client as WebSocketClient} from 'ws';
|
|
||||||
|
|
||||||
const blacklist = [
|
|
||||||
'Libraries/Utilities/HMRClient.js',
|
|
||||||
];
|
|
||||||
|
|
||||||
type HMRBundle = {
|
|
||||||
getModulesIdsAndCode(): Array<{id: string, code: string}>,
|
|
||||||
getSourceMappingURLs(): Array<mixed>,
|
|
||||||
getSourceURLs(): Array<mixed>,
|
|
||||||
isEmpty(): boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
type DependencyOptions = {|
|
|
||||||
+dev: boolean,
|
|
||||||
+entryFile: string,
|
|
||||||
+hot: boolean,
|
|
||||||
+minify: boolean,
|
|
||||||
+platform: ?string,
|
|
||||||
+recursive: boolean,
|
|
||||||
+rootEntryFile: string,
|
|
||||||
+bundlingOptions?: Object,
|
|
||||||
|};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a subset of the actual `metro-bundler`'s `Server` class,
|
|
||||||
* without all the stuff we don't need to know about. This allows us to use
|
|
||||||
* `attachHMRServer` with different versions of `metro-bundler` as long as
|
|
||||||
* this specific contract is maintained.
|
|
||||||
*/
|
|
||||||
type PackagerServer<TModule> = {
|
|
||||||
buildBundleForHMR(
|
|
||||||
options: {platform: ?string},
|
|
||||||
host: string,
|
|
||||||
port: number,
|
|
||||||
): Promise<HMRBundle>,
|
|
||||||
getDependencies(options: DependencyOptions): Promise<ResolutionResponse<TModule>>,
|
|
||||||
getModuleForPath(entryFile: string): Promise<TModule>,
|
|
||||||
getShallowDependencies(options: DependencyOptions): Promise<Array<string>>,
|
|
||||||
setHMRFileChangeListener(listener: ?(type: string, filePath: string) => mixed): void,
|
|
||||||
};
|
|
||||||
|
|
||||||
type HMROptions<TModule> = {
|
|
||||||
httpServer: HTTPServer | HTTPSServer,
|
|
||||||
packagerServer: PackagerServer<TModule>,
|
|
||||||
path: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Moduleish = {
|
|
||||||
getName(): string,
|
|
||||||
isAsset(): boolean,
|
|
||||||
isJSON(): boolean,
|
|
||||||
path: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches a WebSocket based connection to the Packager to expose
|
|
||||||
* Hot Module Replacement updates to the simulator.
|
|
||||||
*/
|
|
||||||
function attachHMRServer<TModule: Moduleish>(
|
|
||||||
{httpServer, path, packagerServer}: HMROptions<TModule>,
|
|
||||||
) {
|
|
||||||
type Client = {|
|
|
||||||
ws: WebSocketClient,
|
|
||||||
platform: string,
|
|
||||||
bundleEntry: string,
|
|
||||||
dependenciesCache: Array<string>,
|
|
||||||
dependenciesModulesCache: {[mixed]: TModule},
|
|
||||||
shallowDependencies: {[string]: Array<string>},
|
|
||||||
inverseDependenciesCache: mixed,
|
|
||||||
|};
|
|
||||||
|
|
||||||
const clients: Set<Client> = new Set();
|
|
||||||
|
|
||||||
function disconnect(client: Client) {
|
|
||||||
clients.delete(client);
|
|
||||||
|
|
||||||
// If there are no clients connected, stop listenig for file changes
|
|
||||||
if (clients.size === 0) {
|
|
||||||
packagerServer.setHMRFileChangeListener(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For the give platform and entry file, returns a promise with:
|
|
||||||
// - The full list of dependencies.
|
|
||||||
// - The shallow dependencies each file on the dependency list has
|
|
||||||
// - Inverse shallow dependencies map
|
|
||||||
async function getDependencies(platform: string, bundleEntry: string): Promise<{
|
|
||||||
dependenciesCache: Array<string>,
|
|
||||||
dependenciesModulesCache: {[mixed]: TModule},
|
|
||||||
shallowDependencies: {[string]: Array<string>},
|
|
||||||
inverseDependenciesCache: mixed,
|
|
||||||
/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This comment
|
|
||||||
* suppresses an error found when Flow v0.54 was deployed. To see the error
|
|
||||||
* delete this comment and run Flow. */
|
|
||||||
resolutionResponse: ResolutionResponse<TModule>,
|
|
||||||
}> {
|
|
||||||
const response = await packagerServer.getDependencies({
|
|
||||||
dev: true,
|
|
||||||
entryFile: bundleEntry,
|
|
||||||
rootEntryFile: bundleEntry,
|
|
||||||
hot: true,
|
|
||||||
minify: false,
|
|
||||||
platform: platform,
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
/* $FlowFixMe: getModuleId might be null */
|
|
||||||
const {getModuleId}: {getModuleId: () => number} = response;
|
|
||||||
|
|
||||||
// for each dependency builds the object:
|
|
||||||
// `{path: '/a/b/c.js', deps: ['modA', 'modB', ...]}`
|
|
||||||
const deps: Array<{
|
|
||||||
path: string,
|
|
||||||
name?: string,
|
|
||||||
deps: Array<string>,
|
|
||||||
}> = await Promise.all(response.dependencies.map(async (dep: TModule) => {
|
|
||||||
const depName = dep.getName();
|
|
||||||
|
|
||||||
if (dep.isAsset() || dep.isJSON()) {
|
|
||||||
return {path: dep.path, deps: []};
|
|
||||||
}
|
|
||||||
const dependencies = await packagerServer.getShallowDependencies({
|
|
||||||
dev: true,
|
|
||||||
entryFile: dep.path,
|
|
||||||
rootEntryFile: bundleEntry,
|
|
||||||
hot: true,
|
|
||||||
minify: false,
|
|
||||||
platform: platform,
|
|
||||||
recursive: true,
|
|
||||||
bundlingOptions: response.options,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
path: dep.path,
|
|
||||||
name: depName,
|
|
||||||
deps: dependencies,
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
|
|
||||||
// list with all the dependencies' filenames the bundle entry has
|
|
||||||
const dependenciesCache = response.dependencies.map(dep => dep.path);
|
|
||||||
|
|
||||||
// map from module name to path
|
|
||||||
const moduleToFilenameCache = Object.create(null);
|
|
||||||
deps.forEach(dep => {
|
|
||||||
/* $FlowFixMe: `name` could be null, but `deps` would be as well. */
|
|
||||||
moduleToFilenameCache[dep.name] = dep.path;
|
|
||||||
});
|
|
||||||
|
|
||||||
// map that indicates the shallow dependency each file included on the
|
|
||||||
// bundle has
|
|
||||||
const shallowDependencies = Object.create(null);
|
|
||||||
deps.forEach(dep => {
|
|
||||||
shallowDependencies[dep.path] = dep.deps;
|
|
||||||
});
|
|
||||||
|
|
||||||
// map from module name to the modules' dependencies the bundle entry
|
|
||||||
// has
|
|
||||||
const dependenciesModulesCache = Object.create(null);
|
|
||||||
response.dependencies.forEach(dep => {
|
|
||||||
dependenciesModulesCache[getModuleId(dep)] = dep;
|
|
||||||
});
|
|
||||||
|
|
||||||
const inverseDependenciesCache = Object.create(null);
|
|
||||||
const inverseDependencies = getInverseDependencies(response);
|
|
||||||
for (const [module, dependents] of inverseDependencies) {
|
|
||||||
inverseDependenciesCache[getModuleId(module)] =
|
|
||||||
Array.from(dependents).map(getModuleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* $FlowFixMe(>=0.56.0 site=react_native_oss) This comment suppresses an
|
|
||||||
* error found when Flow v0.56 was deployed. To see the error delete this
|
|
||||||
* comment and run Flow. */
|
|
||||||
/* $FlowFixMe(>=0.56.0 site=react_native_fb,react_native_oss) This comment
|
|
||||||
* suppresses an error found when Flow v0.56 was deployed. To see the error
|
|
||||||
* delete this comment and run Flow. */
|
|
||||||
return {
|
|
||||||
dependenciesCache,
|
|
||||||
dependenciesModulesCache,
|
|
||||||
shallowDependencies,
|
|
||||||
inverseDependenciesCache,
|
|
||||||
resolutionResponse: response,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function prepareResponse(
|
|
||||||
client: Client,
|
|
||||||
filename: string,
|
|
||||||
): Promise<?Object> {
|
|
||||||
try {
|
|
||||||
const bundle = await generateBundle(client, filename);
|
|
||||||
|
|
||||||
if (!bundle || bundle.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'update',
|
|
||||||
body: {
|
|
||||||
modules: bundle.getModulesIdsAndCode(),
|
|
||||||
inverseDependencies: client.inverseDependenciesCache,
|
|
||||||
sourceURLs: bundle.getSourceURLs(),
|
|
||||||
sourceMappingURLs: bundle.getSourceMappingURLs(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
// send errors to the client instead of killing packager server
|
|
||||||
let body;
|
|
||||||
if (error.type === 'TransformError' ||
|
|
||||||
error.type === 'NotFoundError' ||
|
|
||||||
error.type === 'UnableToResolveError') {
|
|
||||||
body = {
|
|
||||||
type: error.type,
|
|
||||||
description: error.description,
|
|
||||||
filename: error.filename,
|
|
||||||
lineNumber: error.lineNumber,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
console.error(error.stack || error);
|
|
||||||
body = {
|
|
||||||
type: 'InternalError',
|
|
||||||
description: 'react-packager has encountered an internal error, ' +
|
|
||||||
'please check your terminal error output for more details',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {type: 'error', body};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateBundle(
|
|
||||||
client: Client,
|
|
||||||
filename: string,
|
|
||||||
): Promise<?HMRBundle> {
|
|
||||||
// If the main file is an asset, do not generate a bundle.
|
|
||||||
const moduleToUpdate = await packagerServer.getModuleForPath(filename);
|
|
||||||
if (moduleToUpdate.isAsset()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deps = await packagerServer.getShallowDependencies({
|
|
||||||
dev: true,
|
|
||||||
minify: false,
|
|
||||||
entryFile: filename,
|
|
||||||
rootEntryFile: client.bundleEntry,
|
|
||||||
hot: true,
|
|
||||||
platform: client.platform,
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// if the file dependencies have change we need to invalidate the
|
|
||||||
// dependencies caches because the list of files we need to send
|
|
||||||
// to the client may have changed
|
|
||||||
const oldDependencies = client.shallowDependencies[filename];
|
|
||||||
|
|
||||||
let resolutionResponse;
|
|
||||||
|
|
||||||
if (arrayEquals(deps, oldDependencies)) {
|
|
||||||
// Need to create a resolution response to pass to the bundler
|
|
||||||
// to process requires after transform. By providing a
|
|
||||||
// specific response we can compute a non recursive one which
|
|
||||||
// is the least we need and improve performance.
|
|
||||||
const response = await packagerServer.getDependencies({
|
|
||||||
dev: true,
|
|
||||||
entryFile: filename,
|
|
||||||
rootEntryFile: client.bundleEntry,
|
|
||||||
hot: true,
|
|
||||||
minify: false,
|
|
||||||
platform: client.platform,
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
resolutionResponse = await response.copy({
|
|
||||||
dependencies: [moduleToUpdate]},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// if there're new dependencies compare the full list of
|
|
||||||
// dependencies we used to have with the one we now have
|
|
||||||
const {
|
|
||||||
dependenciesCache: depsCache,
|
|
||||||
dependenciesModulesCache: depsModulesCache,
|
|
||||||
shallowDependencies: shallowDeps,
|
|
||||||
inverseDependenciesCache: inverseDepsCache,
|
|
||||||
resolutionResponse: myResolutionReponse,
|
|
||||||
} = await getDependencies(client.platform, client.bundleEntry);
|
|
||||||
|
|
||||||
// build list of modules for which we'll send HMR updates
|
|
||||||
const modulesToUpdate = [moduleToUpdate];
|
|
||||||
Object.keys(depsModulesCache).forEach(module => {
|
|
||||||
if (!client.dependenciesModulesCache[module]) {
|
|
||||||
modulesToUpdate.push(depsModulesCache[module]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Need to send modules to the client in an order it can
|
|
||||||
// process them: if a new dependency graph was uncovered
|
|
||||||
// because a new dependency was added, the file that was
|
|
||||||
// changed, which is the root of the dependency tree that
|
|
||||||
// will be sent, needs to be the last module that gets
|
|
||||||
// processed. Reversing the new modules makes sense
|
|
||||||
// because we get them through the resolver which returns
|
|
||||||
// a BFS ordered list.
|
|
||||||
modulesToUpdate.reverse();
|
|
||||||
|
|
||||||
// invalidate caches
|
|
||||||
client.dependenciesCache = depsCache;
|
|
||||||
client.dependenciesModulesCache = depsModulesCache;
|
|
||||||
client.shallowDependencies = shallowDeps;
|
|
||||||
client.inverseDependenciesCache = inverseDepsCache;
|
|
||||||
|
|
||||||
resolutionResponse = await myResolutionReponse.copy({
|
|
||||||
dependencies: modulesToUpdate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the file was modified is part of the bundle
|
|
||||||
if (!client.shallowDependencies[filename]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const httpServerAddress = httpServer.address();
|
|
||||||
|
|
||||||
// Sanitize the value from the HTTP server
|
|
||||||
let packagerHost = 'localhost';
|
|
||||||
if (httpServer.address().address &&
|
|
||||||
httpServer.address().address !== '::' &&
|
|
||||||
httpServer.address().address !== '') {
|
|
||||||
packagerHost = httpServerAddress.address;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bundle: HMRBundle = await packagerServer.buildBundleForHMR(
|
|
||||||
{
|
|
||||||
entryFile: client.bundleEntry,
|
|
||||||
platform: client.platform,
|
|
||||||
resolutionResponse,
|
|
||||||
},
|
|
||||||
packagerHost,
|
|
||||||
httpServerAddress.port,
|
|
||||||
);
|
|
||||||
|
|
||||||
return bundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFileChange(
|
|
||||||
type: string,
|
|
||||||
filename: string,
|
|
||||||
): void {
|
|
||||||
clients.forEach(
|
|
||||||
client => sendFileChangeToClient(client, type, filename),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendFileChangeToClient(
|
|
||||||
client: Client,
|
|
||||||
type: string,
|
|
||||||
filename: string,
|
|
||||||
): Promise<mixed> {
|
|
||||||
const blacklisted = blacklist.find(
|
|
||||||
blacklistedPath => filename.indexOf(blacklistedPath) !== -1,
|
|
||||||
);
|
|
||||||
if (blacklisted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clients.has(client)) {
|
|
||||||
client.ws.send(JSON.stringify({type: 'update-start'}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type !== 'delete') {
|
|
||||||
const response = await prepareResponse(client, filename);
|
|
||||||
|
|
||||||
if (response && clients.has(client)) {
|
|
||||||
client.ws.send(JSON.stringify(response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clients.has(client)) {
|
|
||||||
client.ws.send(JSON.stringify({type: 'update-done'}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an
|
|
||||||
* error found when Flow v0.54 was deployed. To see the error delete this
|
|
||||||
* comment and run Flow. */
|
|
||||||
const WebSocketServer = require('ws').Server;
|
|
||||||
const wss = new WebSocketServer({
|
|
||||||
server: httpServer,
|
|
||||||
path: path,
|
|
||||||
});
|
|
||||||
|
|
||||||
wss.on('connection', async ws => {
|
|
||||||
/* $FlowFixMe: url might be null */
|
|
||||||
const params = querystring.parse(url.parse(ws.upgradeReq.url).query);
|
|
||||||
|
|
||||||
const {
|
|
||||||
dependenciesCache,
|
|
||||||
dependenciesModulesCache,
|
|
||||||
shallowDependencies,
|
|
||||||
inverseDependenciesCache,
|
|
||||||
} = await getDependencies(params.platform, params.bundleEntry);
|
|
||||||
|
|
||||||
const client = {
|
|
||||||
ws,
|
|
||||||
platform: params.platform,
|
|
||||||
bundleEntry: params.bundleEntry,
|
|
||||||
dependenciesCache,
|
|
||||||
dependenciesModulesCache,
|
|
||||||
shallowDependencies,
|
|
||||||
inverseDependenciesCache,
|
|
||||||
};
|
|
||||||
clients.add(client);
|
|
||||||
|
|
||||||
// If this is the first client connecting, start listening to file changes
|
|
||||||
if (clients.size === 1) {
|
|
||||||
packagerServer.setHMRFileChangeListener(handleFileChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.ws.on('error', e => {
|
|
||||||
console.error('[Hot Module Replacement] Unexpected error', e);
|
|
||||||
disconnect(client);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.ws.on('close', () => disconnect(client));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayEquals<T>(arrayA: Array<T>, arrayB: Array<T>): boolean {
|
|
||||||
arrayA = arrayA || [];
|
|
||||||
arrayB = arrayB || [];
|
|
||||||
return (
|
|
||||||
arrayA.length === arrayB.length &&
|
|
||||||
arrayA.every((element, index) => {
|
|
||||||
return element === arrayB[index];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = attachHMRServer;
|
|
|
@ -1,70 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2015-present, Facebook, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the BSD-style license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree. An additional grant
|
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a subset of the actual `metro-bundler`'s `ResolutionResponse` class,
|
|
||||||
* without all the stuff we don't need to know about. This allows us to use
|
|
||||||
* `getInverseDependencies` with different versions of `metro-bundler`.
|
|
||||||
*/
|
|
||||||
export type ResolutionResponse<TModule> = {
|
|
||||||
copy(data: {
|
|
||||||
dependencies?: Array<TModule>,
|
|
||||||
mainModuleId?: number,
|
|
||||||
mocks?: mixed,
|
|
||||||
}): ResolutionResponse<TModule>,
|
|
||||||
dependencies: Array<TModule>,
|
|
||||||
getResolvedDependencyPairs(
|
|
||||||
module: TModule,
|
|
||||||
): $ReadOnlyArray<[string, TModule]>,
|
|
||||||
options: Object,
|
|
||||||
};
|
|
||||||
|
|
||||||
function resolveModuleRequires<TModule>(
|
|
||||||
resolutionResponse: ResolutionResponse<TModule>,
|
|
||||||
module: TModule,
|
|
||||||
): Array<TModule> {
|
|
||||||
const pairs = resolutionResponse.getResolvedDependencyPairs(module);
|
|
||||||
return pairs ? pairs.map(([, dependencyModule]) => dependencyModule) : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModuleDependents<TModule>(
|
|
||||||
cache: Map<TModule, Set<TModule>>,
|
|
||||||
module: TModule,
|
|
||||||
): Set<TModule> {
|
|
||||||
let dependents = cache.get(module);
|
|
||||||
if (!dependents) {
|
|
||||||
dependents = new Set();
|
|
||||||
cache.set(module, dependents);
|
|
||||||
}
|
|
||||||
return dependents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an object that indicates in which module each module is required.
|
|
||||||
*/
|
|
||||||
function getInverseDependencies<TModule>(
|
|
||||||
resolutionResponse: ResolutionResponse<TModule>,
|
|
||||||
): Map<TModule, Set<TModule>> {
|
|
||||||
const cache = new Map();
|
|
||||||
|
|
||||||
resolutionResponse.dependencies.forEach(module => {
|
|
||||||
resolveModuleRequires(resolutionResponse, module).forEach(dependency => {
|
|
||||||
getModuleDependents(cache, dependency).add(module);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = getInverseDependencies;
|
|
Loading…
Reference in New Issue