mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
vdom support
This commit is contained in:
parent
f309250843
commit
e76c7fa6d6
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
||||
Copyright 2011 eBay Software Foundation
|
||||
Copyright 2016 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.
|
||||
|
||||
@ -61,6 +61,10 @@ function makeNode(arg) {
|
||||
return arg;
|
||||
} else if (arg == null) {
|
||||
return undefined;
|
||||
} else if (Array.isArray(arg)) {
|
||||
return arg.map((arg) => {
|
||||
return makeNode(arg);
|
||||
});
|
||||
} else {
|
||||
throw new Error('Argument should be a string or Node or null. Actual: ' + arg);
|
||||
}
|
||||
|
||||
@ -39,9 +39,11 @@ class FinalNodes {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node instanceof Html && this.lastNode instanceof Html) {
|
||||
this.lastNode.append(node);
|
||||
return;
|
||||
if (node instanceof Html) {
|
||||
if (this.lastNode instanceof Html) {
|
||||
this.lastNode.append(node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.setFinalNode) {
|
||||
@ -185,6 +187,7 @@ class CodeGenerator {
|
||||
node.setCodeGenerator(null);
|
||||
|
||||
generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, false);
|
||||
|
||||
if (generatedCode != null && generatedCode !== node) {
|
||||
node = null;
|
||||
this._generateCode(generatedCode, finalNodes);
|
||||
|
||||
@ -9,8 +9,10 @@ const Container = require('./ast/Container');
|
||||
const isValidJavaScriptVarName = require('./util/isValidJavaScriptVarName');
|
||||
|
||||
class CodeWriter {
|
||||
constructor(options) {
|
||||
constructor(options, builder) {
|
||||
ok(builder, '"builder" is required');
|
||||
options = options || {};
|
||||
this.builder = builder;
|
||||
this.root = null;
|
||||
this._indentStr = options.indent != null ? options.indent : ' ';
|
||||
this._indentSize = this._indentStr.length;
|
||||
|
||||
@ -13,6 +13,8 @@ var Node = require('./ast/Node');
|
||||
var macros = require('./util/macros');
|
||||
var extend = require('raptor-util/extend');
|
||||
var Walker = require('./Walker');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var utilFingerprint = require('./util/fingerprint');
|
||||
|
||||
const FLAG_PRESERVE_WHITESPACE = 'PRESERVE_WHITESPACE';
|
||||
|
||||
@ -66,8 +68,9 @@ const helpers = {
|
||||
'loadTemplate': 'l'
|
||||
};
|
||||
|
||||
class CompileContext {
|
||||
class CompileContext extends EventEmitter {
|
||||
constructor(src, filename, builder, options) {
|
||||
super();
|
||||
ok(typeof src === 'string', '"src" string is required');
|
||||
ok(filename, '"filename" is required');
|
||||
|
||||
@ -103,6 +106,8 @@ class CompileContext {
|
||||
}
|
||||
|
||||
this._helpers = {};
|
||||
this._imports = {};
|
||||
this._fingerprint = undefined;
|
||||
}
|
||||
|
||||
setInline(isInline) {
|
||||
@ -184,9 +189,16 @@ class CompileContext {
|
||||
throw new Error('"path" should be a string');
|
||||
}
|
||||
|
||||
return this.addStaticVar(varName, 'require("' + path + '")');
|
||||
}
|
||||
var varId = this._imports[path];
|
||||
|
||||
if (!varId) {
|
||||
var builder = this.builder;
|
||||
var requireFuncCall = this.builder.require(builder.literal(path));
|
||||
this._imports[path] = varId = this.addStaticVar(varName, requireFuncCall);
|
||||
}
|
||||
|
||||
return varId;
|
||||
}
|
||||
|
||||
addVar(name, init) {
|
||||
var actualVarName = this._uniqueVars.addVar(name, init);
|
||||
@ -527,6 +539,19 @@ class CompileContext {
|
||||
|
||||
return helperIdentifier;
|
||||
}
|
||||
|
||||
getFingerprint(len) {
|
||||
var fingerprint = this._fingerprint;
|
||||
if (!fingerprint) {
|
||||
this._fingerprint = fingerprint = utilFingerprint(this.src);
|
||||
}
|
||||
|
||||
if (len == null || len >= this._fingerprint) {
|
||||
return fingerprint;
|
||||
} else {
|
||||
return fingerprint.substring(0, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompileContext.prototype.util = {
|
||||
|
||||
@ -87,7 +87,7 @@ class CompiledTemplate {
|
||||
handleErrors(this.context);
|
||||
|
||||
// console.log(module.id, 'FINAL AST:' + JSON.stringify(finalAST, null, 4));
|
||||
var codeWriter = new CodeWriter(this.context.options);
|
||||
var codeWriter = new CodeWriter(this.context.options, this.context.builder);
|
||||
codeWriter.write(this.ast);
|
||||
|
||||
handleErrors(this.context);
|
||||
|
||||
@ -58,6 +58,7 @@ class InlineCompiler {
|
||||
constructor(context, compiler) {
|
||||
this.context = context;
|
||||
this.compiler = compiler;
|
||||
this.builder = context.builder;
|
||||
|
||||
context.setInline(true);
|
||||
}
|
||||
@ -75,7 +76,7 @@ class InlineCompiler {
|
||||
return null;
|
||||
}
|
||||
|
||||
let codeWriter = new CodeWriter(this.context.options);
|
||||
let codeWriter = new CodeWriter(this.context.options, this.builder);
|
||||
codeWriter.write(staticNodes);
|
||||
return codeWriter.getCode();
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ class Parser {
|
||||
var builder = this.context.builder;
|
||||
|
||||
if (this.prevTextNode && this.prevTextNode.isLiteral()) {
|
||||
this.prevTextNode.appendText(text);
|
||||
this.prevTextNode.argument.value += text;
|
||||
} else {
|
||||
var escape = false;
|
||||
this.prevTextNode = builder.text(builder.literal(text), escape);
|
||||
|
||||
@ -8,6 +8,8 @@ class Walker {
|
||||
constructor(options) {
|
||||
this._enter = options.enter || noop;
|
||||
this._exit = options.exit || noop;
|
||||
this._enterArray = options.enterArray || noop;
|
||||
this._exitArray = options.exitArray || noop;
|
||||
this._stopped = false;
|
||||
this._reset();
|
||||
this._stack = [];
|
||||
@ -38,6 +40,8 @@ class Walker {
|
||||
_walkArray(array) {
|
||||
var hasRemoval = false;
|
||||
|
||||
array = this._enterArray(array) || array;
|
||||
|
||||
array.forEach((node, i) => {
|
||||
var transformed = this.walk(node);
|
||||
if (transformed == null) {
|
||||
@ -56,6 +60,8 @@ class Walker {
|
||||
}
|
||||
}
|
||||
|
||||
array = this._exitArray(array) || array;
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ class Html extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
generateCode() {
|
||||
generateHTMLCode() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
'use strict';
|
||||
var Node = require('./Node');
|
||||
var Literal = require('./Literal');
|
||||
var ok = require('assert').ok;
|
||||
var escapeXmlAttr = require('raptor-util/escapeXml').attr;
|
||||
|
||||
var attr = require('raptor-util/attr');
|
||||
var compiler = require('../');
|
||||
var escapeXmlAttr = require('raptor-util/escapeXml').attr;
|
||||
|
||||
function isStringLiteral(node) {
|
||||
return node.type === 'Literal' && typeof node.value === 'string';
|
||||
@ -112,98 +109,29 @@ function generateCodeForExpressionAttr(name, value, escape, codegen) {
|
||||
return finalNodes;
|
||||
}
|
||||
|
||||
module.exports = function generateCode(node, codegen) {
|
||||
let name = node.name;
|
||||
let value = node.value;
|
||||
let argument = node.argument;
|
||||
let escape = node.escape !== false;
|
||||
var builder = codegen.builder;
|
||||
|
||||
function beforeGenerateCode(event) {
|
||||
event.codegen.isInAttribute = true;
|
||||
}
|
||||
|
||||
function afterGenerateCode(event) {
|
||||
event.codegen.isInAttribute = false;
|
||||
}
|
||||
|
||||
class HtmlAttribute extends Node {
|
||||
constructor(def) {
|
||||
super('HtmlAttribute');
|
||||
|
||||
ok(def, 'Invalid attribute definition');
|
||||
this.type = 'HtmlAttribute';
|
||||
this.name = def.name;
|
||||
this.value = def.value;
|
||||
this.rawValue = def.rawValue;
|
||||
this.escape = def.escape;
|
||||
|
||||
if (typeof this.value === 'string') {
|
||||
this.value = compiler.builder.parseExpression(this.value);
|
||||
}
|
||||
|
||||
if (this.value && !(this.value instanceof Node)) {
|
||||
throw new Error('"value" should be a Node instance');
|
||||
}
|
||||
|
||||
this.argument = def.argument;
|
||||
|
||||
this.def = def.def; // The attribute definition loaded from the taglib (if any)
|
||||
|
||||
this.on('beforeGenerateCode', beforeGenerateCode);
|
||||
this.on('afterGenerateCode', afterGenerateCode);
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
isLiteralValue() {
|
||||
return this.value instanceof Literal;
|
||||
if (node.isLiteralValue()) {
|
||||
return builder.htmlLiteral(attr(name, value.value));
|
||||
} else if (value != null) {
|
||||
return generateCodeForExpressionAttr(name, value, escape, codegen);
|
||||
} else if (argument) {
|
||||
return [
|
||||
builder.htmlLiteral(' ' + name + '('),
|
||||
builder.htmlLiteral(argument),
|
||||
builder.htmlLiteral(')')
|
||||
];
|
||||
} else {
|
||||
// Attribute with no value is a boolean attribute
|
||||
return builder.htmlLiteral(' ' + name);
|
||||
}
|
||||
|
||||
isLiteralString() {
|
||||
return this.isLiteralValue() &&
|
||||
typeof this.value.value === 'string';
|
||||
}
|
||||
|
||||
isLiteralBoolean() {
|
||||
return this.isLiteralValue() &&
|
||||
typeof this.value.value === 'boolean';
|
||||
}
|
||||
|
||||
generateHTMLCode(codegen) {
|
||||
let name = this.name;
|
||||
let value = this.value;
|
||||
let argument = this.argument;
|
||||
let escape = this.escape !== false;
|
||||
var builder = codegen.builder;
|
||||
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isLiteralValue()) {
|
||||
return builder.htmlLiteral(attr(name, value.value));
|
||||
} else if (value != null) {
|
||||
return generateCodeForExpressionAttr(name, value, escape, codegen);
|
||||
} else if (argument) {
|
||||
return [
|
||||
builder.htmlLiteral(' ' + name + '('),
|
||||
builder.htmlLiteral(argument),
|
||||
builder.htmlLiteral(')')
|
||||
];
|
||||
} else {
|
||||
// Attribute with no value is a boolean attribute
|
||||
return builder.htmlLiteral(' ' + name);
|
||||
}
|
||||
}
|
||||
|
||||
walk(walker) {
|
||||
this.value = walker.walk(this.value);
|
||||
}
|
||||
|
||||
get literalValue() {
|
||||
if (this.isLiteralValue()) {
|
||||
return this.value.value;
|
||||
} else {
|
||||
throw new Error('Attribute value is not a literal value. Actual: ' + JSON.stringify(this.value, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HtmlAttribute.isHtmlAttribute = function(attr) {
|
||||
return (attr instanceof HtmlAttribute);
|
||||
};
|
||||
|
||||
module.exports = HtmlAttribute;
|
||||
};
|
||||
85
compiler/ast/HtmlAttribute/index.js
Normal file
85
compiler/ast/HtmlAttribute/index.js
Normal file
@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
var Node = require('../Node');
|
||||
var Literal = require('../Literal');
|
||||
var ok = require('assert').ok;
|
||||
var compiler = require('../../');
|
||||
var generateHTMLCode = require('./html/generateCode');
|
||||
var generateVDOMCode = require('./vdom/generateCode');
|
||||
var vdomUtil = require('../../util/vdom');
|
||||
|
||||
function beforeGenerateCode(event) {
|
||||
event.codegen.isInAttribute = true;
|
||||
}
|
||||
|
||||
function afterGenerateCode(event) {
|
||||
event.codegen.isInAttribute = false;
|
||||
}
|
||||
|
||||
class HtmlAttribute extends Node {
|
||||
constructor(def) {
|
||||
super('HtmlAttribute');
|
||||
|
||||
ok(def, 'Invalid attribute definition');
|
||||
this.type = 'HtmlAttribute';
|
||||
this.name = def.name;
|
||||
this.value = def.value;
|
||||
this.rawValue = def.rawValue;
|
||||
this.escape = def.escape;
|
||||
|
||||
if (typeof this.value === 'string') {
|
||||
this.value = compiler.builder.parseExpression(this.value);
|
||||
}
|
||||
|
||||
if (this.value && !(this.value instanceof Node)) {
|
||||
throw new Error('"value" should be a Node instance');
|
||||
}
|
||||
|
||||
this.argument = def.argument;
|
||||
|
||||
this.def = def.def; // The attribute definition loaded from the taglib (if any)
|
||||
|
||||
this.on('beforeGenerateCode', beforeGenerateCode);
|
||||
this.on('afterGenerateCode', afterGenerateCode);
|
||||
}
|
||||
|
||||
generateHTMLCode(codegen) {
|
||||
return generateHTMLCode(this, codegen);
|
||||
}
|
||||
|
||||
generateVDOMCode(codegen) {
|
||||
return generateVDOMCode(this, codegen, vdomUtil);
|
||||
}
|
||||
|
||||
isLiteralValue() {
|
||||
return this.value instanceof Literal;
|
||||
}
|
||||
|
||||
isLiteralString() {
|
||||
return this.isLiteralValue() &&
|
||||
typeof this.value.value === 'string';
|
||||
}
|
||||
|
||||
isLiteralBoolean() {
|
||||
return this.isLiteralValue() &&
|
||||
typeof this.value.value === 'boolean';
|
||||
}
|
||||
|
||||
walk(walker) {
|
||||
this.value = walker.walk(this.value);
|
||||
}
|
||||
|
||||
get literalValue() {
|
||||
if (this.isLiteralValue()) {
|
||||
return this.value.value;
|
||||
} else {
|
||||
throw new Error('Attribute value is not a literal value. Actual: ' + JSON.stringify(this.value, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HtmlAttribute.isHtmlAttribute = function(attr) {
|
||||
return (attr instanceof HtmlAttribute);
|
||||
};
|
||||
|
||||
module.exports = HtmlAttribute;
|
||||
6
compiler/ast/HtmlAttribute/vdom/generateCode.js
Normal file
6
compiler/ast/HtmlAttribute/vdom/generateCode.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = function generateCode(node, codegen, vdomUtil) {
|
||||
node.name = codegen.generateCode(node.name);
|
||||
node.value = codegen.generateCode(node.value);
|
||||
node.isStatic = vdomUtil.isStaticValue(node.value);
|
||||
return node;
|
||||
};
|
||||
@ -1,295 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var Node = require('./Node');
|
||||
var Literal = require('./Literal');
|
||||
var HtmlAttributeCollection = require('./HtmlAttributeCollection');
|
||||
|
||||
class StartTag extends Node {
|
||||
constructor(def) {
|
||||
super('StartTag');
|
||||
|
||||
this.tagName = def.tagName;
|
||||
this.attributes = def.attributes;
|
||||
this.argument = def.argument;
|
||||
this.selfClosed = def.selfClosed;
|
||||
this.dynamicAttributes = def.dynamicAttributes;
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
var builder = codegen.builder;
|
||||
|
||||
var tagName = this.tagName;
|
||||
var selfClosed = this.selfClosed;
|
||||
var dynamicAttributes = this.dynamicAttributes;
|
||||
var context = codegen.context;
|
||||
|
||||
var nodes = [
|
||||
builder.htmlLiteral('<'),
|
||||
builder.html(tagName),
|
||||
];
|
||||
|
||||
var attributes = this.attributes;
|
||||
|
||||
if (attributes) {
|
||||
for (let i=0; i<attributes.length; i++) {
|
||||
let attr = attributes[i];
|
||||
nodes.push(codegen.generateCode(attr));
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicAttributes) {
|
||||
dynamicAttributes.forEach(function(attrsExpression) {
|
||||
let attrsFunctionCall = builder.functionCall(context.helper('attrs'), [attrsExpression]);
|
||||
nodes.push(builder.html(attrsFunctionCall));
|
||||
});
|
||||
}
|
||||
|
||||
if (selfClosed) {
|
||||
nodes.push(builder.htmlLiteral('/>'));
|
||||
} else {
|
||||
nodes.push(builder.htmlLiteral('>'));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
|
||||
class EndTag extends Node {
|
||||
constructor(def) {
|
||||
super('EndTag');
|
||||
this.tagName = def.tagName;
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
var tagName = this.tagName;
|
||||
var builder = codegen.builder;
|
||||
|
||||
return [
|
||||
builder.htmlLiteral('</'),
|
||||
builder.html(tagName),
|
||||
builder.htmlLiteral('>')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function beforeGenerateCode(event) {
|
||||
if (event.node.tagName === 'script') {
|
||||
event.context.pushFlag('SCRIPT_BODY');
|
||||
}
|
||||
}
|
||||
|
||||
function afterGenerateCode(event) {
|
||||
if (event.node.tagName === 'script') {
|
||||
event.context.popFlag('SCRIPT_BODY');
|
||||
}
|
||||
}
|
||||
|
||||
class HtmlElement extends Node {
|
||||
constructor(def) {
|
||||
super('HtmlElement');
|
||||
this.tagName = null;
|
||||
this.tagNameExpression = null;
|
||||
this.setTagName(def.tagName);
|
||||
this._attributes = def.attributes;
|
||||
this.body = this.makeContainer(def.body);
|
||||
this.argument = def.argument;
|
||||
|
||||
if (!(this._attributes instanceof HtmlAttributeCollection)) {
|
||||
this._attributes = new HtmlAttributeCollection(this._attributes);
|
||||
}
|
||||
|
||||
this.openTagOnly = def.openTagOnly;
|
||||
this.selfClosed = def.selfClosed;
|
||||
this.dynamicAttributes = undefined;
|
||||
this.bodyOnlyIf = undefined;
|
||||
|
||||
this.on('beforeGenerateCode', beforeGenerateCode);
|
||||
this.on('afterGenerateCode', afterGenerateCode);
|
||||
}
|
||||
|
||||
generateHTMLCode(codegen) {
|
||||
var tagName = this.tagName;
|
||||
|
||||
// Convert the tag name into a Node so that we generate the code correctly
|
||||
if (tagName) {
|
||||
tagName = codegen.builder.literal(tagName);
|
||||
} else {
|
||||
tagName = this.tagNameExpression;
|
||||
}
|
||||
|
||||
var context = codegen.context;
|
||||
|
||||
if (context.isMacro(this.tagName)) {
|
||||
// At code generation time, if this tag corresponds to a registered macro
|
||||
// then invoke the macro based on this HTML element instead of generating
|
||||
// the code to render an HTML element.
|
||||
return codegen.builder.invokeMacroFromEl(this);
|
||||
}
|
||||
|
||||
var attributes = this._attributes && this._attributes.all;
|
||||
var body = this.body;
|
||||
var argument = this.argument;
|
||||
var hasBody = body && body.length;
|
||||
var openTagOnly = this.openTagOnly;
|
||||
var bodyOnlyIf = this.bodyOnlyIf;
|
||||
var dynamicAttributes = this.dynamicAttributes;
|
||||
var selfClosed = this.selfClosed === true;
|
||||
|
||||
var builder = codegen.builder;
|
||||
|
||||
if (hasBody) {
|
||||
body = codegen.generateCode(body);
|
||||
}
|
||||
|
||||
if (hasBody || bodyOnlyIf) {
|
||||
openTagOnly = false;
|
||||
selfClosed = false;
|
||||
} else if (selfClosed){
|
||||
openTagOnly = true;
|
||||
}
|
||||
|
||||
var startTag = new StartTag({
|
||||
tagName: tagName,
|
||||
attributes: attributes,
|
||||
argument: argument,
|
||||
selfClosed: selfClosed,
|
||||
dynamicAttributes: dynamicAttributes
|
||||
});
|
||||
|
||||
var endTag;
|
||||
|
||||
if (!openTagOnly) {
|
||||
endTag = new EndTag({
|
||||
tagName: tagName
|
||||
});
|
||||
}
|
||||
|
||||
if (bodyOnlyIf) {
|
||||
var startIf = builder.ifStatement(builder.negate(bodyOnlyIf), [
|
||||
startTag
|
||||
]);
|
||||
|
||||
var endIf = builder.ifStatement(builder.negate(bodyOnlyIf), [
|
||||
endTag
|
||||
]);
|
||||
|
||||
return [
|
||||
startIf,
|
||||
body,
|
||||
endIf
|
||||
];
|
||||
} else {
|
||||
if (openTagOnly) {
|
||||
return codegen.generateCode(startTag);
|
||||
} else {
|
||||
return [
|
||||
startTag,
|
||||
body,
|
||||
endTag
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addDynamicAttributes(expression) {
|
||||
if (!this.dynamicAttributes) {
|
||||
this.dynamicAttributes = [];
|
||||
}
|
||||
|
||||
this.dynamicAttributes.push(expression);
|
||||
}
|
||||
|
||||
getAttribute(name) {
|
||||
return this._attributes != null && this._attributes.getAttribute(name);
|
||||
}
|
||||
|
||||
getAttributeValue(name) {
|
||||
var attr = this._attributes != null && this._attributes.getAttribute(name);
|
||||
if (attr) {
|
||||
return attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
addAttribute(attr) {
|
||||
this._attributes.addAttribute(attr);
|
||||
}
|
||||
|
||||
setAttributeValue(name, value) {
|
||||
this._attributes.setAttributeValue(name, value);
|
||||
}
|
||||
|
||||
replaceAttributes(newAttributes) {
|
||||
this._attributes.replaceAttributes(newAttributes);
|
||||
}
|
||||
|
||||
removeAttribute(name) {
|
||||
if (this._attributes) {
|
||||
this._attributes.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
removeAllAttributes() {
|
||||
this._attributes.removeAllAttributes();
|
||||
}
|
||||
|
||||
hasAttribute(name) {
|
||||
return this._attributes != null && this._attributes.hasAttribute(name);
|
||||
}
|
||||
|
||||
getAttributes() {
|
||||
return this._attributes.all;
|
||||
}
|
||||
|
||||
get attributes() {
|
||||
return this._attributes.all;
|
||||
}
|
||||
|
||||
forEachAttribute(callback, thisObj) {
|
||||
var attributes = this._attributes.all.concat([]);
|
||||
|
||||
for (let i=0, len=attributes.length; i<len; i++) {
|
||||
callback.call(thisObj, attributes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
setTagName(tagName) {
|
||||
this.tagName = null;
|
||||
this.tagNameExpression = null;
|
||||
|
||||
if (tagName instanceof Node) {
|
||||
if (tagName instanceof Literal) {
|
||||
this.tagName = tagName.value;
|
||||
this.tagNameExpression = tagName;
|
||||
} else {
|
||||
this.tagNameExpression = tagName;
|
||||
}
|
||||
} else if (typeof tagName === 'string') {
|
||||
this.tagNameExpression = new Literal({value: tagName});
|
||||
this.tagName = tagName;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: this.type,
|
||||
tagName: this.tagName,
|
||||
attributes: this._attributes,
|
||||
argument: this.argument,
|
||||
body: this.body,
|
||||
bodyOnlyIf: this.bodyOnlyIf,
|
||||
dynamicAttributes: this.dynamicAttributes
|
||||
};
|
||||
}
|
||||
|
||||
setBodyOnlyIf(condition) {
|
||||
this.bodyOnlyIf = condition;
|
||||
}
|
||||
|
||||
walk(walker) {
|
||||
this.setTagName(walker.walk(this.tagNameExpression));
|
||||
this._attributes.walk(walker);
|
||||
this.body = walker.walk(this.body);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HtmlElement;
|
||||
23
compiler/ast/HtmlElement/html/EndTag.js
Normal file
23
compiler/ast/HtmlElement/html/EndTag.js
Normal file
@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
var Node = require('../../Node');
|
||||
|
||||
class EndTag extends Node {
|
||||
constructor(def) {
|
||||
super('EndTag');
|
||||
this.tagName = def.tagName;
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
var tagName = this.tagName;
|
||||
var builder = codegen.builder;
|
||||
|
||||
return [
|
||||
builder.htmlLiteral('</'),
|
||||
builder.html(tagName),
|
||||
builder.htmlLiteral('>')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EndTag;
|
||||
55
compiler/ast/HtmlElement/html/StartTag.js
Normal file
55
compiler/ast/HtmlElement/html/StartTag.js
Normal file
@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
var Node = require('../../Node');
|
||||
|
||||
class StartTag extends Node {
|
||||
constructor(def) {
|
||||
super('StartTag');
|
||||
|
||||
this.tagName = def.tagName;
|
||||
this.attributes = def.attributes;
|
||||
this.argument = def.argument;
|
||||
this.selfClosed = def.selfClosed;
|
||||
this.dynamicAttributes = def.dynamicAttributes;
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
var builder = codegen.builder;
|
||||
|
||||
var tagName = this.tagName;
|
||||
var selfClosed = this.selfClosed;
|
||||
var dynamicAttributes = this.dynamicAttributes;
|
||||
var context = codegen.context;
|
||||
|
||||
var nodes = [
|
||||
builder.htmlLiteral('<'),
|
||||
builder.html(tagName),
|
||||
];
|
||||
|
||||
var attributes = this.attributes;
|
||||
|
||||
if (attributes) {
|
||||
for (let i=0; i<attributes.length; i++) {
|
||||
let attr = attributes[i];
|
||||
nodes.push(codegen.generateCode(attr));
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicAttributes) {
|
||||
dynamicAttributes.forEach(function(attrsExpression) {
|
||||
let attrsFunctionCall = builder.functionCall(context.helper('attrs'), [attrsExpression]);
|
||||
nodes.push(builder.html(attrsFunctionCall));
|
||||
});
|
||||
}
|
||||
|
||||
if (selfClosed) {
|
||||
nodes.push(builder.htmlLiteral('/>'));
|
||||
} else {
|
||||
nodes.push(builder.htmlLiteral('>'));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StartTag;
|
||||
88
compiler/ast/HtmlElement/html/generateCode.js
Normal file
88
compiler/ast/HtmlElement/html/generateCode.js
Normal file
@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
var StartTag = require('./StartTag');
|
||||
var EndTag = require('./EndTag');
|
||||
|
||||
module.exports = function generateCode(node, codegen) {
|
||||
var tagName = node.tagName;
|
||||
|
||||
// Convert the tag name into a Node so that we generate the code correctly
|
||||
if (tagName) {
|
||||
tagName = codegen.builder.literal(tagName);
|
||||
} else {
|
||||
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;
|
||||
var hasBody = body && body.length;
|
||||
var openTagOnly = node.openTagOnly;
|
||||
var bodyOnlyIf = node.bodyOnlyIf;
|
||||
var dynamicAttributes = node.dynamicAttributes;
|
||||
var selfClosed = node.selfClosed === true;
|
||||
|
||||
var builder = codegen.builder;
|
||||
|
||||
if (hasBody) {
|
||||
body = codegen.generateCode(body);
|
||||
}
|
||||
|
||||
if (hasBody || bodyOnlyIf) {
|
||||
openTagOnly = false;
|
||||
selfClosed = false;
|
||||
} else if (selfClosed){
|
||||
openTagOnly = true;
|
||||
}
|
||||
|
||||
var startTag = new StartTag({
|
||||
tagName: tagName,
|
||||
attributes: attributes,
|
||||
argument: argument,
|
||||
selfClosed: selfClosed,
|
||||
dynamicAttributes: dynamicAttributes
|
||||
});
|
||||
|
||||
var endTag;
|
||||
|
||||
if (!openTagOnly) {
|
||||
endTag = new EndTag({
|
||||
tagName: tagName
|
||||
});
|
||||
}
|
||||
|
||||
if (bodyOnlyIf) {
|
||||
var startIf = builder.ifStatement(builder.negate(bodyOnlyIf), [
|
||||
startTag
|
||||
]);
|
||||
|
||||
var endIf = builder.ifStatement(builder.negate(bodyOnlyIf), [
|
||||
endTag
|
||||
]);
|
||||
|
||||
return [
|
||||
startIf,
|
||||
body,
|
||||
endIf
|
||||
];
|
||||
} else {
|
||||
if (openTagOnly) {
|
||||
return codegen.generateCode(startTag);
|
||||
} else {
|
||||
return [
|
||||
startTag,
|
||||
body,
|
||||
endTag
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
158
compiler/ast/HtmlElement/index.js
Normal file
158
compiler/ast/HtmlElement/index.js
Normal file
@ -0,0 +1,158 @@
|
||||
'use strict';
|
||||
|
||||
var Node = require('../Node');
|
||||
var Literal = require('../Literal');
|
||||
var HtmlAttributeCollection = require('../HtmlAttributeCollection');
|
||||
var generateHTMLCode = require('./html/generateCode');
|
||||
var generateVDOMCode = require('./vdom/generateCode');
|
||||
var vdomUtil = require('../../util/vdom');
|
||||
|
||||
function beforeGenerateCode(event) {
|
||||
if (event.node.tagName === 'script') {
|
||||
event.context.pushFlag('SCRIPT_BODY');
|
||||
}
|
||||
}
|
||||
|
||||
function afterGenerateCode(event) {
|
||||
if (event.node.tagName === 'script') {
|
||||
event.context.popFlag('SCRIPT_BODY');
|
||||
}
|
||||
}
|
||||
|
||||
class HtmlElement extends Node {
|
||||
constructor(def) {
|
||||
super('HtmlElement');
|
||||
this.tagName = null;
|
||||
this.tagNameExpression = null;
|
||||
this.setTagName(def.tagName);
|
||||
this._attributes = def.attributes;
|
||||
this.body = this.makeContainer(def.body);
|
||||
this.argument = def.argument;
|
||||
|
||||
if (!(this._attributes instanceof HtmlAttributeCollection)) {
|
||||
this._attributes = new HtmlAttributeCollection(this._attributes);
|
||||
}
|
||||
|
||||
this.openTagOnly = def.openTagOnly;
|
||||
this.selfClosed = def.selfClosed;
|
||||
this.dynamicAttributes = undefined;
|
||||
this.bodyOnlyIf = undefined;
|
||||
|
||||
this.on('beforeGenerateCode', beforeGenerateCode);
|
||||
this.on('afterGenerateCode', afterGenerateCode);
|
||||
}
|
||||
|
||||
generateHTMLCode(codegen) {
|
||||
return generateHTMLCode(this, codegen);
|
||||
}
|
||||
|
||||
generateVDOMCode(codegen) {
|
||||
return generateVDOMCode(this, codegen, vdomUtil);
|
||||
}
|
||||
|
||||
addDynamicAttributes(expression) {
|
||||
if (!this.dynamicAttributes) {
|
||||
this.dynamicAttributes = [];
|
||||
}
|
||||
|
||||
this.dynamicAttributes.push(expression);
|
||||
}
|
||||
|
||||
getAttribute(name) {
|
||||
return this._attributes != null && this._attributes.getAttribute(name);
|
||||
}
|
||||
|
||||
getAttributeValue(name) {
|
||||
var attr = this._attributes != null && this._attributes.getAttribute(name);
|
||||
if (attr) {
|
||||
return attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
addAttribute(attr) {
|
||||
this._attributes.addAttribute(attr);
|
||||
}
|
||||
|
||||
setAttributeValue(name, value) {
|
||||
this._attributes.setAttributeValue(name, value);
|
||||
}
|
||||
|
||||
replaceAttributes(newAttributes) {
|
||||
this._attributes.replaceAttributes(newAttributes);
|
||||
}
|
||||
|
||||
removeAttribute(name) {
|
||||
if (this._attributes) {
|
||||
this._attributes.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
removeAllAttributes() {
|
||||
this._attributes.removeAllAttributes();
|
||||
}
|
||||
|
||||
hasAttribute(name) {
|
||||
return this._attributes != null && this._attributes.hasAttribute(name);
|
||||
}
|
||||
|
||||
getAttributes() {
|
||||
return this._attributes.all;
|
||||
}
|
||||
|
||||
get attributes() {
|
||||
return this._attributes.all;
|
||||
}
|
||||
|
||||
forEachAttribute(callback, thisObj) {
|
||||
var attributes = this._attributes.all.concat([]);
|
||||
|
||||
for (let i=0, len=attributes.length; i<len; i++) {
|
||||
callback.call(thisObj, attributes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
setTagName(tagName) {
|
||||
this.tagName = null;
|
||||
this.tagNameExpression = null;
|
||||
|
||||
if (tagName instanceof Node) {
|
||||
if (tagName instanceof Literal) {
|
||||
this.tagName = tagName.value;
|
||||
this.tagNameExpression = tagName;
|
||||
} else {
|
||||
this.tagNameExpression = tagName;
|
||||
}
|
||||
} else if (typeof tagName === 'string') {
|
||||
this.tagNameExpression = new Literal({value: tagName});
|
||||
this.tagName = tagName;
|
||||
}
|
||||
}
|
||||
|
||||
isLiteralTagName() {
|
||||
return this.tagName != null;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: this.type,
|
||||
tagName: this.tagName,
|
||||
attributes: this._attributes,
|
||||
argument: this.argument,
|
||||
body: this.body,
|
||||
bodyOnlyIf: this.bodyOnlyIf,
|
||||
dynamicAttributes: this.dynamicAttributes
|
||||
};
|
||||
}
|
||||
|
||||
setBodyOnlyIf(condition) {
|
||||
this.bodyOnlyIf = condition;
|
||||
}
|
||||
|
||||
walk(walker) {
|
||||
this.setTagName(walker.walk(this.tagNameExpression));
|
||||
this._attributes.walk(walker);
|
||||
this.body = walker.walk(this.body);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HtmlElement;
|
||||
186
compiler/ast/HtmlElement/vdom/HtmlElementVDOM.js
Normal file
186
compiler/ast/HtmlElement/vdom/HtmlElementVDOM.js
Normal file
@ -0,0 +1,186 @@
|
||||
'use strict';
|
||||
|
||||
const Node = require('../../Node');
|
||||
const vdomUtil = require('../../../util/vdom');
|
||||
|
||||
function finalizeCreateArgs(createArgs, builder) {
|
||||
var length = createArgs.length;
|
||||
var lastArg;
|
||||
|
||||
for (var i=length-1; i>=0; i--) {
|
||||
var arg = createArgs[i];
|
||||
if (arg) {
|
||||
lastArg = arg;
|
||||
} else {
|
||||
if (lastArg != null) {
|
||||
createArgs[i] = builder.literalNull();
|
||||
} else {
|
||||
length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createArgs.length = length;
|
||||
return createArgs;
|
||||
}
|
||||
|
||||
class HtmlElementVDOM extends Node {
|
||||
constructor(def) {
|
||||
super('HtmlElementVDOM');
|
||||
this.tagName = def.tagName;
|
||||
this.isStatic = def.isStatic;
|
||||
this.isAttrsStatic = def.isAttrsStatic;
|
||||
this.isHtmlOnly = def.isHtmlOnly;
|
||||
this.attributes = def.attributes;
|
||||
this.body = def.body;
|
||||
this.dynamicAttributes = def.dynamicAttributes;
|
||||
|
||||
this.isChild = false;
|
||||
this.createElementId = undefined;
|
||||
this.attributesArg = undefined;
|
||||
this.nextConstId = undefined;
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
let context = codegen.context;
|
||||
let builder = codegen.builder;
|
||||
|
||||
// When there are any VDOM nodes in the AST then we need to optimize the intermediate AST
|
||||
// before the final AST is returned. We use the "afterTemplateRootBodyGenerated" event
|
||||
// to finalize the VDOM AST nodes.
|
||||
vdomUtil.attachEventListeners(context);
|
||||
|
||||
let attributes = this.attributes;
|
||||
let dynamicAttributes = this.dynamicAttributes;
|
||||
|
||||
let attributesArg = null;
|
||||
|
||||
if (attributes && attributes.length) {
|
||||
let addAttr = function(name, value) {
|
||||
if (!attributesArg) {
|
||||
attributesArg = {};
|
||||
}
|
||||
|
||||
if (value.type === 'Literal') {
|
||||
let literalValue = value.value;
|
||||
if (literalValue == null || literalValue === false) {
|
||||
return;
|
||||
} else if (typeof literalValue === 'number') {
|
||||
value.value = literalValue.toString();
|
||||
}
|
||||
}
|
||||
|
||||
attributesArg[name] = value;
|
||||
};
|
||||
|
||||
attributes.forEach((attr) => {
|
||||
let value = attr.value;
|
||||
|
||||
if (!attr.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
addAttr(attr.name, value);
|
||||
});
|
||||
|
||||
if (attributesArg) {
|
||||
attributesArg = builder.literal(attributesArg);
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicAttributes && dynamicAttributes.length) {
|
||||
dynamicAttributes.forEach((attrs) => {
|
||||
if (attributesArg) {
|
||||
let mergeVar = context.helper('merge');
|
||||
attributesArg = builder.functionCall(mergeVar, [
|
||||
attributesArg, // Input props from the attributes take precedence
|
||||
attrs
|
||||
]);
|
||||
} else {
|
||||
attributesArg = attrs;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.attributesArg = attributesArg;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
walk(walker) {
|
||||
this.tagName = walker.walk(this.tagName);
|
||||
this.attributes = walker.walk(this.attributes);
|
||||
this.body = walker.walk(this.body);
|
||||
}
|
||||
|
||||
writeCode(writer) {
|
||||
let builder = writer.builder;
|
||||
|
||||
let body = this.body;
|
||||
let attributesArg = this.attributesArg;
|
||||
let nextConstId = this.nextConstId;
|
||||
|
||||
let childCount = body && body.length;
|
||||
|
||||
let createArgs = new Array(4); // tagName, attributes, childCount, const ID
|
||||
|
||||
createArgs[0] = this.tagName;
|
||||
|
||||
if (attributesArg) {
|
||||
createArgs[1] = attributesArg;
|
||||
}
|
||||
|
||||
if (childCount != null) {
|
||||
createArgs[2] = builder.literal(childCount);
|
||||
}
|
||||
|
||||
if (nextConstId) {
|
||||
createArgs[3] = nextConstId;
|
||||
}
|
||||
|
||||
// Remove trailing undefined arguments and convert non-trailing
|
||||
// undefined elements to a literal null node
|
||||
createArgs = finalizeCreateArgs(createArgs, builder);
|
||||
|
||||
let funcCall;
|
||||
|
||||
if (this.isChild) {
|
||||
writer.write('.');
|
||||
|
||||
funcCall = builder.functionCall(
|
||||
builder.identifier('e'),
|
||||
createArgs);
|
||||
} else if (this.isStatic) {
|
||||
funcCall = builder.functionCall(
|
||||
this.createElementId,
|
||||
createArgs);
|
||||
} else if (this.isHtmlOnly) {
|
||||
writer.write('out.');
|
||||
funcCall = builder.functionCall(
|
||||
|
||||
builder.identifier('e'),
|
||||
createArgs);
|
||||
} else {
|
||||
writer.write('out.');
|
||||
funcCall = builder.functionCall(
|
||||
builder.identifier('be'),
|
||||
createArgs);
|
||||
}
|
||||
|
||||
writer.write(funcCall);
|
||||
|
||||
if (body && body.length) {
|
||||
writer.incIndent();
|
||||
for(let i=0; i<body.length; i++) {
|
||||
let child = body[i];
|
||||
child.isChild = true;
|
||||
writer.write('\n');
|
||||
writer.writeLineIndent();
|
||||
writer.write(child);
|
||||
}
|
||||
writer.decIndent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HtmlElementVDOM;
|
||||
55
compiler/ast/HtmlElement/vdom/generateCode.js
Normal file
55
compiler/ast/HtmlElement/vdom/generateCode.js
Normal file
@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
var HtmlElementVDOM = require('./HtmlElementVDOM');
|
||||
|
||||
function checkAttributesStatic(attributes) {
|
||||
if (attributes) {
|
||||
for (let i=0; i<attributes.length; i++) {
|
||||
let attr = attributes[i];
|
||||
|
||||
if (!attr.isStatic) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
module.exports = function(node, codegen, vdomUtil) {
|
||||
var body = codegen.generateCode(node.body);
|
||||
var tagName = codegen.generateCode(node.tagNameExpression);
|
||||
var attributes = codegen.generateCode(node.getAttributes());
|
||||
var dynamicAttributes = codegen.generateCode(node.dynamicAttributes);
|
||||
|
||||
var isAttrsStatic = checkAttributesStatic(attributes);
|
||||
var isStatic = isAttrsStatic && node.isLiteralTagName();
|
||||
var isHtmlOnly = true;
|
||||
|
||||
if (body && body.length) {
|
||||
for (var i=0; i<body.length; i++) {
|
||||
let child = body[i];
|
||||
if (child.type === 'HtmlElementVDOM' || child.type === 'TextVDOM') {
|
||||
if (!child.isHtmlOnly) {
|
||||
isStatic = false;
|
||||
isHtmlOnly = false;
|
||||
} if (!child.isStatic) {
|
||||
isStatic = false;
|
||||
}
|
||||
} else {
|
||||
isHtmlOnly = false;
|
||||
isStatic = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new HtmlElementVDOM({
|
||||
tagName,
|
||||
attributes,
|
||||
body,
|
||||
isStatic,
|
||||
isAttrsStatic,
|
||||
isHtmlOnly,
|
||||
dynamicAttributes
|
||||
});
|
||||
};
|
||||
@ -193,6 +193,7 @@ class Node {
|
||||
delete result._events;
|
||||
delete result._finalNode;
|
||||
delete result._trimStartEnd;
|
||||
delete result._childTextNormalized;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,18 @@ class TemplateRoot extends Node {
|
||||
|
||||
var body = codegen.generateCode(this.body);
|
||||
|
||||
var templateRootBodyEvent = {
|
||||
body,
|
||||
context
|
||||
};
|
||||
|
||||
// Emit an event to give code generators one more chance to optimize/finalize the AST
|
||||
// before the final AST is returned. This VDOM AST nodes use this event to otpimize
|
||||
// the AST by separating out static subtrees
|
||||
context.emit('afterTemplateRootBodyGenerated', templateRootBodyEvent);
|
||||
|
||||
body = templateRootBodyEvent.body;
|
||||
|
||||
var builder = codegen.builder;
|
||||
|
||||
let renderStatements = [];
|
||||
@ -48,9 +60,7 @@ class TemplateRoot extends Node {
|
||||
builder.identifierOut()
|
||||
],
|
||||
renderStatements)
|
||||
|
||||
]);
|
||||
|
||||
} else {
|
||||
let createStatements = [];
|
||||
let staticNodes = context.getStaticNodes();
|
||||
|
||||
@ -1,88 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var ok = require('assert').ok;
|
||||
var Node = require('./Node');
|
||||
var Literal = require('./Literal');
|
||||
var escapeXml = require('raptor-util/escapeXml');
|
||||
|
||||
class Text extends Node {
|
||||
constructor(def) {
|
||||
super('Text');
|
||||
this.argument = def.argument;
|
||||
this.escape = def.escape !== false;
|
||||
this.normalized = false;
|
||||
this.isFirst = false;
|
||||
this.isLast = false;
|
||||
this.preserveWhitespace = def.preserveWhitespace === true;
|
||||
|
||||
ok(this.argument, 'Invalid argument');
|
||||
}
|
||||
|
||||
isLiteral() {
|
||||
return this.argument instanceof Node && this.argument.type === 'Literal';
|
||||
}
|
||||
|
||||
generateHTMLCode(codegen) {
|
||||
var context = codegen.context;
|
||||
var argument = this.argument;
|
||||
var escape = this.escape !== false;
|
||||
|
||||
if (argument instanceof Literal) {
|
||||
if (!argument.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.isFlagSet('SCRIPT_BODY')) {
|
||||
escape = false;
|
||||
}
|
||||
|
||||
if (escape === true) {
|
||||
argument.value = escapeXml(argument.value.toString());
|
||||
}
|
||||
} else {
|
||||
let builder = codegen.builder;
|
||||
|
||||
if (escape) {
|
||||
let escapeIdentifier = context.helper('escapeXml');
|
||||
|
||||
if (context.isFlagSet('SCRIPT_BODY')) {
|
||||
escapeIdentifier = context.helper('escapeScript');
|
||||
}
|
||||
|
||||
// TODO Only escape the parts that need to be escaped if it is a compound expression with static
|
||||
// text parts
|
||||
argument = builder.functionCall(
|
||||
escapeIdentifier,
|
||||
[argument]);
|
||||
} else {
|
||||
argument = builder.functionCall(context.helper('str'), [ argument ]);
|
||||
}
|
||||
}
|
||||
|
||||
return codegen.builder.html(argument);
|
||||
}
|
||||
|
||||
isWhitespace() {
|
||||
var argument = this.argument;
|
||||
return (argument instanceof Literal) &&
|
||||
(typeof argument.value === 'string') &&
|
||||
(argument.value.trim() === '');
|
||||
}
|
||||
|
||||
appendText(text) {
|
||||
if (!this.isLiteral()) {
|
||||
throw new Error('Text cannot be appended to a non-literal Text node');
|
||||
}
|
||||
|
||||
this.argument.value += text;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: this.type,
|
||||
argument: this.argument
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Text;
|
||||
61
compiler/ast/Text/html/generateCode.js
Normal file
61
compiler/ast/Text/html/generateCode.js
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
var escapeXml = require('raptor-util/escapeXml');
|
||||
var Literal = require('../..//Literal');
|
||||
|
||||
module.exports = function(node, codegen) {
|
||||
var context = codegen.context;
|
||||
var argument = codegen.generateCode(node.argument);
|
||||
var escape = node.escape !== false;
|
||||
|
||||
var htmlArray = [];
|
||||
|
||||
function append(argument) {
|
||||
if (argument instanceof Literal) {
|
||||
if (!argument.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.isFlagSet('SCRIPT_BODY')) {
|
||||
escape = false;
|
||||
}
|
||||
|
||||
if (escape === true) {
|
||||
argument.value = escapeXml(argument.value.toString());
|
||||
}
|
||||
|
||||
htmlArray.push(argument);
|
||||
} else {
|
||||
let builder = codegen.builder;
|
||||
|
||||
if (escape) {
|
||||
let escapeIdentifier = context.helper('escapeXml');
|
||||
|
||||
if (context.isFlagSet('SCRIPT_BODY')) {
|
||||
escapeIdentifier = context.helper('escapeScript');
|
||||
}
|
||||
|
||||
// TODO Only escape the parts that need to be escaped if it is a compound expression with static
|
||||
// text parts
|
||||
argument = builder.functionCall(
|
||||
escapeIdentifier,
|
||||
[argument]);
|
||||
} else {
|
||||
argument = builder.functionCall(context.helper('str'), [ argument ]);
|
||||
}
|
||||
htmlArray.push(argument);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(argument)) {
|
||||
argument.forEach(append);
|
||||
} else {
|
||||
append(argument);
|
||||
}
|
||||
|
||||
if (htmlArray.length) {
|
||||
return codegen.builder.html(htmlArray);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
85
compiler/ast/Text/index.js
Normal file
85
compiler/ast/Text/index.js
Normal file
@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
var ok = require('assert').ok;
|
||||
var Node = require('../Node');
|
||||
var Literal = require('../Literal');
|
||||
|
||||
var generateHTMLCode = require('./html/generateCode');
|
||||
var generateVDOMCode = require('./vdom/generateCode');
|
||||
var vdomUtil = require('../../util/vdom');
|
||||
|
||||
class Text extends Node {
|
||||
constructor(def) {
|
||||
super('Text');
|
||||
this.argument = def.argument;
|
||||
this.escape = def.escape !== false;
|
||||
this.normalized = false;
|
||||
this.isFirst = false;
|
||||
this.isLast = false;
|
||||
this.preserveWhitespace = def.preserveWhitespace === true;
|
||||
|
||||
ok(this.argument, 'Invalid argument');
|
||||
}
|
||||
|
||||
generateHTMLCode(codegen) {
|
||||
return generateHTMLCode(this, codegen);
|
||||
}
|
||||
|
||||
generateVDOMCode(codegen) {
|
||||
return generateVDOMCode(this, codegen, vdomUtil);
|
||||
}
|
||||
|
||||
isLiteral() {
|
||||
return this.argument instanceof Node && this.argument.type === 'Literal';
|
||||
}
|
||||
|
||||
isWhitespace() {
|
||||
var argument = this.argument;
|
||||
return (argument instanceof Literal) &&
|
||||
(typeof argument.value === 'string') &&
|
||||
(argument.value.trim() === '');
|
||||
}
|
||||
|
||||
// _append(appendArgument) {
|
||||
// var argument = this.argument;
|
||||
//
|
||||
// if (Array.isArray(argument)) {
|
||||
// var len = argument.length;
|
||||
// var last = argument[len-1];
|
||||
//
|
||||
// if (last instanceof Literal && appendArgument instanceof Literal) {
|
||||
// last.value += appendArgument.value;
|
||||
// } else {
|
||||
// this.argument.push(appendArgument);
|
||||
// }
|
||||
// } else {
|
||||
// if (argument instanceof Literal && appendArgument instanceof Literal) {
|
||||
// argument.value += appendArgument.value;
|
||||
// } else {
|
||||
// this.argument = [ this.argument, appendArgument ];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// append(text) {
|
||||
// var appendArgument = text.argument;
|
||||
// if (!appendArgument) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (Array.isArray(appendArgument)) {
|
||||
// appendArgument.forEach(this._append, this);
|
||||
// } else {
|
||||
// this._append(appendArgument);
|
||||
// }
|
||||
// }
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: this.type,
|
||||
argument: this.argument
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Text;
|
||||
87
compiler/ast/Text/vdom/TextVDOM.js
Normal file
87
compiler/ast/Text/vdom/TextVDOM.js
Normal file
@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
const Node = require('../../Node');
|
||||
const Literal = require('../../Literal');
|
||||
const vdomUtil = require('../../../util/vdom');
|
||||
|
||||
class TextVDOM extends Node {
|
||||
constructor(def) {
|
||||
super('TextVDOM');
|
||||
this.arguments = [def.argument];
|
||||
this.isStatic = def.isStatic;
|
||||
this.isHtmlOnly = true;
|
||||
this.isChild = false;
|
||||
this.createTextId = undefined;
|
||||
}
|
||||
|
||||
generateCode(codegen) {
|
||||
var context = codegen.context;
|
||||
|
||||
// When there are any VDOM nodes in the AST then we need to optimize the intermediate AST
|
||||
// before the final AST is returned. We use the "afterTemplateRootBodyGenerated" event
|
||||
// to finalize the VDOM AST nodes.
|
||||
vdomUtil.attachEventListeners(context);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
_append(appendArgument) {
|
||||
let args = this.arguments;
|
||||
let len = args.length;
|
||||
let last = args[len-1];
|
||||
|
||||
if (last instanceof Literal && appendArgument instanceof Literal) {
|
||||
last.value += appendArgument.value;
|
||||
} else {
|
||||
args.push(appendArgument);
|
||||
}
|
||||
}
|
||||
|
||||
append(textVDOMToAppend) {
|
||||
if (!textVDOMToAppend.isStatic) {
|
||||
this.isStatic = false;
|
||||
}
|
||||
textVDOMToAppend.arguments.forEach(this._append, this);
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
let funcCall = builder.functionCall(
|
||||
builder.identifier('t'),
|
||||
[
|
||||
textArg
|
||||
]);
|
||||
|
||||
writer.write('out.');
|
||||
writer.write(funcCall);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextVDOM;
|
||||
16
compiler/ast/Text/vdom/generateCode.js
Normal file
16
compiler/ast/Text/vdom/generateCode.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
var TextVDOM = require('./TextVDOM');
|
||||
var Literal = require('../../Literal');
|
||||
|
||||
module.exports = function(node, codegen, vdomUtil) {
|
||||
var argument = codegen.generateCode(node.argument);
|
||||
|
||||
if (argument instanceof Literal && argument.value === '') {
|
||||
// Don't add empty text nodes to the final tree
|
||||
return null;
|
||||
}
|
||||
|
||||
var isStatic = vdomUtil.isStaticValue(argument);
|
||||
return new TextVDOM({ argument, isStatic });
|
||||
};
|
||||
@ -133,7 +133,6 @@ function createInlineCompiler(filename, userOptions) {
|
||||
|
||||
var compiler = defaultCompiler;
|
||||
var context = new CompileContext('', filename, compiler.builder, options);
|
||||
|
||||
return new InlineCompiler(context, compiler);
|
||||
}
|
||||
|
||||
|
||||
7
compiler/util/fingerprint.js
Normal file
7
compiler/util/fingerprint.js
Normal file
@ -0,0 +1,7 @@
|
||||
var crypto = require('crypto');
|
||||
|
||||
module.exports = function(str) {
|
||||
var shasum = crypto.createHash('sha1');
|
||||
shasum.update(str);
|
||||
return shasum.digest('hex');
|
||||
};
|
||||
161
compiler/util/vdom/finalizeVDOMNodes.js
Normal file
161
compiler/util/vdom/finalizeVDOMNodes.js
Normal file
@ -0,0 +1,161 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
Algorithm:
|
||||
|
||||
Walk the DOM tree to find all HtmlElementVDOM and TextVDOM nodes
|
||||
|
||||
a) If a node is static then move to a static variable. Depending on whether or not the node is a root or nested,
|
||||
we will need to replace it with one of the following:
|
||||
- out.n(staticVar)
|
||||
- .n(staticVar)
|
||||
|
||||
b) If a node is HTML-only then generate code depending on if it is root or not:
|
||||
|
||||
- out.e('div', ...) | out.t('foo')
|
||||
- .e('div', ...) || .t('foo')
|
||||
|
||||
c) Else, generate one of the following:
|
||||
|
||||
- out.beginElement()
|
||||
|
||||
*/
|
||||
|
||||
const Node = require('../../ast/Node');
|
||||
const nextConstIdFuncSymbol = Symbol();
|
||||
|
||||
class NodeVDOM extends Node {
|
||||
constructor(variableIdentifier) {
|
||||
super('NodeVDOM');
|
||||
this.variableIdentifier = variableIdentifier;
|
||||
}
|
||||
|
||||
writeCode(writer) {
|
||||
var builder = writer.builder;
|
||||
|
||||
let funcCall = builder.functionCall(
|
||||
builder.identifier('n'),
|
||||
[
|
||||
this.variableIdentifier
|
||||
]);
|
||||
|
||||
if (this.isChild) {
|
||||
writer.write('.');
|
||||
} else {
|
||||
writer.write('out.');
|
||||
}
|
||||
|
||||
writer.write(funcCall);
|
||||
}
|
||||
}
|
||||
|
||||
class EndElementNode extends Node {
|
||||
constructor() {
|
||||
super('EndElementNode');
|
||||
}
|
||||
|
||||
writeCode(writer) {
|
||||
writer.write('out.ee()');
|
||||
}
|
||||
}
|
||||
|
||||
function finalizeVDOMNodes(nodes, context) {
|
||||
let builder = context.builder;
|
||||
let nextNodeId = 0;
|
||||
let nextAttrsId = 0;
|
||||
function generateStaticNode(node) {
|
||||
if (node.type === 'HtmlElementVDOM') {
|
||||
node.createElementId = context.importModule('marko_createElement', 'marko/vdom/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 fingerprintLiteral = builder.literal(context.getFingerprint(6));
|
||||
nextConstIdFunc = context.data[nextConstIdFuncSymbol] = context.addStaticVar('marko_const_nextId', builder.functionCall(constId, [ fingerprintLiteral ]));
|
||||
}
|
||||
|
||||
node.nextConstId = builder.functionCall(nextConstIdFunc, []);
|
||||
|
||||
node.isStaticRoot = true;
|
||||
let staticNodeId = context.addStaticVar('marko_node' + (nextNodeId++), node);
|
||||
|
||||
return new NodeVDOM(staticNodeId);
|
||||
}
|
||||
|
||||
function handleStaticAttributes(node) {
|
||||
var attributesArg = node.attributesArg;
|
||||
if (attributesArg) {
|
||||
node.isStaticRoot = true;
|
||||
let staticAttrsId = context.addStaticVar('marko_attrs' + (nextAttrsId++), attributesArg);
|
||||
node.attributesArg = staticAttrsId;
|
||||
}
|
||||
}
|
||||
|
||||
function generateNodesForArray(nodes) {
|
||||
let finalNodes = [];
|
||||
let i = 0;
|
||||
|
||||
while (i<nodes.length) {
|
||||
let node = nodes[i];
|
||||
if (node.type === 'HtmlElementVDOM') {
|
||||
if (node.isStatic) {
|
||||
finalNodes.push(generateStaticNode(node));
|
||||
} else {
|
||||
if (node.isAttrsStatic) {
|
||||
handleStaticAttributes(node);
|
||||
}
|
||||
|
||||
if (node.isHtmlOnly) {
|
||||
finalNodes.push(node);
|
||||
} else {
|
||||
finalNodes.push(node);
|
||||
finalNodes = finalNodes.concat(generateNodesForArray(node.body));
|
||||
node.body = null;
|
||||
finalNodes.push(new EndElementNode());
|
||||
}
|
||||
}
|
||||
} else if (node.type === 'TextVDOM') {
|
||||
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);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if (firstTextNode.isStatic) {
|
||||
// finalNodes.push(generateStaticNode(firstTextNode));
|
||||
// continue;
|
||||
// } else {
|
||||
// finalNodes.push(firstTextNode);
|
||||
// }
|
||||
firstTextNode.isStatic = false;
|
||||
finalNodes.push(firstTextNode);
|
||||
|
||||
continue;
|
||||
} else {
|
||||
finalNodes.push(node);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return finalNodes;
|
||||
}
|
||||
|
||||
let walker = context.createWalker({
|
||||
enterArray(nodes) {
|
||||
return generateNodesForArray(nodes);
|
||||
}
|
||||
});
|
||||
|
||||
return walker.walk(nodes);
|
||||
}
|
||||
|
||||
module.exports = finalizeVDOMNodes;
|
||||
18
compiler/util/vdom/index.js
Normal file
18
compiler/util/vdom/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
var finalizeVDOMNodes = require('./finalizeVDOMNodes');
|
||||
var isStaticValue = require('./isStaticValue');
|
||||
var vdomEventListenersAttached = Symbol();
|
||||
|
||||
function attachEventListeners(context) {
|
||||
var data = context.data;
|
||||
if (!data[vdomEventListenersAttached]) {
|
||||
data[vdomEventListenersAttached] = true;
|
||||
|
||||
context.on('afterTemplateRootBodyGenerated', function(event) {
|
||||
event.body = finalizeVDOMNodes(event.body, context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.finalizeVDOMNodes = finalizeVDOMNodes;
|
||||
exports.isStaticValue = isStaticValue;
|
||||
exports.attachEventListeners = attachEventListeners;
|
||||
50
compiler/util/vdom/isStaticValue.js
Normal file
50
compiler/util/vdom/isStaticValue.js
Normal file
@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
var Literal = require('../../ast/Literal');
|
||||
var Node = require('../../ast/Node');
|
||||
|
||||
function isStaticArray(array) {
|
||||
for (let i=0; i<array.length; i++) {
|
||||
if (!isStaticValue(array[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isStaticObject(object) {
|
||||
for (var k in object) {
|
||||
if (object.hasOwnProperty(k)) {
|
||||
let v = object[k];
|
||||
if (!isStaticValue(v)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isStaticValue(value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value instanceof Node) {
|
||||
if (value instanceof Literal) {
|
||||
return isStaticValue(value.value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (typeof value === 'object') {
|
||||
if (Array.isArray(value)) {
|
||||
return isStaticArray(value);
|
||||
} else {
|
||||
return isStaticObject(value);
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = isStaticValue;
|
||||
@ -36,6 +36,7 @@
|
||||
"events": "^1.0.2",
|
||||
"htmljs-parser": "^1.5.3",
|
||||
"lasso-package-root": "^1.0.0",
|
||||
"marko-vdom": "^0.3.0",
|
||||
"minimatch": "^3.0.2",
|
||||
"object-assign": "^4.1.0",
|
||||
"property-handlers": "^1.0.0",
|
||||
|
||||
@ -109,7 +109,7 @@ Template.prototype = {
|
||||
|
||||
if (localData.$global) {
|
||||
out.global = extend(out.global, localData.$global);
|
||||
delete localData.$global;
|
||||
localData.$global = null;
|
||||
}
|
||||
|
||||
this._(localData, out);
|
||||
@ -145,7 +145,7 @@ Template.prototype = {
|
||||
if ((globalData = data.$global)) {
|
||||
// We will *move* the "$global" property
|
||||
// into the "out.global" object
|
||||
delete data.$global;
|
||||
data.$global = null;
|
||||
}
|
||||
} else {
|
||||
finalData = {};
|
||||
|
||||
6
runtime/vdom/const.js
Normal file
6
runtime/vdom/const.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = function(id) {
|
||||
var i=0;
|
||||
return function() {
|
||||
return id + (i++);
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
function create(__markoHelpers) {
|
||||
return function render(data, out) {
|
||||
out.e("div", {
|
||||
foo: "bar",
|
||||
hello: "world"
|
||||
}, 1)
|
||||
.t(("Hello " + name) + "!");
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
@ -0,0 +1,3 @@
|
||||
<div ${ { foo: 'bar', hello: 'world' } }>
|
||||
Hello ${name}!
|
||||
</div>
|
||||
13
test/autotests/vdom-compiler/attrs-dynamic/expected.js
Normal file
13
test/autotests/vdom-compiler/attrs-dynamic/expected.js
Normal file
@ -0,0 +1,13 @@
|
||||
function create(__markoHelpers) {
|
||||
return function render(data, out) {
|
||||
var attrs = {
|
||||
foo: "bar",
|
||||
hello: "world"
|
||||
};
|
||||
|
||||
out.e("div", attrs, 1)
|
||||
.t(("Hello " + name) + "!");
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
@ -0,0 +1,5 @@
|
||||
var attrs={ foo: 'bar', hello: 'world' }
|
||||
|
||||
<div ${attrs}>
|
||||
Hello ${name}!
|
||||
</div>
|
||||
12
test/autotests/vdom-compiler/dynamic-body-text/expected.js
Normal file
12
test/autotests/vdom-compiler/dynamic-body-text/expected.js
Normal file
@ -0,0 +1,12 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_attrs0 = {
|
||||
"class": "foo"
|
||||
};
|
||||
|
||||
return function render(data, out) {
|
||||
out.e("div", marko_attrs0, 1)
|
||||
.t(("Hello " + name) + "!");
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
@ -0,0 +1,3 @@
|
||||
<div.foo>
|
||||
Hello ${name}!
|
||||
</div>
|
||||
28
test/autotests/vdom-compiler/simple/expected.js
Normal file
28
test/autotests/vdom-compiler/simple/expected.js
Normal file
@ -0,0 +1,28 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_forEach = __markoHelpers.f,
|
||||
marko_createElement = require("marko/vdom/createElement"),
|
||||
marko_const = require("marko/runtime/vdom/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) + "!");
|
||||
|
||||
if (data.colors.length) {
|
||||
out.be("ul");
|
||||
|
||||
marko_forEach(data.colors, function(color) {
|
||||
out.e("li", null, 1)
|
||||
.t(color);
|
||||
});
|
||||
|
||||
out.ee();
|
||||
} else {
|
||||
out.n(marko_node0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
12
test/autotests/vdom-compiler/simple/template.marko
Normal file
12
test/autotests/vdom-compiler/simple/template.marko
Normal file
@ -0,0 +1,12 @@
|
||||
<h1>
|
||||
Hello ${data.name}!
|
||||
</h1>
|
||||
|
||||
<ul if(data.colors.length)>
|
||||
<li for(color in data.colors)>
|
||||
${color}
|
||||
</li>
|
||||
</ul>
|
||||
<div else>
|
||||
No colors!
|
||||
</div>
|
||||
@ -0,0 +1,19 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_createElement = require("marko/vdom/createElement"),
|
||||
marko_const = require("marko/runtime/vdom/const"),
|
||||
marko_const_nextId = marko_const("69a896"),
|
||||
marko_node0 = marko_createElement("div", {
|
||||
"class": "hello",
|
||||
onclick: "onClick()"
|
||||
}, 1, marko_const_nextId())
|
||||
.t("Welcome!");
|
||||
|
||||
return function render(data, out) {
|
||||
out.e("span", null, 2)
|
||||
.e("h1", null, 1)
|
||||
.t(("Hello " + data.name) + "!")
|
||||
.n(marko_node0);
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
@ -0,0 +1,6 @@
|
||||
<span>
|
||||
<h1>Hello ${data.name}!</h1>
|
||||
<div class="hello" onclick="onClick()">
|
||||
Welcome!
|
||||
</div>
|
||||
</span>
|
||||
16
test/autotests/vdom-compiler/static-element-root/expected.js
Normal file
16
test/autotests/vdom-compiler/static-element-root/expected.js
Normal file
@ -0,0 +1,16 @@
|
||||
function create(__markoHelpers) {
|
||||
var marko_createElement = require("marko/vdom/createElement"),
|
||||
marko_const = require("marko/runtime/vdom/const"),
|
||||
marko_const_nextId = marko_const("0524f9"),
|
||||
marko_node0 = marko_createElement("div", {
|
||||
"class": "hello",
|
||||
onclick: "onClick()"
|
||||
}, 1, marko_const_nextId())
|
||||
.t("Hello World!");
|
||||
|
||||
return function render(data, out) {
|
||||
out.n(marko_node0);
|
||||
};
|
||||
}
|
||||
|
||||
(module.exports = require("marko").c(__filename)).c(create);
|
||||
@ -0,0 +1,3 @@
|
||||
<div class="hello" onclick="onClick()">
|
||||
Hello World!
|
||||
</div>
|
||||
@ -19,7 +19,7 @@ function createCodeGenerator(context) {
|
||||
}
|
||||
|
||||
function createCodeWriter(context) {
|
||||
return new CodeWriter(context);
|
||||
return new CodeWriter(context, builder);
|
||||
}
|
||||
|
||||
describe('compiler/codegen', function() {
|
||||
|
||||
50
test/vdom-compiler-test.js
Normal file
50
test/vdom-compiler-test.js
Normal file
@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
require('./patch-module');
|
||||
|
||||
var chai = require('chai');
|
||||
chai.config.includeStack = true;
|
||||
var path = require('path');
|
||||
var compiler = require('../compiler');
|
||||
var autotest = require('./autotest');
|
||||
var fs = require('fs');
|
||||
|
||||
require('marko/node-require').install();
|
||||
|
||||
describe('vdom-compiler', function() {
|
||||
var autoTestDir = path.join(__dirname, 'autotests/vdom-compiler');
|
||||
|
||||
autotest.scanDir(autoTestDir, function run(dir, helpers, done) {
|
||||
var templatePath = path.join(dir, 'template.marko');
|
||||
var mainPath = path.join(dir, 'test.js');
|
||||
var main;
|
||||
|
||||
if (fs.existsSync(mainPath)) {
|
||||
main = require(mainPath);
|
||||
}
|
||||
|
||||
var compilerOptions = { output: 'vdom' };
|
||||
|
||||
if (main && main.checkError) {
|
||||
var e;
|
||||
|
||||
try {
|
||||
compiler.compileFile(templatePath, compilerOptions);
|
||||
} catch(_e) {
|
||||
e = _e;
|
||||
}
|
||||
|
||||
if (!e) {
|
||||
throw new Error('Error expected');
|
||||
}
|
||||
|
||||
main.checkError(e);
|
||||
done();
|
||||
|
||||
} else {
|
||||
var compiledSrc = compiler.compileFile(templatePath, compilerOptions);
|
||||
helpers.compare(compiledSrc, '.js');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user