grunt-collection-helper

Helper function to be used with Grunt, for defining resource collections in packages.

npm install grunt-collection-helper
6 downloads in the last week
34 downloads in the last month

Build Status

grunt-collection-helper

grunt-collection-helper is designed to aid the construction of resource collections from description files. It aims to separate the description of the list of resources, from their way of usage from the Gruntfile.js.

Simple usage:

uglify: {
    default: {
        dest: 'dist/full.min.js',
        src: collect.select('full.js')
        )
    }
}

provided collection.js defines a list of resources for full.js:

{
    'full.js': {
        'src/module1.js',
        'src/module2.js',
        'src/sub/module09.js'
    }
}

The collection.json can be provided locally, or originated from a package installed by a supported packaging / installation method.

Goals

  • provides a way to describe a named collection of resource lists in a JSON description file

  • provides a simple API usable from a Gruntfile.js script, to select the list of resources from the above files by their name. The resulting resource lists can be used:

    • with any grunt task and file mapping format

    • in a flexible way, as the collection helper does not support or dictate their way of usage, it merely provides the lists themselves

  • can support multiple packaging / installation methods via its 'locators'

    • currently local() and bower() are implemented

Installation

The package can be installed with npm.

$ npm install grunt-collection-helper

Dependency from package.json

You may want to automate the installation of this package from the same way you have installed grunt packages from. Normally, put something like the following into the package.json next to the Gruntfile.js where you want to use the collection helper from:

"devDependencies": {
    "grunt": ">=0.4",
    ...
    "grunt-collection-helper": "*"
}

Development installation

With npm:

$ npm install git://github.com/reebalazs/grunt-collection-helper.git

How the selection API work

Import the package first. (Assumed in all of the following examples.)

var collect = require('grunt-collection-helper');

Following this you can access the API through the collect variable from an expression.

The API is designed to be very simple: you need to apply a locator first to specify the root of a package, and then select a collection of resources (select(...)), or refer to a specific resource relative from within that package (path(...)).

collect.<LOCATOR>(...).select(str)
collect.<LOCATOR>(...).path(str)

The collection.json configuration file

As an example, let us create a collection.json file in the same directory where our Gruntfile.js resides. For example, it could have the following simple content:

{
    "flower.js": ["js/rose.js", "js/bluebell.js"],
    "fruit.css": ["css/apple.css", "css/orange.css"]
}

This describes the construction order for flower.js and fruit.css resources. The resource paths are specified relative from the collection.json file.

The file must contain a dictionary in valid JSON format. This dictionary contains named lists of resource paths. A script then can query for flower.js, and receive the list of the files associated with this name. After this, the script will process this list according to the way it needs to.

Locators

The resources are always selected relatively to their 'package root', depending the installation method. The collection helper provides 'locators' to find the root of a given package.

The local() locator

The following expressions will select the construction order for the specified resource:

collect.local().select('flower.js')
        //=> ['js/rose.js', 'js/bluebell.js']
collect.local().select('fruit.css')
        //=> ['css/apple.css',  'css/orange.css']

A path parameter can be specified to local, where a collection.json file will be read from. For example, src/scrumptious/collection.json could contain:

{
    "cakes.js": ["js/dobos.js", "js/tiramisu.js"],
    "nuts.css": ["css/almond.css",  "css/chestnut.css"]
}

This would make the following expressions work:

collect.local('src/scrumptious').select('cakes.js')  
        //=> ['js/dobos.js', 'js/tiramisu.js']
collect.local('src/scrumptious').select('nuts.css')
        //=> ['css/almond.css',  'chestnut.css']

The local() locator is the default locator

In case the local locator stands without parameters, it can be omitted.

collect.select('flower.js')

is equivalent to:

collect.local().select('flower.js')

and the followings resolve as:

collect.select('flower.js')
        //=> ['js/rose.js', 'js/bluebell.js']
collect.select('fruit.css')
        //=> ['css/apple.css',  'css/orange.css']

The bower locator

The following expression finds the source for a given package (in our example the boo package), that has been installed with bower:

collect.bower('boo').select('boolib.js')

This also assumes that the 'boo' package has a collection.json that defines a list for the key boolib.js. In other words, selection works the same way with each locator, only the initial package directory is different.

