Skip to content

Code Play#

View Markdown

このページは @ox-content/code-playJavaScriptTypeScriptRustGo を有効にします。それ以外の言語は、サイト側で opt-in するまで通常の code fence のままです。Code Play がないページは通常の docs ページのままで、 ox-code-play.js を読み込みません。

プラグインを有効にする#

import { oxContent } from "@ox-content/vite-plugin";
import { codePlay } from "@ox-content/code-play";

export default {
  plugins: [
    oxContent({ highlight: true }),
    codePlay({
      languages: {
        javascript: true,
        typescript: { execute: true, typecheck: true },
        rust: true,
        go: true,
      },
      ui: "default",
      viewers: { config: true, stdio: true, stderr: true, provenance: true, timing: true },
    }),
  ],
};

TypeScript サンプル#

const message: string = "hello from Code Play";
console.log(message);
console.warn("this warning is a stderr chunk");

サンプルごとの config#

play-<config-key>=... は、そのサンプルだけ言語 config を上書きします。

const label = "works without an explicit type annotation";
console.log(label.toUpperCase());

Rust サンプル#

公式 Rust playground adapter で動きます。小さな Markdown 文字列から見出しを集め、 slug を検証してからナビゲーション先を出力します。

#[derive(Debug, PartialEq, Eq)]
struct Heading {
    level: usize,
    text: String,
    slug: String,
}

fn collect_headings(markdown: &str) -> Vec<Heading> {
    markdown
        .lines()
        .filter_map(|line| {
            let trimmed = line.trim_start();
            let level = trimmed.chars().take_while(|&ch| ch == '#').count();
            if level == 0
                || level > 6
                || !trimmed
                    .as_bytes()
                    .get(level)
                    .is_some_and(|byte| byte.is_ascii_whitespace())
            {
                return None;
            }
            let text = trimmed[level..].trim();
            Some(Heading {
                level,
                text: text.to_string(),
                slug: slugify(text),
            })
        })
        .collect()
}

fn slugify(text: &str) -> String {
    let mut slug = String::new();
    let mut pending_dash = false;

    for ch in text.chars().flat_map(char::to_lowercase) {
        if ch.is_ascii_alphanumeric() {
            if pending_dash && !slug.is_empty() {
                slug.push('-');
            }
            slug.push(ch);
            pending_dash = false;
        } else if ch.is_whitespace() || matches!(ch, '-' | '_' | ':' | '/') {
            pending_dash = true;
        }
    }

    if slug.is_empty() {
        "section".to_string()
    } else {
        slug
    }
}

fn main() {
    let markdown = "# Code Play\n\n## Rust Runner\n\n### Stdio & Timing";
    let headings = collect_headings(markdown);
    assert_eq!(
        headings.iter().map(|heading| heading.slug.as_str()).collect::<Vec<_>>(),
        ["code-play", "rust-runner", "stdio-timing"]
    );

    for heading in &headings {
        println!("h{} {} -> #{}", heading.level, heading.text, heading.slug);
    }
}

Go サンプル#

Go playground adapter で vet も有効にして動きます。fenced code block を言語ごとに 集計し、ソートした結果を出力します。

package main

import (
	"fmt"
	"sort"
	"strings"
)

type Fence struct {
	Language string
	Lines    int
}

func collectFences(markdown string) []Fence {
	var fences []Fence
	inFence := false
	current := Fence{Language: "text"}

	for _, line := range strings.Split(markdown, "\n") {
		if strings.HasPrefix(line, "```") {
			if inFence {
				fences = append(fences, current)
				inFence = false
				current = Fence{Language: "text"}
				continue
			}
			language := strings.TrimSpace(strings.TrimPrefix(line, "```"))
			if language == "" {
				language = "text"
			}
			current = Fence{Language: language}
			inFence = true
			continue
		}
		if inFence {
			current.Lines++
		}
	}

	return fences
}

func main() {
	markdown := strings.Join([]string{
		"# Samples",
		"",
		"```go",
		`fmt.Println("ok")`,
		"```",
		"",
		"```rust",
		`println!("ok");`,
		"```",
	}, "\n")

	fences := collectFences(markdown)
	sort.Slice(fences, func(i, j int) bool {
		return fences[i].Language < fences[j].Language
	})

	if len(fences) != 2 {
		panic("expected two fenced code blocks")
	}

	for _, fence := range fences {
		fmt.Printf("%s: %d line(s)\n", fence.Language, fence.Lines)
	}
}

実行時エラー#

console.log("before");
throw new Error("boom from the example");

Headless usage#

import { createCodePlay } from "@ox-content/code-play";

const play = createCodePlay({ languages: { typescript: true } });
const session = play.createSession({
  language: "ts",
  code: "const n: number = 1;",
});

const result = await session.run();
result.stdio;
result.stdout;
result.stderr;
result.provenance.compile;
result.provenance.execute;
result.timing.phases;

RunActionState ヘルパーは idle、running、result、error、offline を表します。 transport / CORS の失敗は status: "offline" です。

詳しくは @ox-content/code-playロードマップ を見てください。

Last updated: