packager: MapWithDefaults: @flow

Summary:
It was hard to type the resolution main algo, I had to put type annotations explicitely everywhere, otherwise Flow would get in some kind of loop and do weird errors. I think it's because the algo is recursive and Flow tries to infer types too deeply because of the generics.

Anyway, apart from that it's good to get this extra type security in the few other places. We require Node v4 minimum, that according to the internets supports the `class` syntax without transform, and I verified that inheriting from `Map` actually works as expected.

Reviewed By: davidaurelio

Differential Revision: D5078023

fbshipit-source-id: 05dfc4acf5b07cdda8a7b36ec9cba216d1810643
This commit is contained in:
Jean Lauliac 2017-05-17 09:21:25 -07:00 committed by Facebook Github Bot
parent 0357303f5b
commit e6bc8ea10a
8 changed files with 60 additions and 30 deletions

View File

@ -642,7 +642,7 @@ class Bundler {
entryFilePath: string,
options: BundlingOptions,
getModuleId: () => number,
dependencyPairs: Array<[mixed, {path: string}]>,
dependencyPairs: Array<[string, Module]>,
assetPlugins: Array<string>,
}): Promise<ModuleTransport> {
let moduleTransport;

View File

@ -552,6 +552,7 @@ class Server {
changedModules.forEach(m => {
response.setResolvedDependencyPairs(
m,
/* $FlowFixMe: should be enforced not to be null. */
dependencyPairs.get(m.path),
{ignoreFinalized: true},
);

View File

@ -12,13 +12,14 @@
'use strict';
import type {RawMapping} from '../Bundler/source-map';
import type Module from '../node-haste/Module';
import type {SourceMap} from './SourceMap';
type SourceMapOrMappings = SourceMap | Array<RawMapping>;
type Metadata = {
dependencies?: ?Array<string>,
dependencyPairs?: Array<[mixed, {path: string}]>,
dependencyPairs?: Array<[string, Module]>,
preloaded: ?boolean,
};

View File

@ -214,9 +214,10 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
);
});
const collectedDependencies = new MapWithDefaults(module =>
collect(module),
);
const collectedDependencies: MapWithDefaults<
TModule,
Promise<Array<TModule>>,
> = new MapWithDefaults(module => collect(module));
const crawlDependencies = (mod, [depNames, dependencies]) => {
const filteredPairs = [];

View File

@ -11,8 +11,6 @@
'use strict';
import type Module from '../Module';
const NO_OPTIONS = {};
class ResolutionResponse<TModule: {hash(): string}, TOptions> {
@ -26,7 +24,7 @@ class ResolutionResponse<TModule: {hash(): string}, TOptions> {
// This is monkey-patched from Resolver.
getModuleId: ?() => number;
_mappings: {};
_mappings: {[hash: string]: Array<[string, TModule]>};
_finalized: boolean;
_mainModule: ?TModule;
@ -104,8 +102,8 @@ class ResolutionResponse<TModule: {hash(): string}, TOptions> {
}
setResolvedDependencyPairs(
module: Module,
pairs: mixed,
module: TModule,
pairs: Array<[string, TModule]>,
options: {ignoreFinalized?: boolean} = NO_OPTIONS,
) {
if (!options.ignoreFinalized) {
@ -121,7 +119,7 @@ class ResolutionResponse<TModule: {hash(): string}, TOptions> {
this.mocks = mocks;
}
getResolvedDependencyPairs(module: TModule) {
getResolvedDependencyPairs(module: TModule): $ReadOnlyArray<[string, TModule]> {
this._assertFinalized();
return this._mappings[module.hash()];
}

View File

@ -1,30 +1,35 @@
/**
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
module.exports = function MapWithDefaults(factory, iterable) {
// This can't be `MapWithDefaults extends Map`, b/c the way babel transforms
// super calls in constructors: Map.call(this, iterable) throws for native
// Map objects in node 4+.
// TODO(davidaurelio) switch to a transform that does not transform classes
// and super calls, and change this into a class
class MapWithDefaults<TK, TV> extends Map<TK, TV> {
_factory: TK => TV;
const map = iterable ? new Map(iterable) : new Map();
const {get} = map;
map.get = key => {
if (map.has(key)) {
return get.call(map, key);
constructor(factory: TK => TV, iterable?: Iterable<[TK, TV]>) {
super(iterable);
this._factory = factory;
}
get(key: TK): TV {
if (this.has(key)) {
/* $FlowFixMe: can never be `undefined` since we tested with `has`
* (except if `TV` includes `void` as subtype, ex. is nullable) */
return Map.prototype.get.call(this, key);
}
const value = factory(key);
map.set(key, value);
const value = this._factory(key);
this.set(key, value);
return value;
};
return map;
};
}
}
module.exports = MapWithDefaults;

View File

@ -0,0 +1,24 @@
/**
* 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.
*
* @format
*/
'use strict';
jest.disableAutomock();
const MapWithDefaults = require('../MapWithDefaults');
describe('MapWithDefaults', function() {
it('works', () => {
const map = new MapWithDefaults(() => ['bar']);
map.get('foo').push('baz');
expect(map.get('foo')).toEqual(['bar', 'baz']);
});
});

View File

@ -17,7 +17,7 @@ import type ResolutionResponse from '../DependencyGraph/ResolutionResponse';
function resolveModuleRequires<TModule: {hash(): string}, TOptions>(
resolutionResponse: ResolutionResponse<TModule, TOptions>,
module: TModule,
) {
): Array<TModule> {
const pairs = resolutionResponse.getResolvedDependencyPairs(module);
return pairs ? pairs.map(([, dependencyModule]) => dependencyModule) : [];
}