1 | # Substrat - Relax your build ![Build Status](https://api.travis-ci.org/BonsaiDen/substrat.png)
|
2 |
|
3 | **Substrat** is a powerful yet simple build system for HTML5 projects.
|
4 |
|
5 | It's easy and quick to set up, configurable, extendable and built with for
|
6 | frontend heavy, single page applications.
|
7 |
|
8 | ![npm Details](https://nodei.co/npm/substrat.png)
|
9 |
|
10 |
|
11 | ## Features
|
12 |
|
13 | - Automatic monitoring and syncing our source and build directories
|
14 | - Supports complex [file patterns](#patterns) for file filtering and ordering
|
15 | - Has task dependencies to re-build files when other files are changed (based on patterns)
|
16 | - Built in static web server with support for automatic page reload on each build
|
17 | - Comes with many built-in tasks for things like:
|
18 |
|
19 | - JS Minification (using [UglifyJS](https://github.com/mishoo/UglifyJS2))
|
20 | - Stylesheet compilation (using [lesscss](https://github.com/less/less.js))
|
21 | - HTML Templating (using [Jade](https://github.com/visionmedia/jade))
|
22 | - Markdown to HTML (using [markdown-js](https://github.com/evilstreak/markdown-js))
|
23 | - Generating files from templates (using [mustache.js](https://github.com/janl/mustache.js))
|
24 | - Dynamic file generation (using your custom functions)
|
25 |
|
26 | - Easily set up proxies:
|
27 |
|
28 | - Avoid CORS configuration and other issues during local development
|
29 | - Proxy a directory and easily inject mocks for your tests
|
30 | - Add delays to all requests in order to simulate bad networks
|
31 |
|
32 | - Is easy to extend with your own, custom tasks
|
33 | - Completely generic, can be used with (e.g. [Grunt](http://gruntjs.com), [Jake](https://github.com/mde/jake) or any other task runner or in a standlone script)
|
34 |
|
35 |
|
36 | ## Usage
|
37 |
|
38 | 1. Get it via `npm install substrat`
|
39 |
|
40 | ```javascript
|
41 | var substrat = require('substrat');
|
42 | ```
|
43 |
|
44 | 2. Setup your patterns, these allow you to group and filter the files for your build
|
45 |
|
46 | ```javascript
|
47 | var patterns = {
|
48 |
|
49 | js: {
|
50 | // Match all source files of the application, and put app and config
|
51 | // at the end when generating a array of the filenames from this pattern
|
52 | app: substrat.pattern(/js\/.*\.js$/).last('js/config.js', 'js/app.js'),
|
53 |
|
54 | // Match all the javascript source files of the libraries, but ignore any pre-minified ones
|
55 | lib: substrat.pattern(/lib\/.*\.js$/).not(/\.min\.js$/)
|
56 | },
|
57 |
|
58 | compile: {
|
59 | jade: substrat.pattern(/\.jade$/),
|
60 | less: substrat.pattern(/\.less$/)
|
61 | },
|
62 |
|
63 | // Match all style sheets both generated and existing ones
|
64 | // but put the generated ones at the end when generating a array of the
|
65 | // filenames from this pattern
|
66 | style: substrat.pattern(/(\.css|\.less)$/).last(/\.less$/),
|
67 |
|
68 | // A matcher for everything else
|
69 | all: substrat.pattern('*')
|
70 |
|
71 | };
|
72 | ```
|
73 |
|
74 | 3. Define an environment for the use in your templates, you can also expose the patterns so you can include all your scripts and styles automatically
|
75 |
|
76 | ```javascript
|
77 | var env = {
|
78 | title: 'Substrat',
|
79 | version: 0.1,
|
80 | patterns: patterns // Expose the patterns for later usage
|
81 | };
|
82 | ```
|
83 |
|
84 | 4. Create a new instance of substrat with your specific configuration
|
85 |
|
86 | ```javascript
|
87 | var s = substrat.init({
|
88 |
|
89 | // The source directory to watch
|
90 | src: 'src',
|
91 |
|
92 | // The destination directory for the build
|
93 | dest: 'public',
|
94 |
|
95 | // Whether or not to log build events, will still print general info
|
96 | silent: false,
|
97 |
|
98 | // Will disable all logging (sets silent to true)
|
99 | quiet: false,
|
100 |
|
101 | // If true, will produce lots of internal logging output
|
102 | debug: false,
|
103 |
|
104 | // Enable compression in tasks (e.g. strip whitespace, minify js etc.)
|
105 | compress: false,
|
106 |
|
107 | // Set up dependencies
|
108 | depends: [
|
109 | // Rebuild src/index.jade every time a js or less file changes
|
110 | // This way, the template can automatically update the included
|
111 | // scripts and styles
|
112 | ['index.jade', [patterns.js, patterns.style]]
|
113 | ],
|
114 |
|
115 | // Define the tasks
|
116 | // Tasks are run in order, each task will filter out the files it matched
|
117 | // so they are not subject to any further tasks in the chain
|
118 | tasks: [
|
119 |
|
120 | // Compile all app specific scripts with uglify-js
|
121 | substrat.task.compile(patterns.js.app, 'js'),
|
122 |
|
123 | // Compile all jade files to html and supply them with the locals from "env"
|
124 | substrat.task.compile(patterns.compile.jade, 'jade', env),
|
125 |
|
126 | // Compile all less stylesheets to css
|
127 | substrat.task.compile(patterns.compile.less, 'less'),
|
128 |
|
129 | // Copy all other files which did not match any previous tasks
|
130 | substrat.task.copy(patterns.all)
|
131 |
|
132 | ],
|
133 |
|
134 | // Setup some proxies for testing and local database access
|
135 | proxy: {
|
136 |
|
137 | // Proxy the local couchdb instance to avoid messy CORS setup during
|
138 | // development
|
139 | '/couchdb': {
|
140 |
|
141 | // URL is the target of the proxy
|
142 | host: 'localhost',
|
143 | port: 5984,
|
144 |
|
145 | // Add 500 milliseconds of delay to each request
|
146 | delay: 500
|
147 |
|
148 | },
|
149 |
|
150 | // Proxy the "public" directory itself but "mock" out a couple of
|
151 | // files to inject test mocks / frameworks
|
152 | '/test': {
|
153 |
|
154 | // The directory to serve
|
155 | root: 'public',
|
156 |
|
157 | // Replace the main application file and include additional files
|
158 | // for testing
|
159 | mock: {
|
160 | 'js/app.js': [
|
161 | 'test/e2e/app.js',
|
162 | 'test/e2e/mocks.js'
|
163 | ]
|
164 | }
|
165 |
|
166 | }
|
167 |
|
168 | }
|
169 |
|
170 | });
|
171 | ```
|
172 |
|
173 | 5. Start your continous build that automatically reloads your browser while you're editing
|
174 |
|
175 | ```javascript
|
176 | s.listen(4444);
|
177 | ```
|
178 |
|
179 | Read on for more details on the configuration options and tasks.
|
180 |
|
181 |
|
182 | ## Configuration Options
|
183 |
|
184 | - `src`: *String*
|
185 |
|
186 | The source directory which contains the file to build.
|
187 |
|
188 | - `dest`: *String*
|
189 |
|
190 | The destination directory were the files produced by the build are to be found.
|
191 |
|
192 | The contents of the directory are automatically synced with the source,
|
193 | meaning that files and folders which no longer exist in the source directory
|
194 | will automatically be removed.
|
195 |
|
196 | - `silent`: *Boolean (false)*
|
197 |
|
198 | If `true` disables substrat logging (except for top level logs).
|
199 |
|
200 | - `quiet`: *Boolean (false)*
|
201 |
|
202 | If `true` disables **all** substrat logging (enables `silent`).
|
203 |
|
204 | - `debug`: *Boolean (false)*
|
205 |
|
206 | If `true` enables internal logging of substrat's components.
|
207 |
|
208 | - `hidden`: *Boolean (true)*
|
209 |
|
210 | When `true` substrat will ingore any dotfiles.
|
211 |
|
212 | - `compress`: *Boolean(false)*
|
213 |
|
214 | A flag which indicates to tasks that the should compress / minify their
|
215 | output.
|
216 |
|
217 | See the [Tasks](#tasks) section for more details.
|
218 |
|
219 | - `depends`: *Array[Array[Pattern, Pattern|Array[Patterns]]...]*
|
220 |
|
221 | A array containing arrays of patterns which specify which files should be
|
222 | rebuild once other files matching the specified patterns have changed.
|
223 |
|
224 | See the [Dependencies](#dependencies) section for more details.
|
225 |
|
226 | - `tasks`: *Array*
|
227 |
|
228 | A listing of tasks which will be executed in order once the contents of the
|
229 | `src` directory change. Each successive tasks will filter out the files it
|
230 | matched from the list of files that have changed.
|
231 |
|
232 | See the [Tasks](#tasks) section for more details.
|
233 |
|
234 | - `proxy`: *Object*
|
235 |
|
236 | A mapping of paths to proxy configurations.
|
237 |
|
238 | See the [Proxies](#proxies) section for more details.
|
239 |
|
240 |
|
241 | ## Methods
|
242 |
|
243 | - `run()` -> *this*
|
244 |
|
245 | Invokes the build once and then finishes.
|
246 |
|
247 | Will emit the `done` event once the build has finished.
|
248 |
|
249 |
|
250 | - `watch()` -> *this*
|
251 |
|
252 | Will continously monitor the source directory for changed and re-build
|
253 | automatically.
|
254 |
|
255 | Triggers a `build` event after each completed build.
|
256 |
|
257 |
|
258 | - `listen(indexUrl, port [, host])` -> *this*
|
259 |
|
260 | Same as `watch()` but will also start a local web server on the specified
|
261 | `host` and `port` and will patch the specified `indexUrl` HTML file to
|
262 | automatically reload on every build.
|
263 |
|
264 | To disable automatic reloading, simply pass `null` as the value of `indexUrl`.
|
265 |
|
266 |
|
267 | - `stop()` -> *this*
|
268 |
|
269 | Stops substrat in case it is watching or listening.
|
270 |
|
271 | Triggers the `done` event.
|
272 |
|
273 |
|
274 | - `pattern(expr)` -> *Pattern*
|
275 |
|
276 | Creates a new substrat pattern from the given expression.
|
277 |
|
278 |
|
279 | - `files(pattern)` -> *Array[String]*
|
280 |
|
281 | Returns a list of files for the **destination** directory which match the
|
282 | specified pattern(s).
|
283 |
|
284 |
|
285 | ## Patterns
|
286 |
|
287 | Substrat makes heavy use of patterns for both file matching and listing.
|
288 |
|
289 | Patterns can be created from a variety of sources and are converted to regular
|
290 | expressions internally. You can also create pass objects of patterns which will
|
291 | be merged, as well as arrays which will concatenate their matches.
|
292 |
|
293 | > Note: All paths and files within substrat are treated relative to either the
|
294 | `src` or `dest` directories. E.g. `/home/user/project/src/js/app.js` will be
|
295 | treated as `js/app.js`.
|
296 |
|
297 |
|
298 | ### From Strings
|
299 |
|
300 | All strings parsed via [minimatch](https://github.com/isaacs/minimatch) and
|
301 | converted into regular expressions. This means that you can use standard *glob*
|
302 | patterns like `**/*.js` and the like.
|
303 |
|
304 | The only exception to this rule is the special string `*` which will get
|
305 | converted to `/^.*$/`.
|
306 |
|
307 |
|
308 | ### From Regular Expressions
|
309 |
|
310 | You can pass any valid regular expression as a pattern.
|
311 |
|
312 |
|
313 | ### From Objects
|
314 |
|
315 | Patterns from objects are merged, they object's keys are sorted via the standard
|
316 | `sort()` function and are then used to merge the object's values into a new
|
317 | pattern.
|
318 |
|
319 |
|
320 | ### From Functions
|
321 |
|
322 | Functions which are passed as pattern will get invoked with the filename they
|
323 | should test for matching. They should return either `true` or `false`.
|
324 |
|
325 |
|
326 | ### From Arrays
|
327 |
|
328 | Arrays will create so called *Pattern Groups*. Pattern groups apply all included
|
329 | patterns in order and will preserve the ordering of the files returned by the
|
330 | individual sub patterns.
|
331 |
|
332 |
|
333 | ### Ordering
|
334 |
|
335 | Patterns have the very useful `pattern.first(patterns...)` and
|
336 | `pattern.last(patterns...)` methods which will move the files matching the
|
337 | specified patterns either to the beginning or the end of the file list.
|
338 |
|
339 | ```javascript
|
340 | substrat.pattern(/js\/.*\.js$/).first('js/config.js').last('js/init.js', 'js/afterInit.js');
|
341 | ```
|
342 |
|
343 | For example, this allows you to get a list of all JavaScript files in your
|
344 | application and then put the file that defines your namespaces and configuration
|
345 | and the beginning
|
346 | of the list and the file initializing your code at the very end.
|
347 |
|
348 | ### Exclusion
|
349 |
|
350 | In addition patterns can include one or more files via the
|
351 | `pattern.not(pattern...)` method.
|
352 |
|
353 |
|
354 | ## Dependencies
|
355 |
|
356 | Substrat includes a minimal - but efficient - dependency management for files
|
357 | which is also based on pattern.
|
358 |
|
359 | Dependencies are specified in the format of an array with two entries. The first
|
360 | one is a pattern which describes which files will be re-build and the second
|
361 | entry being a pattern which specifies which files will trigger the re-build.
|
362 |
|
363 | ```javascript
|
364 | ['index.jade', [/*.js$/, /*.less$/]]
|
365 | ```
|
366 |
|
367 | The above will rebuild `index.jade` every time that a `.js` or `.less` file has
|
368 | been added, changed or removed from the source directory.
|
369 |
|
370 | Of course it is also possibly to re-build multiple files, simply supply a more
|
371 | complex pattern as the first entry of the array:
|
372 |
|
373 | ```javascript
|
374 | [/template\/view\/controller\/*.jade$/, [/*.js$/, /*.less$/]]
|
375 | ```
|
376 |
|
377 | > Note: Every rebuild will trigger another check for dependencies.
|
378 | > This allows for the creation of dependencies that depend on other dependencies.
|
379 |
|
380 |
|
381 | ## Tasks
|
382 |
|
383 | Tasks in substrat are highly configurable and easy to extend.
|
384 |
|
385 |
|
386 | ### Built-in Tasks
|
387 |
|
388 | - __Compile__
|
389 |
|
390 | `substrat.task.compile(pattern, compiler[, config])`
|
391 |
|
392 | Compiles all the files matching the `pattern` from the `src` to the `dest`
|
393 | using the specified `compiler`. Following compilers are available out of the box:
|
394 |
|
395 | > Note: The compile tasks by will **only** obfuscate and/or minify their
|
396 | > output when the `substrat.compress` option is set.
|
397 |
|
398 | - `js`
|
399 |
|
400 | Compiles JavaScript files using `uglify-js`, if the `substrat.compress`
|
401 | option is **enabled**, otherwise it will simply copy the JS files.
|
402 |
|
403 | __Example: Minifying all applications JS files__
|
404 |
|
405 | substrat.task.compile('js/**/*.js', 'js')
|
406 |
|
407 | - `less`
|
408 |
|
409 | Compiles `less` files into CSS, changing the file extension in the process.
|
410 | If `substrat.compress` is set it will stip whitespace from the output files.
|
411 |
|
412 | __Example: Transforming less files into CSS__
|
413 |
|
414 | substrat.task.compile(/*\.less$/, 'less')
|
415 |
|
416 | - `jade`
|
417 |
|
418 | Compiles `jade` files into HTML, changing the file extension in the process.
|
419 | The `config` paramter should be an object and will be populate the **locals**
|
420 | of the template.
|
421 |
|
422 | __Example: Converting all jade templates into HTML__
|
423 |
|
424 | substrat.task.compile(/*\.jade$/, 'jade', config)
|
425 |
|
426 |
|
427 | - __Concat__
|
428 |
|
429 | `substrat.task.compile(pattern, type, outputFile)`
|
430 |
|
431 | This task is pretty much the same as `compile` task but only supports `js`
|
432 | and `less` at the moment and will merge all the files into the specified
|
433 | `outputFile`.
|
434 |
|
435 |
|
436 | - __Copy__
|
437 |
|
438 | `substrat.task.compile(pattern)`
|
439 |
|
440 | Copies all the files matching the `pattern` from the `src` to the `dest`
|
441 | directory. This task uses `fs.stream` interally for efficient copying and
|
442 | will create directories in the destination as requried.
|
443 |
|
444 | __Example: Copying all outstanding files as the last task__
|
445 |
|
446 | substrat.task.copy('*')
|
447 |
|
448 |
|
449 | - __Template__
|
450 |
|
451 | `substrat.task.template(pattern, locals [, tags])`
|
452 |
|
453 | Compiles all files matching the `pattern` as `mustache.js` templates and
|
454 | supplies them with `locals`. The files get rendered to a file with the same
|
455 | name in the `dest` directory.
|
456 |
|
457 | The optional`tags` array can be used to replace the default tags used in
|
458 | mustache templates with custom ones. e.g. `['<%', '%>']`.
|
459 |
|
460 | __Example: Rendering configuration file with custom tags to keep it JSHint friendly__
|
461 |
|
462 | substrat.task.template('js/config.js', config, ['"{{', '}}"']),
|
463 |
|
464 |
|
465 | ### Custom Tasks
|
466 |
|
467 | New tasks can be created via the `substrat.Task` constructor:
|
468 |
|
469 | new substrat.Task(taskName, filePattern, handler, config)
|
470 |
|
471 | - `taskName`: *String*
|
472 |
|
473 | This simply is a internal name for the task which is used in debug logging.
|
474 |
|
475 | - `filePattern`: *Pattern*
|
476 |
|
477 | A substrat pattern which describes all files for which the task should be
|
478 | executed.
|
479 |
|
480 | > Note: A `null` pattern will run the task on every build, not matter which
|
481 | > files have changed.
|
482 |
|
483 | - `handlerDescription`: *Object*
|
484 |
|
485 | A object which implements the actual logic of the task.
|
486 |
|
487 | - `config`: *Object*
|
488 |
|
489 | Additional configuration which is available to the task logic during
|
490 | execution.
|
491 |
|
492 |
|
493 | ### Task Handler Description
|
494 |
|
495 | A task handler description consists of a number of properties and methods:
|
496 |
|
497 | var handlerDescription = {
|
498 |
|
499 | // Run the task independently for each file
|
500 | mode: substrat.Task.Each,
|
501 |
|
502 | // Automatically provide the file data to the task
|
503 | data: true,
|
504 |
|
505 | // Map the source files to html files in the output
|
506 | map: function(e, file) {
|
507 | return file.replace(/\.jade$/, '.html');
|
508 | },
|
509 |
|
510 | // The actual task logic
|
511 | run: function(e, done) {
|
512 |
|
513 | try {
|
514 |
|
515 | // Use the custom configuration of the task as the locals
|
516 | var locals = util.merge(e.config, {
|
517 | pretty: !e.options.compress,
|
518 | substrat: e.substrat
|
519 | });
|
520 |
|
521 | done(null, jade.render(e.data.toString(), locals));
|
522 |
|
523 | } catch(err) {
|
524 | done(err);
|
525 | }
|
526 |
|
527 | }
|
528 |
|
529 | };
|
530 |
|
531 |
|
532 | - `mode`: *Integer*
|
533 |
|
534 | One of the following:
|
535 |
|
536 | - `Each`
|
537 |
|
538 | Run the task independently for each file, meaning that for five input
|
539 | files the task will be run five times.
|
540 |
|
541 | - `All`
|
542 |
|
543 | Run the task once on all files, meaning that for five input files
|
544 | the task will be called exactly one time and will be provided with all
|
545 | the files and their data at once.
|
546 |
|
547 | - `Single`
|
548 |
|
549 | Run the task once and don't care about the input. Useful for auto
|
550 | generation of files and other things.
|
551 |
|
552 |
|
553 | - `data`: *Boolean|Function(e)*
|
554 |
|
555 | Whether or not to automatically read the input file(s) and supply their
|
556 | buffers to the task. Can also be a function which gets passed the [task
|
557 | execution environment](#task-execution-environment) and should return a
|
558 | `boolean`.
|
559 |
|
560 |
|
561 | - `map`: *Function(e, file)*
|
562 |
|
563 | A function which maps the input filename to the respective outputs, can
|
564 | also return an array with multiple output names (e.g. a JS file and its
|
565 | corresponding source map file).
|
566 |
|
567 | It's arguments consists of the
|
568 | [task execution environment](#task-execution-environment) and the path of
|
569 | the file in source directory.
|
570 |
|
571 | These mappings are used to create the output files of the task in the
|
572 | destination directory.
|
573 |
|
574 | In addition, they also server to synchronize the destination directory and
|
575 | automatically remove files which are no longer exist in the source.
|
576 |
|
577 | They are also available via `substrat.files(patterns)` and can be used to
|
578 | automatically include files in HTML and other templates.
|
579 |
|
580 |
|
581 | - `run`: *Function(e, done(err[, data]))*
|
582 |
|
583 | A function which performs the actual task logic.
|
584 |
|
585 | It's arguments consists of the
|
586 | [task execution environment](#task-execution-environment) and a callback
|
587 | function.
|
588 |
|
589 | The `done` callback takes the following arguments:
|
590 |
|
591 | - `err`: *Null|Error*
|
592 |
|
593 | The error value in case the task could failed. Pass `null` if the task
|
594 | was successful.A
|
595 |
|
596 | - `data`: *String|Array[String]* (Optional)
|
597 |
|
598 | The file data to be written into the files indicated by the return
|
599 | value(s) of the handlers `map()` function.
|
600 |
|
601 | If left out, no file be written. This can be used by tasks which handle
|
602 | the writing on their own (e.g. the `copy` task which uses streams).
|
603 |
|
604 |
|
605 | ### Task Execution Environment
|
606 |
|
607 | This "environment" argument is passed to all functions of a task handler
|
608 | description and has the following structure:
|
609 |
|
610 | - `options`: *Object*
|
611 |
|
612 | A reference to the configuration object passed into `substrat.init()`.
|
613 |
|
614 | - `config`: *Object*
|
615 |
|
616 | A reference to the configuration object passed into the task constructor.
|
617 |
|
618 | - `mapped`: *String|Array[String]*
|
619 |
|
620 | The filename(s) returned by the `map()` function of the task handler.
|
621 |
|
622 | *Only for tasks running with mode `Task.Each` or `Task.All`*
|
623 |
|
624 | - `source`: *String*
|
625 |
|
626 | The filename from the source directory.
|
627 |
|
628 | *Only for tasks running with mode `Task.Each`*
|
629 |
|
630 | - `data`: *Buffer*
|
631 |
|
632 | A `Buffer` object with the contents of the file reference by `source`.
|
633 |
|
634 | *Only for tasks running with mode `Task.Each`*
|
635 |
|
636 | - `path`: *String*
|
637 |
|
638 | The full path to the file in the source directory.
|
639 |
|
640 | *Only for tasks running with mode `Task.Each`*
|
641 |
|
642 | - `all`: *Array[Object]*
|
643 |
|
644 | A array of objects with `source`, `data` and `path` properties as described
|
645 | above.
|
646 |
|
647 | *Only for tasks running with mode `Task.All`*
|
648 |
|
649 |
|
650 | ## Proxies
|
651 |
|
652 | Substrat can be used to quickly configure proxies to both http endpoints as well
|
653 | as local directories, this is done via the `substrat.proxy` option which takes a
|
654 | mapping of absolute paths to **proxy configuration objects** having the following
|
655 | structure:
|
656 |
|
657 | - `host`: *String*
|
658 |
|
659 | - `port`: *Integer*
|
660 |
|
661 | - `delay`: *Integer*
|
662 |
|
663 | - `root`: *String*
|
664 |
|
665 | - `mock`: *Object*
|
666 |
|
667 |
|
668 | ## Outstanding Features / Fixes
|
669 |
|
670 | - Add a grunt task
|
671 | - Create a repository with a demo/example project
|
672 | - Add Support for a `Subfile.js`
|
673 | - Correctly write out source maps for JS and CSS files
|
674 |
|
675 |
|
676 | ## License
|
677 |
|
678 | **Substrat** is licenses under MIT.
|
679 |
|