記事一覧に戻る

Prismjsでシンタックスハイライトする方法

はじめに

function hello(){
    console.log("hello,world")
}

こんな感じで、いい感じにコードの部分をCSSで装飾しているウェブページをよく見かけます。技術系のサイトはもとより、プログラミング言語やパッケージのドキュメントでも、基本的にどのサイトを見てもコード部分は綺麗にハイライトされています。

はじめは、どうやってハイライトを実現しているのかよく分かりませんでした。言語別に強調したいキーワードも違いますし、「まさかいちいち<span class="fn">function</span><span class="bracket">(</span><span class="args">x,y</span><span class="bracket">)</span>みたいに書いているのか!?」と不安になったものです。

もちろんそんなことはなく、「シンタックス・ハイライト」でGoogle検索したら解決策は割とすぐに見つかりました。方法はたくさん見つかりましたが、Prismjsが特に有名なようです。

私のサイトもPrismjsでハイライトしていますが、あまり内容を良く調べずドキュメントのサンプルを参考にいじった程度だったので、これを機にPrismjsについて調べてみたいと思います。

Prismjsについて

Prismjsの公式サイトでは、「軽量で拡張可能な、モダンなウェブ標準を意識して作られたシンタックス・ハイライターです」と記載されています。Reactや、MozillaのMDNのウェブサイトでも利用されていると紹介されています。

また、ハイライトだけでなく、ファイル名を表示したり、プラグインを利用することで行番号を表示させたり、コピー機能をつけたり等、様々な機能拡張が可能なようです。

使い方

公式サイトからJSとCSSをダウンロードしてHTML内で読み込ませる方法と、マークダウンファイル内のコード部分に適用させる方法の2つを紹介します。 今回は、シンタックス・ハイライト機能に絞って解説します。

■ HTMLファイル内でCSSやJSを読む込む場合

一番オーソドックスな使い方です。

JSとCSSをダウンロードする

公式サイトのダウンロードページから、テーマ(Themes)と言語(Languages)を選択して、JSとCSSをそれぞれダウンロードします。

download

私はテーマはOkaidiaにして、言語はデフォルトにpythonを加えてダウンロードしました。JSはprism.js、CSSはprism.cssの名前で保存しています。

HTMLで読み込む

ファイルは以下のように全て同じ階層に配置しています。

index.html
package.json
prism.css
prism.js

index.htmlで、prism.jsとprism.cssを読み込ませます。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- ダウンロードしたcssを適用 -->
  <link rel="stylesheet" href="./prism.css">
  <title>Prism Test</title>
</head>

<body>
  <!-- ダウンロードしたjsを適用 -->
  <script src="./prism.js"></script>
</body>

</html>

実際のコード部分をHTMLに書いていきます。ルールは2つあります。

  • <pre>タグで<code>タグを囲む。
  • <code>タグにlanguage-プログラム名のクラスを追加する。例えばPythonなら<code class="language-python">とクラス名を追加します。
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- ダウンロードしたcssを適用 -->
  <link rel="stylesheet" href="./prism.css">
  <title>Prism Test</title>
</head>

<body>
  <h1>Prismjsテスト</h1>
  <section>
    <h2>Python</h2>
    <pre>
      <code class="language-python">
        import requests
        res = requests.get("https://some-url.com/v1/some-endpoint")
        content = res.json()
        print(content)
      </code>
    </pre>
  </section>
  <section>
    <h2>Javascript</h2>
    <pre>
      <code class="language-javascript">
        var val = "world";
        function hello(){
          return "hello," + val;
        }
      </code>
    </pre>
  </section>
  <!-- ダウンロードしたjsを適用 -->
  <script src="./prism.js"></script>
</body>

</html>

index.htmlを確認してみる

check-html

ちゃんとハイライトされています。

HTMLでは<code>タグにクラスを追加しましたが、読み込んだprism.jsにより、<pre>にもクラスが追加されていることが確認できます。

pre-class

CSSをカスタムする

コード部分の余白が気になるので、調整するCSSを加えます。prism.cssを直接編集するのも手ですが、デフォルトだとminified版がインストールされていると思います。

minified-css

改行コードも削除されているので直接編集するのは難しそうです。別のCSSで対応します。名前はoverride.cssにしておきます。

/*override.css*/
pre[class*=language-] {
    padding-top: 0px;
    padding-bottom: 0px;
    white-space: pre-wrap;
}

加えて、HTML内のインデントやコードの最後の改行コードも律儀に描写されているので、見た目は悪いですがHTML内で不要なインデントは落として記述します。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- ダウンロードしたcssを適用 -->
  <link rel="stylesheet" href="./prism.css">
  <!-- 上書き用のカスタムcssを適用 -->
  <link rel="stylesheet" href="./override.css">
  <title>Prism Test</title>
</head>

<body>
  <h1>Prismjsテスト</h1>
  <section>
    <h2>Python</h2>
    <pre>
      <code class="language-python">
import requests
res = requests.get("https://some-url.com/v1/some-endpoint")
content = res.json()</code>
    </pre>
  </section>
  <section>
    <h2>Javascript</h2>
    <pre>
      <code class="language-javascript">
var val = "world";
function hello(){
  return "hello," + val;
}</code>
    </pre>
  </section>
  <script src="./prism.js"></script>
</body>

</html>

実際にページを開いて確認します。

html-with-fixed-css

だいぶ綺麗になりました!

