---
name: "up.compiler"
area: "API"
kind: "JavaScript function"
parent: "up.script"
url: "https://unpoly.com/api/up.compiler"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [API](https://unpoly.com/api.md) · [up.script](https://unpoly.com/api/up.script.md)</nav>

# up.compiler(selector, [options], compiler)

Registers a function to be called when Unpoly inserts a matching element.

<nav aria-label="Guides">

- [Learn: Enhancing elements with JavaScript](https://unpoly.com/learn/enhancing-elements.md)

</nav>

<nav aria-label="Contents">

- [When compilers are called](#when-compilers-are-called)
- [Anatomy of a compiler](#anatomy-of-a-compiler)
- [Destructor functions](#destructor)
- [Batching multiple elements](#batching-multiple-elements)
- [Error handling](#errors)
- [Compilation order](#order)
- [Asynchronous compilers](#async)
- [Registering compilers after booting](#late)
- [Parameters](#parameters)

</nav>

## When compilers are called {#when-compilers-are-called}

A compiler is called for matching elements:

- on the initial page, when Unpoly [boots](https://unpoly.com/api/up.boot.md)
- in every new fragment that Unpoly renders
- in elements that you pass to [`up.hello()`](https://unpoly.com/api/up.hello.md)

Elements that other code inserts, e.g. a framework like React, are not compiled automatically. See [compiling elements inserted by other code](https://unpoly.com/learn/enhancing-elements.md#hello).

## Anatomy of a compiler {#anatomy-of-a-compiler}

This code registers a function that is called when an element with a `.foo` class is inserted:

```js
up.compiler('.foo', function(element, data, meta) {
  // do something with `element`
})
```

The compiler function is passed three arguments:

1. The newly inserted `element` matching the selector
2. Any [data attached to the element](https://unpoly.com/learn/data.md)
3. [Meta information](https://unpoly.com/learn/enhancing-elements.md#meta) about the current render pass

## Destructor functions {#destructor}

When a compiler registers effects _outside_ the compiling element's subtree, it must return a destructor function that reverts the effect. The destructor function is called automatically when the element is swapped or destroyed later.

Examples of non-local effects are global event handlers bound to the `document`, or `setInterval()`:

```js
up.compiler('.current-time', function(element) {
  let update = () => element.textContent = new Date().toString()
  let updateInterval = setInterval(update, 1000)
  return () => clearInterval(updateInterval)
})
```

See [cleaning up after yourself](https://unpoly.com/learn/enhancing-elements.md#destructor) for more details and examples.

## Batching multiple elements {#batching-multiple-elements}

When multiple elements in an update match the selector, the compiler function is called once for each element.

Let's say a fragment with three `.card` elements is inserted and compiled:

```html
<div class="fragment">
  <div class="card">...</div>
  <div class="card">...</div>
  <div class="card">...</div>
</div>
```

A compiler function for `.card` would be called three separate times:

```js
up.compiler('.card', function(card, data) {
  // called three times
})
```

If you would like the compiler to run _once_ for all matches within a render pass, use the `{ batch: true }` option:

```js
up.compiler('.card', { batch: true }, function(cards, datas) {
  console.debug(`We have ${cards.length} new cards`)
})
```

## Error handling {#errors}

It is safe to throw exceptions from a compiler or its [destructor](#destructor). A crashing compiler will _not_ interrupt a render pass or prevent other compilers on the same element.

Exceptions thrown by compiler functions are logged to the browser's [error console](https://developer.mozilla.org/en-US/docs/Web/API/console/error). Unpoly also emits an [`error` event on `window`](https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event).

See [errors in user code](https://unpoly.com/learn/render-lifecycle.md#errors-in-user-code) for details.

## Compilation order {#order}

Compilers are called in the order they were registered.

You can control the order by passing a `{ priority }` option. The default priority is `0`. Compilers with a higher priority are run first.

```js
up.compiler('.foo', { priority: 999 }, function() {
  // called before compilers with a lower priority
})
```

When a compiler sets an Unpoly attribute, this usually has no effect since attributes have already been evaluated. Use a [macro](https://unpoly.com/api/up.macro.md) instead.

See [Rendering lifecycle](https://unpoly.com/learn/render-lifecycle.md) for a full overview of Unpoly's rendering steps.

## Asynchronous compilers {#async}

Compiler functions can be [`async`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). This is useful when a compiler needs to fetch network resources or when calling a library with an asynchronous API:

```js
up.compiler('textarea.wysiwyg', async function(textarea) {
  let editor = await import('wysiwyg-editor')
  editor.init(textarea)
})
```

You can also use this to split up expensive tasks, giving the browser a chance to render and process user input:

```js
up.compiler('.element', async function(element) {
  doRenderBlockingWork(element)
  await scheduler.yield()
  doUserVisibleWork(element)
})
```

### Cleaning up async work {#async-destructors}

Like synchronous compilers, async compiler functions can return a [destructor function](#destructor):

```js
up.compiler('textarea.wysiwyg', async function(textarea) {
  let editor = await import('wysiwyg-editor')
  editor.init(textarea)
  return () => editor.destroy(textarea)
})
```

Unpoly guarantees that the destructor is called even if the element is destroyed before the compiler function terminates.

### Timing render-blocking mutations {#async-timing}

Unpoly will run the first [task](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) of every compiler function before allowing the browser to render DOM changes. If an async compiler function runs for multiple tasks, the browser will render between tasks. If you have render-blocking mutations that should be hidden from the user, they must happen in the first task.

<figure>
<img src="https://unpoly.com/images/api/compiler-tasks.svg" alt="Timing diagram of compiler tasks and browser renders">
<figcaption>

After Unpoly updates the DOM, it runs synchronous compilers and the first task of each async compiler. Only then does the browser render. The promise returned by [`up.render()`](https://unpoly.com/api/up.render.md) fulfills after this first task. The second task of an async compiler runs after the browser has rendered. `up.render().finished` fulfills only after that task is done.

</figcaption>
</figure>

Async compilers will not delay the promise returned by rendering functions [`up.render()`](https://unpoly.com/api/up.render.md) or [`up.layer.open()`](https://unpoly.com/api/up.layer.open.md).
Async compilers _will_ delay the promise returned by [`up.render().finished`](https://unpoly.com/learn/render-lifecycle.md#postprocessing) and [`up.hello()`](https://unpoly.com/api/up.hello.md).

## Registering compilers after booting {#late}

When you [deliver your JavaScript in multiple files](https://makandracards.com/makandra/498036-webpacker-loading-code-on-demand), you may register compilers after Unpoly has booted.

When compilers are registered after Unpoly has booted, they are run on current elements, but **only** if the compiler has the default priority.

If the compiler has a non-default [priority](#order), it is run on future fragments only. In this case, either remove the `{ priority }` option or manually call [`up.hello()`](https://unpoly.com/api/up.hello.md) on an element that should be [recompiled](https://unpoly.com/api/up.hello.md#recompiling-elements).

---

## Parameters {#parameters}

### selector {#selector}

(required)

The selector to match.

Type: string

### [options.priority=0] {#options.priority}

(optional)

The priority of this compiler.

Compilers with a higher priority are run first. Two compilers with the same priority are run in the order they were registered.

Type: number

### [options.batch=false] {#options.batch}

(optional)

If set to `true` and a fragment insertion contains multiple elements matching `selector`, the `compiler` function is only called once with all these elements.

Type: boolean

### compiler {#compiler}

(required)

The function to call when an element matching `selector` is inserted.

Up to three arguments can be accepted:

1. The newly inserted `element` matching the selector
2. Any [data attached to the element](https://unpoly.com/learn/data.md)
3. [Meta information](https://unpoly.com/learn/enhancing-elements.md#meta) about the current render pass

The function may return a [destructor function](https://unpoly.com/learn/enhancing-elements.md#destructor) that is called before it is removed from the DOM. The destructor function is called with the compiled element.

The compiler function can be [`async`](https://unpoly.com/api/up.compiler.md#async).

Type: Function(element, [data](https://unpoly.com/learn/data.md), meta): (Function|[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<Function>|[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises)<Function>|undefined)

