【Prism.js】カラーテーマを作ってみた

# はじめに

この度Prism.jsを導入したのですが、既存のカラーテーマだと合うのがなかったため、**VSCodeのダークテーマ**風のカラーテーマを作ってみました。

ということで、この記事ではPrism.jsのカラーテーマの作り方を紹介します。

:::note warn
もし著作権やライセンス的にこの記事が問題ある場合は削除します。
その場合はコメント欄等で知らせてもらえると助かります。
:::

:::note warn
初心者のため間違っている内容がある可能性があります。
そのためここの情報は鵜呑みにせず、**一度自分で調べることを推奨します。**

おかしな箇所がありましたら、編集リクエストやコメントで教えてくださると嬉しいです。
:::


## 出来上がったもの

ハイライトが適用されるとこんな感じになります。

![見本.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/7bc3459c-6051-6984-377d-42d2ee5381a4.png)

もちろんVSCodeの画面ではありません。
HTMLをブラウザで表示した結果です。

* 対応~~していると思いたい~~言語：HTML / CSS / JavaScript
* できるだけVSCodeでハイライトされる色に寄せた
* 行番号はLine Numbersというプラグインで追加（色はカスタマイズ済み）

ちなみにサンプルのコードは、いろんな構文を使おうと思った結果こうなりました。
なので特に意味はありません。
上の方には`for`や`if`、`const`なんかが書かれています。


## 環境

* ブラウザ：Chrome v125
* エディタ：VSCode（テーマ：ダークモダン）
* 使用言語：CSS


## 参考とかクレジット

参考になりそうなリンクはこちらです。
VSCodeやPrism.jsの制作に関わっている方、本当にありがとうございます。

