AssetGraph documentation | |
| lib/assets/Asset.js |
class: Asset extends: EventEmitter
An asset object represents a single node in an AssetGraph, but can
be used and manipulated on its own outside the graph context. It
can even be present in multiple AssetGraphs at once.
|
var EventEmitter = require('events').EventEmitter,
path = require('path'),
util = require('util'),
_ = require('underscore'),
extendWithGettersAndSetters = require('../util/extendWithGettersAndSetters'),
passError = require('../util/passError'),
uniqueId = require('../util/uniqueId'),
urlEndsWithSlashRegExp = /\/(?:[?#].*)?$/;
|
new Asset(options)
Create a new Asset instance.
Most of the time it's unnecessary to create asset objects
directly. When you need to manipulate assets that already exist on
disc or on a web server, the loadAssets and populate transforms
are the easiest way to get the objects created. See the section about
transforms below.
Note that the Asset base class is only intended to be used to
represent assets for which there's no specific subclass.
Options
rawSrc Buffer object containing the raw source of the asset.
Mandatory unless the rawSrcProxy option is provided.rawSrcProxy Function that provides the raw source of the asset
to a callback (and optionally a metadata object),
for example by loading it from disc or fetching it
via http. Mandatory unless the rawSrc option is
provided.contentType (optional) The Content-Type (MIME type) of the asset.
For subclasses of Asset there will be a reasonable
default. Can also be provided by the rawSrcProxy
in the metadata object.url (optional) The fully qualified (absolute) url of the
asset. If not provided, the asset will be considered
inline. Can also be provided by the rawSrcProxy
in the `metadata' object (think HTTP redirects).extension The desired file name extension of the asset. Will
be extracted from the url option if possible, and in
that case, the extension option will be ignored.
|
function Asset(config) {
EventEmitter.call(this);
if (config.rawSrc) {
this._rawSrc = config.rawSrc;
delete config.rawSrc;
}
if (config.parseTree) {
this._parseTree = config.parseTree;
delete config.parseTree;
}
if (config.url) {
this._url = config.url;
if (!urlEndsWithSlashRegExp.test(this._url)) {
this._extension = path.extname(this.url.replace(/[?#].*$/, ''));
}
delete config.url;
} else if (config.extension) {
if (!this._extension) {
this._extension = config.extension;
}
delete config.extension;
}
if (config.outgoingRelations) {
this._outgoingRelations = config.outgoingRelations;
delete config.outgoingRelations;
}
_.extend(this, config);
this.id = uniqueId();
}
util.inherits(Asset, EventEmitter);
extendWithGettersAndSetters(Asset.prototype, {
|
asset.isAsset
{Boolean} Property that's true for all Asset instances. Avoids
reliance on the instanceof operator.
|
isAsset: true,
|
asset.contentType
{String} The Content-Type (MIME type) of the asset.
|
contentType: 'application/octet-stream',
|
asset.defaultExtension
{String} The default extension for the asset type.
|
defaultExtension: '',
|
asset.parseTree (getter)
Some asset classes support inspection and manipulation using a high
level interface. If you modify the parse tree, you have to call
asset.markDirty() so any cached serializations of the asset are
invalidated.
These are the formats you'll get:
assets.Html and assets.Xml :
jsdom <https://github.com/tmpvar/jsdom> _ document object.
assets.Css
CSSOM <https://github.com/NV/CSSOM> _ CSSStyleSheet object.
assets.JavaScript
UglifyJS <https://github.com/mishoo/UglifyJS> _ AST object.
assets.Json
Regular JavaScript object (the result of JSON.parse on the decoded source).
assets.CacheManifest
A JavaScript object with a key for each section present in the
manifest (CACHE , NETWORK , REMOTE ). The value is an array with
an item for each entry in the section. Refer to the source for
details.
|
|
asset.load(cb)
Makes sure the asset is loaded, then calls the supplied
callback. This is Asset's only async method, as soon as it is
loaded, everything can happen synchronously.
Usually you'll want to use transforms.loadAssets , which will
handle this automatically.
- param: Function cb The callback to invoke when the asset is
loaded.
|
load: function (cb) {
var that = this;
if (that._rawSrc || that._parseTree) {
process.nextTick(cb);
} else if (that.rawSrcProxy) {
that.rawSrcProxy(passError(cb, function (rawSrc, metadata) {
that._rawSrc = rawSrc;
if (metadata) {
_.extend(this, metadata);
}
delete that.rawSrcProxy;
cb();
}));
} else {
process.nextTick(function () {
cb(new Error("Asset.load: No rawSrc or rawSrcProxy found, cannot load"));
});
}
},
|
asset.extension (getter/setter)
The file name extension for the asset (String). It is
automatically kept in sync with the url, but preserved if the
asset is inlined or set to a value that ends with a slash.
If updated, the url of the asset will also be updated.
The extension includes the leading dot and is thus kept in the
same format as require('path').extname and the basename
command line utility use.
|
get extension() {
if ('_extension' in this) {
return this._extension;
} else {
return this.defaultExtension;
}
},
set extension(extension) {
this._extension = extension;
if (this.url) {
this.url = this.url.replace(/(?:\.\w+)?([?#]|$)/, this._extension + "$1");
}
},
|
asset.rawSrc (getter/setter)
Get or set the raw source of the asset.
If the internal state has been changed since the asset was
initialized, it will automatically be reserialized when this
property is retrieved, for example:
var htmlAsset = new assets.Html({
rawSrc: new Buffer('<html><body>Hello!</body></html>')
});
htmlAsset.parseTree.body.innerHTML = "Bye!";
htmlAsset.markDirty();
htmlAsset.rawSrc.toString(); // "<body>Bye!</body>"
Setting this property after the outgoing relations have been
accessed currently leads to undefined behavior.
|
get rawSrc() {
if (!this._rawSrc) {
throw new Error("Asset.rawSrc getter: Asset isn't loaded");
}
return this._rawSrc;
},
set rawSrc(rawSrc) {
this._rawSrc = rawSrc;
this.markDirty();
},
|
asset.url (getter/setter)
Get or set the absolute url of the asset (String).
The url will use the file: schema if loaded from disc. Will be
falsy for inline assets.
Setting the url causes the asset to emit the setUrl event
with the asset object, the new url, and the old url as
parameters, in that order. It will also update the extension
property if one can be extracted from the new url.
|
get url() {
return this._url;
},
set url(url) {
var oldUrl = this._url;
this._url = url;
this.emit('setUrl', this, url, oldUrl);
if (url && !urlEndsWithSlashRegExp.test(url)) {
var extension = path.extname(this.url.replace(/[?#].*$/, ''));
if (extension) {
this._extension = extension;
}
}
},
|
asset.isInline (getter)
Determine whether the asset is inline (shorthand for checking
whether it has a url).
|
get isInline() {
return !this.url;
},
|
asset.markDirty()
Sets the dirty flag of the asset, which is the way to say
that the asset has been manipulated since it was first loaded
(read from disc or loaded via http). For inline assets the flag
is set if the asset has been manipulated since it was last
synchronized with (copied into) its containing asset.
For assets that support a text or parseTree property, calling
markDirty() will invalidate any cached serializations of the
asset,
|
markDirty: function () {
this.isDirty = true;
delete this._rawSrc;
this.emit('dirty', this);
},
|
asset.outgoingRelations (getter)
Get the outgoing relations of the asset. Only supported by a
few subclasses (Css , Html , CacheManifest , and
JavaScript ), all others return an empty array.
|
get outgoingRelations() {
if (!this._outgoingRelations) {
this._outgoingRelations = [];
}
return this._outgoingRelations;
},
|
asset.toString()
Get a brief text containing the type, id, and url (if not inline) of the asset.
|
toString: function () {
return "[" + this.type + "/" + this.id + (this.url ? " " + this.url : "") + "]";
}
});
module.exports = Asset;
|
| lib/assets/Text.js |
class: Text extends: Asset
|
var util = require('util'),
_ = require('underscore'),
iconv = require('iconv'),
extendWithGettersAndSetters = require('../util/extendWithGettersAndSetters'),
Asset = require('./Asset');
|
new Text(options)
Create a new Text asset instance.
Adds text encoding and decoding support to Asset. Serves as a
superclass for Html , Xml , Css , JavaScript , CoffeeScript ,
Json , and CacheManifest .
In addition to the options already supported by the Asset base
class, these options are supported:
text (String) The decoded source of the asset. Can be used
instead of rawSrc and rawSrcProxy .encoding (String) Used to decode and reencode the rawSrc . Can
be any encoding supported by the iconv module. Can
be changed later using the encoding
getter/setter. Defaults to "utf-8" (see the docs for
defaultEncoding below).
Example
var textAsset = new Text({
// "æøå" in iso-8859-1:
rawSrc: new Buffer([0xe6, 0xf8, 0xe5]),
encoding: "iso-8859-1"
});
textAsset.text; // "æøå" (decoded JavaScript string)
textAsset.encoding = 'utf-8';
textAsset.rawSrc; // <Buffer c3 a6 c3 b8 c3 a5>
|
function Text(config) {
if ('text' in config) {
this._text = config.text;
delete config.text;
}
if (config.encoding) {
this._encoding = config.encoding;
delete config.encoding;
}
Asset.call(this, config);
}
util.inherits(Text, Asset);
extendWithGettersAndSetters(Text.prototype, {
|
text.isText
Property that's true for all Text instances. Avoids reliance on
the instanceof operator.
|
isText: true,
|
text.defaultEncoding
The default encoding for the Text (sub)class. Used for decoding
the raw source when the encoding cannot be determined by other
means, such as a Content-Type header (when the asset was
fetched via http), or another indicator specific to the given
asset type (@charset for Css, <meta
http-equiv="Content-Type" ...> for Html).
Factory setting is "utf-8", but you can override it by setting
Text.prototype.defaultEncoding to another value supported by
the iconv module.
|
defaultEncoding: 'utf-8',
defaultExtension: '.txt',
alternativeExtensions: ['.xtemplate'],
contentType: 'text/plain',
load: function (cb) {
if ('_text' in this || this._parseTree) {
process.nextTick(cb);
} else {
Asset.prototype.load.call(this, cb);
}
},
|
Text.encoding (getter/setter)
Get or set the encoding (charset) used for re-encoding the raw
source of the asset. To affect the initial decoding of the
rawSrc option, provide the encoding option to the
constructor.
|
get encoding() {
if (!this._encoding) {
this._encoding = this.defaultEncoding;
}
return this._encoding;
},
set encoding(encoding) {
if (encoding !== this.encoding) {
var text = this.text;
delete this._rawSrc;
this._encoding = encoding;
this.markDirty();
}
},
get rawSrc() {
if (!this._rawSrc) {
if ('_text' in this || this._parseTree) {
if (/^utf-?8$/i.test(this.encoding)) {
this._rawSrc = new Buffer(this.text);
} else {
this._rawSrc = new iconv.Iconv('utf-8', this.encoding).convert(this.text);
}
} else {
throw new Error("assets.Text.rawSrc getter: No _rawSrc or _text property found, asset not loaded?");
}
}
return this._rawSrc;
},
set rawSrc(rawSrc) {
this._rawSrc = rawSrc;
delete this._parseTree;
delete this._text;
this.markDirty();
},
|
text.text (getter/setter)
Get or set the decoded text contents of the of the asset as a
JavaScript string. Unlike browsers AssetGraph doesn't try to
sniff the charset of your text-based assets. It will fall back
to assuming utf-8 if it's unable to determine the
encoding/charset from HTTP headers, <meta
http-equiv='Content-Type'> tags (Html), @charset (Css), so
if for some reason you're not using utf-8 for all your
text-based assets, make sure to provide those hints. Other
asset types provide no standard way to specify the charset
within the file itself, so presently there's no way to load
eg. JavaScript from disc if it's not utf-8 or ASCII, except by
overriding Text.prototype.defaultEncoding globally.
If the internal state has been changed since the asset was
initialized, it will automatically be reserialized when the
text property is retrieved, for example:
var htmlAsset = new Html({
rawSrc: new Buffer("<body>hello</body>");
});
htmlAsset.text; // "<body>hello</body>"
htmlAsset.parseTree.body.innerHTML = "bye";
htmlAsset.markDirty();
htmlAsset.text; // "<body>bye</body>"
Setting this property after the outgoing relations have been
accessed currently leads to undefined behavior.
|
get text() {
if (!('_text' in this)) {
this._text = this._getTextFromRawSrc();
}
return this._text;
},
set text(text) {
this._text = text;
delete this._rawSrc;
delete this._parseTree;
this.markDirty();
},
markDirty: function () {
if (this._parseTree) {
delete this._text;
}
Asset.prototype.markDirty.call(this);
},
_getTextFromRawSrc: function () {
if (!this._rawSrc) {
throw new Error("assets.Text._getTextFromRawSrc(): No _rawSrc property found.");
}
if (/^utf-?8$/i.test(this.encoding)) {
return this._rawSrc.toString('utf-8');
} else {
return new iconv.Iconv(this.encoding, 'utf-8').convert(this._rawSrc).toString('utf-8');
}
},
_clone: function () {
return new this.constructor({
encoding: this.encoding,
text: this.text
});
}
});
module.exports = Text;
|
| lib/relations/Relation.js |
In graph terminology a relation represents a directed edge, a
reference from one asset to another. For the purpose of being able
to treat all relations equally, there's a subclass for each
supported relation type, encapsulating the details of how to
retrieve, update, and (optionally) inline the asset being pointed
to.
These are some examples of included subclasses:
relations.HtmlAnchor An anchor tag in an HTML document <a href='...'> .relations.HtmlImage An <img src='...'> tag in an HTML document.relations.CssImport An @import declaration in a CSS asset.relations.CacheManifestEntry A line in a cache manifest.
|
var _ = require('underscore'),
extendWithGettersAndSetters = require('../util/extendWithGettersAndSetters'),
urlTools = require('../util/urlTools'),
uniqueId = require('../util/uniqueId'),
query = require('../query');
|
new Relation(options)
Create a new Relation instance. For existing assets the
instantiation of relations happens automatically if you use the
populate transform. You only need to create relations manually
when you need to introduce new ones.
Note that the base Relation class should be considered
abstract. Please instantiate the appropriate subclass.
Options
from The source asset of the relation.to The target asset of the relation, or an asset configuration
object if the target asset hasn't yet been resolved and created.
|
function Relation(config) {
_.extend(this, config);
this.id = uniqueId();
}
Relation.prototype = {
|
relation.from (Asset)
The source asset of the relation.
|
|
relation.to (Asset or asset config object)
The target asset of the relation. If the relation hasn't yet
been resolved, it can also be a relative url string or an asset
configuration object.
|
|
relation.href (getter/setter)
Get or set the href of the relation. The relation must be
attached to an asset.
What is actually retrieved or updated depends on the relation
type. For HtmlImage the src attribute of the HTML element
is changed, for CssImport the parsed representation of
the @import rule is updated, etc.
Most of the time you don't need to think about this property,
as the href is automatically updated when the url of the source
or target asset is changed, or an intermediate asset is
inlined.
|
|
relation.isRelation (boolean)
Property that's true for all relation instances. Avoids
reliance on the instanceof operator.
|
isRelation: true,
|
relation.baseAssetQuery (Object)
Subclass-specific query object used for finding the base asset
for the relation (the asset whose url should be the basis for
resolving the href of the relation). This is usually the first
non-inline asset, but for some relation types it's the first
Html document.
You shouldn't need to worry about this.
|
baseAssetQuery: {isInline: false},
|
relation.detach()
Detaches the relation from the asset it is currently attached
to. You probably want to use
AssetGraph.detachAndRemoveRelation() instead.
|
detach: function () {
var indexInOutgoingRelations = this.from.outgoingRelations.indexOf(this);
if (indexInOutgoingRelations === -1) {
throw new Error("relations.Relation.detach: Relation " + this + " not found in the outgoingRelations array");
}
this.from.outgoingRelations.splice(indexInOutgoingRelations, 1);
this.from.markDirty();
},
|
relation.toString()
Get a brief text containing the type, id of the relation. Will
also contain the .toString() of the relation's source and
target assets if available.
|
toString: function () {
return "[" + this.type + "/" + this.id + ": " + ((this.from && this.to) ? this.from.toString() + " => " + this.to.toString() : "unattached") + "]";
}
};
module.exports = Relation;
|
| lib/AssetGraph.js |
class: AssetGraph extends: EventEmitter
|
var util = require('util'),
events = require('events'),
Path = require('path'),
_ = require('underscore'),
urlTools = require('./util/urlTools'),
passError = require('./util/passError');
|
new AssetGraph([options])
Create a new AssetGraph instance.
Options
root (optional) The root URL of the graph, either as a fully
qualified file: or http: url or file system
path. Defaults to the current directory,
ie. file://<process.cwd()>/ . The purpose of the root
option is to allow resolution of root-relative urls
(eg. <a href="/foo.html"> ) from file: locations.
Examples
new AssetGraph()
// => root: "file:///current/working/dir/"
new AssetGraph({root: '/absolute/fs/path'});
// => root: "file:///absolute/fs/path/"
new AssetGraph({root: 'relative/path'})
// => root: "file:///current/working/dir/relative/path/"
param: Object options api: public
|
function AssetGraph(options) {
if (!(this instanceof AssetGraph)) {
return new AssetGraph(options);
}
events.EventEmitter.call(this);
_.extend(this, options);
if (!('root' in this)) {
this.root = urlTools.fsDirToFileUrl(process.cwd());
} else if (!/^[a-z0-9]+:/.test(this.root)) {
this.root = urlTools.fsDirToFileUrl(this.root);
} else {
this.root = urlTools.ensureTrailingSlash(this.root);
}
this._assets = [];
this._relations = [];
this._baseAssetPathForRelation = {};
this._objInBaseAssetPaths = {};
this._relationsWithNoBaseAsset = [];
this._indices = {};
this.urlIndex = {};
this.idIndex = {};
this.resolverByProtocol = {
data: AssetGraph.resolvers.data(),
file: AssetGraph.resolvers.file(),
http: AssetGraph.resolvers.http(),
https: AssetGraph.resolvers.http()
};
_.each(AssetGraph.query.indices, function (indexNames, indexType) {
this._indices[indexType] = {};
indexNames.forEach(function (indexName) {
this._indices[indexType][indexName] = {};
}, this);
}, this);
};
util.inherits(AssetGraph, events.EventEmitter);
AssetGraph.assets = require('./assets');
AssetGraph.relations = require('./relations');
AssetGraph.transforms = require('./transforms');
AssetGraph.query = require('./query');
AssetGraph.resolvers = require('./resolvers');
_.extend(AssetGraph.prototype, {
|
assetGraph.root
The absolute root url of the graph, always includes a trailing
slash. A normalized version of the root option provided to
the constructor.
|
|
assetGraph.addAsset(asset)
Add an asset to the graph.
|
addAsset: function (asset) {
if (!asset || !asset.id || !asset.isAsset) {
throw new Error("AssetGraph.addAsset: " + asset + " is not an asset");
}
if (asset.id in this.idIndex) {
throw new Error("AssetGraph.addAsset: " + asset + " already in graph");
}
if (asset.url && asset.url in this.urlIndex) {
throw new Error("AssetGraph.addAsset: " + asset.url + " already loaded");
}
this._assets.push(asset);
this._addToIndices(asset);
this._objInBaseAssetPaths[asset.id] = [];
var bindAndMark = function (fn, scope) {
var bound = fn.bind(scope);
bound._belongsToAssetGraph = this;
return bound;
}.bind(this);
asset
.on('setUrl', bindAndMark(this._updateUrlIndex, this))
.on('serialize', bindAndMark(this._refreshInlineRelations, this))
.on('dirty', bindAndMark(this._markContainingAssetDirtyIfInline, this));
this.emit('addAsset', asset);
},
|
assetGraph.removeAsset(asset[, detachIncomingRelations])
Remove an asset from the graph. Also removes the incoming and
outgoing relations of the asset.
|
removeAsset: function (asset, detachIncomingRelations) {
if (!(asset.id in this.idIndex)) {
throw new Error("AssetGraph.removeAsset: " + asset + " not in graph");
}
['setUrl', 'serialize', 'dirty'].forEach(function (eventName) {
var listeners = asset.listeners(eventName);
for (var i = 0 ; i < listeners.length ; i += 1) {
if (listeners[i]._belongsToAssetGraph === this) {
listeners.splice(i, 1);
i -= 1;
}
}
}, this);
var incomingRelations = this.findRelations({to: asset});
this.findRelations({from: asset}, true).forEach(function (outgoingRelation) {
this.removeRelation(outgoingRelation);
if (outgoingRelation.to.isAsset && outgoingRelation.to.isInline) {
this.removeAsset(outgoingRelation.to);
}
}, this);
if (incomingRelations.length) {
incomingRelations.forEach(function (incomingRelation) {
if (detachIncomingRelations) {
this.detachAndRemoveRelation(incomingRelation);
} else {
this.removeRelation(incomingRelation);
}
}, this);
}
var affectedRelations = [].concat(this._objInBaseAssetPaths[asset.id]);
affectedRelations.forEach(function (affectedRelation) {
this._unregisterBaseAssetPath(affectedRelation);
}, this);
delete this._objInBaseAssetPaths[asset.id];
var assetIndex = this._assets.indexOf(asset);
if (assetIndex === -1) {
throw new Error("removeAsset: " + asset + " not in graph");
} else {
this._assets.splice(assetIndex, 1);
}
this._removeFromIndices(asset);
affectedRelations.forEach(function (affectedRelation) {
this._registerBaseAssetPath(affectedRelation);
}, this);
this.emit('removeAsset', asset);
},
|
assetGraph.removeRelation(relation)
Remove a relation from the graph. Leaves the relation attached
to the source asset (compare with the detachAndRemoveRelation
method).
|
removeRelation: function (relation) {
if (!relation || !relation.isRelation) {
throw new Error("AssetGraph.removeRelation: Not a relation: ", relation);
}
if (!(relation.id in this.idIndex)) {
throw new Error("AssetGraph.removeRelation: " + relation + " not in graph");
}
var affectedRelations = [].concat(this._objInBaseAssetPaths[relation.id]);
affectedRelations.forEach(function (affectedRelation) {
this._unregisterBaseAssetPath(affectedRelation);
}, this);
this._unregisterBaseAssetPath(relation);
this._removeFromIndices(relation);
var relationIndex = this._relations.indexOf(relation);
if (relationIndex === -1) {
throw new Error("removeRelation: " + relation + " not in graph");
} else {
this._relations.splice(relationIndex, 1);
}
delete this._objInBaseAssetPaths[relation.id];
affectedRelations.forEach(function (affectedRelation) {
this._registerBaseAssetPath(affectedRelation);
}, this);
this.emit('removeRelation', relation);
},
|
assetGraph.detachAndRemoveRelation(relation)
Detach a relation from its source asset, then remove it from the graph.
|
detachAndRemoveRelation: function (relation) {
if (!relation || !relation.isRelation) {
throw new Error("AssetGraph.detachAndRemoveRelation: Not a relation: ", relation);
}
relation.detach(relation);
relation.from.markDirty();
this.removeRelation(relation);
},
|
assetGraph.findAssets([queryObj])
Query assets in the graph.
Example usage:
var allAssetsInGraph = ag.findAssets();
var htmlAssets = ag.findAssets({type: 'Html'});
var localImageAssets = ag.findAssets({
url: /^file:.*\.(?:png|gif|jpg)$/
});
var orphanedJavaScriptAssets = ag.findAssets(function (asset) {
return asset.type === 'JavaScript' &&
ag.findRelations({to: asset}).length === 0;
});
var textBasedAssetsOnGoogleCom = ag.findAssets({
isText: true,
url: /^https?:\/\/(?:www\.)google\.com\//
});
|
findAssets: function (queryObj) {
return AssetGraph.query.queryAssetGraph(this, 'asset', queryObj);
},
|
assetGraph.findRelations([queryObj[, includeUnpopulated]])
Query relations in the graph.
Example usage:
var allRelationsInGraph = ag.findRelations();
var allHtmlScriptRelations = ag.findRelations({
type: 'HtmlScript'
});
var htmlAnchorsPointingAtLocalImages = ag.findRelations({
type: 'HtmlAnchor',
to: {isImage: true, url: /^file:/}
});
param: Object queryObj (optional). Will match all relations if not provided. param: Boolean includeUnpopulated (optional). Whether to also consider relations that weren't followed during population. Defaults to false. return: Array The found relations. api: public
|
findRelations: function (queryObj, includeUnpopulated) {
var relations = AssetGraph.query.queryAssetGraph(this, 'relation', queryObj);
if (includeUnpopulated) {
return relations;
} else {
return relations.filter(function (relation) {
return relation.to.isAsset;
});
}
},
|
assetGraph.inlineRelation(relation)
Inline a relation. This is only supported by certain relation
types and will produce different results depending on the type
(data: url, inline script, inline stylesheet...).
Will make a clone of the target asset if it has more incoming
relations than the one being inlined.
|
inlineRelation: function (relation) {
if (!relation || !relation.isRelation) {
throw new Error("AssetGraph.inlineRelation: Not a relation: ", relation);
}
var relationsToAssetToBeInlined = this.findRelations({to: relation.to});
if (relationsToAssetToBeInlined.length !== 1) {
this.cloneAsset(relation.to, [relation]);
}
if (!relation.to.isInline) {
var affectedRelations = [].concat(this._objInBaseAssetPaths[relation.to.id]);
affectedRelations.forEach(function (affectedRelation) {
this._unregisterBaseAssetPath(affectedRelation);
}, this);
relation.to.url = null;
affectedRelations.forEach(function (affectedRelation) {
this._registerBaseAssetPath(affectedRelation);
this.refreshRelationHref(affectedRelation);
}, this);
}
this._refreshInlineRelations(relation.to);
relation._inline();
relation.from.markDirty();
},
|
assetGraph.refreshRelationHref(relation)
Update href of a relation to make sure it points at the
current url of its target asset.
It's not necessary to call this function manually as long as
the source and target assets of the relation have only been
moved by having their url property changed (the recommended
way), but some transforms will need this after some low-level
surgery, such as attaching an existing relation to a different
asset.
|
refreshRelationHref: function (relation) {
if (!relation || !relation.isRelation || !relation.to) {
throw new Error('AssetGraph.refreshRelationHref: Not a relation: ', relation);
}
if (!relation.to.isInline) {
var relativeUrl = urlTools.buildRelativeUrl(this.getBaseAssetForRelation(relation).url, relation.to.url);
if (relation.href !== relativeUrl) {
relation.href = relativeUrl;
relation.from.markDirty();
}
}
},
|
assetGraph.clone()
Clone the AssetGraph instance. The set of assets and relations
won't be cloned, they will be shared between this the original
and cloned graph, so changing urls and hrefs or attaching and
detaching of relations will affect both graphs.
|
clone: function () {
var clone = new AssetGraph({root: this.root});
this._assets.forEach(function (asset) {
clone.addAsset(asset);
});
this._relations.forEach(function (relation) {
clone.addRelation(relation, 'last');
});
return clone;
},
|
assetGraph.updateRelationTarget(relation, newTargetAsset)
Point a relation at a different asset. Saves you from having to
remove the relation, update its to property, then add it
again at the right position.
|
updateRelationTarget: function (relation, newTargetAsset) {
if (!relation || !relation.isRelation) {
throw new Error("AssetGraph.updateRelationTarget: Not a relation: ", relation);
}
if (!newTargetAsset || !newTargetAsset.isAsset) {
throw new Error("AssetGraph.updateRelationTarget: Target is not an asset: ", newTargetAsset);
}
var oldGlobalPosition = this._relations.indexOf(relation),
oldTypeIndexPosition = this._indices.relation.type[relation.type].indexOf(relation),
oldFromIndexPosition = this._indices.relation.from[relation.from.id].indexOf(relation);
this.removeRelation(relation);
relation.to = newTargetAsset;
this.addRelation(relation);
this._indices.relation.from[relation.from.id].splice(this._indices.relation.from[relation.from.id].indexOf(relation), 1);
this._indices.relation.from[relation.from.id].splice(oldFromIndexPosition, 0, relation);
this._indices.relation.type[relation.type].splice(this._indices.relation.type[relation.type].indexOf(relation), 1);
this._indices.relation.type[relation.type].splice(oldTypeIndexPosition, 0, relation);
this._relations.splice(this._relations.indexOf(relation), 1);
this._relations.splice(oldGlobalPosition, 0, relation);
this.refreshRelationHref(relation);
},
|
assetGraph.replaceAsset(oldAsset, newAsset)
Replace an asset in the graph with another asset, then remove
the old asset from the graph.
Updates the incoming relations of the old asset to point at the
new one and preserves the url of the old asset if it's not
inline.
|
replaceAsset: function (oldAsset, newAsset) {
if (!oldAsset || !oldAsset.isAsset) {
throw new Error("AssetGraph.replaceAsset: oldAsset is not an asset: ", oldAsset);
}
if (!newAsset || !newAsset.isAsset) {
throw new Error("AssetGraph.replaceAsset: newAsset is not an asset: ", newAsset);
}
if (!(oldAsset.id in this.idIndex)) {
throw new Error("AssetGraph.replaceAsset: Old asset isn't in the graph: " + oldAsset);
}
if (newAsset.id in this.idIndex) {
throw new Error("AssetGraph.replaceAsset: New asset is already in the graph: " + newAsset);
}
this.addAsset(newAsset);
this.findRelations({to: oldAsset}).forEach(function (incomingRelation) {
this.updateRelationTarget(incomingRelation, newAsset);
}, this);
this.removeAsset(oldAsset);
if (oldAsset.url) {
newAsset.url = oldAsset.url;
}
},
|
assetGraph.cloneAsset(asset[, incomingRelations])
Clone an asset and add the clone to the graph. As an extra
service, optionally update some caller-specified relations to
point at the clone.
Makes up an url for the clone if the original asset wasn't
inline.
param: Asset asset The asset to clone. param: Array incomingRelations (optional) Some incoming relations that should be pointed at the clone. return: Asset The cloned asset. api: public
|
cloneAsset: function (asset, incomingRelations) {
if (!asset || !asset.isAsset) {
throw new Error("AssetGraph.cloneAsset: Not an asset: ", asset);
}
this._refreshInlineRelations(asset);
var clone = asset._clone();
if (!asset.isInline) {
clone.url = urlTools.resolveUrl(asset.url, clone.id + asset.extension);
}
if (asset.isInitial) {
clone.isInitial = true;
}
this.addAsset(clone);
if (incomingRelations) {
incomingRelations.forEach(function (incomingRelation) {
if (!incomingRelation || !incomingRelation.isRelation) {
throw new Error("AssetGraph.cloneAsset: Incoming relation is not a relation: ", incomingRelation);
}
if (!(incomingRelation.id in this.idIndex)) {
throw new Error("AssetGraph.cloneAsset: Incoming relation is not in the graph: ", incomingRelation);
}
this.updateRelationTarget(incomingRelation, clone);
}, this);
}
this.populateRelationsToExistingAssets(clone);
return clone;
},
|
assetGraph.populateRelationsToExistingAssets(asset)
Go through the unresolved outgoing relations of an asset and
add the ones that refer to assets that are already part of
the graph. Recurses into inline assets.
You shouldn't need to worry about this. It's mostly useful as a
helper for the cloneAsset method, but also used by the
compressJavaScript transform.
|
populateRelationsToExistingAssets: function (asset) {
if (!asset || !asset.isAsset) {
throw new Error("AssetGraph.populateRelationsToExistingAssets: Not an asset: ", asset);
}
asset.outgoingRelations.forEach(function (outgoingRelation) {
var relativeUrl;
if (outgoingRelation.to.url || typeof outgoingRelation.to === 'string') {
var targetUrl = urlTools.resolveUrl(this.getBaseAssetForRelation(outgoingRelation).url, outgoingRelation.to.url || outgoingRelation.to);
if (targetUrl in this.urlIndex) {
outgoingRelation.to = this.urlIndex[targetUrl];
this.addRelation(outgoingRelation);
}
} else {
outgoingRelation.to = AssetGraph.assets.create(outgoingRelation.to);
this.addAsset(outgoingRelation.to);
this.addRelation(outgoingRelation);
this.populateRelationsToExistingAssets(outgoingRelation.to);
}
}, this);
},
|
assetGraph.getBaseAssetForRelation(relation)
Find the asset from which the url of a given relation is to be
resolved. This is usually the first non-inline containing
asset, but for some relation types it's the first Html ancestor
-- infamously CssAlphaImageLoader and CssBehavior , but also
JavaScriptOneGetStaticUrl .
The relation doesn't have to be in the graph, so this can be used
during population of the graph.
|
getBaseAssetForRelation: function (relation) {
if (!relation || !relation.isRelation) {
throw new Error("AssetGraph.getBaseAssetForRelation: Not a relation: ", relation);
}
if (!(relation.from.id in this.idIndex)) {
throw new Error("AssetGraph.getBaseAssetForRelation: The 'from' asset of the relation is not in the graph: ", relation.from);
}
if (relation.id in this.idIndex) {
return this._baseAssetPathForRelation[relation.id][0];
} else {
return this._findBaseAssetPathForRelation(relation)[0];
}
},
|
assetGraph.recomputeBaseAssets()
Recompute the base asset paths for all relations for which the base asset
path couldn't be computed due to the graph being incomplete at the time
they were added.
Usually you shouldn't have to worry about this. This method is
only exposed for transforms that do certain manipulations
causing to graph to temporarily be in a state where the base
asset of some relations couldn't be computed, e.g. if
intermediate relations are been removed and attached again.
Will throw an error if the base asset for any relation couldn't be found.
- api: public
|
recomputeBaseAssets: function () {
[].concat(this._relationsWithNoBaseAsset).forEach(function (relation) {
this._unregisterBaseAssetPath(relation);
if (!this._registerBaseAssetPath(relation)) {
throw new Error("recomputeBaseAssets: Couldn't find base asset for " + relation);
}
}, this);
},
|
assetGraph._refreshInlineRelations(asset)
Prepare an asset for serialization by re-inlining its inline assets that have been
marked dirty.
|
_refreshInlineRelations: function (asset) {
if (!asset || !asset.isAsset) {
throw new Error("AssetGraph._refreshInlineRelations: Not an asset: ", asset);
}
this.findRelations({from: asset, to: {isInline: true}}).forEach(function (relation) {
this._refreshInlineRelations(relation.to);
if (relation.to.isDirty) {
relation.to.isDirty = false;
if (relation._inline) {
relation._inline();
relation.from.markDirty();
}
}
}, this);
},
|