lib/assets/index.js

lib/ AssetGraph.js errors.js index.js query.js
assets/ Asset.js Atom.js CacheManifest.js CoffeeScript.js Css.js Flash.js Gif.js Htc.js Html.js I18n.js Ico.js Image.js JavaScript.js Jpeg.js Json.js KnockoutJsTemplate.js Less.js Png.js Rss.js StaticUrlMap.js Stylus.js Text.js Xml.js index.js
relations/ CacheManifestEntry.js CssAlphaImageLoader.js CssBehavior.js CssFontFaceSrc.js CssImage.js CssImport.js CssUrlTokenRelation.js HtmlAlternateLink.js HtmlAnchor.js HtmlAppleTouchStartupImage.js HtmlApplet.js HtmlAudio.js HtmlCacheManifest.js HtmlConditionalComment.js HtmlDataBindAttribute.js HtmlEdgeSideInclude.js HtmlEmbed.js HtmlFrame.js HtmlIFrame.js HtmlIFrameSrcDoc.js HtmlImage.js HtmlInlineScriptTemplate.js HtmlKnockoutContainerless.js HtmlObject.js HtmlRelation.js HtmlRequireJsMain.js HtmlScript.js HtmlShortcutIcon.js HtmlStyle.js HtmlStyleAttribute.js HtmlVideo.js HtmlVideoPoster.js JavaScriptAmdDefine.js JavaScriptAmdRequire.js JavaScriptCommonJsRequire.js JavaScriptExtJsRequire.js JavaScriptGetStaticUrl.js JavaScriptGetText.js JavaScriptInclude.js JavaScriptShimRequire.js JavaScriptTrHtml.js Relation.js StaticUrlMapEntry.js index.js
resolvers/ data.js extJs4Dir.js file.js fixedDirectory.js http.js index.js javascript.js
transforms/ addCacheManifest.js bundleRelations.js bundleRequireJs.js compileCoffeeScriptToJavaScript.js compileLessToCss.js compileStylusToCss.js compressJavaScript.js convertCssImportsToHtmlStyles.js convertHtmlStylesToInlineCssImports.js convertStylesheetsToInlineStyles.js drawGraph.js executeJavaScriptInOrder.js externalizeRelations.js flattenStaticIncludes.js inlineCssImagesWithLegacyFallback.js inlineRelations.js loadAssets.js mergeIdenticalAssets.js minifyAssets.js moveAssets.js moveAssetsInOrder.js populate.js prettyPrintAssets.js pullGlobalsIntoVariables.js registerRequireJsConfig.js removeAssets.js removeRelations.js setAssetContentType.js setAssetEncoding.js setAssetExtension.js setHtmlImageDimensions.js startOverIfAssetSourceFilesChange.js writeAssetsToDisc.js writeAssetsToStdout.js writeStatsToStderr.js
util/ deepCopy.js extendWithGettersAndSetters.js fsTools.js getImageInfoFromBuffers.js memoizeAsyncAccessor.js uniqueId.js urlTools.js
var childProcess = require('child_process'),
    util = require('util'),
    Path = require('path'),
    _ = require('underscore'),
    seq = require('seq'),
    passError = require('passerror'),
    urlTools = require('../util/urlTools'),
    assets = {};

assets.typeByExtension = {};

assets.typeByContentType = {};

assets.register = function (type, Constructor) {
    var prototype = Constructor.prototype;
    prototype.type = type;
    assets[type] = Constructor;
    Constructor.prototype['is' + type] = true;
    if (prototype.contentType) {
        if (prototype.contentType in assets.typeByContentType) {
            console.warn(type + ': Redefinition of Content-Type ' + prototype.contentType);
            console.trace();
        }
        assets.typeByContentType[prototype.contentType] = type;
    }
    if (prototype.supportedExtensions) {
        prototype.supportedExtensions.forEach(function (supportedExtension) {
            if (supportedExtension in assets.typeByExtension) {
                console.warn(type + ': Redefinition of ' + supportedExtension + ' extension');
                console.trace();
            }
            assets.typeByExtension[supportedExtension] = type;
        });
    }
};

assets.lookupContentType = function (contentType) {
    if (contentType) {
        // Trim whitespace and semicolon suffixes such as ;charset=...
        contentType = contentType.match(/^\s*([^;\s]*)(?:;|\s|$)/)[1].toLowerCase(); // Will always match
        if (contentType in assets.typeByContentType) {
            return assets.typeByContentType[contentType];
        } else if (/\+xml$/i.test(contentType)) {
            return 'Xml';
        } else if (/^text\//i.test(contentType)) {
            return 'Text';
        } else {
            return 'Asset';
        }
    }
};

