asset-manager

Asset manager built on top of connect-asset for managing multiple asset folders.


Keywords
core, deprecated
Install
npm install asset-manager@0.3.8

Documentation

asset-manager

Static Asset Manager that allows you to declare multiple asset folders that will be searched when resolving static assets in your app. This library also provides the ability to precompile all of the static assets into their production form (e.g., minified content with hashed filenames). The precompile step generates a manifest file that will be used in production to resolve requested assets. It also generates a clientManifest that can be in the browser to dynamically load static assets (e.g., people using the Inject dependency management library - https://github.com/linkedin/inject)

build status

Node Versions Supported:

0.10.x through 0.12.x

How?

First, install it in your project's directory:

npm install asset-manager

Then add this line to your app's configuration:

var assetManager = require('asset-manager')

Finally, initialize the manager with the paths it should search for static assets:

assetManager.start({
    paths: ["assets",
            "../global/assets",
            "vendor"],
    inProd: (process.env.NODE_ENV === 'production')
  }, callback);

Markup functions

asset-manager provides three global functions named img, js, and css. Use them in your views to resolve static assets into the markup need to resolve these assets in your page. For instance, in an [EJS template]:

<%- css('normalize') %>
<%- js('jquery') %>
<%- img('icon') %>

Supported CSS Precompilers

asset-manager has built in support for the following CSS preprocessors:

  • Less
  • Stylus

Express Middleware

If you want to have your app serve the static assets as well (a likely case at dev time), you can use the provided Express middle ware to do this:

app.use(assetManager.expressMiddleware);

Express Production Middleware

If you want to have your app serve the static assets in production as well, you can use the provided static Express middle ware to do this (the final parameter is whether or not the assets are gzip encoded):

app.use(assetManager.staticAssetMiddleware(express.static(__dirname + '/builtAssets', { maxAge: 31536000000 }), true));

Precompile assets

You can precompile your assets into their production form as follows (CDN_BASE_URL should be set to whatever URL you want prepended to your static asset paths):

assetManager.precompile({
    paths: ["assets",
            "../global/assets",
            "vendor")],
    servePath: CDN_BASE_URL,
    gzip: true
  }, callback);

Options

If you like, you can pass any of these options to the start or precompile functions:

  • paths (required): An array of paths that should be used to find static assets.
  • inProd (defaults to false): Indicates whether the application is running in production mode or not.
  • servePath (defaults to ''): The path you want to append to all asset URLs. Useful for pointing at an external CDN location.
  • builtAssets (defaults to 'builtAssets'): The folder you want precompiled assets to be placed in.
  • context (defaults to global): The object you want to hang the 'css', 'js', and 'img' functions on for resolving static assets.
  • gzip (defaults to false): Whether or not to gzip the contents of 'css' and 'js' files.
  • scanDir (defaults to ''): Include a base path you want asset-manager to scan for modules that contain asset-manifest.json files indicating the module contains static assets that should be available for use.

Minification

Asset manager currently minifies CSS and JS files, and JS assemblies.

  • CSS: Remove whitespace using css-clean
  • JS: Remove whitespace and mangle vars using uglifyJS

USAGE

assembly.json parameters

The Asset Manager uses the file assembly.json to define the list of file to combine into the output file. The output filename will be the name of the folder that holds the assembly.json file with the filename extension of .js. So if the folder name was widget then the output file would be called widget.js. A folder named MyControl would produce an output file named MyControl.js (Notice the case of the filename.)

There are three main sections of the assembly.json file:

  • files (array of files): The list of one or more files, normally JavaScript files, to combine into the output file.
  • simpleWrap (true/false): If true then wrap the output file in a Immediately-Invoked Function Expression (IIFE). This is a function closure that is wrapped around the code to prevent namespace leakage.
  • assemblies (array of folders): A list of one or more folders which contain an assembly.json file that is assembled into the output file.
  • templatePath (string): A relative path indicating where to load the template files. The default path is ./templates.
  • localePath (string): A relative path indicating where to load the locale files. The default path is ./locales.
  • localeFileName (string): The name, minus _en.json of the file to load as the locale files. The default is the name of the folder containing the assembly.json file.

files

files is an array of files that are to be included in the output file.

{
  "files": [
    "main.js",
    "folder1/other.js",
    "folder2/additional.js"
  ]
}

All files in the files array are relative to the path of the assembly.json file.

simpleWrap

simpleWrap indicates the developers desire to wrap this assembly output file inside of an IIFE. It is recommended that this option be set to true in most cases. This helps to protect your code, in an IIFE closure, and prevents your code from polluting the global namespace.

