grunt-bake

1.9.0 • Public • Published

grunt-bake

Build Status Downloads Version

Bake static pages for production while using modular files while in development.

Getting Started

This plugin requires Grunt ~0.4.0

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-bake --save-dev

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

grunt.loadNpmTasks( "grunt-bake" );

The "bake" task

Overview

This module helps creating static pages while still having the coding comfort of multiple small files. It also helps not to repeat yourself as includes can be used at multiple places.

The module parses the files recursivly, meaning it allows for nested includes. While parsing the includes it also performs a simple find and replace on placeholders. The replacements are supplied in a JSON file but more an here.

When grunt-bake parses files it looks for anchors like this: <!--(bake path/to/file.html)-->.

Setup the bake task like so:

grunt.initConfig( {
    bake: {
        your_target: {
            options: {
                // Task-specific options go here.
            },
 
            files: {
                // files go here, like so:
 
                "dist/index.html": "app/index.html",
                "dist/mobile.html": "app/mobile.html"
 
                // etc ...
            }
        },
    },
} )

With a app/index.html file like this one:

<html>
    <head></head>
    <body>
        <!--(bake includes/container.html)-->
        <!--(bake includes/footer.html)-->
    </body>
</html>

The paths given are relative to the file being parsed.

Options

options.content

Type: String or Object or Function Default value: null

A string value that determines the location of the JSON file that is used to fill the place holders. If a Object is specified it will be used as content. If a Function is specified its return (should be JSON) will be used as content.

Additionally to the content provided, bake comes with a set of default values that are attached to a __bake object which gets injected to the user content. Even if no content is provided.

__bake.filename // the file path tbeing baked
__bake.srcFilename // same as __bake.filename
__bake.destFilename // the file path it is being written to
__bake.timestamp // a timestamp (milliseconds) at baking

These can be especially usefull in combnation with transforms.

<html>
    <head></head>
    <body>
        <!--(bake-start)-->
        {{__bake.destFilename}} was written at {{__base.timestamp | parseDate }}
        <!--(bake-end)-->
    </body>
</html>

options.section

Type: String Default value: ""

A string that determines which subsection of the JSON passed as content should be used. If no section is passed the entire JSON will be used for templating.

Given a content JSON like such:

{
    "en": {
        "title": "Book",
 
        "info": {
            "author": "Joe Do",
            "job": "Writer"
        }
    },
 
    "de": {
        "title": "Buch",
 
        "info": {
            "author": "Joe Do",
            "job": "Schreiber"
        }
    }
}

If "en" is passed as section, { "title": "Book", "info": { ... } } will be passed to the include. If no section is specified the entire JSON will be passed.

This could be used to parse a template like such:

<div>{{title}}</div>
<div>
    <span>{{info.author}}</span>
    <span>{{info.job}}</span>
</div>

options.parsePattern

Type: Regex Default value: /\{\{\s?([\.\-\w]*)\s?\}\}/g

Determines the regex to parse the files in order to insert the content from the JSON file. The default pattern allows place holders such as: {{value}}.

options.process

Type: Function Default value: default process procedure

A Function which is used to process the template before putting it into the file. If process is defined as null or false the includes won't be processed at all. The default process uses two curly braces as marker, such as {{json.value.name}}. However the parse regex is costumizable using options.parsePattern.

The function gets passed two arguments:

  • String: representing the template to parse.
  • Object: the content from the JSON file as object.

options.basePath

Type: String Default value: ""

Determines the base directory for includes that are specified with an absolute path. All paths starting with an / are absolute while other paths starting with folder or file names are relative to the include being parsed.

<!--(bake includes/footer.html)--> relative to the file

<!--(bake /includes/footer.html)--> relative to the basePath (level of Gruntfile by default)

options.transforms

Type: Object Default value: {}

Registers callbacks that can be used as transforms in the template with {{myvar | upper}}. It is possible to chain transforms like {{myvar | upper | nl2br}}.

transforms: {
    upper: function(str) {
        return String(str).toUpperCase();
    },
    nl2br: function(str) {
        // ...
    }
}

