[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.isAsset = fields.isAsset || false;
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() {
var root = '/root';
fs.__setMockFilesystem({

View File

@ -28,12 +28,22 @@ var validateOpts = declareOpts({
type: 'object',
required: true,
},
assetRoots: {
type: 'array',
default: [],
},
assetExts: {
type: 'array',
default: ['png'],
}
});
function DependecyGraph(options) {
var opts = validateOpts(options);
this._roots = opts.roots;
this._assetRoots = opts.assetRoots;
this._assetExts = opts.assetExts;
this._ignoreFilePath = opts.ignoreFilePath;
this._fileWatcher = options.fileWatcher;
@ -50,7 +60,16 @@ function DependecyGraph(options) {
}
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,
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;
// Package relative modules starts with '.' or '..'.
@ -214,32 +242,13 @@ DependecyGraph.prototype._search = function() {
// 2. Filter the files and queue up the directories.
// 3. Process any package.json in the files
// 4. recur.
return readDir(dir)
.then(function(files){
return q.all(files.map(function(filePath) {
return realpath(path.join(dir, filePath)).catch(handleBrokenLink);
}));
})
.then(function(filePaths) {
filePaths = filePaths.filter(function(filePath) {
if (filePath == null) {
return readAndStatDir(dir)
.spread(function(files, stats) {
var modulePaths = files.filter(function(filePath, i) {
if (self._ignoreFilePath(filePath)) {
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()) {
self._queue.push(filePath);
return false;
@ -465,6 +474,19 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) {
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.
*/
@ -511,4 +533,70 @@ function handleBrokenLink(e) {
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;

View File

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

View File

@ -40,6 +40,11 @@ describe('Packager', function() {
var modules = [
{id: 'foo', path: '/root/foo.js', dependencies: []},
{id: 'bar', path: '/root/bar.js', dependencies: []},
{ id: 'image!img',
path: '/root/img/img.png',
isAsset: true,
dependencies: [],
}
];
getDependencies.mockImpl(function() {
@ -74,6 +79,15 @@ describe('Packager', function() {
'source /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([
{runMainModule: true}

View File

@ -44,6 +44,10 @@ var validateOpts = declareOpts({
type: 'boolean',
default: false,
},
assetRoots: {
type: 'array',
required: false,
},
});
function Packager(options) {
@ -56,7 +60,8 @@ function Packager(options) {
blacklistRE: opts.blacklistRE,
polyfillModuleNames: opts.polyfillModuleNames,
nonPersistent: opts.nonPersistent,
moduleFormat: opts.moduleFormat
moduleFormat: opts.moduleFormat,
assetRoots: opts.assetRoots,
});
this._transformer = new Transformer({
@ -118,10 +123,18 @@ Packager.prototype.getDependencies = function(main, isDev) {
};
Packager.prototype._transformModule = function(module) {
var transform;
if (module.isAsset) {
transform = q(generateAssetModule(module));
} else {
transform = this._transformer.loadFileAndTransform(
path.resolve(module.path)
);
}
var resolver = this._resolver;
return this._transformer.loadFileAndTransform(
path.resolve(module.path)
).then(function(transformed) {
return transform.then(function(transformed) {
return _.extend(
{},
transformed,
@ -140,5 +153,17 @@ Packager.prototype.getGraphDebugInfo = function() {
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;

View File

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