* [VSCodeのリポジトリ](https://github.com/microsoft/vscode)
* [使用しているハイライトの色の定義箇所](https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/dark_plus.json)
* [Prism.jsで使われている標準トークン一覧](https://prismjs.com/tokens.html)
* [Prism.jsの特定の言語で使われているトークン一覧](https://prismjs.com/faq.html#how-do-i-know-which-tokens-i-can-style-for)

あとVSCodeは神エディタなのでぜひ使いましょう。

https://code.visualstudio.com/

あとPrism.jsも素敵なコードハイライトライブラリなのでぜひ使ってみてください。

https://prismjs.com/index.html


# 準備

さて、早速カラーテーマを作りたいところですが、その前に一度準備をします。
手順はこんな感じなので、不要だと思う部分は飛ばしてください。

* [Prism.jsを導入](#Prism.jsを導入)
* [必要なプラグインを導入](#必要なプラグインを導入)（Highlight Keywords）
* [importして使うようにする](#importして使うようにする)（オプション）
    * ダークテーマに合うようHTMLの背景を黒にする（オプション）
    * `styles/prism.css`というテーマのCSSを書くファイルを作る

また、全ての手順を飛ばす方は[カラーテーマの作り方](#カラーテーマの作り方)まで進んでください。

## Prism.jsを導入

まずはPrism.jsが導入されたHTMLファイルを用意します。
ここでは以下のコードを使います。

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

<head>
  <title>Prism.jsのテスト</title>
  <!-- Prism.js -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
  <!-- スペースを消し去る -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/normalize-whitespace/prism-normalize-whitespace.min.js"></script>
</head>

<body>
  <p>ここからコード</p>
  <pre><code class="lang-js line-numbers">
    const text = 'Hello World!';
    console.log(text);
  </code></pre>
  <p><code class="lang-js">console.log('これ')</code>はインラインコード</p>
</body>

</html>
```

ブラウザで表示するとこんな感じになるはずです。

![初期.png](https://camo.qiitausercontent.com/c0b085954612af68733f58b71a817c586acf3ebd/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f333538383038332f35366134646264362d373835322d643465352d313664652d3938313430313633353231372e706e67)

ちなみに、余分なスペースは[Normalize Whitespace](https://prismjs.com/plugins/normalize-whitespace/)で削除しています。
こちらは必須ではないので、お好みでご使用ください。

導入方法や基本的な使い方は前に記事にしているので、よければご覧ください。

https://qiita.com/nanasi-1/items/ab3cd94f5627b4e31dea


### テーマを指定している理由

ちなみにカラーテーマをTOMORROW NIGHTにしているのは、一応理由があります。

まず、カラーテーマがない状態でやるのは、私的には結構やりづらいと思います。
全くCSSが適用されないため、どこかどうなっているのか全然わかりませんでした。

そのため何かしらのカラーテーマを入れておいて、そこから**色を上書きしていく**イメージでやるのがいいと思います。
まあ最終的には消すのですが...

その上で8種のテーマの中からTOMORROW NIGHTを選んでいるのは、こんな理由があります。

* 今から作るVSCodeのダークテーマと同じダークテーマ
* ダークテーマの中で一番好き（個人的な感想）
* ハイライトの色が見やすい（個人的な感想）

もちろん何がやりやすいかは人それぞれなので、自分に合った方法でやるのをおすすめします。


## 必要なプラグインを導入

ここで導入するのは、Highlight Keywordsというプラグインです。

https://prismjs.com/plugins/highlight-keywords/

Highlight Keywordsは、簡単に言うとキーワードにクラスを付与してくれます。
これだけだとわかりづらいと思いますが、VSCodeっぽいテーマを作るには必須です。
なぜ必須なのかは後述します。

導入方法は以下のように`script`タグを追加するだけです。

```diff_html
<head>
  <title>Prism.jsのテスト</title>
  <!-- Prism.js -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
  <!-- スペースを消し去る -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/normalize-whitespace/prism-normalize-whitespace.min.js"></script>
+ <!-- キーワードにクラスを付与 -->
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/highlight-keywords/prism-highlight-keywords.min.js"></script>
</head>

<body>
  <p>ここからコード</p>
  <pre><code class="lang-js line-numbers">
    const text = 'Hello World!';
    console.log(text);
  </code></pre>
  <p><code class="lang-js">console.log('これ')</code>はインラインコード</p>
</body>
```

これで必要なプラグインが導入できました。

...カラーテーマがプラグインに依存するって？
それを気にしたら負けです。
まあCSSなのでプラグインなくても動くには動くのが救いですね。


## importして使うようにする

個人的な好みの問題ですが、HTML`link`タグを大量に書くより`@import`を使ってCSSを読み込む方が好きなので、今回はこちらでやります。
もちろん`link`タグで全てのCSSを読み込んでも大丈夫です。

`@import`についてはこちら。

https://developer.mozilla.org/ja/docs/Web/CSS/@import

ついでに制作段階だけですがレイヤーも使います。
`layer`についてはこちら。

https://developer.mozilla.org/ja/docs/Web/CSS/@layer

* * *

まずは`styles`というフォルダを作り、`main.css`というファイルを作ります。

```styles/main.css
/* まずは空 */
```

`main.css`は全てのスタイルを詰め込むので、HTML側でこのファイルを読み込むと全てのスタイルが適用される感じです。
といっても今回は内容が内容なのであまり大きくはなりませんが...

次に、このファイルをHTMLで読み込みます。

```diff_html
<head>
  <title>Prism.jsのテスト</title>
  <!-- Prism.js -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
  <!-- スペースを消し去る -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/normalize-whitespace/prism-normalize-whitespace.min.js"></script>
  <!-- キーワードにクラスを付与 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/highlight-keywords/prism-highlight-keywords.min.js"></script>

+ <!-- CSS -->
+ <link rel="stylesheet" href="styles/main.css">
</head>

<body>
  <p>ここからコード</p>
  <pre><code class="lang-js line-numbers">
    const text = 'Hello World!';
    console.log(text);
  </code></pre>
  <p><code class="lang-js">console.log('これ')</code>はインラインコード</p>
</body>
```

このままでも大丈夫ですが、ここではダークテーマなのに背景が白だと合わないという理由で背景を黒に変更します。

```styles/main.css
body {
  background-color: black;
  color: white;
}
```

そしたら`styles/prism.css`を作成します。
このファイルは今から作るPrism.jsのテーマ関連を詰め込みます。

```styles/prism.css
/* とりあえず空 */
```

このファイルは`main.css`で`@import`します。
これで`prism.css`がHTMLから読み込まれるようになります。

```diff_css:main.css
+ @import url(./prism.css);

body {
  background-color: black;
  color: white;
}
```

また、制作途中で使うTOMORROW NIGHTのテーマはここで`@import`します。

```diff_html:index.html
<head>
  <title>Prism.jsのテスト</title>
  <!-- Prism.js -->
- <link ref="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
  <!-- スペースを消し去る -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/normalize-whitespace/prism-normalize-whitespace.min.js"></script>
  <!-- キーワードにクラスを付与 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/highlight-keywords/prism-highlight-keywords.min.js"></script>

  <!-- CSS -->
  <link rel="stylesheet" href="./style.css">
</head>
```

```styles/prism.css
@import url(https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css) layer(prism);
```

ちなみに最後の`layer(prism)`は、これから書くCSSが確実に適用されるために書いています。

CSSにはレイヤーありよりレイヤーなしのスタイルが優先して適用されるため、こうしておけばレイヤーなしで書く自作テーマのスタイルが適用されるはずです。

まあ最後には消すんですけどね。


## サンプルコードを書く

このままでも始められるのですが、一つだけ問題があります。
それは、今のままだとコードが短すぎて、まともにテーマが見れないということです。

![初期.png](https://camo.qiitausercontent.com/c0b085954612af68733f58b71a817c586acf3ebd/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f333538383038332f35366134646264362d373835322d643465352d313664652d3938313430313633353231372e706e67)

<sub>（背景が白いのは画像が使い回しだから）</sub>

今HTMLに書かれているコードはたったの2行しかありません。
（インラインコードは既存の行とほぼ同じなのでカウントしない）

```js:HTML内のコード
const text = 'Hello World!';
console.log(text);
```

ということで、まずはいろんな構文を網羅した、ハイライトのしやすいコードを書く必要があります。

```index.html
<body>
  <pre><code class="lang-言語名を入れる">
    ここに書いたコードを入れる
  </code></pre>
</body>
```

一応私が雑に作ったHTML/CSS/JavaScript/TypeScriptのコードを載せておくので、これで問題ないという方はこちらを使ってください。

```plaintext:HTML
&lt;!DOCTYPE html&gt;
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"&gt;

&lt;div id="root"&gt;
  &lt;h1&gt;これはh1&lt;/h1&gt;
  &lt;p&gt;これは&lt;code&gt;p&lt;/code&gt;タグ&lt;/p&gt;
  &lt;p&gt;
    &lt;span style="margin: 10px;"&gt;これ&lt;/span&gt;
    はインラインスタイル
  &lt;/p&gt;
  &lt;!-- これはコメント --&gt;
&lt;/div&gt;

&lt;style&gt;
  h1 {
    margin: 0;
    color: #d7ba7d;
  }
&lt;/style&gt;

&lt;ns1:description&gt;&lt;![CDATA[
  CDATA is &lt;not&gt; magical.
]]>&lt;/ns1:description&gt;

&lt;html:p foo:bar="baz" foo:namespace&gt;&lt;/html:p&gt;

&lt;p&gt;&amp;lt; これは特殊文字 &amp;#160;&lt;/p&gt;
```

```plaintext:CSS
/* これはh1 */
h1 {
  margin: 0;
  color: #d7ba7d;
}

&.attr-value,
&.attr-value>.punctuation {
  color: var(--string);
}

span::before {
  margin: 10px !important;
}

@layer base, custom;
@import url(./base) @layer(base);

@layer custom {
  pre[class*="css"] {
    --var: 10;
  }
}
```

```plaintext:JavaScript
// これはコメント
 const constVariable = 1
 let letVariable = 'string' // 可変
 var varVariable = true // 巻き上げ

 if (window === console) {
   console.log(null);
 } else {
   console.log(undefined)
 }

 switch(aaa) {
   case 1:
     return '1'
     break
   case 2:
     return '2'
     break
   // 略
 }

 for (let i = 0; 1 < 10; i++) {
   console.log(i % 2 === 0 ? 'a': 'b')
   continue
 }

 const array = new Array(10).fill({
   message: '',
   index: Infinity
 }).map((value, i) => {
   value.index = i
   return value
 })
 array[1]?.message = 'JavaScriptだけ長すぎん？'

 /**
  * 一定秒数待つ関数
  * @param {number} sec ミリ秒
  */
 const sleep = async (sec) => {
   return new Promise((resolve, reject) => {
     setTimeout(resolve, sec)
   })
 }
 await sleep(500)

 import Prism from 'url'
 export {sleep}

 class People {
   #name
   static aaa = 'bbb'
   constructor(name) {
     this.#name = name
   }
   get name() {
     return this.#name
   }
 }
 new People('田中太郎').name
 People.aaa

 class Teacher extends People {
   #students
   constructor(name, students) {
     if(!name || !students) {
       throw new TypeError(`name:${name}かstudent:${student}の値が違うよ`)
     }
     super(name)
     this.#students = students
   }
 }

 const REG = /abc/g
 REG.test('abcd')

 const obj = {
   'タグ付きテンプレートリテラル': tag``,
   'スプレッド': {...obj1, ...obj2},
   'アロー関数': (...rest) => {
     for (val of rest) {
       console.log(val)
     }
   },
   [Symbol.iterator]() {
     return this
   }
   next() {
     // 何かの処理
   },
   CONSTANT: 'value'
 }
 obj['タグ付きテンプレートリテラル']

 function* generate () {
   yield 1;
   yield 2;
   yield 3;
 }
 generate().next()

 instanceof reg
 typeof 123
```

```plaintext:TypeScript
type Gender = '男性' | '女性' | 'その他'
interface People {
  name: string
  age: number
  gender: Gender
}

class Teacher implements People {
  static count: number = 0
  constructor(
    readonly public name: string,
    readonly public age: string,
    readonly public gender: Gender
  ) {
    Teacher.count++
  }
}

declare const document: {
  querySelector(selector: string): HTMLElement
  getElementById(id: string): HTMLElement
}

// これ型変換関数として使えるのでは...?
const func = <T>(value): T => {
  return value
}
func<unknown>('aaa')

// 配列からMapを作る
// https://qiita.com/uhyo/items/0e7821ce494024c98da5
function mapFromArray<
  T, K extends keyof T
>(arr: T[], key: K): Map<T[K], T> {
  const result = new Map();
  for (const obj of arr) {
    result.set(obj[key], obj);
  }
  return result;
}

abstract class AAA {
  abstract method1(): void
  method2(): People {
    const array = [10] as const
    return new People() // error!
  }
}
```

一応こんな感じになります。
見切れてるのは気にしないでください。

![サンプルコード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/d14be369-b7d0-99bc-b65c-c5df65310d44.png)

...ちなみに中身のコードは気にしたら負けです。


# カラーテーマの作り方

まずはざっくりとした作り方を紹介します。


## Prism.jsの仕組み

Prism.jsは大体以下のような感じで動いています。

1. JavaScriptが`lang-言語名`というクラスのついた`pre`タグや`code`タグを取得する
1. コードを解析し、特定の構文をクラスのついた`span`タグで囲む
1. カラーテーマのCSSがクラスごとにスタイルを当てる

...推測でしかないので、細かいところは間違っているかもしれません。
が、多分大筋はこんな感じだと思います。


### 解析されたコードを見る

さて、ここで重要なのは2のコード解析です。
2で解析したコードを3のCSSでスタイリングするのですが、解析したコードがどうなっているのかを知っておくとスタイリングがやりやすいと思います。

ということで、開発者ツールから解析されたコードを見てみます。
お使いのブラウザの開発者ツールからぜひ見てみてください。

ここではJavaScriptのサンプルを載せておきます。

```javascript:JavaScriptのコード
// これはコメント
const constVariable = 1
let letVariable = 'string' // 可変
var varVariable = true // 巻き上げ

if (window === console) {
  console.log(null);
} else {
  console.log(undefined)
}
```

```html:コードの解析結果
<pre class="language-js" tabindex="0">
<code class="language-js">
    <span class="token comment">// これはコメント</span>
    <span class="token keyword keyword-const">const</span> 
    constVariable 
    <span class="token operator">=</span> 
    <span class="token number">1</span>
    <span class="token keyword keyword-let">let</span> 
    letVariable 
    <span class="token operator">=</span> 
    <span class="token string">'string'</span> 
    <span class="token comment">// 可変</span>
    <span class="token keyword keyword-var">var</span> 
    varVariable 
    <span class="token operator">=</span> 
    <span class="token boolean">true</span> 
    <span class="token comment">// 巻き上げ</span>

    <span class="token keyword keyword-if">if</span> 
    <span class="token punctuation">(</span>
    window 
    <span class="token operator">===</span> 
    console
    <span class="token punctuation">)</span> 
    <span class="token punctuation">{</span>
    console
    <span class="token punctuation">.</span>
    <span class="token function">log</span>
    <span class="token punctuation">(</span>
    <span class="token keyword keyword-null">null</span>
    <span class="token punctuation">)</span>
    <span class="token punctuation">;</span>
    <span class="token punctuation">}</span> 
    <span class="token keyword keyword-else">else</span> 
    <span class="token punctuation">{</span>
    console
    <span class="token punctuation">.</span>
    <span class="token function">log</span>
    <span class="token punctuation">(</span>
    <span class="token keyword keyword-undefined">undefined</span>
    <span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <!-- 略 -->
</code>
</pre>
```

このような感じで、解析された構文は`span`タグで囲まれています。
その`span`タグには`token`クラスと`functino`や`keyword`などのクラスが付与されています。

例えば、`const constVariable = 1`という文字列のうち、

* `const`は`keyword`
* `=`は`operator`
* `1`は`number`

というクラスが付与されています。
ちなみに`constVariable`は`span`タグで囲まれていません。


### スタイリング方法

上で紹介した通り、Prism.jsでは解析した構文をクラス付きの`span`タグで囲んでくれます。
しかし、当然ながら囲んだだけでは何も起きません。
**CSSで適切なスタイリングをする**ことで、初めてコードをハイライトすることができます。

といっても、スタイリングは簡単です。
何せ、クラスごとに色を変えていけばいいだけなんです。

もし数値の色=`number`クラスの色を変えたければ、以下のようなスタイルを書きます。

```css
code[class*="lang"] .token {
  &.number {
    color: pink;
  }
}
```

少し見づらいですが、このように2行目の数値がピンク色に変わっています。
ちなみに元々は`boolean`と同じ黄色でした。

![number-pink.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/0e03e41a-8dc8-f509-bca6-47dd1f69362a.png)

* * *

ちなみに、セレクターをどのように書くかは自由です。
私はネストCSSから抜け出せなくなったのでこのように書いていますが、もちろん使わなくても構いません。

```css:ネストCSSを使わない例
code[class*="lang"] .token.number {
  color: pink;
}

/* or */

.token.number {
  color: pink;
}
```

この記事では、~~主に私の好みにより~~以下のような構成のCSSを書いています。

```css
pre:has(code[class*="lang"]) {
  /* preタグに適用するスタイル */
}

code[class*="lang"] {
  /* codeタグに適用するスタイル */

  &.token {
    &.クラス名 {
      color: 色;
    }
    &.クラス名2 {
      color: 色2;
    }
    /* etc... */
  }
}
```


## Highlight Keywordsの必要性

さて、Prism.jsの基本的なハイライトの方法は上で説明した通りですが、これだけだとカバーしきれない部分があります。
その一つに、**`keyword`の色が違う問題**が挙げられます。

例えばJavaScriptの以下の構文は、VSCodeだと青色でハイライトされます。

* `const`や`let`、`var`などの変数宣言
* `class`、`get`や`set`、`static`、`this`
* `null`、`undefined`
* `new`演算子
* `function`、`async`

そして以下の構文は紫色でハイライトされます。

* `for`や`if`、`switch`などの制御構文
* `return`、`break`、`continue`
* `throw`、`await`

これだけなら別に問題はないのですが、問題はPrism.jsが**これら全てに`keyword`というクラスを付与する**ことです。
するとCSS側で構文を識別することができず、同じ色でハイライトするしかなくなります。

```html:例
<!-- constの場合 -->
<span class="token keyword">const</span>
<!-- ifの場合 -->
<span class="token keyword">if</span>
```

これは深刻な問題です。
これらの構文を同じ色でハイライトしてしまうと、どうにもVSCodeっぽくなりません。

ここで登場するのが、Highlight Keywordsというプラグインです。

https://prismjs.com/plugins/highlight-keywords/

このプラグインを使うと、`keyword`クラスがついている`span`タグに個別でクラスを付与してくれるのです。

```html:例
<!-- constの場合 -->
<span class="token keyword keyword-const">const</span>
<!-- ifの場合 -->
<span class="token keyword keyword-if">if</span>
```

こんな風にキーワードごとにクラスを付与してくれるため、識別ができるようになります。
あとはこれで頑張ればなんとかなりますね。

```css:イメージ
&.keyword-if,
&.keyword-else,
&.keyword-for,
&.keyword-return,
&.keyword-break,
/* etc... */ {
    color: 紫;
}
```


## 余白等の指定

構文のハイライトは上で書いた通りですが、実はPrism.jsのカラーテーマが指定するのは**構文の色だけではありません。**
なぜかというと、Prism.jsで使うCSSはカラーテーマのCSSファイルだけだからです。
共通のスタイルなんてありません。たぶん。

いい感じの見栄えにするには、追加で以下のスタイルが必要です。

* コードブロックの余白
* コードの背景色
* フォント、文字サイズ
* インデントのサイズ（オプション）

これらをどうするかもカラーテーマ製作者の腕に関わってきますね。


## 実際の作成手順

ということで、ここまでのまとめも兼ねて、実際にカラーテーマを作るときの手順を考えてみました。

1. 何らかのカラーテーマを入れる
    * どこをハイライトすべきかわかってやりやすい
    * ついでに[サンプルコード](#サンプルコードを書く)も入れておく
1. 余白やフォントなど、共通のスタイルを定義する
    * 完成系を見るため、1で入れたカラーテーマをコメントアウトしたりしながらやるといいかも
1. 作るカラーテーマの色アセットを大体洗い出す
    * 足りない分は後から見つければよし
1. 指定するトークンを洗い出す
    * 標準トークン一覧は[こちら](https://prismjs.com/tokens.html)
    * 言語ごとに使われているトークンは[こちら](https://prismjs.com/faq.html#how-do-i-know-which-tokens-i-can-style-for)
1. 色アセットとトークンを対応させる
    * プレビューを見ながらやるとイメージが掴みやすい
    * キーワードの識別はHighlight Keywordsがある
1. 1で入れたカラーテーマを消す
    * ここで問題があればスタイルを編集する
1. 完成！

対応言語を増やしたい場合：

1. 指定するトークンを洗い出す
1. 色アセットとトークンを対応させる
    * 色が足りなければその都度見つける
    * ハイライトする箇所がわかりづらければカラーテーマを入れる
1. 完成！

多分こんな感じで作れると思います。

もし自分はこういう感じで作っている/作ったというのがありましたら、ぜひコメント欄等で教えてください。


### おまけ：色の特定方法

:::note info
特に本題とは関係ないので、読み飛ばしてもらっても大丈夫です。
その場合は[作ってみる](#作ってみる)まで飛ばしてください。
:::

さて、上で紹介した手順ですが、一つだけ詳細な解説がないステップがあります。
それは「色アセットを大体洗い出す」です。
書かなかった理由は作るテーマによって方法が全く異なるからというのもありますが、実は私がやらかしたからというのもあります。

まず、私がVSCodeのダークテーマ風のカラーテーマを作るときに、どうやって色を調べたのかを紹介します。
その方法とはズバリ、「**Googleドキュメントにコピペする**」です。

というのも、実はこのカラーテーマを作るより前に一度、VSCodeで使われている色のカラーコードを調べようと思ったことがありました。
そのときはOSSのコードを読むという発想がなかったため、VSCodeの公式リポジトリは見ませんでした。

そんな昔の私が思い出したのは、**書式ごとコピペする**ことができるGoogleドキュメントです。
そして、これを使えば色のカラーコードがわかるのでは...?と考えつきました。
やり方は至ってシンプルで、VSCodeで書いたコードをコピーしてGoogleドキュメントに貼り付けるだけです。
やってみたら普通にできました。

そのときに主要な色のカラーコードは大体わかったので、今回のVSCodeのダークテーマ風カラーテーマの制作でもそれを使いました。
ですが、そんな邪道とも言える方法をQiitaに書くのはなー、そもそもカラーテーマって著作権とかないのかなー、などと考えていたら不安になってきたため、VSCodeのGitHubリポジトリからカラーテーマの定義箇所を探したらありました。
それが[こちら](https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/dark_plus.json)です。

要するに何が言いたいかというと、よくわからない方法に手を出す前に**公式に載ってないかを確かめよう**ということです。

邪道とも言える方法をやっていいのは、公式ソースを隅々まで探しても見つからなかったときだけです。
まあ見つからなかったなら隠してる可能性があるので、そもそもやるべきではない気もしますが...


# 作ってみる

ここから先はVSCodeのダークテーマ風のカラーテーマを作る様子を書いています。
ちなみにリファクタリング目的で1から作り直しています。
この例が何かの役に立てば幸いです。

ちなみにこの状態のコードからスタートします。

```html:index.html
<!DOCTYPE html>
<html lang="ja">

<head>
  <title>Prism.jsのテスト</title>
  <!-- Prism.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
  <!-- スペースを消し去る -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/normalize-whitespace/prism-normalize-whitespace.min.js"></script>
  <!-- キーワードにクラスを付与 -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/highlight-keywords/prism-highlight-keywords.min.js"></script>

  <!-- CSS -->
  <link rel="stylesheet" href="styles/main.css">
</head>

<body>
  <h3>HTML</h3>
  <pre><code class="lang-html">
    HTMLのサンプルコード（長いため略）
  </code></pre>
  <h3>CSS</h3>
  <pre><code class="lang-css">
    CSSのサンプルコード（長いため略）
  </code></pre>
  <h3>JavaScript</h3>
  <pre><code class="lang-js">
    JavaScriptのサンプルコード（長いため略）
  </code></pre>
  <h3>TypeScript</h3>
  <pre><code class="lang-ts">
    TypeScriptのサンプルコード（長いため略）
  </code></pre>
</body>

</html>
```

```styles/main.css
@import url(prism.css);

body {
  background-color: black;
  color: white;
}
```

```styles/prism.css
@import url(https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css) layer(prism);

pre[class*="lang"] {}

code[class*="lang"] {}
```


## 共通のスタイルを定義する

ということで、まずは共通のスタイルを定義します。

何も見ないでやると辛いので、こんな感じでTOMORROW NIGHTテーマをコメントアウトしたりコメントアウト解除したりしながらやるといいかもしれません。

```diff_css:styles/prism.css
- @import url(https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css) layer(prism);
+ /* @import url(https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css) layer(prism); */

pre[class*="lang"] {}

code[class*="lang"] {}
```

という感じで、できたのがこちらです。

```diff_css:styles/prism.css
@import url(https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css) layer(prism);

pre[class*="lang"] {
+ background-color: #1F1F1F;
+ padding: 1rem;
+ font-size: 0.9em;
+ line-height: 1.5;
+ tab-size: 2;
}

code[class*="lang"] {
+ font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ color: #CCCCCC;
}
```

ちなみに`font-family`は[Bootstrap](https://getbootstrap.jp/)のデフォルトで`code`タグに適用されるスタイルをパクr...参考にしました。


## 色アセットを洗い出す

共通のスタイルができたので、次はカラーテーマで使う色を洗い出します。
といっても面倒になったので、[色の定義箇所](https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/dark_plus.json)見ながら作りたいと思います。

まあ色一覧が載っているページがあればそれで問題ないわけだし...
ということで次に行きます。


## トークンを洗い出す

CSSで指定するべきクラス名を洗い出していきます。
今回対応する予定の言語は以下です。

* HTML
* CSS
* JavaScript
* TypeScript

対応言語は後からでも追加できるので、最初に作る言語は最低限だけで大丈夫だと思います。

言語ごとに使われているクラスはここから調べます。

https://prismjs.com/faq.html#how-do-i-know-which-tokens-i-can-style-for

ちなみにこのリスト、アルファベット順ではないので注意してください。
言語検索には~~今即興で作った~~以下の関数が使えます。

```js
function find(lang) {
    return [...document.getElementById('language-select').children].map(e => e.value).reduce((arr, text, i) => {if(text.includes(lang)) arr.push(`${i}: ${text}`); return arr}, [])
}

// 例：HTMLっぽい言語ないかなー？
find('html') // ['220: cshtml']
// ってことはHTMLで使われるトークンはmarkupを見ればいいな

// 例:JavaScriptどこ...?
find('javascript') // [`3: javascript`]
// あ、上から３番目（配列仕様index）にあったわ

// 例：jsっぽいやつってどれくらいあるんだろ
find('js') // 10個くらいの長さの配列
// 意外とあるんだな...
```

開発者ツールのコンソールでコピペして使ってみてください。
ちなみに動く保証はありません。
あと全文字半角で、略称ではなく正式名称で入力してください。

また、標準トークン一覧はこちらです。
このクラス名何？っていうのがあれば、こちらに載っているかもしれません。

https://prismjs.com/tokens.html

...これも洗い出すの面倒なので、作るときに一つづつ考えてやります。
まあなんとかなるでしょう。


## 色とトークンを対応させる

さて、前の2ステップをサボったせいでめちゃくちゃ大変ですが、まあがんばります。

まずはトークンと色を書くところを作ります。

```diff_css
code[class*="lang"] {
  font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  color: #CCCCCC;

+ .token {}
}
```

そしたらあとは気合いで作ります。
その結果できたものがこちらです。

```css:styles/prism.css
@import url(https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css) layer(prism);

pre[class*="lang"] {
  background-color: #1F1F1F;
  padding: 1rem;
  font-size: 0.9em;
  line-height: 1.5;
  tab-size: 2;
}

code[class*="lang"] {
  font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  color: #CCCCCC;

  .token {
    &.comment {
      color: #6A9955;
    }

    &.punctuation {
      color: #808080;
    }

    &.doctype-tag,
    &.prolog,
    &.tag>.tag,
    &.entity,
    &.important,
    &.interpolation-punctuation,
    &.regex-flags,
    &.boolean {
      color: #569cd6;
    }

    &.doctype>.name,
    &.attr-name,
    &.atrule>.url,
    &.property {
      color: #9cdcfe;
    }

    &.string,
    &.cdata,
    &.attr-value,
    &.string-property {
      color: #ce9178;
    }

    &.keyword {
      color: #569cd6;
    }

    &.atrule>.rule,
    &.keyword-if,
    &.keyword-elif,
    &.keyword-else,
    &.keyword-for,
    &.keyword-return,
    &.keyword-break,
    &.keyword-continue,
    &.keyword-throw,
    &.keyword-import,
    &.keyword-from,
    &.keyword-as,
    &.keyword-in,
    &.keyword-of,
    &.keyword-export,
    &.keyword-await,
    &.keyword-switch,
    &.keyword-case,
    &.keyword-yield {
      color: #C586C0;
    }

    &.function {
      color: #DCDCAA;
    }

    &.selector {
      color: #d7ba7d;
    }

    &.class-name {
      color: #4EC9B0;
    }

    &.regex {
      color: #d16969;
    }

    &.constant {
      color: #4FC1FF;
    }

    &.number {
      color: #b5cea8;
    }

    &.operator {
      color: #d4d4d4;
    }

    &.bold {
      font-weight: bold;
    }

    &.italic {
      font-style: italic;
    }

    &.strike {
      text-decoration-line: line-through;
    }
  }
}

:is(code, code[class*="lang"] span):is(
  [class*="js"],
  [class*="ts"]
) {
  color: #9cdcfe;

  .token.punctuation {
    color: #d4d4d4;
  }

  .token.builtin {
    color: #4EC9B0;
  }
}
```

ちなみにですが、特定の言語のみにスタイルを当てる場合は`code[class*="言語"]`のようにするのがおすすめです。
特定の複数の言語に当てる場合は、`:is()`を使うと簡潔に書けるかもしれません。

```css
:is(code, code[class*="lang"] span):is(
  [class*="js"], /* JavaScriptと */
  [class*="ts"] /* TypeScriptに当てる */
) {
    /* スタイルを書く */
}
```


## 最初に入れたテーマを消す

では最後に、役目を終えた（？）TOMORROW NIGHTテーマの`@import`を削除して終わります。

```diff_css:styles/prism.css
- @import url(https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css) layer(prism);

pre[class*="lang"] {
  background-color: #1F1F1F;
  padding: 1rem;
  font-size: 0.9em;
  line-height: 1.5;
  /* 略 */
```

これで問題がないか、ブラウザの表示を見て確かめます。

![最終.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/c07209d7-e96f-873d-1bec-0c1de8a86972.png)

まあたぶん問題はないでしょう。
ということで、VSCodeのダークテーマ風カラーテーマ、完成です。