blade
Advanced tools
Comparing version 3.0.0-alpha1 to 3.0.0-alpha13
@@ -9,12 +9,8 @@ /** Blade Public API and Middleware | ||
*/ | ||
var fs = require('fs'), | ||
path = require('path'), | ||
url = require('url'), | ||
Compiler = require('./compiler'), | ||
uglifyjs = null; | ||
var fs = require("fs"), | ||
path = require("path"), | ||
url = require("url"), | ||
Compiler = require("./compiler"), | ||
bladeutil = require("./util"); | ||
try { | ||
uglifyjs = require('uglify-js'); | ||
} catch(e) {}; | ||
exports.compile = compile; | ||
@@ -36,3 +32,3 @@ exports.compileFile = compileFile; | ||
if(options.cache && !options.filename) | ||
cb(new Error('The `filename` option is required for caching')); | ||
cb(new Error("The `filename` option is required for caching")); | ||
else if(options.cache && cache[options.filename]) | ||
@@ -95,12 +91,12 @@ cb(null, cache[options.filename]); | ||
options = options || {}; | ||
options.mount = options.mount || '/views/'; | ||
options.mount = options.mount || "/views/"; | ||
if(typeof options.runtimeMount == "undefined") | ||
options.runtimeMount = '/blade/blade.js'; | ||
options.runtimeMount = "/blade/blade.js"; | ||
if(typeof options.pluginsMount == "undefined") | ||
options.pluginsMount = '/blade/plugins/'; | ||
options.pluginsMount = "/blade/plugins/"; | ||
if(options.compileOptions == null) | ||
options.compileOptions = { | ||
'cache': process.env.NODE_ENV == "production", | ||
'minify': process.env.NODE_ENV == "production", | ||
'includeSource': process.env.NODE_ENV == "development" | ||
"cache": process.env.NODE_ENV == "production", | ||
"minify": process.env.NODE_ENV == "production", | ||
"includeSource": process.env.NODE_ENV == "development" | ||
}; | ||
@@ -114,10 +110,10 @@ sourcePath = path.resolve(sourcePath); | ||
{ | ||
if(fileCache['runtime']) | ||
res.type('application/javascript').send(fileCache['runtime']); | ||
if(fileCache["runtime"]) | ||
res.type("application/javascript").send(fileCache["runtime"]); | ||
else | ||
fs.readFile(__dirname + "/../lib/runtime.js", function(err, data) { | ||
if(err) return next(err); | ||
data = uglify(data.toString() ); | ||
fileCache['runtime'] = data; | ||
res.type('application/javascript').send(data); | ||
data = bladeutil.uglify(data.toString(), true); | ||
fileCache["runtime"] = data; | ||
res.type("application/javascript").send(data); | ||
}); | ||
@@ -134,9 +130,9 @@ } | ||
else if(fileCache[fullPath]) | ||
res.type('application/javascript').send(fileCache[fullPath]); | ||
res.type("application/javascript").send(fileCache[fullPath]); | ||
else | ||
fs.readFile(fullPath, function(err, data) { | ||
if(err) return next(err); | ||
data = uglify(data.toString() ); | ||
data = bladeutil.uglify(data.toString(), true); | ||
fileCache[fullPath] = data; | ||
res.type('application/javascript').send(data); | ||
res.type("application/javascript").send(data); | ||
}); | ||
@@ -153,3 +149,3 @@ } | ||
if(err) return next(err); | ||
res.type('application/javascript'); | ||
res.type("application/javascript"); | ||
res.send("blade._cachedViews[" + JSON.stringify(filename) + "]=" + tmpl.toString() + | ||
@@ -164,12 +160,1 @@ ";if(blade._cb[" + JSON.stringify(filename) + "])blade._cb[" + | ||
} | ||
function uglify(str) { | ||
if(uglifyjs) | ||
{ | ||
var ast = uglifyjs.parser.parse(str), | ||
ugly = uglifyjs.uglify; | ||
ast = ugly.ast_mangle(ast); | ||
ast = ugly.ast_squeeze(ast); | ||
str = ugly.gen_code(ast); | ||
} | ||
return str; | ||
} |
@@ -15,9 +15,4 @@ /** Blade Compiler | ||
selfClosingTags = require('./self-closing-tags'), | ||
filters = require('./filters'), | ||
uglifyjs = null; | ||
filters = require('./filters'); | ||
try { | ||
uglifyjs = require('uglify-js'); | ||
} catch(e) {} | ||
module.exports = Compiler; | ||
@@ -99,7 +94,12 @@ | ||
var baseRelStart = this.buf.length; | ||
//Expose the filename no matter what; this is needed for branch labels if a | ||
//"live page update engine" is used | ||
this._pushOff(this.options.filename ? ns + ".filename = " + | ||
JSON.stringify(this.options.filename) + ";" : ""); | ||
//Only include error handling and source code, if needed | ||
if(this.options.minify !== true && this.options.includeSource) | ||
this._pushOff(ns + '.source = ' + JSON.stringify(this.string) + ";"); | ||
if(this.options.minify !== true) | ||
this._pushOff((this.options.filename ? ns + ".filename = " + | ||
JSON.stringify(this.options.filename) + ";" : "") + "\ntry {"); | ||
this._pushOff("\ntry {"); | ||
//Now compile the template | ||
this._pushOff('with(' + ns + '.locals) {'); | ||
@@ -150,15 +150,3 @@ if(this.ast.doctype != null) | ||
//Try to use uglify-js | ||
var str = this.template.toString(); | ||
if(uglifyjs) | ||
{ | ||
var ast = uglifyjs.parser.parse(str), | ||
ugly = uglifyjs.uglify; | ||
if(this.minify) | ||
{ | ||
ast = ugly.ast_mangle(ast); | ||
ast = ugly.ast_squeeze(ast); | ||
} | ||
str = ugly.gen_code(ast, {'beautify': !this.minify}); | ||
} | ||
return str; | ||
return bladeutil.uglify(this.template.toString(), this.minify); | ||
}; | ||
@@ -243,8 +231,21 @@ } catch(e) { | ||
//id attribute | ||
if(!attrs.id && node.id) | ||
attrs.id = {'escape': false, 'text': node.id}; | ||
if(node.id) | ||
{ | ||
/* If the tag doesn't have an "id" attribute, add it now; otherwise, | ||
only add it if the attribute is 'code'. | ||
That is, `div(id="foo")` always takes precedence over `div#foo` | ||
*/ | ||
if(!attrs.id || attrs.id.text == "") | ||
attrs.id = {'escape': false, 'text': node.id}; | ||
else if(attrs.id.code) | ||
attrs.id.code = "(" + attrs.id.code + ") || " + JSON.stringify(node.id); | ||
} | ||
//class attribute | ||
if(node.classes.length > 0) | ||
{ | ||
if(attrs['class'] == undefined) | ||
/* If the tag doesn't have a "class" attribute, add it now; otherwise, | ||
if the "class" attribute is text, just append to it now; otherwise, | ||
append the classes at runtime | ||
*/ | ||
if(!attrs['class']) | ||
attrs['class'] = {'escape': false, 'text': node.classes.join(" ")}; | ||
@@ -299,2 +300,14 @@ else if(attrs['class'].text) | ||
{ | ||
//interpolate text attributes | ||
if(attrs[i].text) | ||
{ | ||
var stringified = JSON.stringify(attrs[i].text), | ||
interpolated = bladeutil.interpolate(stringified, ns); | ||
//check to see if this text attribute needs to be interpolated | ||
if(interpolated != stringified) | ||
{ | ||
delete attrs[i].text; | ||
attrs[i].code = interpolated; | ||
} | ||
} | ||
//take care of text attributes here | ||
@@ -310,8 +323,8 @@ if(attrs[i].text) | ||
} | ||
else if(i == "class" && attrs[i].append) | ||
varAttrs += "," + i + ":{a:" + JSON.stringify(attrs[i].append) + | ||
",v:" + attrs[i].code + (attrs[i].escape ? ", e:1" : "") + "}"; | ||
//take care of code attributes here | ||
else | ||
varAttrs += "," + JSON.stringify(i) + ":{v:" + attrs[i].code + | ||
(attrs[i].escape ? ", e:1" : "") + "}"; | ||
(attrs[i].escape ? ",e:1" : "") + | ||
(i == "class" && attrs[i].append ? | ||
",a:" + JSON.stringify(attrs[i].append): "") + "}"; | ||
} | ||
@@ -387,4 +400,7 @@ if(varAttrs.length > 0) | ||
} | ||
var output = this.options.filters[node.name](node.filtered_text, | ||
{'minify': this.options.minify, 'compress': this.options.minify}); | ||
var output = this.options.filters[node.name](node.filtered_text, { | ||
'minify': this.options.minify, | ||
'compress': this.options.minify, | ||
'filename': this.options.filename | ||
}); | ||
//Ensure we prepend a newline if the last node was a text node. | ||
@@ -510,16 +526,27 @@ if(this.prependNewline) | ||
var attrs = node.children[0].attributes; | ||
modifyAttribute('id', 'this.id', false); | ||
modifyAttribute('class', 'this.classes', true); | ||
function modifyAttribute(attrName, mergeWith, append) { | ||
modifyAttribute('id'); | ||
modifyAttribute('class'); | ||
function modifyAttribute(attrName) { | ||
//If the attribute does not exist, simply add it as code; no need to escape | ||
if(attrs[attrName] == null) | ||
attrs[attrName] = {'escape': false, 'code': mergeWith}; | ||
attrs[attrName] = {'escape': false, 'code': "this." + (attrName == "class" ? "classes" : attrName) }; | ||
//Merge passed-in classes with the class attribute | ||
else if(attrName == "class") | ||
{ | ||
if(attrs[attrName].code) | ||
attrs[attrName].code = "(this.classes || []).concat(" + attrs[attrName].code + ")"; | ||
else | ||
{ | ||
attrs[attrName].code = "(this.classes || []).concat(" + | ||
bladeutil.interpolate(JSON.stringify(attrs[attrName].text) ) + ")"; | ||
delete attrs[attrName].text; | ||
} | ||
} | ||
//Replace the attribute with the passed-in attribute value | ||
else if(attrs[attrName].code) | ||
attrs[attrName].code = "(" + mergeWith + (append ? '.push(' + | ||
attrs[attrName].code + '),' + mergeWith | ||
: '||' + attrs[attrName].code) + ")"; | ||
attrs[attrName].code = "this." + attrName + '||' + attrs[attrName].code; | ||
else | ||
{ | ||
attrs[attrName].code = "(" + mergeWith + (append ? '.push(' + | ||
JSON.stringify(attrs[attrName].text) + '),' + mergeWith | ||
: '||' + JSON.stringify(attrs[attrName].text)) + ")"; | ||
attrs[attrName].code = "this." + attrName + '||' + | ||
bladeutil.interpolate(JSON.stringify(attrs[attrName].text) ); | ||
delete attrs[attrName].text; | ||
@@ -565,3 +592,3 @@ } | ||
case 'constant': | ||
this._pushOff(ns + ".r.constant(function() {"); | ||
this._pushOff(ns + ".r.constant(" + node.line + ",function() {"); | ||
for(var i = 0; i < node.children.length; i++) | ||
@@ -571,2 +598,10 @@ this._compileNode(node.children[i]); | ||
break; | ||
case 'preserve': | ||
if(!node.preserved) | ||
node.preserved = "[]"; | ||
this._pushOff(ns + ".r.preserve(" + node.line + ",(" + node.preserved + ")||[],function() {"); | ||
for(var i = 0; i < node.children.length; i++) | ||
this._compileNode(node.children[i]); | ||
this._pushOff("}," + ns + ");"); | ||
break; | ||
case 'foreach': | ||
@@ -573,0 +608,0 @@ this._pushOff(ns + ".r.foreach(" + ns + "," + node.cursor + ",function(" + |
@@ -23,3 +23,3 @@ exports['nl2br'] = function(text) { | ||
return '<script type="text/javascript">\n' + | ||
require('coffee-script').compile(str) + | ||
require('coffee-script').compile(text) + | ||
'\n</script>'; | ||
@@ -26,0 +26,0 @@ }; |
/** Blade Run-time helper functions | ||
(c) Copyright 2012. Blake Miner. All rights reserved. | ||
(c) Copyright 2012-2013. Blake Miner. All rights reserved. | ||
https://github.com/bminer/node-blade | ||
@@ -15,22 +15,20 @@ http://www.blakeminer.com/ | ||
htmlNoOp = function(arg1, html) {return html;}, | ||
funcNoOp = function(arg1, func) {return func();}, | ||
funcNoOp = function(func) {return func();}, | ||
funcNoOp2 = function(arg1, func) {return func();}, | ||
liveUpdate = { | ||
"attachEvents": htmlNoOp, | ||
"setDataContext": htmlNoOp, | ||
"isolate": function(func) {return func();}, | ||
"isolate": funcNoOp, | ||
"render": funcNoOp, | ||
"list": function(cursor, itemFunc, elseFunc) { | ||
var itemList = cursor || []; | ||
//cursor could have an observe method, in which case... | ||
if(cursor && "observe" in cursor) | ||
{ | ||
//Let's go ahead and observe it... | ||
itemList = []; | ||
cursor.observe({ | ||
"added": function(item) { | ||
//added must be called once per element before the | ||
//`observe` call completes | ||
itemList.push(item); | ||
} | ||
}).stop(); //and then stop observing it. | ||
} | ||
var itemList = []; | ||
//cursor must have an observe method | ||
//Let's go ahead and observe it... | ||
cursor.observe({ | ||
"added": function(item) { | ||
//added must be called once per element before the | ||
//`observe` call completes | ||
itemList.push(item); | ||
} | ||
}).stop(); //and then stop observing it. | ||
if(!itemList.length) //If itemList.length is null, zero, etc. | ||
@@ -44,4 +42,5 @@ return elseFunc(); | ||
}, | ||
"labelBranch": funcNoOp, | ||
"createLandmark": funcNoOp | ||
"labelBranch": funcNoOp2, | ||
"createLandmark": funcNoOp2, | ||
"finalize": htmlNoOp //should do nothing and return nothing meaningful | ||
}; | ||
@@ -142,5 +141,28 @@ /* blade.Runtime.mount is the URL where the Blade middleware is mounted (or where | ||
render callback function. | ||
- events - a space-delimited string of event types (i.e. "click change") | ||
- elementID - the "id" attribute of the element to which an event handler is to | ||
be bound | ||
- eventHandler - the event handler | ||
- buf - the Blade template buffer | ||
- commentExists - false if and only if this is the first call to runtime.bind | ||
for this element | ||
*/ | ||
runtime.bind = function(events, elementID, eventHandler, buf, commentExists) { | ||
eventHandlers[elementID] = {"events": events, "handler": eventHandler}; | ||
/* Place event map into `eventHandlers` global. | ||
Examples of event maps: | ||
// Fires when any element is clicked | ||
"click": function (event) { ... } | ||
//Fires when an element with class "accept" is clicked, or when a key is pressed | ||
"keydown, click .accept": function (event) { ... } | ||
//Fires when an element with class "accept" is either clicked or changed | ||
"click .accept, change .accept": function (event) { ... } | ||
See http://docs.meteor.com/#eventmaps for more information | ||
*/ | ||
var eventMapKey = ""; | ||
var eventTypes = events.split(" "); | ||
for(var i = 0; i < eventTypes.length; i++) | ||
eventMapKey = "," + eventTypes[i] + " #" + elementID; | ||
eventHandlers[eventMapKey.substr(1)] = eventHandler; | ||
var comment = "i[" + JSON.stringify(events) + "]=" + eventHandler.toString(); | ||
@@ -236,10 +258,13 @@ //If other event handlers were already declared for this element, | ||
} | ||
//Set a timer to return an Error after a timeout expires. | ||
var timer = setTimeout(function() { | ||
//Function to be called if the template could not be loaded | ||
function errorFunction(reason) { | ||
var cb = blade._cb[filename].cb; //array of callbacks | ||
delete blade._cb[filename]; | ||
st.parentNode.removeChild(st); | ||
callCallbacks(cb, new Error("Timeout Error: Blade Template [" + filename + | ||
"] could not be loaded.") ); | ||
}, runtime.options.loadTimeout); | ||
callCallbacks(cb, new Error("Blade Template [" + filename + | ||
"] could not be loaded: " + (reason ? reason : "Request timed out") ) ); | ||
} | ||
//Set a timer to return an Error after a timeout expires. | ||
var timer = setTimeout(errorFunction, runtime.options.loadTimeout); | ||
//Setup a callback to be called if the template is loaded successfully | ||
var tmp = blade._cb[filename] = function(dependenciesReldir, dependencies, unknownDependencies) { | ||
@@ -267,2 +292,11 @@ clearTimeout(timer); | ||
s.parentNode.insertBefore(st, s); | ||
//Also setup onload, onreadystatechange, and onerror callbacks to detect errors earlier than the timeout | ||
st.onload = st.onreadystatechange = st.onerror = function() { | ||
var x = this.readyState; | ||
if((!x || x == "loaded" || x == "complete") && blade._cb[filename]) | ||
{ | ||
clearTimeout(timer); | ||
errorFunction("Request failed"); | ||
} | ||
}; | ||
} | ||
@@ -285,9 +319,18 @@ return false; | ||
/* This function is a hack to get the resolved URL, so that caching works | ||
okay with relative URLs */ | ||
okay with relative URLs. | ||
This function does not work properly if `filename` contains too many "../" | ||
For example, passing "alpha/beta/../../filename.blade" is acceptable; whereas, | ||
"alpha/beta/../../../filename.blade" is unacceptable input. | ||
*/ | ||
runtime.resolve = function(filename) { | ||
if(runtime.client) { | ||
//Use the browser's ability to resolve relative URLs | ||
var x = document.createElement('div'); | ||
x.innerHTML = '<a href="' + runtime.escape("./" + filename) + '"></a>'; | ||
x = x.firstChild.href; | ||
x = x.substr(window.location.href.length).replace(/\/\//g, '/'); | ||
/* suppose `window.location.href` is "http://www.example.com/foo/bar/document.html" | ||
and `filename` is "alpha/./beta/../charlie.blade", then | ||
`x` will be something like "http://www.example.com/foo/bar//alpha/charlie.blade" */ | ||
var prefix = window.location.href; | ||
x = x.substr(prefix.substr(0, prefix.lastIndexOf("/") ).length).replace(/\/[\/]+/g, '/'); | ||
if(x.charAt(0) == '/') x = x.substr(1); | ||
@@ -307,3 +350,5 @@ return x; | ||
pSource = info.source, | ||
pLocals = info.locals; | ||
pLocals = info.locals, | ||
pUnsafeBlockAction = info.unsafeBlockAction, | ||
pPreserve = info.preserve; | ||
info.inc = true; | ||
@@ -333,2 +378,4 @@ //If exposing locals, the included view gets its own set of locals | ||
info.locals = pLocals; | ||
info.unsafeBlockAction = pUnsafeBlockAction; | ||
info.preserve = pPreserve; | ||
}, info); | ||
@@ -421,22 +468,65 @@ }); | ||
/* Define a constant block */ | ||
runtime.constant = function(func, buf) { | ||
buf.push(liveUpdate.createLandmark({"constant": true}, function(landmark) { | ||
/* Note: This following line is the same as: | ||
var len = buf.length; | ||
func(); | ||
return runtime.capture(buf, len); | ||
*/ | ||
return runtime.capture(buf, buf.length, func() ); | ||
runtime.constant = function(label, func, buf) { | ||
if(buf.unsafeBlockAction) | ||
throw new Error("You cannot preserve elements here because this template either defines a block or performs a block modification."); | ||
buf.preserve = true; | ||
buf.push(liveUpdate.labelBranch(buf.filename + ":" + label, function () { | ||
return liveUpdate.createLandmark({"constant": true}, function(landmark) { | ||
/* Note: This following line is the same as: | ||
var len = buf.length; | ||
func(); | ||
return runtime.capture(buf, len); | ||
*/ | ||
return runtime.capture(buf, buf.length, func() ); | ||
}); | ||
}) ); | ||
}; | ||
/* Foreach/else block */ | ||
runtime.foreach = function(buf, cursor, listFunc, elseFunc) { | ||
buf.push(liveUpdate.list(cursor, function(item) { | ||
return runtime.capture(buf, buf.length, listFunc.call(item, item) ); | ||
}, function() { | ||
return runtime.capture(buf, buf.length, elseFunc() ); | ||
/* Define a preserve block */ | ||
runtime.preserve = function(label, preserved, func, buf) { | ||
if(buf.unsafeBlockAction) | ||
throw new Error("You cannot preserve elements here because this template either defines a block or performs a block modification."); | ||
buf.preserve = true; | ||
buf.push(liveUpdate.labelBranch(buf.filename + ":" + label, function () { | ||
return liveUpdate.createLandmark({"preserve": preserved}, function(landmark) { | ||
/* Note: This following line is the same as: | ||
var len = buf.length; | ||
func(); | ||
return runtime.capture(buf, len); | ||
*/ | ||
return runtime.capture(buf, buf.length, func() ); | ||
}); | ||
}) ); | ||
}; | ||
/* Foreach/else block */ | ||
runtime.foreach = function(buf, cursor, itemFunc, elseFunc) { | ||
//Define wrapper functions for itemFunc and elseFunc | ||
function itemFuncWrapper(item) { | ||
var label = (item._id || (typeof item === 'string' ? item : null) || liveUpdate.UNIQUE_LABEL); | ||
return liveUpdate.labelBranch(label, function() { | ||
return runtime.capture(buf, buf.length, itemFunc.call(item, item) ); | ||
}); | ||
} | ||
function elseFuncWrapper() { | ||
return liveUpdate.labelBranch("else", function() { | ||
return elseFunc ? runtime.capture(buf, buf.length, elseFunc() ) : ""; | ||
}); | ||
} | ||
//Call liveUpdate.list for Cursor Objects | ||
if(cursor && "observe" in cursor) | ||
buf.push(liveUpdate.list(cursor, itemFuncWrapper, elseFuncWrapper) ); | ||
else | ||
{ | ||
//Allow non-Cursor Objects or Arrays to work, as well | ||
var html = "", empty = 1; | ||
for(var i in cursor) | ||
{ | ||
empty = 0; | ||
html += itemFuncWrapper(cursor[i]); | ||
} | ||
buf.push(empty ? elseFuncWrapper() : html); | ||
} | ||
}; | ||
/* Copies error reporting information from a block's buffer to the main | ||
@@ -456,2 +546,5 @@ buffer */ | ||
runtime.blockDef = function(blockName, buf, childFunc) { | ||
if(buf.preserve) | ||
throw new Error("You cannot define a block in this template because element preservation is being used."); | ||
buf.unsafeBlockAction = true; | ||
var block = buf.blocks[blockName] = { | ||
@@ -492,2 +585,4 @@ 'parent': buf.block || null, //set parent block | ||
runtime.blockRender = function(type, blockName, buf) { | ||
if(buf.preserve) | ||
throw new Error("You cannot render a block in this template because element preservation is being used."); | ||
var block = buf.blocks[blockName]; | ||
@@ -499,2 +594,3 @@ if(block == null) | ||
"' is a regular, non-parameterized block, which cannot be rendered."); | ||
buf.unsafeBlockAction = true; | ||
//Extract arguments | ||
@@ -532,5 +628,8 @@ var args = [block.buf]; | ||
runtime.blockMod = function(type, blockName, buf, childFunc) { | ||
if(buf.preserve) | ||
throw new Error("You cannot modify a block in this template because element preservation is being used."); | ||
var block = buf.blocks[blockName]; | ||
if(block == null) | ||
throw new Error("Block '" + blockName + "' is undefined."); | ||
buf.unsafeBlockAction = true; | ||
if(type == "r") //replace | ||
@@ -548,3 +647,8 @@ { | ||
{ | ||
try {childFunc(block.buf);} | ||
try { | ||
//Copy buf.rel and buf.base to block.buf | ||
block.buf.rel = buf.rel; | ||
block.buf.base = buf.base; | ||
childFunc(block.buf); | ||
} | ||
catch(e) {blockError(buf, block.buf); throw e;} | ||
@@ -664,2 +768,3 @@ } | ||
} | ||
where keys are space-delimited event types and values are event handler functions | ||
*/ | ||
@@ -666,0 +771,0 @@ //now r refers to the properties populated in the event Object map |
@@ -25,2 +25,30 @@ /** Blade utility functions | ||
return str.replace(/'/g, "\\'"); | ||
} | ||
}; | ||
/* Use uglify-js to obfuscate and optimize JavaScript code */ | ||
var uglifyjs = null; | ||
try { | ||
uglifyjs = require("uglify-js"); | ||
} catch(e) {} | ||
exports.uglify = function(str, minify) { | ||
if(uglifyjs) | ||
{ | ||
var ast = uglifyjs.parse(str); | ||
if(minify) | ||
{ | ||
//compress | ||
ast.figure_out_scope(); | ||
var comp = uglifyjs.Compressor(); | ||
ast = ast.transform(comp); | ||
//mangle | ||
ast.figure_out_scope(); | ||
ast.compute_char_frequency(); | ||
ast.mangle_names(); | ||
} | ||
//output | ||
var stream = uglifyjs.OutputStream({"beautify": !minify}); | ||
ast.print(stream); | ||
str = stream.toString(); | ||
} | ||
return str; | ||
}; |
@@ -5,8 +5,15 @@ var path = require('path'); | ||
try { | ||
blade = require('blade'); | ||
blade = require('blade') | ||
} | ||
catch(e) { | ||
//XXX super lame! we actually have to give paths relative to | ||
// app/inner/app.js, since that's who's evaling us. | ||
blade = require('../../packages/blade/node_modules/blade'); | ||
try { | ||
//XXX super lame! we actually have to give paths relative to | ||
// app/lib/packages.js, since that's who's evaling us. | ||
// The next line is for the core Meteor-installed package | ||
blade = require('../../packages/blade/node_modules/blade'); | ||
} | ||
catch(e) { | ||
//XXX super lame! The next line is for the Meteorite-installed package | ||
blade = require(process.cwd() + "/.meteor/meteorite/packages/blade/node_modules/blade"); | ||
} | ||
} | ||
@@ -38,3 +45,3 @@ //-- end of horrible hack | ||
if(err) throw err; | ||
if(templateName == "head") | ||
if(templateName == "head" || templateName == "body") | ||
tmpl({}, function(err, html) { | ||
@@ -44,3 +51,3 @@ //This should happen synchronously due to compile options set above | ||
bundle.add_resource({ | ||
type: 'head', | ||
type: templateName, //either "head" or "body" | ||
data: html, | ||
@@ -50,17 +57,7 @@ where: where | ||
}); | ||
else if(templateName == "body") | ||
tmpl({}, function(err, html) { | ||
//This should happen synchronously due to compile options set above | ||
if(err) throw err; | ||
bundle.add_resource({ | ||
type: 'body', | ||
data: html, | ||
where: where | ||
}); | ||
}); | ||
bundle.add_resource({ | ||
type: 'js', | ||
path: "/views/" + templateName + ".js", //This can be changed to whatever | ||
data: new Buffer("blade.cachedViews[" + | ||
//just put the template itself in blade.cachedViews | ||
data: new Buffer("blade._cachedViews[" + | ||
//just put the template itself in blade._cachedViews | ||
JSON.stringify(templateName + ".blade") + "]=" + tmpl.toString() + ";" + | ||
@@ -79,5 +76,9 @@ //define a template with the proper name | ||
there is no async. All code is ran synchronously. */ | ||
"var ret = ''; blade.cachedViews[" + JSON.stringify(templateName + ".blade") + | ||
"](data, function(err,html) {" + | ||
"if(err) throw err; ret = html;" + | ||
"var ret = ''; blade._cachedViews[" + JSON.stringify(templateName + ".blade") + | ||
"](data, function(err,html,info) {" + | ||
"if(err) throw err;" + | ||
//Remove event handler attributes | ||
'html = html.replace(/on[a-z]+\\=\\"return blade\\.Runtime\\.trigger\\(this\\,arguments\\)\\;\\"/g, "");' + | ||
//now bind any inline events and return | ||
"ret = blade.LiveUpdate.attachEvents(info.eventHandlers, html);" + | ||
"});\n" + | ||
@@ -84,0 +85,0 @@ //so... by here, we can just return `ret`, and everything works okay |
@@ -1,14 +0,37 @@ | ||
blade.Runtime.loadTemplate = function(baseDir, filename, compileOptions, cb) { | ||
//Append .blade for filenames without an extension | ||
if(filename.split("/").pop().indexOf(".") < 0) | ||
filename += ".blade"; | ||
//Either pull from the cache or return an error | ||
filename = blade.Runtime.resolve(filename); | ||
if(blade.cachedViews[filename]) | ||
{ | ||
cb(null, blade.cachedViews[filename]); | ||
return true; | ||
(function() { | ||
//Helper function | ||
function resolveFilename(filename) { | ||
//Append .blade for filenames without an extension | ||
if(filename.split("/").pop().indexOf(".") < 0) | ||
filename += ".blade"; | ||
return blade.Runtime.resolve(filename); | ||
} | ||
cb(new Error("Template '" + filename + "' could not be loaded.") ); | ||
return false; | ||
}; | ||
//Overwrite blade.Runtime.loadTemplate and include functions | ||
blade.Runtime.loadTemplate = function(baseDir, filename, compileOptions, cb) { | ||
filename = resolveFilename(filename); | ||
//Either pull from the cache or return an error | ||
if(blade._cachedViews[filename]) | ||
{ | ||
cb(null, blade._cachedViews[filename]); | ||
return true; | ||
} | ||
cb(new Error("Template '" + filename + "' could not be loaded.") ); | ||
return false; | ||
}; | ||
var oldInclude = blade.Runtime.include; | ||
blade.Runtime.include = function(relFilename, info) { | ||
var name = resolveFilename(info.rel + "/" + relFilename); | ||
//Remove .blade file extension | ||
if(name.substr(-6) == ".blade") | ||
name = name.substr(0, name.length - 6); | ||
//Add helpers to info.locals | ||
var tmpl = Template[name] || {}; | ||
var tmplData = tmpl._tmpl_data || {}; | ||
_.extend(info.locals, Meteor._partials[name], tmplData.helpers || {}); | ||
//Now call original "include" function | ||
return oldInclude.apply(this, arguments); | ||
}; | ||
//Use Spark as the live update engine | ||
blade.LiveUpdate = Spark; | ||
})(); |
@@ -18,3 +18,3 @@ { | ||
], | ||
"version": "3.0.0-alpha1", | ||
"version": "3.0.0alpha13", | ||
"homepage": "https://github.com/bminer/node-blade", | ||
@@ -33,6 +33,6 @@ "repository": { | ||
"pegjs": ">=0.7", | ||
"uglify-js": ">=1.2" | ||
"uglify-js": "~2" | ||
}, | ||
"optionalDependencies": { | ||
"uglify-js": ">=1.2" | ||
"uglify-js": "~2" | ||
}, | ||
@@ -46,7 +46,7 @@ "engines": { | ||
"prepublish": "./lib/parser/build.sh", | ||
"postinstall": "node ./postinstall.js" | ||
"postinstall": "node ./postinstall.js" | ||
}, | ||
"contributors": [ | ||
"Michel Löhr (https://github.com/mlohr)" | ||
"Michel Löhr (https://github.com/mlohr)" | ||
] | ||
} | ||
} |
/** Blade Live UI plugin | ||
(c) Copyright 2012. Blake Miner. All rights reserved. | ||
https://github.com/bminer/node-blade | ||
http://www.blakeminer.com/ | ||
(c) Copyright 2012-2013. Blake Miner. All rights reserved. | ||
https://github.com/bminer/node-blade | ||
http://www.blakeminer.com/ | ||
See the full license here: | ||
https://raw.github.com/bminer/node-blade/master/LICENSE.txt | ||
See the full license here: | ||
https://raw.github.com/bminer/node-blade/master/LICENSE.txt | ||
Hard Dependencies: | ||
- node-blade runtime | ||
Soft Dependencies (this plugin should still work without these): | ||
- Spark (https://github.com/meteor/meteor/wiki/Spark) | ||
- The easiest way to obtain Spark is to clone the Meteor Github repo | ||
(git://github.com/meteor/meteor.git) and run `admin/spark-standalone.sh` | ||
- Underscore.js is also a requirement for Spark at this time | ||
Works well with: | ||
- jQuery | ||
Adds the following to the `blade` global variable: | ||
@@ -13,6 +25,5 @@ - Model | ||
Adds the following to `blade.Runtime`: | ||
- render(viewName, locals, cb) | ||
- renderTo(element, viewName, locals [, cb]) | ||
- renderTo(element, viewName, locals [,landmarkOptions] [, cb]) | ||
Adds the following functions to jQuery.fn: | ||
- render(viewName, locals [, cb]) | ||
- render(viewName, locals [,landmarkOptions] [, cb]) | ||
@@ -28,71 +39,66 @@ Browser Support: | ||
using a conditional comment like this: | ||
```html | ||
<!--[if IE 8]> | ||
<script type="text/javascript" src="/blade/plugins/definePropertyIE8.js"></script> | ||
<![endif]--> | ||
For element preservation support, please add jQuery 1.7+ to your project. | ||
``` | ||
*/ | ||
(function() { | ||
if(!window.blade) return; //Nothing to expose, so just quit | ||
var Context = function () { | ||
// Each context has a unique number. You can use this to avoid | ||
// storing multiple copies of the same context in the | ||
// invalidation list. | ||
this.id = Context.next_id++; | ||
this._callbacks = []; //each of these are called when invalidated | ||
this._invalidated = false; | ||
}; | ||
blade.Context = Context; //expose this Object | ||
//Global static variables | ||
Context.next_id = 0; | ||
Context.current = null; | ||
Context.pending_invalidate = []; //list of Contexts that have been invalidated but not flushed | ||
//Calls all Context _callbacks on each Context listed in Context.pending_invalidate | ||
Context.flush = function() { | ||
while (Context.pending_invalidate.length > 0) { | ||
var pending = Context.pending_invalidate; | ||
Context.pending_invalidate = []; | ||
for(var i = 0; i < pending.length; i++) { | ||
var ctx = pending[i]; | ||
for(var j = 0; j < ctx._callbacks.length; j++) | ||
ctx._callbacks[j](ctx); | ||
delete ctx._callbacks; //maybe help the GC | ||
//This plugin *can* work without Spark... | ||
var Context = blade.Context = (window.Meteor && Meteor.deps) ? Meteor.deps.Context || {} : {}; | ||
//Use Spark as the live update engine | ||
if(window.Spark) | ||
{ | ||
//--- Basically an excerpt from https://github.com/meteor/meteor/blob/master/packages/spark/utils.js --- | ||
//--- Minor modification is to exclude id's starting with "blade_" | ||
Spark._labelFromIdOrName = function(n) { | ||
var label = null; | ||
if (n.nodeType === 1 /*ELEMENT_NODE*/) { | ||
if (n.id && n.id.substr(0, 6) != "blade_") { | ||
label = '#' + n.id; | ||
} else if (n.getAttribute("name")) { | ||
label = n.getAttribute("name"); | ||
// Radio button special case: radio buttons | ||
// in a group all have the same name. Their value | ||
// determines their identity. | ||
// Checkboxes with the same name and different | ||
// values are also sometimes used in apps, so | ||
// we treat them similarly. | ||
if (n.nodeName === 'INPUT' && | ||
(n.type === 'radio' || n.type === 'checkbox') && | ||
n.value) | ||
label = label + ':' + n.value; | ||
// include parent names and IDs up to enclosing ID | ||
// in the label | ||
while (n.parentNode && | ||
n.parentNode.nodeType === 1 /*ELEMENT_NODE*/) { | ||
n = n.parentNode; | ||
if (n.id) { | ||
label = '#' + n.id + "/" + label; | ||
break; | ||
} else if (n.getAttribute('name')) { | ||
label = n.getAttribute('name') + "/" + label; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return label; | ||
}; | ||
//--- End | ||
//--- Excerpt from https://github.com/meteor/meteor/blob/master/packages/preserve-inputs/preserve-inputs.js --- | ||
var inputTags = 'input textarea button select option'.split(' '); | ||
var selector = _.map(inputTags, function (t) { | ||
return t.replace(/^.*$/, '$&[id], $&[name]'); | ||
}).join(', '); | ||
Spark._globalPreserves[selector] = Spark._labelFromIdOrName; | ||
//--- End | ||
//Copy stuff from Spark to blade.LiveUpdate | ||
blade.LiveUpdate = Spark; | ||
} | ||
//Run a function in this Context | ||
Context.prototype.run = function (f) { | ||
var previous = Context.current; | ||
Context.current = this; | ||
try { var ret = f(); } | ||
finally { Context.current = previous; } | ||
return ret; | ||
}; | ||
//Just mark the Context as invalidated; do not call any invalidation functions | ||
//just yet; instead, schedule them to be executed soon. | ||
Context.prototype.invalidate = function () { | ||
if (!this._invalidated) { | ||
this._invalidated = true; | ||
// If this is first invalidation, schedule a flush. | ||
// We may be inside a flush already, in which case this | ||
// is unnecessary but harmless. | ||
if (Context.pending_invalidate.length == 0) | ||
setTimeout(Context.flush, 1); | ||
Context.pending_invalidate.push(this); | ||
} | ||
}; | ||
//Calls f immediately if this context was already | ||
//invalidated. The callback receives one argument, the Context. | ||
Context.prototype.on_invalidate = function (f) { | ||
if (this._invalidated) | ||
f(this); | ||
else | ||
this._callbacks.push(f); | ||
}; | ||
function Model(data) { | ||
@@ -157,3 +163,3 @@ /*A proxy object that can be written to | ||
self._keyDeps[key][context.id] = context; | ||
context.on_invalidate(function() { | ||
context.onInvalidate(function () { | ||
//Check to see if self._keyDeps[key] exists first, | ||
@@ -257,134 +263,74 @@ //as this property might have been deleted | ||
/* render(viewName, locals, cb) | ||
Asynchronously loads (if necessary) and renders the specified template | ||
using the specified locals in a new Context. If the Context is invalidated, | ||
the template will be re-rendered and the callback will be called again. | ||
- viewName - the name of the view to be loaded and rendered | ||
/* Renders the specified view using the specified locals and injects the generated | ||
DOM into the specified element. In addition, any event handlers created by | ||
the view are bound. | ||
Finally, the element in focus is "preserved" and if the element either has an | ||
'id' attribute or has a 'name' attribute and a parent who has an 'id' attribute. | ||
Views are rendered within the context specific to the `element`, as expected. | ||
That is, running renderTo against the same element will destroy all registered | ||
Contexts and their callbacks. | ||
- element - the DOM element into which the generated HTML code will be injected | ||
- viewName - the view template to be loaded and rendered | ||
- locals - the locals to be passed to the view. If a `Model` object is | ||
passed to this method, the Model's `observable` Object will be passed | ||
to the view. | ||
- cb - a callback of the form cb(err, html) where `html` is an string of | ||
HTML produced by the view template | ||
- [landmarkOptions] - the options passed to the created Landmark | ||
(see https://github.com/meteor/meteor/wiki/Spark) | ||
- [cb] - a callback of the form cb(err) where `err` is the Error object thrown | ||
when the template was loaded (or null if no error occurred). This callback is | ||
called exactly once, when the template is loaded. | ||
It should also be noted that changing the contents of `el` or removing `el` from | ||
the DOM may confuse Spark and cause errors. To remove `el` from the DOM or to | ||
delete its child nodes, for example, it is best to call `Spark.finalize(el)` first. | ||
*/ | ||
blade.Runtime.render = function(viewName, locals, cb) { | ||
//Load and render the template | ||
blade.Runtime.renderTo = function(el, viewName, locals, landmarkOptions, cb) { | ||
//Reorganize args | ||
if(typeof landmarkOptions == "function") | ||
cb = landmarkOptions, landmarkOptions = {}; | ||
//Load blade template | ||
blade.Runtime.loadTemplate(viewName, function(err, tmpl) { | ||
if(err) return cb(err); | ||
(function renderTemplate() { | ||
function renderIt() { | ||
tmpl(locals ? locals.observable || locals : {}, cb); | ||
} | ||
var context = new Context(); | ||
context.on_invalidate(renderTemplate); //recurse | ||
context.run(renderIt); | ||
})(); | ||
}); | ||
}; | ||
/* renderTo(element, viewName, locals [, cb]) | ||
Same as render(), except the output of the view is immediately injected | ||
into the specified element. In addition, any event handlers created by | ||
the view are bound. Finally, the element in focus is "preserved" if jQuery | ||
is available and if the element either has an 'id' attribute or has a 'name' | ||
attribute and a parent who has an 'id' attribute. | ||
Also, from within the callback, `this` refers to the `element`. | ||
*/ | ||
blade.Runtime.renderTo = function(el, viewName, locals, cb) { | ||
blade.Runtime.render(viewName, locals, function(err, html, info) { | ||
if(err) {if(cb) cb.call(el, err); return;} | ||
try | ||
//Call optional callback or throw error, if needed | ||
if(cb) | ||
cb(err); | ||
if(err) | ||
{ | ||
//Start preserving the element in focus, if necessary | ||
var focus = document.activeElement, | ||
$ = jQuery, | ||
preserve = jQuery && //jQuery is required | ||
//if <body> is in focus, ignore preservation | ||
! $(focus).is("body") && | ||
//the element must have an 'id' or a 'name' | ||
(focus.id || focus.name) && | ||
//Make sure that this node is a descendant of `el` | ||
$(focus).parents().index(el) >= 0; | ||
if(preserve) | ||
{ | ||
//Setup the new element query now because the 'id' attribute will be deleted soon | ||
var newElementIDQuery = focus.id ? "#" + focus.id : null, | ||
newElementNameQuery = focus.name ? ( | ||
$(focus).parent().closest("[id]").length > 0 ? | ||
"#" + $(focus).parent().closest("[id]").attr("id") + " " : "" | ||
) + "[name=" + focus.name + "]" : null, | ||
tmpValue = focus.value; | ||
//Save the selection, if needed | ||
if($(focus).is("input[type=text],input[type=password],textarea")) | ||
var selectionStart = focus.selectionStart, | ||
selectionEnd = focus.selectionEnd; | ||
//Remove event handlers and attributes; in Chrome, 'blur' and possibly 'change' | ||
//events are fired when an in-focus element is removed from the DOM | ||
$(focus).off(); | ||
for(var i = focus.attributes.length - 1; i >= 0; i--) | ||
focus.removeAttributeNode(focus.attributes.item(i) ); | ||
focus.onchange = focus.onblur = null; | ||
//Now it's safe to call blur and remove this element from the DOM | ||
focus.blur(); | ||
} | ||
//Insert newly rendered content (jQuery is not required here) | ||
if(el.html) | ||
el.html(html); | ||
else | ||
el.innerHTML = html; | ||
//Preserve element value, focus, cursor position, etc. | ||
if(preserve) | ||
{ | ||
//Find new element in newly rendered content | ||
var newElement = $(newElementIDQuery); | ||
if(newElement.length != 1) | ||
newElement = $(newElementNameQuery); | ||
//If found, do element preservation stuff... | ||
if(newElement.length == 1) | ||
{ | ||
var oldValue = $(newElement).val(); //Save the value that's currently in the model | ||
newElement = newElement[0]; | ||
newElement.focus(); //Give the new element focus | ||
if(document.activeElement === newElement) | ||
{ | ||
//Set value to the temporary value and setup blur event handler to trigger `change`, if needed | ||
$(newElement).val(tmpValue).blur(function(e) { | ||
$(this).unbind(e); | ||
if(this.value !== oldValue) | ||
$(this).trigger('change'); | ||
if(!cb) throw err; | ||
return; | ||
} | ||
//Destroy the LiveRanges in this element, if any | ||
var LiveUpdate = blade.LiveUpdate; | ||
LiveUpdate.finalize(el); | ||
var dom = LiveUpdate.render(function() { | ||
return LiveUpdate.labelBranch(viewName + "@" + el.id, function () { | ||
return LiveUpdate.createLandmark(landmarkOptions, function (landmark) { | ||
return LiveUpdate.isolate(function () { | ||
var ret; | ||
tmpl(locals ? locals.observable || locals : {}, function(err, html, info) { | ||
//Remove event handler attributes | ||
html = html.replace(/on[a-z]+\=\"return blade\.Runtime\.trigger\(this\,arguments\)\;\"/g, ""); | ||
//Return | ||
ret = LiveUpdate.attachEvents(info.eventHandlers, html); | ||
}); | ||
//Set focus again and set cursor & text selection | ||
newElement.focus(); | ||
if($(newElement).is("input[type=text],input[type=password],textarea")) | ||
{ | ||
newElement.selectionStart = selectionStart; | ||
newElement.selectionEnd = selectionEnd; | ||
} | ||
} | ||
} | ||
} | ||
//Register event handlers | ||
for(var i in info.eventHandlers) | ||
{ | ||
var events = info.eventHandlers[i].events.split(" "), | ||
elem = document.getElementById(i); | ||
for(var j = 0; j < events.length; j++) | ||
if(elem === newElement && events[j] == "change") | ||
(function(elem, handler) { | ||
elem['on' + events[j]] = function() { | ||
setTimeout(function() { | ||
elem['on' + events[j]] = handler; //put everything back | ||
}, 1); | ||
//intercept event, if needed | ||
if(this.value !== oldValue) | ||
//call original handler | ||
return handler.apply(this, arguments); | ||
}; | ||
})(elem, info.eventHandlers[i].handler); | ||
else | ||
elem['on' + events[j]] = info.eventHandlers[i].handler; | ||
//Delete comment before element | ||
elem.parentNode.removeChild(elem.previousSibling); | ||
} | ||
if(cb) cb.call(el, null, html, info); | ||
return ret; | ||
}); | ||
}); | ||
}); | ||
}); | ||
if(window.jQuery) | ||
//Use jQuery's empty() function to call `jQuery.cleanData` and prevent memory leaks | ||
jQuery(el).empty(); | ||
else | ||
{ | ||
while(el.firstChild) | ||
el.removeChild(el.firstChild); | ||
} | ||
catch(e) {if(cb) cb.call(el, e);} | ||
if(typeof dom == "string") | ||
el.innerHTML = dom; | ||
else | ||
el.appendChild(dom); | ||
}); | ||
@@ -394,6 +340,8 @@ }; | ||
if(window.jQuery) | ||
jQuery.fn.render = function(viewName, locals, cb) { | ||
blade.Runtime.renderTo(this, viewName, locals, cb); | ||
jQuery.fn.render = function(viewName, locals, landmarkOptions, cb) { | ||
this.each(function() { | ||
blade.Runtime.renderTo(this, viewName, locals, landmarkOptions, cb); | ||
}); | ||
}; | ||
})(); |
@@ -17,5 +17,2 @@ Blade - HTML Template Compiler | ||
**UPDATE:** Meteor 0.4.0 support is coming soon! Thanks for your patience! If | ||
anyone wants to help implement support for Meteor 0.4.0, please contact me. | ||
<img src="http://www.empireonline.com/images/features/100greatestcharacters/photos/47.jpg" | ||
@@ -114,2 +111,5 @@ alt="Blade" width="150" height="169"/> | ||
**UPDATE:** Meteor 0.5 support is now available, and many more features are coming soon! | ||
Thanks for your patience! | ||
I'd say that Blade is **stable**. There are very few (if any) | ||
@@ -134,3 +134,3 @@ [known issues](https://github.com/bminer/node-blade/issues), and I think that Blade | ||
Minified runtime is about 5-6 KB, uncompressed. | ||
Minified runtime is about 6-7 KB, uncompressed. | ||
@@ -911,5 +911,32 @@ Syntax | ||
### Isolates | ||
Isolates are only useful when using a live page update engine. Creating an isolate | ||
ensures that if data dependencies relating only to that isolate are updated, then only | ||
the part of the template within isolate will be re-rendered. All other parts of the | ||
Blade template will *not* be re-rendered. | ||
Example (using Meteor): | ||
``` | ||
- console.log("Rendering header...") | ||
h1 This is a header | ||
isolate | ||
p The current user is: #{Session.get("user")} | ||
``` | ||
In the example above, "Rendering header..." will be printed to the console when the | ||
whole template is rendered, but if the reactive variable is updated using | ||
`Session.set("user", ...)`, only the isolate will be re-rendered. In this case, | ||
nothing will print to the console. | ||
Note: As with Blade functions, any [blocks](#blocks) defined within an isolate will be | ||
deleted and unaccessible outside the isolate block. | ||
See [Reactivity isolation](http://docs.meteor.com/#isolate) on the Meteor documentation | ||
for more details. | ||
### Chunks | ||
#### Chunks are deprecated as of Blade 3.0 | ||
#### Chunks are *deprecated* as of Blade 3.0. You should use [isolates](#isolates) instead. | ||
@@ -1272,4 +1299,9 @@ Chunks are simply functions that return HTML. They behave a bit differently than | ||
#### An Atmosphere smart package will be available soon! | ||
#### An [Atmosphere smart package](https://atmosphere.meteor.com/package/blade) is also available. | ||
To install Blade using Atmosphere, simply [install Meteorite] | ||
(https://atmosphere.meteor.com/wtf/app), navigate to your Meteor project directory, | ||
and type `mrt add blade`. Then, don't forget to run your project using `mrt` instead | ||
of `meteor`. | ||
**More documentation and examples for Meteor + Blade can be found [on this wiki page] | ||
@@ -1276,0 +1308,0 @@ (https://github.com/bminer/node-blade/wiki/Using-Blade-with-Meteor).** |
@@ -6,6 +6,6 @@ { | ||
"homepage": "https://github.com/bminer/node-blade", | ||
"version": "3.0.0-alpha1", | ||
"version": "3.0.0alpha13", | ||
"git": "https://github.com/bminer/node-blade.git", | ||
"packages": { | ||
} | ||
} | ||
} |
@@ -35,3 +35,6 @@ console.log("Stupid tests...\n---------------"); | ||
var fs = require('fs'); | ||
var files = fs.readdirSync(__dirname + "/templates"); | ||
var filesTemp = fs.readdirSync(__dirname + "/templates"), files = []; | ||
for(var i in filesTemp) | ||
if(filesTemp[i].substr(-6) == ".blade") | ||
files.push(filesTemp[i]); | ||
var blade = require('../'); | ||
@@ -38,0 +41,0 @@ var numLines = 0; |
@@ -67,5 +67,5 @@ var blade = require('../lib/blade'), | ||
console.log("-----------------------------------------------"); | ||
console.log("Done - " + (done-failed) + " of " + total + " tests passed"); | ||
console.log("Done - " + (total-failed) + " of " + total + " tests passed"); | ||
console.timeEnd("Test"); | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
AI detected anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
345663
98
7231
1384