Transforms support parameters like {{myvar | replace:'A':'B'}}. Parameters are handed into the callback as additional parameters.

transforms: {
    // str => content of myvar,  searchvalue => 'A',  newvalue => 'B'
    replace: function(str, searchvalue, newvalue) {
        return String(str).replace(searchvalue, newvalue);
    }
}

options.semanticIf

Type: Bool | Array | Function Default value: false

Set to true enables support for no/yes and off/on in _if statements. Alternatively false values can be defined via Array or a callback can be used for evaluation.

options.removeUndefined

Type: Bool Default value: true

Set to false, placeholders that could not be resolved (= no matching key in content) will be kept untouched in the output.

options.variableParsePattern

Type: Regex Default value: /\{\{!\s*([^\}]+)\s*\}\}/

This regex is used to parse the variable specified inline with the bake task. Any inline attribute that is not preficed with an unerscore such as _if and _section is considered a variable and is passed to the bake include. For more detail check out the section on Inline Attributes. However, if you want to pass not a value but a reference to an different object you can do so by writing the inline value as variable="{{!foo.bar}}". Mind the exclamation mark. Assuming bar is an object as well, this will give you a reference to bar instead of the string.

Usage Examples

Simple bake

This example shows a simple baking process with all default options.

grunt.initConfig( {
    bake: {
        build: {
            files: {
                "app/index.html": "app/base.html"
            }
        }
    }
} )

app/base.html:

<html>
    <body>
        <!--(bake includes/container.html)-->
    </body>
</html>

app/includes/container.html:

<div id="container"></div>

This bake task will create app/index.html:

<html>
    <body>
        <div id="container"></div>
    </body>
</html>

Bake with content

This example shows how to use the bake process to parse the templates with a provided JSON and a section.

grunt.initConfig( {
    bake: {
        build: {
            options: {
                content: "app/content.json",
                section: "en"
            }
 
            files: {
                "app/index.html": "app/base.html"
            }
        }
    }
} )

app/content.json:

{
    "en": {
        "title": "Hello World"
    },
 
    "de": {
        "title": "Hallo Welt"
    }
}

app/base.html:

<html>
    <body>
        <!--(bake includes/container.html)-->
    </body>
</html>

app/includes/container.html:

<div id="container">{{title}}</div>

This bake task will create app/index.html:

<html>
    <body>
        <div id="container">Hello World</div>
    </body>
</html>

Inline attributes

In addition to the file the bake anchor tag also allows for inline attributs which will override the content from the JSON file. Note: Please note that the parsing of inline attributes requires double quotes in the definition as shown in the example

Same scenario as above.

app/base.html:

<html>
    <body>
        <!--(bake includes/container.html title="Salut Monde" name="Mathias")-->
    </body>
</html>

app/includes/container.html:

<div id="container">{{title}}</div>
<span>{{name}}</span>

This bake task will create app/index.html:

<html>
    <body>
        <div id="container">Salut monde</div>
        <span>Mathias</span>
    </body>
</html>

IF Statement

The bake task also allows a simple if conditional. Inline attributes named _if are treated as such. If the value that _if holds can't be found in the content.json or if found equals to the value false the include will be ignored. The _if can also be used inverted to create a _else effect in a way. A definition as _if="!name" would mean the template will be rendered when name cannot be found or is false.

Alternativly, _if suppoerts two operators. the == and the != operator. This allows to specify the name of the value and the content in single quotes, if the content is a string. Note: This is a simple implementation of the equals operator and is based solely on strings.

app/base.html:

<html>
    <body>
        <!--(bake includes/container.html _if="name")-->
 
        <!--(bake includes/other.html _if="foo == 'bar'")-->
    </body>
</html>

includes/other.html:

<span>{{foo}}</span>

app/content.json:

{
    "foo": "bar"
}

This bake task will create app/index.html:

<html>
    <body>
 
        <span>bar</span>
    </body>
</html>

Additionally the _if statement also works with inlining the bake content.

<html>
    <body>
        <!--(bake-start _if="name")-->
        <h1>{{name}}</h1>
        <!--(bake-end)-->
    </body>
