Components

We have three types of components:

  1. Dynamic server-side or page components
  2. Client-side components with some functionality
  3. Static components with some functionality or markup

Server-side components will have a <script> tag without context, or <script context="module"> for module-level functionality.

Client-side components will have a <script context="client">

Static components may not contain script tags, but can access their props through the $$props variable.

We explore other kind of scripts later, for now we'll focus on static or dynamic components with or without context.

File-naming

Any .{md,html} file within the ./pages directory is a component, if the filename ends on +page, +error or +layout then it will be used to declare and decorate your application routes.

You can also place +server.mjs files aside your components, they'll also decorate your routes with additional middleware definitions, handlers and actions.

This results in a tree of all your declared routes with their nearest layout, error and middleware modules found.

We save this information with your compiled files for later usage in a index.json file, i.e. jamrock route use this file.


Composition

To use other components to compose the UI you need to import them.

<script>
  import Hello from './components/hello.html';
</script>

<Hello>World</Hello>

You can't import components in your own modules as they are resolved at compile time.

Also, importing page-components from other pages is disallowed.

Content

Use the children prop to render any given content.

<script>
  export let children;
</script>

<div>Hello {@render children?.()}</div>

The result from above would be <div>Hello World</div>.

Props

Top-level export declarations are the props, e.g.

<script>
  export let test;
  export let value;
</script>

Got: {test} ({typeof value} {value})

Using let helps to omit initial values, while const will enforce you otherwise, however the former is preferred.

Now your component can be used this way:

<Example test="OSOM" value={42} />

It would yield: Got: OSOM (number 42)

Passing props between server-side components is granted for any type (almost!), but when you pass props to client-side components they should be serializable values.

In the case of static components without a <script> tag you should use $$props.thing syntax to access any given prop.


Templating

We've been using stuff like {...}, {@render ...} and {#snippet ...}, they are template-tags or expressions.

They just render any given values on specific ways, enabling you to compose using logical expressions, loops and so on.

Let's explore all the available tags:

{...}

They can render simple values or basic JavaScript expressions.

Try to keep things simple, we don't support fully featured JavaScript expressions to abuse from!

{#snippet ...}...{/snippet}

Declare reusable chunks of markup in your components.

They can appear at component root to behave as fallbacks if they're not given as props, i.e.

<script>
  export let sample;
</script>

{#snippet sample(value)}
  Got: {value}
{/snippet}

<div>{@render sample(42)}</div>

In this example, the component will receive a sample prop that will fallback to the inlined version if omitted.

<Example>
  {#snippet sample(value)}
    OSOM: {value}
  {/snippet}
</Example>

This would yield: <div>OSOM: 42</div>

Snippets are values, so they can be passed down as arguments that you can pass again or render, etc.

{@render ...}

Will take any expression to produce markup.

It's encouraged to call these expressions with ?.() to avoid unexpected exceptions if you don't provide them.

{#if ...}...{/if}

It'll render the underlying block if the expressions is truthy, i.e.

{#if true}
  42
{/if}

As any other expression given, keep it simple for now!

{:else ...}

You can use {:else} or {:else if ...} blocks to render as fallbacks from their previous condition.

These tags are not limited to if's, they can be used to declare fallbacks from {#each ...} blocks, see below.

{#each ...}...{/each}

Allows to iterate values within the template, it can take arrays, generators, promises, etc.

Almost anything that can produce an iterator, e.g.

<script>
  const empty = Promise.resolve([]);
  const numbers = [1, 2, 3];
  function *values() {
    yield 1;
    yield 2;
    yield 3;
  }
</script>

{#each empty as _}
  Not empty
{:else}
  Empty
{/each}

{#each numbers as num}
  {num}
{/each}

{#each values as val}
  {val}
{/each}

Iterators are constrained by time and length, so they'll be stopped once a given limit or maximum execution-time is reached!

{@raw ...}

These expressions will be inlined as is on the compiled code, be wise and careful as you're inlining code!

Usually this is required to inline markup on its AST form, e.g.

{@raw ['h1', {}, 'It works!']}

{@html ...}

This will render any given string as HTML.

Its contents is not processed, so it'll be rendered as is.

{@debug ...}

It'll print out the JSON.stringify({ ... }) result from any given argument, variables are just treated as object fields.

This will not support expressions, just variable names.


Markdown

Jamrock supports some markdown content through kramed on *-page.html components and on any *.md component.

For pages it's allowed on the top-level of the file only.

Example of page component
<script>
  export let children;
</script>

# Markdown works here

- This is the top-level of the file,
- you don't need to wrap your content
- in body or head tags on your components

<div>
  ## Markdown here does not work!
  
  {@render children?.()}
</div>

<mkd>

You can use the <mkd> element to render Markdown within other html nodes, it may work with some expressions!

You can import and include other components in your Markdown, that's expected and works out of the box.

Code blocks

Tagged blocks are ignored by the parser as they are handled by highlight.js, this produces highlighted code as the resulting markup.

```html
<h1>It works!</h1>
```

Jamrock includes support for: shell, less, css, html, xml and js languages for now.

Untagged blocks are parsed by the framework instead, this way components can be rendered within.

You'll need to escape HTML tags and other entities to render them as text, i.e.

Examples of html-entities encoded
```
&lt;h1&gt;It works!&lt;/h1&gt;
```

or

`&lbrace; ... }`
  1. You MUST encode the { character as &lbrace; outside tagged blocks, otherwise they'll be parsed.
  2. Any other HTML tag or entity within inline `...` backticks MUST be encoded as well.
  3. Only ``` blocks are handlded this way, code blocks made by white-space indentation are ignored.

This way the markup is rendered as is, so you can decorate the code by yourself with components or regular HTML tags.

Emoji support

Inside markdown we can use emojis, or shortcuts like :beer: (🍺).

To render the shortcut as text just encode it as &#58;beer:.

Emoji support is enabled through the emojify option.

Enable twemoji to inline the emojis as images, it'll work for both shortcuts and any other emojis found.

If you use emojis or shortcuts outside markdown they'll be kept as is, use the <mkd>:beer:</mkd> tag to render or inline emojis.

For now, this is what you can do within components for authoring markup, next we'll be learning about live updates on the DOM! 🚀