shiki-tinymist
v0.1.0
Published
A Shiki transformer that enriches Typst code blocks with Tinymist WASM language-service hovers.
Maintainers
Readme
shiki-tinymist
A Shiki transformer for Typst code blocks powered by the Tinymist WASM language server.
The package queries the Tinymist WASM LSP bridge before Shiki renders a code
block, then attaches hover data at // ^? marker positions.
Install
npm install shiki-tinymist shikiTinymist's WASM runtime is bundled with shiki-tinymist, so consumers do not
need to install a separate tinymist package.
Usage
import { codeToHtml } from "shiki";
import { createTinymistTransformer } from "shiki-tinymist";
const code = `#let answer = 42
// ^?`;
const transformer = await createTinymistTransformer(code);
const html = await codeToHtml(code, {
lang: "typst",
theme: "vitesse-dark",
transformers: [transformer],
});When used in Markdown integrations, set explicitTrigger: true to only run on
code fences with tinymist or typst-lsp in the meta string.
```typst tinymist
#let answer = 42
// ^?
```Import the default styles:
import "shiki-tinymist/style-rich.css";For scrollable code containers, install the small floating client so hover popups are moved outside the code block before they are positioned:
import { initTinymistFloating } from "shiki-tinymist/client";
initTinymistFloating();Framework Adapters
Tinymist queries are asynchronous, so the framework adapters prepare Typst fences before the normal Markdown renderer finishes.
Rehype
import rehypeStringify from "rehype-stringify";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import { unified } from "unified";
import { rehypeTinymist } from "shiki-tinymist/rehype";
const html = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeTinymist({ explicitTrigger: true }))
.use(rehypeStringify)
.process(markdown);markdown-it
markdown-it renders synchronously, so the adapter adds an async
renderTinymist helper instead of replacing the sync fence renderer.
import MarkdownIt from "markdown-it";
import { markdownItTinymist } from "shiki-tinymist/markdown-it";
const md = new MarkdownIt({ html: true });
md.use(markdownItTinymist({ explicitTrigger: true }));
const html = await md.renderTinymist(markdown);VitePress
import { defineConfig } from "vitepress";
import { vitepressTinymist } from "shiki-tinymist/vitepress";
export default defineConfig({
vite: {
plugins: [vitepressTinymist({ explicitTrigger: true })],
},
});Astro
import { defineConfig } from "astro/config";
import { astroTinymist } from "shiki-tinymist/astro";
export default defineConfig({
integrations: [astroTinymist({ explicitTrigger: true })],
});MDX
import { mdxTinymist } from "shiki-tinymist/mdx";
export default {
rehypePlugins: [mdxTinymist({ explicitTrigger: true })],
};Next
import createMDX from "@next/mdx";
import { nextTinymist } from "shiki-tinymist/next";
const withMDX = createMDX({
options: {
rehypePlugins: [nextTinymist({ explicitTrigger: true })],
},
});
export default withMDX({
pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
});Nuxt
export default defineNuxtConfig({
css: ["shiki-tinymist/style-rich.css"],
content: {
build: {
markdown: {
rehypePlugins: {
"shiki-tinymist/nuxt": { explicitTrigger: true },
},
},
},
},
});Full-feature examples for every adapter live in examples/.
Documentation Site
The documentation site is an Astro Starlight project in docs/. Adapter pages
are generated from docs/src/data/adapters.mjs and reuse the runnable fixtures
in examples/, so adding an adapter updates the sidebar, overview, preview, and
source panels from one registry.
npm run docs:dev
npm run docs:buildThe documentation site uses Astro and requires Node.js 22.12 or newer.
GitHub Pages deployment is still handled by .github/workflows/pages.yml. The
workflow builds the Starlight site with the GitHub Pages base path and uploads
docs/dist.
Marker Syntax
Use a Typst line comment under the target code.
Hover query:
#let answer = 42
// ^?Completion query:
#ans
// ^|Static highlight:
#answer
// ^^^^^^Marker lines are removed before Shiki highlights the code.
Twoslash Compatibility
shiki-tinymist supports the Twoslash-style notation that maps cleanly to
Typst/Tinymist:
| Notation | Status | Behavior |
| ----------------------------------------- | ---------- | ------------------------------------------------------------------------------------------ |
| // ^? | supported | Queries Tinymist hover and renders it on the target range. |
| // ^\| | supported | Queries Tinymist completions and renders up to 5 inline items by default. |
| // ^^^ | supported | Highlights the target range without an LSP query. |
| // ---cut--- / // ---cut-before--- | supported | Hides previous lines from output while keeping them in the Tinymist query. |
| // ---cut-after--- | supported | Hides following lines from output while keeping them in the Tinymist query. |
| // ---cut-start--- / // ---cut-end--- | supported | Hides paired output ranges while keeping them in the Tinymist query. |
| // @filename: name.typ | supported | Splits the query into virtual files; the filename comment remains visible unless cut away. |
| // @noErrors | supported | Suppresses rendered diagnostics. |
| // @errors: text | supported | Renders diagnostics whose code or message includes one of the listed tokens. |
| // @showEmit / // @showEmittedFile | recognized | Removed from output, but no Typst emit replacement is produced. |
| Other // @name options | recognized | Removed from output and exposed in ParsedTinymistCode.directives. |
Development
Tinymist is tracked as a git submodule. Its WASM package is intentionally not
built by npm run build; build it manually when setting up the repo or when
updating the submodule.
git submodule update --init --recursive
npm run tinymist:build
npm install
npm run demo
npm test
npm run typecheck
npm run buildnpm run demo writes demo/index.html.
Why a Prepare Step?
Shiki transformer hooks are synchronous, while WASM/LSP calls are asynchronous.
The package therefore queries Tinymist before calling codeToHtml, then passes
a synchronous transformer to Shiki.