</html>

Foreach Loop

Another special inline attribute is the _foreach attribute. This keyword expects a specific syntax and can be used both inline as well as pulling content from the json. This allows to loop over a set of values and using that value in the partial. It accepts an inline syntax: _foreach="name:[mike, drew, steve]" as well as a reference to an array in the json: _foreach="name:authors.names". The values from the array can then be used with the key name. This key can be chosen arbitrarily.

app/base.html:

<html>
    <body>
        <ul class="first">
            <!--(bake includes/li.html _foreach="name:[mike, drew, steve]")-->
        </ul>
 
        <ul class="second">
            <!--(bake includes/li.html _foreach="name:authors.names")-->
        </ul>
    </body>
</html>

app/includes/li.html:

<li>{{name}}</li>

app/content.json:

{
    "authors": {
        "names": [ "jenna", "carla", "susy" ]
    }
}

This bake task will create app/index.html:

<html>
    <body>
        <ul class="first">
            <li>mike</li>
            <li>drew</li>
            <li>steve</li>
        </ul>
 
        <ul class="second">
            <li>jenna</li>
            <li>carla</li>
            <li>susy</li>
        </ul>
    </body>
</html>

Just like the _if statement the _foreach also works with inlined content:

<html>
    <body>
        <ul>
        <!--(bake-start _foreach="name:[robert, susan, carl]")-->
            <li>{{name}}</li>
        <!--(bake-end)-->
        </ul>
    </body>
</html>

Bake automatically supplies meta information for each loop, like current index. Values can be accessed by the defined key followed by @index, @iteration, @first, @last, or @total.

<html>
    <body>
        <ul>
        <!--(bake-start _foreach="name:[Robert, Susan, Carl]")-->
            <li><a href="#anchor-{{name@iteration}}">{{name}}</a></li>
        <!--(bake-end)-->
        </ul>
    </body>
</html>

This will render the following:

<html>
    <body>
        <ul>
            <li><a href="#anchor-1">Robert</a></li>
            <li><a href="#anchor-2">Susan</a></li>
            <li><a href="#anchor-3">Carl</a></li>
        </ul>
    </body>
</html>

Inline Section statement

The _section attribute, when used inline, allows to use a specific subcontent of the values.

app/base.html:

<html>
    <body>
        <!--(bake includes/file.html _section="home")-->
        <!--(bake includes/file.html _section="about")-->
    </body>
</html>

app/includes/file.html:

<h1>{{title}}</h1>
<p>{{content}}</p>

With the following content file

{
    "home": {
        "title": "Home",
        "content": "This is home"
    },
    "about": {
        "title": "About",
        "content": "This is about"
    }
}

This will render the following:

<html>
    <body>
        <h1>Home</h1>
        <p>This is home</p>
        <h1>About</h1>
        <p>This is about</p>
    </body>
</html>

Inline _render statement

The _render statement simular to the _if statement determines whether or not the include is parsed. However the _render statement looks for it's counterpart in the options not in the content JSON. It then determines whether or not the field exists and if so, if the field has a truthy value. If the field doesnt exist the _render will be ignored. If it does existes a true value will render the template and a false value will skip the template.

app/base.html:

<html>
    <body>
        <!--(bake includes/file.html _render="baseline")-->
    </body>
</html>

With the following grunt task:

bake: {
    your_target: {
        options: {
            baseline: false
        },
 
        files: {
            "dist/index.html": "app/base.html"
        }
    },
}

This will create:

dist/index.html:

<html>
    <body>
    </body>
</html>

Inline _assign statement

The _assign statement determines to save included content into a variable instead of placing it directly. The variables name is defined by _assign-value.

app/base.html:

<html>
    <body>
        <!--(bake includes/file.html _assign="foo")-->
        {{foo}}
        <p>{{foo}}</p>
    </body>
</html>

app/includes/file.html:

<span>Hello World</span>

This will create:

dist/index.html:

<html>
    <body>
        <span>Hello World</span>
        <p><span>Hello World</span></p>
    </body>
</html>

Inline _process statement

