lowlight
構文解析だけするhighlight.js
https://github.com/wooorm/lowlight
HAST形式でsyntaxを出力する
virtual DOMやSSR環境でsyntax highlightするときに便利
doc.deno.landのコードから見つけたtakker.icon
API Reference
https://doc.deno.land/https://esm.sh/lowlight@2.8.1 からはうまく型定義を読み込めない
cdn.skypack.devも同様
構文解析結果の型定義はここを参照
解説
全ての言語のsyntax highlightを有効にするときは https://esm.sh/lowlight@2.8.1/lib/all.js を使う
サンプル
構文解析結果を出力する
code:test.js
const { render, html, useState, useEffect } = htmPreact;
render(html`<${() => {
const json, setJson = useState("Loading modules...");
useEffect(() => {
const code = new URLSearchParams(location.search).get("code");
if (!code) {
setJson("No source file specified.");
return;
}
(async () => {
const [
{ lowlight },
{ toHtml },
] = await Promise.all([
import("https://esm.sh/lowlight@2.8.1"),
import("https://esm.sh/hast-util-to-html@8.0.4"),
]);
setJson("Loading Code...");
const res = await fetch(code);
const tree = lowlight.highlightAuto(await res.text());
setJson(JSON.stringify(tree, null, 2));
//setJson(toHtml(tree));
})().catch((e)=>alert(JSON.stringify(e)));
}, []);
return html<style>pre{white-space:pre-wrap}</style><pre><code>${json}</code></pre>;
}} />`, document.body);
構文解析結果を1行ごとに分割して出力する
DOMを改行で分割するコードを参考に作る
うまく分割できたっぽいtakker.icon
code:test2.js
const { render, html, useState, useEffect } = htmPreact;
/** 空文字のnodeならfalseを返す */
const isEmptyNode = (node) =>
(node.type === "comment" || node.type === "text") && node.value === "";
/** 与えられたhastを1行ごとに分割する
*
* @param node 分割したいhast
* @return 分割したhastを1行ずつ返す
*/
function* splitNode(node) {
switch (node.type) {
case "root":
yield* splitNode({
type: "element",
tagName: "div",
children: node.children,
});
return;
case "doctype":
yield node;
return;
case "comment":
case "text":
yield* node.value.split("\n").map(
(value) => ({ type: node.type, value })
);
return;
case "element": {
if (node.children.length === 0) {
yield node;
return;
}
let prev;
const { children, ...rest } = node;
for (const child of children) {
let counter = 0;
for (const splitted of splitNode(child)) {
if (prev && counter > 0) {
yield prev;
prev = undefined;
}
counter++;
const isEmpty = isEmptyNode(splitted);
if (prev) {
if (isEmpty) continue;
prev.children.push(splitted);
continue;
}
prev = { ...rest, children: isEmpty ? [] : splitted };
}
}
if (prev) yield prev;
return;
}
}
}
render(html`<${() => {
const json, setJson = useState("Loading modules...");
useEffect(() => {
const code = new URLSearchParams(location.search).get("code");
if (!code) {
setJson("No source file specified.");
return;
}
(async () => {
const [
{ lowlight },
{ toHtml },
] = await Promise.all([
import("https://esm.sh/lowlight@2.8.1"),
import("https://esm.sh/hast-util-to-html@8.0.4"),
]);
setJson("Loading Code...");
const res = await fetch(code);
const tree = lowlight.highlightAuto(await res.text());
setJson(JSON.stringify(...splitNode(tree), null, 2));
/*setJson(toHtml(
{ type: "root", children: ...splitNode(tree) }
));*/
})().catch((e)=>alert(JSON.stringify(e)));
}, []);
return html<style>pre{white-space:pre-wrap}</style><pre><code>${json}</code></pre>;
}} />`, document.body);
#2023-02-18 02:26:46
#2023-02-17 23:04:20