Prose
Code Snippet
Dynamically import and display code from your project files or external URLs without duplicating content in your documentation.
Overview
The ProseCodeSnippet component allows you to reference actual source files from your project or external URLs, ensuring your documentation always shows the latest code without manual updates. Perfect for keeping docs in sync with your codebase.
You will need to copy the source code and add it to your own project to use it.
- Dynamic Imports - Load code directly from your project files
- External URLs - Fetch code from GitHub, GitLab, or any URL
- Line Extraction - Show specific line ranges from large files
- Syntax Highlighting - Full language support via Shiki
- Auto-sync - Documentation updates automatically when source code changes
- Performance - Lazy loads files only when needed
import.meta.glob patterns in the component. The current setup includes all .vue, .ts, .css, and .json files from /app/** (excluding test files). Adjust patterns as needed for your use case.Basic Usage
Import a component from your project:
Browser Frame Component
<script setup lang="ts">
defineProps<{
/**
* URL displayed in the address bar.
* @example "https://example.com"
*/
url?: string;
}>();
const emit = defineEmits<{
/**
* Emitted when the close (red) traffic light button is clicked.
*/
close: [];
/**
* Emitted when the minimize (yellow) traffic light button is clicked.
*/
minimize: [];
/**
* Emitted when the maximize (green) traffic light button is clicked.
*/
maximize: [];
/**
* Emitted when the sidebar/panel toggle button is clicked.
*/
panel: [];
/**
* Emitted when the back navigation button is clicked.
*/
back: [];
/**
* Emitted when the forward navigation button is clicked.
*/
forward: [];
/**
* Emitted when the page refresh button is clicked.
*/
refresh: [];
/**
* Emitted when the download button is clicked.
*/
download: [];
/**
* Emitted when the share button is clicked.
*/
share: [];
/**
* Emitted when the new tab button is clicked.
*/
newTab: [];
/**
* Emitted when the copy button is clicked.
*/
copy: [];
}>();
defineSlots<{
/**
* Replaces the three traffic light buttons (close, minimize, maximize).
*/
"traffic-lights": () => void;
/**
* Replaces the sidebar panel toggle and back/forward navigation buttons.
*/
"nav-controls": () => void;
/**
* Replaces the address bar area.
*/
"address-bar": () => void;
/**
* Replaces the right-side action buttons (download, share, new tab, copy).
*/
actions: () => void;
/**
* Content rendered inside the browser frame body.
*/
default: () => void;
}>();
</script>
<template>
<div class="w-full overflow-hidden rounded-xl border shadow-md">
<div class="flex items-center gap-3 border-b px-3 py-2">
<!-- Traffic lights -->
<div class="flex shrink-0 items-center gap-1.5">
<slot name="traffic-lights">
<button
type="button"
aria-label="Close"
class="size-3 rounded-full bg-red-500 transition-opacity hover:opacity-80"
@click="emit('close')"
/>
<button
type="button"
aria-label="Minimize"
class="size-3 rounded-full bg-yellow-400 transition-opacity hover:opacity-80"
@click="emit('minimize')"
/>
<button
type="button"
aria-label="Maximize"
class="size-3 rounded-full bg-green-500 transition-opacity hover:opacity-80"
@click="emit('maximize')"
/>
</slot>
</div>
<!-- Panel + back/forward -->
<div class="flex shrink-0 items-center gap-0.5">
<slot name="nav-controls">
<button
type="button"
aria-label="Toggle sidebar"
class="rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
@click="emit('panel')"
>
<Icon name="lucide:panel-left" class="size-4" />
</button>
<button
type="button"
aria-label="Go back"
class="rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
@click="emit('back')"
>
<Icon name="lucide:chevron-left" class="size-4" />
</button>
<button
type="button"
aria-label="Go forward"
class="rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
@click="emit('forward')"
>
<Icon name="lucide:chevron-right" class="size-4" />
</button>
</slot>
</div>
<!-- Address bar -->
<div class="flex min-w-0 flex-1 justify-center">
<slot name="address-bar">
<div
class="flex w-full max-w-sm items-center gap-1.5 rounded-md border bg-background px-2.5 py-1 text-xs text-muted-foreground dark:border-muted/50 dark:bg-muted/50"
>
<Icon name="lucide:shield" class="size-3 shrink-0" />
<span class="min-w-0 flex-1 truncate text-center">{{ url ?? "about:blank" }}</span>
<button
type="button"
aria-label="Refresh"
class="shrink-0 transition-opacity hover:opacity-70"
@click="emit('refresh')"
>
<Icon name="lucide:rotate-cw" class="size-3" />
</button>
</div>
</slot>
</div>
<!-- Right-side actions -->
<div class="flex shrink-0 items-center gap-0.5">
<slot name="actions">
<button
type="button"
aria-label="Download"
class="rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
@click="emit('download')"
>
<Icon name="lucide:arrow-down-to-line" class="size-4" />
</button>
<button
type="button"
aria-label="Share"
class="rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
@click="emit('share')"
>
<Icon name="lucide:share" class="size-4" />
</button>
<button
type="button"
aria-label="New tab"
class="rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
@click="emit('newTab')"
>
<Icon name="lucide:plus" class="size-4" />
</button>
<button
type="button"
aria-label="Copy"
class="rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
@click="emit('copy')"
>
<Icon name="lucide:copy" class="size-4" />
</button>
</slot>
</div>
</div>
<div>
<slot mdc-unwrap="p" />
</div>
</div>
</template>
Import Project Files
TypeScript Utilities
Reference ts files:
App Configuration
const repoBase = "https://github.com/BayBreezy/docd";
export default defineAppConfig({
docd: {
github: {
repo: repoBase,
branch: "main",
contentDir: "docs/content",
},
ui: {
borderType: "dashed",
header: {
title: "Docd",
logo: {
alt: "Docd Logo",
light: "/logos/docd-logo-dark.svg",
dark: "/logos/docd-logo-light.svg",
favicon: "/favicon.svg",
},
},
extraLinks: [
{ icon: "lucide:star", label: "Star on GitHub", external: true, href: repoBase },
{
icon: "lucide:bug",
label: "Report an issue",
external: true,
href: `${repoBase}/issues/new`,
},
],
transition: {
name: "fade",
},
},
},
});
Extract Specific Lines
Use start and offset to show only relevant portions of large files:
App Configuration
docd: {
github: {
repo: repoBase,
branch: "main",
contentDir: "docs/content",
},
The example above starts at line 4 and shows the next 6 lines.
Highlight Lines
Combine with the highlights prop to emphasize important code:
App Configuration
export default defineAppConfig({
docd: {
github: {
repo: repoBase,
branch: "main",
contentDir: "docs/content",
},
ui: {
borderType: "dashed",
header: {
title: "Docd",
logo: {
alt: "Docd Logo",
light: "/logos/docd-logo-dark.svg",
dark: "/logos/docd-logo-light.svg",
favicon: "/favicon.svg",
},
},
extraLinks: [
{ icon: "lucide:star", label: "Star on GitHub", external: true, href: repoBase },
{
icon: "lucide:bug",
label: "Report an issue",
external: true,
href: `${repoBase}/issues/new`,
},
],
transition: {
name: "fade",
},
},
},
});
External URLs
GitHub Raw Content
Load code directly from GitHub:
Nuxt useState Source
import { isRef, toRef } from 'vue'
import type { Ref } from 'vue'
import { useNuxtApp } from '../nuxt'
import { toArray } from '../utils'
// @ts-expect-error virtual file
import { useStateDefaults } from '#build/nuxt.config.mjs'
const useStateKeyPrefix = '$s'
/**
* Create a global reactive ref that will be hydrated but not shared across ssr requests
* @since 3.0.0
* @param key a unique key ensuring that data fetching can be properly de-duplicated across requests
* @param init a function that provides initial value for the state when it's not initiated
*/
export function useState<T> (key?: string, init?: (() => T | Ref<T>)): Ref<T>
export function useState<T> (init?: (() => T | Ref<T>)): Ref<T>
export function useState<T> (...args: any): Ref<T> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
const [_key, init] = args as [string, (() => T | Ref<T>)]
if (!_key || typeof _key !== 'string') {
throw new TypeError('[nuxt] [useState] key must be a string: ' + _key)
}
if (init !== undefined && typeof init !== 'function') {
throw new Error('[nuxt] [useState] init must be a function: ' + init)
}
const key = useStateKeyPrefix + _key
const nuxtApp = useNuxtApp()
const state = toRef(nuxtApp.payload.state, key)
// Register the init function for reset support
if (init) {
nuxtApp._state[key] ??= { _default: init }
}
if (state.value === undefined && init) {
const initialValue = init()
if (isRef(initialValue)) {
// vue will unwrap the ref for us
nuxtApp.payload.state[key] = initialValue
return initialValue as Ref<T>
}
state.value = initialValue
}
return state
}
export interface ClearNuxtStateOptions {
/**
* Reset the state to the initial value provided by the `init` function of `useState`
* instead of setting it to `undefined`.
*
* When not specified, this defaults to the value of `experimental.defaults.useState.resetOnClear`
* in your Nuxt config (which defaults to `true` with `compatibilityVersion: 5`).
*/
reset?: boolean
}
/** @since 3.6.0 */
export function clearNuxtState (
keys?: string | string[] | ((key: string) => boolean),
opts?: ClearNuxtStateOptions,
): void {
const reset = opts?.reset ?? useStateDefaults.resetOnClear
const nuxtApp = useNuxtApp()
const _allKeys = Object.keys(nuxtApp.payload.state)
.filter(key => key.startsWith(useStateKeyPrefix))
.map(key => key.substring(useStateKeyPrefix.length))
const _keys: string[] = !keys
? _allKeys
: typeof keys === 'function'
? _allKeys.filter(keys)
: toArray(keys)
for (const _key of _keys) {
const key = useStateKeyPrefix + _key
if (key in nuxtApp.payload.state) {
if (reset && nuxtApp._state[key]) {
nuxtApp.payload.state[key] = nuxtApp._state[key]._default()
} else {
nuxtApp.payload.state[key] = undefined
}
}
}
}
External Examples
Pull examples from documentation sites or CDNs.
This example fetches the Vue 3 ESM build from a CDN and displays lines 1-20:
Vue 3 ESM Build
/**
* vue v3.5.33
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
// @__NO_SIDE_EFFECTS__
function makeMap(str) {
const map = /* @__PURE__ */ Object.create(null);
for (const key of str.split(",")) map[key] = 1;
return (val) => val in map;
}
const EMPTY_OBJ = Object.freeze({}) ;
const EMPTY_ARR = Object.freeze([]) ;
const NOOP = () => {
};
const NO = () => false;
const isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // uppercase letter
(key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);
const isModelListener = (key) => key.startsWith("onUpdate:");
Advanced Meta Options
Use the meta prop to pass additional options to the code block:
App Configuration
const repoBase = "https://github.com/BayBreezy/docd";
export default defineAppConfig({
docd: {
github: {
repo: repoBase,
branch: "main",
contentDir: "docs/content",
},
ui: {
borderType: "dashed",
header: {
title: "Docd",
logo: {
alt: "Docd Logo",
light: "/logos/docd-logo-dark.svg",
dark: "/logos/docd-logo-light.svg",
favicon: "/favicon.svg",
},
},
extraLinks: [
{ icon: "lucide:star", label: "Star on GitHub", external: true, href: repoBase },
{
icon: "lucide:bug",
label: "Report an issue",
external: true,
href: `${repoBase}/issues/new`,
},
],
transition: {
name: "fade",
},
},
},
});
Available meta options:
icon=<name>- Custom file iconnoFormat- Disable code formattinglines- Show line numbersnoHeader- Hide title header
Performance Considerations
Bundle Size Impact
The import.meta.glob pattern includes files in your build. Be strategic:
✅ Good Practices:
- Use specific file extensions:
*.{vue,ts}instead of* - Exclude test files:
!**/*.test.ts - Only include directories you'll reference in docs
- Use
eager: falsefor lazy loading (already configured)
❌ Avoid:
- Overly broad patterns like
/app/**/*(includes everything) - Including asset files (images, videos)
- Globbing node_modules or build outputs
Current Configuration Impact
With the default patterns (/app/**/*.{vue,ts,css,json}), expect:
- Small projects (<100 files): ~100-200KB added to bundle
- Medium projects (100-500 files): ~200KB-1MB added to bundle
- Large projects (500+ files): 1MB+ added to bundle
Files are lazy-loaded, so only referenced snippets are downloaded to the client.
URL Fetching for Large Files
For very large files or files outside your project:
::prose-code-snippet{url="/api/files/large-schema.json" language="json"}
::
This avoids bundling and fetches on-demand.
Error Handling
The component handles errors gracefully:
File Not Found
::prose-code-snippet{file="/app/nonexistent.ts" language="typescript"}
::
Shows error callout: "Cannot load code: /app/nonexistent.ts"
/app/nonexistent.tsInvalid URL
::prose-code-snippet{url="https://invalid-url-404.com/code.js" language="javascript"}
::
Shows error callout with the URL.
https://invalid-url-404.com/code.jsBest Practices
- Keep Docs in Sync - Reference actual source files instead of copying code
- Show Relevant Code - Use
startandoffsetto show only what matters - Add Context - Always use
titleto explain what the code does - Highlight Key Lines - Use
highlightsto draw attention to important parts - Version Control - Reference specific branches/tags in URLs for stable examples
- Test Paths - Ensure file paths match your glob patterns
- Performance - For large apps, consider creating a
/docs-examples/directory with curated files
Code Snippet API
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
language | string | — | Yes | Programming language for syntax highlighting |
file | string | undefined | No | Relative path to a file in your project to import Must match a pattern in the globalThis._importMeta_.glob array |
url | string | undefined | No | External URL to fetch code from |
title | string | undefined | No | Optional title displayed above the code block |
highlights | string | undefined | No | Line numbers or ranges to highlight (comma-separated) |
meta | string | undefined | No | Additional metadata for the code block (passed to ProsePre) |
start | string | number | undefined | No | Starting line number to extract from the file (1-indexed) |
offset | string | number | undefined | No | Number of lines to extract from the starting line |