From 732d57e935a7cfb80d62c3d1d90da0f1ac51a0e9 Mon Sep 17 00:00:00 2001 From: Andrey Mereskin Date: Thu, 7 Mar 2013 08:04:08 -0500 Subject: [PATCH] Allow compilation of templates to Javascript functions, wrapped for JST or AMD. Closes gh-14. --- Gruntfile.js | 37 ++++++++++ README.md | 45 +++++++++++- docs/jade-options.md | 44 +++++++++++ package.json | 3 +- tasks/jade.js | 117 ++++++++++++++++++++++++------ test/expected/amd/jade.js | 18 +++++ test/expected/amd/jade2.js | 18 +++++ test/expected/amd/jadeInclude.js | 18 +++++ test/expected/amd/jadeTemplate.js | 16 ++++ test/expected/jst/jade.js | 16 ++++ test/expected/jst/jade2.js | 16 ++++ test/expected/jst/jadeInclude.js | 16 ++++ test/expected/jst/jadeTemplate.js | 14 ++++ test/jade_amd_test.js | 27 +++++++ test/jade_jst_test.js | 27 +++++++ 15 files changed, 409 insertions(+), 23 deletions(-) create mode 100644 test/expected/amd/jade.js create mode 100644 test/expected/amd/jade2.js create mode 100644 test/expected/amd/jadeInclude.js create mode 100644 test/expected/amd/jadeTemplate.js create mode 100644 test/expected/jst/jade.js create mode 100644 test/expected/jst/jade2.js create mode 100644 test/expected/jst/jadeInclude.js create mode 100644 test/expected/jst/jadeTemplate.js create mode 100644 test/jade_amd_test.js create mode 100644 test/jade_jst_test.js diff --git a/Gruntfile.js b/Gruntfile.js index aecaca8..a8c7c4f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -43,6 +43,43 @@ module.exports = function(grunt) { year: '<%= grunt.template.today("yyyy") %>' } } + }, + + compile_amd: { + files: { + 'tmp/amd/jade.js': ['test/fixtures/jade.jade'], + 'tmp/amd/jade2.js': ['test/fixtures/jade2.jade'], + 'tmp/amd/jadeInclude.js': ['test/fixtures/jadeInclude.jade'], + 'tmp/amd/jadeTemplate.js': ['test/fixtures/jadeTemplate.jade'] + }, + options: { + client: true, + amd: true, + namespace: false, + compileDebug: false, + data: { + test: true, + year: '<%= grunt.template.today("yyyy") %>' + } + } + }, + + compile_jst: { + files: { + 'tmp/jst/jade.js': ['test/fixtures/jade.jade'], + 'tmp/jst/jade2.js': ['test/fixtures/jade2.jade'], + 'tmp/jst/jadeInclude.js': ['test/fixtures/jadeInclude.jade'], + 'tmp/jst/jadeTemplate.js': ['test/fixtures/jadeTemplate.jade'] + }, + options: { + client: true, + compileDebug: false, + processName: function(str) { return str.match(/^test\/fixtures\/(.*)\.jade$/)[1]; }, + data: { + test: true, + year: '<%= grunt.template.today("yyyy") %>' + } + } } }, diff --git a/README.md b/README.md index b3eb0b3..ccd1caa 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,49 @@ Type: `Object` Sets the data passed to `jade` during template compilation. Any data can be passed to the template (including `grunt` templates). +#### comileDebug +Type: `Boolean` + +Set `compileDebug: fase` to remove `jade` debug instructions in javascript templates. + +#### namespace +Type: `String` `false` +Default: 'JST' + +The namespace in which the precompiled templates will be assigned. *Use dot notation (e.g. App.Templates) for nested namespaces or false for no namespace wrapping.* When false with `amd` option set `true`, templates will be returned directly from the AMD wrapper. + +Example: +```js +options: { + namespace: 'MyApp.Templates' +} +``` + +#### amd +Type: `Boolean` +default: `false` + +Wraps the output file with an AMD define function and returns the compiled template namespace unless namespace has been explicitly set to false in which case the template function will be returned directly. + +```js +define(function() { + //...// + returns this['[template namespace]']; +}); +``` + +#### processName +Type: `function` + +This option accepts a function which takes one argument (the template filepath) and returns a string which will be used as the key for the precompiled template object. The example below stores all templates on the default JST namespace in capital letters. + +```js +options: { + processName: function(filename) { + return filename.toUpperCase(); + } +} +``` ### Usage Examples ```js @@ -134,4 +177,4 @@ jade: { Task submitted by [Eric Woroshow](http://ericw.ca/) -*This file was generated on Wed Feb 20 2013 12:35:52.* +*This file was generated on Wed Mar 06 2013 17:45:50.* diff --git a/docs/jade-options.md b/docs/jade-options.md index 4ce61d1..8086161 100644 --- a/docs/jade-options.md +++ b/docs/jade-options.md @@ -10,3 +10,47 @@ Output indented HTML. Type: `Object` Sets the data passed to `jade` during template compilation. Any data can be passed to the template (including `grunt` templates). + +## comileDebug +Type: `Boolean` + +Set `compileDebug: fase` to remove `jade` debug instructions in javascript templates. + +## namespace +Type: `String` `false` +Default: 'JST' + +The namespace in which the precompiled templates will be assigned. *Use dot notation (e.g. App.Templates) for nested namespaces or false for no namespace wrapping.* When false with `amd` option set `true`, templates will be returned directly from the AMD wrapper. + +Example: +```js +options: { + namespace: 'MyApp.Templates' +} +``` + +## amd +Type: `Boolean` +default: `false` + +Wraps the output file with an AMD define function and returns the compiled template namespace unless namespace has been explicitly set to false in which case the template function will be returned directly. + +```js +define(function() { + //...// + returns this['[template namespace]']; +}); +``` + +## processName +Type: `function` + +This option accepts a function which takes one argument (the template filepath) and returns a string which will be used as the key for the precompiled template object. The example below stores all templates on the default JST namespace in capital letters. + +```js +options: { + processName: function(filename) { + return filename.toUpperCase(); + } +} +``` \ No newline at end of file diff --git a/package.json b/package.json index 0df8176..d5214d2 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "test": "grunt test" }, "dependencies": { - "jade": "~0.27.2" + "jade": "~0.27.2", + "grunt-lib-contrib": "~0.5.1" }, "devDependencies": { "grunt-contrib-jshint": "~0.2.0", diff --git a/tasks/jade.js b/tasks/jade.js index e6edd01..08be9fa 100644 --- a/tasks/jade.js +++ b/tasks/jade.js @@ -9,39 +9,114 @@ 'use strict'; module.exports = function(grunt) { + var _ = grunt.util._; + var helpers = require('grunt-lib-contrib').init(grunt); - grunt.registerMultiTask('jade', 'Compile Jade templates into HTML.', function() { + // content conversion for templates + var defaultProcessContent = function(content) { return content; }; + + // filename conversion for templates + var defaultProcessName = function(name) { return name.replace('.jade', ''); }; + + grunt.registerMultiTask('jade', 'Compile jade templates.', function() { var options = this.options({ - data: {} + namespace: 'JST', + separator: grunt.util.linefeed + grunt.util.linefeed, + amd: false }); - grunt.verbose.writeflags(options, 'Options'); + var data = options.data; + delete options.data; + + var nsInfo; + + if(options.namespace !== false){ + nsInfo = helpers.getNamespaceDeclaration(options.namespace); + } + + // assign transformation functions + var processContent = options.processContent || defaultProcessContent; + var processName = options.processName || defaultProcessName; + this.files.forEach(function(f) { - var output = f.src.map(function(file) { - return compileJade(file, options, options.data); - }).join(grunt.util.normalizelf(grunt.util.linefeed)); + var templates = []; + + f.src.filter(function(filepath) { + // Warn on and remove invalid source files (if nonull was set). + if (!grunt.file.exists(filepath)) { + grunt.log.warn('Source file "' + filepath + '" not found.'); + return false; + } else { + return true; + } + }) + .forEach(function(filepath) { + var src = processContent(grunt.file.read(filepath)); + var compiled, filename; + filename = processName(filepath); + + options = grunt.util._.extend(options, { filename: filepath }); + + try { + compiled = require('jade').compile(src, options); + // if in client mode, return function source + if (options.client) { + compiled = compiled.toString(); + } else { + compiled = compiled(data); + } + + // if configured for amd and the namespace has been explicitly set + // to false, the jade template will be directly returned + if (options.client && options.amd && options.namespace === false) { + compiled = 'return ' + compiled; + } + } catch (e) { + grunt.log.error(e); + grunt.fail.warn('Jade failed to compile '+filepath+'.'); + } + if (options.client && options.namespace !== false) { + templates.push(nsInfo.namespace+'['+JSON.stringify(filename)+'] = '+compiled+';'); + } else { + templates.push(compiled); + } + }); + + var output = templates; if (output.length < 1) { grunt.log.warn('Destination not written because compiled files were empty.'); } else { - grunt.file.write(f.dest, output); - grunt.log.writeln('File ' + f.dest.cyan + ' created.'); + if (options.client && options.namespace !== false) { + output.unshift(nsInfo.declaration); + + if (options.node) { + output.unshift('var jade = jade || require(\'jade\').runtime;'); + + var nodeExport = 'if (typeof exports === \'object\' && exports) {'; + nodeExport += 'module.exports = ' + nsInfo.namespace + ';}'; + + output.push(nodeExport); + } + } + + if (options.amd) { + // Wrap the file in an AMD define fn. + output.unshift("define(['jade'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; }"); + if (options.namespace !== false) { + // Namespace has not been explicitly set to false; the AMD + // wrapper will return the object containing the template. + output.push("return "+nsInfo.namespace+";"); + } + output.push("});"); + } + + grunt.file.write(f.dest, output.join(grunt.util.normalizelf(options.separator))); + grunt.log.writeln('File "' + f.dest + '" created.'); } }); - }); - - var compileJade = function(srcFile, options, data) { - options = grunt.util._.extend({filename: srcFile}, options); - delete options.data; - var srcCode = grunt.file.read(srcFile); + }); - try { - return require('jade').compile(srcCode, options)(data); - } catch (e) { - grunt.log.error(e); - grunt.fail.warn('Jade failed to compile.'); - } - }; }; diff --git a/test/expected/amd/jade.js b/test/expected/amd/jade.js new file mode 100644 index 0000000..0941cf7 --- /dev/null +++ b/test/expected/amd/jade.js @@ -0,0 +1,18 @@ +define(['jade'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } + +return function anonymous(locals, attrs, escape, rethrow, merge) { +attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge; +var buf = []; +with (locals || {}) { +var interp; +buf.push('
data'); +if ( test) +{ +buf.push('
testing
'); +} +buf.push('
'); +} +return buf.join(""); +} + +}); \ No newline at end of file diff --git a/test/expected/amd/jade2.js b/test/expected/amd/jade2.js new file mode 100644 index 0000000..112288a --- /dev/null +++ b/test/expected/amd/jade2.js @@ -0,0 +1,18 @@ +define(['jade'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } + +return function anonymous(locals, attrs, escape, rethrow, merge) { +attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge; +var buf = []; +with (locals || {}) { +var interp; +buf.push('
data'); +if ( test) +{ +buf.push('
testing 2
'); +} +buf.push('
'); +} +return buf.join(""); +} + +}); \ No newline at end of file diff --git a/test/expected/amd/jadeInclude.js b/test/expected/amd/jadeInclude.js new file mode 100644 index 0000000..8a68bee --- /dev/null +++ b/test/expected/amd/jadeInclude.js @@ -0,0 +1,18 @@ +define(['jade'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } + +return function anonymous(locals, attrs, escape, rethrow, merge) { +attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge; +var buf = []; +with (locals || {}) { +var interp; +buf.push('TEST'); + var a = 'hello jade test' +buf.push('

