Start pulling DependencyResolver apart

Summary: I'm planning to split up `DependencyResolver` into the react-native specific implementation of it and a new, generic resolver that is going to replace `node-haste` for jest and other places where we might need JS dependency resolution.

The plan is to split the two folders up so that:

* `Resolver` is the folder for all the react-native specific resolver code
* `DependencyResolver` will become a standalone library, eventually moving into a package called `node-haste`.

There is still a lot to be figured out. This is just the first diff of likely many. The current goal is to make `DependencyResolver` standalone to be able to create an instance of a resolver and resolve all dependencies for a file. This is when I can start integrating it more seriously with jest.

This diff simply moves a bunch of things around and turns `HasteModuleResolver` into an ES2015 class ( :) ).

bypass-lint
public

Reviewed By: davidaurelio

Differential Revision: D2614151

fb-gh-sync-id: ff4e434c4747d2fb032d34dc19fb85e0b0c553ac
This commit is contained in:
Christoph Pojer 2015-11-04 16:04:44 -08:00 committed by facebook-github-bot-3
parent 29befc8d1c
commit c731001864
15 changed files with 208 additions and 209 deletions

View File

@ -19,7 +19,7 @@ jest.mock('fs');
var Bundler = require('../'); var Bundler = require('../');
var JSTransformer = require('../../JSTransformer'); var JSTransformer = require('../../JSTransformer');
var DependencyResolver = require('../../DependencyResolver'); var Resolver = require('../../Resolver');
var sizeOf = require('image-size'); var sizeOf = require('image-size');
var fs = require('fs'); var fs = require('fs');
@ -54,7 +54,7 @@ describe('Bundler', function() {
beforeEach(function() { beforeEach(function() {
getDependencies = jest.genMockFn(); getDependencies = jest.genMockFn();
wrapModule = jest.genMockFn(); wrapModule = jest.genMockFn();
DependencyResolver.mockImpl(function() { Resolver.mockImpl(function() {
return { return {
getDependencies: getDependencies, getDependencies: getDependencies,
wrapModule: wrapModule, wrapModule: wrapModule,

View File

@ -16,7 +16,7 @@ const ProgressBar = require('progress');
const BundlesLayout = require('../BundlesLayout'); const BundlesLayout = require('../BundlesLayout');
const Cache = require('../Cache'); const Cache = require('../Cache');
const Transformer = require('../JSTransformer'); const Transformer = require('../JSTransformer');
const DependencyResolver = require('../DependencyResolver'); const Resolver = require('../Resolver');
const Bundle = require('./Bundle'); const Bundle = require('./Bundle');
const Activity = require('../Activity'); const Activity = require('../Activity');
const ModuleTransport = require('../lib/ModuleTransport'); const ModuleTransport = require('../lib/ModuleTransport');
@ -94,7 +94,7 @@ class Bundler {
transformModulePath: opts.transformModulePath, transformModulePath: opts.transformModulePath,
}); });
this._resolver = new DependencyResolver({ this._resolver = new Resolver({
projectRoots: opts.projectRoots, projectRoots: opts.projectRoots,
blacklistRE: opts.blacklistRE, blacklistRE: opts.blacklistRE,
polyfillModuleNames: opts.polyfillModuleNames, polyfillModuleNames: opts.polyfillModuleNames,

View File

@ -13,14 +13,14 @@ jest.dontMock('../index')
var Promise = require('promise'); var Promise = require('promise');
var BundlesLayout = require('../index'); var BundlesLayout = require('../index');
var DependencyResolver = require('../../DependencyResolver'); var Resolver = require('../../Resolver');
var loadCacheSync = require('../../lib/loadCacheSync'); var loadCacheSync = require('../../lib/loadCacheSync');
describe('BundlesLayout', () => { describe('BundlesLayout', () => {
function newBundlesLayout(options) { function newBundlesLayout(options) {
return new BundlesLayout(Object.assign({ return new BundlesLayout(Object.assign({
projectRoots: ['/root'], projectRoots: ['/root'],
dependencyResolver: new DependencyResolver(), dependencyResolver: new Resolver(),
}, options)); }, options));
} }
@ -38,7 +38,7 @@ describe('BundlesLayout', () => {
} }
pit('should bundle sync dependencies', () => { pit('should bundle sync dependencies', () => {
DependencyResolver.prototype.getDependencies.mockImpl((path) => { Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) { switch (path) {
case '/root/index.js': case '/root/index.js':
return Promise.resolve({ return Promise.resolve({
@ -67,7 +67,7 @@ describe('BundlesLayout', () => {
}); });
pit('should separate async dependencies into different bundle', () => { pit('should separate async dependencies into different bundle', () => {
DependencyResolver.prototype.getDependencies.mockImpl((path) => { Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) { switch (path) {
case '/root/index.js': case '/root/index.js':
return Promise.resolve({ return Promise.resolve({
@ -100,7 +100,7 @@ describe('BundlesLayout', () => {
}); });
pit('separate async dependencies of async dependencies', () => { pit('separate async dependencies of async dependencies', () => {
DependencyResolver.prototype.getDependencies.mockImpl((path) => { Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) { switch (path) {
case '/root/index.js': case '/root/index.js':
return Promise.resolve({ return Promise.resolve({
@ -142,7 +142,7 @@ describe('BundlesLayout', () => {
}); });
pit('separate bundle sync dependencies of async ones on same bundle', () => { pit('separate bundle sync dependencies of async ones on same bundle', () => {
DependencyResolver.prototype.getDependencies.mockImpl((path) => { Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) { switch (path) {
case '/root/index.js': case '/root/index.js':
return Promise.resolve({ return Promise.resolve({
@ -180,7 +180,7 @@ describe('BundlesLayout', () => {
}); });
pit('separate cache in which bundle is each dependency', () => { pit('separate cache in which bundle is each dependency', () => {
DependencyResolver.prototype.getDependencies.mockImpl((path) => { Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) { switch (path) {
case '/root/index.js': case '/root/index.js':
return Promise.resolve({ return Promise.resolve({
@ -218,7 +218,7 @@ describe('BundlesLayout', () => {
}); });
pit('separate cache in which bundle is each dependency', () => { pit('separate cache in which bundle is each dependency', () => {
DependencyResolver.prototype.getDependencies.mockImpl((path) => { Resolver.prototype.getDependencies.mockImpl((path) => {
switch (path) { switch (path) {
case '/root/index.js': case '/root/index.js':
return Promise.resolve({ return Promise.resolve({

View File

@ -20,7 +20,7 @@ jest.mock('fs');
var BundlesLayout = require('../index'); var BundlesLayout = require('../index');
var Cache = require('../../Cache'); var Cache = require('../../Cache');
var DependencyResolver = require('../../DependencyResolver'); var Resolver = require('../../Resolver');
var fs = require('fs'); var fs = require('fs');
describe('BundlesLayout', () => { describe('BundlesLayout', () => {
@ -47,7 +47,7 @@ describe('BundlesLayout', () => {
describe('generate', () => { describe('generate', () => {
function newBundlesLayout() { function newBundlesLayout() {
const resolver = new DependencyResolver({ const resolver = new Resolver({
projectRoots: ['/root', '/' + __dirname.split('/')[1]], projectRoots: ['/root', '/' + __dirname.split('/')[1]],
fileWatcher: fileWatcher, fileWatcher: fileWatcher,
cache: new Cache(), cache: new Cache(),
@ -577,7 +577,7 @@ describe('BundlesLayout', () => {
}); });
function getBaseFs() { function getBaseFs() {
const p = path.join(__dirname, '../../../DependencyResolver/polyfills').substring(1); const p = path.join(__dirname, '../../../Resolver/polyfills').substring(1);
const root = {}; const root = {};
let currentPath = root; let currentPath = root;

View File

@ -1,175 +0,0 @@
/**
* 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.
*/
'use strict';
var path = require('path');
var DependencyGraph = require('./DependencyGraph');
var replacePatterns = require('./replacePatterns');
var Polyfill = require('./Polyfill');
var declareOpts = require('../lib/declareOpts');
var Promise = require('promise');
var validateOpts = declareOpts({
projectRoots: {
type: 'array',
required: true,
},
blacklistRE: {
type: 'object', // typeof regex is object
},
polyfillModuleNames: {
type: 'array',
default: [],
},
moduleFormat: {
type: 'string',
default: 'haste',
},
assetRoots: {
type: 'array',
default: [],
},
fileWatcher: {
type: 'object',
required: true,
},
assetExts: {
type: 'array',
required: true,
},
cache: {
type: 'object',
required: true,
},
});
function HasteDependencyResolver(options) {
var opts = validateOpts(options);
this._depGraph = new DependencyGraph({
roots: opts.projectRoots,
assetRoots_DEPRECATED: opts.assetRoots,
assetExts: opts.assetExts,
ignoreFilePath: function(filepath) {
return filepath.indexOf('__tests__') !== -1 ||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
},
fileWatcher: opts.fileWatcher,
cache: opts.cache,
});
this._polyfillModuleNames = opts.polyfillModuleNames || [];
}
var getDependenciesValidateOpts = declareOpts({
dev: {
type: 'boolean',
default: true,
},
platform: {
type: 'string',
required: false,
},
});
HasteDependencyResolver.prototype.getDependencies = function(main, options) {
var opts = getDependenciesValidateOpts(options);
return this._depGraph.getDependencies(main, opts.platform).then(
resolutionResponse => {
this._getPolyfillDependencies(opts.dev).reverse().forEach(
polyfill => resolutionResponse.prependDependency(polyfill)
);
return resolutionResponse.finalize();
}
);
};
HasteDependencyResolver.prototype._getPolyfillDependencies = function(isDev) {
var polyfillModuleNames = [
isDev
? path.join(__dirname, 'polyfills/prelude_dev.js')
: path.join(__dirname, 'polyfills/prelude.js'),
path.join(__dirname, 'polyfills/require.js'),
path.join(__dirname, 'polyfills/polyfills.js'),
path.join(__dirname, 'polyfills/console.js'),
path.join(__dirname, 'polyfills/error-guard.js'),
path.join(__dirname, 'polyfills/String.prototype.es6.js'),
path.join(__dirname, 'polyfills/Array.prototype.es6.js'),
].concat(this._polyfillModuleNames);
return polyfillModuleNames.map(
(polyfillModuleName, idx) => new Polyfill({
path: polyfillModuleName,
id: polyfillModuleName,
dependencies: polyfillModuleNames.slice(0, idx),
isPolyfill: true,
})
);
};
HasteDependencyResolver.prototype.wrapModule = function(resolutionResponse, module, code) {
return Promise.resolve().then(() => {
if (module.isPolyfill()) {
return Promise.resolve(code);
}
const resolvedDeps = Object.create(null);
const resolvedDepsArr = [];
return Promise.all(
resolutionResponse.getResolvedDependencyPairs(module).map(
([depName, depModule]) => {
if (depModule) {
return depModule.getName().then(name => {
resolvedDeps[depName] = name;
resolvedDepsArr.push(name);
});
}
}
)
).then(() => {
const relativizeCode = (codeMatch, pre, quot, depName, post) => {
const depId = resolvedDeps[depName];
if (depId) {
return pre + quot + depId + post;
} else {
return codeMatch;
}
};
return module.getName().then(
name => defineModuleCode({
code: code.replace(replacePatterns.IMPORT_RE, relativizeCode)
.replace(replacePatterns.EXPORT_RE, relativizeCode)
.replace(replacePatterns.REQUIRE_RE, relativizeCode),
moduleName: name,
})
);
});
});
};
HasteDependencyResolver.prototype.getDebugInfo = function() {
return this._depGraph.getDebugInfo();
};
function defineModuleCode({moduleName, code}) {
return [
`__d(`,
`'${moduleName}',`,
'function(global, require, module, exports) {',
` ${code}`,
'\n});',
].join('');
}
module.exports = HasteDependencyResolver;

View File

@ -10,19 +10,20 @@
jest.dontMock('../') jest.dontMock('../')
.dontMock('underscore') .dontMock('underscore')
.dontMock('../replacePatterns'); .dontMock('../../DependencyResolver/replacePatterns');
jest.mock('path'); jest.mock('path');
var Promise = require('promise'); var Promise = require('promise');
var HasteDependencyResolver = require('../'); var Resolver = require('../');
var Module = require('../Module'); var Module = require('../../DependencyResolver/Module');
var Polyfill = require('../Polyfill'); var Polyfill = require('../../DependencyResolver/Polyfill');
var DependencyGraph = require('../../DependencyResolver/DependencyGraph');
var path = require('path'); var path = require('path');
var _ = require('underscore'); var _ = require('underscore');
describe('HasteDependencyResolver', function() { describe('Resolver', function() {
beforeEach(function() { beforeEach(function() {
Polyfill.mockClear(); Polyfill.mockClear();
@ -60,13 +61,11 @@ describe('HasteDependencyResolver', function() {
var module = createModule('index'); var module = createModule('index');
var deps = [module]; var deps = [module];
var depResolver = new HasteDependencyResolver({ var depResolver = new Resolver({
projectRoot: '/root', projectRoot: '/root',
}); });
// Is there a better way? How can I mock the prototype instead? DependencyGraph.prototype.getDependencies.mockImpl(function() {
var depGraph = depResolver._depGraph;
depGraph.getDependencies.mockImpl(function() {
return Promise.resolve(new ResolutionResponseMock({ return Promise.resolve(new ResolutionResponseMock({
dependencies: deps, dependencies: deps,
mainModuleId: 'index', mainModuleId: 'index',
@ -144,12 +143,11 @@ describe('HasteDependencyResolver', function() {
var module = createModule('index'); var module = createModule('index');
var deps = [module]; var deps = [module];
var depResolver = new HasteDependencyResolver({ var depResolver = new Resolver({
projectRoot: '/root', projectRoot: '/root',
}); });
var depGraph = depResolver._depGraph; DependencyGraph.prototype.getDependencies.mockImpl(function() {
depGraph.getDependencies.mockImpl(function() {
return Promise.resolve(new ResolutionResponseMock({ return Promise.resolve(new ResolutionResponseMock({
dependencies: deps, dependencies: deps,
mainModuleId: 'index', mainModuleId: 'index',
@ -160,7 +158,7 @@ describe('HasteDependencyResolver', function() {
return depResolver.getDependencies('/root/index.js', { dev: true }) return depResolver.getDependencies('/root/index.js', { dev: true })
.then(function(result) { .then(function(result) {
expect(result.mainModuleId).toEqual('index'); expect(result.mainModuleId).toEqual('index');
expect(depGraph.getDependencies).toBeCalledWith('/root/index.js', undefined); expect(DependencyGraph.mock.instances[0].getDependencies).toBeCalledWith('/root/index.js', undefined);
expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]); expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]);
expect(result.dependencies[result.dependencies.length - 1]) expect(result.dependencies[result.dependencies.length - 1])
.toBe(module); .toBe(module);
@ -171,13 +169,12 @@ describe('HasteDependencyResolver', function() {
var module = createModule('index'); var module = createModule('index');
var deps = [module]; var deps = [module];
var depResolver = new HasteDependencyResolver({ var depResolver = new Resolver({
projectRoot: '/root', projectRoot: '/root',
polyfillModuleNames: ['some module'], polyfillModuleNames: ['some module'],
}); });
var depGraph = depResolver._depGraph; DependencyGraph.prototype.getDependencies.mockImpl(function() {
depGraph.getDependencies.mockImpl(function() {
return Promise.resolve(new ResolutionResponseMock({ return Promise.resolve(new ResolutionResponseMock({
dependencies: deps, dependencies: deps,
mainModuleId: 'index', mainModuleId: 'index',
@ -209,11 +206,10 @@ describe('HasteDependencyResolver', function() {
describe('wrapModule', function() { describe('wrapModule', function() {
pit('should resolve modules', function() { pit('should resolve modules', function() {
var depResolver = new HasteDependencyResolver({ var depResolver = new Resolver({
projectRoot: '/root', projectRoot: '/root',
}); });
var depGraph = depResolver._depGraph;
var dependencies = ['x', 'y', 'z', 'a', 'b']; var dependencies = ['x', 'y', 'z', 'a', 'b'];
/*eslint-disable */ /*eslint-disable */

178
react-packager/src/Resolver/index.js vendored Normal file
View File

@ -0,0 +1,178 @@
/**
* 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.
*/
'use strict';
var path = require('path');
var DependencyGraph = require('../DependencyResolver/DependencyGraph');
var replacePatterns = require('../DependencyResolver/replacePatterns');
var Polyfill = require('../DependencyResolver/Polyfill');
var declareOpts = require('../lib/declareOpts');
var Promise = require('promise');
var validateOpts = declareOpts({
projectRoots: {
type: 'array',
required: true,
},
blacklistRE: {
type: 'object', // typeof regex is object
},
polyfillModuleNames: {
type: 'array',
default: [],
},
moduleFormat: {
type: 'string',
default: 'haste',
},
assetRoots: {
type: 'array',
default: [],
},
fileWatcher: {
type: 'object',
required: true,
},
assetExts: {
type: 'array',
required: true,
},
cache: {
type: 'object',
required: true,
},
});
var getDependenciesValidateOpts = declareOpts({
dev: {
type: 'boolean',
default: true,
},
platform: {
type: 'string',
required: false,
},
});
class Resolver {
constructor(options) {
var opts = validateOpts(options);
this._depGraph = new DependencyGraph({
roots: opts.projectRoots,
assetRoots_DEPRECATED: opts.assetRoots,
assetExts: opts.assetExts,
ignoreFilePath: function(filepath) {
return filepath.indexOf('__tests__') !== -1 ||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
},
fileWatcher: opts.fileWatcher,
cache: opts.cache,
});
this._polyfillModuleNames = opts.polyfillModuleNames || [];
}
getDependencies(main, options) {
var opts = getDependenciesValidateOpts(options);
return this._depGraph.getDependencies(main, opts.platform).then(
resolutionResponse => {
this._getPolyfillDependencies(opts.dev).reverse().forEach(
polyfill => resolutionResponse.prependDependency(polyfill)
);
return resolutionResponse.finalize();
}
);
}
_getPolyfillDependencies(isDev) {
var polyfillModuleNames = [
isDev
? path.join(__dirname, 'polyfills/prelude_dev.js')
: path.join(__dirname, 'polyfills/prelude.js'),
path.join(__dirname, 'polyfills/require.js'),
path.join(__dirname, 'polyfills/polyfills.js'),
path.join(__dirname, 'polyfills/console.js'),
path.join(__dirname, 'polyfills/error-guard.js'),
path.join(__dirname, 'polyfills/String.prototype.es6.js'),
path.join(__dirname, 'polyfills/Array.prototype.es6.js'),
].concat(this._polyfillModuleNames);
return polyfillModuleNames.map(
(polyfillModuleName, idx) => new Polyfill({
path: polyfillModuleName,
id: polyfillModuleName,
dependencies: polyfillModuleNames.slice(0, idx),
isPolyfill: true,
})
);
}
wrapModule(resolutionResponse, module, code) {
return Promise.resolve().then(() => {
if (module.isPolyfill()) {
return Promise.resolve(code);
}
const resolvedDeps = Object.create(null);
const resolvedDepsArr = [];
return Promise.all(
resolutionResponse.getResolvedDependencyPairs(module).map(
([depName, depModule]) => {
if (depModule) {
return depModule.getName().then(name => {
resolvedDeps[depName] = name;
resolvedDepsArr.push(name);
});
}
}
)
).then(() => {
const relativizeCode = (codeMatch, pre, quot, depName, post) => {
const depId = resolvedDeps[depName];
if (depId) {
return pre + quot + depId + post;
} else {
return codeMatch;
}
};
return module.getName().then(
name => defineModuleCode({
code: code.replace(replacePatterns.IMPORT_RE, relativizeCode)
.replace(replacePatterns.EXPORT_RE, relativizeCode)
.replace(replacePatterns.REQUIRE_RE, relativizeCode),
moduleName: name,
})
);
});
});
}
getDebugInfo() {
return this._depGraph.getDebugInfo();
}
}
function defineModuleCode({moduleName, code}) {
return [
`__d(`,
`'${moduleName}',`,
'function(global, require, module, exports) {',
` ${code}`,
'\n});',
].join('');
}
module.exports = Resolver;

View File

@ -55,7 +55,7 @@
mod.isInitialized = true; mod.isInitialized = true;
// keep args in sync with with defineModuleCode in // keep args in sync with with defineModuleCode in
// packager/react-packager/src/DependencyResolver/index.js // packager/react-packager/src/Resolver/index.js
mod.factory.call(global, global, require, mod.module, mod.module.exports); mod.factory.call(global, global, require, mod.module, mod.module.exports);
} catch (e) { } catch (e) {
mod.hasError = true; mod.hasError = true;