diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index 1192159b..023fca50 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -42,6 +42,7 @@ class DependencyGraph { extensions, mocksPattern, extractRequires, + transformCode, shouldThrowOnUnresolvedErrors = () => true, }) { this._opts = { @@ -54,13 +55,13 @@ class DependencyGraph { providesModuleNodeModules, platforms: platforms || [], preferNativePlatform: preferNativePlatform || false, - cache, extensions: extensions || ['js', 'json'], mocksPattern, extractRequires, shouldThrowOnUnresolvedErrors, + transformCode }; - this._cache = this._opts.cache; + this._cache = cache; this._helpers = new DependencyGraphHelpers(this._opts); this.load().catch((err) => { // This only happens at initialization. Live errors are easier to recover from. @@ -98,12 +99,13 @@ class DependencyGraph { this._fastfs.on('change', this._processFileChange.bind(this)); - this._moduleCache = new ModuleCache( - this._fastfs, - this._cache, - this._opts.extractRequires, - this._helpers - ); + this._moduleCache = new ModuleCache({ + fastfs: this._fastfs, + cache: this._cache, + extractRequires: this._opts.extractRequires, + transformCode: this._opts.transformCode, + depGraphHelpers: this._helpers, + }); this._hasteMap = new HasteMap({ fastfs: this._fastfs, diff --git a/react-packager/src/DependencyResolver/Module.js b/react-packager/src/DependencyResolver/Module.js index 09fbe793..df6ed710 100644 --- a/react-packager/src/DependencyResolver/Module.js +++ b/react-packager/src/DependencyResolver/Module.js @@ -15,7 +15,15 @@ const extractRequires = require('./lib/extractRequires'); class Module { - constructor({ file, fastfs, moduleCache, cache, extractor, depGraphHelpers }) { + constructor({ + file, + fastfs, + moduleCache, + cache, + extractor = extractRequires, + transformCode, + depGraphHelpers, + }) { if (!isAbsolutePath(file)) { throw new Error('Expected file to be absolute path but got ' + file); } @@ -27,6 +35,7 @@ class Module { this._moduleCache = moduleCache; this._cache = cache; this._extractor = extractor; + this._transformCode = transformCode; this._depGraphHelpers = depGraphHelpers; } @@ -38,6 +47,10 @@ class Module { ); } + getCode() { + return this.read().then(({code}) => code); + } + getName() { return this._cache.get( this.path, @@ -114,13 +127,22 @@ class Module { if (this.isJSON() || 'extern' in moduleDocBlock) { data.dependencies = []; data.asyncDependencies = []; + data.code = content; + return data; } else { - var dependencies = (this._extractor || extractRequires)(content).deps; - data.dependencies = dependencies.sync; - data.asyncDependencies = dependencies.async; - } + const transformCode = this._transformCode; + const codePromise = transformCode + ? transformCode(this, content) + : Promise.resolve({code: content}); - return data; + return codePromise.then(({code, dependencies, asyncDependencies}) => { + const {deps} = this._extractor(code); + data.dependencies = dependencies || deps.sync; + data.asyncDependencies = asyncDependencies || deps.async; + data.code = code; + return data; + }); + } }); } diff --git a/react-packager/src/DependencyResolver/ModuleCache.js b/react-packager/src/DependencyResolver/ModuleCache.js index a4836d0a..fac1820b 100644 --- a/react-packager/src/DependencyResolver/ModuleCache.js +++ b/react-packager/src/DependencyResolver/ModuleCache.js @@ -7,13 +7,21 @@ const path = require('path'); class ModuleCache { - constructor(fastfs, cache, extractRequires, depGraphHelpers) { + constructor({ + fastfs, + cache, + extractRequires, + transformCode, + depGraphHelpers, + }) { this._moduleCache = Object.create(null); this._packageCache = Object.create(null); this._fastfs = fastfs; this._cache = cache; this._extractRequires = extractRequires; + this._transformCode = transformCode; this._depGraphHelpers = depGraphHelpers; + fastfs.on('change', this._processFileChange.bind(this)); } @@ -26,6 +34,7 @@ class ModuleCache { moduleCache: this, cache: this._cache, extractor: this._extractRequires, + transformCode: this._transformCode, depGraphHelpers: this._depGraphHelpers, }); } diff --git a/react-packager/src/DependencyResolver/__tests__/Module-test.js b/react-packager/src/DependencyResolver/__tests__/Module-test.js index f0c9cbd2..947fa0b1 100644 --- a/react-packager/src/DependencyResolver/__tests__/Module-test.js +++ b/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -26,74 +26,74 @@ const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelper const Promise = require('promise'); const fs = require('graceful-fs'); +function mockIndexFile(indexJs) { + fs.__setMockFilesystem({'root': {'index.js': indexJs}}); +} + describe('Module', () => { const fileWatcher = { on: () => this, isWatchman: () => Promise.resolve(false), }; + const fileName = '/root/index.js'; - const Cache = jest.genMockFn(); - Cache.prototype.get = jest.genMockFn().mockImplementation( - (filepath, field, cb) => cb(filepath) - ); - Cache.prototype.invalidate = jest.genMockFn(); - Cache.prototype.end = jest.genMockFn(); + let cache, fastfs; + const createCache = () => ({ + get: jest.genMockFn().mockImplementation( + (filepath, field, cb) => cb(filepath) + ), + invalidate: jest.genMockFn(), + end: jest.genMockFn(), + }); + + const createModule = (options) => + new Module({ + cache, + fastfs, + file: fileName, + depGraphHelpers: new DependencyGraphHelpers(), + moduleCache: new ModuleCache({fastfs, cache}), + ...options, + }); + + beforeEach(function(done) { + cache = createCache(); + fastfs = new Fastfs( + 'test', + ['/root'], + fileWatcher, + {crawling: Promise.resolve([fileName]), ignore: []}, + ); + + fastfs.build().then(done); + }); describe('Async Dependencies', () => { function expectAsyncDependenciesToEqual(expected) { - const fastfs = new Fastfs( - 'test', - ['/root'], - fileWatcher, - {crawling: Promise.resolve(['/root/index.js']), ignore: []}, + const module = createModule(); + return module.getAsyncDependencies().then(actual => + expect(actual).toEqual(expected) ); - const cache = new Cache(); - - return fastfs.build().then(() => { - const module = new Module({ - file: '/root/index.js', - fastfs, - moduleCache: new ModuleCache(fastfs, cache), - cache: cache, - depGraphHelpers: new DependencyGraphHelpers(), - }); - - return module.getAsyncDependencies().then(actual => - expect(actual).toEqual(expected) - ); - }); } pit('should recognize single dependency', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'System.' + 'import("dep1")', - }, - }); + mockIndexFile('System.' + 'import("dep1")'); return expectAsyncDependenciesToEqual([['dep1']]); }); pit('should parse single quoted dependencies', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'System.' + 'import(\'dep1\')', - }, - }); + mockIndexFile('System.' + 'import(\'dep1\')'); return expectAsyncDependenciesToEqual([['dep1']]); }); pit('should parse multiple async dependencies on the same module', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - 'System.' + 'import("dep1")', - 'System.' + 'import("dep2")', - ].join('\n'), - }, - }); + mockIndexFile([ + 'System.' + 'import("dep1")', + 'System.' + 'import("dep2")', + ].join('\n')); return expectAsyncDependenciesToEqual([ ['dep1'], @@ -102,53 +102,125 @@ describe('Module', () => { }); pit('parse fine new lines', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'System.' + 'import(\n"dep1"\n)', - }, - }); + mockIndexFile('System.' + 'import(\n"dep1"\n)'); return expectAsyncDependenciesToEqual([['dep1']]); }); }); + describe('Code', () => { + const fileContents = 'arbitrary(code)'; + beforeEach(function() { + mockIndexFile(fileContents); + }); + + pit('exposes file contents as `code` property on the data exposed by `read()`', () => + createModule().read().then(({code}) => + expect(code).toBe(fileContents)) + ); + + pit('exposes file contes via the `getCode()` method', () => + createModule().getCode().then(code => + expect(code).toBe(fileContents)) + ); + + pit('does not save the code in the cache', () => + createModule().getCode().then(() => + expect(cache.get).not.toBeCalled() + ) + ); + }); + describe('Extrators', () => { - function createModuleWithExtractor(extractor) { - const fastfs = new Fastfs( - 'test', - ['/root'], - fileWatcher, - {crawling: Promise.resolve(['/root/index.js']), ignore: []}, - ); - const cache = new Cache(); - - return fastfs.build().then(() => { - return new Module({ - file: '/root/index.js', - fastfs, - moduleCache: new ModuleCache(fastfs, cache), - cache, - extractor, - depGraphHelpers: new DependencyGraphHelpers(), - }); - }); - } - pit('uses custom require extractors if specified', () => { - fs.__setMockFilesystem({ - 'root': { - 'index.js': '', - }, + mockIndexFile(''); + const module = createModule({ + extractor: code => ({deps: {sync: ['foo', 'bar']}}), }); - return createModuleWithExtractor( - code => ({deps: {sync: ['foo', 'bar']}}) - ).then(module => - module.getDependencies().then(actual => - expect(actual).toEqual(['foo', 'bar']) - ) - ); + return module.getDependencies().then(actual => + expect(actual).toEqual(['foo', 'bar'])); + }); + }); + + describe('Custom Code Transform', () => { + let transformCode; + const fileContents = 'arbitrary(code);'; + const exampleCode = ` + require('a'); + System.import('b'); + require('c');`; + + beforeEach(function() { + transformCode = jest.genMockFn(); + mockIndexFile(fileContents); + transformCode.mockReturnValue(Promise.resolve({code: ''})); + }); + + pit('passes the module and file contents to the transform function when reading', () => { + const module = createModule({transformCode}); + return module.read() + .then(() => { + expect(transformCode).toBeCalledWith(module, fileContents); + }); + }); + + pit('uses the code that `transformCode` resolves to to extract dependencies', () => { + transformCode.mockReturnValue(Promise.resolve({code: exampleCode})); + const module = createModule({transformCode}); + + return Promise.all([ + module.getDependencies(), + module.getAsyncDependencies(), + ]).then(([dependencies, asyncDependencies]) => { + expect(dependencies).toEqual(['a', 'c']); + expect(asyncDependencies).toEqual([['b']]); + }); + }); + + pit('uses dependencies that `transformCode` resolves to, instead of extracting them', () => { + const mockedDependencies = ['foo', 'bar']; + transformCode.mockReturnValue(Promise.resolve({ + code: exampleCode, + dependencies: mockedDependencies, + })); + const module = createModule({transformCode}); + + return Promise.all([ + module.getDependencies(), + module.getAsyncDependencies(), + ]).then(([dependencies, asyncDependencies]) => { + expect(dependencies).toEqual(mockedDependencies); + expect(asyncDependencies).toEqual([['b']]); + }); + }); + + pit('uses async dependencies that `transformCode` resolves to, instead of extracting them', () => { + const mockedAsyncDependencies = [['foo', 'bar'], ['baz']]; + transformCode.mockReturnValue(Promise.resolve({ + code: exampleCode, + asyncDependencies: mockedAsyncDependencies, + })); + const module = createModule({transformCode}); + + return Promise.all([ + module.getDependencies(), + module.getAsyncDependencies(), + ]).then(([dependencies, asyncDependencies]) => { + expect(dependencies).toEqual(['a', 'c']); + expect(asyncDependencies).toEqual(mockedAsyncDependencies); + }); + }); + + pit('exposes the transformed code rather than the raw file contents', () => { + transformCode.mockReturnValue(Promise.resolve({code: exampleCode})); + const module = createModule({transformCode}); + return Promise.all([module.read(), module.getCode()]) + .then(([data, code]) => { + expect(data.code).toBe(exampleCode); + expect(code).toBe(exampleCode); + }); }); }); });