Set to true the _process statement prevents bake from processing the included files content. The include takes place, but neither placeholders become replaced nor further bake sections processed.

app/base.html:

<html>
    <body>
        <!--(bake includes/file.html _process="false")-->
    </body>
</html>

app/includes/file.html:

<!--(bake includes/other.html)-->
<span>{{foo}}</span>

This will create:

dist/index.html:

<html>
    <body>
        <!--(bake includes/other.html)-->
        <span>{{foo}}</span>
    </body>
</html>

Bake extra pages (e.g. detail pages)

Another special inline attribute is the _bake attribute. This keyword expects a specific syntax which allows to dynamically create additional files. It accepts the syntax: _bake="template.html > target.html".

The following example will create two additional files named info-John.html and info-Jane.html which will be baked using app/detail.html with corresponding values from app/content.json. For linking to genereated files a @link variable is available. For linking the originating file from generated files a @referrer variable is available.

app/detail.html:

<html>
    <body>
        <h1>My name is {{member.name}}</h1>
        <p>I am a {{member.profession}}</p>
        <p>
            <a href="{{@referrer}}">Back to team</a>
        </p>
    </body>
</html>

app/base.html:

<html>
    <body>
        <ul>
            <!--(bake li.html _foreach="member:members" _bake="detail.html > member-{{member.name}}.html")-->
        </ul>
    </body>
</html>
 

app/li.html:

    <li><a href="{{@link}}">More about {{member.name}}</a></li>

app/content.json:

{
    "members": [
        {
            "name": "John",
            "profession": "Dentist"
        },
        {
            "name": "Jane",
            "profession": "Pilot"
        }
    ]
}

Alternative app/base.html with inline-section instead of additional app/li.html file:

app/base.html:

<html>
    <body>
        <ul>
            <!--(bake-start _foreach="member:members" _bake="detail.html > member-{{member.name}}.html")-->
                <li><a href="{{@link}}">More about {{member.name}}</a></li>
            <!--(bake-end)-->
        </ul>
    </body>
</html>

app/detail.html and app/content.json same as above.

Custom process

This example shows the use of a custom process function.

 
var processFunction( source, content ) {
    return source + "<br>";
}
 
grunt.initConfig( {
    bake: {
        build: {
            options: {
                content: "app/content.json",
                section: "en",
                process: processFunction
            },
 
            files: {
                "app/index.html": "app/base.html"
            }
        }
    }
} )

Continuous development

For ease of development just add the bake task to your watch list. The static page will be baked every time you change the template.

watch: {
    bake: {
        files: [ "app/includes/**" ],
        tasks: "bake:build"
    }
}

Changelog

  • 1.9.0 1-2-2018 Adds variableParsePattern for inline variables.
  • 1.8.0 4-20-2016 Adds permanent variables under __bake.
  • 1.7.2 4-20-2016 Resolves recursion issues in _process and _assign.
  • 1.7.1 4-8-2016 Fix for issue with _process.
  • 1.7.0 4-7-2016 Adds _process and _assign attributes.
  • 1.6.4 4-4-2016 Bug fixes.
  • 1.6.3 2-26-2016 Allow inline section attribute to have multiple leves.
  • 1.6.2 2-26-2016 Update dependecies.
  • 1.6.1 2-11-2016 fixes error for options.section on multiple files.
  • 1.6.0 2-10-2016 adds support for parameters in transforms. Also introduces a breaking change away from transformGutter.
  • 1.5.1 2-9-2016 adds @referrer attribute to _bake.
  • 1.5.0 2-2-2016 adds support for _bake attribute.
  • 1.4.1 2-2-2016 fixes minor bug fix #72.
  • 1.4.0 1-30-2016 adds full JS support for evaluating _if.
  • 1.3.1 1-20-2016 adds support for parsing values in inline variables.
  • 1.3.0 1-13-2016 adds support for parsing file paths in bake tag.

Package Sidebar

Install

npm i grunt-bake

Weekly Downloads

252

Version

1.9.0

License

none

Last publish

Collaborators

  • david-zacharias
  • roboshoes