'); +var __val__ = a +buf.push(escape(null == __val__ ? "" : __val__)); +buf.push('

'); +} +return buf.join(""); +} + +}); \ No newline at end of file diff --git a/test/expected/amd/jadeTemplate.js b/test/expected/amd/jadeTemplate.js new file mode 100644 index 0000000..d004aae --- /dev/null +++ b/test/expected/amd/jadeTemplate.js @@ -0,0 +1,16 @@ +define(['jade'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } + +return function anonymous(locals, attrs, escape, rethrow, merge) { +attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge; +var buf = []; +with (locals || {}) { +var interp; +buf.push('
'); +var __val__ = year +buf.push(escape(null == __val__ ? "" : __val__)); +buf.push('
'); +} +return buf.join(""); +} + +}); \ No newline at end of file diff --git a/test/expected/jst/jade.js b/test/expected/jst/jade.js new file mode 100644 index 0000000..29136f2 --- /dev/null +++ b/test/expected/jst/jade.js @@ -0,0 +1,16 @@ +this["JST"] = this["JST"] || {}; + +this["JST"]["jade"] = function anonymous(locals, attrs, escape, rethrow, merge) { +attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge; +var buf = []; +with (locals || {}) { +var interp; +buf.push('
data'); +if ( test) +{ +buf.push('
testing
'); +} +buf.push('
'); +} +return buf.join(""); +}; \ No newline at end of file diff --git a/test/expected/jst/jade2.js b/test/expected/jst/jade2.js new file mode 100644 index 0000000..73d994f --- /dev/null +++ b/test/expected/jst/jade2.js @@ -0,0 +1,16 @@ +this["JST"] = this["JST"] || {}; + +this["JST"]["jade2"] = function anonymous(locals, attrs, escape, rethrow, merge) { +attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge; +var buf = []; +with (locals || {}) { +var interp; +buf.push('
data'); +if ( test) +{ +buf.push('
testing 2
'); +} +buf.push('
'); +} +return buf.join(""); +}; \ No newline at end of file diff --git a/test/expected/jst/jadeInclude.js b/test/expected/jst/jadeInclude.js new file mode 100644 index 0000000..3c6b935 --- /dev/null +++ b/test/expected/jst/jadeInclude.js @@ -0,0 +1,16 @@ +this["JST"] = this["JST"] || {}; + +this["JST"]["jadeInclude"] = function anonymous(locals, attrs, escape, rethrow, merge) { +attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge; +var buf = []; +with (locals || {}) { +var interp; +buf.push('TEST'); + var a = 'hello jade test' +buf.push('

