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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@ -61,6 +61,10 @@ function makeNode(arg) {
|
|||||||
return arg;
|
return arg;
|
||||||
} else if (arg == null) {
|
} else if (arg == null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
} else if (Array.isArray(arg)) {
|
||||||
|
return arg.map((arg) => {
|
||||||
|
return makeNode(arg);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Argument should be a string or Node or null. Actual: ' + arg);
|
throw new Error('Argument should be a string or Node or null. Actual: ' + arg);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,9 +39,11 @@ class FinalNodes {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node instanceof Html && this.lastNode instanceof Html) {
|
if (node instanceof Html) {
|
||||||
this.lastNode.append(node);
|
if (this.lastNode instanceof Html) {
|
||||||
return;
|
this.lastNode.append(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.setFinalNode) {
|
if (node.setFinalNode) {
|
||||||
@ -185,6 +187,7 @@ class CodeGenerator {
|
|||||||
node.setCodeGenerator(null);
|
node.setCodeGenerator(null);
|
||||||
|
|
||||||
generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, false);
|
generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, false);
|
||||||
|
|
||||||
if (generatedCode != null && generatedCode !== node) {
|
if (generatedCode != null && generatedCode !== node) {
|
||||||
node = null;
|
node = null;
|
||||||
this._generateCode(generatedCode, finalNodes);
|
this._generateCode(generatedCode, finalNodes);
|
||||||
|
|||||||
@ -9,8 +9,10 @@ const Container = require('./ast/Container');
|
|||||||
const isValidJavaScriptVarName = require('./util/isValidJavaScriptVarName');
|
const isValidJavaScriptVarName = require('./util/isValidJavaScriptVarName');
|
||||||
|
|
||||||
class CodeWriter {
|
class CodeWriter {
|
||||||
constructor(options) {
|
constructor(options, builder) {
|
||||||
|
ok(builder, '"builder" is required');
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
this.builder = builder;
|
||||||
this.root = null;
|
this.root = null;
|
||||||
this._indentStr = options.indent != null ? options.indent : ' ';
|
this._indentStr = options.indent != null ? options.indent : ' ';
|
||||||
this._indentSize = this._indentStr.length;
|
this._indentSize = this._indentStr.length;
|
||||||
|
|||||||
@ -13,6 +13,8 @@ var Node = require('./ast/Node');
|
|||||||
var macros = require('./util/macros');
|
var macros = require('./util/macros');
|
||||||
var extend = require('raptor-util/extend');
|
var extend = require('raptor-util/extend');
|
||||||
var Walker = require('./Walker');
|
var Walker = require('./Walker');
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
var utilFingerprint = require('./util/fingerprint');
|
||||||
|
|
||||||
const FLAG_PRESERVE_WHITESPACE = 'PRESERVE_WHITESPACE';
|
const FLAG_PRESERVE_WHITESPACE = 'PRESERVE_WHITESPACE';
|
||||||
|
|
||||||
@ -66,8 +68,9 @@ const helpers = {
|
|||||||
'loadTemplate': 'l'
|
'loadTemplate': 'l'
|
||||||
};
|
};
|
||||||
|
|
||||||
class CompileContext {
|
class CompileContext extends EventEmitter {
|
||||||
constructor(src, filename, builder, options) {
|
constructor(src, filename, builder, options) {
|
||||||
|
super();
|
||||||
ok(typeof src === 'string', '"src" string is required');
|
ok(typeof src === 'string', '"src" string is required');
|
||||||
ok(filename, '"filename" is required');
|
ok(filename, '"filename" is required');
|
||||||
|
|
||||||
@ -103,6 +106,8 @@ class CompileContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._helpers = {};
|
this._helpers = {};
|
||||||
|
this._imports = {};
|
||||||
|
this._fingerprint = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
setInline(isInline) {
|
setInline(isInline) {
|
||||||
@ -184,9 +189,16 @@ class CompileContext {
|
|||||||
throw new Error('"path" should be a string');
|
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) {
|
addVar(name, init) {
|
||||||
var actualVarName = this._uniqueVars.addVar(name, init);
|
var actualVarName = this._uniqueVars.addVar(name, init);
|
||||||
@ -527,6 +539,19 @@ class CompileContext {
|
|||||||
|
|
||||||
return helperIdentifier;
|
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 = {
|
CompileContext.prototype.util = {
|
||||||
|
|||||||
@ -87,7 +87,7 @@ class CompiledTemplate {
|
|||||||
handleErrors(this.context);
|
handleErrors(this.context);
|
||||||
|
|
||||||
// console.log(module.id, 'FINAL AST:' + JSON.stringify(finalAST, null, 4));
|
// 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);
|
codeWriter.write(this.ast);
|
||||||
|
|
||||||
handleErrors(this.context);
|
handleErrors(this.context);
|
||||||
|
|||||||
@ -58,6 +58,7 @@ class InlineCompiler {
|
|||||||
constructor(context, compiler) {
|
constructor(context, compiler) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.compiler = compiler;
|
this.compiler = compiler;
|
||||||
|
this.builder = context.builder;
|
||||||
|
|
||||||
context.setInline(true);
|
context.setInline(true);
|
||||||
}
|
}
|
||||||
@ -75,7 +76,7 @@ class InlineCompiler {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let codeWriter = new CodeWriter(this.context.options);
|
let codeWriter = new CodeWriter(this.context.options, this.builder);
|
||||||
codeWriter.write(staticNodes);
|
codeWriter.write(staticNodes);
|
||||||
return codeWriter.getCode();
|
return codeWriter.getCode();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,7 +114,7 @@ class Parser {
|
|||||||
var builder = this.context.builder;
|
var builder = this.context.builder;
|
||||||
|
|
||||||
if (this.prevTextNode && this.prevTextNode.isLiteral()) {
|
if (this.prevTextNode && this.prevTextNode.isLiteral()) {
|
||||||
this.prevTextNode.appendText(text);
|
this.prevTextNode.argument.value += text;
|
||||||
} else {
|
} else {
|
||||||
var escape = false;
|
var escape = false;
|
||||||
this.prevTextNode = builder.text(builder.literal(text), escape);
|
this.prevTextNode = builder.text(builder.literal(text), escape);
|
||||||
|
|||||||
@ -8,6 +8,8 @@ class Walker {
|
|||||||
constructor(options) {
|
constructor(options) {
|
||||||
this._enter = options.enter || noop;
|
this._enter = options.enter || noop;
|
||||||
this._exit = options.exit || noop;
|
this._exit = options.exit || noop;
|
||||||
|
this._enterArray = options.enterArray || noop;
|
||||||
|
this._exitArray = options.exitArray || noop;
|
||||||
this._stopped = false;
|
this._stopped = false;
|
||||||
this._reset();
|
this._reset();
|
||||||
this._stack = [];
|
this._stack = [];
|
||||||
@ -38,6 +40,8 @@ class Walker {
|
|||||||
_walkArray(array) {
|
_walkArray(array) {
|
||||||
var hasRemoval = false;
|
var hasRemoval = false;
|
||||||
|
|
||||||
|
array = this._enterArray(array) || array;
|
||||||
|
|
||||||
array.forEach((node, i) => {
|
array.forEach((node, i) => {
|
||||||
var transformed = this.walk(node);
|
var transformed = this.walk(node);
|
||||||
if (transformed == null) {
|
if (transformed == null) {
|
||||||
@ -56,6 +60,8 @@ class Walker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
array = this._exitArray(array) || array;
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class Html extends Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateCode() {
|
generateHTMLCode() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
'use strict';
|
'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 attr = require('raptor-util/attr');
|
||||||
var compiler = require('../');
|
var escapeXmlAttr = require('raptor-util/escapeXml').attr;
|
||||||
|
|
||||||
function isStringLiteral(node) {
|
function isStringLiteral(node) {
|
||||||
return node.type === 'Literal' && typeof node.value === 'string';
|
return node.type === 'Literal' && typeof node.value === 'string';
|
||||||
@ -112,98 +109,29 @@ function generateCodeForExpressionAttr(name, value, escape, codegen) {
|
|||||||
return finalNodes;
|
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) {
|
if (!name) {
|
||||||
event.codegen.isInAttribute = true;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isLiteralValue() {
|
if (node.isLiteralValue()) {
|
||||||
return this.value instanceof Literal;
|
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._events;
|
||||||
delete result._finalNode;
|
delete result._finalNode;
|
||||||
delete result._trimStartEnd;
|
delete result._trimStartEnd;
|
||||||
|
delete result._childTextNormalized;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,18 @@ class TemplateRoot extends Node {
|
|||||||
|
|
||||||
var body = codegen.generateCode(this.body);
|
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;
|
var builder = codegen.builder;
|
||||||
|
|
||||||
let renderStatements = [];
|
let renderStatements = [];
|
||||||
@ -48,9 +60,7 @@ class TemplateRoot extends Node {
|
|||||||
builder.identifierOut()
|
builder.identifierOut()
|
||||||
],
|
],
|
||||||
renderStatements)
|
renderStatements)
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
let createStatements = [];
|
let createStatements = [];
|
||||||
let staticNodes = context.getStaticNodes();
|
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 compiler = defaultCompiler;
|
||||||
var context = new CompileContext('', filename, compiler.builder, options);
|
var context = new CompileContext('', filename, compiler.builder, options);
|
||||||
|
|
||||||
return new InlineCompiler(context, compiler);
|
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",
|
"events": "^1.0.2",
|
||||||
"htmljs-parser": "^1.5.3",
|
"htmljs-parser": "^1.5.3",
|
||||||
"lasso-package-root": "^1.0.0",
|
"lasso-package-root": "^1.0.0",
|
||||||
|
"marko-vdom": "^0.3.0",
|
||||||
"minimatch": "^3.0.2",
|
"minimatch": "^3.0.2",
|
||||||
"object-assign": "^4.1.0",
|
"object-assign": "^4.1.0",
|
||||||
"property-handlers": "^1.0.0",
|
"property-handlers": "^1.0.0",
|
||||||
|
|||||||
@ -109,7 +109,7 @@ Template.prototype = {
|
|||||||
|
|
||||||
if (localData.$global) {
|
if (localData.$global) {
|
||||||
out.global = extend(out.global, localData.$global);
|
out.global = extend(out.global, localData.$global);
|
||||||
delete localData.$global;
|
localData.$global = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._(localData, out);
|
this._(localData, out);
|
||||||
@ -145,7 +145,7 @@ Template.prototype = {
|
|||||||
if ((globalData = data.$global)) {
|
if ((globalData = data.$global)) {
|
||||||
// We will *move* the "$global" property
|
// We will *move* the "$global" property
|
||||||
// into the "out.global" object
|
// into the "out.global" object
|
||||||
delete data.$global;
|
data.$global = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
finalData = {};
|
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) {
|
function createCodeWriter(context) {
|
||||||
return new CodeWriter(context);
|
return new CodeWriter(context, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('compiler/codegen', function() {
|
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