Using of the 'path' method makes it possible to refer to a resource relative to the bower package root, independently from a collection.json in that package. path(...) will always return a string.

collect.bower('coo').path('fun/entertainment/discoo.js')
        //=> './components/coo/fun/entertainment/discoo.js'
collect.bower('coo').path('agriculture/food/coocumber.css')
        //=> './components/coo/agriculture/food/coocumber.css'

The locator will find the 'components' package each time by walking up to the parent folders, in the above example it was found in the same directory where Gruntfile.js resides.

Using main property from bower's component.json

In addition to a composition order given in component.json, the main property of Bower's component.json can also be used. It will add main.<EXT> keys which group the files from main by file extension.

collect.bower('jquery').select('main.js')


// component.json:                generates into collection.js:
// 
// main: 'single.js',        =>   {'main.js': ['single.js']}
// 
// main: ['apple.js',        =>   {'main.js': ['apple.js', 'tree/orange.js']}
//        'tree/orange.js']
//
// main: ['apple.js',        =>   {'main.js': ['apple.js', 'tree/orange.js'],
//        'tree/orange.js',        'main.css': ['cucumber.css']}
//         'cucumber.css']

The keys in collection.json will take precedent: if it already defines a main.js, it will win over the main value from component.json.

Other locators

It is extendable. We can easily create a jamjs, ... locator.

A locator can accept any number of parameters.

Future plans

Consider settings from .bowerrc in finding the components directory

.bowerrc provides a way to specify a different components directory for bower to use. We currently ignore this and always use components.

Parametrized selection

At some time later in the future, a more flexible way of parametrizing the selection could also be supported:

collect.bower('foo').select({'features': ['drag', 'drop']})
    // uses a function from collection.js

Composition of lists with collection.json

Let us extend our collection.json as follows:

{
    "flower.js": ["js/rose.js", "js/bluebell.js"],
    "fruit.css": ["css/apple.css",  "css/orange.css"],
    "dist.js": [
        ["bower", "jquery", "jquery.js"],
        ["bower", "bootstrap", "docs/assets/js/bootstrap.js"]
    ]
}

If the resource is represented by an array instead of a string, then the first parameter will select a locator, which will receice the rest of the parameter except the last one. The last parameter will be used to select the path of the resource relative inside the package.

For example, ["bower", "jquery", "jquery.js"] will be translated to bower('jquery').path('jquery.js').

collect.select('dist.js')
        //=> ['./components/jquery/jquery.js',
        //    './components/bootstrap/docs/assets/js/bootstrap.js']

Example usage from the Gruntfile.js

As the select function returns just normal arrays, it can be used with any grunt plugin, and in various combinations, from Gruntfile.js. Consider the following examples:

concat: {
    'default': {
        files: {
            'dist/flower.js':
                collect.select('flower.js'),
            'src/scrumptious/dist/nuts.css':
                collect.local('src/scrumptious').select('nuts.css')
        }
    }
},

or (a watch rule where adding the lists is demonstrated. Note that we used [].concat(...), because lists are not addable with the plus operator in JavaScript):

watch: {
    'default': {
        files: [].concat(
            collect.select('flower.js'),
            collect.local('src/scrumptious').select('nuts.css'),
        ),
        tasks: ['concat:default']
    },
},

or (when we also add files to the collections manually):

watch: {
    'default': {
        files: [].concat(
            collect.select('flower.js'),
            collect.local('src/scrumptious').select('nuts.css'),
            [
                collect.bower('jquery').path('jquery.js')
            ]
        ),
        tasks: ['concat:default']
    },
},

or (an uglify rule, where we use a different src - dest mapping format than previously):

uglify: {
    default: {
        dest: 'dist/full.min.js',
        src: [].concat(
            collect.select('flower.js'),
            collect.local('src/scrumptious').select('cakes.js'),
            collect.bower('jquery').select('main.js')
        )
    }
}

or (to see composition of lists at work, with our extended collection.json):

uglify: {
    default: {
        dest: 'dist/full.min.js',
        src: collect.select('dist.js')
    }
},
watch: {
    'default': {
        files: collect.select('dist.js'),
        tasks: ['uglify:default']
    },
}
npm loves you