Skip to content

Commit

Permalink
Migrate legacy macro syntax to tag params
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanPiercey committed Feb 5, 2019
1 parent b2b1bd6 commit 2abf853
Show file tree
Hide file tree
Showing 59 changed files with 389 additions and 458 deletions.
65 changes: 54 additions & 11 deletions docs/core-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,28 +301,71 @@ Special HTML characters will _not_ be escaped since the file is expected to be a

### `<macro>`

Parameterized macros allow for reusable fragments within an HTML template.
A macro can be defined using the `<macro>` directive.
Macros allow for reusable fragments within an HTML template.
A macro can be defined using the `<macro>` tag, with a `name` attribute.

```marko
<macro greeting(name, count)>
Hello ${name}! You have ${count} new messages.
<macro name="greeting">
<span>Welcome!</span>
</macro>
```

The above macro can then be invoked as part of any expression. The following
sample template shows how to use macro functions inside expressions:
The above macro can then be used as if it was a regular `<greeting>` tag.

```marko
<macro greeting(name, count)>
Hello ${name}! You have ${count} new messages.
<greeting/>
<greeting/>
```

The output HTML would be the following:

```html
<span>Welcome!</span> <span>Welcome!</span>
```

Macros become more useful when combined with [tag parameters](./syntax.md#tag-body-parameters), allowing for more complex templates like so:

```marko
<macro|{ name, count }| name="greeting">
<span>Hello ${name}! You have ${count} new messages.</span>
</macro>
```

This time the `<greeting>` macro is able to receive parameters from the outside, in this case `name` and `count`.

```marko
<greeting name="Frank" count=20/>
```

The output HTML would be the following:

```html
<span> Hello Frank! You have 10 new messages. </span>
```

Macros receive input similar to the root template, including a `renderBody` for displaying any provided body content.

```marko
<macro|{ renderBody }| name="special-heading">
<h1>
<${renderBody}/>!
</h1>
</macro>
<p>
<greeting("John", 10)/>
<special-heading>
Hello
</special-heading>
</p>
```

The output HTML would be the following:

```html
<p>
<!-- Or, using named attributes: -->
<greeting name="Frank" count=20/>
<h1>
Hello!
</h1>
</p>
```

Expand Down
9 changes: 0 additions & 9 deletions src/compiler/Builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ var UpdateExpression = require("./ast/UpdateExpression");
var UnaryExpression = require("./ast/UnaryExpression");
var MemberExpression = require("./ast/MemberExpression");
var Code = require("./ast/Code");
var InvokeMacro = require("./ast/InvokeMacro");
var Macro = require("./ast/Macro");
var ConditionalExpression = require("./ast/ConditionalExpression");
var NewExpression = require("./ast/NewExpression");
Expand Down Expand Up @@ -331,14 +330,6 @@ class Builder {
return new If({ test, body, else: elseStatement });
}

invokeMacro(name, args, body) {
return new InvokeMacro({ name, args, body });
}

invokeMacroFromEl(el) {
return new InvokeMacro({ el });
}

literal(value) {
return new Literal({ value });
}
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/CompileContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -684,12 +684,12 @@ class CompileContext extends EventEmitter {
return this._macros.getRegisteredMacro(name);
}

registerMacro(name, params) {
registerMacro(name) {
if (!this._macros) {
this._macros = macros.createMacrosContext();
}

return this._macros.registerMacro(name, params);
return this._macros.registerMacro(name);
}

importTemplate(relativePath, varName) {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/Normalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class Normalizer {
if (
elNode.params.length &&
elNode.tagName !== "for" &&
elNode.tagName !== "macro" &&
!(
(elNode.tagName === "@then" || elNode.tagName === "@catch") &&
elNode.parentNode.tagName === "await"
Expand Down
53 changes: 1 addition & 52 deletions src/compiler/ast/CustomTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const HtmlElement = require("./HtmlElement");
const removeDashes = require("../util/removeDashes");
const safeVarName = require("../util/safeVarName");
const ok = require("assert").ok;
const Node = require("./Node");
const merge = require("../util/mergeProps");

const CUSTOM_TAG_KEY = Symbol("CustomTag");
const HAS_RENDER_BODY_PROP_ADDED = Symbol();
Expand Down Expand Up @@ -202,57 +202,6 @@ function isWhiteSpaceTextNode(node) {
/^\s+$/.test(node.argument.value)
);
}

function merge(props1, props2, context) {
if (props1 && !(props1 instanceof Node)) {
props1 = context.builder.objectExpression(props1);
}

if (props2) {
if (!(props2 instanceof Node)) {
props2 = context.builder.objectExpression(props2);
}
} else {
return props1;
}

if (props2.type === "ObjectExpression" && !props2.hasProperties()) {
return props1;
}

if (props1.type === "ObjectExpression") {
let argProp = props1.getProperty("_arg");

if (argProp) {
let mergeVar = context.helper("merge");
argProp.value = context.builder.functionCall(mergeVar, [
props2, // Input props from the attributes take precedence
argProp.value
]);

return props1;
}

if (props2.type === "ObjectExpression") {
props1.addProperties(props2.properties);
return props1;
} else {
let mergeVar = context.helper("merge");

return context.builder.functionCall(mergeVar, [
props2, // Input props from the attributes take precedence
props1
]);
}
} else {
let mergeVar = context.helper("merge");
return context.builder.functionCall(mergeVar, [
props2, // Input props from the attributes take precedence
props1
]);
}
}

class CustomTag extends HtmlElement {
constructor(el, tagDef) {
super(el);
Expand Down
14 changes: 0 additions & 14 deletions src/compiler/ast/HtmlElement/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,24 +80,10 @@ class HtmlElement extends Node {
}

generateHTMLCode(codegen) {
if (codegen.context.isMacro(this.tagName)) {
// At code generation time, if node tag corresponds to a registered macro
// then invoke the macro based on node HTML element instead of generating
// the code to render an HTML element.
return codegen.builder.invokeMacroFromEl(this);
}

return generateHTMLCode(this, codegen);
}

generateVDOMCode(codegen) {
if (codegen.context.isMacro(this.tagName)) {
// At code generation time, if node tag corresponds to a registered macro
// then invoke the macro based on node HTML element instead of generating
// the code to render an HTML element.
return codegen.builder.invokeMacroFromEl(this);
}

return generateVDOMCode(this, codegen, vdomUtil);
}

Expand Down
147 changes: 0 additions & 147 deletions src/compiler/ast/InvokeMacro.js

This file was deleted.

9 changes: 6 additions & 3 deletions src/compiler/ast/Macro.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ class Macro extends Node {

generateCode(codegen) {
var name = this.name;
var params = this.params || [];
var builder = codegen.builder;
var macroDef = codegen.context.registerMacro(name, params);
var macroDef = codegen.context.registerMacro(name);
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,
[builder.identifier("out")].concat(this.params),
body
);
}

walk(walker) {
Expand Down

0 comments on commit 2abf853

Please sign in to comment.