mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
Runtime now supports both vdom and html output
All tests are passing
This commit is contained in:
parent
0d6efeaf6d
commit
ee815fc49b
@ -1,4 +1,7 @@
|
||||
{
|
||||
"predef": [
|
||||
"document"
|
||||
],
|
||||
"node" : true,
|
||||
"esnext": true,
|
||||
"boss" : false,
|
||||
|
||||
@ -55,6 +55,7 @@ const helpers = {
|
||||
'attrs': 'as',
|
||||
'classAttr': 'ca',
|
||||
'classList': 'cl',
|
||||
'const': 'const',
|
||||
'createElement': 'e',
|
||||
'escapeXml': 'x',
|
||||
'escapeXmlAttr': 'xa',
|
||||
@ -516,7 +517,7 @@ class CompileContext extends EventEmitter {
|
||||
get helpersIdentifier() {
|
||||
if (!this._helpersIdentifier) {
|
||||
if (this.inline) {
|
||||
this._helpersIdentifier = this.importModule('__markoHelpers', 'marko/runtime/helpers');
|
||||
this._helpersIdentifier = this.importModule('__markoHelpers', 'marko/runtime/html/helpers');
|
||||
} else {
|
||||
// The helpers variable is a parameter of the outer create function
|
||||
this._helpersIdentifier = this.builder.identifier('__markoHelpers');
|
||||
|
||||
@ -9,7 +9,7 @@ class HtmlJsParser {
|
||||
parse(src, handlers) {
|
||||
var listeners = {
|
||||
onText(event) {
|
||||
handlers.handleCharacters(event.value);
|
||||
handlers.handleCharacters(event.value, event.parseMode);
|
||||
},
|
||||
|
||||
onPlaceholder(event) {
|
||||
@ -32,7 +32,7 @@ class HtmlJsParser {
|
||||
},
|
||||
|
||||
onCDATA(event) {
|
||||
handlers.handleCharacters(event.value);
|
||||
handlers.handleCharacters(event.value, 'static-text');
|
||||
},
|
||||
|
||||
onOpenTag(event, parser) {
|
||||
|
||||
@ -110,13 +110,17 @@ class Parser {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
handleCharacters(text) {
|
||||
handleCharacters(text, parseMode) {
|
||||
var builder = this.context.builder;
|
||||
|
||||
if (this.prevTextNode && this.prevTextNode.isLiteral()) {
|
||||
var escape = parseMode !== 'html';
|
||||
// NOTE: If parseMode is 'static-text' or 'parsed-text' then that means that special
|
||||
// HTML characters may not have been escaped on the way in so we need to escape
|
||||
// them on the way out
|
||||
|
||||
if (this.prevTextNode && this.prevTextNode.isLiteral() && this.prevTextNode.escape === escape) {
|
||||
this.prevTextNode.argument.value += text;
|
||||
} else {
|
||||
var escape = false;
|
||||
this.prevTextNode = builder.text(builder.literal(text), escape);
|
||||
this.parentNode.appendChild(this.prevTextNode);
|
||||
}
|
||||
|
||||
@ -1,6 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function generateCode(node, codegen, vdomUtil) {
|
||||
node.name = codegen.generateCode(node.name);
|
||||
var context = codegen.context;
|
||||
var builder = codegen.builder;
|
||||
|
||||
// node.name = codegen.generateCode(node.name);
|
||||
node.value = codegen.generateCode(node.value);
|
||||
node.isStatic = vdomUtil.isStaticValue(node.value);
|
||||
|
||||
var name = node.name;
|
||||
|
||||
if (node.value && node.value.type !== 'Literal') {
|
||||
if (name === 'class') {
|
||||
node.value = builder.functionCall(context.helper('classAttr'), [node.value]);
|
||||
} else if (name === 'style') {
|
||||
node.value = builder.functionCall(context.helper('styleAttr'), [node.value]);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
@ -19,6 +19,19 @@ class HtmlComment extends Node {
|
||||
];
|
||||
}
|
||||
|
||||
generateVDOMCode(codegen) {
|
||||
var comment = this.comment;
|
||||
var builder = codegen.builder;
|
||||
|
||||
return builder.functionCall(
|
||||
builder.memberExpression(
|
||||
builder.identifierOut(),
|
||||
builder.identifier('comment')),
|
||||
[
|
||||
comment
|
||||
]);
|
||||
}
|
||||
|
||||
walk(walker) {
|
||||
this.comment = walker.walk(this.comment);
|
||||
}
|
||||
|
||||
@ -13,15 +13,6 @@ module.exports = function generateCode(node, codegen) {
|
||||
tagName = node.tagNameExpression;
|
||||
}
|
||||
|
||||
var context = codegen.context;
|
||||
|
||||
if (context.isMacro(node.tagName)) {
|
||||
// At code generation time, if node tag corresponds to a registered macro
|
||||
// then invoke the macro based on node HTML element instead of generating
|
||||
// the code to render an HTML element.
|
||||
return codegen.builder.invokeMacroFromEl(node);
|
||||
}
|
||||
|
||||
var attributes = node._attributes && node._attributes.all;
|
||||
var body = node.body;
|
||||
var argument = node.argument;
|
||||
|
||||
@ -43,10 +43,24 @@ class HtmlElement extends Node {
|
||||
}
|
||||
|
||||
generateHTMLCode(codegen) {
|
||||
if (codegen.context.isMacro(this.tagName)) {
|
||||
// At code generation time, if node tag corresponds to a registered macro
|
||||
// then invoke the macro based on node HTML element instead of generating
|
||||
// the code to render an HTML element.
|
||||
return codegen.builder.invokeMacroFromEl(this);
|
||||
}
|
||||
|
||||
return generateHTMLCode(this, codegen);
|
||||
}
|
||||
|
||||
generateVDOMCode(codegen) {
|
||||
if (codegen.context.isMacro(this.tagName)) {
|
||||
// At code generation time, if node tag corresponds to a registered macro
|
||||
// then invoke the macro based on node HTML element instead of generating
|
||||
// the code to render an HTML element.
|
||||
return codegen.builder.invokeMacroFromEl(this);
|
||||
}
|
||||
|
||||
return generateVDOMCode(this, codegen, vdomUtil);
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,10 @@ class HtmlElementVDOM extends Node {
|
||||
attributes.forEach((attr) => {
|
||||
let value = attr.value;
|
||||
|
||||
if (value == null) {
|
||||
value = builder.literal(true);
|
||||
}
|
||||
|
||||
if (!attr.name) {
|
||||
return;
|
||||
}
|
||||
@ -154,7 +158,6 @@ class HtmlElementVDOM extends Node {
|
||||
} else if (this.isHtmlOnly) {
|
||||
writer.write('out.');
|
||||
funcCall = builder.functionCall(
|
||||
|
||||
builder.identifier('e'),
|
||||
createArgs);
|
||||
} else {
|
||||
|
||||
@ -21,6 +21,7 @@ module.exports = function(node, codegen, vdomUtil) {
|
||||
var tagName = codegen.generateCode(node.tagNameExpression);
|
||||
var attributes = codegen.generateCode(node.getAttributes());
|
||||
var dynamicAttributes = codegen.generateCode(node.dynamicAttributes);
|
||||
var builder = codegen.builder;
|
||||
|
||||
var isAttrsStatic = checkAttributesStatic(attributes);
|
||||
var isStatic = isAttrsStatic && node.isLiteralTagName();
|
||||
@ -30,6 +31,9 @@ module.exports = function(node, codegen, vdomUtil) {
|
||||
for (var i=0; i<body.length; i++) {
|
||||
let child = body[i];
|
||||
if (child.type === 'HtmlElementVDOM' || child.type === 'TextVDOM') {
|
||||
if (child.type === 'TextVDOM' && child.escape === false) {
|
||||
isHtmlOnly = false;
|
||||
}
|
||||
if (!child.isHtmlOnly) {
|
||||
isStatic = false;
|
||||
isHtmlOnly = false;
|
||||
@ -43,6 +47,11 @@ module.exports = function(node, codegen, vdomUtil) {
|
||||
}
|
||||
}
|
||||
|
||||
var bodyOnlyIf = node.bodyOnlyIf;
|
||||
if (bodyOnlyIf) {
|
||||
isHtmlOnly = false;
|
||||
}
|
||||
|
||||
var htmlElVDOM = new HtmlElementVDOM({
|
||||
tagName,
|
||||
attributes,
|
||||
@ -53,7 +62,24 @@ module.exports = function(node, codegen, vdomUtil) {
|
||||
dynamicAttributes
|
||||
});
|
||||
|
||||
if (isHtmlOnly) {
|
||||
|
||||
if (bodyOnlyIf) {
|
||||
htmlElVDOM.body = null;
|
||||
|
||||
var startIf = builder.ifStatement(builder.negate(bodyOnlyIf), [
|
||||
htmlElVDOM
|
||||
]);
|
||||
|
||||
var endIf = builder.ifStatement(builder.negate(bodyOnlyIf), [
|
||||
new EndElementVDOM()
|
||||
]);
|
||||
|
||||
return [
|
||||
startIf,
|
||||
body,
|
||||
endIf
|
||||
];
|
||||
} else if (isHtmlOnly) {
|
||||
return htmlElVDOM;
|
||||
} else {
|
||||
htmlElVDOM.body = null;
|
||||
|
||||
@ -16,13 +16,15 @@ class Literal extends Node {
|
||||
if (isArray(this.value)) {
|
||||
this.value = codegen.generateCode(this.value);
|
||||
} else if (typeof this.value === 'object') {
|
||||
var newObject = {};
|
||||
for (var k in this.value) {
|
||||
if (this.value.hasOwnProperty(k)) {
|
||||
newObject[k] = codegen.generateCode(this.value[k]);
|
||||
if (!(this.value instanceof RegExp)) {
|
||||
var newObject = {};
|
||||
for (var k in this.value) {
|
||||
if (this.value.hasOwnProperty(k)) {
|
||||
newObject[k] = codegen.generateCode(this.value[k]);
|
||||
}
|
||||
}
|
||||
this.value = newObject;
|
||||
}
|
||||
this.value = newObject;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
|
||||
@ -11,8 +11,6 @@ function createVarsArray(vars) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
class TemplateRoot extends Node {
|
||||
constructor(def) {
|
||||
super('TemplateRoot');
|
||||
|
||||
@ -9,9 +9,11 @@ class TextVDOM extends Node {
|
||||
super('TextVDOM');
|
||||
this.arguments = [def.argument];
|
||||
this.isStatic = def.isStatic;
|
||||
this.escape = def.escape !== false;
|
||||
this.isHtmlOnly = true;
|
||||
this.isChild = false;
|
||||
this.createTextId = undefined;
|
||||
this.strFuncId = undefined;
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
@ -19,9 +21,15 @@ class TextVDOM extends Node {
|
||||
|
||||
vdomUtil.registerOptimizer(context);
|
||||
|
||||
// if (this.isStatic) {
|
||||
// this.createTextId = context.importModule('marko_createText', 'marko/vdom/createText');
|
||||
// }
|
||||
var args = this.arguments;
|
||||
|
||||
for (var i=0, len=args.length; i<len; i++) {
|
||||
var arg = args[i];
|
||||
if (arg.type !== 'Literal') {
|
||||
this.strFuncId = context.helper('str');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -39,49 +47,68 @@ class TextVDOM extends Node {
|
||||
}
|
||||
|
||||
append(textVDOMToAppend) {
|
||||
if (textVDOMToAppend.escape !== this.escape) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!textVDOMToAppend.isStatic) {
|
||||
this.isStatic = false;
|
||||
}
|
||||
|
||||
if (textVDOMToAppend.strFuncId) {
|
||||
this.strFuncId = textVDOMToAppend.strFuncId;
|
||||
}
|
||||
|
||||
textVDOMToAppend.arguments.forEach(this._append, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
writeCode(writer) {
|
||||
let builder = writer.builder;
|
||||
let args = this.arguments;
|
||||
let textArg = args[0];
|
||||
for (let i=1; i<args.length; i++) {
|
||||
textArg = builder.binaryExpression(textArg, '+', args[i]);
|
||||
let escape = this.escape;
|
||||
|
||||
var funcName = escape ? 't' : 'h';
|
||||
var strFuncId = this.strFuncId;
|
||||
|
||||
function writeTextArgs() {
|
||||
writer.write('(');
|
||||
|
||||
for (let i=0, len=args.length; i<len; i++) {
|
||||
let arg = args[i];
|
||||
|
||||
if (i !== 0) {
|
||||
writer.write(' +\n');
|
||||
writer.writeLineIndent();
|
||||
writer.writeIndent();
|
||||
}
|
||||
|
||||
if (arg.type === 'Literal') {
|
||||
writer.write(arg);
|
||||
} else {
|
||||
writer.write(strFuncId);
|
||||
writer.write('(');
|
||||
writer.write(arg);
|
||||
writer.write(')');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
writer.write(')');
|
||||
}
|
||||
|
||||
if (this.isChild) {
|
||||
let funcCall = builder.functionCall(
|
||||
builder.identifier('t'),
|
||||
[
|
||||
textArg
|
||||
]);
|
||||
|
||||
writer.write('.');
|
||||
writer.write(funcCall);
|
||||
} else if (this.isStatic) {
|
||||
let funcCall = builder.functionCall(
|
||||
this.createTextId,
|
||||
[
|
||||
textArg
|
||||
]);
|
||||
|
||||
writer.write(funcCall);
|
||||
writer.write(builder.identifier(funcName));
|
||||
} else if (this.isStatic && this.createTextId) {
|
||||
writer.write(this.createTextId);
|
||||
} else {
|
||||
let funcCall = builder.functionCall(
|
||||
builder.identifier('t'),
|
||||
[
|
||||
textArg
|
||||
]);
|
||||
|
||||
writer.write('out.');
|
||||
writer.write(funcCall);
|
||||
writer.write(builder.identifier(funcName));
|
||||
}
|
||||
|
||||
|
||||
writeTextArgs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,15 +2,38 @@
|
||||
|
||||
var TextVDOM = require('./TextVDOM');
|
||||
var Literal = require('../../Literal');
|
||||
var he = require('he'); // Used for dealing with HTML entities
|
||||
|
||||
module.exports = function(node, codegen, vdomUtil) {
|
||||
var argument = codegen.generateCode(node.argument);
|
||||
var escape = node.escape !== false;
|
||||
var isStatic = null;
|
||||
|
||||
if (argument instanceof Literal && argument.value === '') {
|
||||
// Don't add empty text nodes to the final tree
|
||||
return null;
|
||||
if (codegen.context.isFlagSet('SCRIPT_BODY')) {
|
||||
escape = true;
|
||||
}
|
||||
|
||||
var isStatic = vdomUtil.isStaticValue(argument);
|
||||
return new TextVDOM({ argument, isStatic });
|
||||
if (argument instanceof Literal) {
|
||||
var literalValue = argument.value;
|
||||
if (literalValue == null || literalValue === '') {
|
||||
// Don't add empty text nodes to the final tree
|
||||
return null;
|
||||
}
|
||||
|
||||
if (escape === false) {
|
||||
escape = true;
|
||||
|
||||
if (typeof literalValue === 'string') {
|
||||
if (literalValue.indexOf('<') !== -1) {
|
||||
escape = false;
|
||||
} else if (literalValue.indexOf('&') !== -1) {
|
||||
argument = codegen.builder.literal(he.decode(literalValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isStatic = isStatic == null ? vdomUtil.isStaticValue(argument) : isStatic;
|
||||
return new TextVDOM({ argument, isStatic, escape });
|
||||
};
|
||||
@ -34,7 +34,10 @@ if (g.__MARKO_CONFIG) {
|
||||
* If true, whitespace will be preserved in templates. Defaults to false.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
preserveWhitespace: false
|
||||
preserveWhitespace: false,
|
||||
|
||||
// The default output mode for compiled templates
|
||||
output: 'html'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,9 @@ c) Else, generate one of the following:
|
||||
const Node = require('../../ast/Node');
|
||||
const nextConstIdFuncSymbol = Symbol();
|
||||
|
||||
const OPTIONS_DEFAULT = { optimizeTextNodes: true, optimizeStaticNodes: true };
|
||||
const OPTIONS_OPTIMIZE_TEXT_NODES = { optimizeTextNodes: true, optimizeStaticNodes: false };
|
||||
|
||||
class NodeVDOM extends Node {
|
||||
constructor(variableIdentifier) {
|
||||
super('NodeVDOM');
|
||||
@ -49,20 +52,24 @@ class NodeVDOM extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
function optimizeVDOMNodes(nodes, context) {
|
||||
function generateNodesForArray(nodes, context, options) {
|
||||
let builder = context.builder;
|
||||
let nextNodeId = 0;
|
||||
let nextAttrsId = 0;
|
||||
|
||||
var optimizeTextNodes = options.optimizeTextNodes !== false;
|
||||
var optimizeStaticNodes = options.optimizeStaticNodes !== false;
|
||||
|
||||
function generateStaticNode(node) {
|
||||
if (node.type === 'HtmlElementVDOM') {
|
||||
node.createElementId = context.importModule('marko_createElement', 'marko/vdom/createElement');
|
||||
node.createElementId = context.helper('createElement');
|
||||
}/* else {
|
||||
node.createTextId = context.importModule('marko_createText', 'marko/vdom/createText');
|
||||
}*/
|
||||
|
||||
let nextConstIdFunc = context.data[nextConstIdFuncSymbol];
|
||||
if (!nextConstIdFunc) {
|
||||
let constId = context.importModule('marko_const', 'marko/runtime/vdom/const');
|
||||
let constId = context.helper('const');
|
||||
let fingerprintLiteral = builder.literal(context.getFingerprint(6));
|
||||
nextConstIdFunc = context.data[nextConstIdFuncSymbol] = context.addStaticVar('marko_const_nextId', builder.functionCall(constId, [ fingerprintLiteral ]));
|
||||
}
|
||||
@ -84,15 +91,16 @@ function optimizeVDOMNodes(nodes, context) {
|
||||
}
|
||||
}
|
||||
|
||||
function generateNodesForArray(nodes) {
|
||||
let finalNodes = [];
|
||||
let i = 0;
|
||||
let finalNodes = [];
|
||||
let i = 0;
|
||||
|
||||
while (i<nodes.length) {
|
||||
let node = nodes[i];
|
||||
if (node.type === 'HtmlElementVDOM') {
|
||||
while (i<nodes.length) {
|
||||
let node = nodes[i];
|
||||
if (node.type === 'HtmlElementVDOM') {
|
||||
if (optimizeStaticNodes) {
|
||||
if (node.isStatic) {
|
||||
finalNodes.push(generateStaticNode(node));
|
||||
doOptimizeNode(node, context, OPTIONS_OPTIMIZE_TEXT_NODES);
|
||||
} else {
|
||||
if (node.isAttrsStatic) {
|
||||
handleStaticAttributes(node);
|
||||
@ -100,52 +108,58 @@ function optimizeVDOMNodes(nodes, context) {
|
||||
|
||||
finalNodes.push(node);
|
||||
}
|
||||
} else if (node.type === 'TextVDOM') {
|
||||
} else {
|
||||
finalNodes.push(node);
|
||||
}
|
||||
|
||||
} else if (node.type === 'TextVDOM') {
|
||||
if (optimizeTextNodes) {
|
||||
let firstTextNode = node;
|
||||
|
||||
// We will need to merge the text nodes into a single node
|
||||
while(++i<nodes.length) {
|
||||
let currentTextNode = nodes[i];
|
||||
if (currentTextNode.type === 'TextVDOM') {
|
||||
firstTextNode.append(currentTextNode);
|
||||
if (!firstTextNode.append(currentTextNode)) {
|
||||
// If the current text node was not appendable then
|
||||
// we will stop. We can only merge text nodes that are compatible
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if (firstTextNode.isStatic) {
|
||||
// finalNodes.push(generateStaticNode(firstTextNode));
|
||||
// continue;
|
||||
// } else {
|
||||
// finalNodes.push(firstTextNode);
|
||||
// }
|
||||
firstTextNode.isStatic = false;
|
||||
// firstTextNode.isStatic = false;
|
||||
finalNodes.push(firstTextNode);
|
||||
|
||||
continue;
|
||||
} else {
|
||||
finalNodes.push(node);
|
||||
}
|
||||
|
||||
i++;
|
||||
} else {
|
||||
finalNodes.push(node);
|
||||
}
|
||||
|
||||
return finalNodes;
|
||||
i++;
|
||||
}
|
||||
|
||||
return finalNodes;
|
||||
}
|
||||
|
||||
function doOptimizeNode(node, context, options) {
|
||||
let walker = context.createWalker({
|
||||
enterArray(nodes) {
|
||||
return generateNodesForArray(nodes);
|
||||
return generateNodesForArray(nodes, context, options);
|
||||
}
|
||||
});
|
||||
|
||||
return walker.walk(nodes);
|
||||
return walker.walk(node);
|
||||
}
|
||||
|
||||
class VDOMOptimizer {
|
||||
optimize(node, context) {
|
||||
if (node.body) {
|
||||
node.body = optimizeVDOMNodes(node.body, context);
|
||||
}
|
||||
doOptimizeNode(node, context, OPTIONS_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,10 +21,13 @@ var fsReadOptions = { encoding: 'utf8' };
|
||||
|
||||
function compile(templatePath, markoCompiler, compilerOptions) {
|
||||
|
||||
var writeToDisk = compilerOptions.writeToDisk;
|
||||
if (writeToDisk == null) {
|
||||
writeToDisk = markoCompiler.defaultOptions.writeToDisk;
|
||||
if (compilerOptions) {
|
||||
compilerOptions = markoCompiler.defaultOptions;
|
||||
} else {
|
||||
compilerOptions = Object.assign({}, markoCompiler.defaultOptions, compilerOptions);
|
||||
}
|
||||
var writeToDisk = compilerOptions.writeToDisk;
|
||||
|
||||
var templateSrc;
|
||||
var compiledSrc;
|
||||
|
||||
@ -51,7 +54,7 @@ function compile(templatePath, markoCompiler, compilerOptions) {
|
||||
compiledSrc = fs.readFileSync(targetFile, fsReadOptions);
|
||||
} else {
|
||||
templateSrc = fs.readFileSync(templatePath, fsReadOptions);
|
||||
compiledSrc = markoCompiler.compile(templateSrc, templatePath);
|
||||
compiledSrc = markoCompiler.compile(templateSrc, templatePath, compilerOptions);
|
||||
|
||||
// Write to a temporary file and move it into place to avoid problems
|
||||
// assocatiated with multiple processes write to the same file. We only
|
||||
@ -76,7 +79,7 @@ function getLoadedTemplate(path) {
|
||||
exports.install = function(options) {
|
||||
options = options || {};
|
||||
|
||||
var compilerOptions = options.compilerOptions || {};
|
||||
var compilerOptions = options.compilerOptions;
|
||||
|
||||
var extension = options.extension || '.marko';
|
||||
|
||||
|
||||
@ -28,12 +28,13 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"app-module-path": "^1.0.5",
|
||||
"async-writer": "^1.4.0",
|
||||
"async-writer": "^1.4.4",
|
||||
"browser-refresh-client": "^1.0.0",
|
||||
"char-props": "~0.1.5",
|
||||
"deresolve": "^1.0.0",
|
||||
"esprima": "^2.7.0",
|
||||
"events": "^1.0.2",
|
||||
"he": "^1.1.0",
|
||||
"htmljs-parser": "^1.5.3",
|
||||
"lasso-package-root": "^1.0.0",
|
||||
"marko-vdom": "^0.3.0",
|
||||
@ -59,7 +60,9 @@
|
||||
"chai": "^3.3.0",
|
||||
"coveralls": "^2.11.9",
|
||||
"express": "^4.13.4",
|
||||
"fs-extra": "^0.30.0",
|
||||
"istanbul": "^0.4.3",
|
||||
"jsdom": "^9.6.0",
|
||||
"jshint": "^2.5.0",
|
||||
"mocha": "^2.3.3",
|
||||
"request": "^2.72.0",
|
||||
@ -69,7 +72,7 @@
|
||||
"bin": {
|
||||
"markoc": "bin/markoc"
|
||||
},
|
||||
"main": "runtime/marko-runtime.js",
|
||||
"main": "runtime/html/index.js",
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
|
||||
@ -15,14 +15,7 @@
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
var escapeXml = require('raptor-util/escapeXml');
|
||||
var escapeXmlAttr = escapeXml.attr;
|
||||
var runtime = require('./'); // Circular dependency, but that is okay
|
||||
var attr = require('raptor-util/attr');
|
||||
var isArray = Array.isArray;
|
||||
var STYLE_ATTR = 'style';
|
||||
var CLASS_ATTR = 'class';
|
||||
var escapeEndingScriptTagRegExp = /<\//g;
|
||||
|
||||
function classListHelper(arg, classNames) {
|
||||
var len;
|
||||
@ -109,8 +102,9 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
s: function(str) {
|
||||
return (str == null) ? '' : str;
|
||||
return (str == null) ? '' : str.toString();
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal helper method to handle loops with a status variable
|
||||
* @private
|
||||
@ -172,128 +166,9 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Internal method to escape special XML characters
|
||||
* @private
|
||||
*/
|
||||
x: escapeXml,
|
||||
/**
|
||||
* Internal method to escape special XML characters within an attribute
|
||||
* @private
|
||||
*/
|
||||
xa: escapeXmlAttr,
|
||||
|
||||
/**
|
||||
* Escapes the '</' sequence in the body of a <script> body to avoid the `<script>` being
|
||||
* ended prematurely.
|
||||
*
|
||||
* For example:
|
||||
* var evil = {
|
||||
* name: '</script><script>alert(1)</script>'
|
||||
* };
|
||||
*
|
||||
* <script>var foo = ${JSON.stringify(evil)}</script>
|
||||
*
|
||||
* Without escaping the ending '</script>' sequence the opening <script> tag would be
|
||||
* prematurely ended and a new script tag could then be started that could then execute
|
||||
* arbitrary code.
|
||||
*/
|
||||
xs: function(val) {
|
||||
return (typeof val === 'string') ? val.replace(escapeEndingScriptTagRegExp, '\\u003C/') : val;
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal method to render a single HTML attribute
|
||||
* @private
|
||||
*/
|
||||
a: attr,
|
||||
|
||||
/**
|
||||
* Internal method to render multiple HTML attributes based on the properties of an object
|
||||
* @private
|
||||
*/
|
||||
as: function(arg) {
|
||||
if (typeof arg === 'object') {
|
||||
var out = '';
|
||||
for (var attrName in arg) {
|
||||
out += attr(attrName, arg[attrName]);
|
||||
}
|
||||
return out;
|
||||
} else if (typeof arg === 'string') {
|
||||
return arg;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal helper method to handle the "style" attribute. The value can either
|
||||
* be a string or an object with style propertes. For example:
|
||||
*
|
||||
* sa('color: red; font-weight: bold') ==> ' style="color: red; font-weight: bold"'
|
||||
* sa({color: 'red', 'font-weight': 'bold'}) ==> ' style="color: red; font-weight: bold"'
|
||||
*/
|
||||
sa: function(style) {
|
||||
if (!style) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof style === 'string') {
|
||||
return attr(STYLE_ATTR, style, false);
|
||||
} else if (typeof style === 'object') {
|
||||
var parts = [];
|
||||
for (var name in style) {
|
||||
if (style.hasOwnProperty(name)) {
|
||||
var value = style[name];
|
||||
if (value) {
|
||||
parts.push(name + ':' + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts ? attr(STYLE_ATTR, parts.join(';'), false) : '';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal helper method to handle the "class" attribute. The value can either
|
||||
* be a string, an array or an object. For example:
|
||||
*
|
||||
* ca('foo bar') ==> ' class="foo bar"'
|
||||
* ca({foo: true, bar: false, baz: true}) ==> ' class="foo baz"'
|
||||
* ca(['foo', 'bar']) ==> ' class="foo bar"'
|
||||
*/
|
||||
ca: function(classNames) {
|
||||
if (!classNames) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof classNames === 'string') {
|
||||
return attr(CLASS_ATTR, classNames, false);
|
||||
} else {
|
||||
return attr(CLASS_ATTR, classList(classNames), false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads a template (__helpers.l --> loadTemplate(path))
|
||||
*/
|
||||
l: function(path) {
|
||||
if (typeof path === 'string') {
|
||||
return runtime.load(path);
|
||||
} else {
|
||||
// Assume it is already a pre-loaded template
|
||||
return path;
|
||||
}
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// The helpers listed below require an out
|
||||
// ----------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Invoke a tag handler render function
|
||||
* Helper to load a custom tag
|
||||
*/
|
||||
t: function (renderer, targetProperty, isRepeated, hasNestedTags) {
|
||||
if (renderer) {
|
||||
@ -330,6 +205,13 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// The helpers listed below require an out
|
||||
// ----------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Internal method to handle includes/partials
|
||||
* @private
|
||||
|
||||
147
runtime/html/helpers.js
Normal file
147
runtime/html/helpers.js
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2011 eBay Software Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
var escapeXml = require('raptor-util/escapeXml');
|
||||
var escapeXmlAttr = escapeXml.attr;
|
||||
var runtime = require('../'); // Circular dependency, but that is okay
|
||||
var attr = require('raptor-util/attr');
|
||||
var extend = require('raptor-util/extend');
|
||||
|
||||
var STYLE_ATTR = 'style';
|
||||
var CLASS_ATTR = 'class';
|
||||
var escapeEndingScriptTagRegExp = /<\//g;
|
||||
|
||||
var commonHelpers = require('../helpers');
|
||||
|
||||
var classList = commonHelpers.cl;
|
||||
|
||||
module.exports = extend({
|
||||
/**
|
||||
* Internal method to escape special XML characters
|
||||
* @private
|
||||
*/
|
||||
x: escapeXml,
|
||||
/**
|
||||
* Internal method to escape special XML characters within an attribute
|
||||
* @private
|
||||
*/
|
||||
xa: escapeXmlAttr,
|
||||
|
||||
/**
|
||||
* Escapes the '</' sequence in the body of a <script> body to avoid the `<script>` being
|
||||
* ended prematurely.
|
||||
*
|
||||
* For example:
|
||||
* var evil = {
|
||||
* name: '</script><script>alert(1)</script>'
|
||||
* };
|
||||
*
|
||||
* <script>var foo = ${JSON.stringify(evil)}</script>
|
||||
*
|
||||
* Without escaping the ending '</script>' sequence the opening <script> tag would be
|
||||
* prematurely ended and a new script tag could then be started that could then execute
|
||||
* arbitrary code.
|
||||
*/
|
||||
xs: function(val) {
|
||||
return (typeof val === 'string') ? val.replace(escapeEndingScriptTagRegExp, '\\u003C/') : val;
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal method to render a single HTML attribute
|
||||
* @private
|
||||
*/
|
||||
a: attr,
|
||||
|
||||
/**
|
||||
* Internal method to render multiple HTML attributes based on the properties of an object
|
||||
* @private
|
||||
*/
|
||||
as: function(arg) {
|
||||
if (typeof arg === 'object') {
|
||||
var out = '';
|
||||
for (var attrName in arg) {
|
||||
out += attr(attrName, arg[attrName]);
|
||||
}
|
||||
return out;
|
||||
} else if (typeof arg === 'string') {
|
||||
return arg;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal helper method to handle the "style" attribute. The value can either
|
||||
* be a string or an object with style propertes. For example:
|
||||
*
|
||||
* sa('color: red; font-weight: bold') ==> ' style="color: red; font-weight: bold"'
|
||||
* sa({color: 'red', 'font-weight': 'bold'}) ==> ' style="color: red; font-weight: bold"'
|
||||
*/
|
||||
sa: function(style) {
|
||||
if (!style) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof style === 'string') {
|
||||
return attr(STYLE_ATTR, style, false);
|
||||
} else if (typeof style === 'object') {
|
||||
var parts = [];
|
||||
for (var name in style) {
|
||||
if (style.hasOwnProperty(name)) {
|
||||
var value = style[name];
|
||||
if (value) {
|
||||
parts.push(name + ':' + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts ? attr(STYLE_ATTR, parts.join(';'), false) : '';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal helper method to handle the "class" attribute. The value can either
|
||||
* be a string, an array or an object. For example:
|
||||
*
|
||||
* ca('foo bar') ==> ' class="foo bar"'
|
||||
* ca({foo: true, bar: false, baz: true}) ==> ' class="foo baz"'
|
||||
* ca(['foo', 'bar']) ==> ' class="foo bar"'
|
||||
*/
|
||||
ca: function(classNames) {
|
||||
if (!classNames) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof classNames === 'string') {
|
||||
return attr(CLASS_ATTR, classNames, false);
|
||||
} else {
|
||||
return attr(CLASS_ATTR, classList(classNames), false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads a template (__helpers.l --> marko_loadTemplate(path))
|
||||
*/
|
||||
l: function(path) {
|
||||
if (typeof path === 'string') {
|
||||
return runtime.load(path);
|
||||
} else {
|
||||
// Assume it is already a pre-loaded template
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}, commonHelpers);
|
||||
@ -88,15 +88,18 @@ Template.prototype = {
|
||||
this._ = createFunc(helpers);
|
||||
},
|
||||
renderSync: function(data) {
|
||||
var localData = data || {};
|
||||
var out = new AsyncWriter();
|
||||
out.sync();
|
||||
var localData;
|
||||
var globalData;
|
||||
|
||||
if (localData.$global) {
|
||||
out.global = extend(out.global, localData.$global);
|
||||
localData.$global = null;
|
||||
if ((localData = data)) {
|
||||
globalData = localData.$global;
|
||||
} else {
|
||||
localData = {};
|
||||
}
|
||||
|
||||
var out = new AsyncWriter(null, null, globalData);
|
||||
out.sync();
|
||||
|
||||
this._(localData, out);
|
||||
return out.getOutput();
|
||||
},
|
||||
@ -275,4 +278,4 @@ exports._inline = createInlineMarkoTemplate;
|
||||
// loaded and cached. On the server, the loader will use
|
||||
// the compiler to compile the template and then load the generated
|
||||
// module file using the Node.js module loader
|
||||
loader = require('./loader');
|
||||
loader = require('../loader');
|
||||
@ -20,8 +20,7 @@ var fs = require('fs');
|
||||
var Module = require('module').Module;
|
||||
var markoCompiler = require('../../compiler');
|
||||
var cwd = process.cwd();
|
||||
var fsReadOptions = {encoding: 'utf8'};
|
||||
var extend = require('raptor-util/extend');
|
||||
var fsOptions = {encoding: 'utf8'};
|
||||
|
||||
if (process.env.hasOwnProperty('MARKO_HOT_RELOAD')) {
|
||||
require('../../hot-reload').enable();
|
||||
@ -79,6 +78,8 @@ function getLoadedTemplate(path) {
|
||||
}
|
||||
|
||||
function loadFile(templatePath, options) {
|
||||
options = Object.assign({}, markoCompiler.defaultOptions, options);
|
||||
|
||||
var targetFile = templatePath + '.js';
|
||||
|
||||
// Short-circuit loading if the template has already been cached in the Node.js require cache
|
||||
@ -97,8 +98,6 @@ function loadFile(templatePath, options) {
|
||||
return cachedTemplate;
|
||||
}
|
||||
|
||||
options = extend(extend({}, markoCompiler.defaultOptions), options);
|
||||
|
||||
// If the `assumeUpToDate` option is true then we just assume that the compiled template on disk is up-to-date
|
||||
// if it exists
|
||||
if (options.assumeUpToDate) {
|
||||
@ -120,22 +119,16 @@ function loadFile(templatePath, options) {
|
||||
var filename = nodePath.basename(targetFile);
|
||||
var targetDir = nodePath.dirname(targetFile);
|
||||
var tempFile = nodePath.join(targetDir, '.' + process.pid + '.' + Date.now() + '.' + filename);
|
||||
fs.writeFileSync(tempFile, compiledSrc, fsReadOptions);
|
||||
fs.writeFileSync(tempFile, compiledSrc, fsOptions);
|
||||
fs.renameSync(tempFile, targetFile);
|
||||
|
||||
return require(targetFile);
|
||||
}
|
||||
|
||||
module.exports = function load(templatePath, templateSrc, options) {
|
||||
var writeToDisk;
|
||||
options = Object.assign({}, markoCompiler.defaultOptions, options);
|
||||
|
||||
if (options && (options.writeToDisk != null)) {
|
||||
// options is provided and options.writeToDisk is non-null
|
||||
writeToDisk = options.writeToDisk;
|
||||
} else {
|
||||
// writeToDisk should be inferred from defaultOptions
|
||||
writeToDisk = markoCompiler.defaultOptions.writeToDisk;
|
||||
}
|
||||
var writeToDisk = options.writeToDisk;
|
||||
|
||||
// If the template source is provided then we can compile the string
|
||||
// in memory and there is no need to read template file from disk or
|
||||
@ -148,10 +141,16 @@ module.exports = function load(templatePath, templateSrc, options) {
|
||||
// directly from the compiled source using the internals of the
|
||||
// Node.js module loading system.
|
||||
if (templateSrc === undefined) {
|
||||
templateSrc = fs.readFileSync(templatePath, fsReadOptions);
|
||||
templateSrc = fs.readFileSync(templatePath, fsOptions);
|
||||
}
|
||||
|
||||
var compiledSrc = markoCompiler.compile(templateSrc, templatePath, options);
|
||||
|
||||
if (writeToDisk === true) {
|
||||
var targetFile = templatePath + '.js';
|
||||
fs.writeFileSync(targetFile, compiledSrc, fsOptions);
|
||||
}
|
||||
|
||||
return loadSource(templatePath, compiledSrc);
|
||||
} else {
|
||||
return loadFile(templatePath, options);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{
|
||||
"main": "./marko-runtime.js",
|
||||
"main": "./html/index.js",
|
||||
"browser": {
|
||||
"./loader/index.js": "./loader/index-browser.js",
|
||||
"./stream/index.js": "./stream/index-browser.js"
|
||||
"./loader/index.js": "./loader/index-browser.js"
|
||||
}
|
||||
}
|
||||
@ -45,7 +45,7 @@ Readable.prototype = {
|
||||
|
||||
require('raptor-util/inherit')(Readable, stream.Readable);
|
||||
|
||||
require('./').Template.prototype.stream = function(data) {
|
||||
require('./html').Template.prototype.stream = function(data) {
|
||||
if (!stream) {
|
||||
throw new Error('Module not found: stream');
|
||||
}
|
||||
|
||||
@ -17,8 +17,13 @@
|
||||
'use strict';
|
||||
|
||||
var markoVDOM = require('marko-vdom');
|
||||
var commonHelpers = require('../helpers');
|
||||
var extend = require('raptor-util/extend');
|
||||
var runtime;
|
||||
|
||||
module.exports = {
|
||||
var classList = commonHelpers.cl;
|
||||
|
||||
module.exports = extend({
|
||||
e: markoVDOM.createElement,
|
||||
t: markoVDOM.createText,
|
||||
const: function(id) {
|
||||
@ -26,5 +31,66 @@ module.exports = {
|
||||
return function() {
|
||||
return id + (i++);
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Loads a template (__helpers.l --> marko_loadTemplate(path))
|
||||
*/
|
||||
l: function(path) {
|
||||
if (typeof path === 'string') {
|
||||
return runtime.load(path);
|
||||
} else {
|
||||
// Assume it is already a pre-loaded template
|
||||
return path;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper for generating the string for a style attribute
|
||||
* @param {[type]} style [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
sa: function(style) {
|
||||
if (!style) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof style === 'string') {
|
||||
return style;
|
||||
} else if (typeof style === 'object') {
|
||||
var parts = [];
|
||||
for (var name in style) {
|
||||
if (style.hasOwnProperty(name)) {
|
||||
var value = style[name];
|
||||
if (value) {
|
||||
parts.push(name + ':' + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parts ? parts.join(';') : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal helper method to handle the "class" attribute. The value can either
|
||||
* be a string, an array or an object. For example:
|
||||
*
|
||||
* ca('foo bar') ==> ' class="foo bar"'
|
||||
* ca({foo: true, bar: false, baz: true}) ==> ' class="foo baz"'
|
||||
* ca(['foo', 'bar']) ==> ' class="foo bar"'
|
||||
*/
|
||||
ca: function(classNames) {
|
||||
if (!classNames) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof classNames === 'string') {
|
||||
return classNames;
|
||||
} else {
|
||||
return classList(classNames);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, commonHelpers);
|
||||
|
||||
runtime = require('./');
|
||||
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright 2011 eBay Software Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This module provides the lightweight runtime for loading and rendering
|
||||
* templates. The compilation is handled by code that is part of the
|
||||
* [marko/compiler](https://github.com/raptorjs/marko/tree/master/compiler)
|
||||
* module. If rendering a template on the client, only the runtime is needed
|
||||
* on the client and not the compiler
|
||||
*/
|
||||
|
||||
// async-writer provides all of the magic to support asynchronous
|
||||
// rendering to a stream
|
||||
|
||||
'use strict';
|
||||
/**
|
||||
* Method is for internal usage only. This method
|
||||
* is invoked by code in a compiled Marko template and
|
||||
* it is used to create a new Template instance.
|
||||
* @private
|
||||
*/
|
||||
exports.c = function createTemplate(path) {
|
||||
return new Template(path);
|
||||
};
|
||||
|
||||
var asyncVdomBuilder = require('async-vdom-builder');
|
||||
|
||||
// helpers provide a core set of various utility methods
|
||||
// that are available in every template (empty, notEmpty, etc.)
|
||||
var helpers = require('./helpers');
|
||||
|
||||
var loader;
|
||||
|
||||
// If the optional "stream" module is available
|
||||
// then Readable will be a readable stream
|
||||
|
||||
var AsyncVDOMBuilder = asyncVdomBuilder.AsyncVDOMBuilder;
|
||||
var extend = require('raptor-util/extend');
|
||||
|
||||
function renderCallback(renderFunc, data, globalData, callback) {
|
||||
var out = new AsyncVDOMBuilder(globalData);
|
||||
|
||||
if (globalData) {
|
||||
extend(out.global, globalData);
|
||||
}
|
||||
|
||||
renderFunc(data, out);
|
||||
|
||||
return out.end()
|
||||
.on('finish', function() {
|
||||
callback(null, out.getOutput(), out);
|
||||
})
|
||||
.once('error', callback);
|
||||
}
|
||||
|
||||
function Template(path, func) {
|
||||
this.path = path;
|
||||
this._ = func;
|
||||
}
|
||||
|
||||
Template.prototype = {
|
||||
createOut() {
|
||||
return new AsyncVDOMBuilder();
|
||||
},
|
||||
/**
|
||||
* Internal method to initialize a loaded template with a
|
||||
* given create function that was generated by the compiler.
|
||||
* Warning: User code should not depend on this method.
|
||||
*
|
||||
* @private
|
||||
* @param {Function(__helpers)} createFunc The function used to produce the render(data, out) function.
|
||||
*/
|
||||
c: function(createFunc) {
|
||||
this._ = createFunc(helpers);
|
||||
},
|
||||
renderSync: function(data) {
|
||||
var localData;
|
||||
var globalData;
|
||||
|
||||
if (data) {
|
||||
localData = data;
|
||||
globalData = data.$global;
|
||||
localData.$global = null;
|
||||
} else {
|
||||
localData = {};
|
||||
}
|
||||
|
||||
var out = new AsyncVDOMBuilder(globalData);
|
||||
out.sync();
|
||||
this._(localData, out);
|
||||
return out.getOutput();
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders a template to either a stream (if the last
|
||||
* argument is a Stream instance) or
|
||||
* provides the output to a callback function (if the last
|
||||
* argument is a Function).
|
||||
*
|
||||
* Supported signatures:
|
||||
*
|
||||
* render(data, callback)
|
||||
* render(data, out)
|
||||
* render(data, stream)
|
||||
* render(data, out, callback)
|
||||
* render(data, stream, callback)
|
||||
*
|
||||
* @param {Object} data The view model data for the template
|
||||
* @param {AsyncVDOMBuilder} out A Stream or an AsyncVDOMBuilder instance
|
||||
* @param {Function} callback A callback function
|
||||
* @return {AsyncVDOMBuilder} Returns the AsyncVDOMBuilder instance that the template is rendered to
|
||||
*/
|
||||
render: function(data, out, callback) {
|
||||
var renderFunc = this._;
|
||||
var finalData;
|
||||
var globalData;
|
||||
if (data) {
|
||||
finalData = data;
|
||||
|
||||
if ((globalData = data.$global)) {
|
||||
// We will *move* the "$global" property
|
||||
// into the "out.global" object
|
||||
data.$global = null;
|
||||
}
|
||||
} else {
|
||||
finalData = {};
|
||||
}
|
||||
|
||||
if (typeof out === 'function') {
|
||||
// Short circuit for render(data, callback)
|
||||
return renderCallback(renderFunc, finalData, globalData, out);
|
||||
}
|
||||
|
||||
// NOTE: We create new vars here to avoid a V8 de-optimization due
|
||||
// to the following:
|
||||
// Assignment to parameter in arguments object
|
||||
var finalOut = out;
|
||||
|
||||
var shouldEnd = false;
|
||||
|
||||
if (arguments.length === 3) {
|
||||
if (globalData) {
|
||||
extend(finalOut.global, globalData);
|
||||
}
|
||||
|
||||
finalOut
|
||||
.on('finish', function() {
|
||||
callback(null, finalOut.getOutput(), finalOut);
|
||||
})
|
||||
.once('error', callback);
|
||||
} else if (!finalOut) {
|
||||
// Assume the "finalOut" is really a stream
|
||||
//
|
||||
// By default, we will buffer rendering to a stream to prevent
|
||||
// the response from being "too chunky".
|
||||
finalOut = new AsyncVDOMBuilder(globalData);
|
||||
shouldEnd = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Invoke the compiled template's render function to have it
|
||||
// write out strings to the provided out.
|
||||
renderFunc(finalData, finalOut);
|
||||
|
||||
// Automatically end output stream (the writer) if we
|
||||
// had to create an async writer (which might happen
|
||||
// if the caller did not provide a writer/out or the
|
||||
// writer/out was not an AsyncVDOMBuilder).
|
||||
//
|
||||
// If out parameter was originally an AsyncVDOMBuilder then
|
||||
// we assume that we are writing to output that was
|
||||
// created in the context of another rendering job.
|
||||
return shouldEnd ? finalOut.end() : finalOut;
|
||||
}
|
||||
};
|
||||
|
||||
function createRenderProxy(template) {
|
||||
return function(data, out) {
|
||||
template._(data, out);
|
||||
};
|
||||
}
|
||||
|
||||
function initTemplate(rawTemplate, templatePath) {
|
||||
if (rawTemplate.render) {
|
||||
return rawTemplate;
|
||||
}
|
||||
|
||||
var createFunc = rawTemplate.create || rawTemplate;
|
||||
|
||||
var template = createFunc.loaded;
|
||||
if (!template) {
|
||||
template = createFunc.loaded = new Template(templatePath);
|
||||
template.c(createFunc);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
function load(templatePath, templateSrc, options) {
|
||||
if (!templatePath) {
|
||||
throw new Error('"templatePath" is required');
|
||||
}
|
||||
|
||||
if (arguments.length === 1) {
|
||||
// templateSrc and options not provided
|
||||
} else if (arguments.length === 2) {
|
||||
// see if second argument is templateSrc (a String)
|
||||
// or options (an Object)
|
||||
var lastArg = arguments[arguments.length - 1];
|
||||
if (typeof lastArg !== 'string') {
|
||||
options = arguments[1];
|
||||
templateSrc = undefined;
|
||||
}
|
||||
} else if (arguments.length === 3) {
|
||||
// assume function called according to function signature
|
||||
} else {
|
||||
throw new Error('Illegal arguments');
|
||||
}
|
||||
|
||||
var template;
|
||||
|
||||
if (typeof templatePath === 'string') {
|
||||
template = initTemplate(loader(templatePath, templateSrc, options), templatePath);
|
||||
} else if (templatePath.render) {
|
||||
template = templatePath;
|
||||
} else {
|
||||
template = initTemplate(templatePath);
|
||||
}
|
||||
|
||||
if (options && (options.buffer != null)) {
|
||||
template = new Template(
|
||||
template.path,
|
||||
createRenderProxy(template),
|
||||
options);
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
function createInlineMarkoTemplate(filename, renderFunc) {
|
||||
return new Template(filename, renderFunc);
|
||||
}
|
||||
|
||||
exports.load = load;
|
||||
|
||||
exports.createOut = function() {
|
||||
return new AsyncVDOMBuilder();
|
||||
};
|
||||
|
||||
exports.helpers = helpers;
|
||||
|
||||
exports.Template = Template;
|
||||
|
||||
exports._inline = createInlineMarkoTemplate;
|
||||
|
||||
/**
|
||||
* Used to associate a DOM Document with marko. This is needed
|
||||
* to parse HTML fragments to insert into the VDOM tree.
|
||||
*/
|
||||
exports.setDocument = function(newDoc) {
|
||||
AsyncVDOMBuilder.prototype.document = newDoc;
|
||||
};
|
||||
|
||||
// The loader is used to load templates that have not already been
|
||||
// loaded and cached. On the server, the loader will use
|
||||
// the compiler to compile the template and then load the generated
|
||||
// module file using the Node.js module loader
|
||||
loader = require('../loader');
|
||||
@ -16,9 +16,12 @@
|
||||
|
||||
'use strict';
|
||||
module.exports = function render(input, out) {
|
||||
out.write('<!--');
|
||||
if (input.renderBody) {
|
||||
input.renderBody(out);
|
||||
if (out.write) {
|
||||
out.write('<!--');
|
||||
if (input.renderBody) {
|
||||
input.renderBody(out);
|
||||
}
|
||||
out.write('-->');
|
||||
}
|
||||
out.write('-->');
|
||||
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module.exports = function render(input, context) {
|
||||
module.exports = function render(input, out) {
|
||||
var content = {};
|
||||
|
||||
if (input.getContent) {
|
||||
@ -20,5 +20,5 @@ module.exports = function render(input, context) {
|
||||
}
|
||||
}
|
||||
templateData.layoutContent = content;
|
||||
input.__template.render(templateData, context);
|
||||
input.__template.render(templateData, out);
|
||||
};
|
||||
@ -22,9 +22,9 @@ var path = require('path');
|
||||
var assert = require('assert');
|
||||
|
||||
|
||||
function compareHelper(dir, actual, suffix) {
|
||||
var actualPath = path.join(dir, 'actual' + suffix);
|
||||
var expectedPath = path.join(dir, 'expected' + suffix);
|
||||
function compareHelper(dir, actual, prefix, suffix) {
|
||||
var actualPath = path.join(dir, prefix + 'actual' + suffix);
|
||||
var expectedPath = path.join(dir, prefix + 'expected' + suffix);
|
||||
|
||||
var isObject = typeof actual === 'string' ? false : true;
|
||||
var actualString = isObject ? JSON.stringify(actual, null, 4) : actual;
|
||||
@ -51,8 +51,12 @@ function autoTest(name, dir, run, options, done) {
|
||||
options = options || {};
|
||||
|
||||
var helpers = {
|
||||
compare(actual, suffix) {
|
||||
compareHelper(dir, actual, suffix);
|
||||
compare(actual, prefix, suffix) {
|
||||
if (arguments.length === 2) {
|
||||
suffix = prefix;
|
||||
prefix = null;
|
||||
}
|
||||
compareHelper(dir, actual, prefix || '', suffix || '');
|
||||
}
|
||||
};
|
||||
|
||||
@ -67,6 +71,10 @@ exports.scanDir = function(autoTestDir, run, options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.endsWith('.skip')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabledTests && !enabledTests[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
6
test/autotests/.gitignore
vendored
6
test/autotests/.gitignore
vendored
@ -2,6 +2,10 @@ actual.js
|
||||
actual.html
|
||||
actual.json
|
||||
*.marko.js
|
||||
*.marko.vdom.js
|
||||
actual.txt
|
||||
error.txt
|
||||
actual-*
|
||||
actual-*
|
||||
*.generated.*
|
||||
__vdom__
|
||||
*.skip/
|
||||
@ -1,3 +1,3 @@
|
||||
<div foo='Hello ${data.foo}'>
|
||||
<div foo='Hello ${data.foo || ''}'>
|
||||
Hello World!
|
||||
</div>
|
||||
@ -1,3 +1,3 @@
|
||||
<div foo='Hello $!{data.foo}'>
|
||||
<div foo='Hello $!{data.foo || ''}'>
|
||||
Hello World!
|
||||
</div>
|
||||
@ -1 +1 @@
|
||||
<hello>
|
||||
<hello>
|
||||
@ -0,0 +1 @@
|
||||
<div class="foo baz"></div>
|
||||
@ -0,0 +1,2 @@
|
||||
<div.foo class={ bar: false, baz: true }>
|
||||
</div>
|
||||
@ -0,0 +1 @@
|
||||
exports.templateData = {};
|
||||
@ -1,13 +1,24 @@
|
||||
exports.render = function(input, out) {
|
||||
out.write('Hello ' + input.name + '!');
|
||||
|
||||
var text = 'Hello ' + input.name + '!';
|
||||
|
||||
if (input.adult === true) {
|
||||
out.write(' (adult)');
|
||||
text += ' (adult)';
|
||||
} else if (input.adult === false) {
|
||||
out.write(' (child)');
|
||||
text += ' (child)';
|
||||
}
|
||||
|
||||
if (input.renderBody) {
|
||||
text += ' BODY: ';
|
||||
}
|
||||
|
||||
if (out.write) {
|
||||
out.write(text);
|
||||
} else {
|
||||
out.text(text);
|
||||
}
|
||||
|
||||
if (input.renderBody) {
|
||||
out.write(' BODY: ');
|
||||
input.renderBody(out);
|
||||
}
|
||||
|
||||
|
||||
@ -1 +1,2 @@
|
||||
exports.templateData = {};
|
||||
exports.vdomSkip = true;
|
||||
@ -1 +1,2 @@
|
||||
exports.templateData = {};
|
||||
exports.vdomSkip = true;
|
||||
@ -1,3 +1,5 @@
|
||||
exports.templateData = {
|
||||
attrs: ' foo="bar" baz'
|
||||
};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1 +1 @@
|
||||
<p><div><span>Hello Frank!</span></div></p>
|
||||
<p><span><a>Hello Frank!</a></span></p>
|
||||
@ -1,6 +1,6 @@
|
||||
p
|
||||
<div>
|
||||
<span>
|
||||
<span>
|
||||
<a>
|
||||
Hello ${data.name}!
|
||||
</>
|
||||
</>
|
||||
@ -1 +1 @@
|
||||
<div data-attr="Hello "John" <foo>">Hello <John>© <hello></div> ©
|
||||
<div data-attr="Hello "John" <foo>">Hello <John>© <hello></div> ©
|
||||
@ -7,7 +7,7 @@ exports.checkError = function(e) {
|
||||
expect(e.message).to.contain('<custom-tag>');
|
||||
|
||||
//includes the line number of the template
|
||||
expect(e.message).to.contain('error-thrown-in-generator/template.marko:2');
|
||||
expect(e.message).to.contain('template.marko:2');
|
||||
|
||||
//retains original stack trace
|
||||
expect(e.stack.toString()).to.contain('custom-tag.js:2:11');
|
||||
|
||||
@ -3,3 +3,5 @@ exports.templateData = {
|
||||
name: 'Evil </script>'
|
||||
}
|
||||
};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1 +1 @@
|
||||
<!--[if lt IE 9]><div><![endif]-->
|
||||
<!--This is a comment-->
|
||||
@ -1 +1 @@
|
||||
<html-comment><![CDATA[[if lt IE 9]><div><![endif]]]></html-comment>
|
||||
<html-comment>This is a comment</html-comment>
|
||||
@ -1,3 +1,5 @@
|
||||
exports.templateData = {
|
||||
"name": "World"
|
||||
};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1,3 +1,5 @@
|
||||
exports.templateData = {
|
||||
"name": "World"
|
||||
};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1 +1 @@
|
||||
<div><span if(foo)> Hello Frank! </span></div>
|
||||
<div><span if(foo)> Hello Frank! </span></div>
|
||||
@ -1 +1 @@
|
||||
<div><span if(foo)> Hello ${THIS IS NOT VALID}! </span></div>
|
||||
<div><span if(foo)> Hello ${THIS IS NOT VALID}! </span></div>
|
||||
@ -1 +1,2 @@
|
||||
exports.templateData = {};
|
||||
exports.vdomSkip = true;
|
||||
@ -1,5 +1,5 @@
|
||||
<div class="overlay">
|
||||
<div class="overlay-header ${data.header.className}" if(data.header)>
|
||||
<div class="overlay-header ${data.header.className || ''}" if(data.header)>
|
||||
<invoke data.header.renderBody(out)/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
<div class="overlay"><div class="overlay-header ">Header content!</div><div class="overlay-body my-body">Body content</div><div class="overlay-footer my-footer">Footer content</div></div>
|
||||
<div class="overlay"><div class="overlay-header">Header content!</div><div class="overlay-body my-body">Body content</div><div class="overlay-footer my-footer">Footer content</div></div>
|
||||
@ -1,5 +1,5 @@
|
||||
<div class="overlay">
|
||||
<div class="overlay-header ${data.header.className}" if(data.header)>
|
||||
<div class="overlay-header" if(data.header)>
|
||||
<invoke data.header.renderBody(out)/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
exports.templateData = {};
|
||||
exports.preserveWhitespaceGlobal = true;
|
||||
exports.preserveWhitespaceGlobal = true;
|
||||
exports.vdomSkip = true;
|
||||
@ -2,3 +2,4 @@ exports.templateData = {};
|
||||
exports.loadOptions = {
|
||||
preserveWhitespace: true
|
||||
};
|
||||
exports.vdomSkip = true;
|
||||
@ -1,3 +1,5 @@
|
||||
exports.templateData = {
|
||||
"name": "<script>evil</script>"
|
||||
};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1 +1,3 @@
|
||||
exports.templateData = {};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1 +1,3 @@
|
||||
exports.templateData = {};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1,3 +1,5 @@
|
||||
exports.templateData = {
|
||||
colors: ['red', 'green', 'blue']
|
||||
};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1,3 +1,5 @@
|
||||
exports.templateData = {
|
||||
colors: ['red', 'green', 'blue']
|
||||
};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1,3 +1,5 @@
|
||||
exports.templateData = {
|
||||
colors: ['red', 'green', 'blue']
|
||||
};
|
||||
|
||||
exports.vdomSkip = true;
|
||||
@ -1 +1,2 @@
|
||||
exports.templateData = {};
|
||||
exports.vdomSkip = true;
|
||||
@ -1 +1 @@
|
||||
<p>A <i>B</i> C</p> --- <p>D <i>E</i> F</p> --- <p>G <i>H</i> I</p> --- <p>J <div>K</div> L <div>M</div> N</p> --- <p><div>O</div><div>P</div><span>Q</span> <span>R</span></p>
|
||||
<p>A <i>B</i> C</p> --- <p>D <i>E</i> F</p> --- <p>G <i>H</i> I</p> --- <p>J <strong>K</strong> L <strong>M</strong> N</p> --- <p><strong>O</strong><strong>P</strong><span>Q</span> <span>R</span></p>
|
||||
@ -14,16 +14,16 @@
|
||||
---
|
||||
<p>
|
||||
J
|
||||
<div>K</div>
|
||||
<strong>K</strong>
|
||||
L
|
||||
<div>M</div>
|
||||
<strong>M</strong>
|
||||
N
|
||||
</p>
|
||||
---
|
||||
<p>
|
||||
<div>O</div>
|
||||
<strong>O</strong>
|
||||
|
||||
<div>P</div>
|
||||
<strong>P</strong>
|
||||
|
||||
<span>Q</span> <span>R</span>
|
||||
</p>
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_classList = __markoHelpers.cl,
|
||||
marko_str = __markoHelpers.s,
|
||||
marko_classAttr = __markoHelpers.ca;
|
||||
|
||||
return function render(data, out) {
|
||||
out.e("div", {
|
||||
"class": marko_classAttr(marko_classList("foo", {
|
||||
bar: true,
|
||||
baz: false
|
||||
}))
|
||||
}, 1)
|
||||
.t("Hello " +
|
||||
marko_str(name) +
|
||||
"!");
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko/vdom").c(__filename)).c(create);
|
||||
@ -0,0 +1,3 @@
|
||||
<div.foo class={ bar: true, baz: false }>
|
||||
Hello ${name}!
|
||||
</div>
|
||||
@ -1,10 +1,14 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_str = __markoHelpers.s;
|
||||
|
||||
return function render(data, out) {
|
||||
out.e("div", {
|
||||
foo: "bar",
|
||||
hello: "world"
|
||||
}, 1)
|
||||
.t(("Hello " + name) + "!");
|
||||
.t("Hello " +
|
||||
marko_str(name) +
|
||||
"!");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_str = __markoHelpers.s;
|
||||
|
||||
return function render(data, out) {
|
||||
var attrs = {
|
||||
foo: "bar",
|
||||
@ -6,7 +8,9 @@ function create(__markoHelpers) {
|
||||
};
|
||||
|
||||
out.e("div", attrs, 1)
|
||||
.t(("Hello " + name) + "!");
|
||||
.t("Hello " +
|
||||
marko_str(name) +
|
||||
"!");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_attrs0 = {
|
||||
var marko_str = __markoHelpers.s,
|
||||
marko_attrs0 = {
|
||||
"class": "foo"
|
||||
};
|
||||
|
||||
return function render(data, out) {
|
||||
out.e("div", marko_attrs0, 1)
|
||||
.t(("Hello " + name) + "!");
|
||||
.t("Hello " +
|
||||
marko_str(name) +
|
||||
"!");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
13
test/autotests/vdom-compiler/no-escape/expected.js
Normal file
13
test/autotests/vdom-compiler/no-escape/expected.js
Normal file
@ -0,0 +1,13 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_str = __markoHelpers.s;
|
||||
|
||||
return function render(data, out) {
|
||||
out.t("Hello " +
|
||||
marko_str(name) +
|
||||
"! ");
|
||||
|
||||
out.h(marko_str(message));
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko/vdom").c(__filename)).c(create);
|
||||
1
test/autotests/vdom-compiler/no-escape/template.marko
Normal file
1
test/autotests/vdom-compiler/no-escape/template.marko
Normal file
@ -0,0 +1 @@
|
||||
- Hello ${name}! $!{message}
|
||||
@ -1,21 +1,24 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_forEach = __markoHelpers.f,
|
||||
marko_createElement = require("marko/vdom/createElement"),
|
||||
marko_const = require("marko/runtime/vdom/const"),
|
||||
var marko_str = __markoHelpers.s,
|
||||
marko_forEach = __markoHelpers.f,
|
||||
marko_createElement = __markoHelpers.e,
|
||||
marko_const = __markoHelpers.const,
|
||||
marko_const_nextId = marko_const("733fee"),
|
||||
marko_node0 = marko_createElement("div", null, 1, marko_const_nextId())
|
||||
.t("No colors!");
|
||||
|
||||
return function render(data, out) {
|
||||
out.e("h1", null, 1)
|
||||
.t(("Hello " + data.name) + "!");
|
||||
.t("Hello " +
|
||||
marko_str(data.name) +
|
||||
"!");
|
||||
|
||||
if (data.colors.length) {
|
||||
out.be("ul");
|
||||
|
||||
marko_forEach(data.colors, function(color) {
|
||||
out.e("li", null, 1)
|
||||
.t(color);
|
||||
.t(marko_str(color));
|
||||
});
|
||||
|
||||
out.ee();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_createElement = require("marko/vdom/createElement"),
|
||||
marko_const = require("marko/runtime/vdom/const"),
|
||||
var marko_str = __markoHelpers.s,
|
||||
marko_createElement = __markoHelpers.e,
|
||||
marko_const = __markoHelpers.const,
|
||||
marko_const_nextId = marko_const("69a896"),
|
||||
marko_node0 = marko_createElement("div", {
|
||||
"class": "hello",
|
||||
@ -11,7 +12,9 @@ function create(__markoHelpers) {
|
||||
return function render(data, out) {
|
||||
out.e("span", null, 2)
|
||||
.e("h1", null, 1)
|
||||
.t(("Hello " + data.name) + "!")
|
||||
.t("Hello " +
|
||||
marko_str(data.name) +
|
||||
"!")
|
||||
.n(marko_node0);
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_createElement = require("marko/vdom/createElement"),
|
||||
marko_const = require("marko/runtime/vdom/const"),
|
||||
var marko_createElement = __markoHelpers.e,
|
||||
marko_const = __markoHelpers.const,
|
||||
marko_const_nextId = marko_const("0524f9"),
|
||||
marko_node0 = marko_createElement("div", {
|
||||
"class": "hello",
|
||||
|
||||
15
test/autotests/vdom-compiler/tag-body/expected.js
Normal file
15
test/autotests/vdom-compiler/tag-body/expected.js
Normal file
@ -0,0 +1,15 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_loadTag = __markoHelpers.t,
|
||||
test_hello = marko_loadTag(require("./tags/test-hello/renderer"));
|
||||
|
||||
return function render(data, out) {
|
||||
test_hello({
|
||||
name: "World",
|
||||
renderBody: function renderBody(out) {
|
||||
out.t("Body content");
|
||||
}
|
||||
}, out);
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko/vdom").c(__filename)).c(create);
|
||||
3
test/autotests/vdom-compiler/tag-body/marko.json
Normal file
3
test/autotests/vdom-compiler/tag-body/marko.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"tags-dir": "./tags"
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"renderer": "./renderer.js",
|
||||
"@name": "string",
|
||||
"@adult": "boolean"
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
exports.render = function(input, out) {
|
||||
};
|
||||
3
test/autotests/vdom-compiler/tag-body/template.marko
Normal file
3
test/autotests/vdom-compiler/tag-body/template.marko
Normal file
@ -0,0 +1,3 @@
|
||||
<test-hello name="World">
|
||||
Body content
|
||||
</test-hello>
|
||||
@ -4,10 +4,17 @@ var Module = require('module').Module;
|
||||
var oldResolveFilename = Module._resolveFilename;
|
||||
|
||||
var rootDir = nodePath.join(__dirname, '../');
|
||||
|
||||
Module._resolveFilename = function(request, parent, isMain) {
|
||||
if (request.startsWith('marko')) {
|
||||
request = request.substring('marko'.length);
|
||||
request = rootDir + request;
|
||||
}
|
||||
if (request.charAt(0) !== '.') {
|
||||
var firstSlash = request.indexOf('/');
|
||||
var targetPackageName = firstSlash === -1 ? request : request.substring(0, firstSlash);
|
||||
|
||||
if (targetPackageName === 'marko') {
|
||||
request = request.substring('marko'.length);
|
||||
request = rootDir + request;
|
||||
}
|
||||
}
|
||||
|
||||
return oldResolveFilename.call(this, request, parent, isMain);
|
||||
};
|
||||
117
test/util/domToHTML.js
Normal file
117
test/util/domToHTML.js
Normal file
@ -0,0 +1,117 @@
|
||||
function ltrim(s) {
|
||||
return s ? s.replace(/^\s\s*/,'') : '';
|
||||
}
|
||||
|
||||
function vdomToHTML(node, options) {
|
||||
|
||||
// NOTE: We don't use XMLSerializer because we need to sort the attributes to correctly compare output HTML strings
|
||||
// BAD: return (new XMLSerializer()).serializeToString(node);
|
||||
var html = '';
|
||||
function serializeHelper(node, indent) {
|
||||
if (node.nodeType === 1) {
|
||||
serializeElHelper(node, indent);
|
||||
} else if (node.nodeType === 3) {
|
||||
serializeTextHelper(node, indent);
|
||||
} else if (node.nodeType === 8) {
|
||||
serializeCommentHelper(node, indent);
|
||||
} else {
|
||||
console.log('Invalid node:', node);
|
||||
html += indent + `INVALID NODE TYPE ${node.nodeType}\n`;
|
||||
// throw new Error('Unexpected node type');
|
||||
}
|
||||
}
|
||||
|
||||
function serializeElHelper(el, indent) {
|
||||
var tagName = el.nodeName;
|
||||
|
||||
if (el.namespaceURI === 'http://www.w3.org/2000/svg') {
|
||||
tagName = 'svg:' + tagName;
|
||||
} else if (el.namespaceURI === 'http://www.w3.org/1998/Math/MathML') {
|
||||
tagName = 'math:' + tagName;
|
||||
}
|
||||
|
||||
html += indent + '<' + tagName;
|
||||
|
||||
var attributes = el.attributes;
|
||||
var attributesArray = [];
|
||||
var attrName;
|
||||
|
||||
if (typeof attributes.length === 'number') {
|
||||
for (var i=0; i<attributes.length; i++) {
|
||||
var attr = attributes[i];
|
||||
if (attr.namespaceURI) {
|
||||
attrName = attr.namespaceURI + ':' + attr.localName;
|
||||
} else {
|
||||
attrName = attr.name;
|
||||
}
|
||||
|
||||
if (attrName === 'data-marko-const') {
|
||||
continue;
|
||||
}
|
||||
attributesArray.push(' ' + attrName + '="' + attr.value + '"');
|
||||
}
|
||||
} else {
|
||||
for (attrName in attributes) {
|
||||
if (attrName === 'data-marko-const') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var attrValue = attributes[attrName];
|
||||
if (typeof attrValue !== 'string') {
|
||||
if (attrValue === true) {
|
||||
attrValue = '';
|
||||
} else if (!attrValue) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrName === 'xlink:href') {
|
||||
attrName = 'http://www.w3.org/1999/xlink:href';
|
||||
}
|
||||
attributesArray.push(' ' + attrName + '="' + attrValue + '"');
|
||||
}
|
||||
}
|
||||
|
||||
attributesArray.sort();
|
||||
|
||||
html += attributesArray.join('');
|
||||
|
||||
html += '>\n';
|
||||
|
||||
if (tagName.toUpperCase() === 'TEXTAREA') {
|
||||
html += indent + ' VALUE: ' + JSON.stringify(ltrim(el.value)) + '\n';
|
||||
} else {
|
||||
|
||||
if (tagName.toUpperCase() === 'PRE' && el.firstChild && el.firstChild.nodeType === 3) {
|
||||
el.firstChild.nodeValue = ltrim(el.firstChild.nodeValue);
|
||||
}
|
||||
var curChild = el.firstChild;
|
||||
while(curChild) {
|
||||
serializeHelper(curChild, indent + ' ');
|
||||
curChild = curChild.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function serializeTextHelper(node, indent) {
|
||||
html += indent + JSON.stringify(node.nodeValue) + '\n';
|
||||
}
|
||||
|
||||
function serializeCommentHelper(node, indent) {
|
||||
html += indent + '<!--' + JSON.stringify(node.nodeValue) + '-->\n';
|
||||
}
|
||||
|
||||
if (node.nodeType === 11 /* DocumentFragment */ || (options && options.childrenOnly)) {
|
||||
var curChild = node.firstChild;
|
||||
while(curChild) {
|
||||
serializeHelper(curChild, '');
|
||||
curChild = curChild.nextSibling;
|
||||
}
|
||||
} else {
|
||||
serializeHelper(node, '');
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
module.exports = vdomToHTML;
|
||||
139
test/vdom-render-test.js
Normal file
139
test/vdom-render-test.js
Normal file
@ -0,0 +1,139 @@
|
||||
'use strict';
|
||||
require('./patch-module');
|
||||
|
||||
var chai = require('chai');
|
||||
chai.config.includeStack = true;
|
||||
var path = require('path');
|
||||
var marko = require('../');
|
||||
var markoVDOM = require('../vdom');
|
||||
var autotest = require('./autotest');
|
||||
var fs = require('fs');
|
||||
var fsExtra = require('fs-extra');
|
||||
var domToHTML = require('./util/domToHTML');
|
||||
var jsdom = require("jsdom").jsdom;
|
||||
|
||||
require('../node-require').install();
|
||||
|
||||
var defaultDocument = jsdom('<html><body></body></html>');
|
||||
markoVDOM.setDocument(defaultDocument); // We need this to parse HTML fragments on the server
|
||||
|
||||
describe('render-vdom', function() {
|
||||
var autoTestDir = path.join(__dirname, 'autotests/render');
|
||||
|
||||
autotest.scanDir(
|
||||
autoTestDir,
|
||||
function run(dir, helpers, done) {
|
||||
require('../compiler').configure({ output: 'html' });
|
||||
|
||||
var vdomDir = path.join(dir, '../' + path.basename(dir) + '_vdom.skip');
|
||||
|
||||
fsExtra.removeSync(vdomDir);
|
||||
|
||||
fsExtra.copySync(dir, vdomDir, {
|
||||
filter: function(file) {
|
||||
if (file.endsWith('.marko.js') || file.indexOf('.generated.') !== -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var htmlTemplatePath = path.join(dir, 'template.marko');
|
||||
var vdomMainPath = path.join(vdomDir, 'test.js');
|
||||
var htmlMainPath = path.join(dir, 'test.js');
|
||||
var htmlMain = fs.existsSync(htmlMainPath) ? require(htmlMainPath) : {};
|
||||
var htmlTemplate = htmlMain.checkError ? null : marko.load(htmlTemplatePath);
|
||||
|
||||
|
||||
require('../compiler').configure({ output: 'vdom' });
|
||||
|
||||
var oldDone = done;
|
||||
done = function(err) {
|
||||
require('../compiler').configure({ output: 'html' });
|
||||
oldDone(err);
|
||||
};
|
||||
|
||||
var vdomMain = fs.existsSync(vdomMainPath) ? require(vdomMainPath) : {};
|
||||
|
||||
if (vdomMain && vdomMain.vdomSkip) {
|
||||
return done();
|
||||
}
|
||||
|
||||
var loadOptions = vdomMain.loadOptions;
|
||||
if (loadOptions) {
|
||||
loadOptions = Object.assign({}, loadOptions);
|
||||
} else {
|
||||
loadOptions = {};
|
||||
}
|
||||
|
||||
loadOptions.output = 'vdom';
|
||||
// loadOptions.writeToDisk = false;
|
||||
|
||||
if (vdomMain.writeToDisk === false) {
|
||||
require('marko/compiler').defaultOptions.writeToDisk = false;
|
||||
}
|
||||
|
||||
if (vdomMain.preserveWhitespaceGlobal === true) {
|
||||
require('marko/compiler').defaultOptions.preserveWhitespace = true;
|
||||
}
|
||||
|
||||
var templateSrc = fs.readFileSync(htmlTemplatePath, { encoding: 'utf8' });
|
||||
var vdomTemplatePath = path.join(vdomDir, 'template.marko');
|
||||
|
||||
try {
|
||||
if (vdomMain.checkError) {
|
||||
var e;
|
||||
|
||||
try {
|
||||
marko.load(vdomTemplatePath, templateSrc, loadOptions);
|
||||
} catch(_e) {
|
||||
e = _e;
|
||||
var errorFile = path.join(dir, 'error.txt');
|
||||
fs.writeFileSync(errorFile, e.stack.toString(), { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
if (!e) {
|
||||
throw new Error('Error expected');
|
||||
}
|
||||
|
||||
vdomMain.checkError(e);
|
||||
require('../compiler').configure({ output: 'html' });
|
||||
return done();
|
||||
} else {
|
||||
var vdomTemplate = marko.load(vdomTemplatePath, loadOptions);
|
||||
|
||||
var templateData = vdomMain.templateData || {};
|
||||
|
||||
var vdomTree = vdomTemplate.renderSync(templateData);
|
||||
|
||||
|
||||
var expectedHtml;
|
||||
|
||||
try {
|
||||
expectedHtml = fs.readFileSync(path.join(dir, 'vdom-expected.html'), { encoding: 'utf8'});
|
||||
} catch(e) {}
|
||||
|
||||
if (!expectedHtml) {
|
||||
var html = htmlTemplate.renderSync(htmlMain.templateData || {});
|
||||
var document = jsdom('<html><body>' + html + '</body></html>');
|
||||
expectedHtml = domToHTML(document.body, { childrenOnly: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(dir, 'vdom-expected.generated.html'), expectedHtml, { encoding: 'utf8' });
|
||||
var vdomHtml = domToHTML(vdomTree.actualize(defaultDocument));
|
||||
helpers.compare(vdomHtml, 'vdom-', '.generated.html');
|
||||
require('../compiler').configure({ output: 'html' });
|
||||
return done();
|
||||
}
|
||||
} finally {
|
||||
if (vdomMain.writeToDisk === false) {
|
||||
delete require('marko/compiler').defaultOptions.writeToDisk;
|
||||
}
|
||||
|
||||
if (vdomMain.preserveWhitespaceGlobal === true) {
|
||||
delete require('marko/compiler').defaultOptions.preserveWhitespace;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user