From d2cf0de12e3f3c44b3aa653309bdd144551afaff Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 13 Mar 2015 16:42:18 -0700 Subject: [PATCH] [react-packager] Implement image loading i.e. ix('img') -> require('image!img'); --- .../DependencyResolver/ModuleDescriptor.js | 2 + .../__tests__/DependencyGraph-test.js | 34 +++++ .../haste/DependencyGraph/index.js | 136 ++++++++++++++---- .../src/DependencyResolver/haste/index.js | 8 +- .../src/Packager/__tests__/Packager-test.js | 14 ++ react-packager/src/Packager/index.js | 33 ++++- react-packager/src/Server/index.js | 4 + 7 files changed, 201 insertions(+), 30 deletions(-) diff --git a/react-packager/src/DependencyResolver/ModuleDescriptor.js b/react-packager/src/DependencyResolver/ModuleDescriptor.js index f1a30545..df29e57c 100644 --- a/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -22,6 +22,8 @@ function ModuleDescriptor(fields) { this.isPolyfill = fields.isPolyfill || false; + this.isAsset = fields.isAsset || false; + this._fields = fields; } diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index eb839296..6dee93ec 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -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({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index ce631856..122701d5 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -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; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 6aada00b..fdc779ed 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -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, }); diff --git a/react-packager/src/Packager/__tests__/Packager-test.js b/react-packager/src/Packager/__tests__/Packager-test.js index d8348be8..498faea3 100644 --- a/react-packager/src/Packager/__tests__/Packager-test.js +++ b/react-packager/src/Packager/__tests__/Packager-test.js @@ -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} diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index 75cccdb2..c21889b4 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -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; diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 8982bc91..7df686ad 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -43,6 +43,10 @@ var validateOpts = declareOpts({ type: 'boolean', default: false, }, + assetRoots: { + type: 'array', + required: false, + }, }); function Server(options) {