1 | # Asset Smasher
|
2 |
|
3 | Asset pre-processor, merger, and compressor for Node.js
|
4 |
|
5 | - [Structuring Your Assets](#structure-assets)
|
6 | - [Manifest Files](#manifest-files)
|
7 | - [Using via Command Line](#command-line)
|
8 | - [Helpers](#cli-helpers)
|
9 | - [Plugins](#cli-plugins)
|
10 | - [Using via Express Middleware](#express-middleware)
|
11 | - [Using via Programmatic Interface](#programmatic-interface)
|
12 | - [Transformer Notes](#transformer-notes)
|
13 | - [LESS](#tn-less)
|
14 | - [ejs](#tn-ejs)
|
15 | - [dust and Handlebars](#tn-dust-hbs)
|
16 |
|
17 | ## Overview
|
18 |
|
19 | Asset Smasher is a command-line tool, express middleware, and programmatic interface for:
|
20 |
|
21 | - Pre-processing and transforming files down to plain JavaScript and CSS.
|
22 | - `.coffee` - Compile CoffeeScript into JavaScript
|
23 | - `.ejs` - Run a file through EJS (e.g. to populate configuration parameters into a JavaScript file)
|
24 | - `.less` - Compile Less into CSS
|
25 | - `.hbs` - Precompile Handlebars templates into JavaScript files that register them with `Handlebars.templates`.
|
26 | - `.dust` - Precompile Dust templates into JavaScript files that register them for use with `dust.render`.
|
27 | - Processors can be chained together. E.g `test.js.hbs.ejs` (run Handlebars template through EJS, then compile it)
|
28 | - Additional processors can be plugged in.
|
29 | - Merging files together using Manifest files (`.mf`) with dependency management directives similar to Sprockets.
|
30 | - `require` - Require a single file
|
31 | - `require_dir` - Require all the files in a specific directory
|
32 | - `require_tree` - Require all the files in a specific directory (and subdirectories)
|
33 | - Compressing, gzipping, and generating hashed file names.
|
34 | - Compress JavaScript files with `uglify-js`
|
35 | - Compress LESS during LESS preprocessing
|
36 | - Generate Gzipped versions of files
|
37 | - Include a MD5 hash of the file's contents in the file name. `myAsset.js` -> `myAsset-c89cba7b7df028e65cb01d86f4d27077.js`
|
38 | - `asset_path` helper that can be used to reference the hashed name.
|
39 |
|
40 | It's released under the [MIT](http://en.wikipedia.org/wiki/MIT_License) license.
|
41 |
|
42 | ## <a name="structure-assets"></a> Structuring Your Assets
|
43 |
|
44 | Asset Smasher has the concept of "asset paths". These are locations in which your asset files will be located, and from which any relative asset paths will be rooted to.
|
45 |
|
46 | The simplest structure has one asset path.
|
47 |
|
48 | E.g.
|
49 |
|
50 | Asset Paths
|
51 | -----------
|
52 | - app
|
53 |
|
54 | File Structure
|
55 | --------------
|
56 | app/
|
57 | js/
|
58 | css/
|
59 | images/
|
60 |
|
61 | A more complicated structure might be
|
62 |
|
63 | Asset Paths
|
64 | -----------
|
65 | - app
|
66 | - lib
|
67 | - vendor
|
68 |
|
69 | File Structure
|
70 | --------------
|
71 | app/
|
72 | js/
|
73 | css/
|
74 | images/
|
75 | lib/
|
76 | js/
|
77 | css/
|
78 | images/
|
79 | vendor/
|
80 | js/
|
81 | css/
|
82 | images/
|
83 |
|
84 | Both of these examples will result in a compiled structure of
|
85 |
|
86 | js/
|
87 | css/
|
88 | images/
|
89 |
|
90 | ### <a name="manifest-files"></a> Manifest Files
|
91 |
|
92 | Manifest (`.mf`) files are used to merge many assets into a single resulting file. The file should be named with the resulting file type before the `.mf` extension (e.g. `manifest.css.mf` or `manifest.js.mf`. *Manifest files can `require` other manifest files*
|
93 |
|
94 | A simple manifest file might look like
|
95 |
|
96 | # A comment here
|
97 | require "./one.js"
|
98 | require_dir "./subdir1"
|
99 | #
|
100 | # Another comment
|
101 | require_tree "./subdir2"
|
102 |
|
103 | **Directives:**
|
104 |
|
105 | <table border="1" cellpadding="5" cellspacing="0" width="100%">
|
106 | <thead>
|
107 | <tr><th width="15%">Directive</th><th width="85%">Description</th></tr>
|
108 | </thead>
|
109 | <tbody>
|
110 | <tr>
|
111 | <td><code>require "[path]"</code></td>
|
112 | <td>
|
113 | <strong>Include a single file</strong>
|
114 | <ul>
|
115 | <li>
|
116 | If the path starts with <code>"/"</code>, <code>"../"</code>, or <code>"./"</code>, process and include the specified file. The file <em>must</em> be
|
117 | inside one of the configured asset paths.
|
118 | </li>
|
119 | <li>
|
120 | If the path does not start with <code>"/"</code>, <code>"../"</code>, or <code>"./"</code>, the file will be searched for in all of the configured
|
121 | asset paths. E.g. if there are asset paths <code>one</code> and <code>two</code> defined, <code>require "js/test.js"</code>
|
122 | will look for <code>one/js/test.js</code> and then <code>two/js/test.js</code> stopping when it finds a matching file.
|
123 | </li>
|
124 | <li>
|
125 | The filename part of the path does not have to include the whole extension. E.g <code>require "test"</code>
|
126 | finds the first file that matches the name in the asset paths (for example <code>test.js.ejs</code>)
|
127 | </li>
|
128 | </ul>
|
129 | </td>
|
130 | </tr>
|
131 | <tr>
|
132 | <td><code>require_dir "[path]"</code></td>
|
133 | <td>
|
134 | <strong>Include all the files in a directory</strong>
|
135 | <ul>
|
136 | <li>
|
137 | The path must be absolute, or relative to the current directory. E.g. you can do <code>require_dir "../some/other/dir"</code>
|
138 | but not <code>require_dir "somedir"</code>
|
139 | </li>
|
140 | <li>
|
141 | If using absolute paths, or <code>".."</code> in your paths, the resulting directory needs to be inside one of the configured asset paths.
|
142 | </li>
|
143 | <li>
|
144 | Make sure the directory only contains assets of the type you want. E.g. for <code>myManifest.js.mf</code>, the dir required had better
|
145 | only contain javascript files, or else bad things will happen.
|
146 | </li>
|
147 | </ul>
|
148 | </td>
|
149 | </tr>
|
150 | <tr>
|
151 | <td><code>require_tree "[path]"</code></td>
|
152 | <td>
|
153 | <strong>Include the files in a directory recursively</strong>
|
154 | <ul>
|
155 | <li>The rules for <code>require_tree</code> are the same as the rules for <code>require_dir</code></li>
|
156 | </ul>
|
157 | </td>
|
158 | </tr>
|
159 | </tbody>
|
160 | </table>
|
161 |
|
162 | ## <a name="command-line"></a> Using via Command-Line
|
163 |
|
164 | Use `npm install -g asset-smasher` to install the `asset-smasher` command-line tool globally.
|
165 |
|
166 | asset-smasher --help
|
167 |
|
168 | Usage: asset-smasher [options] <output dir>
|
169 |
|
170 | Options:
|
171 |
|
172 | -h, --help output usage information
|
173 | -V, --version output the version number
|
174 | --compress compress/minify the generated files
|
175 | --hash generate versions of the files with md5 hashes in the name
|
176 | --gzip generate gzipped versions of the compiled files
|
177 | --hashVersion <version> invalidate all assets without changing file contents [1.0]
|
178 | --only <pattern,...> only process the files matching these glob patterns (relative to any of the paths) [**/*]
|
179 | --paths <path,...> list of paths to look for assets [.]
|
180 | --prefix <prefix> prefix to append to logical paths when constructing urls. use if output dir is not served from the root of your web app []
|
181 | --helpers <js_file> a .js module of helper functions require()s to expose to transforms []
|
182 | --plugins <js_file> a .js plugin module []
|
183 |
|
184 | If --only is not specified, *all* files in the --paths will be processed.
|
185 |
|
186 | Examples:
|
187 |
|
188 | Compile all assets in the current directory to /home/me/compiledAssets
|
189 |
|
190 | $ asset-smasher /home/me/compiledAssets
|
191 |
|
192 | Something similar to what the Rails asset pipeline does by default
|
193 |
|
194 | $ asset-smasher --compress --hash --gzip --prefix=/assets \
|
195 | --paths=./js,./css,./images \
|
196 | --only **/*.{jpg,gif,png},application.js.mf,application.css.mf ./public/assets
|
197 |
|
198 | Compile assets, providing some custom helpers to the transformation
|
199 |
|
200 | $ asset-smasher --helpers helpers.js output
|
201 |
|
202 | ### <a name="cli-helpers"></a> Helpers
|
203 |
|
204 | There is a built-in `asset_path` helper that can be used to get the "real" (i.e. with hashed file name) path of an asset. E.g. `asset_path('css/myFile.css')` might return `'/assets/css/myFile-c89cba7b7df028e65cb01d86f4d27077.css`.
|
205 |
|
206 | Some transformers (e.g. the `.ejs` one) take in a set of local variables that they can use during transformation. You can pass in the path to a JavaScript module whose exports will be included in this set of variables.
|
207 |
|
208 | You can use this, for example, to set configuration parameters in your JS files:
|
209 |
|
210 | **helper.js**
|
211 |
|
212 | exports.serviceUrl = 'http://my.service/';
|
213 |
|
214 | **config.js.ejs**
|
215 |
|
216 | //...
|
217 | var serviceUrl = '<%= serviceUrl %>';
|
218 | var cssLocation = '<%= asset_path('css/myFile.css') %>';
|
219 | //...
|
220 |
|
221 | **Execution**
|
222 |
|
223 | $ asset-smasher --helpers helper.js --only config.js.ejs,css/myFile.css .
|
224 | $ cat config.js
|
225 | var serviceUrl = 'http://my.service/';
|
226 | var cssLocation = '/assets/css/myFile-c89cba7b7df028e65cb01d86f4d27077.css';
|
227 |
|
228 |
|
229 | ### <a name="cli-plugins"></a> Plugins
|
230 |
|
231 | If there's a type of file you want to pre-process that is not natively supported by Asset Smasher, you can add it using a plugin file.
|
232 |
|
233 | *TODO: How to add additional transformers via a plugin file.*
|
234 |
|
235 | ## <a name="express-middleware"></a> Using via Express Middleware
|
236 |
|
237 | Asset smasher exposes an `express` middleware that can:
|
238 |
|
239 | - Serve your assets un-merged/mangled in development mode.
|
240 | - Serve precompiled assets (with hashed file names) in production mode.
|
241 |
|
242 | The middleware takes in the same arguments as the `Smasher` constructor, with a few extras:
|
243 |
|
244 | - `serve` - boolean whether the middleware should serve the asset files. Usualy set this to `true` in development, `false` in production
|
245 | - `assetMapLocation` - path to the `map.json` generated by the command-line `asset-smasher` util. This allows the helper methods to determine what the hashed file names were
|
246 |
|
247 | The middleware exposes two helpers to your views:
|
248 |
|
249 | - `js_asset(logicalPath)` - Render a `<script>` tag for the specified JS asset. When `serve` is true, this will "explode" manifests and write out a separate `<script>` for each required file. This makes debugging much easier.
|
250 | - `css_asset(logicalPath)` - Render a `<link>` tag for the specified CSS asset. Same thing happens when `serve` is true.
|
251 |
|
252 | ### Example
|
253 |
|
254 | var assetSmasher = require('asset-smasher');
|
255 |
|
256 | **Middleware config (Dev)**
|
257 |
|
258 | app.use(assetSmasher.middleware({
|
259 | serve: true,
|
260 | paths: [path.join(__dirname, 'assetDir1'), path.join(__dirname, 'assetDir2')],
|
261 | prefix: '/assets',
|
262 | outputTo: path.join(__dirname, 'tmp')
|
263 | }));
|
264 |
|
265 | **Middleware config (Prod)**
|
266 |
|
267 | app.use(assetSmasher.middleware({
|
268 | serve: false,
|
269 | prefix: '/assets',
|
270 | assetMapLocation: path.join(__dirname, 'public/assets/map.json')
|
271 | }));
|
272 |
|
273 | **View (ejs here, but could be others)**
|
274 |
|
275 | <!DOCTYPE html>
|
276 | <html>
|
277 | <head>
|
278 | <title>Test</title>
|
279 | <%- css_asset('application.css') %>
|
280 | <%- js_asset('application.js') %>
|
281 | </head>
|
282 | <body>
|
283 | This is a test
|
284 | </body>
|
285 | </html>
|
286 |
|
287 | ## <a name="programmatic-interface"></a> Using via Programmatic Interface
|
288 |
|
289 | You can invoke Asset Smasher programmatically by `require`ing it. You can also plug in additional transformers this way.
|
290 |
|
291 | The `Smasher` object has the following methods:
|
292 |
|
293 | - `compileAssets(cb)` - Find and compile all the assets.
|
294 | - `compileSingleAsset(assetFilePath, cb)` - Compile a single asset (assetFilePath is the actual path to the file, not a logical path)
|
295 | - `findAssets(cb)` - Find, but don't compile the assets. Good for determining dependency graph without compiling.
|
296 | - `getAssetByLogicalPath(logicalPath)` - Get information about an asset by its logical path. Only call this after finding/compiling assets.
|
297 | - `getHashedFileMapping()` - When `hash` is true, this returns a mapping of logical path to "hashed" logical path. This object is what the command-line tool outputs to `map.json`. Only call this after finding/compiling assets.
|
298 | - `getRequiredLogicalPathsFor(asset)` - Get the logical paths of the assets that should be merged into the specified asset (populated for `.mf` files). Only call this after finding/compiling assets.
|
299 | - `getProcessingOrderLogicalPaths()` - Get a list of the order in which assets should be processed in order to satisfy all dependencies. Only call this after finding/compiling assets.
|
300 |
|
301 | The `Asset` object returned by `getAssetByLogicalPath` has the following properties:
|
302 | - `logicalPath` - The logical path
|
303 | - `hashedPath` - If `hash` is true, the hashed filename path, otherwise the same as `logicalPath`
|
304 | - `assetFilePath` - The full path to the actual source asset
|
305 | - `compiled` - Whether the asset has been compiled
|
306 | - `compiledAssetFilePath` - The full path to the compiled asset file
|
307 |
|
308 | **Example**
|
309 |
|
310 | var assetSmasher = require('asset-smasher');
|
311 | var Smasher = assetSmasher.Smasher;
|
312 |
|
313 | // Plug in a custom transformer
|
314 | assetSmasher.transforms['MyAwesomeFormat'] = require('myAwesomeFormatTransformer');
|
315 |
|
316 | var sm = new Smasher({
|
317 | paths:['/path/one', '/path/two'],
|
318 | only:['**/*.{jpg,gif,png}', 'application.js.mf', 'application.css.mf'],
|
319 | prefix:'/assets',
|
320 | compress:true,
|
321 | hash:true,
|
322 | hashVersion:'1.0',
|
323 | gzip:true,
|
324 | outputTo:__dirname + '/public/assets',
|
325 | helpers:{
|
326 | my: 'helper',
|
327 | another: 'helper'
|
328 | }
|
329 | });
|
330 | sm.compileAssets(function(err) {
|
331 | if(err) {
|
332 | console.log('An error occurred', err);
|
333 | } else {
|
334 | console.log('Compilation done!');
|
335 | }
|
336 | });
|
337 |
|
338 | ## <a name="transformer-notes"></a> Transformer Notes
|
339 |
|
340 | ### <a name="tn-less"></a> LESS
|
341 |
|
342 | - When the `compress` option is true, the compression is done directly via the `less` compiler
|
343 | - Any `@include` paths are *relative to the path that the file is in*.
|
344 | - Any `@include`d files will *not* be processed individually by Asset Smasher (i.e. you can't `@include` a LESS file that is preprocessed by ejs)
|
345 |
|
346 | ### <a name="tn-ejs"></a> ejs
|
347 |
|
348 | - Any registered helpers will be exposed as global variables to the `ejs` transform.
|
349 | - The built-in `asset_paths` helper can be used here.
|
350 |
|
351 | ### <a name="tn-dust-hbs"></a> dust and Handlebars
|
352 |
|
353 | - The name of the template will be the template's "logical path" (minus the asset path it is in), minus the `.js.dust` or `.js.hbs` file extension.
|
354 | - E.g. `/my/templates/test.js.dust`'s template name will be `test` (assuming `/my/templates` is the asset path)
|