---
title: When code downloads, and when it runs
description: The compiler splits handlers and updates into symbols, production builds pack them into a few preloaded chunks, and each symbol runs only when its trigger fires.
sidebar: { label: Lazy chunks }
---

Last page, every subscription and every handler turned out to be a small function. Where does that code live, and when does it reach the browser?

This page answers two questions that sound like one. When does the code **download**? When does it **run**? Markless gives them different answers.

## The problem

Downloads cost network time. Running code costs main-thread time. A page that waits for the first click to download its click code makes that click slow. A page that runs all its code at load makes the load slow.

Markless separates the two. In a production build, the code for first interactions downloads early. It still runs only when its event fires.

## The mental model

The compiler cuts your component into **symbols**. A symbol is one function with a stable ID, such as `symbol:0`. The compiler plans one symbol for each of these:

- an event handler, such as `onClick`
- a DOM update, such as the text that shows `count`
- an `attach={...}` behavior
- the derive function of a sync `computed()`, and the runner of an async one
- a `state()` initializer and a `shared()` seed
- a few more internal kinds, such as `@if` branch updates

A **chunk** is one JavaScript file that the browser downloads. One chunk can hold many symbols.

Think of a kitchen. That is an analogy. Packing a chunk is like a delivery. Running a symbol is like cooking one dish. The ingredients can arrive early, and the cook still waits for each order.

Press **+1** twice. Then press **Settings**. Watch the two lanes.

<UnderShelfFigure />

Notice that the first-use pack downloads before your first click. Clicks fill the ran lane and download nothing new.

## When code runs

A symbol runs only when its trigger fires. A click runs its handler. A write runs the updates that read the written path. An `onVisible` handler runs when its element scrolls into view.

At mount, no handler symbol runs. Markless's test for `render()` checks this. After mount, the list of loaded symbols is empty. One click loads exactly one symbol, `symbol:click`.

## When code downloads: packing

Production client builds turn on **packing** by default. Packing puts the lazily loaded modules into a few chunks instead of one chunk per module. The bundler cuts those chunks by what each page or route needs, and when:

| Pack | Holds | Downloads |
| --- | --- | --- |
| First use | Code that the page load and its first interactions run | With the page, through `modulepreload` |
| Navigation | Code that only a client-side navigation runs | On navigation intent, never on a landing page |
| Deferred | Code that no page load, first use, or navigation needs | On its first `import()` |

A `<link rel="modulepreload">` tag tells the browser to fetch and parse a module early. It does not run the module. A production build adds these tags for the handler chunks, at high fetch priority.

One packed chunk still runs lazily inside. Each module in it initializes only when something first imports it. A Markless bundler test proves this with two modules in one chunk.

Set `packing: false` to ship one chunk per module. Markless never packs dev builds.

:::note[So does nothing download before the click?]
Not in a production build. The code for first interactions downloads with the page. What waits for the click is running that code.
:::

## What a symbol can capture

A symbol runs later, after the component body has finished. On a server-rendered page, it also runs on a different machine. So it can only reach values that Markless can find again. The compiler checks every symbol when it compiles the file. A symbol can use:

- state and computed values, by graph reference
- element handles from `element()`
- props and `shared()` values
- module imports
- serializable constants, such as a string or a `Date`

Anything else fails the build. A DOM node in a local variable is a common case:

```tsrx Panel.tsrx
const panel = document.querySelector('#panel');

// MARKLESS_EVENT_HANDLER_EMIT_UNSUPPORTED
<button onClick={() => panel?.scrollIntoView()}>Show</button>
```

Use an element handle instead. This example comes from Markless's codegen size corpus:

```tsrx ElementBehavior.tsrx
let field = element();
let status = state('idle');

<input el={field} />
<button onClick={() => { field.focus(); status = 'focused'; }}>Focus</button>
```

A local helper function fails the same way. Move it to module scope, or turn its result into a `computed()`.

| Symbol kind | Error code |
| --- | --- |
| Event handler or callback prop | `MARKLESS_EVENT_HANDLER_EMIT_UNSUPPORTED` |
| `attach={...}` behavior | `MARKLESS_BEHAVIOR_SYMBOL_EMIT_UNSUPPORTED` |
| Any other symbol | `MARKLESS_CAPTURE_UNSUPPORTED_VALUE` |

:::tip[How do I see what ran?]
Open the page with `?markless-log`, or set `localStorage.marklessLog = "1"`. On `localhost` the log is on by default. It reports what ran at load and on each interaction. It counts code that ran, not code that downloaded.
:::

**Next:** The graph and its symbols are data and small functions. Does a browser have to run them? [Native targets →](/how-it-works/native-targets)
