# apostrophe-assets

# Inherits from: apostrophe-module

# apos.assets

This module provides minification and delivery of browser-side assets such as stylesheets and javascript.

You'll want to call the pushAsset method of your own module, which takes advantage of the services provided by this module thanks to the apostrophe-module base class.

Apostrophe implements two "asset scenes," anon and user. When you call self.pushAsset('script', 'myfile', { when: 'user' }), that script is normally pushed only to logged-in users. When you call self.pushAsset('script', 'myfile', { when: 'always' }), that script is pushed to everyone, logged in or not.

If you want assets that are normally available only to logged-in users for a particular page, set req.scene = "user;" and that particular page will render with the full set of assets. This is useful if you wish to use apostrophe schema-based forms on a page for anonymous site visitors.

This module also pushes most of Apostrophe's standard front-end assets, notably jQuery, lodash, async, moment, moog and a polyfill for setImmediate. You may assume all of these are available in the browser at all times.

Other assets are pushed by individual core modules that require them.

# Options:

# minify

Set for you automatically if APOS_BUNDLE=1 or APOS_MINIFY=1 in the environment.

If set to true, both stylesheets and scripts are combined into a single file and unnecessary whitespace removed to speed delivery. It is strongly recommended that you enable this option in production, and also in staging so you can see any unexpected effects.

It never makes sense to run with no minified assets in production.

# lean

If this option is set to true, Apostrophe will not push any assets to an anonymous, logged-out site visitor, except for those pushed with { when: 'lean' }. By default this includes only a tiny subset of the apos.utils library with necessary services to make widget players possible, with no library dependencies.

Note that this means assets pushed with { when: 'always' } will not be received, except by logged-in users.

There are also no widget players, except for modules that allow you to opt in to a lean widget player by passing the player: true option when configuring those modules. This is currently supported by apostrophe-video-widgets.

# static

Pass options to the express.static (opens new window) middleware, such as Cache-Control and more. If no options are defined, the default options from the middleware will be used, except for index and redirect which are set to false to avoid redirect loops caused by Apostrophe's automatic removal of trailing slashes from page slugs.

Please note you might want to define different options depending on your environment. You could for example set max-age only for production to ensure fresh files during development. Example:

{
  static: {
    maxAge: '1y',
    etag: false
  }
}

# uploadfsBundleCleanup

If explicitly set to false, the mechanism that otherwise removes stale uploadfs static asset bundles five minutes after launch is disabled. See Deploying Apostrophe in the Cloud with Heroku for more information.

# Additional Environment Variables

# APOS_BUNDLE

Set APOS_BUNDLE=1 for a simple way to handle copying static assets to the cloud in production.

First run this task in a production environment:

APOS_BUNDLE=1 node app apostrophe:generation

Then make sure the variable is also set when running actual production instances of the site:

APOS_BUNDLE=1 node app

If in your environment the bundle has already been extracted and the root directory is now read-only, you can use this additional environment variable to avoid an error from tar:

APOS_BUNDLE=1 APOS_EXTRACT_BUNDLE=0 node app

Alternatively, if you specified an explicit bundle name to --create-bundle when using apostrophe:generation, stored it to git and deployed it, you can specify that bundle name as the value of APOS_BUNDLE. But this is more work; we recommend the easy way.

# APOS_BUNDLE_IN_UPLOADFS

Legacy. For use when APOS_BUNDLE is set to an explicit bundle name but you still wish static asset URLs to be generated to reference those files via uploadfs. But this is the hard way; just run apostrophe:generation with APOS_BUNDLE=1, and also set APOS_BUNDLE=1 in the environment when launching Apostrophe. That's really all you have to do. See Deploying Apostrophe in the Cloud with Heroku for more information.

# APOS_BUNDLE_CLEANUP_DELAY

If set to a number of milliseconds, Apostrophe delays that long before cleaning up obsolete static asset bundles in uploadfs. The default is 5 minutes. The assumption is that all production servers have received the new deployment and finished serving any straggler HTTP requests 5 minutes after a new version is first launched. See Deploying Apostrophe in the Cloud with Heroku for more information.

# Methods

# setDefaultStylesheets()

# setDefaultScripts()

# setAssetTypes()

# setTypeMap()

# determineGenerationFromDb()

If self.simpleBundle is true, determine the current asset generation via the database and set self.generation accordingly. If we are running the generation task in that situation, set the generation id in the database. In all other cases, determine the generation via legacy methods.

# determineGeneration()

Determine the current asset generation identifier (self.generation) and prep the bundle folder, if any is needed.

# determineDevGeneration()

