AssetGraph is an extensible, node.js-based framework for manipulating and optimizing web pages and web applications. The main core is a dependency graph model of your entire website, where all assets are treated as first class citizens. It can automatically dicsover assets based on your declarative code, reducing the configuration needs to a minimum.
If you just want to get started with the basics, read Peter Müller - Getting started with Assetgraph.
If you are looking for a prepackaged build system take a look at Assetgraph-builder.
All web build tools, even those that target very specific problems, have to get a bunch of boring stuff right just to get started, such as loading files from disc, parsing and serializing them, charsets, inlining, finding references to other files, resolution of and updating urls, etc.
The observation that inspired the project is that most of these tasks can be viewed as graph problems, where the nodes are the assets (HTML, CSS, images, JavaScript...) and the edges are the relations between them, e.g. anchor tags, image tags, favorite icons, css background-image properties and so on.

An AssetGraph object is a collection of assets (nodes) and the relations (edges) between them. It's a basic data model that allows you to populate, query, and manipulate the graph at a high level of abstraction. For instance, if you change the url of an asset, all relations pointing at it are automatically updated.
Additionally, each individual asset can be inspected and massaged using a relevant API: jsdom for HTML, PostCSS for CSS, and an Esprima AST for Javascript.
AssetGraph represents inline assets the same way as non-inline ones,
so eg. inline scripts, stylesheets, and images specified as data:
urls are also first-class nodes in the graph. This means that you
don't need to dig into the HTML of the containing asset to manipulate
them. An extreme example would be an Html asset with a conditional
comment with an inline stylesheet with an inline image, which are
modelled as 4 separate assets:
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body {
background-image: url(data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==);
}
</style>
</head>
<body></body>
</html>
These are some of the supported assets and associated relation types:
<a>, <link rel="stylesheet|shortcut icon|fluid-icon|alternate|serviceworker">, <script>, <style>,
<html manifest="..."> <img>, <video>, <audio>, <applet>,
<embed>, <esi:include>, <iframe>, <svg>, <meta property="og:...">
<style>, inline style=... attributes, event handlers, <?xml-stylesheet href=...>, <font-face-src>
//# sourceMappingURL=..., background-image: url(...), @import url(...), behavior: url(...),
filter: AlphaImageLoader(src='...'), @font-face { src: url(...) }
//# sourceMappingURL=..., homegrown 'foo/bar.png'.toString('url') syntax for referencing external files
Icon urls, related_applications, start_url, etc.
Entries in the CACHE, NETWORK and FALLBACK sections
(none)
<script> and <link rel='stylesheet'> tags in your
HTML.-ag-sprite prefix.Make sure you have node.js and npm installed, then run:
$ npm install assetgraph
AssetGraph supports a flexible syntax for finding assets and relations
in a populated graph using the findAssets and findRelations
methods. Both methods take a query object as the first argument.
The query engine uses MongoDB-like queries via the
sift module. Please consult that
to learn about the advanced querying features. Below are some basic examples.
Get an array containing all assets in the graph:
var allAssets = assetGraph.findAssets();
Find assets by type:
var htmlAssets = assetGraph.findAssets({ type: 'Html' });
Find assets of different named types:
var jsAndCss = assetGraph.findAssets({ type: { $in: ['Css', 'JavaScript' ] });
Find assets by matching a regular expression against the url:
var localImageAssets = assetGraph.findAssets({
url: { $regex: /^file:.*\.(?:png|gif|jpg)$/ },
});
Find assets by predicate function:
var orphanedJavaScriptAssets = assetGraph.findAssets(function (asset) {
return (
asset.type === 'JavaScript' &&
assetGraph.findRelations({ to: asset }).length === 0
);
});
Find all HtmlScript (<script src=...> and inline <script>) relations:
var allHtmlScriptRelations = assetGraph.findRelations({ type: 'HtmlScript' });
Query objects have "and" semantics, so all conditions must be met for a multi-criteria query to match:
var textBasedAssetsOnGoogleCom = assetGraph.findAssets({
isText: true,
url: { $regex: /^https?:\/\/(?:www\.)google\.com\// },
});
Find assets by existence of incoming relations:
var importedCssAssets = assetGraph.findAssets({
type: 'Css',
incomingRelations: { $elemMatch: { type: 'CssImport' } },
});
Relation queries can contain nested asset queries when querying the
to and from properties.
Find all HtmlAnchor (<a href=...>) relations pointing at local images:
assetGraph.findRelations({
type: 'HtmlAnchor',
to: { isImage: true, url: { $regex: /^file:/ } },
});
AssetGraph comes with a collection of premade "transforms" that you can use as high level building blocks when putting together your build procedure. Most transforms work on a set of assets or relations and usually accept a query object so they can be scoped to work on only a specific subset of the graph.
Usually you'll start by loading some initial assets from disc or via
http using the loadAssets transform, then get the related assets
added using the populate transform, then do the actual
processing. Eventually you'll probably write the resulting assets back
to disc.
Thus the skeleton looks something like this:
var AssetGraph = require('assetgraph');
const assetGraph = new AssetGraph({ root: '/the/root/directory/' });
await assetGraph.loadAssets('*.html'); // Load all Html assets in the root dir
await assetGraph.populate({ followRelations: { type: 'HtmlAnchor' } }); // Follow <a href=...>
// More work...
await assetGraph.writeAssetsToDisc({ type: 'Html' }); // Overwrite existing files
// Done!
In the following sections the built-in transforms are documented individually:
Add a CacheManifest asset to each Html asset in the graph (or
to all Html assets matched by queryObj if provided). The cache
manifests will contain relations to all assets reachable by traversing
the graph through relations other than HtmlAnchor.
Bundle the Css and JavaScript assets pointed to by the
relations matched by queryObj.
The strategyName (string) parameter can be either:
oneBundlePerIncludingAsset (the default)Each unique asset pointing to one or more of the assets being
bundled will get its own bundle. This can lead to duplication if
eg. several Html assets point to the same sets of assets, but
guarantees that the number of http requests is kept low.
sharedBundlesCreate as many bundles as needed, optimizing for combined byte size
of the bundles rather than http requests. Warning: Not as well
tested as oneBundlePerIncludingAsset.
Note that a conditional comment within an Html asset conveniently
counts as a separate including asset, so in the below example
ie.css and all.css won't be bundled together:
<link rel="stylesheet" href="https://github.com/assetgraph/assetgraph/raw/v7.13.0/all.css" />
The created bundles will be placed at the root of the asset graph with
names derived from their unique id (for example
file://root/of/graph/124.css) and will replace the original
assets.
Compresses all JavaScript assets in the graph (or those specified by
queryObj).
The compressorName (string) parameter can be either:
uglifyJs (the default and the fastest)The excellent UglifyJS
compressor. If provided, the compressorOptions object will be
passed to UglifyJS' ast_squeeze command.
yuicompressorYahoo's YUICompressor though Tim-Smart's node-yuicompressor
module. If provided, the compressorOptions object will be
passed as the second argument to require('yui-compressor').compile.
closurecompilerGoogle's Closure Compiler through Tim-Smart's node-closure
module. If provided, the compressorOptions object will be
passed as the second argument to
require('closure-compiler').compile.
Finds all Html assets in the graph (or those specified by
queryObj), finds all CssImport relations (@import url(...)) in inline and external CSS and converts them to
HtmlStyle relations directly from the Html document.
Effectively the inverse of assetGraph.convertHtmlStylesToInlineCssImports.
Example:
<style type="text/css">
@import url(print.css) print;
@import url(foo.css);
body {
color: red;
}
</style>
is turned into:
<link rel="stylesheet" href="https://github.com/assetgraph/assetgraph/raw/v7.13.0/print.css" media="print" />
<link rel="stylesheet" href="https://github.com/assetgraph/assetgraph/raw/v7.13.0/foo.css" />
<style type="text/css">
body {
color: red;
}
</style>
Finds all Html assets in the graph (or those specified by
queryObj), finds all outgoing, non-inline HtmlStyle relations
(<link rel='stylesheet' href='...'>) and turns them into groups of
CssImport relations (@import url(...)) in inline
stylesheets. A maximum of 31 CssImports will be created per inline
stylesheet.
Example:
<link rel="stylesheet" href="https://github.com/assetgraph/assetgraph/raw/v7.13.0/foo.css" />
<link rel="stylesheet" href="https://github.com/assetgraph/assetgraph/raw/v7.13.0/bar.css" />
is turned into:
<style type="text/css">
@import url(foo.css);
@import url(bar.css);
</style>
This is a workaround for the limit of 31 stylesheets in Internet Explorer <= 8. This transform allows you to have up to 31*31 stylesheets in the development version of your HTML and still have it work in older Internet Explorer versions.
Uses the Graphviz dot command to render the current contents of the
graph and writes the result to fileName. The image format is
automatically derived from the extension and can be any of these. Using
.svg is recommended.
Requires Graph
$ claude mcp add assetgraph \
-- python -m otcore.mcp_server <graph>