Fixes #349 - Inline Marko template compilation support

Also changed how JavaScript code is generated
This commit is contained in:
Patrick Steele-Idem 2016-08-15 14:54:13 -06:00
parent 78a73e573e
commit c386da875e
164 changed files with 1803 additions and 1380 deletions

View File

@ -23,7 +23,6 @@ var Text = require('./ast/Text');
var ForEach = require('./ast/ForEach'); var ForEach = require('./ast/ForEach');
var ForEachProp = require('./ast/ForEachProp'); var ForEachProp = require('./ast/ForEachProp');
var ForRange = require('./ast/ForRange'); var ForRange = require('./ast/ForRange');
var Slot = require('./ast/Slot');
var HtmlComment = require('./ast/HtmlComment'); var HtmlComment = require('./ast/HtmlComment');
var SelfInvokingFunction = require('./ast/SelfInvokingFunction'); var SelfInvokingFunction = require('./ast/SelfInvokingFunction');
var ForStatement = require('./ast/ForStatement'); var ForStatement = require('./ast/ForStatement');
@ -72,6 +71,7 @@ var literalUndefined = new Literal({value: undefined});
var literalTrue = new Literal({value: true}); var literalTrue = new Literal({value: true});
var literalFalse = new Literal({value: false}); var literalFalse = new Literal({value: false});
var identifierOut = new Identifier({name: 'out'}); var identifierOut = new Identifier({name: 'out'});
var identifierRequire = new Identifier({name: 'require'});
class Builder { class Builder {
arrayExpression(elements) { arrayExpression(elements) {
@ -266,6 +266,11 @@ class Builder {
} }
} }
htmlLiteral(htmlCode) {
var argument = new Literal({value: htmlCode});
return new Html({argument});
}
identifier(name) { identifier(name) {
ok(typeof name === 'string', '"name" should be a string'); ok(typeof name === 'string', '"name" should be a string');
@ -425,7 +430,7 @@ class Builder {
require(path) { require(path) {
path = makeNode(path); path = makeNode(path);
let callee = 'require'; let callee = identifierRequire;
let args = [ path ]; let args = [ path ];
return new FunctionCall({callee, args}); return new FunctionCall({callee, args});
} }
@ -462,10 +467,6 @@ class Builder {
return new SelfInvokingFunction({params, args, body}); return new SelfInvokingFunction({params, args, body});
} }
slot(onDone) {
return new Slot({onDone});
}
strictEquality(left, right) { strictEquality(left, right) {
left = makeNode(left); left = makeNode(left);
right = makeNode(right); right = makeNode(right);

View File

@ -5,10 +5,9 @@ const Node = require('./ast/Node');
const Literal = require('./ast/Literal'); const Literal = require('./ast/Literal');
const Identifier = require('./ast/Identifier'); const Identifier = require('./ast/Identifier');
const HtmlElement = require('./ast/HtmlElement'); const HtmlElement = require('./ast/HtmlElement');
const Html = require('./ast/Html');
const ok = require('assert').ok; const ok = require('assert').ok;
const Container = require('./ast/Container'); const Container = require('./ast/Container');
const util = require('util');
const isValidJavaScriptVarName = require('./util/isValidJavaScriptVarName');
const createError = require('raptor-util/createError'); const createError = require('raptor-util/createError');
class GeneratorEvent { class GeneratorEvent {
@ -19,93 +18,45 @@ class GeneratorEvent {
this.isBefore = true; this.isBefore = true;
this.builder = codegen.builder; this.builder = codegen.builder;
this.context = codegen.context; this.context = codegen.context;
this.insertedNodes = null;
} }
insertCode(newCode) { insertCode(newCode) {
this.codegen.generateStatements(newCode); this.insertedNodes = newCode;
if (this.isBefore) {
if (!this.codegen._code.endsWith(this.codegen.currentIndent)) {
this.codegen.writeLineIndent();
}
}
} }
} }
class Slot { class FinalNodes {
constructor(codegen, slotNode) { constructor() {
this._content = null; this.nodes = [];
this.nodes._finalNode = true; // Mark the array as a collection of final nodes
this._start = codegen._code.length; this.lastNode = null;
codegen.write('/* slot */');
if (slotNode.statement) {
codegen.write('\n');
}
this._end = codegen._code.length;
this.currentIndent = codegen.currentIndent;
this._inFunction = codegen.inFunction;
this._statement = slotNode.statement;
} }
setContent(content) { push(node) {
this._content = content; if (!node) {
} return;
generateCode(codegen) {
let content = this._content;
let slotCode;
if (content) {
let isStatement = this._statement;
codegen.currentIndent = this.currentIndent;
codegen.inFunction = this._inFunction;
let capture = codegen._beginCaptureCode();
if (isStatement) {
codegen.generateStatements(content);
} else {
codegen.generateCode(content);
}
slotCode = capture.end();
if (isStatement && slotCode.startsWith(codegen.currentIndent)) {
slotCode = slotCode.substring(codegen.currentIndent.length);
}
} }
if (node instanceof Html && this.lastNode instanceof Html) {
this.lastNode.append(node);
let oldCode = codegen._code; return;
let beforeCode = oldCode.substring(0, this._start);
let afterCode = oldCode.substring(this._end);
if (slotCode) {
codegen._code = beforeCode + slotCode + afterCode;
} else {
let beforeWhitespaceMatches = beforeCode.match(/[\n]\s*$/);
if (beforeWhitespaceMatches != null) {
let beforeWhitespace = beforeWhitespaceMatches[0];
if (afterCode.startsWith(beforeWhitespace)) {
afterCode = afterCode.substring(beforeWhitespace.length);
}
}
codegen._code = beforeCode + afterCode;
} }
if (node.setFinalNode) {
node.setFinalNode(true);
}
this.lastNode = node;
this.nodes.push(node);
} }
} }
class Generator { class CodeGenerator {
constructor(context, options) { constructor(context, options) {
options = options || {}; options = options || {};
this.root = null; this.root = null;
this._indentStr = options.indent != null ? options.indent : ' ';
this._indentSize = this._indentStr.length;
this._code = ''; this._code = '';
this.currentIndent = ''; this.currentIndent = '';
@ -113,7 +64,7 @@ class Generator {
this._doneListeners = []; this._doneListeners = [];
this._bufferedWrites = null;
this.builder = context.builder; this.builder = context.builder;
this.outputType = options.output || 'html'; this.outputType = options.output || 'html';
this.context = context; this.context = context;
@ -124,16 +75,6 @@ class Generator {
this.outputType.charAt(0).toUpperCase() + this.outputType.charAt(0).toUpperCase() +
this.outputType.substring(1) + this.outputType.substring(1) +
'Code'; 'Code';
this._slots = [];
}
beginSlot(slotNode) {
var addSeparator = slotNode.statement;
this._flushBufferedWrites(addSeparator);
let slot = new Slot(this, slotNode);
this._slots.push(slot);
return slot;
} }
addVar(name, value) { addVar(name, value) {
@ -156,33 +97,63 @@ class Generator {
return this.context.importModule(varName, path); return this.context.importModule(varName, path);
} }
generateCode(node) { _invokeCodeGenerator(func, node, isMethod) {
ok(node != null, '"node" is required'); try {
if (isMethod) {
return func.call(node, this);
} else {
return func.call(node, node, this);
}
} catch(err) {
var errorMessage = 'Generating code for ';
if (typeof node === 'string' || if (node instanceof HtmlElement) {
typeof node === 'number' || errorMessage += '<'+node.tagName+'> tag';
typeof node === 'boolean') { } else {
this.write(node); errorMessage += node.type + ' node';
return; }
} else if (isArray(node)) {
node.forEach(this.generateCode, this); if (node.pos) {
errorMessage += ' ('+this.context.getPosInfo(node.pos)+')';
}
errorMessage += ' failed. Error: ' + err;
throw createError(errorMessage, err /* cause */);
}
}
_generateCode(node, finalNodes) {
if (isArray(node)) {
node.forEach((child) => {
this._generateCode(child, finalNodes);
});
return; return;
} else if (node instanceof Container) { } else if (node instanceof Container) {
node.forEach((child) => { node.forEach((child) => {
if (child.container === node) { if (child.container === node) {
this.generateCode(child); this._generateCode(child, finalNodes);
} }
}); });
return; return;
} }
if (node == null) {
return;
}
if (typeof node === 'string' || node._finalNode || !(node instanceof Node)) {
finalNodes.push(node);
return;
}
if (node._normalizeChildTextNodes) {
node._normalizeChildTextNodes(this.context);
}
let oldCurrentNode = this._currentNode; let oldCurrentNode = this._currentNode;
this._currentNode = node; this._currentNode = node;
let finalNode;
let generateCodeFunc;
var isStatement = node.statement;
var beforeAfterEvent; var beforeAfterEvent;
if (node.listenerCount('beforeGenerateCode') || node.listenerCount('afterGenerateCode')) { if (node.listenerCount('beforeGenerateCode') || node.listenerCount('afterGenerateCode')) {
@ -191,83 +162,56 @@ class Generator {
var isWhitespacePreserved = node.isPreserveWhitespace(); var isWhitespacePreserved = node.isPreserveWhitespace();
if (isWhitespacePreserved) {
this.context.beginPreserveWhitespace();
}
if (beforeAfterEvent) { if (beforeAfterEvent) {
beforeAfterEvent.isBefore = true; beforeAfterEvent.isBefore = true;
beforeAfterEvent.node.emit('beforeGenerateCode', beforeAfterEvent); beforeAfterEvent.node.emit('beforeGenerateCode', beforeAfterEvent);
if (isWhitespacePreserved) { if (beforeAfterEvent.insertedNodes) {
this.context.beginPreserveWhitespace(); this._generateCode(beforeAfterEvent.insertedNodes, finalNodes);
beforeAfterEvent.insertedNodes = null;
} }
} }
let codeGeneratorFunc;
let generatedCode;
if (node.getCodeGenerator) { if (node.getCodeGenerator) {
generateCodeFunc = node.getCodeGenerator(this.outputType); codeGeneratorFunc = node.getCodeGenerator(this.outputType);
if (generateCodeFunc) {
try {
finalNode = generateCodeFunc(node, this);
} catch(err) {
var errorMessage = 'Generating code for ';
if (node instanceof HtmlElement) { if (codeGeneratorFunc) {
errorMessage += '<'+node.tagName+'> tag'; node.setCodeGenerator(null);
} else {
errorMessage += node.type + ' node';
}
if (node.pos) { generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, false);
errorMessage += ' ('+this.context.getPosInfo(node.pos)+')'; if (generatedCode != null && generatedCode !== node) {
}
errorMessage += ' failed. Error: ' + err;
throw createError(errorMessage, err /* cause */);
}
if (finalNode === node) {
// If the same node was returned then we will generate
// code for the node as normal
finalNode = null;
} else if (finalNode == null) {
// If nothing was returned then don't generate any code
node = null; node = null;
this._generateCode(generatedCode, finalNodes);
} }
} }
} }
if (finalNode) { if (node != null) {
if (isStatement) { codeGeneratorFunc = node.generateCode;
this.generateStatements(finalNode);
} else {
this.generateCode(finalNode);
}
} else if (node) {
let generateCodeMethod = node.generateCode;
if (!generateCodeMethod) { if (!codeGeneratorFunc) {
generateCodeMethod = node[this._codegenCodeMethodName]; codeGeneratorFunc = node[this._codegenCodeMethodName];
if (!generateCodeMethod) {
throw new Error('No code codegen for node of type "' +
node.type +
'" (output type: "' + this.outputType + '"). Node: ' + util.inspect(node));
}
} }
// The generateCode function can optionally return either of the following: if (codeGeneratorFunc) {
// - An AST node generatedCode = this._invokeCodeGenerator(codeGeneratorFunc, node, true);
// - An array/cointainer of AST nodes
finalNode = generateCodeMethod.call(node, this);
if (finalNode != null) { if (generatedCode === undefined || generatedCode === node) {
if (finalNode === node) { finalNodes.push(node);
throw new Error('Invalid node returned. Same node returned: ' + util.inspect(node)); } else if (generatedCode === null) {
} // If nothing was returned then don't generate any code
if (isStatement) {
this.generateStatements(finalNode);
} else { } else {
this.generateCode(finalNode); this._generateCode(generatedCode, finalNodes);
} }
} else {
finalNodes.push(node);
} }
} }
@ -275,263 +219,45 @@ class Generator {
beforeAfterEvent.isBefore = false; beforeAfterEvent.isBefore = false;
beforeAfterEvent.node.emit('afterGenerateCode', beforeAfterEvent); beforeAfterEvent.node.emit('afterGenerateCode', beforeAfterEvent);
if (isWhitespacePreserved) { if (beforeAfterEvent.insertedNodes) {
this.context.endPreserveWhitespace(); this._generateCode(beforeAfterEvent.insertedNodes, finalNodes);
beforeAfterEvent.insertedNodes = null;
} }
} }
if (isWhitespacePreserved) {
this.context.endPreserveWhitespace();
}
this._currentNode = oldCurrentNode; this._currentNode = oldCurrentNode;
} }
getCode() { generateCode(node) {
this._flushBufferedWrites(); if (!node) {
return null;
}
while(this._doneListeners.length || this._slots.length) { if (node._finalNode) {
return node;
}
let doneListeners = this._doneListeners; let finalNodes = new FinalNodes();
if (doneListeners.length) {
this._doneListeners = [];
for (let i=0; i<doneListeners.length; i++) { var isList = typeof node.forEach === 'function';
let doneListener = doneListeners[i];
doneListener(this);
}
}
let slots = this._slots; this._generateCode(node, finalNodes);
if (slots.length) { finalNodes = finalNodes.nodes;
this._slots = [];
for (let i=slots.length-1; i>=0; i--) { if (!isList) {
let slot = slots[i]; if (finalNodes.length === 0) {
slot.generateCode(this); return null;
} } else if (finalNodes.length === 1) {
return finalNodes[0];
} }
} }
return this._code; return finalNodes;
}
generateBlock(body) {
if (!body) {
this.write('{}');
return;
}
if (typeof body === 'function') {
body = body();
}
if (!isArray(body) && !(body instanceof Container)) {
throw new Error('Invalid body');
}
if (body.length === 0) {
this.write('{}');
return;
}
this.write('{\n')
.incIndent();
let oldCodeLength = this._code.length;
this.generateStatements(body);
if (this._bufferedWrites) {
if (this._code.length !== oldCodeLength) {
this._code += '\n';
}
this._flushBufferedWrites();
}
this.decIndent()
.writeLineIndent()
.write('}');
}
generateStatements(nodes) {
ok(nodes, '"nodes" expected');
let firstStatement = true;
if (nodes instanceof Node) {
nodes = [nodes];
}
nodes.forEach((node) => {
if (node instanceof Node) {
node.statement = true;
}
let startCodeLen = this._code.length;
let currentIndent = this.currentIndent;
if (!firstStatement) {
this._write('\n');
}
if (!this._code.endsWith(currentIndent)) {
this.writeLineIndent();
}
let startPos = this._code.length;
if (Array.isArray(node) || (node instanceof Container)) {
this.generateStatements(node);
} else {
this.generateCode(node);
}
if (this._code.length === startPos) {
// No code was generated. Remove any code that was previously added
this._code = this._code.slice(0, startCodeLen);
return;
}
if (this._code.endsWith('\n')) {
// Do nothing
} else if (this._code.endsWith(';')) {
this._code += '\n';
} else if (this._code.endsWith('\n' + this.currentIndent)) {
// Do nothing
} else {
this._code += ';\n';
}
firstStatement = false;
});
}
_beginCaptureCode() {
let oldCode = this._code;
this._code = '';
return {
codegen: this,
end() {
let newCode = this.codegen._code;
this.codegen._code = oldCode;
return newCode;
}
};
}
addWriteLiteral(value) {
if (!(value instanceof Literal)) {
value = new Literal({value});
}
this.addWrite(value);
}
addWrite(output) {
ok(output, '"output" is required');
if (output instanceof Literal) {
let lastWrite = this._bufferedWrites ?
this._bufferedWrites[this._bufferedWrites.length-1] :
null;
if (lastWrite instanceof Literal) {
lastWrite.value += output.value;
return;
}
} else {
if (!(output instanceof Node)) {
throw new Error('Invalid write: ' + JSON.stringify(output, null, 2));
}
}
if (!this._bufferedWrites) {
this._bufferedWrites = [output];
} else {
this._bufferedWrites.push(output);
}
}
_flushBufferedWrites(addSeparator) {
let bufferedWrites = this._bufferedWrites;
if (!bufferedWrites) {
return;
}
this._bufferedWrites = null;
if (!addSeparator && !this._code.endsWith(this.currentIndent)) {
this.writeLineIndent();
}
let len = bufferedWrites.length;
for (let i=0; i<len; i++) {
let write = bufferedWrites[i];
if (i === 0) {
this._write('out.w(');
} else {
this._write(' +\n');
this.writeLineIndent();
this._write(this._indentStr);
}
this.generateCode(write);
}
this._write(');\n');
if (addSeparator) {
this._write('\n' + this.currentIndent);
}
}
write(code) {
if (this._bufferedWrites) {
this._flushBufferedWrites(true /* add separator */);
}
this._code += code;
return this;
}
_write(code) {
this._code += code;
return this;
}
incIndent(count) {
this._flushBufferedWrites(true /* add separator */);
if (count != null) {
for (let i=0; i<count; i++) {
this.currentIndent += ' ';
}
} else {
this.currentIndent += this._indentStr;
}
return this;
}
decIndent(count) {
if (count == null) {
count = this._indentSize;
}
this.currentIndent = this.currentIndent.substring(
0,
this.currentIndent.length - count);
return this;
}
writeLineIndent() {
this._code += this.currentIndent;
return this;
}
writeIndent() {
this._code += this._indentStr;
return this;
} }
isLiteralNode(node) { isLiteralNode(node) {
@ -542,94 +268,6 @@ class Generator {
return node instanceof Identifier; return node instanceof Identifier;
} }
writeLiteral(value) {
if (value === null) {
this.write('null');
} else if (value === undefined) {
this.write('undefined');
} else if (typeof value === 'string') {
this.write(JSON.stringify(value));
} else if (value === true) {
this.write('true');
} else if (value === false) {
this.write('false');
} else if (isArray(value)) {
if (value.length === 0) {
this.write('[]');
return;
}
this.write('[\n');
this.incIndent();
for (let i=0; i<value.length; i++) {
let v = value[i];
this.writeLineIndent();
if (v instanceof Node) {
this.generateCode(v);
} else {
this.writeLiteral(v);
}
if (i < value.length - 1) {
this.write(',\n');
} else {
this.write('\n');
}
}
this.decIndent();
this.writeLineIndent();
this.write(']');
} else if (typeof value === 'number') {
this.write(value.toString());
} else if (value instanceof RegExp) {
this.write(value.toString());
} else if (typeof value === 'object') {
let keys = Object.keys(value);
if (keys.length === 0) {
this.write('{}');
return;
}
this.incIndent();
this.write('{\n');
this.incIndent();
for (let i=0; i<keys.length; i++) {
let k = keys[i];
let v = value[k];
this.writeLineIndent();
if (isValidJavaScriptVarName(k)) {
this.write(k + ': ');
} else {
this.write(JSON.stringify(k) + ': ');
}
if (v instanceof Node) {
this.generateCode(v);
} else {
this.writeLiteral(v);
}
if (i < keys.length - 1) {
this.write(',\n');
} else {
this.write('\n');
}
}
this.decIndent();
this.writeLineIndent();
this.write('}');
this.decIndent();
}
}
isPreserveWhitespaceEnabled() { isPreserveWhitespaceEnabled() {
return false; return false;
} }
@ -661,4 +299,4 @@ class Generator {
} }
} }
module.exports = Generator; module.exports = CodeGenerator;

268
compiler/CodeWriter.js Normal file
View File

@ -0,0 +1,268 @@
'use strict';
const isArray = Array.isArray;
const Node = require('./ast/Node');
const Literal = require('./ast/Literal');
const Identifier = require('./ast/Identifier');
const ok = require('assert').ok;
const Container = require('./ast/Container');
const isValidJavaScriptVarName = require('./util/isValidJavaScriptVarName');
class CodeWriter {
constructor(options) {
options = options || {};
this.root = null;
this._indentStr = options.indent != null ? options.indent : ' ';
this._indentSize = this._indentStr.length;
this._code = '';
this.currentIndent = '';
this.outputType = options.output || 'html';
this._writeCodeMethodName = 'write' +
this.outputType.charAt(0).toUpperCase() +
this.outputType.substring(1) +
'Code';
}
getCode() {
return this._code;
}
writeBlock(body) {
if (!body) {
this.write('{}');
return;
}
if (typeof body === 'function') {
body = body();
}
if (!body ||
(Array.isArray(body) && body.length === 0) ||
(body instanceof Container && body.length === 0)) {
this.write('{}');
return;
}
this.write('{\n')
.incIndent();
this.writeStatements(body);
this.decIndent()
.writeLineIndent()
.write('}');
}
writeStatements(nodes) {
if (!nodes) {
return;
}
ok(nodes, '"nodes" expected');
let firstStatement = true;
var writeNode = (node) => {
if (Array.isArray(node) || (node instanceof Container)) {
node.forEach(writeNode);
return;
} else {
if (firstStatement) {
firstStatement = false;
} else {
this._write('\n');
}
this.writeLineIndent();
if (typeof node === 'string') {
this._write(node);
} else {
node.statement = true;
this.write(node);
}
if (this._code.endsWith('\n')) {
// Do nothing
} else if (this._code.endsWith(';')) {
this._code += '\n';
} else if (this._code.endsWith('\n' + this.currentIndent)) {
// Do nothing
} else {
this._code += ';\n';
}
}
};
if (nodes instanceof Node) {
writeNode(nodes);
} else {
nodes.forEach(writeNode);
}
}
write(code) {
if (code == null || code === '') {
return;
}
if (code instanceof Node) {
let node = code;
if (!node.writeCode) {
throw new Error('Node does not have a `writeCode` method: ' + JSON.stringify(node, null, 4));
}
node.writeCode(this);
} else if (isArray(code) || code instanceof Container) {
code.forEach(this.write, this);
return;
} else if (typeof code === 'string') {
this._code += code;
} else if (typeof code === 'boolean' || typeof code === 'number') {
this._code += code.toString();
} else {
throw new Error('Illegal argument: ' + JSON.stringify(code));
}
return this;
}
_write(code) {
this._code += code;
return this;
}
incIndent(count) {
if (count != null) {
for (let i=0; i<count; i++) {
this.currentIndent += ' ';
}
} else {
this.currentIndent += this._indentStr;
}
return this;
}
decIndent(count) {
if (count == null) {
count = this._indentSize;
}
this.currentIndent = this.currentIndent.substring(
0,
this.currentIndent.length - count);
return this;
}
writeLineIndent() {
this._code += this.currentIndent;
return this;
}
writeIndent() {
this._code += this._indentStr;
return this;
}
isLiteralNode(node) {
return node instanceof Literal;
}
isIdentifierNode(node) {
return node instanceof Identifier;
}
writeLiteral(value) {
if (value === null) {
this.write('null');
} else if (value === undefined) {
this.write('undefined');
} else if (typeof value === 'string') {
this.write(JSON.stringify(value));
} else if (value === true) {
this.write('true');
} else if (value === false) {
this.write('false');
} else if (isArray(value)) {
if (value.length === 0) {
this.write('[]');
return;
}
this.write('[\n');
this.incIndent();
for (let i=0; i<value.length; i++) {
let v = value[i];
this.writeLineIndent();
if (v instanceof Node) {
this.write(v);
} else {
this.writeLiteral(v);
}
if (i < value.length - 1) {
this.write(',\n');
} else {
this.write('\n');
}
}
this.decIndent();
this.writeLineIndent();
this.write(']');
} else if (typeof value === 'number') {
this.write(value.toString());
} else if (value instanceof RegExp) {
this.write(value.toString());
} else if (typeof value === 'object') {
let keys = Object.keys(value);
if (keys.length === 0) {
this.write('{}');
return;
}
this.incIndent();
this.write('{\n');
this.incIndent();
for (let i=0; i<keys.length; i++) {
let k = keys[i];
let v = value[k];
this.writeLineIndent();
if (isValidJavaScriptVarName(k)) {
this.write(k + ': ');
} else {
this.write(JSON.stringify(k) + ': ');
}
if (v instanceof Node) {
this.write(v);
} else {
this.writeLiteral(v);
}
if (i < keys.length - 1) {
this.write(',\n');
} else {
this.write('\n');
}
}
this.decIndent();
this.writeLineIndent();
this.write('}');
this.decIndent();
}
}
}
module.exports = CodeWriter;

View File

@ -48,8 +48,26 @@ function requireResolve(builder, path) {
return builder.functionCall(requireResolveNode, [ path ]); return builder.functionCall(requireResolveNode, [ path ]);
} }
const helpers = {
'escapeXml': 'x',
'escapeXmlAttr': 'xa',
'escapeScript': 'xs',
'str': 's',
'merge': 'm',
'classAttr': 'ca',
'styleAttr': 'sa',
'attr': 'a',
'attrs': 'as',
'loadTag': 't',
'forEachWithStatusVar': 'fv',
'forEach': 'f',
'forEachProp': 'fp',
'classList': 'cl',
'loadTemplate': 'l'
};
class CompileContext { class CompileContext {
constructor(src, filename, builder) { constructor(src, filename, builder, options) {
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');
@ -61,6 +79,8 @@ class CompileContext {
this.taglibLookup = taglibLookup.buildLookup(this.dirname); this.taglibLookup = taglibLookup.buildLookup(this.dirname);
this.data = {}; this.data = {};
this.options = options || {};
this._vars = {}; this._vars = {};
this._uniqueVars = new UniqueVars(); this._uniqueVars = new UniqueVars();
this._staticVars = {}; this._staticVars = {};
@ -72,6 +92,19 @@ class CompileContext {
this._macros = null; this._macros = null;
this._preserveWhitespace = null; this._preserveWhitespace = null;
this._preserveComments = null; this._preserveComments = null;
this.inline = this.options.inline === true;
this._helpersIdentifier = null;
if (this.options.preserveWhitespace) {
this.setPreserveWhitespace(true);
}
this._helpers = {};
}
setInline(isInline) {
this.inline = isInline === true;
} }
getPosInfo(pos) { getPosInfo(pos) {
@ -195,10 +228,6 @@ class CompileContext {
return this._staticCode; return this._staticCode;
} }
getEscapeXmlAttrVar() {
return this.addStaticVar('escapeXmlAttr', '__helpers.xa');
}
getTagDef(tagName) { getTagDef(tagName) {
var taglibLookup = this.taglibLookup; var taglibLookup = this.taglibLookup;
@ -378,14 +407,8 @@ class CompileContext {
ok(typeof relativePath === 'string', '"path" should be a string'); ok(typeof relativePath === 'string', '"path" should be a string');
var builder = this.builder; var builder = this.builder;
// We want to add the following import:
// var loadTemplate = __helpers.t;
// var template = loadTemplate(require.resolve(<templateRequirePath>))
var loadTemplateVar = this.addStaticVar('loadTemplate', '__helpers.l');
var requireResolveTemplate = requireResolve(builder, builder.literal(relativePath)); var requireResolveTemplate = requireResolve(builder, builder.literal(relativePath));
var loadFunctionCall = builder.functionCall(loadTemplateVar, [ requireResolveTemplate ]); var loadFunctionCall = builder.functionCall(this.helper('loadTemplate'), [ requireResolveTemplate ]);
var templateVar = this.addStaticVar(removeExt(relativePath), loadFunctionCall); var templateVar = this.addStaticVar(removeExt(relativePath), loadFunctionCall);
return templateVar; return templateVar;
} }
@ -447,6 +470,61 @@ class CompileContext {
return pathExpression; return pathExpression;
} }
getStaticNodes() {
let builder = this.builder;
let staticNodes = [];
let staticVars = this.getStaticVars();
let staticVarNodes = Object.keys(staticVars).map((varName) => {
var varInit = staticVars[varName];
return builder.variableDeclarator(varName, varInit);
});
if (staticVarNodes.length) {
staticNodes.push(this.builder.vars(staticVarNodes));
}
var staticCodeArray = this.getStaticCode();
if (staticCodeArray) {
staticNodes = staticNodes.concat(staticCodeArray);
}
return staticNodes;
}
get helpersIdentifier() {
if (!this._helpersIdentifier) {
if (this.inline) {
this._helpersIdentifier = this.importModule('__markoHelpers', 'marko/runtime/helpers');
} else {
// The helpers variable is a parameter of the outer create function
this._helpersIdentifier = this.builder.identifier('__markoHelpers');
}
}
return this._helpersIdentifier;
}
helper(name) {
var helperIdentifier = this._helpers[name];
if (!helperIdentifier) {
var methodName = helpers[name];
if (!methodName) {
throw new Error('Invalid helper: ' + name);
}
var methodIdentifier = this.builder.identifier(methodName);
helperIdentifier = this.addStaticVar(
'marko_' + name,
this.builder.memberExpression(this.helpersIdentifier, methodIdentifier));
this._helpers[name] = helperIdentifier;
}
return helperIdentifier;
}
} }
CompileContext.prototype.util = { CompileContext.prototype.util = {

View File

@ -1,10 +1,8 @@
'use strict'; 'use strict';
var ok = require('assert').ok; var ok = require('assert').ok;
var CodeGenerator = require('./CodeGenerator'); var CodeGenerator = require('./CodeGenerator');
var CompileContext = require('./CompileContext'); var CodeWriter = require('./CodeWriter');
var createError = require('raptor-util/createError'); var createError = require('raptor-util/createError');
var config = require('./config');
var extend = require('raptor-util/extend');
const FLAG_TRANSFORMER_APPLIED = 'transformerApply'; const FLAG_TRANSFORMER_APPLIED = 'transformerApply';
@ -61,8 +59,47 @@ function transformTree(rootNode, context) {
return rootNode; return rootNode;
} }
function handleErrors(context) {
// If there were any errors then compilation failed.
if (context.hasErrors()) {
var errors = context.getErrors();
var message = 'An error occurred while trying to compile template at path "' + context.filename + '". Error(s) in template:\n';
for (var i = 0, len = errors.length; i < len; i++) {
let error = errors[i];
message += (i + 1) + ') ' + error.toString() + '\n';
}
var error = new Error(message);
error.errors = errors;
throw error;
}
}
class CompiledTemplate {
constructor(ast, context, codeGenerator) {
this.ast = ast;
this.context = context;
this.filename = context.filename;
}
get code() {
// STAGE 3: Generate the code using the final AST
handleErrors(this.context);
// console.log(module.id, 'FINAL AST:' + JSON.stringify(finalAST, null, 4));
var codeWriter = new CodeWriter(this.context.options);
codeWriter.write(this.ast);
handleErrors(this.context);
// Return the generated code as the compiled output:
var compiledSrc = codeWriter.getCode();
return compiledSrc;
}
}
class Compiler { class Compiler {
constructor(options) { constructor(options, userOptions, inline) {
ok(options, '"options" is required'); ok(options, '"options" is required');
this.builder = options.builder; this.builder = options.builder;
@ -72,32 +109,14 @@ class Compiler {
ok(this.parser, '"options.parser" is required'); ok(this.parser, '"options.parser" is required');
} }
compile(src, filename, userOptions) { compile(src, context) {
ok(typeof src === 'string', '"src" argument should be a string'); ok(typeof src === 'string', '"src" argument should be a string');
ok(filename, '"filename" argument is required');
ok(typeof filename === 'string', '"filename" argument should be a string');
var context = new CompileContext(src, filename, this.builder);
var options = {};
extend(options, config);
if (userOptions) {
extend(options, userOptions);
}
if (options.preserveWhitespace) {
context.setPreserveWhitespace(true);
}
var codeGenerator = new CodeGenerator(context); var codeGenerator = new CodeGenerator(context);
// STAGE 1: Parse the template to produce the initial AST // STAGE 1: Parse the template to produce the initial AST
var ast = this.parser.parse(src, context); var ast = this.parser.parse(src, context);
// Trim start and end whitespace for the root node
ast._normalizeChildTextNodes(codeGenerator, true /* trim start and end */, true /* force */);
context.root = ast; context.root = ast;
// console.log('ROOT', JSON.stringify(ast, null, 2)); // console.log('ROOT', JSON.stringify(ast, null, 2));
@ -105,30 +124,13 @@ class Compiler {
var transformedAST = transformTree(ast, context); var transformedAST = transformTree(ast, context);
// console.log('transformedAST', JSON.stringify(ast, null, 2)); // console.log('transformedAST', JSON.stringify(ast, null, 2));
// Trim start and end whitespace for the root node (again, after the transformation) handleErrors(context);
transformedAST._normalizeChildTextNodes(codeGenerator, true /* trim start and end */, true /* force */);
// STAGE 3: Generate the code using the final AST var finalAST = codeGenerator.generateCode(transformedAST);
codeGenerator.generateCode(transformedAST); handleErrors(context);
// If there were any errors then compilation failed. return new CompiledTemplate(finalAST, context);
if (context.hasErrors()) {
var errors = context.getErrors();
var message = 'An error occurred while trying to compile template at path "' + filename + '". Error(s) in template:\n';
for (var i = 0, len = errors.length; i < len; i++) {
let error = errors[i];
message += (i + 1) + ') ' + error.toString() + '\n';
}
var error = new Error(message);
error.errors = errors;
throw error;
}
// Return the generated code as the compiled output:
var compiledSrc = codeGenerator.getCode();
return compiledSrc;
} }
} }

View File

@ -0,0 +1,84 @@
'use strict';
let CodeWriter = require('./CodeWriter');
function fixIndentation(lines) {
let length = lines.length;
let startLine = 0;
let endLine = length;
for (; startLine<length; startLine++) {
let line = lines[startLine];
if (line.trim() !== '') {
break;
}
}
for (; endLine>startLine; endLine--) {
let line = lines[endLine-1];
if (line.trim() !== '') {
break;
}
}
if (endLine === startLine) {
return '';
}
if (startLine !== 0 || endLine !== length) {
lines = lines.slice(startLine, endLine);
}
let firstLine = lines[0];
let indentToRemove = /^\s*/.exec(firstLine)[0];
if (indentToRemove) {
for (let i=0; i<lines.length; i++) {
let line = lines[i];
if (line.startsWith(indentToRemove)) {
lines[i] = line.substring(indentToRemove.length);
}
}
}
return lines.join('\n');
}
function normalizeTemplateSrc(src) {
let lines = src.split(/\r\n|\n\r|\n/);
if (lines.length) {
if (lines[0].trim() === '') {
return fixIndentation(lines);
}
}
return src.trim();
}
class InlineCompiler {
constructor(context, compiler) {
this.context = context;
this.compiler = compiler;
context.setInline(true);
}
compile(src) {
src = normalizeTemplateSrc(src);
// console.log('TEMPLATE SRC:>\n' + src + '\n<');
return this.compiler.compile(src, this.context);
}
get staticCode() {
let staticNodes = this.context.getStaticNodes();
if (!staticNodes || staticNodes.length === 0) {
return null;
}
let codeWriter = new CodeWriter(this.context.options);
codeWriter.write(staticNodes);
return codeWriter.getCode();
}
}
module.exports = InlineCompiler;

View File

@ -63,8 +63,8 @@ function mergeShorthandClassNames(el, shorthandClassNames, context) {
if (finalClassNames.length === 1) { if (finalClassNames.length === 1) {
el.setAttributeValue('class', finalClassNames[0]); el.setAttributeValue('class', finalClassNames[0]);
} else { } else {
var classListVar = context.addStaticVar('__classList', '__helpers.cl');
el.setAttributeValue('class', builder.functionCall(classListVar, finalClassNames)); el.setAttributeValue('class', builder.functionCall(context.helper('classList'), finalClassNames));
} }
} }
@ -141,6 +141,8 @@ class Parser {
if (tagNameExpression) { if (tagNameExpression) {
tagName = builder.parseExpression(tagNameExpression); tagName = builder.parseExpression(tagNameExpression);
} else if (tagName === 'marko-compiler-options') { } else if (tagName === 'marko-compiler-options') {
this.parentNode.setTrimStartEnd(true);
attributes.forEach(function (attr) { attributes.forEach(function (attr) {
let attrName = attr.name; let attrName = attr.name;
let handler = COMPILER_ATTRIBUTE_HANDLERS[attrName]; let handler = COMPILER_ATTRIBUTE_HANDLERS[attrName];
@ -279,6 +281,8 @@ class Parser {
this.prevTextNode = null; this.prevTextNode = null;
this.stack.pop(); this.stack.pop();
} }
handleComment(comment) { handleComment(comment) {

View File

@ -9,32 +9,37 @@ class ArrayExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.elements = codegen.generateCode(this.elements);
return this;
}
writeCode(writer) {
var elements = this.elements; var elements = this.elements;
if (!elements || !elements.length) { if (!elements || !elements.length) {
codegen.write('[]'); writer.write('[]');
return; return;
} }
codegen.incIndent(); writer.incIndent();
codegen.write('[\n'); writer.write('[\n');
codegen.incIndent(); writer.incIndent();
elements.forEach((element, i) => { elements.forEach((element, i) => {
codegen.writeLineIndent(); writer.writeLineIndent();
codegen.generateCode(element); writer.write(element);
if (i < elements.length - 1) { if (i < elements.length - 1) {
codegen.write(',\n'); writer.write(',\n');
} else { } else {
codegen.write('\n'); writer.write('\n');
} }
}); });
codegen.decIndent(); writer.decIndent();
codegen.writeLineIndent(); writer.writeLineIndent();
codegen.write(']'); writer.write(']');
codegen.decIndent(); writer.decIndent();
} }
walk(walker) { walk(walker) {

View File

@ -11,23 +11,29 @@ class Assignment extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.left = codegen.generateCode(this.left);
this.right = codegen.generateCode(this.right);
return this;
}
writeCode(writer) {
var left = this.left; var left = this.left;
var right = this.right; var right = this.right;
var operator = this.operator; var operator = this.operator;
codegen.generateCode(left); writer.write(left);
codegen.write(' ' + (operator || '=') + ' '); writer.write(' ' + (operator || '=') + ' ');
var wrap = right instanceof Assignment; var wrap = right instanceof Assignment;
if (wrap) { if (wrap) {
codegen.write('('); writer.write('(');
} }
codegen.generateCode(right); writer.write(right);
if (wrap) { if (wrap) {
codegen.write(')'); writer.write(')');
} }
} }

View File

@ -10,7 +10,12 @@ class AttributePlaceholder extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
codegen.generateCode(this.value); this.value = codegen.generateCode(this.value);
return this;
}
writeCode(writer) {
writer.write(this.value);
} }
walk(walker) { walk(walker) {

View File

@ -3,17 +3,17 @@
var Node = require('./Node'); var Node = require('./Node');
var isCompoundExpression = require('../util/isCompoundExpression'); var isCompoundExpression = require('../util/isCompoundExpression');
function generateCodeForOperand(node, codegen) { function writeCodeForOperand(node, writer) {
var wrap = isCompoundExpression(node); var wrap = isCompoundExpression(node);
if (wrap) { if (wrap) {
codegen.write('('); writer.write('(');
} }
codegen.generateCode(node); writer.write(node);
if (wrap) { if (wrap) {
codegen.write(')'); writer.write(')');
} }
} }
@ -44,6 +44,35 @@ class BinaryExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.left = codegen.generateCode(this.left);
this.right = codegen.generateCode(this.right);
var left = this.left;
var right = this.right;
var operator = this.operator;
if (!left || !right) {
throw new Error('Invalid BinaryExpression: ' + this);
}
var builder = codegen.builder;
if (left.type === 'Literal' && right.type === 'Literal') {
if (operator === '+') {
return builder.literal(left.value + right.value);
} else if (operator === '-') {
return builder.literal(left.value - right.value);
} else if (operator === '*') {
return builder.literal(left.value * right.value);
} else if (operator === '/') {
return builder.literal(left.value / right.value);
}
}
return this;
}
writeCode(writer) {
var left = this.left; var left = this.left;
var operator = this.operator; var operator = this.operator;
var right = this.right; var right = this.right;
@ -52,23 +81,11 @@ class BinaryExpression extends Node {
throw new Error('Invalid BinaryExpression: ' + this); throw new Error('Invalid BinaryExpression: ' + this);
} }
if (left.type === 'Literal' && right.type === 'Literal') { writeCodeForOperand(left, writer);
if (operator === '+') { writer.write(' ');
return codegen.generateCode(codegen.builder.literal(left.value + right.value)); writer.write(operator);
} else if (operator === '-') { writer.write(' ');
return codegen.generateCode(codegen.builder.literal(left.value - right.value)); writeCodeForOperand(right, writer);
} else if (operator === '*') {
return codegen.generateCode(codegen.builder.literal(left.value * right.value));
} else if (operator === '/') {
return codegen.generateCode(codegen.builder.literal(left.value / right.value));
}
}
generateCodeForOperand(left, codegen);
codegen.write(' ');
codegen.generateCode(operator);
codegen.write(' ');
generateCodeForOperand(right, codegen);
} }
isCompoundExpression() { isCompoundExpression() {

View File

@ -10,15 +10,19 @@ class Code extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
return this;
}
writeCode(writer) {
var code = this.value; var code = this.value;
if (!code) { if (!code) {
return; return;
} }
code = adjustIndent(code, codegen.currentIndent); code = adjustIndent(code, writer.currentIndent);
codegen.write(code); writer.write(code);
} }
} }

View File

@ -11,16 +11,22 @@ class ConditionalExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.test = codegen.generateCode(this.test);
this.consequent = codegen.generateCode(this.consequent);
this.alternate = codegen.generateCode(this.alternate);
return this;
}
writeCode(writer) {
var test = this.test; var test = this.test;
var consequent = this.consequent; var consequent = this.consequent;
var alternate = this.alternate; var alternate = this.alternate;
writer.write(test);
codegen.generateCode(test); writer.write(' ? ');
codegen.write(' ? '); writer.write(consequent);
codegen.generateCode(consequent); writer.write(' : ');
codegen.write(' : '); writer.write(alternate);
codegen.generateCode(alternate);
} }
isCompoundExpression() { isCompoundExpression() {

View File

@ -8,6 +8,10 @@ class ContainerNode extends Node {
this.body = this.makeContainer(def.body); this.body = this.makeContainer(def.body);
} }
generateCode(codegen) {
return codegen.genereateCode(this.body);
}
walk(walker) { walk(walker) {
this.body = walker.walk(this.body); this.body = walker.walk(this.body);
} }

View File

@ -230,7 +230,7 @@ class CustomTag extends HtmlElement {
let parentTagNode = getNestedTagParentNode(this, parentTagName); let parentTagNode = getNestedTagParentNode(this, parentTagName);
if (!parentTagNode) { if (!parentTagNode) {
codegen.addError('Invalid usage of the <' + this.tagName + '> nested tag. Tag not nested within a <' + parentTagName + '> tag.'); codegen.addError('Invalid usage of the <' + this.tagName + '> nested tag. Tag not nested within a <' + parentTagName + '> tag.');
return; return null;
} }
parentTagVar = parentTagNode.data.nestedTagVar; parentTagVar = parentTagNode.data.nestedTagVar;
} }
@ -239,9 +239,9 @@ class CustomTag extends HtmlElement {
var inputProps = buildInputProps(this, context); var inputProps = buildInputProps(this, context);
var renderBodyFunction; var renderBodyFunction;
var body = codegen.generateCode(this.body);
if (this.body && this.body.length) { if (body && body.length) {
if (tagDef.bodyFunction) { if (tagDef.bodyFunction) {
let bodyFunction = tagDef.bodyFunction; let bodyFunction = tagDef.bodyFunction;
let bodyFunctionName = bodyFunction.name; let bodyFunctionName = bodyFunction.name;
@ -249,9 +249,9 @@ class CustomTag extends HtmlElement {
return builder.identifier(param); return builder.identifier(param);
}); });
inputProps[bodyFunctionName] = builder.functionDeclaration(bodyFunctionName, bodyFunctionParams, this.body); inputProps[bodyFunctionName] = builder.functionDeclaration(bodyFunctionName, bodyFunctionParams, body);
} else { } else {
renderBodyFunction = context.builder.renderBodyFunction(this.body); renderBodyFunction = context.builder.renderBodyFunction(body);
if (nestedTagVar) { if (nestedTagVar) {
renderBodyFunction.params.push(nestedTagVar); renderBodyFunction.params.push(nestedTagVar);
} else { } else {
@ -289,7 +289,7 @@ class CustomTag extends HtmlElement {
if (Object.keys(inputProps.value).length === 0) { if (Object.keys(inputProps.value).length === 0) {
inputProps = argument; inputProps = argument;
} else { } else {
var mergeVar = codegen.addStaticVar('__merge', '__helpers.m'); var mergeVar = context.helper('merge');
inputProps = builder.functionCall(mergeVar, [ inputProps = builder.functionCall(mergeVar, [
inputProps, // Input props from the attributes take precedence inputProps, // Input props from the attributes take precedence
argument argument
@ -320,8 +320,6 @@ class CustomTag extends HtmlElement {
let renderFunctionCall = builder.functionCall(renderMethod, renderArgs); let renderFunctionCall = builder.functionCall(renderMethod, renderArgs);
finalNode = renderFunctionCall; finalNode = renderFunctionCall;
} else { } else {
var loadTagVar = codegen.addStaticVar('__loadTag', '__helpers.t');
var loadTagArgs = [ var loadTagArgs = [
requireRendererFunctionCall // The first param is the renderer requireRendererFunctionCall // The first param is the renderer
]; ];
@ -340,7 +338,7 @@ class CustomTag extends HtmlElement {
} }
} }
var loadTag = builder.functionCall(loadTagVar, loadTagArgs); var loadTag = builder.functionCall(context.helper('loadTag'), loadTagArgs);
var tagVar = tagDef.name; var tagVar = tagDef.name;
if (context.util.isJavaScriptReservedWord(tagVar)) { if (context.util.isJavaScriptReservedWord(tagVar)) {

View File

@ -8,12 +8,13 @@ class Declaration extends Node {
} }
generateHtmlCode(codegen) { generateHtmlCode(codegen) {
var builder = codegen.builder; var builder = codegen.builder;
codegen.addWrite(builder.literal('<?')); return [
codegen.addWrite(this.declaration); builder.htmlLiteral('<?'),
codegen.addWrite(builder.literal('?>')); codegen.generateCode(this.declaration),
builder.htmlLiteral('?>')
];
} }
toJSON() { toJSON() {

View File

@ -8,12 +8,13 @@ class DocumentType extends Node {
} }
generateHtmlCode(codegen) { generateHtmlCode(codegen) {
var builder = codegen.builder; var builder = codegen.builder;
codegen.addWrite(builder.literal('<!')); return [
codegen.addWrite(this.documentType); builder.htmlLiteral('<!'),
codegen.addWrite(builder.literal('>')); builder.html(codegen.generateCode(this.documentType)),
builder.htmlLiteral('>')
];
} }
toJSON() { toJSON() {

View File

@ -14,11 +14,15 @@ class Else extends Node {
codegen.addError('Unmatched else statement'); codegen.addError('Unmatched else statement');
return; return;
} }
var body = this.body;
codegen.write('else '); this.body = codegen.generateCode(this.body);
codegen.generateBlock(body); return this;
codegen.write('\n'); }
writeCode(writer) {
var body = this.body;
writer.writeBlock(body);
writer.write('\n');
} }
walk(walker) { walk(walker) {

View File

@ -17,9 +17,7 @@ class ElseIf extends Node {
return; return;
} }
var ifStatement = codegen.builder.ifStatement(this.test, this.body, this.else); return codegen.builder.ifStatement(this.test, this.body, this.else);
codegen.write('else ');
codegen.generateCode(ifStatement);
} }
walk(walker) { walk(walker) {

View File

@ -11,7 +11,11 @@ class Expression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
codegen.generateCode(this.value); return this;
}
writeCode(writer) {
writer.write(this.value);
} }
isCompoundExpression() { isCompoundExpression() {

View File

@ -22,7 +22,7 @@ class ForEach extends Node {
var separator = this.separator; var separator = this.separator;
var statusVarName = this.statusVarName; var statusVarName = this.statusVarName;
var iterator = this.iterator; var iterator = this.iterator;
var context = codegen.context;
var builder = codegen.builder; var builder = codegen.builder;
if (separator && !statusVarName) { if (separator && !statusVarName) {
@ -41,7 +41,7 @@ class ForEach extends Node {
builder.functionDeclaration(null, params, this.body) builder.functionDeclaration(null, params, this.body)
]); ]);
} else if (statusVarName) { } else if (statusVarName) {
let forEachVarName = codegen.addStaticVar('forEachWithStatusVar', '__helpers.fv');
let body = this.body; let body = this.body;
if (separator) { if (separator) {
@ -58,14 +58,12 @@ class ForEach extends Node {
]); ]);
} }
return builder.functionCall(forEachVarName, [ return builder.functionCall(context.helper('forEachWithStatusVar'), [
inExpression, inExpression,
builder.functionDeclaration(null, [varName, statusVarName], body) builder.functionDeclaration(null, [varName, statusVarName], body)
]); ]);
} else { } else {
let forEachVarName = codegen.addStaticVar('forEach', '__helpers.f'); return builder.functionCall(context.helper('forEach'), [
return builder.functionCall(forEachVarName, [
inExpression, inExpression,
builder.functionDeclaration(null, [varName], this.body) builder.functionDeclaration(null, [varName], this.body)
]); ]);

View File

@ -18,6 +18,7 @@ class ForEachProp extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
var context = codegen.context;
var nameVarName = this.nameVarName; var nameVarName = this.nameVarName;
var valueVarName = this.valueVarName; var valueVarName = this.valueVarName;
var inExpression = this.in; var inExpression = this.in;
@ -55,11 +56,12 @@ class ForEachProp extends Node {
builder.functionDeclaration(null, [nameVarName, valueVarName, statusVarName], body) builder.functionDeclaration(null, [nameVarName, valueVarName, statusVarName], body)
]); ]);
} else { } else {
let forEachVarName = codegen.addStaticVar('forEachProp', '__helpers.fp'); return builder.functionCall(
return builder.functionCall(forEachVarName, [ context.helper('forEachProp'),
inExpression, [
builder.functionDeclaration(null, [nameVarName, valueVarName], body) inExpression,
]); builder.functionDeclaration(null, [nameVarName, valueVarName], body)
]);
} }
} }

View File

@ -12,34 +12,42 @@ class ForStatement extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.init = codegen.generateCode(this.init);
this.test = codegen.generateCode(this.test);
this.update = codegen.generateCode(this.update);
this.body = codegen.generateCode(this.body);
return this;
}
writeCode(writer) {
var init = this.init; var init = this.init;
var test = this.test; var test = this.test;
var update = this.update; var update = this.update;
var body = this.body; var body = this.body;
codegen.write('for ('); writer.write('for (');
if (init) { if (init) {
codegen.generateCode(init); writer.write(init);
} }
codegen.write('; '); writer.write('; ');
if (test) { if (test) {
codegen.generateCode(test); writer.write(test);
} }
codegen.write('; '); writer.write('; ');
if (update) { if (update) {
codegen.generateCode(update); writer.write(update);
} }
codegen.write(') '); writer.write(') ');
codegen.generateBlock(body); writer.writeBlock(body);
codegen.write('\n'); writer.write('\n');
} }
walk(walker) { walk(walker) {

View File

@ -2,6 +2,7 @@
var ok = require('assert').ok; var ok = require('assert').ok;
var Node = require('./Node'); var Node = require('./Node');
var isCompoundExpression = require('../util/isCompoundExpression');
class FunctionCall extends Node { class FunctionCall extends Node {
constructor(def) { constructor(def) {
@ -27,28 +28,47 @@ class FunctionCall extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.callee = codegen.generateCode(this.callee);
this.args = codegen.generateCode(this.args);
return this;
}
writeCode(writer) {
var callee = this.callee; var callee = this.callee;
var args = this.args; var args = this.args;
codegen.generateCode(callee); var wrapWithParens = isCompoundExpression(callee);
codegen.write('('); if (wrapWithParens) {
writer.write('(');
}
writer.write(callee);
if (wrapWithParens) {
writer.write(')');
}
writer.write('(');
if (args && args.length) { if (args && args.length) {
for (let i=0, argsLen = args.length; i<argsLen; i++) { for (let i=0, argsLen = args.length; i<argsLen; i++) {
if (i !== 0) { if (i !== 0) {
codegen.write(', '); writer.write(', ');
} }
let arg = args[i]; let arg = args[i];
if (!arg) { if (!arg) {
throw new Error('Arg ' + i + ' is not valid for function call: ' + JSON.stringify(this.toJSON())); throw new Error('Arg ' + i + ' is not valid for function call: ' + JSON.stringify(this.toJSON()));
} }
codegen.generateCode(arg); writer.write(arg);
} }
} }
codegen.write(')'); writer.write(')');
} }
walk(walker) { walk(walker) {

View File

@ -12,6 +12,14 @@ class FunctionDeclaration extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
var oldInFunction = codegen.inFunction;
codegen.inFunction = true;
this.body = codegen.generateCode(this.body);
codegen.inFunction = oldInFunction;
return this;
}
writeCode(writer) {
var name = this.name; var name = this.name;
var params = this.params; var params = this.params;
var body = this.body; var body = this.body;
@ -22,39 +30,37 @@ class FunctionDeclaration extends Node {
} }
if (name) { if (name) {
codegen.write('function '); writer.write('function ');
codegen.generateCode(name); writer.write(name);
codegen.write('('); writer.write('(');
} else { } else {
codegen.write('function('); writer.write('function(');
} }
if (params && params.length) { if (params && params.length) {
for (let i=0, paramsLen = params.length; i<paramsLen; i++) { for (let i=0, paramsLen = params.length; i<paramsLen; i++) {
if (i !== 0) { if (i !== 0) {
codegen.write(', '); writer.write(', ');
} }
var param = params[i]; var param = params[i];
if (typeof param === 'string') { if (typeof param === 'string') {
codegen.write(param); writer.write(param);
} else { } else {
if (param.type !== 'Identifier') { if (param.type !== 'Identifier') {
throw new Error('Illegal param ' + JSON.stringify(param) + ' for FunctionDeclaration: ' + JSON.stringify(this)); throw new Error('Illegal param ' + JSON.stringify(param) + ' for FunctionDeclaration: ' + JSON.stringify(this));
} }
codegen.generateCode(param); writer.write(param);
} }
} }
} }
codegen.write(') '); writer.write(') ');
var oldInFunction = codegen.inFunction;
codegen.inFunction = true; writer.writeBlock(body);
codegen.generateBlock(body);
codegen.inFunction = oldInFunction;
if (statement) { if (statement) {
codegen.write('\n'); writer.write('\n');
} }
} }

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
var Node = require('./Node'); var Node = require('./Node');
var Literal = require('./Literal');
class Html extends Node { class Html extends Node {
constructor(def) { constructor(def) {
@ -8,13 +9,70 @@ class Html extends Node {
this.argument = def.argument; this.argument = def.argument;
} }
isLiteral() { _append(appendArgument) {
return this.argument instanceof Node && this.argument.type === 'Literal'; 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 ];
}
}
} }
generateHtmlCode(codegen) { append(html) {
let argument = this.argument; var appendArgument = html.argument;
codegen.addWrite(argument); if (!appendArgument) {
return;
}
if (Array.isArray(appendArgument)) {
appendArgument.forEach(this._append, this);
} else {
this._append(appendArgument);
}
}
generateCode() {
return this;
}
writeCode(writer) {
var argument = this.argument;
if (Array.isArray(argument)) {
let args = argument;
for (let i=0, len=args.length; i<len; i++) {
let arg = args[i];
if (i === 0) {
writer.write('out.w(');
} else {
writer.write(' +\n');
writer.writeLineIndent();
writer.writeIndent();
}
writer.write(arg);
}
writer.write(')');
} else {
writer.write('out.w(');
writer.write(argument);
writer.write(')');
}
} }
walk(walker) { walk(walker) {

View File

@ -52,6 +52,18 @@ function flattenAttrConcats(node) {
function generateCodeForExpressionAttr(name, value, escape, codegen) { function generateCodeForExpressionAttr(name, value, escape, codegen) {
var flattenedConcats = flattenAttrConcats(value); var flattenedConcats = flattenAttrConcats(value);
var hasLiteral = false; var hasLiteral = false;
var builder = codegen.builder;
var finalNodes = [];
var context = codegen.context;
function addHtml(argument) {
finalNodes.push(builder.html(argument));
}
function addHtmlLiteral(value) {
finalNodes.push(builder.htmlLiteral(value));
}
for (let i=0; i<flattenedConcats.length; i++) { for (let i=0; i<flattenedConcats.length; i++) {
if (flattenedConcats[i].type === 'Literal') { if (flattenedConcats[i].type === 'Literal') {
@ -61,7 +73,7 @@ function generateCodeForExpressionAttr(name, value, escape, codegen) {
} }
if (hasLiteral) { if (hasLiteral) {
codegen.addWriteLiteral(' ' + name + '="'); addHtmlLiteral(' ' + name + '="');
for (let i=0; i<flattenedConcats.length; i++) { for (let i=0; i<flattenedConcats.length; i++) {
var part = flattenedConcats[i]; var part = flattenedConcats[i];
if (isStringLiteral(part)) { if (isStringLiteral(part)) {
@ -69,32 +81,21 @@ function generateCodeForExpressionAttr(name, value, escape, codegen) {
} else if (part.type === 'Literal') { } else if (part.type === 'Literal') {
} else if (isNoEscapeXml(part)) { } else if (isNoEscapeXml(part)) {
part = codegen.builder.functionCall(codegen.builder.identifier('str'), [part]); part = codegen.builder.functionCall(context.helper('str'), [part]);
} else { } else {
if (escape !== false) { if (escape !== false) {
var escapeXmlAttrVar = codegen.getEscapeXmlAttrVar(); part = codegen.builder.functionCall(context.helper('escapeXmlAttr'), [part]);
part = codegen.builder.functionCall(escapeXmlAttrVar, [part]);
} }
} }
codegen.addWrite(part); addHtml(part);
} }
codegen.addWriteLiteral('"'); addHtmlLiteral('"');
} else { } else {
if (name === 'class') { if (name === 'class') {
// let builder = codegen.builder; addHtml(codegen.builder.functionCall(context.helper('classAttr'), [value]));
// let valueWithEscaping = handleEscaping(value);
let classAttrVar = codegen.addStaticVar('classAttr', '__helpers.ca');
codegen.addWrite(codegen.builder.functionCall(classAttrVar, [value]));
} else if (name === 'style') { } else if (name === 'style') {
// let builder = codegen.builder; addHtml(codegen.builder.functionCall(context.helper('styleAttr'), [value]));
// let valueWithEscaping = handleEscaping(value);
let styleAttrVar = codegen.addStaticVar('styleAttr', '__helpers.sa');
codegen.addWrite(codegen.builder.functionCall(styleAttrVar, [value]));
} else { } else {
// let builder = codegen.builder;
// let valueWithEscaping = handleEscaping(value);
let attrVar = codegen.addStaticVar('attr', '__helpers.a');
if (escape === false || isNoEscapeXml(value)) { if (escape === false || isNoEscapeXml(value)) {
escape = false; escape = false;
} }
@ -104,12 +105,22 @@ function generateCodeForExpressionAttr(name, value, escape, codegen) {
if (escape === false) { if (escape === false) {
attrArgs.push(codegen.builder.literal(false)); attrArgs.push(codegen.builder.literal(false));
} }
codegen.addWrite(codegen.builder.functionCall(attrVar, attrArgs)); addHtml(codegen.builder.functionCall(context.helper('attr'), attrArgs));
} }
} }
return finalNodes;
} }
function beforeGenerateCode(event) {
event.codegen.isInAttribute = true;
}
function afterGenerateCode(event) {
event.codegen.isInAttribute = false;
}
class HtmlAttribute extends Node { class HtmlAttribute extends Node {
constructor(def) { constructor(def) {
super('HtmlAttribute'); super('HtmlAttribute');
@ -132,6 +143,9 @@ class HtmlAttribute extends Node {
this.argument = def.argument; this.argument = def.argument;
this.def = def.def; // The attribute definition loaded from the taglib (if any) this.def = def.def; // The attribute definition loaded from the taglib (if any)
this.on('beforeGenerateCode', beforeGenerateCode);
this.on('afterGenerateCode', afterGenerateCode);
} }
isLiteralValue() { isLiteralValue() {
@ -153,24 +167,25 @@ class HtmlAttribute extends Node {
let value = this.value; let value = this.value;
let argument = this.argument; let argument = this.argument;
let escape = this.escape !== false; let escape = this.escape !== false;
var builder = codegen.builder;
if (!name) { if (!name) {
return; return null;
} }
if (this.isLiteralValue()) { if (this.isLiteralValue()) {
codegen.addWriteLiteral(attr(name, value.value)); return builder.htmlLiteral(attr(name, value.value));
} else if (value != null) { } else if (value != null) {
codegen.isInAttribute = true; return generateCodeForExpressionAttr(name, value, escape, codegen);
generateCodeForExpressionAttr(name, value, escape, codegen);
codegen.isInAttribute = false;
} else if (argument) { } else if (argument) {
codegen.addWriteLiteral(' ' + name + '('); return [
codegen.addWriteLiteral(argument); builder.htmlLiteral(' ' + name + '('),
codegen.addWriteLiteral(')'); builder.htmlLiteral(argument),
builder.htmlLiteral(')')
];
} else { } else {
// Attribute with no value is a boolean attribute // Attribute with no value is a boolean attribute
codegen.addWriteLiteral(' ' + name); return builder.htmlLiteral(' ' + name);
} }
} }

View File

@ -10,11 +10,13 @@ class HtmlComment extends Node {
generateHtmlCode(codegen) { generateHtmlCode(codegen) {
var comment = this.comment; var comment = this.comment;
var literal = codegen.builder.literal; var builder = codegen.builder;
codegen.addWrite(literal('<!--')); return [
codegen.addWrite(comment); builder.htmlLiteral('<!--'),
codegen.addWrite(literal('-->')); builder.html(comment),
builder.htmlLiteral('-->')
];
} }
walk(walker) { walk(walker) {

View File

@ -21,34 +21,36 @@ class StartTag extends Node {
var tagName = this.tagName; var tagName = this.tagName;
var selfClosed = this.selfClosed; var selfClosed = this.selfClosed;
var dynamicAttributes = this.dynamicAttributes; var dynamicAttributes = this.dynamicAttributes;
var context = codegen.context;
// Starting tag var nodes = [
codegen.addWriteLiteral('<'); builder.htmlLiteral('<'),
builder.html(tagName),
codegen.addWrite(tagName); ];
var attributes = this.attributes; var attributes = this.attributes;
if (attributes) { if (attributes) {
for (let i=0; i<attributes.length; i++) { for (let i=0; i<attributes.length; i++) {
let attr = attributes[i]; let attr = attributes[i];
codegen.generateCode(attr); nodes.push(codegen.generateCode(attr));
} }
} }
if (dynamicAttributes) { if (dynamicAttributes) {
dynamicAttributes.forEach(function(attrsExpression) { dynamicAttributes.forEach(function(attrsExpression) {
codegen.addStaticVar('attrs', '__helpers.as'); let attrsFunctionCall = builder.functionCall(context.helper('attrs'), [attrsExpression]);
let attrsFunctionCall = builder.functionCall('attrs', [attrsExpression]); nodes.push(builder.html(attrsFunctionCall));
codegen.addWrite(attrsFunctionCall);
}); });
} }
if (selfClosed) { if (selfClosed) {
codegen.addWriteLiteral('/>'); nodes.push(builder.htmlLiteral('/>'));
} else { } else {
codegen.addWriteLiteral('>'); nodes.push(builder.htmlLiteral('>'));
} }
return nodes;
} }
} }
@ -60,9 +62,13 @@ class EndTag extends Node {
generateCode(codegen) { generateCode(codegen) {
var tagName = this.tagName; var tagName = this.tagName;
codegen.addWriteLiteral('</'); var builder = codegen.builder;
codegen.addWrite(tagName);
codegen.addWriteLiteral('>'); return [
builder.htmlLiteral('</'),
builder.html(tagName),
builder.htmlLiteral('>')
];
} }
} }
@ -131,6 +137,10 @@ class HtmlElement extends Node {
var builder = codegen.builder; var builder = codegen.builder;
if (hasBody) {
body = codegen.generateCode(body);
}
if (hasBody || bodyOnlyIf) { if (hasBody || bodyOnlyIf) {
openTagOnly = false; openTagOnly = false;
selfClosed = false; selfClosed = false;
@ -170,7 +180,7 @@ class HtmlElement extends Node {
]; ];
} else { } else {
if (openTagOnly) { if (openTagOnly) {
codegen.generateCode(startTag); return codegen.generateCode(startTag);
} else { } else {
return [ return [
startTag, startTag,

View File

@ -9,8 +9,12 @@ class Identifier extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
return this;
}
writeCode(writer) {
var name = this.name; var name = this.name;
codegen.write(name); writer.write(name);
} }
toString() { toString() {

View File

@ -18,7 +18,6 @@ class If extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
if (this.else) { if (this.else) {
this.else.matched = true; this.else.matched = true;
} else { } else {
@ -59,18 +58,26 @@ class If extends Node {
}); });
} }
this.test = codegen.generateCode(this.test);
this.body = codegen.generateCode(this.body);
this.else = codegen.generateCode(this.else);
return this;
}
writeCode(writer) {
var test = this.test; var test = this.test;
var body = this.body; var body = this.body;
codegen.write('if ('); writer.write('if (');
codegen.generateCode(test); writer.write(test);
codegen.write(') '); writer.write(') ');
codegen.generateBlock(body); writer.writeBlock(body);
if (this.else) { if (this.else) {
codegen.write(' '); writer.write(' else ');
codegen.generateCode(this.else); writer.write(this.else);
} else { } else {
codegen.write('\n'); writer.write('\n');
} }
} }

View File

@ -11,8 +11,26 @@ class Literal extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
if (this.value != null) {
if (isArray(this.value)) {
this.value = codegen.generateCode(this.value);
} else if (typeof this.value === 'object') {
var newObject = {};
for (var k in this.value) {
if (this.value.hasOwnProperty(k)) {
newObject[k] = codegen.generateCode(this.value[k]);
}
}
this.value = newObject;
}
}
return this;
}
writeCode(writer) {
var value = this.value; var value = this.value;
codegen.writeLiteral(value); writer.writeLiteral(value);
} }
toString() { toString() {

View File

@ -3,21 +3,21 @@
var Node = require('./Node'); var Node = require('./Node');
var isCompoundExpression = require('../util/isCompoundExpression'); var isCompoundExpression = require('../util/isCompoundExpression');
function generateCodeForOperand(node, codegen) { function generateCodeForOperand(node, writer) {
var wrap = isCompoundExpression(node); var wrap = isCompoundExpression(node);
if (wrap) { if (wrap) {
codegen.write('('); writer.write('(');
} }
codegen.generateCode(node); writer.write(node);
if (wrap) { if (wrap) {
codegen.write(')'); writer.write(')');
} }
} }
function operandToString(node, codegen) { function operandToString(node) {
var wrap = isCompoundExpression(node); var wrap = isCompoundExpression(node);
var result = ''; var result = '';
@ -44,6 +44,12 @@ class LogicalExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.left = codegen.generateCode(this.left);
this.right = codegen.generateCode(this.right);
return this;
}
writeCode(writer) {
var left = this.left; var left = this.left;
var operator = this.operator; var operator = this.operator;
var right = this.right; var right = this.right;
@ -52,11 +58,11 @@ class LogicalExpression extends Node {
throw new Error('Invalid LogicalExpression: ' + this); throw new Error('Invalid LogicalExpression: ' + this);
} }
generateCodeForOperand(left, codegen); generateCodeForOperand(left, writer);
codegen.write(' '); writer.write(' ');
codegen.generateCode(operator); writer.write(operator);
codegen.write(' '); writer.write(' ');
generateCodeForOperand(right, codegen); generateCodeForOperand(right, writer);
} }
isCompoundExpression() { isCompoundExpression() {

View File

@ -20,13 +20,13 @@ class Macro extends Node {
generateCode(codegen) { generateCode(codegen) {
var name = this.name; var name = this.name;
var params = this.params || []; var params = this.params || [];
var body = this.body;
var builder = codegen.builder; var builder = codegen.builder;
var macroDef = codegen.context.registerMacro(name, params); var macroDef = codegen.context.registerMacro(name, params);
var functionName = macroDef.functionName; var functionName = macroDef.functionName;
// Walk the body after registering the macro
var body = codegen.generateCode(this.body);
return builder.functionDeclaration(functionName, macroDef.params, body); return builder.functionDeclaration(functionName, macroDef.params, body);
} }

View File

@ -1,6 +1,8 @@
'use strict'; 'use strict';
var Node = require('./Node'); var Node = require('./Node');
var isCompoundExpression = require('../util/isCompoundExpression');
var ok = require('assert').ok;
class MemberExpression extends Node { class MemberExpression extends Node {
constructor(def) { constructor(def) {
@ -8,22 +10,41 @@ class MemberExpression extends Node {
this.object = def.object; this.object = def.object;
this.property = def.property; this.property = def.property;
this.computed = def.computed; this.computed = def.computed;
ok(this.object, '"object" is required');
ok(this.property, '"property" is required');
} }
generateCode(codegen) { generateCode(codegen) {
this.object = codegen.generateCode(this.object);
this.property = codegen.generateCode(this.property);
return this;
}
writeCode(writer) {
var object = this.object; var object = this.object;
var property = this.property; var property = this.property;
var computed = this.computed; var computed = this.computed;
codegen.generateCode(object); var wrapWithParens = isCompoundExpression(object);
if (wrapWithParens) {
writer.write('(');
}
writer.write(object);
if (wrapWithParens) {
writer.write(')');
}
if (computed) { if (computed) {
codegen.write('['); writer.write('[');
codegen.generateCode(property); writer.write(property);
codegen.write(']'); writer.write(']');
} else { } else {
codegen.write('.'); writer.write('.');
codegen.generateCode(property); writer.write(property);
} }
} }

View File

@ -11,40 +11,46 @@ class NewExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.callee = codegen.generateCode(this.callee);
this.args = codegen.generateCode(this.args);
return this;
}
writeCode(writer) {
var callee = this.callee; var callee = this.callee;
var args = this.args; var args = this.args;
codegen.write('new '); writer.write('new ');
var wrap = isCompoundExpression(callee); var wrap = isCompoundExpression(callee);
if (wrap) { if (wrap) {
codegen.write('('); writer.write('(');
} }
codegen.generateCode(callee); writer.write(callee);
if (wrap) { if (wrap) {
codegen.write(')'); writer.write(')');
} }
codegen.write('('); writer.write('(');
if (args && args.length) { if (args && args.length) {
for (let i=0, argsLen = args.length; i<argsLen; i++) { for (let i=0, argsLen = args.length; i<argsLen; i++) {
if (i !== 0) { if (i !== 0) {
codegen.write(', '); writer.write(', ');
} }
let arg = args[i]; let arg = args[i];
if (!arg) { if (!arg) {
throw new Error('Arg ' + i + ' is not valid for new expression: ' + JSON.stringify(this.toJSON())); throw new Error('Arg ' + i + ' is not valid for new expression: ' + JSON.stringify(this.toJSON()));
} }
codegen.generateCode(arg); writer.write(arg);
} }
} }
codegen.write(')'); writer.write(')');
} }
isCompoundExpression() { isCompoundExpression() {

View File

@ -45,6 +45,8 @@ class Node {
this._events = null; this._events = null;
this._childTextNormalized = undefined; this._childTextNormalized = undefined;
this.data = {}; this.data = {};
this._finalNode = false;
this._trimStartEnd = false;
} }
on(event, listener) { on(event, listener) {
@ -189,6 +191,8 @@ class Node {
delete result.tagDef; delete result.tagDef;
delete result._preserveWhitespace; delete result._preserveWhitespace;
delete result._events; delete result._events;
delete result._finalNode;
delete result._trimStartEnd;
return result; return result;
} }
@ -290,16 +294,26 @@ class Node {
return preserveWhitespace === true; return preserveWhitespace === true;
} }
_normalizeChildTextNodes(codegen, trimStartEnd, force) { setFinalNode(isFinal) {
if (this._childTextNormalized && force !== true) { this._finalNode = true;
}
setTrimStartEnd(trimStartEnd) {
this._trimStartEnd = trimStartEnd;
}
_normalizeChildTextNodes(context) {
if (this._childTextNormalized) {
return; return;
} }
this._childTextNormalized = true; this._childTextNormalized = true;
var trimStartEnd = this._trimStartEnd === true;
var isPreserveWhitespace = false; var isPreserveWhitespace = false;
if (codegen.context.isPreserveWhitespace() || this.preserveWhitespace === true || this.isPreserveWhitespace()) { if (context.isPreserveWhitespace() || this.preserveWhitespace === true || this.isPreserveWhitespace()) {
isPreserveWhitespace = true; isPreserveWhitespace = true;
} }

View File

@ -9,32 +9,38 @@ class ObjectExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.properties = codegen.generateCode(this.properties);
return this;
}
writeCode(writer) {
var properties = this.properties; var properties = this.properties;
if (!properties || !properties.length) { if (!properties || !properties.length) {
codegen.write('{}'); writer.write('{}');
return; return;
} }
codegen.incIndent(); writer.incIndent();
codegen.write('{\n'); writer.write('{\n');
codegen.incIndent(); writer.incIndent();
properties.forEach((prop, i) => { properties.forEach((prop, i) => {
codegen.writeLineIndent(); writer.writeLineIndent();
codegen.generateCode(prop); writer.write(prop);
if (i < properties.length - 1) { if (i < properties.length - 1) {
codegen.write(',\n'); writer.write(',\n');
} else { } else {
codegen.write('\n'); writer.write('\n');
} }
}); });
codegen.decIndent(); writer.decIndent();
codegen.writeLineIndent(); writer.writeLineIndent();
codegen.write('}'); writer.write('}');
codegen.decIndent(); writer.decIndent();
} }
toJSON() { toJSON() {

View File

@ -8,12 +8,12 @@ class Program extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
var body = this.body; this.body = codegen.generateCode(this.body);
codegen.generateStatements(body); return this;
if (codegen._bufferedWrites) { }
codegen._write('\n');
codegen._flushBufferedWrites(); writeCode(writer) {
} writer.writeStatements(this.body);
} }
walk(walker) { walk(walker) {

View File

@ -20,9 +20,18 @@ class Property extends Node {
} }
} }
codegen.generateCode(key); this.key = codegen.generateCode(key);
codegen.write(': '); this.value = codegen.generateCode(value);
codegen.generateCode(value);
return this;
}
writeCode(writer) {
var key = this.key;
var value = this.value;
writer.write(key);
writer.write(': ');
writer.write(value);
} }
toJSON() { toJSON() {

View File

@ -13,13 +13,18 @@ class Return extends Node {
throw new Error('"return" not allowed outside a function body'); throw new Error('"return" not allowed outside a function body');
} }
this.argument = codegen.generateCode(this.argument);
return this;
}
writeCode(writer) {
var argument = this.argument; var argument = this.argument;
if (argument) { if (argument) {
codegen.write('return '); writer.write('return ');
codegen.generateCode(argument); writer.write(argument);
} else { } else {
codegen.write('return'); writer.write('return');
} }
} }

View File

@ -10,16 +10,20 @@ class Scriptlet extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
return this;
}
writeCode(writer) {
var code = this.code; var code = this.code;
if (!code) { if (!code) {
return; return;
} }
code = adjustIndent(code, codegen.currentIndent); code = adjustIndent(code, writer.currentIndent);
codegen.write(code); writer.write(code);
codegen.write('\n'); writer.write('\n');
} }
} }

View File

@ -13,14 +13,12 @@ class SelfInvokingFunction extends Node {
generateCode(codegen) { generateCode(codegen) {
var params = this.params || []; var params = this.params || [];
var args = this.args || []; var args = this.args || [];
var body = this.body; var body = codegen.generateCode(this.body);
codegen.write('(');
var functionDeclaration = codegen.builder.functionDeclaration(null, params, body); var functionDeclaration = codegen.builder.functionDeclaration(null, params, body);
var functionCall = codegen.builder.functionCall(functionDeclaration, args); var functionCall = codegen.builder.functionCall(functionDeclaration, args);
codegen.generateCode(functionCall);
codegen.write(')'); return functionCall;
} }
walk(walker) { walk(walker) {

View File

@ -1,36 +0,0 @@
'use strict';
var Node = require('./Node');
class Slot extends Node {
constructor(def) {
super('Slot');
this.onDone = def.onDone;
this.codegenSlot = null;
}
generateCode(codegen) {
if (this.onDone) {
codegen.onDone((codegen) => {
this.onDone(this, codegen);
});
}
// At the time the code for this node is to be generated we instead
// create a slot. A slot is just a marker in the output code stream
// that we can later inject code into. The injection happens after
// the entire tree has been walked.
this.codegenSlot = codegen.beginSlot(this);
}
setContent(content) {
this.codegenSlot.setContent(content);
}
toJSON() {
return {
type: this.type
};
}
}
module.exports = Slot;

View File

@ -11,6 +11,8 @@ function createVarsArray(vars) {
}); });
} }
var templateExports = null;
class TemplateRoot extends Node { class TemplateRoot extends Node {
constructor(def) { constructor(def) {
super('TemplateRoot'); super('TemplateRoot');
@ -20,51 +22,63 @@ class TemplateRoot extends Node {
generateCode(codegen) { generateCode(codegen) {
var context = codegen.context; var context = codegen.context;
var body = this.body; var body = codegen.generateCode(this.body);
codegen.addStaticVar('str', '__helpers.s');
codegen.addStaticVar('empty', '__helpers.e');
codegen.addStaticVar('notEmpty', '__helpers.ne');
codegen.addStaticVar('escapeXml', '__helpers.x');
var builder = codegen.builder; var builder = codegen.builder;
var program = builder.program;
var functionDeclaration = builder.functionDeclaration;
var returnStatement = builder.returnStatement; let renderStatements = [];
var slot = builder.slot; var vars = createVarsArray(context.getVars());
if (vars.length) {
var staticsSlot = slot(); renderStatements.push(builder.vars(vars));
var varsSlot = slot();
varsSlot.noOutput = true;
body = [ varsSlot ].concat(body.items);
var outputNode = program([
functionDeclaration('create', ['__helpers'], [
staticsSlot,
returnStatement(
functionDeclaration('render', ['data', 'out'], body))
]),
'(module.exports = require("marko").c(__filename)).c(create)'
]);
codegen.generateCode(outputNode);
var staticVars = context.getStaticVars();
var staticCodeArray = context.getStaticCode();
var staticContent = [builder.vars(createVarsArray(staticVars))];
if (staticCodeArray) {
staticCodeArray.forEach((code) => {
staticContent.push(code);
});
} }
staticsSlot.setContent(staticContent); renderStatements = renderStatements.concat(body);
var vars = context.getVars(); if (context.inline) {
varsSlot.setContent(builder.vars(createVarsArray(vars))); var createInlineMarkoTemplateVar = context.importModule('marko_createInlineTemplate', 'marko/runtime/inline');
return builder.functionCall(
createInlineMarkoTemplateVar,
[
builder.identifier('__filename'),
builder.functionDeclaration(
null,
[
builder.identifier('data'),
builder.identifierOut()
],
renderStatements)
]);
} else {
let createStatements = [];
let staticNodes = context.getStaticNodes();
if (staticNodes.length) {
createStatements = createStatements.concat(staticNodes);
}
let renderFunction = builder.functionDeclaration(
'render',
['data', builder.identifierOut()],
renderStatements);
createStatements.push(builder.returnStatement(renderFunction));
if (!templateExports) {
templateExports = builder.parseStatement('(module.exports = require("marko").c(__filename)).c(create)');
}
return builder.program([
builder.functionDeclaration(
'create',
[
context.helpersIdentifier
],
createStatements),
templateExports
]);
}
} }
toJSON(prettyPrinter) { toJSON(prettyPrinter) {

View File

@ -23,20 +23,16 @@ class Text extends Node {
} }
generateHtmlCode(codegen) { generateHtmlCode(codegen) {
var parentNode = this.parentNode; var context = codegen.context;
if (parentNode) {
parentNode._normalizeChildTextNodes(codegen);
}
var argument = this.argument; var argument = this.argument;
var escape = this.escape !== false; var escape = this.escape !== false;
if (argument instanceof Literal) { if (argument instanceof Literal) {
if (!argument.value) { if (!argument.value) {
return; return null;
} }
if (codegen.context.isFlagSet('SCRIPT_BODY')) { if (context.isFlagSet('SCRIPT_BODY')) {
escape = false; escape = false;
} }
@ -47,23 +43,23 @@ class Text extends Node {
let builder = codegen.builder; let builder = codegen.builder;
if (escape) { if (escape) {
let escapeFuncVar = 'escapeXml'; let escapeIdentifier = context.helper('escapeXml');
if (codegen.context.isFlagSet('SCRIPT_BODY')) { if (context.isFlagSet('SCRIPT_BODY')) {
escapeFuncVar = codegen.addStaticVar('escapeScript', '__helpers.xs'); escapeIdentifier = context.helper('escapeScript');
} }
// TODO Only escape the parts that need to be escaped if it is a compound expression with static // TODO Only escape the parts that need to be escaped if it is a compound expression with static
// text parts // text parts
argument = builder.functionCall( argument = builder.functionCall(
escapeFuncVar, escapeIdentifier,
[argument]); [argument]);
} else { } else {
argument = builder.functionCall(builder.identifier('str'), [ argument ]); argument = builder.functionCall(context.helper('str'), [ argument ]);
} }
} }
codegen.addWrite(argument); return codegen.builder.html(argument);
} }
isWhitespace() { isWhitespace() {

View File

@ -8,7 +8,11 @@ class ThisExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
codegen.write('this'); return this;
}
writeCode(writer) {
writer.write('this');
} }
toString() { toString() {

View File

@ -12,32 +12,37 @@ class UnaryExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.argument = codegen.generateCode(this.argument);
return this;
}
writeCode(writer) {
var argument = this.argument; var argument = this.argument;
var operator = this.operator; var operator = this.operator;
var prefix = this.prefix; var prefix = this.prefix;
if (prefix) { if (prefix) {
codegen.write(operator); writer.write(operator);
if (operator === 'typeof' || operator === 'delete') { if (operator === 'typeof' || operator === 'delete') {
codegen.write(' '); writer.write(' ');
} }
} }
var wrap = isCompoundExpression(argument); var wrap = isCompoundExpression(argument);
if (wrap) { if (wrap) {
codegen.write('('); writer.write('(');
} }
codegen.generateCode(argument); writer.write(argument);
if (wrap) { if (wrap) {
codegen.write(')'); writer.write(')');
} }
if (!prefix) { if (!prefix) {
codegen.write(operator); writer.write(operator);
} }
} }

View File

@ -12,28 +12,33 @@ class UpdateExpression extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.argument = codegen.generateCode(this.argument);
return this;
}
writeCode(writer) {
var argument = this.argument; var argument = this.argument;
var operator = this.operator; var operator = this.operator;
var prefix = this.prefix; var prefix = this.prefix;
if (prefix) { if (prefix) {
codegen.generateCode(operator); writer.write(operator);
} }
var wrap = isCompoundExpression(argument); var wrap = isCompoundExpression(argument);
if (wrap) { if (wrap) {
codegen.write('('); writer.write('(');
} }
codegen.generateCode(argument); writer.write(argument);
if (wrap) { if (wrap) {
codegen.write(')'); writer.write(')');
} }
if (!prefix) { if (!prefix) {
codegen.generateCode(operator); writer.write(operator);
} }
} }

View File

@ -23,6 +23,12 @@ class VariableDeclarator extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.id = codegen.generateCode(this.id);
this.init = codegen.generateCode(this.init);
return this;
}
writeCode(writer) {
var id = this.id; var id = this.id;
var init = this.init; var init = this.init;
@ -30,11 +36,11 @@ class VariableDeclarator extends Node {
throw new Error('Invalid variable name: ' + id); throw new Error('Invalid variable name: ' + id);
} }
codegen.generateCode(id); writer.write(id);
if (init != null) { if (init != null) {
codegen.write(' = '); writer.write(' = ');
codegen.generateCode(init); writer.write(init);
} }
} }

View File

@ -12,51 +12,53 @@ class Vars extends Node {
generateCode(codegen) { generateCode(codegen) {
var declarations = this.declarations; var declarations = this.declarations;
var kind = this.kind;
var isStatement = this.statement;
var body = this.body;
var hasBody = this.body && this.body.length; if (!declarations || !declarations.length) {
return null;
}
if(hasBody) { if (this.body && this.body.length) {
var scopedBody = [this].concat(this.body);
var scopedBody = [this].concat(this.body.items);
this.body = null; this.body = null;
return codegen.builder.selfInvokingFunction(scopedBody); return codegen.builder.selfInvokingFunction(scopedBody);
} }
return this;
}
writeCode(writer) {
var declarations = this.declarations;
var kind = this.kind;
var isStatement = this.statement;
if (!declarations || !declarations.length) { if (!declarations || !declarations.length) {
return; return;
} }
codegen.incIndent(4); writer.incIndent(4);
for (let i=0; i<declarations.length; i++) { for (let i=0; i<declarations.length; i++) {
var declarator = declarations[i]; var declarator = declarations[i];
if (i === 0) { if (i === 0) {
codegen.write(kind + ' '); writer.write(kind + ' ');
} else { } else {
codegen.writeLineIndent(); writer.writeLineIndent();
} }
codegen.generateCode(declarator); writer.write(declarator);
if (i < declarations.length - 1) { if (i < declarations.length - 1) {
codegen.write(',\n'); writer.write(',\n');
} else { } else {
if (isStatement) { if (isStatement) {
codegen.write(';\n'); writer.write(';\n');
} }
} }
} }
codegen.decIndent(4); writer.decIndent(4);
if (hasBody) {
codegen.generateCode(body);
}
} }
walk(walker) { walk(walker) {

View File

@ -10,16 +10,22 @@ class WhileStatement extends Node {
} }
generateCode(codegen) { generateCode(codegen) {
this.test = codegen.generateCode(this.test);
this.body = codegen.generateCode(this.body);
return this;
}
writeCode(writer) {
var test = this.test; var test = this.test;
var body = this.body; var body = this.body;
codegen.write('while ('); writer.write('while (');
codegen.generateCode(test); writer.write(test);
codegen.write(') '); writer.write(') ');
codegen.generateBlock(body); writer.write(body);
codegen.write('\n'); writer.write('\n');
} }
walk(walker) { walk(walker) {

View File

@ -8,6 +8,10 @@ var Builder = require('./Builder');
var extend = require('raptor-util/extend'); var extend = require('raptor-util/extend');
var CompileContext = require('./CompileContext'); var CompileContext = require('./CompileContext');
var globalConfig = require('./config'); var globalConfig = require('./config');
var CompileContext = require('./CompileContext');
var InlineCompiler = require('./InlineCompiler');
var ok = require('assert').ok;
var defaults = extend({}, globalConfig); var defaults = extend({}, globalConfig);
Object.defineProperty(exports, 'defaultOptions', { Object.defineProperty(exports, 'defaultOptions', {
@ -55,21 +59,53 @@ function createWalker(options) {
return new Walker(options); return new Walker(options);
} }
function compileFile(filename, options, callback) { function _compile(src, filename, userOptions, callback) {
var fs = req('fs'); ok(filename, '"filename" argument is required');
var compiler; ok(typeof filename === 'string', '"filename" argument should be a string');
var options = {};
extend(options, globalConfig);
if (userOptions) {
extend(options, userOptions);
}
var compiler = defaultCompiler;
var context = new CompileContext(src, filename, compiler.builder, options);
if (callback) {
let compiled;
try {
compiled = compiler.compile(src, context);
} catch(e) {
return callback(e);
}
callback(null, compiled.code);
} else {
let compiled = compiler.compile(src, context);
return compiled.code;
}
}
function compile(src, filename, options, callback) {
if (typeof options === 'function') { if (typeof options === 'function') {
callback = options; callback = options;
options = null; options = null;
} }
if (options) { return _compile(src, filename, options, callback);
compiler = options.compiler; }
}
if (!compiler) { function compileFile(filename, options, callback) {
compiler = defaultCompiler; var fs = req('fs');
if (typeof options === 'function') {
callback = options;
options = null;
} }
if (callback) { if (callback) {
@ -78,43 +114,27 @@ function compileFile(filename, options, callback) {
return callback(err); return callback(err);
} }
try { _compile(templateSrc, filename, options, callback);
callback(null, compiler.compile(templateSrc, filename, options));
} catch(e) {
callback(e);
}
}); });
} else { } else {
let templateSrc = fs.readFileSync(filename, {encoding: 'utf8'}); let templateSrc = fs.readFileSync(filename, {encoding: 'utf8'});
return compiler.compile(templateSrc, filename, options); return _compile(templateSrc, filename, options, callback);
} }
} }
function compile(src, filename, options, callback) { function createInlineCompiler(filename, userOptions) {
var compiler; var options = {};
if (typeof options === 'function') { extend(options, globalConfig);
callback = options;
options = null; if (userOptions) {
extend(options, userOptions);
} }
if (options) { var compiler = defaultCompiler;
compiler = options.compiler; var context = new CompileContext('', filename, compiler.builder, options);
}
if (!compiler) { return new InlineCompiler(context, compiler);
compiler = defaultCompiler;
}
if (callback) {
try {
callback(null, compiler.compile(src, filename, options));
} catch(e) {
callback(e);
}
} else {
return compiler.compile(src, filename, options);
}
} }
function checkUpToDate(templateFile, templateJsFile) { function checkUpToDate(templateFile, templateJsFile) {
@ -160,6 +180,7 @@ exports.createBuilder = createBuilder;
exports.compileFile = compileFile; exports.compileFile = compileFile;
exports.compile = compile; exports.compile = compile;
exports.parseRaw = parseRaw; exports.parseRaw = parseRaw;
exports.createInlineCompiler = createInlineCompiler;
exports.checkUpToDate = checkUpToDate; exports.checkUpToDate = checkUpToDate;
exports.getLastModified = getLastModified; exports.getLastModified = getLastModified;
@ -188,17 +209,4 @@ taglibLookup.registerTaglib(require.resolve('../taglibs/cache/marko.json'));
exports.registerTaglib = function(path) { exports.registerTaglib = function(path) {
taglibLookup.registerTaglib(path); taglibLookup.registerTaglib(path);
clearCaches(); clearCaches();
}; };
/*
exports.Taglib = require('./Taglib');
exports.lookup = require('./taglib-lookup');
exports.buildLookup = exports.lookup.buildLookup;
exports.registerTaglib = exports.lookup.registerTaglib;
exports.excludeDir = exports.lookup.excludeDir;
exports.clearCaches = function() {
exports.lookup.clearCaches();
require('./taglib-finder').clearCaches();
};
*/

View File

@ -5,11 +5,12 @@ The Marko compiler is responsible for taking an input Marko template and produci
# Compiler stages # Compiler stages
The three primary stages of the Marko compiler are parse, transform and generate: The four primary stages of the Marko compiler are parse, transform, generate and write:
- __parse__ - Parse the template source to produce an [Abstract Syntax Tree (AST)](https//en.wikipedia.org/wiki/Abstract_syntax_tree). - __parse__ - Parse the template source to produce an [Abstract Syntax Tree (AST)](https//en.wikipedia.org/wiki/Abstract_syntax_tree).
- __transform__ - Transform the AST (add/remove/modify/rearrange nodes) - __transform__ - Transform the AST using custom transforms (add/remove/modify/rearrange nodes)
- __generate__ - Generate compiled JavaScript code based on the final AST - __generate__ - Generate the final AST
- __write__ - Write the JavaScript code based on the final AST
Each of these stages is described in more detail in the sections below. Each of these stages is described in more detail in the sections below.
@ -143,35 +144,79 @@ Continuing with the previous example, after the transformation stage, the AST wi
} }
``` ```
You'll notice in the transformed AST that the `HtmlElement` associated with the `<div>` tag was wrapped with a new `If` node. After the AST has been transformed it is now time to generate the compiled JavaScript code. You'll notice in the transformed AST that the `HtmlElement` associated with the `<div>` tag was wrapped with a new `If` node.
During the transform stage, the entire AST might be walked multiple times. Not until there are no more nodes transformed does the transform stage complete. During the transform stage, the entire AST might be walked multiple times. Not until there are no more nodes transformed does the transform stage complete.
After the AST has been transformed it is now time to generate the final AST as part of the _generate_ stage.
## Generate stage ## Generate stage
The generate stage is the final stage of the Marko compiler. During the generate stage the Marko compiler will walk the tree to produce the final JavaScript code. Each node in the tree will have an opportunity to generate JavaScript code. The Marko compiler provides a [`CodeGenerator`](../compiler/CodeGenerator.js) class and an API for generating fragments of JavaScript code that makes it easy to produce well-formed and readable JavaScript code as output. After the AST has been transformed, the final AST consisting of only "final" AST nodes that are capable of writing out JavaScript code must be generated. A final AST node is a node that is capable of writing out JavaScript code and it must have a `writeCode(writer)` method. During this stage, `generateCode` is called on the root node and each node is responsible for recursively calling `generateCode` on children AST nodes to produce the final AST nodes.
Every node in the tree must implement one of the following methods: Every node in the tree must implement one of the following methods:
- `generateCode(generator)` - `generateCode(codegen) : Node`
- `generate<OUTPUT_TYPE>Code(generator)` (e.g. `generateHtmlCode(generator)`) - `generate<OUTPUT_TYPE>Code(codegen) : Node` (e.g. `generateHtmlCode(codegen)`)
The `generator` argument will be an instance of [`CodeGenerator`](../compiler/CodeGenerator.js). The `codegen` argument will be an instance of [`CodeGenerator`](../compiler/CodeGenerator.js).
The Marko compiler supports compiling templates differently based on an "output type". Currently, the only supported output type is "Html". With the "Html" output type, the compiled template will be a program that, when executed, will produce an HTML string as output. In the future we may support other output types such as DOM, Virtual DOM, incremental DOM, etc. For example, with the "DOM" output type, the compiled program could use the web browser's DOM API to produce a DOM tree as output (instead of an HTML string). The Marko compiler supports compiling templates differently based on an "output type". Currently, the only supported output type is "Html". With the "Html" output type, the compiled template will be a program that, when executed, will produce an HTML string as output. In the future we may support other output types such as DOM, Virtual DOM, incremental DOM, etc. For example, with the "DOM" output type, the compiled program could use the web browser's DOM API to produce a DOM tree as output (instead of an HTML string).
Below is the fragment of code used by the `ForStatement` node to generate the final AST node:
```javascript
generateCode(codegen) {
this.init = codegen.generateCode(this.init);
this.test = codegen.generateCode(this.test);
this.update = codegen.generateCode(this.update);
this.body = codegen.generateCode(this.body);
return this;
}
```
In the above example code, the node is returning itself as the final code. Alternatively, a node could return a completely different node that will automatically be made final:
```javascript
generateCode(codegen) {
let builder = codegen.builder;
return builder.functionCall(
builder.identifier('console'),
[
builder.literal('Hello World!')
]);
}
```
## Write stage
The write stage is the final stage of the Marko compiler. During the write state, each node will have an opportunity to write out JavaScript code to the final output buffer for the compiled template. The Marko compiler provides a [`CodeWriter`](../compiler/CodeWriter.js) class for writing out JavaScript primitives such as blocks and statements and it also provides support controlling indentation.
Every node in the tree must implement one of the following methods:
- `writeCode(writer)`
- `write<OUTPUT_TYPE>Code(writer)` (e.g. `writerHtmlCode(writer)`)
The `writer` argument will be an instance of [`CodeWriter`](../compiler/CodeWriter.js).
Below is the fragment of code used by the `If` node to generate the output JavaScript code: Below is the fragment of code used by the `If` node to generate the output JavaScript code:
```javascript ```javascript
generator.write('if ('); writeCode(writer) {
generator.generateCode(test); var test = this.test;
generator.write(') '); var body = this.body;
generator.generateBlock(body);
if (elseStatement) { writer.write('if (');
generator.write(' '); writer.write(test);
generator.generateCode(elseStatement); writer.write(') ');
} else { writer.writeBlock(body);
generator.write('\n'); if (this.else) {
writer.write(' else ');
writer.write(this.else);
} else {
writer.write('\n');
}
} }
``` ```

1
runtime/inline.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('./')._inline;

View File

@ -310,6 +310,10 @@ function load(templatePath, templateSrc, options) {
return template; return template;
} }
function createInlineMarkoTemplate(filename, renderFunc) {
return new Template(filename, renderFunc);
}
exports.load = load; exports.load = load;
exports.createWriter = function(writer) { exports.createWriter = function(writer) {
@ -320,6 +324,8 @@ exports.helpers = helpers;
exports.Template = Template; exports.Template = Template;
exports._inline = createInlineMarkoTemplate;
// The loader is used to load templates that have not already been // The loader is used to load templates that have not already been
// loaded and cached. On the server, the loader will use // loaded and cached. On the server, the loader will use
// the compiler to compile the template and then load the generated // the compiler to compile the template and then load the generated

View File

@ -52,7 +52,7 @@ module.exports = function transform(el, context) {
if (isObjectEmpty(arg)) { if (isObjectEmpty(arg)) {
arg = el.getAttributeValue('arg'); arg = el.getAttributeValue('arg');
} else { } else {
let mergeVar = context.addStaticVar('__merge', '__helpers.m'); let mergeVar = context.helper('merge');
arg = builder.functionCall(mergeVar, [ arg = builder.functionCall(mergeVar, [
builder.literal(arg), // Input props from the attributes take precedence builder.literal(arg), // Input props from the attributes take precedence
el.getAttributeValue('arg') el.getAttributeValue('arg')

View File

@ -58,7 +58,7 @@ module.exports = function codeGenerator(el, codegen) {
if (Object.keys(templateData).length === 0) { if (Object.keys(templateData).length === 0) {
templateData = args[1]; templateData = args[1];
} else { } else {
let mergeVar = codegen.addStaticVar('__merge', '__helpers.m'); let mergeVar = codegen.context.helper('merge');
templateData = builder.functionCall(mergeVar, [ templateData = builder.functionCall(mergeVar, [
builder.literal(templateData), // Input props from the attributes take precedence builder.literal(templateData), // Input props from the attributes take precedence
args[1] // The template data object is passed as the second argument: <include("./foo.marko", { ... })/> args[1] // The template data object is passed as the second argument: <include("./foo.marko", { ... })/>

3
test/.gitignore vendored
View File

@ -1,2 +1,3 @@
/node_modules /node_modules
/scratch.js /scratch.js
*.generated.js

View File

@ -1,3 +1,3 @@
out.w("<div class=\"foo" + out.w("<div class=\"foo" +
className + className +
"\">Hello World</div>"); "\">Hello World</div>")

View File

@ -1,7 +1,7 @@
if (true) { if (true) {
if (!(!data.url)) { if (!(!data.url)) {
out.w("<a" + out.w("<a" +
attr("href", data.url) + marko_attr("href", data.url) +
">"); ">");
} }

View File

@ -1,5 +1,6 @@
if (a > b) { if (a > b) {
before(); before();
out.w("<div class=\"greeting\">Hello World</div>"); out.w("<div class=\"greeting\">Hello World</div>");
after(); after();

View File

@ -2,7 +2,9 @@ if (a > b) {
var before; var before;
before(); before();
var foo; var foo;
after(); after();
var after; var after;

View File

@ -1,5 +1,7 @@
if (a > b) { if (a > b) {
before(); before();
var foo; var foo;
after(); after();
} }

View File

@ -1,4 +1,5 @@
before(); before();
out.w("<div class=\"greeting\">Hello World</div>"); out.w("<div class=\"greeting\">Hello World</div>");
after(); after();

View File

@ -21,5 +21,7 @@ module.exports = function(builder) {
event.insertCode(builder.functionCall('after', [])); event.insertCode(builder.functionCall('after', []));
}); });
return htmlElement; return builder.program([
htmlElement
]);
}; };

View File

@ -1,9 +1,5 @@
function create(__helpers) { function create(__markoHelpers) {
var foo = "Hello World", var foo = "Hello World";
str = __helpers.s,
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) { return function render(data, out) {
out.w("<div></div>"); out.w("<div></div>");

View File

@ -1,9 +1,4 @@
function create(__helpers) { function create(__markoHelpers) {
var str = __helpers.s,
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) { return function render(data, out) {
var foo = "Hello World"; var foo = "Hello World";

View File

@ -1,8 +1,5 @@
function create(__helpers) { function create(__markoHelpers) {
var str = __helpers.s, var marko_escapeXml = __markoHelpers.x;
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) { return function render(data, out) {
out.w("<ul>"); out.w("<ul>");
@ -11,7 +8,7 @@ function create(__helpers) {
foo(); foo();
out.w("<li>" + out.w("<li>" +
escapeXml(color) + marko_escapeXml(color) +
"</li>"); "</li>");
bar(); bar();

View File

@ -0,0 +1,9 @@
function create(__markoHelpers) {
var foo = "Hello World";
return function render(data, out) {
out.w("<div></div>");
};
}
(module.exports = require("marko").c(__filename)).c(create);

View File

@ -0,0 +1,15 @@
'use strict';
module.exports = function(builder, codegen) {
var templateRoot = builder.templateRoot([
builder.htmlElement(
'div',
[])
]);
codegen.context.addStaticVar('foo', builder.literal('Hello World'));
codegen.context.addStaticVar('foo', builder.literal('Hello World'));
return templateRoot;
};

View File

@ -1,9 +1,5 @@
function create(__helpers) { function create(__markoHelpers) {
var foo = "Hello World", var foo = "Hello World";
str = __helpers.s,
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) { return function render(data, out) {
out.w("<div></div>"); out.w("<div></div>");

View File

@ -0,0 +1,13 @@
function create(__markoHelpers) {
var fooStatic = "Hello Foo",
barStatic = "Hello Bar";
return function render(data, out) {
var foo = "Hello Foo",
bar = "Hello Bar";
out.w("<div></div>");
};
}
(module.exports = require("marko").c(__filename)).c(create);

View File

@ -0,0 +1,18 @@
'use strict';
module.exports = function(builder, codegen) {
var templateRoot = builder.templateRoot([
builder.htmlElement(
'div',
[])
]);
codegen.context.addVar('foo', builder.literal('Hello Foo'));
codegen.context.addVar('bar', builder.literal('Hello Bar'));
codegen.context.addStaticVar('fooStatic', builder.literal('Hello Foo'));
codegen.context.addStaticVar('barStatic', builder.literal('Hello Bar'));
return templateRoot;
};

View File

@ -1,9 +1,4 @@
function create(__helpers) { function create(__markoHelpers) {
var str = __helpers.s,
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) { return function render(data, out) {
var foo = "Hello World"; var foo = "Hello World";

View File

@ -1,13 +1,10 @@
function create(__helpers) { function create(__markoHelpers) {
var str = __helpers.s, var marko_forEach = __markoHelpers.f,
empty = __helpers.e, marko_escapeXml = __markoHelpers.x;
notEmpty = __helpers.ne,
escapeXml = __helpers.x,
forEach = __helpers.f;
return function render(data, out) { return function render(data, out) {
forEach(data.colors, function(color) { marko_forEach(data.colors, function(color) {
out.w(escapeXml(color)); out.w(marko_escapeXml(color));
}); });
}; };
} }

View File

@ -1,12 +1,8 @@
function create(__helpers) { function create(__markoHelpers) {
var str = __helpers.s, var marko_forEachProp = __markoHelpers.fp;
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x,
forEachProp = __helpers.fp;
return function render(data, out) { return function render(data, out) {
forEachProp(myObject, function(k, v) { marko_forEachProp(myObject, function(k, v) {
console.log("k:", k, "v:", v); console.log("k:", k, "v:", v);
}); });
}; };

View File

@ -1,15 +1,10 @@
function create(__helpers) { function create(__markoHelpers) {
var str = __helpers.s,
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) { return function render(data, out) {
(function() { (function() {
for (var i = 0; i <= myArray.length; i += 2) { for (var i = 0; i <= myArray.length; i += 2) {
console.log(i); console.log(i);
} }
}()); })();
}; };
} }

View File

@ -1 +1 @@
out.w("<div>Hello World</div>"); out.w("<div>Hello World</div>")

View File

@ -1 +1 @@
out.w("<!--This is an HTML comment-->"); out.w("<!--This is an HTML comment-->")

View File

@ -1,3 +1,3 @@
out.w("<div class=\"foo" + out.w("<div class=\"foo" +
className + className +
"\">Hello World</div>"); "\">Hello World</div>")

View File

@ -1,3 +1,3 @@
out.w("<div class=\"greeting\"" + out.w("<div class=\"greeting\"" +
attr("foo", bar) + marko_attr("foo", bar) +
">Hello World</div>"); ">Hello World</div>")

View File

@ -2,4 +2,4 @@ out.w("<" +
data.tagName + data.tagName +
" class=\"greeting\">Hello World</" + " class=\"greeting\">Hello World</" +
data.tagName + data.tagName +
">"); ">")

View File

@ -1 +1 @@
out.w("<div class=\"greeting\">Hello World</div>"); out.w("<div class=\"greeting\">Hello World</div>")

View File

@ -1,9 +1,4 @@
function create(__helpers) { function create(__markoHelpers) {
var str = __helpers.s,
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) { return function render(data, out) {
if (true) { if (true) {
out.w("A"); out.w("A");

View File

@ -1,6 +1,6 @@
function macro_greeting(name, age, out, renderBody) { function macro_greeting(name, age, out, renderBody) {
out.w("Hello " + out.w("Hello " +
escapeXml(name)); marko_escapeXml(name));
} }
macro_greeting("Frank", 10, out, function renderBody(out) { macro_greeting("Frank", 10, out, function renderBody(out) {

View File

@ -1,6 +1,6 @@
function macro_greeting(name, age, out, renderBody) { function macro_greeting(name, age, out, renderBody) {
out.w("Hello " + out.w("Hello " +
escapeXml(name)); marko_escapeXml(name));
} }
macro_greeting("Frank", 10, out); macro_greeting("Frank", 10, out);

View File

@ -1,6 +1,6 @@
function macro_greeting(name, age, out, renderBody) { function macro_greeting(name, age, out, renderBody) {
out.w("Hello " + out.w("Hello " +
escapeXml(name)); marko_escapeXml(name));
} }
macro_greeting("Frank", 10, out, function renderBody(out) { macro_greeting("Frank", 10, out, function renderBody(out) {

View File

@ -1,6 +1,6 @@
function macro_greeting(name, age, out, renderBody) { function macro_greeting(name, age, out, renderBody) {
out.w("Hello " + out.w("Hello " +
escapeXml(name)); marko_escapeXml(name));
} }
macro_greeting("Frank", 10, out); macro_greeting("Frank", 10, out);

View File

@ -1,4 +1,4 @@
function macro_greeting(name, age, out, renderBody) { function macro_greeting(name, age, out, renderBody) {
out.w("Hello " + out.w("Hello " +
escapeXml(name)); marko_escapeXml(name));
} }

View File

@ -1,8 +1,5 @@
function create(__helpers) { function create(__markoHelpers) {
var str = __helpers.s, var marko_escapeXml = __markoHelpers.x;
empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x;
return function render(data, out) { return function render(data, out) {
out.w("Hello" + out.w("Hello" +
@ -14,7 +11,7 @@ function create(__helpers) {
forEach(data.colors, function(color) { forEach(data.colors, function(color) {
out.w("<li class=\"color\">" + out.w("<li class=\"color\">" +
escapeXml(color) + marko_escapeXml(color) +
"</li>"); "</li>");
}); });

View File

@ -17,8 +17,8 @@ module.exports = function(builder) {
div.moveChildrenTo(span); div.moveChildrenTo(span);
return [ return builder.program([
div, div,
span span
]; ]);
}; };

View File

@ -1,8 +1,10 @@
'use strict'; 'use strict';
module.exports = function(builder) { module.exports = function(builder) {
return builder.node(function(node, codegen) { return builder.program([
var builder = codegen.builder; builder.node(function(node, codegen) {
return builder.text(builder.literal('Hello World!')); var builder = codegen.builder;
}); return builder.text(builder.literal('Hello World!'));
})
]);
}; };

View File

@ -12,5 +12,5 @@ module.exports = function(builder) {
div.removeAllAttributes(); div.removeAllAttributes();
return div; return builder.program([div]);
}; };

View File

@ -2,4 +2,4 @@
var foo; var foo;
foo = "bar"; foo = "bar";
}()) })()

View File

@ -2,4 +2,4 @@
var foo; var foo;
foo = "bar"; foo = "bar";
}()) })()

View File

@ -1,3 +1,3 @@
(function(win) { (function(win) {
win.foo = "bar"; win.foo = "bar";
}(window)) })(window)

View File

@ -1,6 +0,0 @@
a = "abc";
var foo = "abc",
bar = 123;
b = "def";

View File

@ -1,25 +0,0 @@
'use strict';
module.exports = function(builder) {
var vars = [];
return builder.program([
builder.assignment('a', builder.literal('abc')),
builder.slot((slot, codegen) => {
slot.setContent(codegen.builder.vars(vars));
}),
builder.node(function(node, codegen) {
vars.push({
id: 'foo',
init: codegen.builder.literal('abc')
});
}),
builder.node(function(node, codegen) {
vars.push({
id: 'bar',
init: codegen.builder.literal(123)
});
}),
builder.assignment('b', builder.literal('def'))
]);
};

View File

@ -1,8 +0,0 @@
if (true) {
out.w("BEFORE - Hello World");
var foo = "abc",
bar = 123;
out.w("AFTER - Hello World");
}

Some files were not shown because too many files have changed in this diff Show More