{
  "files": [
    "myfile.js"
  ],
  "simpleWrap": true
}

assemblies

assemblies is an array of folders which contain an assembly.json file that is assembled into the output file. Each assembly.json file must have simpleWrap set to true to place in it's own IIFE scope so they can each have their own language files and templates. This also provides a unique namespace for each assembly. So accessing values and functions between assemblies must be done through truly global variables. (Variables accessible off of window.)

{
  "assemblies": [
    "sub1",
    "sub2",
    "thingy/item1",
    "thingy/item2"
  ],
  "simpleWrap": true
}

For this assembly to work we would need the file structure to look like this:

componentFolder
│
├── assembly.json
├── sub1
│   │
│   ├── assembly.json
│   └── file.js
│
├── sub2
│   │
│   ├── assembly.json
│   └── file.js
│
└── thingy
    │
    ├── item1
    │   │
    │   ├── assembly.json
    │   └── file.js
    │
    └── item2
        │
        ├── assembly.json
        └── file.js

templatePath

templatePath is a path, relative to the location of the assembly.json file, that indicates where to load the template files. The default value for templatePath is ./templates.

This feature can be used to allow multiple assemblies to use the same set of template files.

localePath

localePath is a path, relative to the location of the assembly.json file, that indicates where to load the locale files. The default value for localePath is ./locales.

This feature allows multiple assemblies to share the same path for their locale files. (One thing to remember is that, unless you also provide the localeFileName parameter, your assembly will try to find the locale files that use the name of your assembly folder name. See localeFileName below for more information.)

localeFileName

localeFileName is the name, minus _en.json of the file to load as the locale file. The default value for localeFileName is name of the folder containing the assembly.json file. Given the following folder structure the default value for localefileName would be CoolThing. This would load the files CoolThing_en.json, CoolThing_es.json, CoolThing_fr.json, CoolThing_it.json and CoolThing_zh.json.

CoolThing
│
├── assembly.json
├── sub1
│   │
│   ├── assembly.json
│   └── file.js
│
└── locales
    │
    ├── CoolThing_en.json
    ├── CoolThing_es.json
    ├── CoolThing_fr.json
    ├── CoolThing_it.json
    ├── CoolThing_zh.json
    ├── otherFile_en.json
    ├── otherFile_es.json
    ├── otherFile_fr.json
    ├── otherFile_it.json
    └── otherFile_zh.json

If localeFileName were set to "otherFile" then Asset Manager would load the files otherFile_en.json, otherFile_es.json, otherFile_fr.json, otherFile_it.json and otherFile_zh.json.

Special files and folders

The Asset Manager uses special files and folders when processing an assembly.json file. These are:

  • locales/localefile_??.json: The locale files accessible through the lang and langs variables.
  • template.html: The single template file accessible through the snippetsRaw variable and the getSnippets() function.
  • templates/files.html: A template files accessible through the templateList object, the getTemplate(key) function and the getTemplateStr(key) function.

Locale files / locales folder

The locales folder is used to store language specific strings in a series of JSON files. These files must have specific names for Asset Manager to use them. The prefix of the filenames must match exactly, including case, of the name of the folder that holds the locales folder. In the example below the name of the folder is MyItem and the prefix of all of the filenames in the locales folder is also MyItem.

MyItem
│
├── assembly.json
├── templates
│   │
│   └── myFile.html
│
└── locales
    │
    ├── MyItem_en.json
    ├── MyItem_fr.json
    ├── MyItem_ja.json
    └── MyItem_zh.json

Each file in the locales folder identifies which language it supports by appending an underscore and the two letter locale to the filename. So _en represents English, _fr represents French, etc. You MUST have the _en file in all cases or the locale system fails.

When these locale strings are loaded into the system they are accessible through the langs object. langs.en.OPEN will access the value for OPEN from the _en file. While langs.fr.OPEN will access the value for OPEN from the _fr file.

The locale system will automatically create a variable called lang which is the set of locale strings for the currently selected locale. The lang object is a combination of English strings overwritten by the strings for the requested locale. Since we create the English version of the strings first and then the other languages are translated later this allows the code to always have a string for every key. If it is translated we get the translated string. If it is not translated we get the English string.

Using the localePath parameter in the assembly.json file you can change the location where Asset Manager loads the locale files from the locales folder to any other folder. The string value of the localePath parameter is a path relative to the location of the assembly.json file.

