
# HTML preview

`htmlPreview` shows an HTML file next to itself: a preview button and a lock in the tab bar, and
a preview tab that renders the file itself as the frame. It keeps up as you type.

```ts
import { htmlPreview } from "codelet/extensions/html";
import { FileSystem, Workbench } from "codelet/workbench";

const workbench = new Workbench({
  parent: document.getElementById("app")!,
  fs: new FileSystem({ "/index.html": "<h1>Hello</h1>" }),
  extensions: [htmlPreview],
});
```

`htmlPreview` takes no options. There's no renderer to fetch — an HTML file previews as itself.

## Try it

Open the preview over `index.html` with the eye button, then click the lock to let its script
run.

::codelet-playground{mode="workbench" extensions="html-preview" active="index.html" height="460"}

```html [index.html]
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="/style.css" />
  </head>
  <body>
    <h1>codelet</h1>
    <p>A minimal web IDE you can embed in your own app.</p>
    <p id="when">The script has not run.</p>
    <script src="/app.js"></script>
  </body>
</html>
```

```css [style.css]
body {
  font-family: ui-sans-serif, system-ui, sans-serif;
  padding: 8px 16px;
}

h1 {
  color: #e5484d;
}
```

```js [app.js]
document.getElementById("when").textContent =
  `The script ran at ${new Date().toLocaleTimeString()}.`;
```

::

## Opening a preview

The same four ways in as the Markdown preview, running **Open Preview**: the tab bar, a Markdown
row's context menu in the explorer, a tab's own context menu, and the command palette
(`HTML: Open Preview`). Running it on a file already showing a preview reveals that tab rather
than opening a second one. The way back is **Open Source**, next to the lock, on the preview's
own tab.

## The lock

A page is a program, so a preview opens restricted: the frame is built without scripts, and
nothing in the document runs — no `<script>`, no `onclick`, no `javascript:` link. The lock
button on the preview's tab bar toggles that for the file it's over, and the frame rebuilds with
scripts allowed.

Trust lasts as long as the extension is running: stopping and starting it from the Extensions
view restricts every file again.

A restricted page with something to run says so along its foot, so a script that's blocked isn't
mistaken for a preview that's broken. A page with no scripts at all previews identically either
way, and says nothing.

::warning
Trust only ever adds scripts. The frame never gets same-origin access, so a trusted page still
has an opaque origin: no cookies, no storage, no form submissions, no popups, and no reach into
the page the workbench is running in. Nothing a previewed page does can touch the workbench,
trusted or not.
::

## Stylesheets and scripts from the tree

A frame has no origin and the workbench serves no files, so a relative `href` in a previewed page
names nothing a browser could fetch. Every `<link rel="stylesheet">` and `<script src>` that
points at a path — `/src/styles.css`, `./app.js` — is answered out of the workbench's own tree
instead, carrying that file's text as a `data:` URI. Editing the stylesheet in another tab
redraws the preview.

Only `.css`, `.js` and `.mjs` are answered this way, being the files the tree holds the whole of.
An image is not — what a media file holds in a workbench is _where_ the image is, a URL or a
`data:` URI, the same thing [`media`](/extensions/media) reads — so write that address into the
`<img src>` and it works as it stands.

## Not the same as the HTML language server

`htmlPreview` only shows a file's contents — it doesn't touch the editor. For diagnostics,
completion and hover while editing the markup itself, use the HTML language server instead. The
two are independent and can run together.

:read-more{to="/extensions/lsp"}

:read-more{to="/extensions"}