Return an asset generation identifier for dev use only. By default the pid (which is constant just for the lifetime of this process) is used.

# mkdirp(path)

# enableBundles()

Initialize services required for asset bundles. Obtains the self.generations mongodb collection and extracts a bundle if appropriate.

# extractBundleFromGenerationCollection()

Extracts the appropriate asset bundle from uploadfs if we are using simple bundles and this is not a command line task.

Returns a promise. Called on the modulesReady event.

If the APOS_EXTRACT_BUNDLE environment variable is set to the string "0" this does not take place. That is useful when the bundle has already been extracted by other means and the filesystem is no longer writable (for instance, platform.sh).

# extractBundleIfAppropriate()

This method supports the less common case where an explicit bundle name is in APOS_BUNDLE and it should be extracted from the filesystem. The more common case, APOS_BUNDLE=1, is implemented elsewhere. The name of this method is kept for bc reasons.

# uploadfsBundleCleanup()

Clean up old asset bundles in uploadfs, if any, after a suitably safe interval allowing services such as Heroku to shut down old instances that might be using them

# extractBundle(name)

Extract the named asset bundle, as created by the apostrophe:generation task with the --create-bundle=NAME option. An asset bundle is just a folder from which files are recursively copied into the project root folder in a production environment after deployment, allowing minified assets to be provided to a server via a separate folder in git rather than cluttering the dev environment with them

# push(type, name, options, context)

This method pushes assets to be delivered to the browser on every page load.

You should be calling the pushAsset method of your module, not this one. It is part of the implementation of the pushAsset method of apostrophe-module, the base class of all modules.

But if you really wanted to, you'd have to do this...

self.pushAsset('stylesheet', 'foo', { when: 'always' }, { dirname: '/path/to/module', name: 'apostrophe-modulename', })

Stylesheets are loaded from the module's public/css folder.

Scripts are loaded from the module's public/js folder.

Do not supply the file extension.

It is acceptable to push an asset more than once. Only one copy is sent, at the earliest point requested.

Returns true if an acceptable source file or function for the asset exists, otherwise false.

# purgeExcept(pattern, exceptSubstring)

Purge files from public folder matching the glob pattern pattern, excepting those with names containing exceptSubstring.

# afterInit(callback)

Wait until the last possible moment - after all modules have been initialized, and notified of each other's initialization - to symlink public/modules subdirectories, build master LESS files, and minify (if desired). This allows other modules to wait until they can talk to each other (modulesReady) before pushing assets.

# ensureFolder(root)

Ensure that the standard asset folders exist at project level, notably public (the web-accessible folder) and public/modules (where symbolic links to the public subdirectories of Apostrophe modules are automatically created by symlinkModules). If root is not set, the root of the project is assumed.

# symlinkModules(callback)

Ensure that public/modules/modulename points to exactly the same content as lib/modules/modulename/public. On platforms that support symbolic links for non-administrators, use them. On platforms that don't, make a recursive copy. (This poses no significant performance problem for Apostrophe's assets, which are modest in size. If you were hoping to push huge files as permanent static assets this way, well... complain to Microsoft.)

# getAssetRoot()

Get the effective project root folder. This will be the actual project root folder except when creating an asset bundle to be unpacked later

# linkAssetFolder(from, to)

Create or refresh a symbolic link from the path "from" to the existing, actual folder "to". If symbolic linking is unavailable on this platform (Windows), recursively copy instead.

Note that "to" is the EXISTING, REAL thing, so the recursive copy actually duplicates "to" in "from". Counterintuitive, but that's because we're thinking in terms of a symbolic link.

# linkAssetFolderOnUnix(from, to)

Create or refresh a symbolic link from the path "from" to the existing, actual folder "to" on Unix-derived platforms (not Windows).

If we are creating an asset bundle to deploy to production, we'll copy everything instead.

# ensureNamespace(folder)

Namespaced npm package names look like @foo/bar, so we might need to create @foo before we can create bar. This method's job is to abstract this detail away from the symlink and recursive copy methods.

# linkAssetFolderOnWindows(from, to)

# removeThenRecursiveCopy(from, to)

Remove the existing folder or symlink to and then recursively copy the contents of from to it, creating a new folder at to.

# recursiveCopy(from, to)

Copy the existing folder at from to the new folder to. If to already exists files are added or overwritten as appropriate and files not present in from are left intact.

# syncToUploadfs(from, to, callback)

