grunt-croc-requirejs

0.3.0 • Public • Published

grunt-croc-requirejs

NPM

Build RequireJS-based applications optimizing js, css and Handlebars templates.

Getting Started

If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:

npm install grunt-croc-requirejs --save-dev

Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:

grunt.loadNpmTasks('grunt-croc-requirejs');

Or just use matchdep module.

Overview

Why do we need another Grunt task for merging RequireJS modules if there're plenty of them? There is a major difference - this task generates a layered application, i.e. all js scripts are not just combined into a single file but instead they are combined into several files - "layers". Here's motivation for such an approach.
Usually a front-end application consists of a bunch of scripts. Some of them belongs to the application but other ones are some 3rd-party libraries. When an application is being developed app scripts are usually changed often but 3rd-party scripts much more rarely. So if we combine all scripts into a single one "mega" script then end-users of the application will have to redownload that script on every change.
This task allows to split scripts into layers so end-users will redownload only changed part of the application.

Also the plugin provides some usefull optimization tools which allows you to:

  • combine all CSS files imported by AMD modules into a single CSS-script ("generic.css")
  • precompile Handlebars-templates imported by modules

CSS

xcss plugin allows a module to import its CSS dependencies as regular AMD-modules.
Usage example:

define([
    "jquery",
    "xcss!lib/ui/styles/jquery.blocked.css"
], function ($) {
});

Here we imported a CSS-file (jquery.blocked.css) as AMD-module.
In run-time xcss plugin requests the CSS-file via XHR and adds its content into STYLE tag (under HEAD). So in a development environment CSS-script are fetched and added into page's STYLE. The nice thing is that modules control their CSS-files on their own.
NOTE: All imports of CSS-files in all modules use a single STYLE tag to be IE-friendly.

In build-time xcss plugin writes down content of CSS-files into a single file (by default it's 'generic.css', but can be controlled by genericCssUrl option). So at the end we'll have a single CSS-file with content of all CSS-scripts imported via xcss plugin. Obviously that script should be loaded via link:

<link rel='stylesheet' href='/client/content/generic.css' type='text/css'>

But it's not enough. In run-time for optimized (or built) application xcss plugin will do nothing. It becomes just a stub. So in runtime there is no any overhead for componentization.

For loading CSS-files the plugin uses standard RJS's plugin text. It should be registered with alias "text".

Handlebars templates

xhtmpl plugin allows a module to import its Handlebars dependencies as regular AMD-modules.
Usage example:

define([
    "xhtmpl!lib/ui/Dialog/template.hbs"
], function (template) {
});

Here we imported a hbs-file (lib/ui/Dialog/template.hbs) as AMD-mobule. hbs-files are Handlebars templates. Actual files extension doesn't matter much. But it's usefull to name them as '*.hbs' due to HB-templates support in WebStorm and Visual Studio.

In runtime the plugin fetches specified template and compiles it via Handlebars. Handlebars compiles templates into js code (functions).
In build-time the plugin emits template's compiled js code as an AMD-module (the module returns template's function). In runtime for optimized (built) application xhtmlp plugin just returns already compiled function. So there is no compilation (i.e. overhead) in runtime for optimized applications.

For loading templates (*.hbs) the plugin uses standard RJS's plugin text. It should be registered with alias "text".

The rjs task

Overview

In your project's Gruntfile, add a section named rjs to the data object passed into grunt.initConfig().

grunt.initConfig({
  rjs: {
    options: {
      // Task-specific options go here.
    },
    your_target: {
      // Target-specific file lists and/or options go here.
    },
  },
})

Options

makeConfigFile

Type: String
Default value: make.config.json
Required: no

Build config file name. If the file exists then task will read its configuration from it. So if you create make.config.json beside Gruntfile.js then it will be used as task's config.
Please note that parameters values from Gruntfile.js will overwrite values from external config file.

input

Type: String
Required: yes

Input directory. It should point to a directory with root modules.

output

Type: String
Required: no

Output directory. If an output directory isn't specified then optimized files will be placed into input directory replacing its content.

buildDir

Type: String
Default value: .make_build/
Required: no

Temporary directory for build.

tempDir

Type: String
Default value: .make_tmp/
Required: no

Temporary directory for intermediate result (should be discarded)

keepBuildDir

Type: Boolean
Default value: false
Required: no

Do not remove buildDir and tempDir after build completes.

dirs

Type: Array of String
Required: no

Directories under input dir with standalone scripts (not referenced by app modules, usually injected on server) to optimize.

requireConfigFile

Type: String
Default value: require.config.json
Required: no

RequireJS config file name (relative to input dir).

requireConfig

Type: Object
Default value: undefined
Required: no

RequireJS config JSON object. It will override config from 'requireConfigFile' option.

requireConfigOutput

Type: Object
Default value: undefined
Required: no

RequireJS config JSON for output optimized application (will override all other configs).

modules

Type: Array
Default value: undefined
Required: no

Array of main modules names. If empty then all *.js files directly under input directory will be used.

ignoreModules

Type: Array
Default value: undefined
Required: no

Array of modules names to ignore. They will not be included into layers modules

optimizeJs

Type: String
Default value: none
Required: no

Optimization method of *.js files in terms of RJS: none, uglify.

optimizeCss

Type: String
Default value: none
Required: no

Optimization method of *.css files in terms of RJS: none, standard, standard.keepLines.

generateSourceMaps

Type: Boolean
Default value: undefined
Required: no

