mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
Various cleanup
This commit is contained in:
parent
eb77d3265d
commit
d621ef13df
0
bin/markoc
Normal file → Executable file
0
bin/markoc
Normal file → Executable file
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
var isArray = Array.isArray;
|
||||
|
||||
var Node = require('./ast/Node');
|
||||
var Program = require('./ast/Program');
|
||||
var TemplateRoot = require('./ast/TemplateRoot');
|
||||
var FunctionDeclaration = require('./ast/FunctionDeclaration');
|
||||
@ -15,19 +16,24 @@ var BinaryExpression = require('./ast/BinaryExpression');
|
||||
var Vars = require('./ast/Vars');
|
||||
var Return = require('./ast/Return');
|
||||
var HtmlElement = require('./ast/HtmlElement');
|
||||
var HtmlOutput = require('./ast/HtmlOutput');
|
||||
var TextOutput = require('./ast/TextOutput');
|
||||
var Html = require('./ast/Html');
|
||||
var Text = require('./ast/Text');
|
||||
var ForEach = require('./ast/ForEach');
|
||||
var Slot = require('./ast/Slot');
|
||||
var HtmlComment = require('./ast/HtmlComment');
|
||||
var SelfInvokingFunction = require('./ast/SelfInvokingFunction');
|
||||
var ForStatement = require('./ast/ForStatement');
|
||||
var BinaryExpression = require('./ast/BinaryExpression');
|
||||
|
||||
class Builder {
|
||||
program(body) {
|
||||
return new Program({body});
|
||||
}
|
||||
|
||||
node(type) {
|
||||
return new Node(type);
|
||||
}
|
||||
|
||||
templateRoot(body) {
|
||||
return new TemplateRoot({body});
|
||||
}
|
||||
@ -109,12 +115,12 @@ class Builder {
|
||||
return new HtmlElement({tagName, attributes, body, argument});
|
||||
}
|
||||
|
||||
htmlOutput(argument) {
|
||||
return new HtmlOutput({argument});
|
||||
html(argument) {
|
||||
return new Html({argument});
|
||||
}
|
||||
|
||||
textOutput(argument, escape) {
|
||||
return new TextOutput({argument, escape});
|
||||
text(argument, escape) {
|
||||
return new Text({argument, escape});
|
||||
}
|
||||
|
||||
htmlComment(comment) {
|
||||
@ -148,6 +154,10 @@ class Builder {
|
||||
return new ForStatement({init, test, update, body});
|
||||
}
|
||||
}
|
||||
|
||||
binaryExpression(left, operator, right) {
|
||||
return new BinaryExpression({left, operator, right});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Builder;
|
||||
@ -86,7 +86,7 @@ class Generator {
|
||||
|
||||
ok(this.builder, '"this.builder" is required');
|
||||
|
||||
this._generatorCodeFuncName = 'generate' +
|
||||
this._generatorCodeMethodName = 'generate' +
|
||||
this.outputType.charAt(0).toUpperCase() +
|
||||
this.outputType.substring(1) +
|
||||
'Code';
|
||||
@ -121,25 +121,48 @@ class Generator {
|
||||
return;
|
||||
}
|
||||
|
||||
var generateCodeFunc = node.generateCode;
|
||||
if (!generateCodeFunc) {
|
||||
generateCodeFunc = node[this._generatorCodeFuncName];
|
||||
|
||||
if (!generateCodeFunc) {
|
||||
throw new Error('Missing generator method for node of type "' +
|
||||
node.type +
|
||||
'". Node: ' + util.inspect(node));
|
||||
}
|
||||
}
|
||||
|
||||
var oldCurrentNode = this._currentNode;
|
||||
this._currentNode = node;
|
||||
var resultNode = generateCodeFunc.call(node, this);
|
||||
if (resultNode) {
|
||||
// The generateCode function can optionally return either of the following:
|
||||
// - An AST node
|
||||
// - An array/cointainer of AST nodes
|
||||
this.generateCode(resultNode);
|
||||
|
||||
|
||||
var finalNode;
|
||||
|
||||
if (node.getCodeGenerator) {
|
||||
let generateCodeFunc = node.getCodeGenerator(this.outputType);
|
||||
if (generateCodeFunc) {
|
||||
finalNode = generateCodeFunc(node, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (finalNode && finalNode !== node) {
|
||||
this.generateCode(finalNode);
|
||||
} else {
|
||||
let generateCodeMethod = node.generateCode;
|
||||
|
||||
if (!generateCodeMethod) {
|
||||
generateCodeMethod = node[this._generatorCodeMethodName];
|
||||
|
||||
if (!generateCodeMethod) {
|
||||
throw new Error('Missing code for node of type "' +
|
||||
node.type +
|
||||
'" (output type: "' + this.outputType + '"). Node: ' + util.inspect(node));
|
||||
}
|
||||
}
|
||||
|
||||
finalNode = generateCodeMethod.call(node, this);
|
||||
if (finalNode != null) {
|
||||
if (finalNode === node) {
|
||||
throw new Error('Invalid node returned. Same node returned: ' + util.inspect(node));
|
||||
}
|
||||
|
||||
// The generateCode function can optionally return either of the following:
|
||||
// - An AST node
|
||||
// - An array/cointainer of AST nodes
|
||||
this.generateCode(finalNode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._currentNode = oldCurrentNode;
|
||||
|
||||
@ -77,7 +77,8 @@ class Compiler {
|
||||
var context = new CompileContext(src, filename, this.builder);
|
||||
var ast = this.parser.parse(src, context);
|
||||
|
||||
// console.log('ROOT', JSON.stringify(ast, null, 2));
|
||||
console.log('ROOT', JSON.stringify(ast, null, 2));
|
||||
|
||||
var transformedAST = transformTree(ast, context);
|
||||
// console.log('transformedAST', JSON.stringify(ast, null, 2));
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
var ok = require('assert').ok;
|
||||
var path = require('path');
|
||||
var Node = require('./ast/Node');
|
||||
|
||||
var COMPILER_ATTRIBUTE_HANDLERS = {
|
||||
whitespace: function(attr, compilerOptions) {
|
||||
@ -83,7 +84,7 @@ class Parser {
|
||||
if (this.prevTextNode && this.prevTextNode.isLiteral()) {
|
||||
this.prevTextNode.appendText(text);
|
||||
} else {
|
||||
this.prevTextNode = builder.textOutput(builder.literal(text));
|
||||
this.prevTextNode = builder.text(builder.literal(text));
|
||||
this.prevTextNode.pos = text.pos;
|
||||
this.parentNode.appendChild(this.prevTextNode);
|
||||
}
|
||||
@ -152,6 +153,9 @@ class Parser {
|
||||
var nodeFactoryFunc = tagDef.getNodeFactory();
|
||||
if (nodeFactoryFunc) {
|
||||
node = nodeFactoryFunc(elNode, context);
|
||||
if (!(node instanceof Node)) {
|
||||
throw new Error('Invalid node returned from node factory for tag "' + tagName + '".');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,8 +251,8 @@ class Parser {
|
||||
|
||||
var builder = this.context.builder;
|
||||
|
||||
var textOutput = builder.textOutput(expression, escape);
|
||||
this.parentNode.appendChild(textOutput);
|
||||
var text = builder.text(expression, escape);
|
||||
this.parentNode.appendChild(text);
|
||||
}
|
||||
|
||||
get parentNode() {
|
||||
|
||||
@ -6,18 +6,46 @@ class BinaryExpression extends Node {
|
||||
constructor(def) {
|
||||
super('BinaryExpression');
|
||||
this.left = def.left;
|
||||
this.right = def.right;
|
||||
this.operator = def.operator;
|
||||
this.right = def.right;
|
||||
this.parens = def.parens === true;
|
||||
}
|
||||
|
||||
generateCode(generator) {
|
||||
var left = this.left;
|
||||
var right = this.right;
|
||||
var operator = this.operator;
|
||||
var parens = this.parens || this.data.isSubExpression;
|
||||
|
||||
generator.generateCode(left);
|
||||
generator.write(' ' + operator + ' ');
|
||||
generator.generateCode(right);
|
||||
if (left instanceof Node) {
|
||||
left.data.isSubExpression = true;
|
||||
}
|
||||
|
||||
if (right instanceof Node) {
|
||||
right.data.isSubExpression = true;
|
||||
}
|
||||
|
||||
if (parens) {
|
||||
generator.write('(');
|
||||
}
|
||||
|
||||
generator.generateCode(this.left);
|
||||
generator.write(' ');
|
||||
generator.generateCode(this.operator);
|
||||
generator.write(' ');
|
||||
generator.generateCode(this.right);
|
||||
|
||||
if (parens) {
|
||||
generator.write(')');
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: 'BinaryExpression',
|
||||
left: this.left,
|
||||
operator: this.operator,
|
||||
right: this.right
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -77,7 +77,7 @@ class ForEach extends Node {
|
||||
if (separator) {
|
||||
body = body.items.concat([
|
||||
builder.ifStatement('!' + statusVarName + '.isLast()', [
|
||||
builder.textOutput(separator)
|
||||
builder.text(separator)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
var Node = require('./Node');
|
||||
|
||||
class HtmlOutput extends Node {
|
||||
class Html extends Node {
|
||||
constructor(def) {
|
||||
super('HtmlOutput');
|
||||
super('Html');
|
||||
this.argument = def.argument;
|
||||
}
|
||||
|
||||
@ -19,4 +19,4 @@ class HtmlOutput extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HtmlOutput;
|
||||
module.exports = Html;
|
||||
@ -112,6 +112,18 @@ class HtmlAttributeCollection {
|
||||
return this.lookup[name];
|
||||
}
|
||||
|
||||
setAttributeValue(name, value) {
|
||||
var attr = this.getAttribute(name);
|
||||
if (attr) {
|
||||
attr.value = value;
|
||||
} else {
|
||||
this.addAttribute({
|
||||
name: name,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getAttributes() {
|
||||
return this.all;
|
||||
}
|
||||
|
||||
@ -122,6 +122,10 @@ class HtmlElement extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
setAttributeValue(name, value) {
|
||||
this._attributes.setAttributeValue(name, value);
|
||||
}
|
||||
|
||||
removeAttribute(name) {
|
||||
if (this._attributes) {
|
||||
this._attributes.removeAttribute(name);
|
||||
@ -152,6 +156,15 @@ class HtmlElement extends Node {
|
||||
var tagName = this.tagName;
|
||||
return '<' + tagName + '>';
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
type: this.type,
|
||||
tagName: this.tagName,
|
||||
attributes: this._attributes,
|
||||
argument: this.argument
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HtmlElement;
|
||||
@ -45,7 +45,7 @@ class If extends Node {
|
||||
previous = curNode;
|
||||
curNode.matched = true;
|
||||
return true; // Keep searching since they may be more ElseIf/Else nodes...
|
||||
} else if (curNode.type === 'TextOutput') {
|
||||
} else if (curNode.type === 'Text') {
|
||||
if (curNode.isWhitespace()) {
|
||||
whitespaceNodes.push(curNode);
|
||||
return true; // Just whitespace... keep searching
|
||||
|
||||
@ -11,7 +11,10 @@ class Node {
|
||||
this.statement = false;
|
||||
this.container = null;
|
||||
this.pos = null; // The character index of the node in the original source file
|
||||
this.transformersApplied = {};
|
||||
this._codeGeneratorFuncs = null;
|
||||
this._flags = {};
|
||||
this._transformersApplied = {};
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
wrap(wrapperNode) {
|
||||
@ -49,11 +52,11 @@ class Node {
|
||||
}
|
||||
|
||||
isTransformerApplied(transformer) {
|
||||
return this.transformersApplied[transformer.id] === true;
|
||||
return this._transformersApplied[transformer.id] === true;
|
||||
}
|
||||
|
||||
setTransformerApplied(transformer) {
|
||||
this.transformersApplied[transformer.id] = true;
|
||||
this._transformersApplied[transformer.id] = true;
|
||||
}
|
||||
|
||||
toString() {
|
||||
@ -65,7 +68,10 @@ class Node {
|
||||
delete result.container;
|
||||
delete result.statement;
|
||||
delete result.pos;
|
||||
delete result.transformersApplied;
|
||||
delete result._transformersApplied;
|
||||
delete result._codeGeneratorFuncs;
|
||||
delete result._flags;
|
||||
delete result.data;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -87,6 +93,46 @@ class Node {
|
||||
// We inspect in the simplified version of this object t
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
setType(newType) {
|
||||
this.type = newType;
|
||||
}
|
||||
|
||||
setCodeGenerator(mode, codeGeneratorFunc) {
|
||||
if (arguments.length === 1) {
|
||||
codeGeneratorFunc = arguments[0];
|
||||
mode = null;
|
||||
}
|
||||
|
||||
if (!this._codeGeneratorFuncs) {
|
||||
this._codeGeneratorFuncs = {};
|
||||
}
|
||||
this._codeGeneratorFuncs[mode || 'DEFAULT'] = codeGeneratorFunc;
|
||||
}
|
||||
|
||||
getCodeGenerator(mode) {
|
||||
if (this._codeGeneratorFuncs) {
|
||||
return this._codeGeneratorFuncs[mode] || this._codeGeneratorFuncs.DEFAULT;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
setFlag(name) {
|
||||
this._flags[name] = true;
|
||||
}
|
||||
|
||||
clearFlag(name) {
|
||||
delete this._flags[name];
|
||||
}
|
||||
|
||||
isFlagSet(name) {
|
||||
return this._flags.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
get parentNode() {
|
||||
return this.container && this.container.node;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Node;
|
||||
@ -3,10 +3,10 @@
|
||||
var Node = require('./Node');
|
||||
var Literal = require('./Literal');
|
||||
|
||||
function trim(textOutputNode) {
|
||||
var text = textOutputNode.argument.value;
|
||||
var isFirst = textOutputNode.isFirst;
|
||||
var isLast = textOutputNode.isLast;
|
||||
function trim(textNode) {
|
||||
var text = textNode.argument.value;
|
||||
var isFirst = textNode.isFirst;
|
||||
var isLast = textNode.isLast;
|
||||
|
||||
if (isFirst) {
|
||||
//First child
|
||||
@ -21,12 +21,12 @@ function trim(textOutputNode) {
|
||||
text = '';
|
||||
}
|
||||
text = text.replace(/\s+/g, ' ');
|
||||
textOutputNode.argument.value = text;
|
||||
textNode.argument.value = text;
|
||||
}
|
||||
|
||||
class TextOutput extends Node {
|
||||
class Text extends Node {
|
||||
constructor(def) {
|
||||
super('TextOutput');
|
||||
super('Text');
|
||||
this.argument = def.argument;
|
||||
this.escape = def.escape;
|
||||
this.normalized = false;
|
||||
@ -80,11 +80,11 @@ class TextOutput extends Node {
|
||||
return;
|
||||
}
|
||||
|
||||
if (curChild.type === 'TextOutput') {
|
||||
if (curChild.type === 'Text') {
|
||||
curChild.normalized = true;
|
||||
}
|
||||
|
||||
if (curChild.type === 'TextOutput' && curChild.isLiteral()) {
|
||||
if (curChild.type === 'Text' && curChild.isLiteral()) {
|
||||
if (currentTextLiteral) {
|
||||
currentTextLiteral.argument.value += curChild.argument.value;
|
||||
curChild.detach();
|
||||
@ -121,7 +121,7 @@ class TextOutput extends Node {
|
||||
|
||||
appendText(text) {
|
||||
if (!this.isLiteral()) {
|
||||
throw new Error('Text cannot be appended to a non-literal TextOutput node');
|
||||
throw new Error('Text cannot be appended to a non-literal Text node');
|
||||
}
|
||||
|
||||
this.argument.value += text;
|
||||
@ -135,4 +135,4 @@ class TextOutput extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextOutput;
|
||||
module.exports = Text;
|
||||
@ -15,9 +15,7 @@
|
||||
*/
|
||||
'use strict';
|
||||
var forEachEntry = require('raptor-util/forEachEntry');
|
||||
var extend = require('raptor-util/extend');
|
||||
var ok = require('assert').ok;
|
||||
var HtmlElement = require('../../ast/HtmlElement');
|
||||
var CustomTag = require('../../ast/CustomTag');
|
||||
|
||||
function inheritProps(sub, sup) {
|
||||
@ -28,25 +26,6 @@ function inheritProps(sub, sup) {
|
||||
});
|
||||
}
|
||||
|
||||
function createNodeFactory(codeGenerator, typeName) {
|
||||
class CustomNode extends HtmlElement {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.type = typeName;
|
||||
|
||||
if (this.init) {
|
||||
this.init(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extend(CustomNode.prototype, codeGenerator);
|
||||
|
||||
return function nodeFactory(el) {
|
||||
return new CustomNode(el);
|
||||
};
|
||||
}
|
||||
|
||||
function createCustomTagNodeFactory(tag) {
|
||||
return function nodeFactory(el) {
|
||||
return new CustomTag(el, tag);
|
||||
@ -224,9 +203,15 @@ class Tag{
|
||||
return nodeFactory;
|
||||
}
|
||||
|
||||
let codeGeneratorModulePath = this.codeGeneratorModulePath;
|
||||
|
||||
if (this.codeGeneratorModulePath) {
|
||||
var loadedCodeGeneratorModule = require(this.codeGeneratorModulePath);
|
||||
nodeFactory = createNodeFactory(loadedCodeGeneratorModule, this.codeGeneratorModulePath);
|
||||
var loadedCodeGenerator = require(this.codeGeneratorModulePath);
|
||||
nodeFactory = function(elNode) {
|
||||
elNode.setType(codeGeneratorModulePath);
|
||||
elNode.setCodeGenerator(loadedCodeGenerator);
|
||||
return elNode;
|
||||
};
|
||||
} else if (this.nodeFactoryPath) {
|
||||
nodeFactory = require(this.nodeFactoryPath);
|
||||
if (typeof nodeFactory !== 'function') {
|
||||
@ -240,6 +225,10 @@ class Tag{
|
||||
|
||||
return (this._nodeFactory = nodeFactory);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Tag;
|
||||
@ -1,12 +1,12 @@
|
||||
/*
|
||||
* Copyright 2011 eBay Software Foundation
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -71,6 +71,8 @@ module.exports = function scanTagsDir(tagsConfigPath, tagsConfigDirname, dir, ta
|
||||
var rendererFile = nodePath.join(dir, childFilename, 'renderer.js');
|
||||
var indexFile = nodePath.join(dir, childFilename, 'index.js');
|
||||
var templateFile = nodePath.join(dir, childFilename, 'template.marko');
|
||||
var codeGeneratorFile = nodePath.join(dir, childFilename, 'code-generator.js');
|
||||
var nodeFactoryFile = nodePath.join(dir, childFilename, 'node-factory.js');
|
||||
var tagDef = null;
|
||||
|
||||
// Record dependencies so that we can check if a template is up-to-date
|
||||
@ -86,7 +88,7 @@ module.exports = function scanTagsDir(tagsConfigPath, tagsConfigDirname, dir, ta
|
||||
throw new Error('Unable to parse JSON file at path "' + tagFile + '". Error: ' + e);
|
||||
}
|
||||
|
||||
if (!tagDef.renderer && !tagDef.template) {
|
||||
if (!tagDef.renderer && !tagDef.template && !tagDef['code-generator'] && !tagDef['node-factory']) {
|
||||
if (fs.existsSync(rendererFile)) {
|
||||
tagDef.renderer = rendererFile;
|
||||
} else if (fs.existsSync(indexFile)) {
|
||||
@ -95,8 +97,12 @@ module.exports = function scanTagsDir(tagsConfigPath, tagsConfigDirname, dir, ta
|
||||
tagDef.template = templateFile;
|
||||
} else if (fs.existsSync(templateFile + ".html")) {
|
||||
tagDef.template = templateFile + ".html";
|
||||
} else if (fs.existsSync(codeGeneratorFile)) {
|
||||
tagDef['code-generator'] = codeGeneratorFile;
|
||||
} else if (fs.existsSync(nodeFactoryFile)) {
|
||||
tagDef['node-factory'] = nodeFactoryFile;
|
||||
} else {
|
||||
throw new Error('Invalid tag file: ' + tagFile + '. Neither a renderer or a template was found for tag.');
|
||||
throw new Error('Invalid tag file: ' + tagFile + '. Neither a renderer or a template was found for tag. ' + JSON.stringify(tagDef, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ var ok = require('assert').ok;
|
||||
var Taglib = require('../taglib-loader/Taglib');
|
||||
var extend = require('raptor-util/extend');
|
||||
var HtmlElement = require('../ast/HtmlElement');
|
||||
var TextOutput = require('../ast/TextOutput');
|
||||
var Text = require('../ast/Text');
|
||||
|
||||
function transformerComparator(a, b) {
|
||||
a = a.priority;
|
||||
@ -223,7 +223,7 @@ class TaglibLookup {
|
||||
*/
|
||||
if (node instanceof HtmlElement) {
|
||||
this.forEachTagTransformer(node, callback, thisObj);
|
||||
} else if (node instanceof TextOutput) {
|
||||
} else if (node instanceof Text) {
|
||||
this.forEachTextTransformer(callback, thisObj);
|
||||
}
|
||||
}
|
||||
|
||||
66
docs/compile-time-tags.md
Normal file
66
docs/compile-time-tags.md
Normal file
@ -0,0 +1,66 @@
|
||||
Compile-time Tags
|
||||
=================
|
||||
|
||||
Marko allows developers to control how a custom tag generates JavaScript code at compile-time. A custom compile-time tag developer can provide a "code generator" function for a custom tag.
|
||||
|
||||
# Custom tag code generator
|
||||
|
||||
Let's take a look at how to create a compile-time tag by providing our own code generator. The first step is to register the custom tag in a `marko-taglib.json` file. We will use the `code-generator` property to associate the custom tag with a function that will be used to generate the code for the element node at compile-time:
|
||||
|
||||
```json
|
||||
{
|
||||
"<greeting>": {
|
||||
"code-generator": "./greeting-tag"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A code generator module should export a function with the following signature:
|
||||
|
||||
```javascript
|
||||
function generateCode(elNode, generator)
|
||||
```
|
||||
|
||||
Continuing with the `<greeting>`, let's assume we have the following template:
|
||||
|
||||
```xml
|
||||
<greeting name="Frank"/>
|
||||
```
|
||||
|
||||
Let's implement the greeting tag that generates code by return an array of output AST nodes.
|
||||
|
||||
```javascript
|
||||
module.exports = function generateCode(elNode, generator) {
|
||||
var builder = generator.builder;
|
||||
|
||||
var nameValue = elNode.getAttributeValue('name');
|
||||
return builder.functionCall('console.log', nameValue);
|
||||
};
|
||||
```
|
||||
|
||||
The above code results in the following compiled code:
|
||||
|
||||
```javascript
|
||||
out.w("Hello FrankHello " +
|
||||
escapeXml(data.name));
|
||||
```
|
||||
|
||||
|
||||
```javascript
|
||||
module.exports = function generateCode(elNode, generator) {
|
||||
var builder = generator.builder;
|
||||
return [
|
||||
builder.text(builder.literal('Hello Frank')),
|
||||
builder.text(elNode.getAttributeValue('name'))
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
So when should you a custom compile-time tag and when should use use a compile-time transformer?
|
||||
|
||||
Utilize custom compile-time tags when you need to create new custom tags that are capable of generating JavaScript code at compile-time. Examples of custom compile-time tags:
|
||||
|
||||
-
|
||||
|
||||
Compile-time transformers are useful for mod
|
||||
140
docs/compiler-advanced.md
Normal file
140
docs/compiler-advanced.md
Normal file
@ -0,0 +1,140 @@
|
||||
Compiler Advanced
|
||||
====================
|
||||
|
||||
Marko allows developers to control how templates generate JavaScript code at compile-time. Developers can create custom compile-time tags and it is also possible to transform the intermediate parse tree by adding, removing, modifying or rearranging nodes at compilation time.
|
||||
|
||||
# Overview
|
||||
|
||||
The three primary stages of the Marko compiler are parse, transform, generate. Each of these stages is described in more detail below:
|
||||
|
||||
## Parse stage
|
||||
|
||||
The first stage of the Marko compiler takes the source template string and produces an Abstract Syntax Tree (AST).
|
||||
|
||||
For example, given the following input template:
|
||||
|
||||
```xml
|
||||
Hello ${data.name}!
|
||||
|
||||
<ul if(notEmpty(data.colors))>
|
||||
<li for(color in data.colors)>
|
||||
${color}
|
||||
</li>
|
||||
</ul>
|
||||
<div else>
|
||||
No colors!
|
||||
</div>
|
||||
```
|
||||
|
||||
The following AST will be produced:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "TemplateRoot",
|
||||
"body": [
|
||||
{
|
||||
"type": "Text",
|
||||
"argument": {
|
||||
"type": "Literal",
|
||||
"value": "Hello "
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"argument": "data.name"
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"argument": {
|
||||
"type": "Literal",
|
||||
"value": "!\n\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "HtmlElement",
|
||||
"tagName": "ul",
|
||||
"attributes": [
|
||||
{
|
||||
"name": "if",
|
||||
"argument": "notEmpty(data.colors)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"argument": {
|
||||
"type": "Literal",
|
||||
"value": "\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "HtmlElement",
|
||||
"tagName": "div",
|
||||
"attributes": [
|
||||
{
|
||||
"name": "else"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Creating a compile-time tag
|
||||
|
||||
Let's take a look at how to create a compile-time tag by providing our own code generator. The first step is to register the custom tag in a `marko-taglib.json` file. We will use the `code-generator` property to associate the custom tag with a function that will be used to generate the code for the element node at compile-time:
|
||||
|
||||
```json
|
||||
{
|
||||
"<greeting>": {
|
||||
"code-generator": "./greeting-tag"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The code generator module should export a function with the following signature:
|
||||
|
||||
```javascript
|
||||
function generateCode(elNode, generator) : Node
|
||||
```
|
||||
|
||||
Continuing with the `<greeting>`, let's assume we have the following template:
|
||||
|
||||
```xml
|
||||
<greeting name="Frank"/>
|
||||
```
|
||||
|
||||
Let's implement the greeting tag that generates code that uses `console.log` to output `Hello <NAME>` as shown below:
|
||||
|
||||
```javascript
|
||||
module.exports = function generateCode(elNode, generator) {
|
||||
var builder = generator.builder;
|
||||
return [
|
||||
builder.text(builder.literal('Hello Frank')),
|
||||
builder.text(elNode.getAttributeValue('name'))
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
```javascript
|
||||
module.exports = function generateCode(elNode, generator) {
|
||||
var builder = generator.builder;
|
||||
return [
|
||||
builder.text(builder.literal('Hello Frank')),
|
||||
builder.text(elNode.getAttributeValue('name'))
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
So when should you a custom compile-time tag and when should use use a compile-time transformer?
|
||||
|
||||
Utilize custom compile-time tags when you need to create new custom tags that are capable of generating JavaScript code at compile-time. Examples of custom compile-time tags:
|
||||
|
||||
-
|
||||
|
||||
Compile-time transformers are useful for mod
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
var parseForEach = require('./util/parseForEach');
|
||||
|
||||
exports.generateCode = function(generator) {
|
||||
var argument = this.argument;
|
||||
module.exports = function nodeFactory(elNode, context) {
|
||||
var argument = elNode.argument;
|
||||
if (!argument) {
|
||||
generator.addError('Invalid <for> tag. Argument is missing. Example; <for(color in colors)>');
|
||||
return;
|
||||
context.addError(elNode, 'Invalid <for> tag. Argument is missing. Example; <for(color in colors)>');
|
||||
return elNode;
|
||||
}
|
||||
|
||||
var forEachProps = parseForEach(argument);
|
||||
forEachProps.body = this.body;
|
||||
forEachProps.body = elNode.body;
|
||||
|
||||
if (this.hasAttribute('separator')) {
|
||||
forEachProps.separator = this.getAttributeValue('separator');
|
||||
if (elNode.hasAttribute('separator')) {
|
||||
forEachProps.separator = elNode.getAttributeValue('separator');
|
||||
}
|
||||
|
||||
if (this.hasAttribute('status-var')) {
|
||||
forEachProps.statusVarName = this.getAttributeValue('status-var');
|
||||
if (elNode.hasAttribute('status-var')) {
|
||||
forEachProps.statusVarName = elNode.getAttributeValue('status-var');
|
||||
}
|
||||
|
||||
return generator.builder.forEach(forEachProps);
|
||||
return context.builder.forEach(forEachProps);
|
||||
};
|
||||
@ -1,16 +1,17 @@
|
||||
module.exports = function nodeFactory(el, context) {
|
||||
var argument = el.argument;
|
||||
|
||||
var attributes = el.attributes;
|
||||
var ifStatement = context.builder.ifStatement(argument || 'INVALID');
|
||||
module.exports = function nodeFactory(elNode, context) {
|
||||
var argument = elNode.argument;
|
||||
|
||||
if (!argument) {
|
||||
context.addError(ifStatement, 'Invalid <if> tag. Argument is missing. Example; <if(foo === true)>');
|
||||
context.addError(elNode, 'Invalid <if> tag. Argument is missing. Example; <if(foo === true)>');
|
||||
return elNode;
|
||||
}
|
||||
|
||||
var attributes = elNode.attributes;
|
||||
|
||||
if (attributes.length) {
|
||||
context.addError(ifStatement, 'Invalid <if> tag. Attributes not allowed.');
|
||||
context.addError(elNode, 'Invalid <if> tag. Attributes not allowed.');
|
||||
return;
|
||||
}
|
||||
|
||||
return ifStatement;
|
||||
return context.builder.ifStatement(argument);
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"<for>": {
|
||||
"code-generator": "./for-tag"
|
||||
"node-factory": "./for-tag"
|
||||
},
|
||||
"<if>": {
|
||||
"node-factory": "./if-tag"
|
||||
|
||||
@ -2,14 +2,9 @@
|
||||
|
||||
module.exports = function(builder) {
|
||||
var templateRoot = builder.templateRoot;
|
||||
var literal = builder.literal;
|
||||
var functionDeclaration = builder.functionDeclaration;
|
||||
var returnStatement = builder.returnStatement;
|
||||
var functionCall = builder.functionCall;
|
||||
var ifStatement = builder.ifStatement;
|
||||
var vars = builder.vars;
|
||||
var textOutput = builder.textOutput;
|
||||
var htmlOutput = builder.htmlOutput;
|
||||
var text = builder.text;
|
||||
var htmlElement = builder.htmlElement;
|
||||
|
||||
return templateRoot([
|
||||
@ -25,7 +20,7 @@ module.exports = function(builder) {
|
||||
'li',
|
||||
[],
|
||||
[
|
||||
textOutput('color')
|
||||
text('color')
|
||||
]),
|
||||
functionCall('bar')
|
||||
])
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
module.exports = function(builder) {
|
||||
var templateRoot = builder.templateRoot;
|
||||
var forEach = builder.forEach;
|
||||
var textOutput = builder.textOutput;
|
||||
var text = builder.text;
|
||||
|
||||
return templateRoot([
|
||||
forEach('color', 'data.colors', [
|
||||
textOutput('color')
|
||||
text('color')
|
||||
])
|
||||
]);
|
||||
};
|
||||
@ -6,14 +6,14 @@ module.exports = function(builder) {
|
||||
var functionDeclaration = builder.functionDeclaration;
|
||||
var functionCall = builder.functionCall;
|
||||
var ifStatement = builder.ifStatement;
|
||||
var textOutput = builder.textOutput;
|
||||
var htmlOutput = builder.htmlOutput;
|
||||
var text = builder.text;
|
||||
var html = builder.html;
|
||||
var htmlElement = builder.htmlElement;
|
||||
|
||||
var rootNode = templateRoot([
|
||||
textOutput(literal('Hello')),
|
||||
htmlOutput('data.name'),
|
||||
textOutput(literal('!')),
|
||||
text(literal('Hello')),
|
||||
html('data.name'),
|
||||
text(literal('!')),
|
||||
ifStatement(
|
||||
functionCall('notEmpty', 'data.colors'),
|
||||
[
|
||||
@ -28,7 +28,7 @@ module.exports = function(builder) {
|
||||
htmlElement('li',
|
||||
{ 'class': literal('color')},
|
||||
[
|
||||
textOutput('color')
|
||||
text('color')
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
3
test/fixtures/marko-taglib.json
vendored
3
test/fixtures/marko-taglib.json
vendored
@ -16,5 +16,6 @@
|
||||
"@name": {
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags-dir": "./taglib/scanned-tags"
|
||||
}
|
||||
|
||||
@ -37,18 +37,18 @@
|
||||
],
|
||||
"body": [
|
||||
{
|
||||
"type": "TextOutput",
|
||||
"type": "Text",
|
||||
"argument": {
|
||||
"type": "Literal",
|
||||
"value": "Hello"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "HtmlOutput",
|
||||
"type": "Html",
|
||||
"argument": "data.name"
|
||||
},
|
||||
{
|
||||
"type": "TextOutput",
|
||||
"type": "Text",
|
||||
"argument": {
|
||||
"type": "Literal",
|
||||
"value": "!"
|
||||
@ -67,7 +67,7 @@
|
||||
{
|
||||
"type": "HtmlElement",
|
||||
"tagName": "ul",
|
||||
"_attributes": [
|
||||
"attributes": [
|
||||
{
|
||||
"name": "class",
|
||||
"value": {
|
||||
@ -75,48 +75,7 @@
|
||||
"value": "colors"
|
||||
}
|
||||
}
|
||||
],
|
||||
"body": [
|
||||
{
|
||||
"type": "FunctionCall",
|
||||
"callee": "forEach",
|
||||
"args": [
|
||||
"data.colors",
|
||||
{
|
||||
"type": "FunctionDeclaration",
|
||||
"name": null,
|
||||
"params": [
|
||||
"color"
|
||||
],
|
||||
"body": [
|
||||
{
|
||||
"type": "HtmlElement",
|
||||
"tagName": "li",
|
||||
"_attributes": [
|
||||
{
|
||||
"name": "class",
|
||||
"value": {
|
||||
"type": "Literal",
|
||||
"value": "color"
|
||||
}
|
||||
}
|
||||
],
|
||||
"body": [
|
||||
{
|
||||
"type": "TextOutput",
|
||||
"argument": "color"
|
||||
}
|
||||
],
|
||||
"allowSelfClosing": false,
|
||||
"startTagOnly": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"allowSelfClosing": false,
|
||||
"startTagOnly": false
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -6,8 +6,8 @@ module.exports = function(builder) {
|
||||
var functionCall = builder.functionCall;
|
||||
var ifStatement = builder.ifStatement;
|
||||
var vars = builder.vars;
|
||||
var textOutput = builder.textOutput;
|
||||
var htmlOutput = builder.htmlOutput;
|
||||
var text = builder.text;
|
||||
var html = builder.html;
|
||||
var htmlElement = builder.htmlElement;
|
||||
|
||||
return templateRoot([
|
||||
@ -19,9 +19,9 @@ module.exports = function(builder) {
|
||||
]),
|
||||
|
||||
returnStatement(functionDeclaration('render', ['data', 'out'], [
|
||||
textOutput(literal('Hello')),
|
||||
htmlOutput('data.name'),
|
||||
textOutput(literal('!')),
|
||||
text(literal('Hello')),
|
||||
html('data.name'),
|
||||
text(literal('!')),
|
||||
ifStatement(
|
||||
functionCall('notEmpty', 'data.colors'),
|
||||
[
|
||||
@ -39,7 +39,7 @@ module.exports = function(builder) {
|
||||
'li',
|
||||
{ 'class': literal('color')},
|
||||
[
|
||||
textOutput('color')
|
||||
text('color')
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
1
test/fixtures/render/autotest/tag-code-generator-return-array/expected.html
vendored
Normal file
1
test/fixtures/render/autotest/tag-code-generator-return-array/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
Hello FrankHello John
|
||||
2
test/fixtures/render/autotest/tag-code-generator-return-array/template.marko
vendored
Normal file
2
test/fixtures/render/autotest/tag-code-generator-return-array/template.marko
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
<test-tag-code-generator-return-array name="Frank"/>
|
||||
<test-tag-code-generator-return-array name=data.name/>
|
||||
3
test/fixtures/render/autotest/tag-code-generator-return-array/test.js
vendored
Normal file
3
test/fixtures/render/autotest/tag-code-generator-return-array/test.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
exports.templateData = {
|
||||
name: 'John'
|
||||
};
|
||||
1
test/fixtures/render/autotest/tag-code-generator-return-node/expected.html
vendored
Normal file
1
test/fixtures/render/autotest/tag-code-generator-return-node/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
Hello FrankHello John
|
||||
2
test/fixtures/render/autotest/tag-code-generator-return-node/template.marko
vendored
Normal file
2
test/fixtures/render/autotest/tag-code-generator-return-node/template.marko
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
<test-tag-code-generator-return-node name="Frank"/>
|
||||
<test-tag-code-generator-return-node name=data.name/>
|
||||
3
test/fixtures/render/autotest/tag-code-generator-return-node/test.js
vendored
Normal file
3
test/fixtures/render/autotest/tag-code-generator-return-node/test.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
exports.templateData = {
|
||||
name: 'John'
|
||||
};
|
||||
1
test/fixtures/render/autotest/tag-code-generator-return-self/expected.html
vendored
Normal file
1
test/fixtures/render/autotest/tag-code-generator-return-self/expected.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
<test-tag-code-generator-return-self name="Frank" foo="bar"></test-tag-code-generator-return-self><test-tag-code-generator-return-self name="John" foo="bar"></test-tag-code-generator-return-self>
|
||||
2
test/fixtures/render/autotest/tag-code-generator-return-self/template.marko
vendored
Normal file
2
test/fixtures/render/autotest/tag-code-generator-return-self/template.marko
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
<test-tag-code-generator-return-self name="Frank"/>
|
||||
<test-tag-code-generator-return-self name=data.name/>
|
||||
3
test/fixtures/render/autotest/tag-code-generator-return-self/test.js
vendored
Normal file
3
test/fixtures/render/autotest/tag-code-generator-return-self/test.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
exports.templateData = {
|
||||
name: 'John'
|
||||
};
|
||||
7
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-array/code-generator.js
vendored
Normal file
7
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-array/code-generator.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = function generateCode(elNode, generator) {
|
||||
var builder = generator.builder;
|
||||
return [
|
||||
builder.text(builder.literal('Hello ')),
|
||||
builder.text(elNode.getAttributeValue('name'))
|
||||
];
|
||||
};
|
||||
3
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-array/marko-tag.json
vendored
Normal file
3
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-array/marko-tag.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"@name": "string"
|
||||
}
|
||||
6
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-node/code-generator.js
vendored
Normal file
6
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-node/code-generator.js
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = function generateCode(elNode, generator) {
|
||||
var builder = generator.builder;
|
||||
return builder.functionCall('out.write', [
|
||||
builder.binaryExpression('"Hello "', '+', elNode.getAttributeValue('name'))
|
||||
]);
|
||||
};
|
||||
3
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-node/marko-tag.json
vendored
Normal file
3
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-node/marko-tag.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"@name": "string"
|
||||
}
|
||||
5
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-self/code-generator.js
vendored
Normal file
5
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-self/code-generator.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = function generateCode(elNode, generator) {
|
||||
var builder = generator.builder;
|
||||
elNode.setAttributeValue('foo', builder.literal('bar'));
|
||||
return elNode;
|
||||
};
|
||||
3
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-self/marko-tag.json
vendored
Normal file
3
test/fixtures/taglib/scanned-tags/test-tag-code-generator-return-self/marko-tag.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"@name": "string"
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
module.exports = function(compiler) {
|
||||
let builder = compiler.createBuilder();
|
||||
|
||||
let textOutput = builder.textOutput;
|
||||
let text = builder.text;
|
||||
let templateRoot = builder.templateRoot;
|
||||
let htmlElement = builder.htmlElement;
|
||||
let ifStatement = builder.ifStatement;
|
||||
@ -29,7 +29,7 @@ module.exports = function(compiler) {
|
||||
}
|
||||
},
|
||||
[
|
||||
textOutput('color')
|
||||
text('color')
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user