PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/metro/src/DeltaBundler
Просмотр файла: DeltaCalculator.js.flow
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {DeltaResult, Options} from './types';
import type {RootPerfLogger} from 'metro-config';
import type {ChangeEvent} from 'metro-file-map';
import {Graph} from './Graph';
import EventEmitter from 'events';
import path from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:DeltaCalculator');
/**
* This class is in charge of calculating the delta of changed modules that
* happen between calls. To do so, it subscribes to file changes, so it can
* traverse the files that have been changed between calls and avoid having to
* traverse the whole dependency tree for trivial small changes.
*/
export default class DeltaCalculator<T> extends EventEmitter {
_changeEventSource: EventEmitter;
_options: Options<T>;
_currentBuildPromise: ?Promise<DeltaResult<T>>;
_deletedFiles: Set<string> = new Set();
_modifiedFiles: Set<string> = new Set();
_addedFiles: Set<string> = new Set();
_requiresReset = false;
_graph: Graph<T>;
constructor(
entryPoints: $ReadOnlySet<string>,
changeEventSource: EventEmitter,
options: Options<T>,
) {
super();
this._options = options;
this._changeEventSource = changeEventSource;
this._graph = new Graph({
entryPoints,
transformOptions: this._options.transformOptions,
});
this._changeEventSource.on('change', this._handleMultipleFileChanges);
}
/**
* Stops listening for file changes and clears all the caches.
*/
end(): void {
this._changeEventSource.removeListener(
'change',
this._handleMultipleFileChanges,
);
this.removeAllListeners();
// Clean up all the cache data structures to deallocate memory.
this._graph = new Graph({
entryPoints: this._graph.entryPoints,
transformOptions: this._options.transformOptions,
});
this._modifiedFiles = new Set();
this._deletedFiles = new Set();
this._addedFiles = new Set();
}
/**
* Main method to calculate the delta of modules. It returns a DeltaResult,
* which contain the modified/added modules and the removed modules.
*/
async getDelta({
reset,
shallow,
}: {
reset: boolean,
shallow: boolean,
...
}): Promise<DeltaResult<T>> {
debug('Calculating delta (reset: %s, shallow: %s)', reset, shallow);
// If there is already a build in progress, wait until it finish to start
// processing a new one (delta server doesn't support concurrent builds).
if (this._currentBuildPromise) {
await this._currentBuildPromise;
}
// We don't want the modified files Set to be modified while building the
// bundle, so we isolate them by using the current instance for the bundling
// and creating a new instance for the file watcher.
const modifiedFiles = this._modifiedFiles;
this._modifiedFiles = new Set();
const deletedFiles = this._deletedFiles;
this._deletedFiles = new Set();
const addedFiles = this._addedFiles;
this._addedFiles = new Set();
const requiresReset = this._requiresReset;
this._requiresReset = false;
// Revisit all files if changes require a graph reset - resolutions may be
// invalidated but we don't yet know which. This should be optimized in the
// future.
if (requiresReset) {
const markModified = (file: string) => {
if (!addedFiles.has(file) && !deletedFiles.has(file)) {
modifiedFiles.add(file);
}
};
this._graph.dependencies.forEach((_, key) => markModified(key));
this._graph.entryPoints.forEach(markModified);
}
// Concurrent requests should reuse the same bundling process. To do so,
// this method stores the promise as an instance variable, and then it's
// removed after it gets resolved.
this._currentBuildPromise = this._getChangedDependencies(
modifiedFiles,
deletedFiles,
addedFiles,
);
let result;
try {
result = await this._currentBuildPromise;
} catch (error) {
// In case of error, we don't want to mark the modified files as
// processed (since we haven't actually created any delta). If we do not
// do so, asking for a delta after an error will produce an empty Delta,
// which is not correct.
modifiedFiles.forEach((file: string) => this._modifiedFiles.add(file));
deletedFiles.forEach((file: string) => this._deletedFiles.add(file));
addedFiles.forEach((file: string) => this._addedFiles.add(file));
throw error;
} finally {
this._currentBuildPromise = null;
}
// Return all the modules if the client requested a reset delta.
if (reset) {
this._graph.reorderGraph({shallow});
return {
added: this._graph.dependencies,
modified: new Map(),
deleted: new Set(),
reset: true,
};
}
return result;
}
/**
* Returns the graph with all the dependencies. Each module contains the
* needed information to do the traversing (dependencies, inverseDependencies)
* plus some metadata.
*/
getGraph(): Graph<T> {
return this._graph;
}
_handleMultipleFileChanges = (changeEvent: ChangeEvent) => {
changeEvent.eventsQueue.forEach(eventInfo => {
this._handleFileChange(eventInfo, changeEvent.logger);
});
};
/**
* Handles a single file change. To avoid doing any work before it's needed,
* the listener only stores the modified file, which will then be used later
* when the delta needs to be calculated.
*/
_handleFileChange = (
{type, filePath, metadata}: ChangeEvent['eventsQueue'][number],
logger: ?RootPerfLogger,
): mixed => {
debug('Handling %s: %s (type: %s)', type, filePath, metadata.type);
if (
metadata.type === 'l' ||
(this._options.unstable_enablePackageExports &&
filePath.endsWith(path.sep + 'package.json'))
) {
this._requiresReset = true;
this.emit('change', {logger});
}
let state: void | 'deleted' | 'modified' | 'added';
if (this._deletedFiles.has(filePath)) {
state = 'deleted';
} else if (this._modifiedFiles.has(filePath)) {
state = 'modified';
} else if (this._addedFiles.has(filePath)) {
state = 'added';
}
let nextState: 'deleted' | 'modified' | 'added';
if (type === 'delete') {
nextState = 'deleted';
} else if (type === 'add') {
// A deleted+added file is modified
nextState = state === 'deleted' ? 'modified' : 'added';
} else {
// type === 'change'
// An added+modified file is added
nextState = state === 'added' ? 'added' : 'modified';
}
switch (nextState) {
case 'deleted':
this._deletedFiles.add(filePath);
this._modifiedFiles.delete(filePath);
this._addedFiles.delete(filePath);
break;
case 'added':
this._addedFiles.add(filePath);
this._deletedFiles.delete(filePath);
this._modifiedFiles.delete(filePath);
break;
case 'modified':
this._modifiedFiles.add(filePath);
this._deletedFiles.delete(filePath);
this._addedFiles.delete(filePath);
break;
default:
(nextState: empty);
}
// Notify users that there is a change in some of the bundle files. This
// way the client can choose to refetch the bundle.
this.emit('change', {
logger,
});
};
async _getChangedDependencies(
modifiedFiles: Set<string>,
deletedFiles: Set<string>,
addedFiles: Set<string>,
): Promise<DeltaResult<T>> {
if (!this._graph.dependencies.size) {
const {added} = await this._graph.initialTraverseDependencies(
this._options,
);
return {
added,
modified: new Map(),
deleted: new Set(),
reset: true,
};
}
// If a file has been deleted, we want to invalidate any other file that
// depends on it, so we can process it and correctly return an error.
deletedFiles.forEach((filePath: string) => {
for (const modifiedModulePath of this._graph.getModifiedModulesForDeletedPath(
filePath,
)) {
// Only mark the inverse dependency as modified if it's not already
// marked as deleted (in that case we can just ignore it).
if (!deletedFiles.has(modifiedModulePath)) {
modifiedFiles.add(modifiedModulePath);
}
}
});
// NOTE(EvanBacon): This check adds extra complexity so we feature gate it
// to enable users to opt out.
if (this._options.unstable_allowRequireContext) {
// Check if any added or removed files are matched in a context module.
// We only need to do this for added files because (1) deleted files will have a context
// module as an inverse dependency, (2) modified files don't invalidate the contents
// of the context module.
addedFiles.forEach(filePath => {
this._graph.markModifiedContextModules(filePath, modifiedFiles);
});
}
// We only want to process files that are in the bundle.
const modifiedDependencies = Array.from(modifiedFiles).filter(
(filePath: string) => this._graph.dependencies.has(filePath),
);
// No changes happened. Return empty delta.
if (modifiedDependencies.length === 0) {
return {
added: new Map(),
modified: new Map(),
deleted: new Set(),
reset: false,
};
}
debug('Traversing dependencies for %s paths', modifiedDependencies.length);
const {added, modified, deleted} = await this._graph.traverseDependencies(
modifiedDependencies,
this._options,
);
debug(
'Calculated graph delta {added: %s, modified: %d, deleted: %d}',
added.size,
modified.size,
deleted.size,
);
return {
added,
modified,
deleted,
reset: false,
};
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!