npm — @minjun0219/mdwire

npm versionnpm monthly downloads

Rust 코어를 WASM 으로 컴파일한 패키지입니다. React 와 이벤트 목록용 진입점은 JS 로 직접 작성했습니다. Node, Bun, 번들러에서 동작합니다.

npm install @minjun0219/mdwire

패키지는 exports 조건으로 빌드를 고릅니다. Node 에서는 WASM 을 디스크에서 읽는 CommonJS 빌드를, 그 밖의 환경에서는 ESM 번들러 빌드를 씁니다. 번들러에서는 WASM 을 ES 모듈로 import 하므로, Vite 를 쓴다면 vite-plugin-wasm 과 build.target: "esnext"(top-level await 용)가 필요합니다. examples/react-streaming/vite.config.js 를 참고하세요.

채널 이름은 Concepts에 나온 문자열을 씁니다.

render(input, channel, options?)

import { render } from "@minjun0219/mdwire";

const parts: string[] = render(markdown, "telegram-html");

완성된 문서를 변환해 보낼 조각 배열을 반환합니다. 채널의 길이 제한(또는 options.limit)을 넘지 않으면 조각은 하나입니다.

renderWithReport(input, channel, options?)

import { renderWithReport } from "@minjun0219/mdwire";

const out = renderWithReport(markdown, "slack-markdown");
out.parts;                   // string[]
out.repairs.closedEmphasis;  // 아래 "Repairs" 참고
out.free();                  // 결과는 WASM 메모리에 있음

render 에 정규화 보고를 더한 함수입니다. 결과와 그 repairs 는 WASM 객체이므로, 다 쓰면 free() 로 해제하거나 using(Symbol.dispose)으로 감싸 쓰세요.

limit(channel)

채널의 조각 길이 제한을 숫자로 반환합니다. html 은 제한이 없어서 4294967295 를 반환합니다.

new Streamer(channel, options?)

import { Streamer } from "@minjun0219/mdwire";

const s = new Streamer("telegram-html");
let acc = "";
for await (const token of tokens) {
  acc += s.push(token);           // 확정된 출력. 다시 바뀌지 않음
  await edit(acc + s.preview());  // + 보류 중인 내용을 입력이 끝난 것처럼 렌더링한 뒷부분
}
acc += s.finish();
if (s.revised()) await edit(acc); // 마지막 화면이 완성본과 같으면 건너뜀
s.free();
메서드 반환
push(chunk) string 지금 보내도 안전한 출력입니다. 이 출력은 확정입니다.
preview() string 메시지 전체를 다시 렌더링할 때 붙이는 뒷부분입니다. 상태를 바꾸지 않습니다. 청크마다 부르지 말고 화면을 갱신할 때만 호출하세요.
closeOpen() string 이미 보낸 블록 마크업만 닫는 뒷부분입니다. 상태를 바꾸지 않습니다.
finish() string 남은 출력입니다. 열린 마크업은 닫아서 반환합니다.
revised() boolean finish 다음에 호출합니다. 완성본이 마지막 preview() 화면과 다른지 알려 줍니다.
repairs() Repairs 지금까지 센 정규화 보고입니다. finish 다음에는 문서 전체에 대한 값입니다.
free() WASM 메모리를 해제합니다.

덧붙이기만 하는 채널(슬랙 appendStream)에는 push 결과를 그대로 보내고 preview() 는 호출하지 않습니다. 스트리밍 계약을 참고하세요.

RenderOptions

interface RenderOptions {
  limit?: number;                       // 1 이상의 정수. 256 보다 작으면 256 으로 올림
  html?: {                              // "html" 채널에서만
    lineBreaks?: "br" | "space";        // 기본 "br"
    images?: "link" | "load";           // 기본 "link"
    schemes?: string[];                 // 기본 ["http", "https", "mailto"]. 더하지 않고 바꿈
  };
}

모르는 채널 이름, 모르는 html 값, 1 이상의 정수가 아닌 limit 을 주면 Error 를 던집니다.

Repairs

필드
closedEmphasis · closedFence · revertedCodeSpan · droppedMarker · guessedPair 다음 릴리스 고친 횟수입니다.
escapedChar · tagEmphasis · strippedHtml · rewrittenBullet · rewrittenTable · convertedMarker 채널에 맞춰 바꾼 횟수입니다.

각 필드가 세는 것은 Concepts에 있습니다.

React — @minjun0219/mdwire/react

React 요소를 createElement 로 만들며 innerHTML 을 쓰지 않습니다. 이스케이프, 허용 태그, 링크 스킴은 코어의 html 채널이 정하고, 사용하는 쪽은 태그마다 어떤 컴포넌트로 렌더링할지만 고릅니다.

import { Markdown, useMarkdownStream } from "@minjun0219/mdwire/react";

<Markdown text={answer} components={{ a: RouterLink }} />

const { elements, push, finish } = useMarkdownStream();      // push(token) … finish()
const settled = useMarkdownStream({ eager: false });        // 확정된 출력만
<Markdown text components? options? /> 완성된 답변을 렌더링합니다. components 로 태그(a, code …)를 직접 만든 컴포넌트에 연결합니다.
useMarkdownStream({ components?, options?, eager?, onSettled? }) 스트리밍용 훅입니다. { elements, push, finish } 를 반환합니다. eager(기본 true)면 보류 중인 내용도 먼저 렌더링하고, false 면 확정된 출력만 렌더링합니다. onSettled(html, revised) 는 finish 다음에 호출됩니다. 옵션은 onSettled 를 빼고 처음 한 번만 읽습니다.
toElements(html, components?, schemes?) html 채널 출력(스트리밍이라면 누적 출력 + preview())을 React 노드로 바꿉니다. schemes 는 코어에 준 값과 같게 주세요.

이벤트 — @minjun0219/mdwire/events

import { toEvents } from "@minjun0219/mdwire/events";

toEvents(html); // [{ type: "open", tag: "p", attrs: {} }, { type: "text", text: "…" }, { type: "close", tag: "p" }, …]

같은 html 출력을 open / text / close / void 이벤트가 중첩 없이 이어진 목록으로 바꿉니다. React 가 아닌 프레임워크에서 쓰는 진입점입니다. 속성은 코어가 출력하는 것만 담깁니다. a 의 href, code 의 class(language-…), th · td 의 style(text-align:…), ol 의 start, 이미지를 불러올 때 img 의 src · alt 입니다.

lezer — @minjun0219/mdwire/lezer 다음 릴리스

import { GFM, parser } from "@lezer/markdown";
import { koreanEmphasis } from "@minjun0219/mdwire/lezer";

const p = parser.configure([GFM, koreanEmphasis]); // GFM 뒤에 둡니다 — Emphasis · Strikethrough 파서를 같은 이름으로 바꿔 끼웁니다
// CodeMirror: markdown({ base: markdownLanguage, extensions: [koreanEmphasis] })

mdwire 를 거치지 않고 @lezer/markdown 이나 CodeMirror 로 마크다운을 그리는 화면을 위한 확장입니다. 코어와 같은 규칙으로 강조를 읽어서 **설정(config)**을 이 별표 대신 굵게로 보입니다. WASM 이 없고, @lezer/markdown(1.5 이상)은 선택 peer 의존성입니다.

코어와 다른 점은 셋이고 모두 의도한 것입니다. 짝을 맺지 못한 마커는 글자로 남깁니다(코어는 일부를 버립니다). 문단 끝까지 열려 있는 강조도 글자로 남깁니다(코어는 닫아 줍니다). 별표 넷 이상은 글자입니다. 실제 문단 6,965개로 코어 출력과 대조했습니다.