diff --git a/packages/metro/src/Bundler/index.js b/packages/metro/src/Bundler/index.js index 06eb14da..f6942ef4 100644 --- a/packages/metro/src/Bundler/index.js +++ b/packages/metro/src/Bundler/index.js @@ -145,6 +145,8 @@ class Bundler { assetExts: opts.assetExts, assetRegistryPath: opts.assetRegistryPath, blacklistRE: opts.blacklistRE, + // TODO: T26134860 Only use experimental caches if stores are provided. + experimentalCaches: !!opts.cacheStores.length, extraNodeModules: opts.extraNodeModules, getPolyfills: opts.getPolyfills, getTransformCacheKey: getTransformCacheKeyFn({ diff --git a/packages/metro/src/node-haste/DependencyGraph.js b/packages/metro/src/node-haste/DependencyGraph.js index 55a527ec..4b85e065 100644 --- a/packages/metro/src/node-haste/DependencyGraph.js +++ b/packages/metro/src/node-haste/DependencyGraph.js @@ -47,6 +47,7 @@ type Options = {| +assetExts: Array, +assetRegistryPath: string, +blacklistRE?: RegExp, + +experimentalCaches: boolean, +extraNodeModules: ?{}, +getPolyfills: ({platform: ?string}) => $ReadOnlyArray, +getTransformCacheKey: GetTransformCacheKey, @@ -202,6 +203,7 @@ class DependencyGraph extends EventEmitter { { assetDependencies: [_opts.assetRegistryPath], depGraphHelpers: this._helpers, + experimentalCaches: _opts.experimentalCaches, getClosestPackage: this._getClosestPackage.bind(this), getTransformCacheKey: _opts.getTransformCacheKey, globalTransformCache: _opts.globalTransformCache, diff --git a/packages/metro/src/node-haste/Module.js b/packages/metro/src/node-haste/Module.js index d9ee7606..a47e0bee 100644 --- a/packages/metro/src/node-haste/Module.js +++ b/packages/metro/src/node-haste/Module.js @@ -16,8 +16,7 @@ const fs = require('fs'); const invariant = require('fbjs/lib/invariant'); const isAbsolutePath = require('absolute-path'); const jsonStableStringify = require('json-stable-stringify'); - -const {join: joinPath, relative: relativePath} = require('path'); +const path = require('path'); import type { TransformedCode, @@ -70,12 +69,13 @@ export type Options = { export type ConstructorArgs = { depGraphHelpers: DependencyGraphHelpers, + experimentalCaches: boolean, file: string, getTransformCacheKey: GetTransformCacheKey, localPath: LocalPath, moduleCache: ModuleCache, options: Options, - transformCode: ?TransformCode, + transformCode: TransformCode, }; type DocBlock = {+[key: string]: string}; @@ -85,8 +85,10 @@ class Module { path: string; type: string; + _experimentalCaches: boolean; + _moduleCache: ModuleCache; - _transformCode: ?TransformCode; + _transformCode: TransformCode; _getTransformCacheKey: GetTransformCacheKey; _depGraphHelpers: DependencyGraphHelpers; _options: Options; @@ -100,9 +102,10 @@ class Module { constructor({ depGraphHelpers, - localPath, + experimentalCaches, file, getTransformCacheKey, + localPath, moduleCache, options, transformCode, @@ -115,6 +118,8 @@ class Module { this.path = file; this.type = 'Module'; + this._experimentalCaches = experimentalCaches; + this._moduleCache = moduleCache; this._transformCode = transformCode; this._getTransformCacheKey = getTransformCacheKey; @@ -138,6 +143,12 @@ class Module { } getName(): string { + // TODO: T26134860 Used for debugging purposes only; disabled with the new + // caches. + if (this._experimentalCaches) { + return path.basename(this.path); + } + if (this.isHaste()) { const name = this._getHasteName(); if (name != null) { @@ -157,10 +168,9 @@ class Module { return this.path; } - return joinPath(packageName, relativePath(p.root, this.path)).replace( - /\\/g, - '/', - ); + return path + .join(packageName, path.relative(p.root, this.path)) + .replace(/\\/g, '/'); } getPackage() { @@ -177,9 +187,16 @@ class Module { * code. */ invalidate() { + this._sourceCode = null; + + // TODO: T26134860 Caches present in Module are not used with experimental + // caches, except for the one related to source code. + if (this._experimentalCaches) { + return; + } + this._readPromises.clear(); this._readResultsByOptionsKey.clear(); - this._sourceCode = null; this._docBlock = null; this._hasteNameCache = null; } @@ -295,15 +312,24 @@ class Module { * Shorthand for reading both from cache or from fresh for all call sites that * are asynchronous by default. */ - read(transformOptions: WorkerOptions): Promise { - return Promise.resolve().then(() => { - const cached = this.readCached(transformOptions); + async read(transformOptions: WorkerOptions): Promise { + // TODO: T26134860 Cache layer lives inside the transformer now; just call + // the transform method. + if (this._experimentalCaches) { + const sourceCode = this._readSourceCode(); - if (cached != null) { - return cached; - } - return this.readFresh(transformOptions); - }); + return { + ...(await this._transformCode(this, sourceCode, transformOptions)), + sourceCode, + }; + } + + const cached = this.readCached(transformOptions); + + if (cached != null) { + return cached; + } + return this.readFresh(transformOptions); } /** diff --git a/packages/metro/src/node-haste/ModuleCache.js b/packages/metro/src/node-haste/ModuleCache.js index 4175a4a8..cface0c3 100644 --- a/packages/metro/src/node-haste/ModuleCache.js +++ b/packages/metro/src/node-haste/ModuleCache.js @@ -31,6 +31,7 @@ type GetClosestPackageFn = (filePath: string) => ?string; type Options = {| assetDependencies: Array, depGraphHelpers: DependencyGraphHelpers, + experimentalCaches: boolean, hasteImplModulePath?: string, getClosestPackage: GetClosestPackageFn, getTransformCacheKey: GetTransformCacheKey, @@ -45,6 +46,7 @@ type Options = {| class ModuleCache { _assetDependencies: Array; _depGraphHelpers: DependencyGraphHelpers; + _experimentalCaches: boolean; _getClosestPackage: GetClosestPackageFn; _getTransformCacheKey: GetTransformCacheKey; _globalTransformCache: ?GlobalTransformCache; @@ -61,6 +63,7 @@ class ModuleCache { const { assetDependencies, depGraphHelpers, + experimentalCaches, getClosestPackage, getTransformCacheKey, globalTransformCache, @@ -70,6 +73,7 @@ class ModuleCache { } = options; this._opts = options; this._assetDependencies = assetDependencies; + this._experimentalCaches = experimentalCaches; this._getClosestPackage = getClosestPackage; this._getTransformCacheKey = getTransformCacheKey; this._globalTransformCache = globalTransformCache; @@ -87,6 +91,7 @@ class ModuleCache { if (!this._moduleCache[filePath]) { this._moduleCache[filePath] = new Module({ depGraphHelpers: this._depGraphHelpers, + experimentalCaches: this._experimentalCaches, file: filePath, getTransformCacheKey: this._getTransformCacheKey, localPath: toLocalPath(this._roots, filePath), @@ -110,6 +115,7 @@ class ModuleCache { */ this._moduleCache[filePath] = new AssetModule({ depGraphHelpers: this._depGraphHelpers, + experimentalCaches: this._experimentalCaches, file: filePath, getTransformCacheKey: this._getTransformCacheKey, globalTransformCache: this._globalTransformCache, diff --git a/packages/metro/src/node-haste/__tests__/Module-test.js b/packages/metro/src/node-haste/__tests__/Module-test.js index 048513a5..67d6dbd3 100644 --- a/packages/metro/src/node-haste/__tests__/Module-test.js +++ b/packages/metro/src/node-haste/__tests__/Module-test.js @@ -85,6 +85,46 @@ describe('Module', () => { transformCache.mock.reset(); }); + describe('Experimental caches', () => { + it('Calls into the transformer directly when having experimental caches on', async () => { + const transformCode = jest.fn().mockReturnValue({ + code: 'code', + dependencies: ['dep1', 'dep2'], + map: [], + }); + + const module = new Module({ + cache, + experimentalCaches: true, + depGraphHelpers: new DependencyGraphHelpers(), + file: fileName, + getTransformCacheKey: () => transformCacheKey, + localPath: fileName, + moduleCache: new ModuleCache({cache}), + options: {transformCache}, + transformCode, + }); + + mockIndexFile('originalCode'); + jest.spyOn(fs, 'readFileSync'); + + // Read the first time, transform code is called. + const res1 = await module.read({foo: 3}); + expect(res1.code).toBe('code'); + expect(res1.dependencies).toEqual(['dep1', 'dep2']); + expect(transformCode).toHaveBeenCalledTimes(1); + + // Read a second time, transformCode is called again! + const res2 = await module.read({foo: 3}); + expect(res2.code).toBe('code'); + expect(res2.dependencies).toEqual(['dep1', 'dep2']); + expect(transformCode).toHaveBeenCalledTimes(2); + + // Code was only read once, though. + expect(fs.readFileSync).toHaveBeenCalledTimes(1); + }); + }); + describe('Module ID', () => { const moduleId = 'arbitraryModule'; const source = `/**