r.js option: If the minifier specified in the "optimize" option supports generating source maps for the minified code, then generate them.

preserveLicenseComments

Type: Boolean
Default value: false
Required: no

r.js option: this option will turn off the auto-preservation of licence comments.

genericCssUrl

Type: String
Default value: content/generic.css
Required: no

Path to generic.css - css file for combining all css imported via xcss.js.

bundledLocale

Type: String
Default value: undefined
Required: no

A locale to bundle - i.e. resources imported via i18n rjs plugin will be included into optimized js-modules and all other resources will be left unchanged.
The value will be used as locale option for r.js.

layers

Type: Object
Default value: undefined
Required: no

An object-map with a mapping of layer name to folder or array of folders.
By default (if no option specified) task will use:

{ 
    "lib-layer": ["lib","modules"],
    "vendor-layer": "vendor"
}

That means that scripts from "lib" and "modules" folders will be combined into "lib-layer" script, and scripts from "vendor" folder - into "vendor-layer".
An example of override:

{
    "lib-layer": "lib",
    "modules": "modules",
    "vendor-layer": "vendor"
},

layerOverrides

Type: Object
Required: no

An object-map with options overrides for particilar layers.
For example you want to minify/uglify all modules except main and report-main:

{
        "optimizeJs": "uglify2",
        "layerOverrides": {
            "main": {
                "optimizeJs": "none"
            },
            "report-main": {
                "optimizeJs": "none"
            }
        }
}

Project structure assumptions

The plugin assumes that client-side of a project has the following structure:

  • all client stuff is inside a single folder (let's call it client root);
  • client root contains root modules - usually these are scripts which are loaded by RequireJS directly (e.g. via data-main attribute) - i.e. main scripts in terms of RequireJS;
  • client root contains subfolders which are treated as layers. Usually we have app, lib and vendor folders (the app can also have any other folders). Scripts (*.js) which are in these layer-folders will be bundled together.

Core concepts

rjs optimizer

RequireJS provides rjs optimizer for building optimized version of apps. rjs is a script to be run under node.js. It has pletly of options - see the doc. This plugin grunt-croc-requirejs is just a wrapper around rjs optimizer.

Root modules

modules option can specifies a bunch of files which will be treated as root modules. If modules options isn't specified then all *.js under inputDir will be treated as root modules. rjs optimizer has two modes: optimizing directory and optimizing files. The plugin uses the latter mode. Files for rjs optimizer are specified in modules option. So these files we're callign as root modules.

Layers

By default if you supply a main script to rjs optimizer it'll combine all depedency tree into a single minified script. rjs does support layers and shared scripts for multi-page applications - see this official sample. But it requires to do a lot of configuration. This plugin supports splitting scripts onto layers depending on folder structure. For example let's consider an application has the following structure:

───Server
   ├───client
   │   ├───app
   │   │   ├───templates
   │   │   └───ui
   │   │       └───styles
   │   ├───boot
   │   ├───content
   │   │   ├───fonts
   │   │   └───images
   │   ├───lib
   │   │   └───...
   │   ├───modules
   │   │   └───...
   │   ├───shim
   │   ├───vendor
   │   │   └───...
   │   └───main.js

Here all client code is placed under client folder (client root). Under client root we can see app, lib, vendor and some other folders. They will layers names. After optimization we'll get:

  • main.js - all scripts imported from main.js except those which included into lib/vendor layers
  • lib-layer.js - all scripts from "lib" and "modules" folders (which are used by other scripts starting from main.js)
  • vendor-layer.js - all scripts from "vendor" folder

Usage Examples

Common options

grunt.initConfig({
        rjs: {
            dist: {
                options: {
                    input: 'dist/dev',
                    output: 'dist/prod',
                    requireConfigFile: 'require.config.json',
                    requireConfigOutput: {
                        paths: {
                            handlebars: "vendor/handlebars/handlebars.runtime"
                        }
                    },
                    genericCssUrl: 'content/generic.css',
                    optimizeJs: 'uglify',
                    optimizeCss: 'standard',
                    generateSourceMaps: true,
                    bundledLocale: 'ru'
                }
            }
        }
})

Passing options for UglifyJS

If optimizeJs option specified as uglify then output files will be processed with UglifyJS. It also has different options which you'd want to override. For example, by default UglifyJS removes quotes for keys if they are not reserved words. This behavior can lead to runtime errors in IE8.
Here's example how to pass options into UglifyJS via Grunt task's options:

        rjs: {
            dist: {
                options: {
                    optimizeJs: 'uglify',
                    requireConfig: {
                        uglify: {
                            output: {
                                quote_keys: true
                            }
                        }
                    },
                }
            }
        }

Release History

  • 2017-12-01 v0.3.0 Added layers option to override folder-layer mapping
  • 2017-08-28 v0.2.0 Support multiple folders for a layer (by default modules from "lib" and "modules" folders go to "lib-layer").
    Option layerOverrides for overriding options for particular layers.
  • 2016-12-26 v0.1.5 Updated README, updated dev-dependencies
  • 2015-12-07 v0.1.3 fix for NPM3 support
  • 2015-09-01 v0.1.0 Updated README
  • 2013-12-03 v0.1.0 Task submitted
  • 2013-08-27 v0.0.0 Work started

Task submitted by Sergei Dorogin

(c) Copyright CROC Inc. 2013-2017

Package Sidebar

Install

npm i grunt-croc-requirejs

Weekly Downloads

11

Version

0.3.0

License

MIT

Last publish

Collaborators

  • shrike