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.

Features
  • 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
Build Consideration
Files must match the 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:

vue

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:

ts

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:

ts

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:

ts

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:

ts

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:

js

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 icon
  • noFormat - Disable code formatting
  • lines - Show line numbers
  • noHeader - 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: false for 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"

Code Snippet Error
Cannot load code: /app/nonexistent.ts

Invalid URL

::prose-code-snippet{url="https://invalid-url-404.com/code.js" language="javascript"}
::

Shows error callout with the URL.

Code Snippet Error
Cannot load code: https://invalid-url-404.com/code.js

Best Practices

Documentation Tips
  1. Keep Docs in Sync - Reference actual source files instead of copying code
  2. Show Relevant Code - Use start and offset to show only what matters
  3. Add Context - Always use title to explain what the code does
  4. Highlight Key Lines - Use highlights to draw attention to important parts
  5. Version Control - Reference specific branches/tags in URLs for stable examples
  6. Test Paths - Ensure file paths match your glob patterns
  7. Performance - For large apps, consider creating a /docs-examples/ directory with curated files

Code Snippet API

NameType Default Required Description
languagestringYesProgramming language for syntax highlighting
filestringundefinedNoRelative path to a file in your project to import Must match a pattern in the globalThis._importMeta_.glob array
urlstringundefinedNoExternal URL to fetch code from
titlestringundefinedNoOptional title displayed above the code block
highlightsstringundefinedNoLine numbers or ranges to highlight (comma-separated)
metastringundefinedNoAdditional metadata for the code block (passed to ProsePre)
startstring | numberundefinedNoStarting line number to extract from the file (1-indexed)
offsetstring | numberundefinedNoNumber of lines to extract from the starting line