mirror of https://github.com/status-im/metro.git
Remove DeltaTransformer + DeltaPatcher
Reviewed By: cpojer Differential Revision: D7320669 fbshipit-source-id: 9e4e44e64c77e1d77a403517c154f64acd1e66ae
This commit is contained in:
parent
5c6bdd35f0
commit
feaf638688
|
@ -1,127 +0,0 @@
|
|||
/**
|
||||
* 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 {
|
||||
DeltaTransformResponse as DeltaBundle,
|
||||
DeltaEntry,
|
||||
} from './DeltaTransformer';
|
||||
|
||||
/**
|
||||
* This is a reference client for the Delta Bundler: it maintains cached the
|
||||
* last patched bundle delta and it's capable of applying new Deltas received
|
||||
* from the Bundler.
|
||||
*/
|
||||
class DeltaPatcher {
|
||||
static _deltaPatchers: Map<string, DeltaPatcher> = new Map();
|
||||
|
||||
_lastBundle = {
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
modules: new Map(),
|
||||
id: undefined,
|
||||
};
|
||||
_initialized = false;
|
||||
_lastNumModifiedFiles = 0;
|
||||
_lastModifiedDate = new Date();
|
||||
|
||||
static get(id: string): DeltaPatcher {
|
||||
let deltaPatcher = this._deltaPatchers.get(id);
|
||||
|
||||
if (!deltaPatcher) {
|
||||
deltaPatcher = new DeltaPatcher();
|
||||
this._deltaPatchers.set(id, deltaPatcher);
|
||||
}
|
||||
|
||||
return deltaPatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a Delta Bundle to the current bundle.
|
||||
*/
|
||||
applyDelta(deltaBundle: DeltaBundle): DeltaPatcher {
|
||||
// Make sure that the first received delta is a fresh one.
|
||||
if (!this._initialized && !deltaBundle.reset) {
|
||||
throw new Error(
|
||||
'DeltaPatcher should receive a fresh Delta when being initialized',
|
||||
);
|
||||
}
|
||||
|
||||
this._initialized = true;
|
||||
|
||||
// Reset the current delta when we receive a fresh delta.
|
||||
if (deltaBundle.reset) {
|
||||
this._lastBundle = {
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
modules: new Map(),
|
||||
id: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
this._lastNumModifiedFiles =
|
||||
deltaBundle.pre.size + deltaBundle.post.size + deltaBundle.delta.size;
|
||||
|
||||
if (this._lastNumModifiedFiles > 0) {
|
||||
this._lastModifiedDate = new Date();
|
||||
}
|
||||
|
||||
this._patchMap(this._lastBundle.pre, deltaBundle.pre);
|
||||
this._patchMap(this._lastBundle.post, deltaBundle.post);
|
||||
this._patchMap(this._lastBundle.modules, deltaBundle.delta);
|
||||
|
||||
this._lastBundle.id = deltaBundle.id;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
getLastBundleId(): ?string {
|
||||
return this._lastBundle.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of modified files in the last received Delta. This is
|
||||
* currently used to populate the `X-Metro-Files-Changed-Count` HTTP header
|
||||
* when metro serves the whole JS bundle, and can potentially be removed once
|
||||
* we only send the actual deltas to clients.
|
||||
*/
|
||||
getLastNumModifiedFiles(): number {
|
||||
return this._lastNumModifiedFiles;
|
||||
}
|
||||
|
||||
getLastModifiedDate(): Date {
|
||||
return this._lastModifiedDate;
|
||||
}
|
||||
|
||||
getAllModules(
|
||||
modifierFn: (
|
||||
modules: $ReadOnlyArray<DeltaEntry>,
|
||||
) => $ReadOnlyArray<DeltaEntry> = modules => modules,
|
||||
): $ReadOnlyArray<DeltaEntry> {
|
||||
return [].concat(
|
||||
Array.from(this._lastBundle.pre.values()),
|
||||
modifierFn(Array.from(this._lastBundle.modules.values())),
|
||||
Array.from(this._lastBundle.post.values()),
|
||||
);
|
||||
}
|
||||
|
||||
_patchMap<K, V>(original: Map<K, V>, patch: Map<K, ?V>) {
|
||||
for (const [key, value] of patch.entries()) {
|
||||
if (value == null) {
|
||||
original.delete(key);
|
||||
} else {
|
||||
original.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeltaPatcher;
|
|
@ -1,481 +0,0 @@
|
|||
/**
|
||||
* 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 DeltaCalculator = require('./DeltaCalculator');
|
||||
|
||||
const createModuleIdFactory = require('../lib/createModuleIdFactory');
|
||||
const crypto = require('crypto');
|
||||
const defaults = require('../defaults');
|
||||
const getPreludeCode = require('../lib/getPreludeCode');
|
||||
|
||||
const {wrapModule} = require('./Serializers/helpers/js');
|
||||
const {EventEmitter} = require('events');
|
||||
|
||||
import type Bundler from '../Bundler';
|
||||
import type {Options as JSTransformerOptions} from '../JSTransformer/worker';
|
||||
import type DependencyGraph from '../node-haste/DependencyGraph';
|
||||
import type Module from '../node-haste/Module';
|
||||
import type {BundleOptions} from '../shared/types.flow';
|
||||
import type {MainOptions} from './';
|
||||
import type {DependencyEdge, DependencyEdges} from './traverseDependencies';
|
||||
import type {MetroSourceMapSegmentTuple} from 'metro-source-map';
|
||||
|
||||
export type DeltaEntryType =
|
||||
| 'asset'
|
||||
| 'module'
|
||||
| 'script'
|
||||
| 'comment'
|
||||
| 'require';
|
||||
|
||||
export type DeltaEntry = {|
|
||||
+code: string,
|
||||
+id: number,
|
||||
+map: Array<MetroSourceMapSegmentTuple>,
|
||||
+name: string,
|
||||
+path: string,
|
||||
+source: string,
|
||||
+type: DeltaEntryType,
|
||||
|};
|
||||
|
||||
export type DeltaEntries = Map<number, ?DeltaEntry>;
|
||||
|
||||
export type DeltaTransformResponse = {|
|
||||
+id: string,
|
||||
+pre: DeltaEntries,
|
||||
+post: DeltaEntries,
|
||||
+delta: DeltaEntries,
|
||||
+reset: boolean,
|
||||
|};
|
||||
|
||||
const globalCreateModuleId = createModuleIdFactory();
|
||||
|
||||
/**
|
||||
* This class is in charge of creating the delta bundle with the actual
|
||||
* transformed source code for each of the modified modules. For each modified
|
||||
* module it returns a `DeltaModule` object that contains the basic information
|
||||
* about that file. Modules that have been deleted contain a `null` module
|
||||
* parameter.
|
||||
*
|
||||
* The actual return format is the following:
|
||||
*
|
||||
* {
|
||||
* pre: [{id, module: {}}], Scripts to be prepended before the actual
|
||||
* modules.
|
||||
* post: [{id, module: {}}], Scripts to be appended after all the modules
|
||||
* (normally the initial require() calls).
|
||||
* delta: [{id, module: {}}], Actual bundle modules (dependencies).
|
||||
* }
|
||||
*/
|
||||
class DeltaTransformer extends EventEmitter {
|
||||
_bundler: Bundler;
|
||||
_dependencyGraph: DependencyGraph;
|
||||
_getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>;
|
||||
_polyfillModuleNames: $ReadOnlyArray<string>;
|
||||
_getModuleId: (path: string) => number;
|
||||
_deltaCalculator: DeltaCalculator;
|
||||
_bundleOptions: BundleOptions;
|
||||
_currentBuildPromise: ?Promise<DeltaTransformResponse>;
|
||||
_lastSequenceId: ?string;
|
||||
|
||||
constructor(
|
||||
bundler: Bundler,
|
||||
dependencyGraph: DependencyGraph,
|
||||
deltaCalculator: DeltaCalculator,
|
||||
options: MainOptions,
|
||||
bundleOptions: BundleOptions,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._bundler = bundler;
|
||||
this._dependencyGraph = dependencyGraph;
|
||||
this._deltaCalculator = deltaCalculator;
|
||||
this._getPolyfills = options.getPolyfills;
|
||||
this._polyfillModuleNames = options.polyfillModuleNames;
|
||||
this._bundleOptions = bundleOptions;
|
||||
|
||||
// Only when isolateModuleIDs is true the Module IDs of this instance are
|
||||
// sandboxed from the rest.
|
||||
// Isolating them makes sense when we want to get consistent module IDs
|
||||
// between different builds of the same bundle (for example when building
|
||||
// production builds), while coupling them makes sense when we want
|
||||
// different bundles to share the same ids (on HMR, where we need to patch
|
||||
// the correct module).
|
||||
this._getModuleId = this._bundleOptions.isolateModuleIDs
|
||||
? (bundleOptions.createModuleIdFactory || createModuleIdFactory)()
|
||||
: globalCreateModuleId;
|
||||
|
||||
this._deltaCalculator.on('change', this._onFileChange);
|
||||
}
|
||||
|
||||
static async create(
|
||||
bundler: Bundler,
|
||||
options: MainOptions,
|
||||
bundleOptions: BundleOptions,
|
||||
): Promise<DeltaTransformer> {
|
||||
const dependencyGraph = await bundler.getDependencyGraph();
|
||||
|
||||
const deltaCalculator = new DeltaCalculator(bundler, dependencyGraph, {
|
||||
...bundleOptions,
|
||||
entryPoints: [bundleOptions.entryFile],
|
||||
type: 'module',
|
||||
});
|
||||
|
||||
return new DeltaTransformer(
|
||||
bundler,
|
||||
dependencyGraph,
|
||||
deltaCalculator,
|
||||
options,
|
||||
bundleOptions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the Delta Transformer and its calculator. This should be used to
|
||||
* clean up memory and resources once this instance is not used anymore.
|
||||
*/
|
||||
end() {
|
||||
this.removeAllListeners();
|
||||
|
||||
return this._deltaCalculator.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that can be used to calculate synchronously the
|
||||
* transitive dependencies of any given file within the dependency graph.
|
||||
**/
|
||||
async getDependenciesFn(): Promise<(string) => Set<string>> {
|
||||
if (!this._deltaCalculator.getGraph().dependencies.size) {
|
||||
// If by any means the dependency graph has not been initialized, call
|
||||
// getDelta() to initialize it.
|
||||
await this._getDelta({reset: false});
|
||||
}
|
||||
|
||||
return this._getDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that can be used to calculate synchronously the
|
||||
* transitive dependencies of any given file within the dependency graph.
|
||||
**/
|
||||
async getInverseDependencies(): Promise<Map<number, $ReadOnlyArray<number>>> {
|
||||
const graph = this._deltaCalculator.getGraph();
|
||||
|
||||
if (!graph.dependencies.size) {
|
||||
// If by any means the dependency graph has not been initialized, call
|
||||
// getDelta() to initialize it.
|
||||
await this._getDelta({reset: false});
|
||||
}
|
||||
|
||||
const output = new Map();
|
||||
|
||||
for (const [path, {inverseDependencies}] of graph.dependencies.entries()) {
|
||||
output.set(
|
||||
this._getModuleId(path),
|
||||
Array.from(inverseDependencies).map(dep => this._getModuleId(dep)),
|
||||
);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to calculate the bundle delta. It returns a DeltaResult,
|
||||
* which contain the source code of the modified and added modules and the
|
||||
* list of removed modules.
|
||||
*/
|
||||
async getDelta(sequenceId: ?string): Promise<DeltaTransformResponse> {
|
||||
// If the passed sequenceId is different than the last calculated one,
|
||||
// return a reset delta (since that means that the client is desynchronized)
|
||||
const reset = !!this._lastSequenceId && sequenceId !== this._lastSequenceId;
|
||||
|
||||
// If there is already a build in progress, wait until it finish to start
|
||||
// processing a new one (delta transformer doesn't support concurrent
|
||||
// builds).
|
||||
if (this._currentBuildPromise) {
|
||||
await this._currentBuildPromise;
|
||||
}
|
||||
|
||||
this._currentBuildPromise = this._getDelta({reset});
|
||||
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = await this._currentBuildPromise;
|
||||
} finally {
|
||||
this._currentBuildPromise = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async _getDelta({
|
||||
reset: resetDelta,
|
||||
}: {
|
||||
reset: boolean,
|
||||
}): Promise<DeltaTransformResponse> {
|
||||
// Calculate the delta of modules.
|
||||
const {modified, deleted, reset} = await this._deltaCalculator.getDelta({
|
||||
reset: resetDelta,
|
||||
});
|
||||
const graph = this._deltaCalculator.getGraph();
|
||||
|
||||
const transformerOptions = await this._deltaCalculator.getTransformerOptions();
|
||||
|
||||
// Return the source code that gets prepended to all the modules. This
|
||||
// contains polyfills and startup code (like the require() implementation).
|
||||
const prependSources = reset
|
||||
? await this._getPrepend(transformerOptions, graph.dependencies)
|
||||
: new Map();
|
||||
|
||||
// Precalculate all module ids sequentially. We do this to be sure that the
|
||||
// mapping between module -> moduleId is deterministic between runs.
|
||||
const modules = Array.from(modified.values());
|
||||
modules.forEach(module => this._getModuleId(module.path));
|
||||
|
||||
// Get the transformed source code of each modified/added module.
|
||||
const modifiedDelta = await this._transformModules(
|
||||
modules,
|
||||
transformerOptions,
|
||||
graph.dependencies,
|
||||
);
|
||||
|
||||
deleted.forEach(id => {
|
||||
modifiedDelta.set(this._getModuleId(id), null);
|
||||
});
|
||||
|
||||
// Return the source code that gets appended to all the modules. This
|
||||
// contains the require() calls to startup the execution of the modules.
|
||||
const appendSources = reset
|
||||
? await this._getAppend(graph.dependencies)
|
||||
: new Map();
|
||||
|
||||
// generate a random
|
||||
this._lastSequenceId = crypto.randomBytes(8).toString('hex');
|
||||
|
||||
return {
|
||||
pre: prependSources,
|
||||
post: appendSources,
|
||||
delta: modifiedDelta,
|
||||
reset,
|
||||
id: this._lastSequenceId,
|
||||
};
|
||||
}
|
||||
|
||||
_getDependencies = (path: string): Set<string> => {
|
||||
const graph = this._deltaCalculator.getGraph();
|
||||
|
||||
const dependencies = this._getDeps(path, graph.dependencies, new Set());
|
||||
|
||||
// Remove the main entry point, since this method only returns the
|
||||
// dependencies.
|
||||
dependencies.delete(path);
|
||||
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
_getDeps(
|
||||
path: string,
|
||||
edges: DependencyEdges,
|
||||
deps: Set<string>,
|
||||
): Set<string> {
|
||||
if (deps.has(path)) {
|
||||
return deps;
|
||||
}
|
||||
|
||||
const edge = edges.get(path);
|
||||
|
||||
if (!edge) {
|
||||
return deps;
|
||||
}
|
||||
|
||||
deps.add(path);
|
||||
|
||||
for (const [, dependencyPath] of edge.dependencies) {
|
||||
this._getDeps(dependencyPath, edges, deps);
|
||||
}
|
||||
|
||||
return deps;
|
||||
}
|
||||
|
||||
async _getPrepend(
|
||||
transformOptions: JSTransformerOptions,
|
||||
dependencyEdges: DependencyEdges,
|
||||
): Promise<DeltaEntries> {
|
||||
const preludeId = this._getModuleId('__prelude__');
|
||||
|
||||
// Get all the polyfills from the relevant option params (the
|
||||
// `getPolyfills()` method and the `polyfillModuleNames` variable).
|
||||
const polyfillModuleNames = this._getPolyfills({
|
||||
platform: this._bundleOptions.platform,
|
||||
}).concat(this._polyfillModuleNames);
|
||||
|
||||
// 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 =>
|
||||
this._dependencyGraph.createPolyfill({
|
||||
file: polyfillModuleName,
|
||||
id: polyfillModuleName,
|
||||
dependencies: [],
|
||||
}),
|
||||
);
|
||||
|
||||
const edges = await Promise.all(
|
||||
modules.map(module =>
|
||||
this._createEdgeFromScript(module, transformOptions),
|
||||
),
|
||||
);
|
||||
|
||||
const transformedModules = await this._transformModules(
|
||||
edges,
|
||||
transformOptions,
|
||||
dependencyEdges,
|
||||
);
|
||||
// The prelude needs to be the first thing in the file, and the insertion
|
||||
// order of entries in the Map is significant.
|
||||
return new Map([
|
||||
[preludeId, this._getPrelude(preludeId)],
|
||||
...transformedModules,
|
||||
]);
|
||||
}
|
||||
|
||||
_getPrelude(id: number): DeltaEntry {
|
||||
const code = getPreludeCode({isDev: this._bundleOptions.dev});
|
||||
const name = '__prelude__';
|
||||
return {code, id, map: [], name, source: code, path: name, type: 'script'};
|
||||
}
|
||||
|
||||
async _getAppend(dependencyEdges: DependencyEdges): Promise<DeltaEntries> {
|
||||
// First, get the modules correspondant to all the module names defined in
|
||||
// the `runBeforeMainModule` config variable. Then, append the entry point
|
||||
// module so the last thing that gets required is the entry point.
|
||||
const append = new Map(
|
||||
this._bundleOptions.runBeforeMainModule
|
||||
.concat(this._bundleOptions.entryFile)
|
||||
.filter(path => dependencyEdges.has(path))
|
||||
.map(this._getModuleId)
|
||||
.map(moduleId => {
|
||||
const code = `require(${JSON.stringify(moduleId)});`;
|
||||
const name = 'require-' + String(moduleId);
|
||||
const path = name + '.js';
|
||||
|
||||
return [
|
||||
moduleId,
|
||||
{
|
||||
code,
|
||||
id: moduleId,
|
||||
map: [],
|
||||
name,
|
||||
source: code,
|
||||
path,
|
||||
type: 'require',
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
|
||||
if (this._bundleOptions.sourceMapUrl) {
|
||||
const code = '//# sourceMappingURL=' + this._bundleOptions.sourceMapUrl;
|
||||
const id = this._getModuleId('/sourcemap.js');
|
||||
|
||||
append.set(id, {
|
||||
code,
|
||||
id,
|
||||
map: [],
|
||||
name: 'sourcemap.js',
|
||||
path: '/sourcemap.js',
|
||||
source: code,
|
||||
type: 'comment',
|
||||
});
|
||||
}
|
||||
|
||||
return append;
|
||||
}
|
||||
|
||||
async _transformModules(
|
||||
modules: Array<DependencyEdge>,
|
||||
transformOptions: JSTransformerOptions,
|
||||
dependencyEdges: DependencyEdges,
|
||||
): Promise<DeltaEntries> {
|
||||
return new Map(
|
||||
await Promise.all(
|
||||
modules.map(module =>
|
||||
this._transformModule(module, transformOptions, dependencyEdges),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async _createEdgeFromScript(
|
||||
module: Module,
|
||||
transformOptions: JSTransformerOptions,
|
||||
): Promise<DependencyEdge> {
|
||||
const result = await module.read(transformOptions);
|
||||
|
||||
const edge = {
|
||||
dependencies: new Map(),
|
||||
inverseDependencies: new Set(),
|
||||
path: module.path,
|
||||
output: {
|
||||
code: result.code,
|
||||
map: result.map,
|
||||
source: result.source,
|
||||
type: 'script',
|
||||
},
|
||||
};
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
async _transformModule(
|
||||
edge: DependencyEdge,
|
||||
transformOptions: JSTransformerOptions,
|
||||
dependencyEdges: DependencyEdges,
|
||||
): Promise<[number, ?DeltaEntry]> {
|
||||
const name = this._dependencyGraph.getHasteName(edge.path);
|
||||
|
||||
const wrappedCode = wrapModule(edge, {
|
||||
createModuleId: this._getModuleId,
|
||||
dev: transformOptions.dev,
|
||||
});
|
||||
|
||||
const {code, map} = transformOptions.minify
|
||||
? await this._bundler.minifyModule(
|
||||
edge.path,
|
||||
wrappedCode,
|
||||
edge.output.map,
|
||||
)
|
||||
: {code: wrappedCode, map: edge.output.map};
|
||||
|
||||
const id = this._getModuleId(edge.path);
|
||||
|
||||
return [
|
||||
id,
|
||||
{
|
||||
code,
|
||||
id,
|
||||
map,
|
||||
name,
|
||||
source: edge.output.source,
|
||||
path: edge.path,
|
||||
type: edge.output.type,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
_onFileChange = () => {
|
||||
this.emit('change');
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = DeltaTransformer;
|
|
@ -10,65 +10,84 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
jest.mock('../DeltaTransformer');
|
||||
jest.mock('../../Bundler');
|
||||
jest.mock('../DeltaCalculator');
|
||||
|
||||
const Bundler = require('../../Bundler');
|
||||
const DeltaTransformer = require('../DeltaTransformer');
|
||||
const DeltaCalculator = require('../DeltaCalculator');
|
||||
|
||||
const DeltaBundler = require('../');
|
||||
|
||||
describe('DeltaBundler', () => {
|
||||
let deltaBundler;
|
||||
let bundler;
|
||||
const initialTransformerResponse = {
|
||||
pre: new Map([[1, {code: 'pre'}]]),
|
||||
post: new Map([[2, {code: 'post'}]]),
|
||||
delta: new Map([[3, {code: 'module3'}], [4, {code: 'another'}]]),
|
||||
inverseDependencies: [],
|
||||
reset: true,
|
||||
|
||||
const graph = {
|
||||
dependencides: new Map([
|
||||
['/entry', {code: 'entry'}],
|
||||
['/foo', {code: 'foo'}],
|
||||
]),
|
||||
entryPoints: ['/entry'],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
DeltaTransformer.prototype.getDelta = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(Promise.resolve(initialTransformerResponse));
|
||||
|
||||
DeltaTransformer.create = jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve(new DeltaTransformer()));
|
||||
|
||||
bundler = new Bundler();
|
||||
deltaBundler = new DeltaBundler(bundler, {});
|
||||
|
||||
DeltaCalculator.prototype.getDelta.mockImplementation(async ({reset}) =>
|
||||
Promise.resolve({
|
||||
modified: reset ? graph.dependencies : new Map(),
|
||||
deleted: new Set(),
|
||||
reset,
|
||||
}),
|
||||
);
|
||||
|
||||
DeltaCalculator.prototype.getGraph.mockReturnValue(graph);
|
||||
});
|
||||
|
||||
it('should create a new transformer the first time it gets called', async () => {
|
||||
await deltaBundler.getDeltaTransformer('foo', {deltaBundleId: 10});
|
||||
it('should create a new graph when buildGraph gets called', async () => {
|
||||
expect(await deltaBundler.buildGraph({})).toEqual(graph);
|
||||
|
||||
expect(DeltaTransformer.create.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should reuse the same transformer after a second call', async () => {
|
||||
await deltaBundler.getDeltaTransformer('foo', {deltaBundleId: 10});
|
||||
await deltaBundler.getDeltaTransformer('foo', {deltaBundleId: 20});
|
||||
|
||||
expect(DeltaTransformer.create.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should create different transformers for different clients', async () => {
|
||||
await deltaBundler.getDeltaTransformer('foo', {});
|
||||
await deltaBundler.getDeltaTransformer('bar', {});
|
||||
|
||||
expect(DeltaTransformer.create.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should reset everything after calling end()', async () => {
|
||||
await deltaBundler.getDeltaTransformer('foo', {deltaBundleId: 10});
|
||||
|
||||
deltaBundler.end();
|
||||
|
||||
await deltaBundler.getDeltaTransformer({deltaBundleId: 10});
|
||||
|
||||
expect(DeltaTransformer.create.mock.calls.length).toBe(2);
|
||||
expect(DeltaCalculator.prototype.getDelta.mock.calls[0][0]).toEqual({
|
||||
reset: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should get a delta when getDelta gets called', async () => {
|
||||
const graph = await deltaBundler.buildGraph({});
|
||||
|
||||
expect(await deltaBundler.getDelta(graph, {reset: false})).toEqual({
|
||||
modified: new Map(),
|
||||
deleted: new Set(),
|
||||
reset: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should get a reset delta when calling getDelta({reset: true})', async () => {
|
||||
const graph = await deltaBundler.buildGraph({});
|
||||
|
||||
expect(await deltaBundler.getDelta(graph, {reset: true})).toEqual({
|
||||
modified: graph.dependencies,
|
||||
deleted: new Set(),
|
||||
reset: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when trying to get the delta of a graph that does not exist', async () => {
|
||||
const graph = await deltaBundler.buildGraph({});
|
||||
|
||||
deltaBundler.endGraph(graph);
|
||||
|
||||
await expect(
|
||||
deltaBundler.getDelta(graph, {reset: false}),
|
||||
).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
it('should throw an error when trying to end a graph twice', async () => {
|
||||
const graph = await deltaBundler.buildGraph({});
|
||||
|
||||
deltaBundler.endGraph(graph);
|
||||
|
||||
expect(() => deltaBundler.endGraph(graph)).toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
/**
|
||||
* 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 DeltaPatcher = require('../DeltaPatcher');
|
||||
|
||||
const INITIAL_TIME = 1482363367000;
|
||||
|
||||
describe('DeltaPatcher', () => {
|
||||
const OriginalDate = global.Date;
|
||||
let deltaPatcher;
|
||||
|
||||
function setCurrentTime(time: number) {
|
||||
global.Date = jest.fn(() => new OriginalDate(time));
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
deltaPatcher = new DeltaPatcher();
|
||||
|
||||
setCurrentTime(INITIAL_TIME);
|
||||
});
|
||||
|
||||
it('should throw if received a non-reset delta as the initial one', () => {
|
||||
expect(() =>
|
||||
deltaPatcher.applyDelta({
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
delta: new Map(),
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should apply an initial delta correctly', () => {
|
||||
expect(
|
||||
deltaPatcher
|
||||
.applyDelta({
|
||||
reset: 1,
|
||||
pre: new Map([[1, {code: 'pre'}]]),
|
||||
post: new Map([[2, {code: 'post'}]]),
|
||||
delta: new Map([[3, {code: 'middle'}]]),
|
||||
})
|
||||
.getAllModules(),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should apply many different patches correctly', () => {
|
||||
const result = deltaPatcher
|
||||
.applyDelta({
|
||||
reset: 1,
|
||||
pre: new Map([[1000, {code: 'pre'}]]),
|
||||
post: new Map([[2000, {code: 'post'}]]),
|
||||
delta: new Map([[1, {code: 'middle'}]]),
|
||||
})
|
||||
.applyDelta({
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
delta: new Map([[2, {code: 'another'}]]),
|
||||
})
|
||||
.applyDelta({
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
delta: new Map([[2, {code: 'another'}], [87, {code: 'third'}]]),
|
||||
})
|
||||
.getAllModules();
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
|
||||
const anotherResult = deltaPatcher
|
||||
.applyDelta({
|
||||
pre: new Map([[1000, {code: 'new pre'}]]),
|
||||
post: new Map(),
|
||||
delta: new Map([[2, {code: 'another'}], [1, null]]),
|
||||
})
|
||||
.applyDelta({
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
delta: new Map([[2, null], [12, {code: 'twelve'}]]),
|
||||
})
|
||||
.getAllModules();
|
||||
|
||||
expect(anotherResult).toMatchSnapshot();
|
||||
|
||||
expect(
|
||||
deltaPatcher
|
||||
.applyDelta({
|
||||
pre: new Map([[1000, {code: '1'}]]),
|
||||
post: new Map([[1000, {code: '1'}]]),
|
||||
delta: new Map([[12, {code: 'ten'}]]),
|
||||
reset: true,
|
||||
})
|
||||
.getAllModules(),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return the number of modified files in the last Delta', () => {
|
||||
deltaPatcher.applyDelta({
|
||||
reset: 1,
|
||||
pre: new Map([[1, {code: 'pre'}]]),
|
||||
post: new Map([[2, {code: 'post'}]]),
|
||||
delta: new Map([[3, {code: 'middle'}]]),
|
||||
});
|
||||
|
||||
expect(deltaPatcher.getLastNumModifiedFiles()).toEqual(3);
|
||||
|
||||
deltaPatcher.applyDelta({
|
||||
reset: 1,
|
||||
pre: new Map([[1, null]]),
|
||||
post: new Map(),
|
||||
delta: new Map([[3, {code: 'different'}]]),
|
||||
});
|
||||
|
||||
// A deleted module counts as a modified file.
|
||||
expect(deltaPatcher.getLastNumModifiedFiles()).toEqual(2);
|
||||
});
|
||||
|
||||
it('should return the time it was last modified', () => {
|
||||
deltaPatcher.applyDelta({
|
||||
reset: 1,
|
||||
pre: new Map([[1, {code: 'pre'}]]),
|
||||
post: new Map([[2, {code: 'post'}]]),
|
||||
delta: new Map([[3, {code: 'middle'}]]),
|
||||
});
|
||||
|
||||
expect(deltaPatcher.getLastModifiedDate().getTime()).toEqual(INITIAL_TIME);
|
||||
setCurrentTime(INITIAL_TIME + 1000);
|
||||
|
||||
// Apply empty delta
|
||||
deltaPatcher.applyDelta({
|
||||
reset: 1,
|
||||
pre: new Map(),
|
||||
post: new Map(),
|
||||
delta: new Map(),
|
||||
});
|
||||
|
||||
expect(deltaPatcher.getLastModifiedDate().getTime()).toEqual(INITIAL_TIME);
|
||||
setCurrentTime(INITIAL_TIME + 2000);
|
||||
|
||||
deltaPatcher.applyDelta({
|
||||
reset: 1,
|
||||
pre: new Map(),
|
||||
post: new Map([[2, {code: 'newpost'}]]),
|
||||
delta: new Map(),
|
||||
});
|
||||
|
||||
expect(deltaPatcher.getLastModifiedDate().getTime()).toEqual(
|
||||
INITIAL_TIME + 2000,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* 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+js_foundation
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.mock('fs', () => new (require('metro-memory-fs'))())
|
||||
.mock('assert')
|
||||
.mock('progress')
|
||||
.mock('../DeltaCalculator')
|
||||
.mock('../../node-haste/DependencyGraph')
|
||||
.mock('../../JSTransformer')
|
||||
.mock('/root/to/something.js', () => ({}), {virtual: true})
|
||||
.mock('/path/to/transformer.js', () => ({}), {virtual: true});
|
||||
|
||||
const fs = require('fs');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
const Bundler = require('../../Bundler');
|
||||
const DeltaTransformer = require('../DeltaTransformer');
|
||||
const DependencyGraph = require('../../node-haste/DependencyGraph');
|
||||
|
||||
const defaults = require('../../defaults');
|
||||
|
||||
const bundlerOptions = {
|
||||
allowBundleUpdates: false,
|
||||
assetExts: defaults.assetExts,
|
||||
cacheStores: [],
|
||||
cacheVersion: 'smth',
|
||||
enableBabelRCLookup: true,
|
||||
extraNodeModules: {},
|
||||
minifierPath: defaults.DEFAULT_METRO_MINIFIER_PATH,
|
||||
platforms: defaults.platforms,
|
||||
resetCache: false,
|
||||
sourceExts: defaults.sourceExts,
|
||||
transformModulePath: '/path/to/transformer.js',
|
||||
watch: false,
|
||||
projectRoots: ['/root'],
|
||||
assetServer: {
|
||||
getAssetData: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
describe('DeltaTransformer', () => {
|
||||
let bundler;
|
||||
beforeEach(() => {
|
||||
DependencyGraph.load = jest
|
||||
.fn()
|
||||
.mockImplementation(opts => Promise.resolve(new DependencyGraph(opts)));
|
||||
|
||||
mkdirp.sync('/path/to');
|
||||
fs.writeFileSync('/path/to/transformer.js', '');
|
||||
mkdirp.sync('/root/to');
|
||||
fs.writeFileSync('/root/to/something.js', '');
|
||||
|
||||
bundler = new Bundler(bundlerOptions);
|
||||
});
|
||||
|
||||
it('should allow setting a custom module ID factory', async () => {
|
||||
const bundlerOptions = {
|
||||
isolateModuleIDs: true,
|
||||
createModuleIdFactory: createPlus10000ModuleIdFactory,
|
||||
};
|
||||
|
||||
const deltaTransformer = await DeltaTransformer.create(
|
||||
bundler,
|
||||
{},
|
||||
bundlerOptions,
|
||||
);
|
||||
|
||||
expect(deltaTransformer._getModuleId('test/path')).toBe(10000);
|
||||
});
|
||||
});
|
||||
|
||||
function createPlus10000ModuleIdFactory(): (path: string) => number {
|
||||
const fileToIdMap: Map<string, number> = new Map();
|
||||
let nextId = 10000;
|
||||
return (path: string) => {
|
||||
let id = fileToIdMap.get(path);
|
||||
if (typeof id !== 'number') {
|
||||
id = nextId++;
|
||||
fileToIdMap.set(path, id);
|
||||
}
|
||||
return id;
|
||||
};
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DeltaPatcher should apply an initial delta correctly 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"code": "pre",
|
||||
},
|
||||
Object {
|
||||
"code": "middle",
|
||||
},
|
||||
Object {
|
||||
"code": "post",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`DeltaPatcher should apply many different patches correctly 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"code": "pre",
|
||||
},
|
||||
Object {
|
||||
"code": "middle",
|
||||
},
|
||||
Object {
|
||||
"code": "another",
|
||||
},
|
||||
Object {
|
||||
"code": "third",
|
||||
},
|
||||
Object {
|
||||
"code": "post",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`DeltaPatcher should apply many different patches correctly 2`] = `
|
||||
Array [
|
||||
Object {
|
||||
"code": "new pre",
|
||||
},
|
||||
Object {
|
||||
"code": "third",
|
||||
},
|
||||
Object {
|
||||
"code": "twelve",
|
||||
},
|
||||
Object {
|
||||
"code": "post",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`DeltaPatcher should apply many different patches correctly 3`] = `
|
||||
Array [
|
||||
Object {
|
||||
"code": "1",
|
||||
},
|
||||
Object {
|
||||
"code": "ten",
|
||||
},
|
||||
Object {
|
||||
"code": "1",
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -11,21 +11,14 @@
|
|||
'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';
|
||||
|
||||
export type MainOptions = {|
|
||||
getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>,
|
||||
polyfillModuleNames: $ReadOnlyArray<string>,
|
||||
|};
|
||||
|
||||
export type Delta = DeltaResult;
|
||||
export type Graph = CalculatorGraph;
|
||||
|
||||
|
@ -37,53 +30,17 @@ export type Graph = CalculatorGraph;
|
|||
*/
|
||||
class DeltaBundler {
|
||||
_bundler: Bundler;
|
||||
_options: MainOptions;
|
||||
_deltaTransformers: Map<string, DeltaTransformer> = new Map();
|
||||
_currentId: number = 0;
|
||||
_deltaCalculators: Map<Graph, DeltaCalculator> = new Map();
|
||||
|
||||
constructor(bundler: Bundler, options: MainOptions) {
|
||||
constructor(bundler: Bundler) {
|
||||
this._bundler = bundler;
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
end() {
|
||||
this._deltaTransformers.forEach(DeltaTransformer => DeltaTransformer.end());
|
||||
this._deltaTransformers = new Map();
|
||||
|
||||
this._deltaCalculators.forEach(deltaCalculator => deltaCalculator.end());
|
||||
this._deltaCalculators = new Map();
|
||||
}
|
||||
|
||||
endTransformer(clientId: string) {
|
||||
const deltaTransformer = this._deltaTransformers.get(clientId);
|
||||
|
||||
if (deltaTransformer) {
|
||||
deltaTransformer.end();
|
||||
|
||||
this._deltaTransformers.delete(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
async getDeltaTransformer(
|
||||
clientId: string,
|
||||
options: BundleOptions,
|
||||
): Promise<DeltaTransformer> {
|
||||
let deltaTransformer = this._deltaTransformers.get(clientId);
|
||||
|
||||
if (!deltaTransformer) {
|
||||
deltaTransformer = await DeltaTransformer.create(
|
||||
this._bundler,
|
||||
this._options,
|
||||
options,
|
||||
);
|
||||
|
||||
this._deltaTransformers.set(clientId, deltaTransformer);
|
||||
}
|
||||
|
||||
return deltaTransformer;
|
||||
}
|
||||
|
||||
async buildGraph(options: Options): Promise<Graph> {
|
||||
const depGraph = await this._bundler.getDependencyGraph();
|
||||
|
||||
|
|
|
@ -232,10 +232,7 @@ class Server {
|
|||
this._symbolicateInWorker = symbolicate.createWorker();
|
||||
this._nextBundleBuildID = 1;
|
||||
|
||||
this._deltaBundler = new DeltaBundler(this._bundler, {
|
||||
getPolyfills: this._opts.getPolyfills,
|
||||
polyfillModuleNames: this._opts.polyfillModuleNames,
|
||||
});
|
||||
this._deltaBundler = new DeltaBundler(this._bundler);
|
||||
}
|
||||
|
||||
end() {
|
||||
|
|
Loading…
Reference in New Issue