4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DOMやwindowに依存しているPrismのプラグインをNode.js上で動かす方法

Last updated at Posted at 2021-05-11

Prism.jsのline-numbersプラグインなどは、ブラウザ上での動作を前提とした設計でDOMやwindowに依存しているため、Node.jsのようなサーバーサイドでrequireしても動作しません。

解決策

ではどうやったら動くか?解決策は次の通りです。

DOMに依存している問題の解決策

DOMに依存しているコードが動くようにするために、jsdomを使います。

グローバルオブジェクトwindowに依存している問題の解決策

line-numbersプラグインなどは、window.Prismundefinedだとプラグインのロードをやめてしまいます。この解決策としては、vmモジュールを使い、擬似的にグローバルオブジェクトとしてwindowがあるサンドボックス環境を作り、その中で、プラグインコードをevalしてやるようにします。要するにPrismにブラウザ環境だと思わせるようにするということです。

上の解決策を講じたコード

次が上の解決策を施したコードです。

loadPlugin.ts
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
    );
  };
}

使い方は次の通り。

usage.ts
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が必要なので次のようなユーティリティ関数を作っておくとよいです。

highlight.ts
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;
}

最終的なコード

上で作った、loadPluginhighlightを使った最終的なコードです。

main.ts
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.

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?