[react-packager] Implement image loading i.e. ix('img') -> require('image!img');

This commit is contained in:
Amjad Masad 2015-03-13 16:42:18 -07:00
parent 449ed58546
commit d2cf0de12e
7 changed files with 201 additions and 30 deletions

View File

@ -22,6 +22,8 @@ function ModuleDescriptor(fields) {
this.isPolyfill = fields.isPolyfill || false; this.isPolyfill = fields.isPolyfill || false;
this.isAsset = fields.isAsset || false;
this._fields = fields; this._fields = fields;
} }

View File

@ -56,6 +56,40 @@ describe('DependencyGraph', function() {
}); });
}); });
pit('should get dependencies', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("image!a")'
].join('\n'),
'imgs': {
'a.png': ''
},
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetRoots: ['/root/imgs']
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{id: 'index', path: '/root/index.js', dependencies: ['image!a']},
{ id: 'image!a',
path: '/root/imgs/a.png',
dependencies: [],
isAsset: true
},
]);
});
});
pit('should get recursive dependencies', function() { pit('should get recursive dependencies', function() {
var root = '/root'; var root = '/root';
fs.__setMockFilesystem({ fs.__setMockFilesystem({

View File

@ -28,12 +28,22 @@ var validateOpts = declareOpts({
type: 'object', type: 'object',
required: true, required: true,
}, },
assetRoots: {
type: 'array',
default: [],
},
assetExts: {
type: 'array',
default: ['png'],
}
}); });
function DependecyGraph(options) { function DependecyGraph(options) {
var opts = validateOpts(options); var opts = validateOpts(options);
this._roots = opts.roots; this._roots = opts.roots;
this._assetRoots = opts.assetRoots;
this._assetExts = opts.assetExts;
this._ignoreFilePath = opts.ignoreFilePath; this._ignoreFilePath = opts.ignoreFilePath;
this._fileWatcher = options.fileWatcher; this._fileWatcher = options.fileWatcher;
@ -50,7 +60,16 @@ function DependecyGraph(options) {
} }
DependecyGraph.prototype.load = function() { DependecyGraph.prototype.load = function() {
return this._loading || (this._loading = this._search()); if (this._loading != null) {
return this._loading;
}
this._loading = q.all([
this._search(),
this._buildAssetMap(),
]);
return this._loading;
}; };
/** /**
@ -115,6 +134,15 @@ DependecyGraph.prototype.resolveDependency = function(
fromModule, fromModule,
depModuleId depModuleId
) { ) {
// Process asset requires.
var assetMatch = depModuleId.match(/^image!(.+)/);
if (assetMatch && assetMatch[1]) {
if (!this._assetMap[assetMatch[1]]) {
throw new Error('Cannot find asset: ' + assetMatch[1]);
}
return this._assetMap[assetMatch[1]];
}
var packageJson, modulePath, dep; var packageJson, modulePath, dep;
// Package relative modules starts with '.' or '..'. // Package relative modules starts with '.' or '..'.
@ -214,32 +242,13 @@ DependecyGraph.prototype._search = function() {
// 2. Filter the files and queue up the directories. // 2. Filter the files and queue up the directories.
// 3. Process any package.json in the files // 3. Process any package.json in the files
// 4. recur. // 4. recur.
return readDir(dir) return readAndStatDir(dir)
.then(function(files){ .spread(function(files, stats) {
return q.all(files.map(function(filePath) { var modulePaths = files.filter(function(filePath, i) {
return realpath(path.join(dir, filePath)).catch(handleBrokenLink); if (self._ignoreFilePath(filePath)) {
}));
})
.then(function(filePaths) {
filePaths = filePaths.filter(function(filePath) {
if (filePath == null) {
return false; return false;
} }
return !self._ignoreFilePath(filePath);
});
var statsP = filePaths.map(function(filePath) {
return lstat(filePath).catch(handleBrokenLink);
});
return [
filePaths,
q.all(statsP)
];
})
.spread(function(files, stats) {
var modulePaths = files.filter(function(filePath, i) {
if (stats[i].isDirectory()) { if (stats[i].isDirectory()) {
self._queue.push(filePath); self._queue.push(filePath);
return false; return false;
@ -465,6 +474,19 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) {
return null; return null;
}; };
DependecyGraph.prototype._buildAssetMap = function() {
if (this._assetRoots == null || this._assetRoots.length === 0) {
return q();
}
var self = this;
return buildAssetMap(this._assetRoots, this._assetExts)
.then(function(map) {
self._assetMap = map;
return map;
});
};
/** /**
* Extract all required modules from a `code` string. * Extract all required modules from a `code` string.
*/ */
@ -511,4 +533,70 @@ function handleBrokenLink(e) {
return q(); return q();
} }
function readAndStatDir(dir) {
return readDir(dir)
.then(function(files){
return q.all(files.map(function(filePath) {
return realpath(path.join(dir, filePath)).catch(handleBrokenLink);
}));
}).then(function(files) {
files = files.filter(function(f) {
return !!f;
});
var stats = files.map(function(filePath) {
return lstat(filePath).catch(handleBrokenLink);
});
return [
files,
q.all(stats),
];
});
}
/**
* Given a list of roots and list of extensions find all the files in
* the directory with that extension and build a map of those assets.
*/
function buildAssetMap(roots, exts) {
var queue = roots.slice(0);
var map = Object.create(null);
function search() {
var root = queue.shift();
if (root == null) {
return q(map);
}
return readAndStatDir(root).spread(function(files, stats) {
files.forEach(function(file, i) {
if (stats[i].isDirectory()) {
queue.push(file);
} else {
var ext = path.extname(file).replace(/^\./, '');
if (exts.indexOf(ext) !== -1) {
var assetName = path.basename(file, '.' + ext);
if (map[assetName] != null) {
debug('Conflcting assets', assetName);
}
map[assetName] = new ModuleDescriptor({
id: 'image!' + assetName,
path: path.resolve(file),
isAsset: true,
dependencies: [],
});
}
}
});
return search();
});
}
return search();
}
module.exports = DependecyGraph; module.exports = DependecyGraph;

View File

@ -17,7 +17,6 @@ var DEFINE_MODULE_CODE = [
].join(''); ].join('');
var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g;
var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi;
var validateOpts = declareOpts({ var validateOpts = declareOpts({
@ -40,6 +39,10 @@ var validateOpts = declareOpts({
type: 'string', type: 'string',
default: 'haste', default: 'haste',
}, },
assetRoots: {
type: 'array',
default: [],
},
}); });
function HasteDependencyResolver(options) { function HasteDependencyResolver(options) {
@ -51,11 +54,12 @@ function HasteDependencyResolver(options) {
this._depGraph = new DependencyGraph({ this._depGraph = new DependencyGraph({
roots: opts.projectRoots, roots: opts.projectRoots,
assetRoots: opts.assetRoots,
ignoreFilePath: function(filepath) { ignoreFilePath: function(filepath) {
return filepath.indexOf('__tests__') !== -1 || return filepath.indexOf('__tests__') !== -1 ||
(opts.blacklistRE && opts.blacklistRE.test(filepath)); (opts.blacklistRE && opts.blacklistRE.test(filepath));
}, },
fileWatcher: this._fileWatcher fileWatcher: this._fileWatcher,
}); });

View File

@ -40,6 +40,11 @@ describe('Packager', function() {
var modules = [ var modules = [
{id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'foo', path: '/root/foo.js', dependencies: []},
{id: 'bar', path: '/root/bar.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []},
{ id: 'image!img',
path: '/root/img/img.png',
isAsset: true,
dependencies: [],
}
]; ];
getDependencies.mockImpl(function() { getDependencies.mockImpl(function() {
@ -74,6 +79,15 @@ describe('Packager', function() {
'source /root/bar.js', 'source /root/bar.js',
'/root/bar.js' '/root/bar.js'
]); ]);
expect(p.addModule.mock.calls[2]).toEqual([
'lol module.exports = ' +
JSON.stringify({ uri: 'img', isStatic: true}) +
'; lol',
'module.exports = ' +
JSON.stringify({ uri: 'img', isStatic: true}) +
';',
'/root/img/img.png'
]);
expect(p.finalize.mock.calls[0]).toEqual([ expect(p.finalize.mock.calls[0]).toEqual([
{runMainModule: true} {runMainModule: true}

View File

@ -44,6 +44,10 @@ var validateOpts = declareOpts({
type: 'boolean', type: 'boolean',
default: false, default: false,
}, },
assetRoots: {
type: 'array',
required: false,
},
}); });
function Packager(options) { function Packager(options) {
@ -56,7 +60,8 @@ function Packager(options) {
blacklistRE: opts.blacklistRE, blacklistRE: opts.blacklistRE,
polyfillModuleNames: opts.polyfillModuleNames, polyfillModuleNames: opts.polyfillModuleNames,
nonPersistent: opts.nonPersistent, nonPersistent: opts.nonPersistent,
moduleFormat: opts.moduleFormat moduleFormat: opts.moduleFormat,
assetRoots: opts.assetRoots,
}); });
this._transformer = new Transformer({ this._transformer = new Transformer({
@ -118,10 +123,18 @@ Packager.prototype.getDependencies = function(main, isDev) {
}; };
Packager.prototype._transformModule = function(module) { Packager.prototype._transformModule = function(module) {
var resolver = this._resolver; var transform;
return this._transformer.loadFileAndTransform(
if (module.isAsset) {
transform = q(generateAssetModule(module));
} else {
transform = this._transformer.loadFileAndTransform(
path.resolve(module.path) path.resolve(module.path)
).then(function(transformed) { );
}
var resolver = this._resolver;
return transform.then(function(transformed) {
return _.extend( return _.extend(
{}, {},
transformed, transformed,
@ -140,5 +153,17 @@ Packager.prototype.getGraphDebugInfo = function() {
return this._resolver.getDebugInfo(); return this._resolver.getDebugInfo();
}; };
function generateAssetModule(module) {
var code = 'module.exports = ' + JSON.stringify({
uri: module.id.replace(/^[^!]+!/, ''),
isStatic: true,
}) + ';';
return {
code: code,
sourceCode: code,
sourcePath: module.path,
};
}
module.exports = Packager; module.exports = Packager;

View File

@ -43,6 +43,10 @@ var validateOpts = declareOpts({
type: 'boolean', type: 'boolean',
default: false, default: false,
}, },
assetRoots: {
type: 'array',
required: false,
},
}); });
function Server(options) { function Server(options) {