Clean-up Module methods/properties

Reviewed By: jeanlauliac

Differential Revision: D7894084

fbshipit-source-id: 1a4826d272da51f8beea44ba9b82fa685d254fbe
This commit is contained in:
Rafael Oleza 2018-05-11 15:05:24 -07:00 committed by Facebook Github Bot
parent 9a1bfe6388
commit e47fdf6f59
9 changed files with 31 additions and 327 deletions

View File

@ -137,22 +137,17 @@ class Bundler {
this._depGraphPromise = DependencyGraph.load({ this._depGraphPromise = DependencyGraph.load({
assetExts: opts.assetExts, assetExts: opts.assetExts,
assetRegistryPath: opts.assetRegistryPath,
blacklistRE: opts.blacklistRE, blacklistRE: opts.blacklistRE,
experimentalCaches: true,
extraNodeModules: opts.extraNodeModules, extraNodeModules: opts.extraNodeModules,
getPolyfills: opts.getPolyfills,
hasteImplModulePath: opts.hasteImplModulePath, hasteImplModulePath: opts.hasteImplModulePath,
maxWorkers: opts.maxWorkers, maxWorkers: opts.maxWorkers,
platforms: new Set(opts.platforms), platforms: new Set(opts.platforms),
polyfillModuleNames: opts.polyfillModuleNames,
projectRoots: opts.projectRoots, projectRoots: opts.projectRoots,
providesModuleNodeModules: providesModuleNodeModules:
opts.providesModuleNodeModules || defaults.providesModuleNodeModules, opts.providesModuleNodeModules || defaults.providesModuleNodeModules,
reporter: opts.reporter, reporter: opts.reporter,
resolveRequest: opts.resolveRequest, resolveRequest: opts.resolveRequest,
sourceExts: opts.sourceExts, sourceExts: opts.sourceExts,
transformCode: this.transformFile.bind(this),
watch: opts.watch, watch: opts.watch,
}); });

View File

@ -44,8 +44,8 @@ beforeEach(() => {
describe('traverseDependencies', function() { describe('traverseDependencies', function() {
let fs; let fs;
let Module;
let traverseDependencies; let traverseDependencies;
let transformFile;
let transformHelpers; let transformHelpers;
let defaults; let defaults;
let UnableToResolveError; let UnableToResolveError;
@ -97,7 +97,7 @@ describe('traverseDependencies', function() {
return await Promise.all( return await Promise.all(
[...dependencies].map(async path => { [...dependencies].map(async path => {
const dep = dgraph.getModuleForPath(path); const dep = dgraph.getModuleForPath(path);
const moduleDependencies = (await dep.read()).dependencies; const moduleDependencies = (await transformFile(path)).dependencies;
return { return {
path: dep.path, path: dep.path,
@ -115,7 +115,6 @@ describe('traverseDependencies', function() {
jest.mock('fs', () => new (require('metro-memory-fs'))()); jest.mock('fs', () => new (require('metro-memory-fs'))());
fs = require('fs'); fs = require('fs');
Module = require('../../node-haste/Module');
traverseDependencies = require('../traverseDependencies'); traverseDependencies = require('../traverseDependencies');
transformHelpers = require('../../lib/transformHelpers'); transformHelpers = require('../../lib/transformHelpers');
({ ({
@ -131,24 +130,23 @@ describe('traverseDependencies', function() {
platforms: new Set(['ios', 'android']), platforms: new Set(['ios', 'android']),
maxWorkers: 1, maxWorkers: 1,
resetCache: true, resetCache: true,
transformCode: (module, sourceCode, transformOptions) => {
return new Promise(resolve => {
// require call must stay inline, so the latest defined mock is used!
const code = require('fs').readFileSync(module.path, 'utf8');
const deps = {dependencies: []};
if (!module.path.endsWith('.json')) {
deps.dependencies = extractDependencies(code);
}
resolve({...deps, code});
});
},
getTransformCacheKey: () => 'abcdef', getTransformCacheKey: () => 'abcdef',
reporter: require('../../lib/reporting').nullReporter, reporter: require('../../lib/reporting').nullReporter,
sourceExts: ['js', 'json'], sourceExts: ['js', 'json'],
watch: true, watch: true,
}; };
transformFile = async filePath => {
// require call must stay inline, so the latest defined mock is used!
const code = require('fs').readFileSync(filePath, 'utf8');
const deps = {dependencies: []};
if (!filePath.endsWith('.json')) {
deps.dependencies = extractDependencies(code);
}
return {...deps, code};
};
}); });
describe('get sync dependencies (posix)', () => { describe('get sync dependencies (posix)', () => {
@ -3462,46 +3460,13 @@ describe('traverseDependencies', function() {
}); });
}); });
describe('Asset module dependencies', () => {
let DependencyGraph;
let processDgraph;
beforeEach(() => {
DependencyGraph = require('../../node-haste/DependencyGraph');
processDgraph = processDgraphFor.bind(null, DependencyGraph);
});
it.skip('allows setting dependencies for asset modules (broken)', async () => {
const assetDependencies = ['/root/apple.png', '/root/banana.png'];
setMockFileSystem({
root: {
'index.js': 'require("./a.png")',
'a.png': '',
'apple.png': '',
'banana.png': '',
},
});
const opts = {...defaults, assetDependencies, projectRoots: ['/root']};
await processDgraph(opts, async dgraph => {
const {dependencies} = await dgraph.getDependencies({
entryPath: '/root/index.js',
});
const [, assetModule] = dependencies;
const deps = await assetModule.getDependencies();
expect(deps).toBe(assetDependencies);
});
});
});
describe('Deterministic order of dependencies', () => { describe('Deterministic order of dependencies', () => {
let callDeferreds, dependencyGraph, moduleReadDeferreds; let callDeferreds, dependencyGraph, moduleReadDeferreds;
let moduleRead; let originalTransformFile;
let DependencyGraph; let DependencyGraph;
beforeEach(() => { beforeEach(() => {
moduleRead = Module.prototype.read; originalTransformFile = transformFile;
DependencyGraph = require('../../node-haste/DependencyGraph'); DependencyGraph = require('../../node-haste/DependencyGraph');
setMockFileSystem({ setMockFileSystem({
root: { root: {
@ -3533,13 +3498,13 @@ describe('traverseDependencies', function() {
moduleReadDeferreds = {}; moduleReadDeferreds = {};
callDeferreds = [defer(), defer()]; // [a.js, b.js] callDeferreds = [defer(), defer()]; // [a.js, b.js]
Module.prototype.read = jest.genMockFn().mockImplementation(function() { transformFile = jest.genMockFn().mockImplementation((path, ...args) => {
const returnValue = moduleRead.apply(this, arguments); const returnValue = originalTransformFile(path, ...args);
if (/\/[ab]\.js$/.test(this.path)) { if (/\/[ab]\.js$/.test(path)) {
let deferred = moduleReadDeferreds[this.path]; let deferred = moduleReadDeferreds[path];
if (!deferred) { if (!deferred) {
deferred = moduleReadDeferreds[this.path] = defer(returnValue); deferred = moduleReadDeferreds[path] = defer(returnValue);
const index = Number(this.path.endsWith('b.js')); // 0 or 1 const index = Number(path.endsWith('b.js')); // 0 or 1
callDeferreds[index].resolve(); callDeferreds[index].resolve();
} }
return deferred.promise; return deferred.promise;
@ -3551,7 +3516,7 @@ describe('traverseDependencies', function() {
afterEach(() => { afterEach(() => {
dependencyGraph.then(dgraph => dgraph.end()); dependencyGraph.then(dgraph => dgraph.end());
Module.prototype.read = moduleRead; transformFile = originalTransformFile;
}); });
it('produces a deterministic tree if the "a" module resolves first', () => { it('produces a deterministic tree if the "a" module resolves first', () => {

View File

@ -115,7 +115,7 @@ async function getResolveDependencyFn(
return (from: string, to: string) => { return (from: string, to: string) => {
return dependencyGraph.resolveDependency( return dependencyGraph.resolveDependency(
dependencyGraph.getModuleForPath(from, false), dependencyGraph.getModuleForPath(from),
to, to,
platform, platform,
).path; ).path;

View File

@ -19,7 +19,6 @@ const ModuleCache = require('./ModuleCache');
const ResolutionRequest = require('./DependencyGraph/ResolutionRequest'); const ResolutionRequest = require('./DependencyGraph/ResolutionRequest');
const fs = require('fs'); const fs = require('fs');
const parsePlatformFilePath = require('./lib/parsePlatformFilePath');
const path = require('path'); const path = require('path');
const toLocalPath = require('../node-haste/lib/toLocalPath'); const toLocalPath = require('../node-haste/lib/toLocalPath');
@ -29,32 +28,24 @@ const {
Logger: {createActionStartEntry, createActionEndEntry, log}, Logger: {createActionStartEntry, createActionEndEntry, log},
} = require('metro-core'); } = require('metro-core');
import type {WorkerOptions} from '../JSTransformer/worker';
import type {TransformResultDependency} from '../ModuleGraph/types.flow';
import type {Reporter} from '../lib/reporting'; import type {Reporter} from '../lib/reporting';
import type {ModuleMap} from './DependencyGraph/ModuleResolution'; import type {ModuleMap} from './DependencyGraph/ModuleResolution';
import type {TransformCode} from './Module';
import type Package from './Package'; import type Package from './Package';
import type {HasteFS} from './types'; import type {HasteFS} from './types';
import type {CustomResolver} from 'metro-resolver'; import type {CustomResolver} from 'metro-resolver';
type Options = {| type Options = {|
+assetExts: Array<string>, +assetExts: Array<string>,
+assetRegistryPath: string,
+blacklistRE?: RegExp, +blacklistRE?: RegExp,
+experimentalCaches: boolean,
+extraNodeModules: ?{}, +extraNodeModules: ?{},
+getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>,
+hasteImplModulePath?: string, +hasteImplModulePath?: string,
+maxWorkers: number, +maxWorkers: number,
+platforms: Set<string>, +platforms: Set<string>,
+polyfillModuleNames?: Array<string>,
+projectRoots: $ReadOnlyArray<string>, +projectRoots: $ReadOnlyArray<string>,
+providesModuleNodeModules: Array<string>, +providesModuleNodeModules: Array<string>,
+reporter: Reporter, +reporter: Reporter,
+resolveRequest: ?CustomResolver, +resolveRequest: ?CustomResolver,
+sourceExts: Array<string>, +sourceExts: Array<string>,
+transformCode: TransformCode,
+watch: boolean, +watch: boolean,
|}; |};
@ -195,30 +186,14 @@ class DependencyGraph extends EventEmitter {
const {_opts} = this; const {_opts} = this;
return new ModuleCache( return new ModuleCache(
{ {
assetDependencies: [_opts.assetRegistryPath],
getClosestPackage: this._getClosestPackage.bind(this), getClosestPackage: this._getClosestPackage.bind(this),
hasteImplModulePath: _opts.hasteImplModulePath, hasteImplModulePath: _opts.hasteImplModulePath,
roots: _opts.projectRoots, roots: _opts.projectRoots,
transformCode: _opts.transformCode,
}, },
_opts.platforms, _opts.platforms,
); );
} }
/**
* Returns a promise with the direct dependencies the module associated to
* the given entryPath has.
*/
async getShallowDependencies(
entryPath: string,
transformOptions: WorkerOptions,
): Promise<$ReadOnlyArray<TransformResultDependency>> {
const module = this._moduleCache.getModule(entryPath);
const result = await module.read(transformOptions);
return result.dependencies;
}
getSha1(filename: string): string { getSha1(filename: string): string {
const sha1 = this._hasteFS.getSha1(filename); const sha1 = this._hasteFS.getSha1(filename);
@ -237,11 +212,7 @@ class DependencyGraph extends EventEmitter {
this._haste.end(); this._haste.end();
} }
getModuleForPath(entryFile: string, isPolyfill: boolean): Module { getModuleForPath(entryFile: string): Module {
if (isPolyfill) {
return this._moduleCache.getPolyfillModule(entryFile);
}
if (this._helpers.isAssetFile(entryFile)) { if (this._helpers.isAssetFile(entryFile)) {
return this._moduleCache.getAssetModule(entryFile); return this._moduleCache.getAssetModule(entryFile);
} }
@ -269,16 +240,6 @@ class DependencyGraph extends EventEmitter {
return this._hasteFS.exists(filePath); return this._hasteFS.exists(filePath);
}; };
_getRequestPlatform(entryPath: string, platform: ?string): ?string {
if (platform == null) {
platform = parsePlatformFilePath(entryPath, this._opts.platforms)
.platform;
} else if (!this._opts.platforms.has(platform)) {
throw new Error('Unrecognized platform: ' + platform);
}
return platform;
}
getHasteName(filePath: string): string { getHasteName(filePath: string): string {
const hasteName = this._hasteFS.getModuleName(filePath); const hasteName = this._hasteFS.getModuleName(filePath);
@ -288,10 +249,6 @@ class DependencyGraph extends EventEmitter {
return toLocalPath(this._opts.projectRoots, filePath); return toLocalPath(this._opts.projectRoots, filePath);
} }
createPolyfill(options: {file: string}) {
return this._moduleCache.createPolyfill(options);
}
} }
module.exports = DependencyGraph; module.exports = DependencyGraph;

View File

@ -10,25 +10,15 @@
'use strict'; 'use strict';
const fs = require('fs');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
import type {TransformedCode, WorkerOptions} from '../JSTransformer/worker';
import type ModuleCache from './ModuleCache'; import type ModuleCache from './ModuleCache';
import type {LocalPath} from './lib/toLocalPath'; import type {LocalPath} from './lib/toLocalPath';
type ReadResult = {
...TransformedCode,
+source: string,
};
export type TransformCode = Function;
export type ConstructorArgs = { export type ConstructorArgs = {
file: string, file: string,
localPath: LocalPath, localPath: LocalPath,
moduleCache: ModuleCache, moduleCache: ModuleCache,
transformCode: TransformCode,
}; };
class Module { class Module {
@ -36,10 +26,9 @@ class Module {
path: string; path: string;
_moduleCache: ModuleCache; _moduleCache: ModuleCache;
_transformCode: TransformCode;
_sourceCode: ?string; _sourceCode: ?string;
constructor({file, localPath, moduleCache, transformCode}: ConstructorArgs) { constructor({file, localPath, moduleCache}: ConstructorArgs) {
if (!isAbsolutePath(file)) { if (!isAbsolutePath(file)) {
throw new Error('Expected file to be absolute path but got ' + file); throw new Error('Expected file to be absolute path but got ' + file);
} }
@ -48,41 +37,13 @@ class Module {
this.path = file; this.path = file;
this._moduleCache = moduleCache; this._moduleCache = moduleCache;
this._transformCode = transformCode;
} }
getPackage() { getPackage() {
return this._moduleCache.getPackageForModule(this); return this._moduleCache.getPackageForModule(this);
} }
invalidate() { invalidate() {}
this._sourceCode = null;
}
_readSourceCode(): string {
if (this._sourceCode == null) {
this._sourceCode = fs.readFileSync(this.path, 'utf8');
}
return this._sourceCode;
}
async read(transformOptions: WorkerOptions): Promise<ReadResult> {
const result: TransformedCode = await this._transformCode(
this,
transformOptions,
);
const module = this;
return {
dependencies: result.dependencies,
output: result.output,
get source() {
return module._readSourceCode();
},
};
}
isAsset() { isAsset() {
return false; return false;

View File

@ -13,48 +13,34 @@
const AssetModule = require('./AssetModule'); const AssetModule = require('./AssetModule');
const Module = require('./Module'); const Module = require('./Module');
const Package = require('./Package'); const Package = require('./Package');
const Polyfill = require('./Polyfill');
const toLocalPath = require('./lib/toLocalPath'); const toLocalPath = require('./lib/toLocalPath');
import type {TransformCode} from './Module';
type GetClosestPackageFn = (filePath: string) => ?string; type GetClosestPackageFn = (filePath: string) => ?string;
type Options = {| type Options = {|
assetDependencies: Array<string>,
hasteImplModulePath?: string, hasteImplModulePath?: string,
getClosestPackage: GetClosestPackageFn, getClosestPackage: GetClosestPackageFn,
roots: $ReadOnlyArray<string>, roots: $ReadOnlyArray<string>,
transformCode: TransformCode,
|}; |};
class ModuleCache { class ModuleCache {
_assetDependencies: Array<string>;
_getClosestPackage: GetClosestPackageFn; _getClosestPackage: GetClosestPackageFn;
_moduleCache: {[filePath: string]: Module, __proto__: null}; _moduleCache: {[filePath: string]: Module, __proto__: null};
_packageCache: {[filePath: string]: Package, __proto__: null}; _packageCache: {[filePath: string]: Package, __proto__: null};
_packageModuleMap: WeakMap<Module, string>; _packageModuleMap: WeakMap<Module, string>;
_platforms: Set<string>; _platforms: Set<string>;
_transformCode: TransformCode;
_roots: $ReadOnlyArray<string>; _roots: $ReadOnlyArray<string>;
_opts: Options; _opts: Options;
constructor(options: Options, platforms: Set<string>) { constructor(options: Options, platforms: Set<string>) {
const { const {getClosestPackage, roots} = options;
assetDependencies,
getClosestPackage,
roots,
transformCode,
} = options;
this._opts = options; this._opts = options;
this._assetDependencies = assetDependencies;
this._getClosestPackage = getClosestPackage; this._getClosestPackage = getClosestPackage;
this._moduleCache = Object.create(null); this._moduleCache = Object.create(null);
this._packageCache = Object.create(null); this._packageCache = Object.create(null);
this._packageModuleMap = new WeakMap(); this._packageModuleMap = new WeakMap();
this._platforms = platforms; this._platforms = platforms;
this._transformCode = transformCode;
this._roots = roots; this._roots = roots;
} }
@ -65,7 +51,6 @@ class ModuleCache {
localPath: toLocalPath(this._roots, filePath), localPath: toLocalPath(this._roots, filePath),
moduleCache: this, moduleCache: this,
options: this._getModuleOptions(), options: this._getModuleOptions(),
transformCode: this._transformCode,
}); });
} }
return this._moduleCache[filePath]; return this._moduleCache[filePath];
@ -86,20 +71,11 @@ class ModuleCache {
localPath: toLocalPath(this._roots, filePath), localPath: toLocalPath(this._roots, filePath),
moduleCache: this, moduleCache: this,
options: this._getModuleOptions(), options: this._getModuleOptions(),
transformCode: this._transformCode,
}); });
} }
return this._moduleCache[filePath]; return this._moduleCache[filePath];
} }
getPolyfillModule(filePath: string) {
if (!this._moduleCache[filePath]) {
this._moduleCache[filePath] = this.createPolyfill({file: filePath});
}
return this._moduleCache[filePath];
}
getPackage(filePath: string): Package { getPackage(filePath: string): Package {
if (!this._packageCache[filePath]) { if (!this._packageCache[filePath]) {
this._packageCache[filePath] = new Package({ this._packageCache[filePath] = new Package({
@ -128,17 +104,6 @@ class ModuleCache {
return this.getPackage(packagePath); return this.getPackage(packagePath);
} }
createPolyfill({file}: {file: string}) {
/* $FlowFixMe: there are missing arguments. */
return new Polyfill({
file,
localPath: toLocalPath(this._roots, file),
moduleCache: this,
options: this._getModuleOptions(),
transformCode: this._transformCode,
});
}
processFileChange(type: string, filePath: string) { processFileChange(type: string, filePath: string) {
if (this._moduleCache[filePath]) { if (this._moduleCache[filePath]) {
this._moduleCache[filePath].invalidate(); this._moduleCache[filePath].invalidate();

View File

@ -1,53 +0,0 @@
/**
* Copyright (c) 2013-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 Module = require('./Module');
import type {ConstructorArgs} from './Module';
class Polyfill extends Module {
_id: string;
_dependencies: Array<string>;
constructor(
options: ConstructorArgs & {
id: string,
dependencies: Array<string>,
},
) {
super(options);
this._id = options.id;
this._dependencies = options.dependencies;
}
isHaste() {
return false;
}
getName() {
return this._id;
}
getPackage() {
return null;
}
async getDependencies() {
return this._dependencies;
}
isPolyfill() {
return true;
}
}
module.exports = Polyfill;

View File

@ -10,43 +10,10 @@
'use strict'; 'use strict';
jest.mock('fs', () => new (require('metro-memory-fs'))());
const AssetModule = require('../AssetModule'); const AssetModule = require('../AssetModule');
const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers');
const ModuleCache = require('../ModuleCache');
const fs = require('fs');
describe('AssetModule:', () => { describe('AssetModule:', () => {
const defaults = {file: '/arbitrary.png'};
beforeEach(() => {
fs.reset();
fs.mkdirSync('/root');
fs.writeFileSync('/root/image.png', 'png data');
});
it('is an asset', () => { it('is an asset', () => {
expect(new AssetModule(defaults).isAsset()).toBe(true); expect(new AssetModule({file: '/arbitrary.png'}).isAsset()).toBe(true);
});
it('returns an empty source code for an asset', async () => {
const module = new AssetModule({
depGraphHelpers: new DependencyGraphHelpers({
providesModuleNodeModules: [],
assetExts: ['png'],
}),
file: '/root/image.png',
getTransformCacheKey: () => 'foo',
localPath: 'image.png',
moduleCache: new ModuleCache({}),
transformCode: () => {
return Promise.resolve({output: [{code: 'module.exports = "asset";'}]});
},
});
const data = await module.read();
expect(data.output[0].code).toBe('module.exports = "asset";');
}); });
}); });

View File

@ -10,37 +10,23 @@
'use strict'; 'use strict';
jest.mock('fs').mock('../ModuleCache');
const fs = require('fs');
const ModuleCache = require('../ModuleCache');
const Module = require('../Module'); const Module = require('../Module');
const ModuleCache = require('../ModuleCache');
describe('Module', () => { describe('Module', () => {
let transformCode;
let moduleCache; let moduleCache;
let module; let module;
beforeEach(() => { beforeEach(() => {
transformCode = jest.fn().mockReturnValue({ moduleCache = new ModuleCache({});
dependencies: ['stdlib.h', 'conio.h'],
output: [{code: 'int main(void) { return -1; }', map: []}],
});
moduleCache = new ModuleCache();
module = new Module({ module = new Module({
file: '/root/to/file.js', file: '/root/to/file.js',
localPath: 'file.js', localPath: 'file.js',
moduleCache, moduleCache,
transformCode,
}); });
}); });
afterEach(() => {
fs.readFileSync.mockReset();
});
it('Returns the correct values for many properties and methods', () => { it('Returns the correct values for many properties and methods', () => {
expect(module.localPath).toBe('file.js'); expect(module.localPath).toBe('file.js');
expect(module.path).toBe('/root/to/file.js'); expect(module.path).toBe('/root/to/file.js');
@ -48,43 +34,4 @@ describe('Module', () => {
expect(module.isAsset()).toBe(false); expect(module.isAsset()).toBe(false);
expect(module.isPolyfill()).toBe(false); expect(module.isPolyfill()).toBe(false);
}); });
it('returns the result from the transform code straight away', async () => {
fs.readFileSync.mockReturnValue('original code');
expect(await module.read({})).toEqual({
dependencies: ['stdlib.h', 'conio.h'],
output: [
{
code: 'int main(void) { return -1; }',
map: [],
},
],
source: 'original code',
});
});
it('checks that code is only read once until invalidated', async () => {
fs.readFileSync.mockReturnValue('original code');
// Read once. No access to "source", so no reads.
await module.read({});
expect(fs.readFileSync).toHaveBeenCalledTimes(0);
// Read again, accessing "source".
expect((await module.read({})).source).toEqual('original code');
expect(fs.readFileSync).toHaveBeenCalledTimes(1);
// Read again, accessing "source" again. Still 1 because code was cached.
expect((await module.read({})).source).toEqual('original code');
expect(fs.readFileSync).toHaveBeenCalledTimes(1);
// Invalidate.
module.invalidate();
// Read again, this time it will read it.
expect((await module.read({})).source).toEqual('original code');
expect(fs.readFileSync).toHaveBeenCalledTimes(2);
});
}); });