npm package: @jxpeng98/vitepress-pyodide
Description: VitePress plugin and components to run Python (Pyodide) in Markdown with shared page state.
Author: Jiaxin Peng
Latest version: 1.0.1

# vitepress-pyodide

Run Python (Pyodide) inline in VitePress. This package provides:

- Markdown-it plugins to turn code into interactive cells:
  - Fenced blocks: `` ```pyodide `` → interactive cell
  - HTML blocks: `<Pyodide>…</Pyodide>` → interactive cell (dedented, newlines preserved)
  - Vue components with a shared in‑browser interpreter per page and a tidy VitePress look

## Highlights

- Per‑page shared state: later cells can reuse variables from earlier cells
- Fresh state on new pages (SPA navigation): the first run clears globals
- Editor hidden by default; buttons: Run, Clear, Show/Hide Code
- Dark mode via VitePress tokens — no hardcoded colors
- Adaptive heights: editor/output avoid empty space, but can grow

## Table of Contents

- [vitepress-pyodide](#vitepress-pyodide)
  - [Highlights](#highlights)
  - [Table of Contents](#table-of-contents)
  - [Installation](#installation)
  - [Setup](#setup)
    - [VitePress Configuration (`.vitepress/config.(ts|mts)`)](#vitepress-configuration-vitepressconfigtsmts)
    - [VitePress Theme Enhancement (`.vitepress/theme/index.(ts|mts)`)](#vitepress-theme-enhancement-vitepressthemeindextsmts)
  - [Usage](#usage)
    - [Fenced Blocks](#fenced-blocks)
      - [Optional Metadata](#optional-metadata)
    - [HTML Form](#html-form)
    - [Loading Python Packages](#loading-python-packages)
      - [Pyodide Package Repository](#pyodide-package-repository)
      - [PyPI Wheels (micropip)](#pypi-wheels-micropip)
    - [Plots (matplotlib)](#plots-matplotlib)
  - [Component API](#component-api)
  - [Behavior Notes](#behavior-notes)
  - [Styling](#styling)
  - [Customize](#customize)

## Installation

```bash
pnpm add -D vitepress-pyodide
```

## Setup

### VitePress Configuration (`.vitepress/config.(ts|mts)`)

```ts
import { createPyodideMarkdownPlugin, createPyodideHtmlBlockPlugin } from 'vitepress-pyodide/markdown'

export default {
  markdown: {
    config(md) {
      // Default layout: vertical (output below). Use 'horizontal' for side‑by‑side.
      md.use(createPyodideMarkdownPlugin({ layout: 'vertical' }))
      md.use(createPyodideHtmlBlockPlugin({ layout: 'vertical' }))
    }
  }
}
```

### VitePress Theme Enhancement (`.vitepress/theme/index.(ts|mts)`)

```ts
import { enhanceAppWithPyodide } from 'vitepress-pyodide/client'

export default {
  enhanceApp(ctx) {
    enhanceAppWithPyodide(ctx)
  }
}
```

## Usage

### Fenced Blocks

Use ` ```pyodide ` to create an interactive Python cell.

```pyodide title="Define data"
data = [3, 3, 6, 9, 12]
print(sum(data), len(data))
```

#### Optional Metadata

Metadata can be added in the info string:

- **Title**: `title="My Cell"` or bare words after `pyodide` (e.g., ` ```pyodide My Cell `)
- **Layout**: `layout=horizontal` | `layout=vertical` or shorthand tokens: `horizontal`/`right`/`vertical`
- **Locale Override**: `locale="en"` | `"zh-CN"`

**Example:**

```pyodide layout=horizontal title="Horizontal (EN)" locale="en"
x = 41
print(f"x = {x}")
```

### HTML Form

Use `<Pyodide>…</Pyodide>` HTML blocks for interactive cells.

```markdown
<Pyodide title="中文示例" locale="zh-CN" layout="vertical">
print("你好，Pyodide！")
</Pyodide>
```

### Loading Python Packages

This plugin can load packages before a cell runs.

#### Pyodide Package Repository

For most packages in the Pyodide distribution (e.g., `numpy`, `pandas`, `matplotlib`), use the `packages` attribute:

**Fenced Block:**

```pyodide packages="pandas, matplotlib"
import pandas as pd
import matplotlib.pyplot as plt
```

**HTML Form:**

```html
<Pyodide packages="pandas, matplotlib">
import pandas as pd
import matplotlib.pyplot as plt
</Pyodide>
```

#### PyPI Wheels (micropip)

For pure-Python packages compatible with Pyodide (PyPI wheels), use the `# micropip:` directive at the top of the cell:

```pyodide title="Install from PyPI"
# micropip: XlsxWriter openpyxl
import micropip
# After the directive runs, your imports work as usual
import XlsxWriter, openpyxl
```

**Notes:**

- The directive `# micropip: ...` (or `# pip: ...`) triggers an installation phase before your code executes. It accepts space- or comma‑separated names.
- Not all PyPI packages have WebAssembly‑compatible wheels; consult [https://pyodide.org](https://pyodide.org).
- The plugin can still auto‑detect and load `matplotlib` when it sees `import matplotlib`.

### Plots (matplotlib)

Matplotlib needs a browser‑friendly output path. This plugin auto‑embeds figures after your code runs:

```pyodide packages="matplotlib" title="Plot"
import matplotlib.pyplot as plt
plt.plot([1,2,3])
plt.show()  # optional — figures are auto‑exported as SVG even without explicit show()
```

**How it works:**

- Before your code runs, the plugin patches `plt.show()` to export existing figures.
- After your code finishes, it exports any remaining figures.
- Figures are saved as SVG and embedded below the text output — no changes to your code required.
- If you prefer explicit control, you can still save and print a data URL yourself:

```pyodide packages="matplotlib"
import io, base64
import matplotlib.pyplot as plt
plt.plot([1,2,3])
buf = io.BytesIO(); plt.savefig(buf, format='svg', bbox_inches='tight'); buf.seek(0)
print('data:image/svg+xml;base64,' + base64.b64encode(buf.read()).decode())
```

## Component API

- `<Pyodide ...>` (wrapper) forwards to `<PyodideCell ...>` ensuring identical UI to fenced blocks
- **Props:**
  - `layout?`: `'vertical'` | `'horizontal'`
  - `locale?`: `string` (e.g., `'en'`, `'zh-CN'`)
  - `title?`: `string`
  - `code?`: `string` (optional initial code)
  - `code64?`: `string` (base64 initial code; used by plugins)

## Behavior Notes

- **Script semantics:** Only printed output appears. Bare expressions won’t display unless printed.
- **First run:** Downloads Pyodide (~10MB). Subsequent runs reuse the same instance.
- **State persistence:** Variables persist within the same page; navigating to another page starts fresh state.

## Styling

- Adapts to VitePress theme via CSS variables (no hardcoded colors)
- Editor/output heights auto‑fit content; users can drag the editor bigger

## Customize

- **Default layout per site:** Pass `{ layout: 'horizontal' }` to the plugins in config
- **Pyodide version/CDN:** Update `packages/vitepress-pyodide/src/runtime.ts`