mdmark

拡張を登録する

registerExtension で印を足す手順。TypeScript を推奨し、配る前に JavaScript へ変換する

使う側のアプリが自分の印を文書に書けるようにするとき、その印を描くモジュールを足すときに実行する。

前提

手順

拡張のモジュールを書く

draw を書く

import type { MdmarkMarkArgs } from "./mdmark/index.js";

export function draw(slot: HTMLElement, value: MdmarkMarkArgs): void {
  slot.textContent = `こんにちは、${value.args[0]}`;
}

拡張は TypeScript と JavaScript のどちらでも書ける。 TypeScript で書くことを推奨する。 draw の引数に配布物の型が付き、書き間違いを配る前に型検査で見つけられる。 slot は印の場所の要素、value.args は キー=値 でない引数を書いた順に並べたもの、value.keys は キー=値 の引数。 draw は Promise を返す非同期でもよい。 draw が例外を投げると、その印の場所だけが知らせの枠に替わる。

JavaScript で書くときは型を付けて点検する

// @ts-check
/** @type {import("../mdmark/index.js").MdmarkExtension} */
const extension = {
  name: "greet",
  directive: "greet",
  load: async () => ({
    draw(slot, value) {
      slot.textContent = `こんにちは、${value.args[0] ?? ""}`;
    },
  }),
};

export default extension;

JavaScript は変換を挟まずに、先頭の // @ts-check と JSDoc で型を付けて書く。 型は TypeScript と同じ配布物の型定義を読む。 npx tsc --noEmit で点検すると、型を誤った行がファイルと行つきで出る。 mdmark init --js が、この形の雛形と JavaScript も点検する tsconfig.json を置く(雛形を置く)。 固有の部品と CSS を文書に当てる手順は 固有の部品とCSSを当てる。

linter で点検する

npx @biomejs/biome check extensions

拡張を書くときは、TypeScript と JavaScript のどちらでも linter を使う。 型検査で見つからない、使っていない変数や書き方の誤りを、配る前に見つけられる。 linter は Biome を推奨する。 1 つのコマンドで linter と整形の点検ができる。

配る

JavaScript に変換する

npx esbuild extensions/greet.ts --format=esm --outfile=public/extensions/greet.js

load が import() で読むのはブラウザなので、配るのは JavaScript にしたファイルだけ。 TypeScript のファイルは配らない。 変換の道具は使う側で選ぶ。 上は esbuild の例。

登録する

registerExtension を呼ぶ

import { registerExtension } from "https://cdn.jsdelivr.net/gh/shuhei1101/mdmark@{タグ}/dist/index.js";

registerExtension({
  name: "greet",
  directive: "greet",
  load: () => import("/extensions/greet.js"),
});

{タグ} は CSS を読み込んだタグに置き換える。 mount や update で描く前に呼ぶ。 描いた後に登録した印は、次の mount・update から効く。 name は知らせの枠・書き方の警告・console.error に出す名前、directive は印の名前。 directive は英小文字で始まり、英小文字・数字・- だけを持つ。 features と記法の印(table・tabs・steps・section・row-detail・fig・toc・glossary・view)と、登録済みの拡張の directive は使えず、Error を投げて登録しない。 書式に合わない項目があると TypeError を投げて登録しない。 設定の libraries や図の部品の印(flow・sequence・chart・param)に同じ名前があるときは、拡張を優先する。

引数を検査する

import * as v from "valibot";

registerExtension({
  name: "greet",
  directive: "greet",
  load: () => import("/extensions/greet.js"),
  schema: v.object({
    args: v.strictTuple([v.pipe(v.string(), v.regex(/^[a-z]+$/))]),
    keys: v.record(v.string(), v.string()),
  }),
});

印の引数を検査するときだけ schema を渡す。 schema は Standard Schema で、zod・valibot など ~standard を持つ検査ライブラリの schema をそのまま渡せる。 mdmark は検査ライブラリを束ねない。 検査する値は { args: string[]; keys: Record<string, string> }。 valibot の tuple は余る引数を許すため、引数の数を限るときは strictTuple を使う。 引数が合わない印の場所は、モジュールを読まずに書き方の警告に替わる。

文書に印を書く

拡張の印を書く

<!-- greet: world lang=ja -->

draw は { args: ["world"], keys: { lang: "ja" } } を受け取る。 文書にその印があるときだけ、mount と update が load を呼ぶ。 同じ文書の同じ拡張の印は、load を 1 回だけ呼ぶ。 render は拡張の印を読み込み中の枠に置き換えるだけで、load も検査も呼ばない。

部品の定義を渡す

<!-- greet -->

```json
{ "title": "段取り", "items": ["a", "b", "c"] }
```

<!-- /greet -->

印を範囲(<!-- greet --> 〜 <!-- /greet -->)で書き、範囲に言語 json のコードブロックを 1 つ置くと、その JSON を読んだ値が value.definition に入る。 schema の検査と draw は同じ値を受け取る。 定義の中身の形は schema が確かめる。 コードブロックが 2 つ以上あるか、JSON として読めないときは、schema も load も呼ばず、印の場所が書き方の警告に替わる。

部品を描く関数を呼ぶ

import { mountView } from "https://cdn.jsdelivr.net/gh/shuhei1101/mdmark@{タグ}/dist/index.js";

export async function draw(slot, value) {
  await mountView(slot, value.definition);
}

draw の中で、配布物が出すビューを描く関数 mountView とコメントを付ける関数 mountComments を、slot に対して呼べる。 draw が部品の Promise を待つと、mount はその後に印の場所を描き終えたものとして扱う。 update と destroy で印の場所を片付けるとき、その中の部品の destroy も行う。 引数と動きは 部品をJavaScriptから使う。

反映

即時。登録は同期で終わり、次に mount か update で描く文書から効く。

確認

印の場所に描かれている

拡張の印を書いた文書を描く。 印の場所の要素に、draw が書いた内容が出る。 ブラウザの開発者ツールのネットワークで、拡張のモジュールへの要求が 1 件ある。

印の無い文書で読まれていない

拡張の印を書いていない文書を描く。 拡張のモジュールへの要求が 0 件になる。

引数が合わないとき

schema に合わない引数の印を書いて描く。 印の場所に題 書き方の警告 の枠が出て、本文に印の文字と拡張の名前(greet)がある。 拡張のモジュールへの要求は 0 件になる。

モジュールを読めないとき

load が指す先を存在しない URL に変えて描く。 印の場所に知らせの枠が出て、題が この図を表示できません、本文が 表示に使う拡張(greet)を読み込めませんでした。 で始まる。 console.error に拡張の名前と元のエラーが出て、ほかの箇所は描かれる。

型検査が通る

TypeScript か、// @ts-check を付けた JavaScript で書いたとき、型検査を流す。

npx tsc --noEmit

型エラーが 0 件になる。

linter が通る

npx @biomejs/biome check extensions

違反が 0 件になる。

元に戻す

registerExtension の呼び出しを消す。 登録は同じページの間だけ効き、消すと文書の拡張の印は未知の印として取り除かれる。 配った JavaScript のファイルは別に消す。