'); +var __val__ = a +buf.push(escape(null == __val__ ? "" : __val__)); +buf.push('

'); +} +return buf.join(""); +}; \ No newline at end of file diff --git a/test/expected/jst/jadeTemplate.js b/test/expected/jst/jadeTemplate.js new file mode 100644 index 0000000..42604a3 --- /dev/null +++ b/test/expected/jst/jadeTemplate.js @@ -0,0 +1,14 @@ +this["JST"] = this["JST"] || {}; + +this["JST"]["jadeTemplate"] = function anonymous(locals, attrs, escape, rethrow, merge) { +attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge; +var buf = []; +with (locals || {}) { +var interp; +buf.push('
'); +var __val__ = year +buf.push(escape(null == __val__ ? "" : __val__)); +buf.push('
'); +} +return buf.join(""); +}; \ No newline at end of file diff --git a/test/jade_amd_test.js b/test/jade_amd_test.js new file mode 100644 index 0000000..8654520 --- /dev/null +++ b/test/jade_amd_test.js @@ -0,0 +1,27 @@ +var grunt = require('grunt'); + +exports.jade = { + compile: function(test) { + 'use strict'; + + test.expect(4); + + var actual = grunt.file.read('tmp/amd/jade.js'); + var expected = grunt.file.read('test/expected/amd/jade.js'); + test.equal(expected, actual, 'should compile jade templates to js'); + + actual = grunt.file.read('tmp/amd/jade2.js'); + expected = grunt.file.read('test/expected/amd/jade2.js'); + test.equal(expected, actual, 'should compile jade templates to js (multiple files support)'); + + actual = grunt.file.read('tmp/amd/jadeInclude.js'); + expected = grunt.file.read('test/expected/amd/jadeInclude.js'); + test.equal(expected, actual, 'should compile jade templates to js with an include'); + + actual = grunt.file.read('tmp/amd/jadeTemplate.js'); + expected = grunt.file.read('test/expected/amd/jadeTemplate.js'); + test.equal(expected, actual, 'should compile jade templates to js with grunt template support'); + + test.done(); + } +}; \ No newline at end of file diff --git a/test/jade_jst_test.js b/test/jade_jst_test.js new file mode 100644 index 0000000..b389021 --- /dev/null +++ b/test/jade_jst_test.js @@ -0,0 +1,27 @@ +var grunt = require('grunt'); + +exports.jade = { + compile: function(test) { + 'use strict'; + + test.expect(4); + + var actual = grunt.file.read('tmp/jst/jade.js'); + var expected = grunt.file.read('test/expected/jst/jade.js'); + test.equal(expected, actual, 'should compile jade templates to JST template'); + + actual = grunt.file.read('tmp/jst/jade2.js'); + expected = grunt.file.read('test/expected/jst/jade2.js'); + test.equal(expected, actual, 'should compile jade templates to JST template (multiple files support)'); + + actual = grunt.file.read('tmp/jst/jadeInclude.js'); + expected = grunt.file.read('test/expected/jst/jadeInclude.js'); + test.equal(expected, actual, 'should compile jade templates to JST template with an include'); + + actual = grunt.file.read('tmp/jst/jadeTemplate.js'); + expected = grunt.file.read('test/expected/jst/jadeTemplate.js'); + test.equal(expected, actual, 'should compile jade templates to JST template with grunt template support'); + + test.done(); + } +}; \ No newline at end of file