Using the localeFileName parameter in the assembly.json file you can change the files that Asset Manager loads as the locale files. For more information on this please see the section on localeFileName above.

Templates

template.html

The template.html function is a way of adding html templates into an assembly. The Asset Manager converts the content of the template.html file into a JavaScript string and save it as the variable snippetsRaw. If there is a <body> tag in the template then just the contents of the <body> tag is included in snippetsRaw.

Your code can access snippetsRaw to get at the content of the template as a string. Or you can call the function getSnippets() to get the contents of the template back as DOM elements in a single <div> element. getSnippets() also translates the template before converting it to DOM elements. More on translation below.

template.html allows you to exclude lines and sections. To exclude a line just add <!-- exclude LINE --> anywhere on that line. Asset Manager will remove the entire line.

To exclude a section place <!-- exclude START --> on the first line to exclude and <!-- exclude END --> on the last line to excluded. Asset Manager will exclude everything from the first line to the last line, including everything on those lines.

templates folder

The templates folder is a way to provide multiple, independent, templates to your code. The templates folder works great for a series of AngularJS directives that each need their own template. It also works well for any code that needs to get at different templates without the need to dig into the DOM created by getSnippets().

Inside the templates folder you can create one or more .html files and each of these become a separately accessible template. Each template file must use the .html extension and can only use alphanumeric characters in the filename.

All of the templates from the templates folder are stored as member variables of the templateList variable. If you had the file myStuff.html then the contents of myStuff.html would be accessible as a string in the variable templateList.myStuff.

Template files in the templates folder should only contain the code needed in the template. Unlike the file template.html files in the templates folder do not allow for excluded lines or section and these files do not attempt to only grab the contents of the <body> tag.

You code can access the templates by using the member variable of the templateList variable. templateList.button would give you the content of the file templates/button.html and templateList.form would give you the contents of the file templates/form.html.

You can also get the translated content by calling the function getTemplateStr(key) where key would be a string of the filename. For example: getTemplateStr("button") would give you the contents of the file templates/button.html after that string had been translated. More on translations below.

Using the templatePath parameter in the assembly.json file you can change the location where Asset Manager loads the template files from the templates folder to any other folder. The string value of the templatePath parameter is a path relative to the location of the assembly.json file.

Template translations

Templates can be auto-translated given the following conditions:

  • There are correctly formatted locale files in the locales folder.
  • The template, either template.html or files in the templates folder, use translations.

For information about correctly formatted locale files see the section above on Locale Files.

To use translations in a template you simple need to place the translation key name (translation key) in between curly braces like this: {OPEN}.

If the translation key OPEN exists within the locale files then {OPEN} will be replaced by the value for OPEN. Below is an example translation file, template and the output you would get after the translation of the template:

Translation file:

{
  "LABEL_OPEN": "Open",
  "LABEL_CLOSE": "Close",
  "HELP": "This is a help string"
}

Template:

<button>{LABEL_OPEN}</button>
<p>{HELP}</p>

Output

<button>Open</button>
<p>This is a help string</p>
Translation keys not found

Be aware that any translation key that is not found in the locale files will be left alone:

Translation file:

{
  "LABEL_OPEN": "Open",
  "LABEL_CLOSE": "Close",
  "HELP": "This is a help string"
}

Template:

<button>{LABEL_OPEN}</button>
<p>{HELP}</p>
<p>{{angularVariable}}</p>

Output

<button>Open</button>
<p>This is a help string</p>
<p>{{angularVariable}}</p>
Translation keys vs. AngularJS scope variables

You must be careful to not use an AngularJS scope variable that is the same name as a translation key. Or it will be changed:

Translation file:

{
  "LABEL_OPEN": "Open",
  "LABEL_CLOSE": "Close",
  "HELP": "This is a help string",
  "CLOSE": "Close this"
}

Template:

<button>{LABEL_OPEN}</button>
<p>{HELP}</p>
<p>{{CLOSE}}</p>

Output

<button>Open</button>
<p>This is a help string</p>
<p>{Close this}</p>

You should either change the Angular scope variable name or the translation key to prevent this problem. Writing the translation key in all caps and the scope variables in CamelCase format should prevent this from happening.

How fonts are currently handled

Right now, Frontier-Build-Tools will rewrite any path containing /fonts to begin with the CDN's domain. It also currently has an override to set this CDN domain as the S3 bucket, rather than the edge CDN domain. Upon further testing in our currently supported browsers, it seems the s3 domain is unneeded, as the edge domain works fine with CORS.