Prism.jsのline-numbersプラグインなどは、ブラウザ上での動作を前提とした設計でDOMやwindowに依存しているため、Node.jsのようなサーバーサイドでrequire
しても動作しません。
解決策
ではどうやったら動くか?解決策は次の通りです。
DOMに依存している問題の解決策
DOMに依存しているコードが動くようにするために、jsdom
を使います。
グローバルオブジェクトwindow
に依存している問題の解決策
line-numbersプラグインなどは、window.Prism
がundefined
だとプラグインのロードをやめてしまいます。この解決策としては、vm
モジュールを使い、擬似的にグローバルオブジェクトとしてwindow
があるサンドボックス環境を作り、その中で、プラグインコードをeval
してやるようにします。要するにPrismにブラウザ環境だと思わせるようにするということです。
上の解決策を講じたコード
次が上の解決策を施したコードです。
import fs from "fs";
import { JSDOM } from "jsdom";
import Prism from "prismjs";
import vm from "vm";
export function createLoadPlugin() {
const { window } = new JSDOM("");
window.Prism = Prism;
const ctx = vm.createContext(window);
return function load(plugin: string): void {
const filename = require.resolve(
`prismjs/plugins/${plugin}/prism-${plugin}`
);
const src = fs.readFileSync(filename, "utf-8");
vm.runInContext(
// language=JavaScript
`
try {
const self = window;
${src};
} catch (err) {
console.error(err);
}
`,
ctx
);
};
}
使い方は次の通り。
import { createLoadPlugin } from "./loadPlugin";
const loadPlugin = createLoadPlugin();
loadPlugin("line-numbers");
loadPlugin("diff-highlight");
loadPlugin("autolinker");
loadPlugin("inline-color");
これで様々なプラグインがロードできるようになります。
ハイライトはPrism.highlightElement
を用いる
Node.jsでハイライトするにはPrism.highlight
を用いるのが一般的ですが、DOM依存のプラグインはそのメソッドでは動かないので、Prism.highlightElement
を使います。
実際にhighlightElement
を使う場合は、DOMが必要なので次のようなユーティリティ関数を作っておくとよいです。
import { JSDOM } from "jsdom";
import Prism from "prismjs";
export function highlight(
code: string,
{
language = "none",
lineNumbers = false,
}: { language?: string; lineNumbers?: boolean } = {}
): string {
const { window } = new JSDOM("");
const pre = window.document.createElement("pre");
const codeElm = window.document.createElement("code");
pre.appendChild(codeElm);
codeElm.textContent = code;
codeElm.setAttribute(
"class",
[`language-${language}`]
.concat(lineNumbers ? ["line-numbers"] : [])
.join(" ")
);
Prism.highlightElement(codeElm);
return pre.outerHTML;
}
最終的なコード
上で作った、loadPlugin
とhighlight
を使った最終的なコードです。
import loadLanguages from "prismjs/components/index";
import { highlight } from "./highlight";
import { createLoadPlugin } from "./loadPlugin";
loadLanguages();
const loadPlugin = createLoadPlugin();
loadPlugin("line-numbers");
loadPlugin("diff-highlight");
loadPlugin("autolinker");
loadPlugin("inline-color");
const code = `span.foo {
background-color: navy;
color: #BFD;
}`;
const html = highlight(code, { language: "css", lineNumbers: true });
console.log(html);
上のhtml
内容は次のようになります。
See the Pen Prism line-numbers inline-colors by suin (@suin) on CodePen.