// Tricky business: Might also modify assetConfig.url and assetConfig.rawSrc
assets.ensureAssetConfigHasType = function (assetConfig, assetGraph, cb) {
    if (assetConfig.isAsset || assetConfig.type) {
        return process.nextTick(cb);
    }
    // Looks like there's no way around loading the asset and looking at the src or metadata
    return assetConfig.rawSrcProxy(function (err, rawSrc, metadata) {
        if (err) {
            assetGraph.emit('error', new Error("assets.ensureAssetConfigHasType: Couldn't load " + (assetConfig.url || util.inspect(assetConfig)) + ", assuming assets.Asset"));
            assetConfig.type = 'Asset';
            return cb();
        }
        assetConfig.rawSrc = rawSrc;
        if (metadata) {
            _.extend(assetConfig, metadata);
        }
        function foundType(type) {
            if (!type) {
                assetGraph.emit('error', new Error("assets.ensureAssetConfigHasType: Couldn't determine asset type from asset config: " + util.inspect(assetConfig)) + ", assuming assets.Asset");
            }
            assetConfig.type = type || 'Asset';
            cb();
        }
        if (metadata && metadata.url) {
            var newExtension = Path.extname(assetConfig.url.replace(/[\?\#].*$/, "")).toLowerCase();
            if (newExtension in assets.typeByExtension) {
                return foundType(assets.typeByExtension[newExtension]);
            }
        }
        if (metadata && metadata.contentType) {
            // If the asset was served using HTTP, we shouldn't try to second guess by sniffing.
            foundType(assets.lookupContentType(metadata.contentType));
        } else if (rawSrc.length === 0) {
            foundType(); // Give up
        } else {
            // Work the magic
            var fileProcess = childProcess.spawn('file', ['-b', '--mime-type', '-']),
                fileOutput = '';

            // The 'file' utility might close its stdin as soon as it has figured out the content type:
            fileProcess.stdin.on('error', function () {});

            fileProcess.stdout.on('data', function (chunk) {
                fileOutput += chunk;
            }).on('end', function () {
                foundType(assets.lookupContentType(fileOutput.match(/^([^\n]*)/)[1]));
            });
            fileProcess.stdin.end(rawSrc);
        }
    });
};

assets.create = function (assetConfig) {
    if (!assetConfig.type) {
        throw new Error("assets.create: No type provided in assetConfig" + util.inspect(assetConfig));
    }
    if (assetConfig.isAsset) {
        return assetConfig;
    } else {
        return new assets[assetConfig.type](assetConfig);
    }
};

assets.resolveConfig = function (assetConfig, fromUrl, assetGraph, cb) {
    if (_.isArray(assetConfig)) {
        // Call ourselves recursively for each item, flatten the results and report back
        if (assetConfig.some(_.isArray)) {
            throw new Error("assets.resolveConfig: Multidimensional array not supported.");
        }
        return seq(assetConfig)
            .parMap(function (_assetConfig) {
                var callback = this;
                assets.resolveConfig(_assetConfig, fromUrl, assetGraph, function (err, _resolvedAssetConfigs) {
                    if (err) {
                        assetGraph.emit('error', err);
                        return callback(null, []);
                    } else {
                        callback(null, _resolvedAssetConfigs);
                    }
                });
            })
            .unflatten()
            .seq(function (resolvedAssetConfigs) {
                cb(null, _.flatten(resolvedAssetConfigs));
            })
            ['catch'](cb);
    }
    if (typeof assetConfig === 'string') {
        if (/^[\w\+]+:/.test(assetConfig)) {
            // Includes protocol, assume url
            assetConfig = {url: assetConfig};
        } else {
            // File system path
            assetConfig = {url: encodeURI(assetConfig)};
        }
    }
    if (assetConfig.isAsset || assetConfig.isResolved) {
        // Almost done, add .type property if possible (this is all we can do without actually fetching the asset):
        assetConfig.type =
            assetConfig.type ||
            (assetConfig.contentType && assets.lookupContentType(assetConfig.contentType)) ||
            assets.typeByExtension[Path.extname(assetConfig.url.replace(/[\?\#].*$/, "")).toLowerCase()];
        return process.nextTick(function () {
            cb(null, assetConfig);
        });
    } else if (assetConfig.url) {
        if (/^[a-zA-Z\+]+:/.test(assetConfig.url)) {
            var protocol = assetConfig.url.substr(0, assetConfig.url.indexOf(':')),
                resolver = assetGraph.resolverByProtocol[protocol] || assetGraph.defaultResolver;
            if (resolver) {
                resolver(assetConfig, fromUrl, function (err, resolvedAssetConfig) {
                    if (err) {
                        assetGraph.emit('error', err);
                        cb(null, []);
                    } else {
                        // Keep reresolving until the .isResolved property shows up:
                        assets.resolveConfig(resolvedAssetConfig, fromUrl, assetGraph, cb);
                    }
                });
            } else {
                return process.nextTick(function () {
                    assetGraph.emit('warn', new Error("assets.resolveConfig: No resolver found for protocol: " + protocol));
                    cb(null, []);
                });
            }
        } else {
            if (/^\/(?:[^\/]|$)/.test(assetConfig.url) && /^file:/.test(fromUrl) && /^file:/.test(assetGraph.root)) {
                assetConfig.url = urlTools.resolveUrl(assetGraph.root, assetConfig.url.substr(1));
            } else {
                assetConfig.url = urlTools.resolveUrl(fromUrl, assetConfig.url);
            }
            assets.resolveConfig(assetConfig, fromUrl, assetGraph, cb);
        }
    } else {
        process.nextTick(function () {
            assetGraph.emit('error', new Error("assets.resolveConfig: Cannot resolve asset config (no url): " + util.inspect(assetConfig)));
            cb(null, []);
        });
    }
};

require('fs').readdirSync(__dirname).forEach(function (fileName) {
    if (/\.js$/.test(fileName) && fileName !== 'index.js') {
        assets.register(fileName.replace(/\.js$/, ''), require('./' + fileName));
    }
});

_.extend(exports, assets);