Remove DeltaTransformer + DeltaPatcher

Reviewed By: cpojer

Differential Revision: D7320669

fbshipit-source-id: 9e4e44e64c77e1d77a403517c154f64acd1e66ae
This commit is contained in:
Rafael Oleza 2018-03-20 06:53:40 -07:00 committed by Facebook Github Bot
parent 5c6bdd35f0
commit feaf638688
8 changed files with 53 additions and 1003 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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();
});
});

View File

@ -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,
);
});
});

View File

@ -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;
};
}

View File

@ -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",
},
]
`;

View File

@ -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();

View File

@ -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() {