packager: sync Module#read()

Reviewed By: davidaurelio

Differential Revision: D4802783

fbshipit-source-id: c6309bcae6ad48bea2350de04353f694be6eea2f
This commit is contained in:
Jean Lauliac 2017-03-31 10:09:31 -07:00 committed by Facebook Github Bot
parent b9bfe3b85d
commit 87c7e781a2
3 changed files with 97 additions and 42 deletions

View File

@ -37,14 +37,14 @@ class AssetModule extends Module {
return Promise.resolve(false); return Promise.resolve(false);
} }
getDependencies() { readCached(): ReadResult {
return Promise.resolve(this._dependencies);
}
read(): Promise<ReadResult> {
/** $FlowFixMe: improper OOP design. AssetModule, being different from a /** $FlowFixMe: improper OOP design. AssetModule, being different from a
* normal Module, shouldn't inherit it in the first place. */ * normal Module, shouldn't inherit it in the first place. */
return Promise.resolve({}); return {dependencies: this._dependencies};
}
readFresh(): Promise<ReadResult> {
return Promise.resolve(this.readCached());
} }
getName() { getName() {

View File

@ -141,6 +141,11 @@ class ResolutionRequest {
return cacheResult(this._resolveNodeDependency(fromModule, toModuleName)); return cacheResult(this._resolveNodeDependency(fromModule, toModuleName));
} }
resolveModuleDependencies(module: Module, dependencyNames: Array<string>): [Array<string>, Array<Module>] {
const dependencies = dependencyNames.map(name => this.resolveDependency(module, name));
return [dependencyNames, dependencies];
}
getOrderedDependencies({ getOrderedDependencies({
response, response,
transformOptions, transformOptions,
@ -158,11 +163,13 @@ class ResolutionRequest {
let totalModules = 1; let totalModules = 1;
let finishedModules = 0; let finishedModules = 0;
const resolveDependencies = module => const resolveDependencies = module => Promise.resolve().then(() => {
module.getDependencies(transformOptions) const result = module.readCached(transformOptions);
.then(dependencyNames => { if (result != null) {
const dependencies = dependencyNames.map(name => this.resolveDependency(module, name)); return this.resolveModuleDependencies(module, result.dependencies);
return [dependencyNames, dependencies]; }
return module.read(transformOptions)
.then(({dependencies}) => this.resolveModuleDependencies(module, dependencies));
}); });
const collectedDependencies = new MapWithDefaults(module => collect(module)); const collectedDependencies = new MapWithDefaults(module => collect(module));

View File

@ -95,6 +95,8 @@ class Module {
_sourceCode: ?string; _sourceCode: ?string;
_readPromises: Map<string, Promise<ReadResult>>; _readPromises: Map<string, Promise<ReadResult>>;
_readResultsByOptionsKey: Map<string, ?ReadResult>;
constructor({ constructor({
cache, cache,
depGraphHelpers, depGraphHelpers,
@ -123,6 +125,7 @@ class Module {
this._globalCache = globalTransformCache; this._globalCache = globalTransformCache;
this._readPromises = new Map(); this._readPromises = new Map();
this._readResultsByOptionsKey = new Map();
} }
isHaste(): Promise<boolean> { isHaste(): Promise<boolean> {
@ -186,6 +189,7 @@ class Module {
invalidate() { invalidate() {
this._cache.invalidate(this.path); this._cache.invalidate(this.path);
this._readPromises.clear(); this._readPromises.clear();
this._readResultsByOptionsKey.clear();
this._sourceCode = null; this._sourceCode = null;
this._docBlock = null; this._docBlock = null;
this._hasteNameCache = null; this._hasteNameCache = null;
@ -250,10 +254,9 @@ class Module {
*/ */
_finalizeReadResult( _finalizeReadResult(
source: string, source: string,
id: ?string,
extern: boolean,
result: TransformedCode, result: TransformedCode,
): ReadResult { ): ReadResult {
const id = this._getHasteName();
if (this._options.cacheTransformResults === false) { if (this._options.cacheTransformResults === false) {
const {dependencies} = result; const {dependencies} = result;
/* $FlowFixMe: this code path is dead, remove. */ /* $FlowFixMe: this code path is dead, remove. */
@ -334,41 +337,61 @@ class Module {
} }
/** /**
* Read everything about a module: source code, transformed code, * Shorthand for reading both from cache or from fresh for all call sites that
* dependencies, etc. The overall process is to read the cache first, and if * are asynchronous by default.
* it's a miss, we let the worker write to the cache and read it again.
*/ */
read(transformOptions: TransformOptions): Promise<ReadResult> { read(transformOptions: TransformOptions): Promise<ReadResult> {
return Promise.resolve().then(() => {
const cached = this.readCached(transformOptions);
if (cached != null) {
return cached;
}
return this.readFresh(transformOptions);
});
}
/**
* Same as `readFresh`, but reads from the cache instead of transforming
* the file from source. This has the benefit of being synchronous. As a
* result it is possible to read many cached Module in a row, synchronously.
*/
readCached(transformOptions: TransformOptions): ?ReadResult {
const key = stableObjectHash(transformOptions || {});
if (this._readResultsByOptionsKey.has(key)) {
return this._readResultsByOptionsKey.get(key);
}
const result = this._readFromTransformCache(transformOptions);
this._readResultsByOptionsKey.set(key, result);
return result;
}
/**
* Read again from the TransformCache, on disk. `readCached` should be favored
* so it's faster in case the results are already in memory.
*/
_readFromTransformCache(transformOptions: TransformOptions): ?ReadResult {
const cacheProps = this._getCacheProps(transformOptions);
const cachedResult = TransformCache.readSync(cacheProps);
if (cachedResult) {
return this._finalizeReadResult(cacheProps.sourceCode, cachedResult);
}
return null;
}
/**
* Gathers relevant data about a module: source code, transformed code,
* dependencies, etc. This function reads and transforms the source from
* scratch. We don't repeat the same work as `readCached` because we assume
* call sites have called it already.
*/
readFresh(transformOptions: TransformOptions): Promise<ReadResult> {
const key = stableObjectHash(transformOptions || {}); const key = stableObjectHash(transformOptions || {});
const promise = this._readPromises.get(key); const promise = this._readPromises.get(key);
if (promise != null) { if (promise != null) {
return promise; return promise;
} }
const freshPromise = Promise.resolve().then(() => { const freshPromise = Promise.resolve().then(() => {
const sourceCode = this._readSourceCode(); const cacheProps = this._getCacheProps(transformOptions);
const moduleDocBlock = this._readDocBlock();
const id = this._getHasteName();
// Ignore requires in JSON files or generated code. An example of this
// is prebuilt files like the SourceMap library.
const extern = this.isJSON() || 'extern' in moduleDocBlock;
if (extern) {
transformOptions = {...transformOptions, extern};
}
const getTransformCacheKey = this._getTransformCacheKey;
const cacheProps = {
filePath: this.path,
sourceCode,
getTransformCacheKey,
transformOptions,
cacheOptions: {
resetCache: this._options.resetCache,
reporter: this._reporter,
},
};
const cachedResult = TransformCache.readSync(cacheProps);
if (cachedResult) {
return Promise.resolve(this._finalizeReadResult(sourceCode, id, extern, cachedResult));
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._getAndCacheTransformedCode( this._getAndCacheTransformedCode(
cacheProps, cacheProps,
@ -378,15 +401,40 @@ class Module {
return; return;
} }
invariant(freshResult != null, 'inconsistent state'); invariant(freshResult != null, 'inconsistent state');
resolve(this._finalizeReadResult(sourceCode, id, extern, freshResult)); resolve(this._finalizeReadResult(cacheProps.sourceCode, freshResult));
}, },
); );
}).then(result => {
this._readResultsByOptionsKey.set(key, result);
return result;
}); });
}); });
this._readPromises.set(key, freshPromise); this._readPromises.set(key, freshPromise);
return freshPromise; return freshPromise;
} }
_getCacheProps(transformOptions: TransformOptions) {
const sourceCode = this._readSourceCode();
const moduleDocBlock = this._readDocBlock();
const getTransformCacheKey = this._getTransformCacheKey;
// Ignore requires in JSON files or generated code. An example of this
// is prebuilt files like the SourceMap library.
const extern = this.isJSON() || 'extern' in moduleDocBlock;
if (extern) {
transformOptions = {...transformOptions, extern};
}
return {
filePath: this.path,
sourceCode,
getTransformCacheKey,
transformOptions,
cacheOptions: {
resetCache: this._options.resetCache,
reporter: this._reporter,
},
};
}
hash() { hash() {
return `Module : ${this.path}`; return `Module : ${this.path}`;
} }