npm — @minjun0219/mdwire
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개로 코어 출력과 대조했습니다.