UNPKG

16.6 kBMarkdownView Raw
1# Syntax
2
3Marko is HTML _re-imagined_ as a language for building dynamic and reactive user interfaces.
4Just about any valid HTML is valid Marko, but Marko extends the HTML language to allow building modern applications in a declarative way.
5
6> **ProTip:** Marko also supports a [beautiful concise syntax](./concise.md). If you'd prefer to see the documentation using this syntax, just click the `switch syntax` button in the corner of any Marko code sample.
7
8> **Note:** Text at the root of a template (outside any tags) must be prefixed with the [concise syntax's `--`](./concise.md#text) to denote it is text. The parser starts in concise mode and would otherwise try to parse what you meant to be text as a concise tag declaration.
9>
10> ```marko
11> -- Root level text
12> ```
13
14## Tags
15
16As you might expect, Marko supports all native HTML/SVG/whatever tags and attributes. In addition to these, it also comes with a set of useful [core tags](./core-tags.md). Beyond this, you can also build your own [custom tags](./custom-tags.md) and [install third-party tags](./custom-tags.md#using-tags-from-npm) from `npm`.
17
18All of these types of tags use the same syntax:
19
20```marko
21<my-tag-name/>
22```
23
24You don't need to import tags. Marko discovers them based on the folder structure—similar to how you don't specify a full path when referencing a module in `node_modules/`. Marko looks in [`components/`](./custom-tags.md#how-tags-are-discovered) by default and this directory can be configured in [`marko.json`](./marko-json.md).
25
26## Dynamic text
27
28You can use placeholders (`${}`) to insert a value into the template:
29Placeholders accept any JavaScript expression and the result of the expression will be inserted into the HTML output:
30
31```marko
32<div>
33 Hello ${"world".toUpperCase()}
34</div>
35```
36
37These values are automatically escaped so you don't accidentally insert malicious code. If you do need to pass unescaped HTML, you can use `$!{}`:
38
39```marko
40<div>
41 Hello $!{"<b>World</b>"}
42</div>
43```
44
45> **ProTip:** If necessary, you can escape `$` using a backslash to have it be treated as text instead of a placeholder token:
46>
47> ```marko
48> <div>
49> Placeholder example: <code>\${someValue}</code>
50> </div>
51> ```
52
53## Attributes
54
55In marko attributes are parsed as JavaScript expressions (instead of just strings).
56
57```marko
58<div class=myClassName/>
59<input type="checkbox" checked=isChecked/>
60
61<custom-tag string="Hello"/>
62<custom-tag number=1/>
63<custom-tag template-string=`Hello ${name}`/>
64<custom-tag boolean=true/>
65<custom-tag array=[1, 2, 3]/>
66<custom-tag object={ hello: "world" }/>
67<custom-tag variable=name/>
68<custom-tag function-call=user.getName()/>
69```
70
71Attributes that are passed to a custom tag are received as it's [`input`](https://markojs.com/docs/class-components/#input).
72
73> **Note:** Although in most cases you won't see a difference, strings are parsed as JavaScript strings, not HTML strings. Where this comes up most often is using the `pattern` attribute with the `<input>` tag: you need to "double escape" your regex escape sequences much like you were passing a string to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) (or you can use a literal `/regex/`).
74>
75> _Marko Source:_
76>
77> ```marko
78> <input pattern="\\w+" type="text"/>
79> <input pattern=/\w+/ type="text"/>
80> ```
81>
82> _HTML Output:_
83>
84> ```marko
85> <input pattern="\w+" type="text"/>
86> ```
87
88### Complex expressions
89
90Any JavaScript expression is a valid attribute value, provided it meets the following criteria:
91
92_It does not contain any spaces_
93
94_It does not contain any right angle brackets (`>`)_
95
96```marko
97<custom-tag sum=1+2 difference=3-4/>
98```
99
100```marko
101custom-tag sum=1+2 difference=3-4
102```
103
104_Spaces and `>` are contained within matching `()`, `[]`, `{}`, strings and regexps_
105
106```marko
107<custom-tag sum=(1 + 2) difference=(3 - 4) greater=(1 > 2)/>
108```
109
110```marko
111custom-tag sum=(1 + 2) difference=(3 - 4) greater=(1 > 2)
112```
113
114### Boolean attributes
115
116HTML defines the following rules for [boolean attributes](https://www.w3.org/TR/2008/WD-html5-20080610/semantics.html#boolean):
117
118> The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.
119
120In Marko when an attribute value evaluates to `false`, `null`, or `undefined`, the attribute is not included in the output. If an attribute value is `true`, only the attribute name is included in the output.
121
122_Marko Source:_
123
124```marko
125<input type="checkbox" checked=true>
126<input type="checkbox" checked=false>
127```
128
129Renders the following HTML:
130
131_HTML Output:_
132
133```html
134<input type="checkbox" checked /> <input type="checkbox" />
135```
136
137Similarly, when only an attribute name is defined, it is equivalent to specifying the attribute with a value of `true`:
138
139```marko
140<!-- These are equivalent -->
141<custom-menu expanded>
142<custom-menu expanded=true>
143```
144
145> **ProTip:**
146> You can take advantage of the way Marko handles boolean attributes to conditionally render attributes:
147>
148> _Marko Source:_
149>
150> ```marko
151> <div class=(active && "tab-active")>Hello</div>
152> ```
153>
154> With a value of `true` for `active`, the output would be the following:
155>
156> _HTML Output:_
157>
158> ```html
159> <div class="tab-active">Hello</div>
160> ```
161>
162> With a value of `false` for `active`, the output would be the following:
163>
164> _HTML Output:_
165>
166> ```html
167> <div>Hello</div>
168> ```
169
170### Dynamic attributes
171
172The spread syntax (`...`) can be used to merge in an object as attributes to a tag:
173
174_Marko Source:_
175
176```marko
177<a ...attrs target="_blank">eBay</a>
178```
179
180With `attrs` as the following value:
181
182```js
183{
184 class: "active",
185 href: "https://ebay.com/"
186}
187```
188
189would output the following HTML:
190
191_HTML Output:_
192
193```html
194<a class="active" href="https://ebay.com/" target="_blank">eBay</a>
195```
196
197> **ProTip:**
198> With spread attributes order matters.
199> You can take advantage of this to implement both default attributes, and enforced attributes.
200>
201> ```marko
202> <custom-tag ...defaults ...userSupplied class="overridden"/>
203> ```
204
205> **ProTip:**
206> You can provide `undefined` to a spread attribute which will output nothing.
207
208### Style attribute
209
210You can pass a string as the value of `style` just as you would in HTML, in addition Marko supports passing an object or array as the value of the `style` attribute:
211
212_Marko Source:_
213
214```marko
215<!-- string: -->
216<div style="display:block;margin-right:16px"/>
217
218<!-- object: -->
219<div style={ display: "block", color: false, marginRight: 16 }/>
220
221<!-- array: -->
222<div style=["display:block", null, { marginRight: 16 }]/>
223```
224
225In all cases, the output will be the same:
226
227_HTML Output:_
228
229```html
230<div style="display:block;margin-right:16px;"></div>
231```
232
233### Class attribute
234
235The `class` attribute also supports receiving an object or array (in addition to a string) as shown below:
236
237_Marko Source:_
238
239```marko
240<!-- string: -->
241<div class="a c"/>
242
243<!-- object: -->
244<div class={ a:true, b:false, c:true }/>
245
246<!-- array: -->
247<div class=["a", null, { c:true }]/>
248```
249
250In all cases, the output will be the same:
251
252_HTML Output:_
253
254```html
255<div class="a c"></div>
256```
257
258### Shorthand attributes
259
260Marko provides a shorthand for declaring classes and ids on an element:
261
262_Marko Source:_
263
264```marko
265<div.my-class/>
266<span#my-id/>
267<button#submit.primary.large/>
268```
269
270Renders the following HTML:
271
272_HTML Output:_
273
274<!-- prettier-ignore -->
275```html
276<div class="my-class"></div>
277<span id="my-id"></span>
278<button id="submit" class="primary large"></button>
279```
280
281## Parameters
282
283When a tag renders its body content, it may provide data which can be received by defining parameters after the tagname. Parameters are available to the tag's body content.
284
285This is a powerful feature that allows components to provide functionality and data while giving you full control over what gets rendered.
286
287In the following example, `<mouse>` provides a parameter which we have named `position`:
288
289```marko
290<mouse|position|>
291 The mouse is at ${position.x}, ${position.y}!
292</mouse>
293```
294
295> `<mouse>` would [render its body](./body-content.md) and provide the position similar to this: `<${input.renderBody} x=0 y=0/>`.
296
297> **ProTip:** Tag `|parameters|` are treated as regular JavaScript function parameters. This means you can destructure, set default values, etc.
298>
299> ```marko
300> <mouse|{ x, y }|>
301> The mouse is at ${x}, ${y}!
302> </mouse>
303> ```
304
305> **Note:** Parameters are not available to attributes, only to the tag body.
306>
307> ```marko
308> <mouse|position| something=position>
309> ReferenceError when setting the "something" attribute
310> </mouse>
311> ```
312
313Parameters are used by some of Marko's [core tags](./core-tags.md) like the [`<for>`](./core-tags.md#for) and [`<await>`](./core-tags.md#await) tags.
314
315## Arguments
316
317Some tags and attributes accept javascript style `arguments`. Arguments are denoted by parenthesis following the tag or attribute name. Arguments provide a way to pass unnamed data to a tag.
318
319```marko
320<if(true)>
321 <strong>Marko is awesome</strong>
322</if>
323
324<h1 body-only-if(skipHeading)>
325 Conditional display heading, but always show content!
326</h1>
327```
328
329Arguments are used by some of Marko's [core tags](./core-tags.md) like the [`<if>`](./core-tags.md#if-else-if-else) tag and [`body-only-if`](./core-tags.md#body-only-if) attribute displayed above.
330
331Previously you could also use them in your own [custom tags](./custom-tags.md) however it is now recommended to use [dynamic attributes](#dynamic-attributes).
332
333## Dynamic tagname
334
335The `<${dynamic}>` syntax is used to render a tag or component that isn't determined until runtime. It can also be used within a [custom tag](./custom-tags.md) to render body content that was passed to that tag.
336
337_Marko Source:_
338
339```marko
340<${href ? 'a' : 'button'} href=href>
341 Click me!
342</>
343```
344
345With `href` as `https://ebay.com` would output the following HTML:
346
347_HTML Output:_
348
349```html
350<a href="https://ebay.com">Click me!</a>
351```
352
353And with `href` as `undefined` would output the following HTML:
354
355_HTML Output:_
356
357```html
358<button>Click me!</button>
359```
360
361> **ProTip:**
362> If you find that you have a wrapper element that is conditional, but whose body should always be rendered then you can use a null dynamic tag. For example, to only render a wrapping `<a>` tag if there is a valid URL then you could do the following:
363>
364> _Marko Source:_
365>
366> ```marko
367> <${input.linkUrl ? "a" : null} href=input.linkUrl >
368> Some body content
369> </>
370> ```
371>
372> Given a value of `"http://localhost/"` for the `input.linkUrl` variable: , the output would be the following:
373>
374> _HTML Output:_
375>
376> ```html
377> <a href="http://localhost/"> Some body content </a>
378> ```
379>
380> Given a value of `undefined` for the `input.linkUrl` variable: , the output would be the following:
381>
382> _HTML Output:_
383>
384> ```html
385> Some body content
386> ```
387
388### Dynamic components
389
390Instead of just strings, the dynamic tagname can also be a component:
391
392```marko
393import componentA from "<component-a>";
394import componentB from "<component-b>";
395
396<${useA ? componentA : componentB}/>
397```
398
399> **ProTip:**
400> You can also switch between a normal HTML tag and a component:
401>
402> ```marko
403> import FancyButton from "./path/to/fancy-button.marko";
404>
405> <${isFancy ? FancyButton : 'button'}>
406> Button text
407> </>
408> ```
409
410> **Note:** You cannot reference a Marko custom tag using a name string:
411>
412> _Marko Source:_
413>
414> ```marko
415> <${isFancy ? 'fancy-button' : 'button'}>
416> Button text
417> </>
418> ```
419>
420> With `isFancy` as `true` would output the following HTML:
421>
422> _HTML Output:_
423>
424> ```marko
425> <fancy-button>Button text</fancy-button>
426> ```
427
428### Dynamic body content
429
430When a custom tag receives [body content](./body-content.md), it is passed as a `renderBody` property. To render this content you can pass the `renderBody` as the dynamic tagname.
431
432```marko
433<div class="container">
434 <${input.renderBody}/>
435</div>
436```
437
438## Attribute Tag
439
440As the name implies, `<@attribute-tags>` are special attributes that take the form of tags. They allow you to pass named body sections to a [custom tag](./custom-tags.md).
441
442The core `<await>` tag allows you to pass multiple body sections that it will conditionally render based on the state of the promise.
443
444```marko
445<await(somePromise)>
446 <@then|result|>
447 The promise resolved: ${result}
448 </@then>
449 <@catch|error|>
450 The promise rejected: ${error.message}
451 </@catch>
452</await>
453```
454
455These body sections are also commonly used to create layouts:
456
457```marko
458<page-layout>
459 <@heading>
460 <h1>Hello</h1>
461 </@heading>
462 <@body>
463 <p>Lorem ipsum....</p>
464 </@body>
465</page-layout>
466```
467
468These tags are passed to the custom tag as objects with a `renderBody`, it can then [render its body content](./body-content.md).
469
470> **Note:**
471> Attribute tags can have their own parameters, but like attributes, they cannot access the parameters of their parent tag:
472>
473> ```marko
474> <list|item|>
475> ${item.name}
476> <@separator>${item} (oops, ReferenceError)</@separator>
477> </list>
478> ```
479
480## Inline JavaScript
481
482To execute JavaScript in your template you can insert a Javascript statement using the `$ <code>` syntax.
483
484A line that starts with a `$` followed by a space will execute the code that follows.
485
486```marko
487$ const name = "World";
488
489<div>
490 Hello, ${name}
491 $ console.log("The value rendered was", name);
492</div>
493```
494
495A statement may continue onto subsequent lines if new lines are bounded by `{}`, `[]`, `()`, ` `` `, or `/**/`:
496
497```marko
498$ const person = {
499 name: "Frank",
500 age: 32
501};
502```
503
504Multiple statements or an unbounded statement may be used by wrapping the statement(s) in a block:
505
506```marko
507$ {
508 const bgColor = getRandomColor();
509 const textColor = isLight(bgColor)
510 ? "black"
511 : "white";
512}
513```
514
515> **ProTip:** Any JavaScript statement can be used here, even `debugger`:
516>
517> ```marko
518> <div>
519> ${textColor}
520> $ debugger; // Quickly debug `textColor`
521> </div>
522> ```
523
524> **ProTip:** If necessary, you can escape `$` using a backslash to have it be treated as text instead of a placeholder token:
525>
526> ```marko
527> <p>You can run JS in a Marko template like this:</p>
528> <code>
529> \$ var num = 123;
530> </code>
531> ```
532
533> **ProTip:** If you find yourself writing a lot of inline JS, consider moving it out to an external file and then [`import`](#importing-external-files) it.
534
535### Static JavaScript
536
537Inline JavaScript will run each time your template is rendered, but the JavaScript code that follows `static` will only run once when the template is loaded. It must be declared at the top level and does not have access to values passed in at render time.
538
539```marko
540static var count = 0;
541static var formatter = new Formatter();
542
543static function sum(a, b) {
544 return a + b;
545};
546
547<div>${formatter.format(sum(2, 3))}</div>
548```
549
550Like inline Javascript, multiple statements or an unbounded statement may be used by wrapping the statement(s) in a block:
551
552```marko
553static {
554 var base = 2;
555 function sum(a, b) {
556 return base + a + b;
557 };
558}
559```
560
561### Importing external files
562
563The `import` statement is used to access data and functions from external files. It follows the same syntax as the [JavaScript `import` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import).
564
565```marko
566import sum from './utils/sum';
567<div>The sum of 2 + 3 is ${sum(2, 3)}</div>
568```
569
570The `from` can also be wrapped in `<angle-brackets>` which will resolve the tag inside the angle brackets using Marko's [tag discovery](./custom-tags.md#how-tags-are-discovered).
571This can be useful when rendering a [dynamic component](#dynamic-components).
572
573```marko
574import FancyLink from '<fancy-link>';
575
576<${input.isFancy ? FancyLink : "a"} href=input.href>
577 Open Link
578</>
579```
580
581> **ProTip:**
582> As a s
583>
584> _Marko Source:_
585>
586> ```marko
587> <div class=(active && "tab-active")>Hello</div>
588> ```
589>
590> With a value of `true` for `active`, the output would be the following:
591>
592> _HTML Output:_
593>
594> ```html
595> <div class="tab-active">Hello</div>
596> ```
597>
598> With a value of `false` for `active`, the output would be the following:
599>
600> _HTML Output:_
601>
602> ```html
603> <div>Hello</div>
604> ```
605
606## Comments
607
608Standard HTML comments can be used and will be stripped out of the rendered output.
609At the top level of the template JavaScript comments (`// comment` and `/** comment */`) can also be used.
610
611```marko
612<!-- This is a comment that will not be rendered -->
613
614<h1>Hello</h1>
615```
616
617If you would like for your HTML comment to show up in the final output then you can use the [`html-comment` core tag](./core-tags.md#html-comment).