Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Grunt task, command line, performance improvements

  • Loading branch information...
commit bcebb9d2e03f74f383e62d6a5ffad84be39abd5d 1 parent ab30f99
@daffl authored
View
3  .gitignore
@@ -12,4 +12,5 @@ logs
results
node_modules
-npm-debug.log
+npm-debug.log
+resources/js/resources.js
View
4 bin/can-compile
@@ -11,6 +11,10 @@ program.version(pkg.version)
.parse(process.argv);
var files = [];
+if(!program.args || program.args.length === 0) {
+ program.args = ['**/*.ejs', '**/*.mustache'];
+}
+
program.args.forEach(function(pattern) {
files = files.concat(globsync.glob(pattern));
});
View
11 empty.html
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>can.compile loader file</title>
-</head>
-<body>
- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.js"></script>
- <script type="text/javascript" src="http://canjs.us/release/latest/can.jquery.js"></script>
- <script type="text/javascript" src="http://canjs.us/release/latest/can.view.mustache.js"></script>
-</body>
-</html>
View
5 example/views.production.js
@@ -0,0 +1,5 @@
+(function(window, undefined) {
+var can = window.can;
+can.view.preload('view_ejs',can.EJS(function(_CONTEXT,_VIEW) { with(_VIEW) { with (_CONTEXT) {var ___v1ew = [];___v1ew.push("<h2>");___v1ew.push(can.view.txt(1,'h2',0,this,function(){ return message }));___v1ew.push("</h2>");; return ___v1ew.join('')}} }));
+can.view.preload('view_mustache',can.Mustache(function(_CONTEXT,_VIEW) { with(_VIEW) { with (_CONTEXT) {var ___v1ew = [];var ___c0nt3xt = []; ___c0nt3xt.___st4ck = true;var ___st4ck = function(context, self) {var s;if (arguments.length == 1 && context) {s = !context.___st4ck ? [context] : context;} else {s = context && context.___st4ck ? context.concat([self]) : ___st4ck(context).concat([self]);}return (s.___st4ck = true) && s;};___v1ew.push("<h2>");___v1ew.push(can.view.txt(1,'h2',0,this,function(){ return can.Mustache.txt(___st4ck(___c0nt3xt,this),null,can.Mustache.get("message",___st4ck(___c0nt3xt,this)));}));___v1ew.push("</h2>");; return ___v1ew.join('')}} }));
+})(this);
View
37 grunt.js
@@ -0,0 +1,37 @@
+/*global module:false*/
+module.exports = function (grunt) {
+
+ // Project configuration.
+ grunt.initConfig({
+ pkg : '<json:package.json>',
+ meta : {
+ resources : 'resources/js'
+ },
+ concat : {
+ resources : {
+ src : [ '<%= meta.resources %>/jquery.1.8.3.js', '<%= meta.resources %>/can.jquery.1.1.2.js',
+ '<%= meta.resources %>/can.view.mustache.1.1.2.js' ],
+ dest : '<%= meta.resources %>/resources.js'
+ }
+ },
+ min : {
+ resources : {
+ src : '<%= meta.resources %>/resources.js',
+ dest : '<%= meta.resources %>/resources.min.js'
+ }
+ },
+ cancompile : {
+ all : {
+ src : ['**/*.ejs', '**/*.mustache'],
+ // Change into the example folder
+ out : 'example/views.production.js'
+ }
+ }
+ });
+
+ grunt.loadTasks("./tasks");
+ // Default task.
+ grunt.registerTask('resources', 'concat:resources min:resources');
+ grunt.registerTask('default', 'resources');
+
+};
View
30 lib/compile.js
@@ -1,26 +1,34 @@
var Zombie = require("zombie");
var fs = require('fs');
-module.exports = function (files, callback) {
- Zombie.visit("file://" + __dirname + "/../empty.html", {
+module.exports = function (files, normalize, callback) {
+ files = Array.isArray(files) ? files : [files];
+ var normalizer = function(filename) {
+ return filename;
+ }
+ if(!callback) {
+ callback = normalize;
+ } else {
+ normalizer = normalize;
+ }
+
+ Zombie.visit("file://" + __dirname + "/../resources/loader.html", {
runScripts : true
}, function (err, browser, status) {
if (err) return callback(err);
var window = browser.document.window;
var can = window.can;
- var getScript = function (id, type, text) {
- if (!text && !type) {
- type = id.substring(id.lastIndexOf('.') + 1, id.length);
- text = fs.readFileSync(id).toString();
- }
- id = can.view.toId(id);
+ var getScript = function (filename) {
+ var type = filename.substring(filename.lastIndexOf('.') + 1, filename.length);
+ var text = fs.readFileSync(filename).toString();
+ // Create an id from the normalized filename
+ var id = can.view.toId(normalizer(filename));
+ // TODO throw error if type is not registered
var script = can.view.types["." + type].script(id, text);
return "can.view.preload('" + id + "'," + script + ");";
}
- var result = files.map(function(filename) {
- return getScript(filename);
- });
+ var result = files.map(getScript);
callback(null, result);
});
View
13 lib/index.js
@@ -1,12 +1,23 @@
var compiler = require('./compile');
var fs = require('fs');
+var path = require('path');
module.exports = function(files, configuration, callback) {
- compiler(files, function(err, compiled) {
+ // Normalize ids to filenames relative to the output file
+ var normalizer = function(filename) {
+ return path.relative(path.dirname(configuration.out), filename);
+ }
+
+ compiler(files, normalizer, function(err, compiled) {
+ if(err) {
+ return callback(err);
+ }
+
if(!callback) {
callback = configuration;
configuration = {};
}
+
var outfile = configuration.out;
var output = '(function(window, undefined) {\nvar can = window.can;\n' +
compiled.join('\n') +
View
49 readme.md
@@ -26,4 +26,51 @@ Compile all EJS and Mustache files in the current directory and all subdirectori
can-compile also comes with a [Grunt](http://gruntjs.com) task so you can easily make it part of your production build.
-## Programmatically
+module.exports = function (grunt) {
+
+```javascript
+// Project configuration.
+grunt.initConfig({
+ pkg : '<json:package.json>',
+ meta : {
+ resources : 'resources/js'
+ },
+ concat : {
+ resources : {
+ src : [ '<%= meta.resources %>/jquery.1.8.3.js', '<%= meta.resources %>/can.jquery.1.1.2.js',
+ '<%= meta.resources %>/can.view.mustache.1.1.2.js' ],
+ dest : '<%= meta.resources %>/resources.js'
+ }
+ },
+ min : {
+ resources : {
+ src : '<%= meta.resources %>/resources.js',
+ dest : '<%= meta.resources %>/resources.min.js'
+ }
+ },
+ cancompile : {
+ all : {
+ src : ['**/*.ejs', '**/*.mustache'],
+ // Change into the example folder
+ out : 'views.production.js'
+ }
+ }
+ });
+
+ grunt.loadTasks("./tasks");
+ // Default task.
+ grunt.registerTask('resources', 'concat:resources min:resources');
+ grunt.registerTask('default', 'resources');
+
+};
+```
+
+## Programmatically
+
+```javascript
+var compiler = require('can-compile');
+
+compiler.compile('file.ejs', function(error, output) {
+ output // -> compiled `file.ejs`
+});
+```
View
3,599 resources/js/can.jquery.1.1.2.js
3,599 additions, 0 deletions not shown
View
758 resources/js/can.view.mustache.1.1.2.js
@@ -0,0 +1,758 @@
+/*
+ * CanJS - 1.1.2 (2012-11-28)
+ * http://canjs.us/
+ * Copyright (c) 2012 Bitovi
+ * Licensed MIT
+ */
+(function (can, window, undefined) {
+ // ## can/view/mustache/mustache.js
+
+ // # mustache.js
+ // `can.Mustache`: The Mustache templating engine.
+ // See the [Transformation](#section-29) section within *Scanning Helpers* for a detailed explanation
+ // of the runtime render code design. The majority of the Mustache engine implementation
+ // occurs within the *Transformation* scanning helper.
+ // ## Initialization
+ // Define the view extension.
+ can.view.ext = ".mustache";
+
+ // ### Setup internal helper variables and functions.
+ // An alias for the context variable used for tracking a stack of contexts.
+ // This is also used for passing to helper functions to maintain proper context.
+ var CONTEXT = '___c0nt3xt',
+ // An alias for the variable used for the hash object that can be passed
+ // to helpers via `options.hash`.
+ HASH = '___h4sh',
+ // An alias for the function that adds a new context to the context stack.
+ STACK = '___st4ck',
+ // An alias for the most used context stacking call.
+ CONTEXT_STACK = STACK + '(' + CONTEXT + ',this)',
+
+
+ isObserve = function (obj) {
+ return can.isFunction(obj.attr) && obj.constructor && !! obj.constructor.canMakeObserve;
+ },
+
+
+ isArrayLike = function (obj) {
+ return obj && obj.splice && typeof obj.length == 'number';
+ },
+
+ // ## Mustache
+ Mustache = function (options) {
+ // Support calling Mustache without the constructor.
+ // This returns a function that renders the template.
+ if (this.constructor != Mustache) {
+ var mustache = new Mustache(options);
+ return function (data) {
+ return mustache.render(data);
+ };
+ }
+
+ // If we get a `function` directly, it probably is coming from
+ // a `steal`-packaged view.
+ if (typeof options == "function") {
+ this.template = {
+ fn: options
+ };
+ return;
+ }
+
+ // Set options on self.
+ can.extend(this, options);
+ this.template = this.scanner.scan(this.text, this.name);
+ };
+
+
+ // Put Mustache on the `can` object.
+ can.Mustache = window.Mustache = Mustache;
+
+
+ Mustache.prototype.
+
+ render = function (object, extraHelpers) {
+ object = object || {};
+ return this.template.fn.call(object, object, {
+ _data: object
+ });
+ };
+
+ can.extend(Mustache.prototype, {
+ // Share a singleton scanner for parsing templates.
+ scanner: new can.view.Scanner({
+ // A hash of strings for the scanner to inject at certain points.
+ text: {
+ // This is the logic to inject at the beginning of a rendered template.
+ // This includes initializing the `context` stack.
+ start: 'var ' + CONTEXT + ' = []; ' + CONTEXT + '.' + STACK + ' = true;' + 'var ' + STACK + ' = function(context, self) {' + 'var s;' + 'if (arguments.length == 1 && context) {' + 's = !context.' + STACK + ' ? [context] : context;' + '} else {' + 's = context && context.' + STACK + ' ? context.concat([self]) : ' + STACK + '(context).concat([self]);' + '}' + 'return (s.' + STACK + ' = true) && s;' + '};'
+ },
+
+ // An ordered token registry for the scanner.
+ // This needs to be ordered by priority to prevent token parsing errors.
+ // Each token follows the following structure:
+ // [
+ // // Which key in the token map to match.
+ // "tokenMapName",
+ // // A simple token to match, like "{{".
+ // "token",
+ // // Optional. A complex (regexp) token to match that
+ // // overrides the simple token.
+ // "[\\s\\t]*{{",
+ // // Optional. A function that executes advanced
+ // // manipulation of the matched content. This is
+ // // rarely used.
+ // function(content){
+ // return content;
+ // }
+ // ]
+ tokens: [
+ // Return unescaped
+ ["returnLeft", "{{{", "{{[{&]"],
+ // Full line comments
+ ["commentFull", "{{!}}", "^[\\s\\t]*{{!.+?}}\\n"],
+ // Inline comments
+ ["commentLeft", "{{!", "(\\n[\\s\\t]*{{!|{{!)"],
+ // Full line escapes
+ // This is used for detecting lines with only whitespace and an escaped tag
+ ["escapeFull", "{{}}", "(^[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}$)", function (content) {
+ return {
+ before: /^\n.+?\n$/.test(content) ? '\n' : '',
+ content: content.match(/\{\{(.+?)\}\}/)[1] || ''
+ };
+ }],
+ // Return escaped
+ ["escapeLeft", "{{"],
+ // Close return unescaped
+ ["returnRight", "}}}"],
+ // Close tag
+ ["right", "}}"]],
+
+ // ## Scanning Helpers
+ // This is an array of helpers that transform content that is within escaped tags like `{{token}}`. These helpers are solely for the scanning phase; they are unrelated to Mustache/Handlebars helpers which execute at render time. Each helper has a definition like the following:
+ // {
+ // // The content pattern to match in order to execute.
+ // // Only the first matching helper is executed.
+ // name: /pattern to match/,
+ // // The function to transform the content with.
+ // // @param {String} content The content to transform.
+ // // @param {Object} cmd Scanner helper data.
+ // // {
+ // // insert: "insert command",
+ // // tagName: "div",
+ // // status: 0
+ // // }
+ // fn: function(content, cmd) {
+ // return 'for text injection' ||
+ // { raw: 'to bypass text injection' };
+ // }
+ // }
+ helpers: [
+ // ### Partials
+ // Partials begin with a greater than sign, like {{> box}}.
+ // Partials are rendered at runtime (as opposed to compile time),
+ // so recursive partials are possible. Just avoid infinite loops.
+ // For example, this template and partial:
+ // base.mustache:
+ // <h2>Names</h2>
+ // {{#names}}
+ // {{> user}}
+ // {{/names}}
+ // user.mustache:
+ // <strong>{{name}}</strong>
+ {
+ name: /^>[\s|\w]\w*/,
+ fn: function (content, cmd) {
+ // Get the template name and call back into the render method,
+ // passing the name and the current context.
+ var templateName = can.trim(content.replace(/^>\s?/, ''));
+ return "can.view.render('" + templateName + "', " + CONTEXT_STACK + ".pop())";
+ }
+ },
+
+ // ### Data Hookup
+ // This will attach the data property of `this` to the element
+ // its found on using the first argument as the data attribute
+ // key.
+ // For example:
+ // <li id="nameli" {{ data 'name' }}></li>
+ // then later you can access it like:
+ // can.$('#nameli').data('name');
+ {
+ name: /^\s?data\s/,
+ fn: function (content, cmd) {
+ var attr = content.replace(/(^\s?data\s)|(["'])/g, '');
+
+ // return a function which calls `can.data` on the element
+ // with the attribute name with the current context.
+ return "can.proxy(function(__){can.data(can.$(__),'" + attr + "', this.pop()); }, " + CONTEXT_STACK + ")";
+ }
+ },
+
+ // ### Transformation (default)
+ // This transforms all content to its interpolated equivalent,
+ // including calls to the corresponding helpers as applicable.
+ // This outputs the render code for almost all cases.
+ // #### Definitions
+ // * `context` - This is the object that the current rendering context operates within.
+ // Each nested template adds a new `context` to the context stack.
+ // * `stack` - Mustache supports nested sections,
+ // each of which add their own context to a stack of contexts.
+ // Whenever a token gets interpolated, it will check for a match against the
+ // last context in the stack, then iterate through the rest of the stack checking for matches.
+ // The first match is the one that gets returned.
+ // * `Mustache.txt` - This serializes a collection of logic, optionally contained within a section.
+ // If this is a simple interpolation, only the interpolation lookup will be passed.
+ // If this is a section, then an `options` object populated by the truthy (`options.fn`) and
+ // falsey (`options.inverse`) encapsulated functions will also be passed. This section handling
+ // exists to support the runtime context nesting that Mustache supports.
+ // * `Mustache.get` - This resolves an interpolation reference given a stack of contexts.
+ // * `options` - An object containing methods for executing the inner contents of sections or helpers.
+ // `options.fn` - Contains the inner template logic for a truthy section.
+ // `options.inverse` - Contains the inner template logic for a falsey section.
+ // `options.hash` - Contains the merged hash object argument for custom helpers.
+ // #### Design
+ // This covers the design of the render code that the transformation helper generates.
+ // ##### Pseudocode
+ // A detailed explanation is provided in the following sections, but here is some brief pseudocode
+ // that gives a high level overview of what the generated render code does (with a template similar to
+ // `"{{#a}}{{b.c.d.e.name}}{{/a}}" == "Phil"`).
+ // *Initialize the render code.*
+ // view = []
+ // context = []
+ // stack = fn { context.concat([this]) }
+ // *Render the root section.*
+ // view.push( "string" )
+ // view.push( can.view.txt(
+ // *Render the nested section with `can.Mustache.txt`.*
+ // txt(
+ // *Add the current context to the stack.*
+ // stack(),
+ // *Flag this for truthy section mode.*
+ // "#",
+ // *Interpolate and check the `a` variable for truthyness using the stack with `can.Mustache.get`.*
+ // get( "a", stack() ),
+ // *Include the nested section's inner logic.
+ // The stack argument is usually the parent section's copy of the stack,
+ // but it can be an override context that was passed by a custom helper.
+ // Sections can nest `0..n` times -- **NESTCEPTION**.*
+ // { fn: fn(stack) {
+ // *Render the nested section (everything between the `{{#a}}` and `{{/a}}` tokens).*
+ // view = []
+ // view.push( "string" )
+ // view.push(
+ // *Add the current context to the stack.*
+ // stack(),
+ // *Flag this as interpolation-only mode.*
+ // null,
+ // *Interpolate the `b.c.d.e.name` variable using the stack.*
+ // get( "b.c.d.e.name", stack() ),
+ // )
+ // view.push( "string" )
+ // *Return the result for the nested section.*
+ // return view.join()
+ // }}
+ // )
+ // ))
+ // view.push( "string" )
+ // *Return the result for the root section, which includes all nested sections.*
+ // return view.join()
+ // ##### Initialization
+ // Each rendered template is started with the following initialization code:
+ // var ___v1ew = [];
+ // var ___c0nt3xt = [];
+ // ___c0nt3xt.___st4ck = true;
+ // var ___st4ck = function(context, self) {
+ // var s;
+ // if (arguments.length == 1 && context) {
+ // s = !context.___st4ck ? [context] : context;
+ // } else {
+ // s = context && context.___st4ck
+ // ? context.concat([self])
+ // : ___st4ck(context).concat([self]);
+ // }
+ // return (s.___st4ck = true) && s;
+ // };
+ // The `___v1ew` is the the array used to serialize the view.
+ // The `___c0nt3xt` is a stacking array of contexts that slices and expands with each nested section.
+ // The `___st4ck` function is used to more easily update the context stack in certain situations.
+ // Usually, the stack function simply adds a new context (`self`/`this`) to a context stack.
+ // However, custom helpers will occasionally pass override contexts that need their own context stack.
+ // ##### Sections
+ // Each section, `{{#section}} content {{/section}}`, within a Mustache template generates a section
+ // context in the resulting render code. The template itself is treated like a root section, with the
+ // same execution logic as any others. Each section can have `0..n` nested sections within it.
+ // Here's an example of a template without any descendent sections.
+ // Given the template: `"{{a.b.c.d.e.name}}" == "Phil"`
+ // Would output the following render code:
+ // ___v1ew.push("\"");
+ // ___v1ew.push(can.view.txt(1, '', 0, this, function() {
+ // return can.Mustache.txt(___st4ck(___c0nt3xt, this), null,
+ // can.Mustache.get("a.b.c.d.e.name",
+ // ___st4ck(___c0nt3xt, this))
+ // );
+ // }));
+ // ___v1ew.push("\" == \"Phil\"");
+ // The simple strings will get appended to the view. Any interpolated references (like `{{a.b.c.d.e.name}}`)
+ // will be pushed onto the view via `can.view.txt` in order to support live binding.
+ // The function passed to `can.view.txt` will call `can.Mustache.txt`, which serializes the object data by doing
+ // a context lookup with `can.Mustache.get`.
+ // `can.Mustache.txt`'s first argument is a copy of the context stack with the local context `this` added to it.
+ // This stack will grow larger as sections nest.
+ // The second argument is for the section type. This will be `"#"` for truthy sections, `"^"` for falsey,
+ // or `null` if it is an interpolation instead of a section.
+ // The third argument is the interpolated value retrieved with `can.Mustache.get`, which will perform the
+ // context lookup and return the approriate string or object.
+ // Any additional arguments, if they exist, are used for passing arguments to custom helpers.
+ // For nested sections, the last argument is an `options` object that contains the nested section's logic.
+ // Here's an example of a template with a single nested section.
+ // Given the template: `"{{#a}}{{b.c.d.e.name}}{{/a}}" == "Phil"`
+ // Would output the following render code:
+ // ___v1ew.push("\"");
+ // ___v1ew.push(can.view.txt(0, '', 0, this, function() {
+ // return can.Mustache.txt(___st4ck(___c0nt3xt, this), "#",
+ // can.Mustache.get("a", ___st4ck(___c0nt3xt, this)),
+ // [{
+ // _: function() {
+ // return ___v1ew.join("");
+ // }
+ // }, {
+ // fn: function(___c0nt3xt) {
+ // var ___v1ew = [];
+ // ___v1ew.push(can.view.txt(1, '', 0, this,
+ // function() {
+ // return can.Mustache.txt(
+ // ___st4ck(___c0nt3xt, this),
+ // null,
+ // can.Mustache.get("b.c.d.e.name",
+ // ___st4ck(___c0nt3xt, this))
+ // );
+ // }
+ // ));
+ // return ___v1ew.join("");
+ // }
+ // }]
+ // )
+ // }));
+ // ___v1ew.push("\" == \"Phil\"");
+ // This is specified as a truthy section via the `"#"` argument. The last argument includes an array of helper methods used with `options`.
+ // These act similarly to custom helpers: `options.fn` will be called for truthy sections, `options.inverse` will be called for falsey sections.
+ // The `options._` function only exists as a dummy function to make generating the section nesting easier (a section may have a `fn`, `inverse`,
+ // or both, but there isn't any way to determine that at compilation time).
+ // Within the `fn` function is the section's render context, which in this case will render anything between the `{{#a}}` and `{{/a}}` tokens.
+ // This function has `___c0nt3xt` as an argument because custom helpers can pass their own override contexts. For any case where custom helpers
+ // aren't used, `___c0nt3xt` will be equivalent to the `___st4ck(___c0nt3xt, this)` stack created by its parent section. The `inverse` function
+ // works similarly, except that it is added when `{{^a}}` and `{{else}}` are used. `var ___v1ew = []` is specified in `fn` and `inverse` to
+ // ensure that live binding in nested sections works properly.
+ // All of these nested sections will combine to return a compiled string that functions similar to EJS in its uses of `can.view.txt`.
+ // #### Implementation
+ {
+ name: /^.*$/,
+ fn: function (content, cmd) {
+ var mode = false,
+ result = [];
+
+ // Trim the content so we don't have any trailing whitespace.
+ content = can.trim(content);
+
+ // Determine what the active mode is.
+ // * `#` - Truthy section
+ // * `^` - Falsey section
+ // * `/` - Close the prior section
+ // * `else` - Inverted section (only exists within a truthy/falsey section)
+ if (content.length && (mode = content.match(/^([#^/]|else$)/))) {
+ mode = mode[0];
+ switch (mode) {
+ // Open a new section.
+ case '#':
+ case '^':
+ result.push(cmd.insert + 'can.view.txt(0,\'' + cmd.tagName + '\',' + cmd.status + ',this,function(){ return ');
+ break;
+ // Close the prior section.
+ case '/':
+ return {
+ raw: 'return ___v1ew.join("");}}])}));'
+ };
+ break;
+ }
+
+ // Trim the mode off of the content.
+ content = content.substring(1);
+ }
+
+ // `else` helpers are special and should be skipped since they don't
+ // have any logic aside from kicking off an `inverse` function.
+ if (mode != 'else') {
+ var args = [],
+ i = 0,
+ hashing = false,
+ arg, split, m;
+
+ // Parse the helper arguments.
+ // This needs uses this method instead of a split(/\s/) so that
+ // strings with spaces can be correctly parsed.
+ (can.trim(content) + ' ').replace(/((([^\s]+?=)?('.*?'|".*?"))|.*?)\s/g, function (whole, part) {
+ args.push(part);
+ });
+
+ // Start the content render block.
+ result.push('can.Mustache.txt(' + CONTEXT_STACK + ',' + (mode ? '"' + mode + '"' : 'null') + ',');
+
+ // Iterate through the helper arguments, if there are any.
+ for (; arg = args[i]; i++) {
+ i && result.push(',');
+
+ // Check for special helper arguments (string/number/boolean/hashes).
+ if (i && (m = arg.match(/^(('.*?'|".*?"|[0-9.]+|true|false)|((.+?)=(('.*?'|".*?"|[0-9.]+|true|false)|(.+))))$/))) {
+ // Found a native type like string/number/boolean.
+ if (m[2]) {
+ result.push(m[0]);
+ }
+ // Found a hash object.
+ else {
+ // Open the hash object.
+ if (!hashing) {
+ hashing = true;
+ result.push('{' + HASH + ':{');
+ }
+
+ // Add the key/value.
+ result.push(m[4], ':', m[6] ? m[6] : 'can.Mustache.get("' + m[5].replace(/"/g, '\\"') + '",' + CONTEXT_STACK + ')');
+
+ // Close the hash if this was the last argument.
+ if (i == args.length - 1) {
+ result.push('}}');
+ }
+ }
+ }
+ // Otherwise output a normal interpolation reference.
+ else {
+ result.push('can.Mustache.get("' +
+ // Include the reference name.
+ arg.replace(/"/g, '\\"') + '",' +
+ // Then the stack of context.
+ CONTEXT_STACK +
+ // Flag as a helper method to aid performance,
+ // if it is a known helper (anything with > 0 arguments).
+ (i == 0 && args.length > 1 ? ',true' : '') + ')');
+ }
+ }
+ }
+
+ // Create an option object for sections of code.
+ mode && mode != 'else' && result.push(',[{_:function(){');
+ switch (mode) {
+ // Truthy section
+ case '#':
+ result.push('return ___v1ew.join("");}},{fn:function(' + CONTEXT + '){var ___v1ew = [];');
+ break;
+ // If/else section
+ // Falsey section
+ case 'else':
+ case '^':
+ result.push('return ___v1ew.join("");}},{inverse:function(' + CONTEXT + '){var ___v1ew = [];');
+ break;
+ // Not a section
+ default:
+ result.push(');');
+ break;
+ }
+
+ // Return a raw result if there was a section, otherwise return the default string.
+ result = result.join('');
+ return mode ? {
+ raw: result
+ } : result;
+ }
+ }]
+ })
+ });
+
+ // Add in default scanner helpers first.
+ // We could probably do this differently if we didn't 'break' on every match.
+ var helpers = can.view.Scanner.prototype.helpers;
+ for (var i = 0; i < helpers.length; i++) {
+ Mustache.prototype.scanner.helpers.unshift(helpers[i]);
+ };
+
+
+ Mustache.txt = function (context, mode, name) {
+ // Grab the extra arguments to pass to helpers.
+ var args = Array.prototype.slice.call(arguments, 3),
+ // Create a default `options` object to pass to the helper.
+ options = can.extend.apply(can, [{
+ fn: function () {},
+ inverse: function () {}
+ }].concat(mode ? args.pop() : [])),
+ // An array of arguments to check for truthyness when evaluating sections.
+ validArgs = args.length ? args : [name],
+ // Whether the arguments meet the condition of the section.
+ valid = true,
+ result = [],
+ i, helper;
+
+ // Validate the arguments based on the section mode.
+ if (mode) {
+ for (i = 0; i < validArgs.length; i++) {
+ // Array-like objects are falsey if their length = 0.
+ if (isArrayLike(validArgs[i])) {
+ valid = mode == '#' ? valid && !! validArgs[i].length : mode == '^' ? valid && !validArgs[i].length : valid;
+ }
+ // Otherwise just check if it is truthy or not.
+ else {
+ valid = mode == '#' ? valid && !! validArgs[i] : mode == '^' ? valid && !validArgs[i] : valid;
+ }
+ }
+ }
+
+ // Check for a registered helper or a helper-like function.
+ if (helper = (Mustache.getHelper(name) || (can.isFunction(name) && {
+ fn: name
+ }))) {
+ // Use the most recent context as `this` for the helper.
+ var context = (context[STACK] && context[context.length - 1]) || context,
+ // Update the options with a function/inverse (the inner templates of a section).
+ opts = {
+ fn: can.proxy(options.fn, context),
+ inverse: can.proxy(options.inverse, context)
+ },
+ lastArg = args[args.length - 1];
+
+ // Add the hash to `options` if one exists
+ if (lastArg && lastArg[HASH]) {
+ opts.hash = args.pop()[HASH];
+ }
+ args.push(opts);
+
+ // Call the helper.
+ return helper.fn.apply(context, args) || '';
+ }
+
+ // Otherwise interpolate like normal.
+ if (valid) {
+ switch (mode) {
+ // Truthy section.
+ case '#':
+ // Iterate over arrays
+ if (isArrayLike(name)) {
+ for (i = 0; i < name.length; i++) {
+ result.push(options.fn.call(name[i] || {}, context) || '');
+ }
+ return result.join('');
+ }
+ // Normal case.
+ else {
+ return options.fn.call(name || {}, context) || '';
+ }
+ break;
+ // Falsey section.
+ case '^':
+ return options.inverse.call(name || {}, context) || '';
+ break;
+ default:
+ // Add + '' to convert things like numbers to strings.
+ // This can cause issues if you are trying to
+ // eval on the length but this is the more
+ // common case.
+ return '' + (name !== undefined ? name : '');
+ break;
+ }
+ }
+
+ return '';
+ };
+
+
+ Mustache.get = function (ref, contexts, isHelper) {
+ // Split the reference (like `a.b.c`) into an array of key names.
+ var names = ref.split('.'),
+ // Assume the local object is the last context in the stack.
+ obj = contexts[contexts.length - 1],
+ // Assume the parent context is the second to last context in the stack.
+ context = contexts[contexts.length - 2],
+ lastValue, value, name, i, j;
+
+ // Handle `this` references for list iteration: {{.}} or {{this}}
+ if (/^\.|this$/.test(ref)) {
+ // If context isn't an object, then it was a value passed by a helper so use it as an override.
+ if (!/^object|undefined$/.test(typeof context)) {
+ return context || '';
+ }
+ // Otherwise just return the closest object.
+ else {
+ while (value = contexts.pop()) {
+ if (typeof value !== 'undefined') {
+ return value;
+ }
+ }
+ return '';
+ }
+ }
+ // Handle object resolution (like `a.b.c`).
+ else if (!isHelper) {
+ // Reverse iterate through the contexts (last in, first out).
+ for (i = contexts.length - 1; i >= 0; i--) {
+ // Check the context for the reference
+ value = contexts[i];
+
+ // Make sure the context isn't a failed object before diving into it.
+ if (value !== undefined) {
+ for (j = 0; j < names.length; j++) {
+ // Keep running up the tree while there are matches.
+ if (typeof value[names[j]] != 'undefined') {
+ lastValue = value;
+ value = value[name = names[j]];
+ }
+ // If it's undefined, still match if the parent is an Observe.
+ else if (isObserve(value)) {
+ lastValue = value;
+ name = names[j];
+ break;
+ }
+ else {
+ lastValue = value = undefined;
+ break;
+ }
+ }
+ }
+
+ // Found a matched reference.
+ if (value !== undefined) {
+ // Support functions stored in objects.
+ if (can.isFunction(lastValue[name])) {
+ return lastValue[name]();
+ }
+ // Add support for observes
+ else if (isObserve(lastValue)) {
+ return lastValue.attr(name);
+ }
+ else {
+ // Invoke the length to ensure that Observe.List events fire.
+ isObserve(value) && isArrayLike(value) && value.attr('length');
+ return value;
+ }
+ }
+ }
+ }
+
+ // Support helper-like functions as anonymous helpers
+ if (obj !== undefined && can.isFunction(obj[ref])) {
+ return obj[ref];
+ }
+ // Support helpers without arguments, but only if there wasn't a matching data reference.
+ else if (value = Mustache.getHelper(ref)) {
+ return ref;
+ }
+
+ return '';
+ };
+
+
+ // ## Helpers
+ // Helpers are functions that can be called from within a template.
+ // These helpers differ from the scanner helpers in that they execute
+ // at runtime instead of during compilation.
+ // Custom helpers can be added via `can.Mustache.registerHelper`,
+ // but there are also some built-in helpers included by default.
+ // Most of the built-in helpers are little more than aliases to actions
+ // that the base version of Mustache simply implies based on the
+ // passed in object.
+ // Built-in helpers:
+ // * `data` - `data` is a special helper that is implemented via scanning helpers.
+ // It hooks up the active element to the active data object: `<div {{data "key"}} />`
+ // * `if` - Renders a truthy section: `{{#if var}} render {{/if}}`
+ // * `unless` - Renders a falsey section: `{{#unless var}} render {{/unless}}`
+ // * `each` - Renders an array: `{{#each array}} render {{this}} {{/each}}`
+ // * `with` - Opens a context section: `{{#with var}} render {{/with}}`
+
+ Mustache.registerHelper = function (name, fn) {
+ this._helpers.push({
+ name: name,
+ fn: fn
+ });
+ };
+
+
+ Mustache.getHelper = function (name) {
+ for (var i = 0, helper; helper = this._helpers[i]; i++) {
+ // Find the correct helper
+ if (helper.name == name) {
+ return helper;
+ }
+ }
+ return null;
+ };
+
+ // The built-in Mustache helpers.
+ Mustache._helpers = [
+ // Implements the `if` built-in helper.
+ {
+ name: 'if',
+ fn: function (expr, options) {
+ if ( !! expr) {
+ return options.fn(this);
+ }
+ else {
+ return options.inverse(this);
+ }
+ }
+ },
+
+ // Implements the `unless` built-in helper.
+ {
+ name: 'unless',
+ fn: function (expr, options) {
+ if (!expr) {
+ return options.fn(this);
+ }
+ }
+ },
+
+ // Implements the `each` built-in helper.
+ {
+ name: 'each',
+ fn: function (expr, options) {
+ if ( !! expr && expr.length) {
+ var result = [];
+ for (var i = 0; i < expr.length; i++) {
+ result.push(options.fn(expr[i]));
+ }
+ return result.join('');
+ }
+ }
+ },
+
+ // Implements the `with` built-in helper.
+ {
+ name: 'with',
+ fn: function (expr, options) {
+ if ( !! expr) {
+ return options.fn(expr);
+ }
+ }
+ }];
+
+ // ## Registration
+ // Registers Mustache with can.view.
+ can.view.register({
+ suffix: "mustache",
+
+ contentType: "x-mustache-template",
+
+ // Returns a `function` that renders the view.
+ script: function (id, src) {
+ return "can.Mustache(function(_CONTEXT,_VIEW) { " + new Mustache({
+ text: src,
+ name: id
+ }).template.out + " })";
+ },
+
+ renderer: function (id, text) {
+ return Mustache({
+ text: text,
+ name: id
+ });
+ }
+ });
+
+
+})(can, this);
View
9,472 resources/js/jquery.1.8.3.js
9,472 additions, 0 deletions not shown
View
1  resources/js/resources.min.js
1 addition, 0 deletions not shown
View
9 resources/loader.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>can.compile loader file</title>
+</head>
+<body>
+ <script src="js/resources.min.js"></script>
+</body>
+</html>
View
11 tasks/cancompile.js
@@ -2,6 +2,17 @@ var compiler = require('../lib');
module.exports = function (grunt) {
grunt.registerMultiTask('cancompile', 'Compile CanJS EJS and Mustache views', function () {
+ var done = this.async();
+ var options = grunt.config.process(['cancompile', this.target]);
+ var files = grunt.file.expandFiles(this.file.src);
+ files.forEach(function(file) {
+ grunt.log.writeln('Adding ' + file);
+ });
+
+ compiler(files, options, function(err, output) {
+ if(err) return grunt.fail.fatal(err);
+ done();
+ });
});
}
Please sign in to comment.
Something went wrong with that request. Please try again.