npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ferguson

v0.6.0

Published

An asset manager

Downloads

9

Readme

Ferguson


Ferguson is a highly-configurable asset manager for node.js with the following features

  • Framework-agnostic compilation and compression
  • Hot-reloading
  • Cache-busting
  • Asset bundles
  • Inline assets

Overview

Install the library with npm

$ npm install ferguson

Setup a ferguson instance and bind it to an express application

var ferguson = require('ferguson');

var assetManager = ferguson('/path/to/assets', options);

assetManager.bind(app);

Your templates now have access to a view helper

{{ asset('favicon.ico') }}
<!--[if lt IE 9]>
  {{ asset('js/ie8.js', { include: ['js/html5shiv.js', 'js/respond.js'] });
<![endif]-->
{{ asset('js/smallFile.js', { inline: true }) }}
{{ asset('css/styles.less') }}

which generates something like

<link rel="shortcut icon" href="/asset-a2029888991a8a83-favicon.ico" />
<!--[if lt IE 9]>
  <script src="/js/asset-0ba08226c3bd0e46-ie8.js" type="text/javascript"></script>
<![endif]-->
<script type="text/javascript">var foo = "bar";</script>
<link rel="stylesheet" href="/css/asset-b5d5d67465f661c1-styles.css" />

Assets are compiled and compressed on demand. Compiled asset filenames only change when one of the included assets is modified, i.e. they're safe to cache forever.

Adding your own formats is easy

var stylus = require('stylus');
assetManager.registerCompiler('.styl', '.css', function (path, buffer, options, callback) {
    stylus.render(buffer.toString(), callback);
});

Now you can call asset('css/foo.styl') to render an asset with stylus.

Options

The following options are available when creating a new ferguson instance with ferguson(dir, options)

  • hash (default: md5) - the hashing algorithm to use.
  • hashLength (default: 16) - the maximum length of the hash in the filenames of compiled assets.
  • viewHelper (default: asset) - the name of the view helper.
  • servePrefix (default: /) - the path to serve assets from.
  • urlPrefix (default: null) - prefix asset URLs with this.
  • maxAge (default: 2419200000 - 4 weeks) - the Cache-Control max-age value (in milliseconds).
  • compress (default: false) - whether to compress assets.
  • hotReload (default: false) - whether to enable hot-reloading of assets.
  • wrapJavascript (default: false) - whether to wrap compiled JS in an IIFE.
  • separateBundles (default: false) - generate separate tags for each asset in a bundle.
  • html5 (default: false) - generate HTML5-compatible tags, e.g. omit the type attribute from a <script> tag.

The following setup is recommended

var production = process.env.NODE_ENV === 'production';
var assetManager = ferguson('/path/to/assets', {
    compress: production
  , hotReload: !production
  , separateBundles: !production
});

Asset Definitions

The following options are available when defining an asset with asset(file, options)

  • include - one or more files that make up the asset bundle. Glob is supported.
  • urlPrefix - prefix the asset URL with this. This overrides the library's urlPrefix option.
  • attributes - an object containing additional HTML attributes.
  • dependencies - one or more files that ferguson should take into account when generating cache-busting hashes (see the Compilers section below for an explanation). Glob is supported.
  • inline - whether to inline the asset instead of referencing an external resource.

Here's an example definition

{{ asset('foo.jpg', { attributes: { alt: 'Foo & bar' }, urlPrefix: 'http://example.com' }) }}

which generates

<img src="http://example.com/asset-d3b07384d113edec-foo.jpg" alt="Foo &amp; bar" />

There are two variations of the view helper: one to output the asset's path and another to output the asset's full URL (if you've provided a urlPrefix)

<script type="text/javascript" src="{{ asset.url('foo.js') }}"></script>
<script type="text/javascript" src="//example.com{{ asset.path('foo.js') }}"></script>

It's also possible to define assets when you create a ferguson instance

var assetManager = ferguson('/path/to/assets');

assetManager.asset('ie8.js', { include: ['html5shiv.js', 'respond.js'] });

Once defined, assets can be referenced by name

{{ asset('ie8.js') }}

Caching

Ferguson generates a cache-busting hash based on the contents of each included asset. This means that the compiled assets are safe to cache indefinitely. Any modifications to your assets will cause the hash (and filename) to change, forcing clients to re-download the asset. The library will automatically cleanup old compiled assets.

Ferguson will write the compiled assets to the static assets directory that you specify. This allows you to serve up raw and compiled assets from the same place. For example, it allows nginx users to use a try_files to serve up all assets directly.

Ferguson keeps an index (filename, hash, mtime) of each file in your static assets directory. The index is persisted across restarts using a manifest file, .asset-manifest. This reduces the amount of hashing required; assets are only hashed when a modification or new file is detected.

If your static assets live in a folder named static then you might want to add the following two lines to your .gitignore

static/.asset-manifest
static/**/asset-*

For assets that don't change very often, e.g. favicons, you can omit the cache busting hash entirely by building your own asset URL

<link rel="shortcut icon" href="{{ asset.prefix }}favicon.ico" />

Compilers

Ferguson does not ship with any compilers since it's trivial to add your own

var less = require('less');
assetManager.registerCompiler('.less', '.css', function (path, buffer, options, callback) {
    less.render(buffer.toString(), callback);
});

Since ferguson is framework-agnostic, there are some cases where you will need to manually specify an asset's dependencies. For example, less allows you to @import a file and browserify allows you to require() a file. In order to generate a correct cache-busting hash, ferguson needs to know about these imports, since the hash is based on the contents of the final compiled asset.

Let's say you have a file called style.less which @import's a variables.less. You'll need to define your asset like so

{{ asset('style.less', { dependencies: ['variables.less'] }) }}

Ferguson will generate the cache-busting hash based on the contents of both style.less and variables.less. Without specifying the dependency, only updates to style.less would cause a new hash to be generated.

A lazy way to specify dependencies is to use a glob pattern that matches everything

{{ asset('style.less', { dependencies: '**/*.less' }) }}

You can also define synchronous compressors by omitting the callback

var marked = require('marked');
assetManager.registerCompiler('.md', '.html', function (path, buffer, options) {
    return marked(buffer.toString());
});

Compressors

Ferguson ships with a JS minifier (uglifyjs) and CSS compressor (clean-css) which are both enabled when the compress option is true.

You can add additional compressors or override an existing one

var yui = require('yuicompressor');
assetManager.registerCompressor('.js', function (buffer, options, callback) {
    yui.compress(buffer.toString(), options, callback);
});

Just like compiler definitions, you can define a synchronous compressor by omitting the callback.

Inline assets

You can generate inline assets using {{ asset(yourAsset, { inline: true }) }}.

Ferguson will wrap Javascript in a <script> tag, wrap CSS in a <style> tag and base64-encode and inline images into an <img> tag. It will output all other assets as-is.

You can add or override an inline formatter

assetManager.registerInlineFormat('.html', function (filename, buffer, options, attributes) {
    return util.format('<script type="text/x-template">%s<script>', buffer);
})

Since the inline formatters have access to the binary contents of the asset, the following is possible

var mime = require('mime');
assetManager.registerInlineFormat(imageExt, function (filename, buffer, options, attributes) {
    return util.format('<img src="data:%s;base64,%s" />',
        mime.lookup(filename),
        buffer.toString('base64'));
});

The following call

{{ asset('1x1.gif', { inline: true }) }}

would output the following

<img src="data:image/gif;base64,R0lGODlhAQABAIABAP8AAP///yH5BAEAAAEALAAAAAABAAEAAAICRAEAOw==" />

Since asset inlining happens synchronously, you can only use the inline option with synchronous compilers and compressors. Attempting to inline an asset that uses either an asynchronous compiler or compressor will fail with an error.

Tag Formats

Ferguson knows how to generate HTML tags for css, js, ico, jpg, gif, png, svg and bmp assets.

You can add and override tag formats if necessary

assetManager.registerTagFormat('.js', function (url, options, attributes) {
    return util.format('<custom-tag src="%s" />', url);
})

Alternatively, you could use the asset.url() view helper to output the asset URL directly

<custom-tag src="{{ asset.url('foo.js') }}" />

Events

The ferguson instance will emit the following events

assetManager.on('error', function (err) {
	// an error occurred
})

assetManager.on('delete', function (file) {
    // a stale version of an asset was removed
});

assetManager.on('change', function (file) {
    // a change was detected in the specified file
});

Multi-process Environments

Ferguson doesn't know how to serve up compiled assets until they are defined. If you plan on using the cluster module, there's a chance that only one process could render a template which contains the asset definition. If another process in the cluster gets a request for the compiled asset and a cached copy doesn't exist, then it won't know what to do since only the process that defines the asset knows how to compile it.

The solution is to define your assets when you initialise the library. That way, all processes in the cluster will see the definition

var assetManager = ferguson('/path/to/assets');

assetManager.asset('ie8.js', { include: ['html5shiv.js', 'respond.js'] });

Developers

To debug all aspects of the library, run the process with the DEBUG env set

$ node yourapp.js DEBUG=ferguson

The test suite can be run with

$ make test

To increase verbosity use V=1 make test. To run a subset of tests use TEST=<pattern> make test

To run coverage analysis and view a HTML report

$ make coverage-html

To run the lint tool

$ make lint

Naming

The name (and logo) is derived from one of the worlds most successful football managers, Sir Alex Ferguson.

License (MIT)

Copyright (c) 2013 Sydney Stockholm [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.