Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tag: v1.0.8
Fetching contributors…

Cannot retrieve contributors at this time

executable file 248 lines (239 sloc) 11.34 kb
#!/usr/bin/env node
var optimist = require('optimist'),
commandLineOptions = optimist
.usage('$0 --root <inputRootDirectory> --outroot <dir> [options] <htmlFile(s)>')
.options('h', {
alias: 'help',
describe: 'Show this help',
type: 'boolean',
default: false
})
.options('root', {
describe: 'Path to your web root',
type: 'string',
demand: true
})
.options('outroot', {
describe: 'Path to the output folder. Will be generated if non-existing',
type: 'string',
demand: true
})
.options('cdnroot', {
describe: 'URI root where the static assets will be deployed. Must be either an absolute or a protocol-relative url.',
type: 'string',
demand: false
})
.options('cdnoutroot', {
describe: 'Path to the CDN output folder. Will be generated if non-existing',
type: 'string',
demand: false
})
.options('exclude', {
describe: 'Url pattern to exclude from the build. Supports * wildcards. You can create multiple of these: --exclude *.php --exclude http://example.com/*.gif',
type: 'string',
demand: false
})
.options('label', {
describe: 'Registers labels as custom protocols for path resolving. You can create multiple of these: --label <labelName>=<dir> --label <otherLabelName>=<otherDir>',
type: 'string',
demand: false
})
.options('parentdir', {
describe: 'If an unknown label (scheme) is found, look for at parent dir of that name before failing (breaks custom protocols)',
type: 'boolean',
demand: false
})
.options('locale', {
describe: 'Comma-separated list of locales to build seperate versions for',
type: 'string',
demand: false
})
.options('defaultlocale', {
describe: 'The locale of the default value in TR statements and tags with a data-i18n attribute',
type: 'string',
default: 'en'
})
.options('localecookiename', {
describe: 'The name of your locale cookie (exposed as LOCALECOOKIENAME)',
type: 'string',
default: 'en'
})
.options('optimizepngs', {
describe: 'Tries optimal palette reduction, removes ancillary chunks and tries for better compression',
type: 'boolean',
default: false
})
.options('inlinesize', {
describe: 'Inline CSS backgrounds below this threshold as data-uris',
default: 8192
})
.options('deferscripts', {
describe: 'Sets the "defer" attribute on all script tags',
type: 'boolean',
default: false
})
.options('asyncscripts', {
describe: 'Sets the "async" attribute on all script tags',
type: 'boolean',
default: false
})
.options('nocompress', {
describe: 'Prettifies HTML, CSS and Javascript for easier debugging',
type: 'boolean',
default: false
})
.options('mangletoplevel', {
describe: 'Wraps your javascript code in a function literal that pulls global variables into local variables for better minification. WARNING: This may break your JS',
type: 'boolean',
default: false
})
.options('manifest', {
describe: 'Generates an appcache manifest file with all static assets included',
type: 'boolean',
default: false
})
.options('less', {
describe: 'Translates .less files to CSS',
type: 'boolean',
default: false
})
.options('stoponwarning', {
describe: 'Whether to stop with a non-zero exit code when a warning is encountered',
type: 'boolean',
default: false
})
.options('version', {
describe: 'Adds or updates <meta http-equiv="Content-Version" content="..."> to the specified value. Use {0} to refer to the current value, eg. --version {0}/production or --version `git describe --long --tags --always --dirty 2>/dev/null || echo unknown`',
type: 'string'
})
.check(function (argv) {
return typeof argv.inlinesize === 'number';
})
.demand(1)
.wrap(72)
.argv;
if (commandLineOptions.h) {
optimist.showHelp();
process.exit(1);
}
var _ = require('underscore'),
AssetGraph = require('assetgraph'),
bootstrapper = require('../lib/bootstrapper'),
query = AssetGraph.query,
urlTools = require('assetgraph/lib/util/urlTools'),
outroot = urlTools.fsDirToFileUrl(commandLineOptions.outroot),
cdnroot = commandLineOptions.cdnroot && urlTools.ensureTrailingSlash(commandLineOptions.cdnroot),
cdnoutroot = commandLineOptions.cdnoutroot && urlTools.fsDirToFileUrl(commandLineOptions.cdnoutroot),
localeIds = commandLineOptions.locale && _.flatten(_.flatten([commandLineOptions.locale]).map(function (localeId) {return localeId.split(",");})),
blacklistUrlRegExp = /^$/;
if (commandLineOptions.exclude) {
blacklistUrlRegExp = new RegExp('(?:' +
_.flatten(_.flatten([commandLineOptions.exclude])).map(function (wildcard) {
return wildcard.replace(/[\.\+\{\}\[\]\(\)\?\^\$]/g, '\\$&').replace(/\*/g, '.*?');
}).join('|') +
')');
}
require('../lib/registerTransforms');
new AssetGraph({root: commandLineOptions.root})
.on('afterTransform', function (transform, elapsedTime) {
console.log((elapsedTime / 1000).toFixed(3) + " secs: " + transform.name);
})
.on('warn', function (err) {
// These are way too noisy
if (err.relationType !== 'JavaScriptCommonJsRequire') {
console.warn((err.asset ? err.asset.urlOrDescription + ': ' : '') + err.message);
if (commandLineOptions.stoponwarning) {
process.exit(1);
}
}
})
.on('error', function (err) {
console.error((err.asset ? err.asset.urlOrDescription + ': ' : '') + err.stack);
process.exit(1);
})
.registerRequireJsConfig()
.registerLabelsAsCustomProtocols(commandLineOptions.label, {installFindParentDirectoryAsDefault: commandLineOptions.parentdir})
.loadAssets(commandLineOptions._.map(urlTools.fsFilePathToFileUrl))
.populate({
followRelations: query.or({to: {type: 'I18n'}},
{type: query.not(['JavaScriptInclude', 'JavaScriptExtJsRequire', 'JavaScriptCommonJsRequire', 'HtmlAnchor']), to: query.and({url: query.not(/^https?:/)}, {url: query.not(blacklistUrlRegExp)})})
})
// Remove bootstrapper scripts injected by buildDevelopment:
.removeRelations({type: 'HtmlScript', node: {id: 'bootstrapper'}, from: {type: 'Html'}}, {detach: true, removeOrphan: true})
.if(commandLineOptions.version)
.addContentVersionMetaElement({type: 'Html'}, commandLineOptions.version, true)
.endif()
.if(commandLineOptions.less)
// Replace Less assets with their Css counterparts:
.compileLessToCss({type: 'Less'})
// Kill in-browser less compiler and remove its incoming relations:
.removeAssets({url: /\/less(?:-\d+\.\d+\.\d+)?(?:\.min)?\.js$/}, true)
// Find and populate CssImage relations from the compiled Less assets:
.populate({from: {type: 'Css'}})
.endif()
.removeRelations({type: 'JavaScriptInclude', to: {type: ['Css', 'JavaScript']}}, {detach: true, unresolved: true})
.convertCssImportsToHtmlStyles()
.removeAssets({isEmpty: true, type: ['Css', 'JavaScript']}, true)
.externalizeRelations({from: {type: query.not('Htc')}, type: ['HtmlStyle', 'HtmlScript'], node: function (node) {return !node.hasAttribute('nobundle');}})
.mergeIdenticalAssets(query.or({isImage: true}, {type: ['JavaScript', 'Css']}))
.spriteBackgroundImages()
.postProcessCssImages()
.if(commandLineOptions.optimizepngs)
.optimizePngs()
.endif()
.bundleRequireJs({type: 'Html'})
.bundleRelations({type: 'HtmlStyle', to: {type: 'Css'}, node: function (node) {return !node.hasAttribute('nobundle');}})
.bundleRelations({type: 'HtmlScript', to: {type: 'JavaScript'}, node: function (node) {return !node.hasAttribute('nobundle');}})
.removeNobundleAttribute({type: ['HtmlScript', 'HtmlStyle']})
.inlineCssImagesWithLegacyFallback({type: 'Html', isInline: false}, commandLineOptions.inlinesize)
.if(commandLineOptions.mangletoplevel)
.pullGlobalsIntoVariables({type: 'JavaScript'})
.endif()
.if(localeIds)
.cloneForEachLocale({type: 'Html', isInitial: true}, {
localeIds: localeIds,
supportedLocaleIds: localeIds,
localeCookieName: commandLineOptions.localeCookieName,
defaultLocaleId: commandLineOptions.defaultlocale
})
.runJavaScriptConditionalBlocks({isInitial: true}, 'LOCALIZE', true)
.endif()
.if(!commandLineOptions.nocompress)
.compressJavaScript({type: 'JavaScript'}, 'uglifyJs', {toplevel: commandLineOptions.mangletoplevel})
.endif()
.removeAssets({type: 'I18n'}, true)
.minifyAssets()
.inlineRelations({
type: ['HtmlStyle', 'HtmlScript'],
from: {isInline: false}, // Excludes relations occurring in conditional comments
to: function (asset) {return asset.isAsset && asset.rawSrc.length < 4096;}
})
.if(commandLineOptions.manifest)
.addCacheManifest({isInitial: true})
.endif()
.if(commandLineOptions.nocompress)
.prettyPrintAssets({type: ['Html', 'JavaScript', 'Css']})
.endif()
.setAsyncOrDeferOnHtmlScripts({to: {isInline: false, url: /^file:/}}, commandLineOptions.asyncscripts, commandLineOptions.deferscripts)
.omitGetStaticUrlFunctionCall()
.inlineRelations({type: 'JavaScriptGetText'})
.moveAssetsInOrder({isInitial: query.not(true), type: query.not('CacheManifest')}, function (asset, assetGraph) {
var targetUrl = "/static/";
// Conservatively assume that all GETSTATICURL relations pointing at non-images are intended to be fetched via XHR
// and thus cannot be put on a CDN because of same origin restrictions:
if (cdnroot && (asset.isImage || assetGraph.findRelations({to: asset, type: 'StaticUrlMapEntry'}).length === 0)) {
targetUrl = cdnroot;
if (/^\/\//.test(cdnroot)) {
assetGraph.findRelations({to: asset}).forEach(function (incomingRelation) {
incomingRelation.hrefType = 'protocolRelative';
});
}
}
return targetUrl + asset.md5Hex.substr(0, 10) + asset.extension + asset.url.replace(/^[^#\?]*(?:)/, ''); // Preserve query string and fragment identifier
})
.writeAssetsToDisc({url: /^file:/}, outroot)
.if(cdnroot)
.writeAssetsToDisc({url: query.createPrefixMatcher(cdnroot)}, cdnoutroot || outroot, cdnroot)
.endif()
.writeStatsToStderr()
.run();
Jump to Line
Something went wrong with that request. Please try again.