HTMLでインデントを調整するのはう~~ん、、、という感じですが、今回はそこまで拘りません。CSSでうまく調整する方法があれば教えてください。

■ マークダウン内のコード部分に適用させる場合

マークダウンをHTMLに変換することはよくあると思います。マークダウン内に記述されたコード部分に、prismjsを適用させる方法を説明します。

マークダウンをHTMLに変換するために、remarkrehypeといったunified関連のnpmパッケージを使います。はじめて利用される方はunifiedの解説を参考にしてください。

パッケージのインストール

マークダウンをHTMLにするため、remark-parserremark-rehyperehype-stringifyを使います。完全なHTMLファイルとして出力したいので、rehype-documentも使います。そして、prismjsを適用させるために、remark-prismというプラグインを利用します。

npm i remark-parse rehype-stringify remark-rehype remark-prism rehype-document

ESMを有効にする

ESMのみ対応のパッケージのため、package.jsonに"type":"module"を追加しておきます。

{
  "name": "prism",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "rehype-document": "^6.1.0",
    "rehype-stringify": "^9.0.3",
    "remark-parse": "^10.0.1",
    "remark-prism": "^1.3.6",
    "remark-rehype": "^10.1.0"
  }
}

マークダウンファイル

変換元のマークダウンは以下の内容とし、md-prism.mdの名前で同じ階層に保存しておきます。

# マークダウンからprismjsを適用させるまで

## Pythonのコード

```python
glb = 555
def test():
    if True:
        return glb
    else:
        return ""
```

## Javascriptのコード

```js
const arr = [1,2,3];
const num = 9;

function callback(v) {
  return v * 3 + num;
}

console.log(arr.map(callback))
```

HTMLに変換するプログラム

remarkとrehype関連のパッケージを使って、マークダウンをHTMLに変換します。prism.jsが行ってくれていた処理は、remark-prismのパッケージが行ってくれますが、CSSの適用は同じように手動でパスを登録する必要があります。remark-prismをインストールすると、 /node_modules/prismjs/themes直下にテーマ別のCSSファイルがダウンロードされているので、好みのテーマを選びます。今回は、prism-coy.cssを使ってみます。

ホームページだと自分で選んだCSSがダウンロードされますが、Node.jsのパッケージだと全てダウンロードされるようですね。

> ls .\node_modules\prismjs\themes\

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2023/02/19     17:50           4031 prism-coy.css
-a----        2023/02/19     17:50           3062 prism-coy.min.css
-a----        2023/02/19     17:50           2070 prism-dark.css
-a----        2023/02/19     17:50           1533 prism-dark.min.css
-a----        2023/02/19     17:50           2495 prism-funky.css
-a----        2023/02/19     17:50           1990 prism-funky.min.css
-a----        2023/02/19     17:50           1812 prism-okaidia.css
-a----        2023/02/19     17:50           1380 prism-okaidia.min.css
-a----        2023/02/19     17:50           2606 prism-solarizedlight.css
-a----        2023/02/19     17:50           1589 prism-solarizedlight.min.css
-a----        2023/02/19     17:50           1766 prism-tomorrow.css
-a----        2023/02/19     17:50           1313 prism-tomorrow.min.css
-a----        2023/02/19     17:50           3620 prism-twilight.css
-a----        2023/02/19     17:50           2440 prism-twilight.min.css
-a----        2023/02/19     17:50           2335 prism.css
-a----        2023/02/19     17:50           1789 prism.min.css

出力するHTMLファイル名はfrom-md.htmlにしておきます。

import { unified } from "unified"
import remarkParse from "remark-parse";
import remarkPrism from "remark-prism";
import remarkRehype from "remark-rehype";
import rehypeDocument from "rehype-document";
import rehypeStringify from "rehype-stringify"

import { readFile, writeFile } from "fs/promises";

// mdファイルを読み取る
const content = await readFile("./md-prism.md", { encoding: "utf-8" });

const vfile = await unified()
    .use(remarkParse) // mdをパース
    .use(remarkPrism) // prismjsのプラグイン
    .use(remarkRehype) // md → html
    .use(rehypeDocument, {
        title: "MD-PRISM", language: "ja",
        style: 'body {background-color:#f7f7f7;color:#333}',
        link: [{
            rel: "stylesheet",
            // prismのCSSのパス
            href: "./node_modules/prismjs/themes/prism-coy.css"
        }]
    }) // 完全なHTML化
    .use(rehypeStringify)
    .process(content);

// htmlファイルとして出力
writeFile("./from-md.html", vfile.toString());

例示として、今回はサーバを介さずローカルでHTMLを開くので、/node_modules/prismjs/themes/prism-coy.cssを直接パス指定しています。 サーバ上に配置して利用する場合は、環境に応じて他のCSSファイルと同じところにコピペしてください。

出力されたファイルを確認

実際にブラウザで開いて確認します。ちゃんと効いてます!

html-from-md

最後に

Prismjsでシンタックス・ハイライトをする方法を2つ紹介しました。他にも、ファイル名を表示させたり、行番号を行事させたり、様々な機能があるようなので 今後も色々試していきたいです。今は使うだけでいっぱいっぱいですが、いつか私もこういうパッケージが作れるようになりたいものです。

Javascriptは本当にたくさんのパッケージがあって面白いですね。しかし有名なパッケージでも、日本語ドキュメントが無いものが多いことに驚きました。基本は英語で見ていかざるを得ないのでしょう。

参考URL

記事一覧に戻る