Copy the existing local folder at from to the uploadfs folder to. (uploadfs doesn't really have folders per se, so this just means prefixing the filenames with "to" plus a slash.)

WARNING: if to already exists, any contents that don't also appear in from are removed.

# enumerateCopies(from, to)

Given a local folder (the public/ subdir of an asset bundle) and an uploadfs path (where it will later be web-accessible), return an array of copies that must be performed, with from and to properties

# buildLessMasters(callback)

# getStylesheetsMasterBase()

# minify(callback)

# outputAndBless(callback)

# forAllAssetScenes(each, callback)

Iterate over all asset scenes. Right now just anon, user

# minifySceneAssetType(scene, type, minifier, callback)

Minify assets required for a particular scene and populate self.minified appropriately. Supports dot notation in "scene" for scene upgrades. Implements md5-based caching for the really expensive javascript minification step.

# minifyStylesheet(stylesheet, callback)

# minifyScript(script, callback)

Minify a single JavaScript file (via the script.file property)

# compileStylesheet(stylesheet, callback)

# filterAssets(assets, when, minifiable)

Part of the implementation of self.afterInit, this method returns only the assets that are suitable for the specified scene (user or anon). Duplicates are suppressed automatically for anything rendered from a file (we can't do that for things rendered by a function).

If minifiable is true you get back the assets that can be minified; if set false you get those that cannot; if it is not specified you get both.

If options: lean is true, "always" is treated as "user". This maintains bc in the logged-in experience without pushing anything unnecessary to the lean logged-out experience.

Regardless of the lean module-level option, anything pushed as "lean" is delivered to both the "anon" and "user" scenes. This provides an upgrade path for gradual migration to lean: true.

# pushConfigured()

Override pushConfigured so that self configured assets are always served, unless specified otherwise

# setWhenIfNotConfigured(item, defaultWhen)

If "when" is not specified, specify a default

# getChain(name)

Fetch an asset chain by name. Note that the name of the chain for a project-level override of the "foo" module is "my-foo". If namespaced and a project level override, it is "@namespace/my-foo". Otherwise it is the name of the module.

# pushDefaults()

# modulesReady()

# splitWithBless(filename, content)

# enableCsrf()

# enableHtmlPageId()

# enablePrefix()

# enableLessMiddleware()

# prefixCssUrls(css)

Prefix all URLs in CSS with the global site prefix

# prefixCssUrlsWith(css, prefix)

Prefix all URLs in CSS with a particular string

# servePublicAssets()

# assetUrl(web)

Given the site-relative URL an asset would have when hosting assets locally, return the asset URL to be used in script or link tags. Often the same, but when APOS_BUNDLE is in effect it can point elsewhere

# getCoreAposProperties(when)

# generationTask(callback)

This task is primarily implemented by the logic in afterInit, however if we are sending a bundle to uploadfs this is a fine time to do that part.

# getUploadfsBundleTempName()

# getUploadfsBundlePath()

# stylesheetsHelper(when)

Implementation of stylesheeets helper, as a method for easier use of super pattern to extend it. See the documentation for the stylesheets helper. Name is suffixed to avoid a conflict with a property.

# scriptsHelper(when)

Implementation of scripts helper, as a method for easier use of super pattern to extend it. See the documentation for the scripts helper. Name is suffixed to avoid a conflict with a property.

# uploadfs()

Override point to use a separate uploadfs instance, for instance in a multisite project with shared assets

# pushCreateSingleton()

# getGenerationPath()

# getThemedGeneration()

# getThemed(s)

# getThemeName()

Can be overridden by modules like apostrophe-multisite to namespace minified asset bundles and LESS masters. Env var option is for unit testing only

# Nunjucks template helpers

# stylesheets(when)

apos.assets.stylesheets renders markup to load CSS that is needed on any page that will use Apostrophe.

when can be set to either user or anon and signifies whether a user is logged in or not; when users are logged in editing-related stylesheets are sent, otherwise not.

The when parameter is made available to your page templates, so typically you just write this in your base layout template in the head element:

{{ apos.assets.stylesheets(data.when) }}

See outerLayout.html in the templates module.

# scripts(when)

apos.assets.scripts renders markup to load JS that is needed on any page that will use Apostrophe.

when can be set to either user or anon and signifies whether a user is logged in or not; when users are logged in editing-related scripts are sent, otherwise not.

The when parameter is made available to your page templates, so typically you just write this in outerLayout.html:

{{ apos.assets.scripts(data.when) }}

See outerLayout.html in the templates module.

apos.assets.scripts also creates the apos object, with its prefix property, so that beforeCkeditor.js and other third party loaders can see the prefix even before our own javascript is loaded.

# templates(when)

Removed in 2.x, however a nonfunctional version did appear at first in 2.x, so we provide an empty function for bc in case someone calls it.