# Monaco Editor에 Shiki 하이라이팅 적용하기

웹에서 코드 편집기를 만들 때 가장 많이 사용하는 선택지 중 하나는 Monaco Editor 입니다. VSCode의 편집기 엔진을 기반으로 하여 자동 완성, 진단, 다중 커서 같은 편집 경험들을 쉽게 구현할 수 있습니다.

다만 기본 문법 강조의 경우, VSCode에서 보이는 결과와 완전히 같지 않을 수 있습니다. Monaco Editor의 기본 토크나이저가 모든 TextMate 문법을 그대로 처리하지 않기 때문입니다.

이때 Shiki를 연결하면 VS Code 계열의 TextMate 문법과 테마를 사용하여 더 정확한 색상 표현을 할 수 있습니다. Shiki는 Monaco 용도의 공식 어댑터 @shikijs/monaco 를 제공합니다. 해당 어댑터를 통해 직접 토큰화 결과를 Monaco에 주입하는 방식보다 이 패키지를 사용하여 적용하는 편이 훨씬 더 간단합니다.

## 패키지 설치하기

Monaco , shiki, @shikijs/monaco 패키지를 설치합니다.

```javascript
npm install monaco-editor shiki @shikijs/monaco
```

## 적용해보기

1. `createHighlighter` 로 필요한 언어와 테마를 로드합니다.

2. 사용할 언어의 ID를 monaco.languages.register() 을 통해 등록합니다.

3. `shikiToMonaco()` 를 통해 Shiki 문법과 테마를 Monaco에 연결합니다.

이때 지원되는 themes (에디터 테마)와 langs (언어 설정) shiki 공식 홈페이지에서 확인이 가능합니다.

```javascript
import * as monaco from 'monaco-editor'
import { createHighlighter } from 'shiki'
import { shikiToMonaco } from '@shikijs/monaco'

async function createEditor() {
  const highlighter = await createHighlighter({
    themes: ['github-dark', 'github-light'],
    langs: ['typescript', 'javascript', 'json'],
  })

  // Shiki로 강조할 언어는 Monaco에도 등록해야 한다.
  for (const id of ['typescript', 'javascript', 'json']) {
    monaco.languages.register({ id })
  }

  // Shiki 테마와 문법 강조 규칙을 Monaco에 등록한다.
  shikiToMonaco(highlighter, monaco)

  monaco.editor.create(document.getElementById('editor')!, {
    value: `const greeting: string = 'Hello, Shiki!'`,
    language: 'typescript',
    theme: 'github-dark',
    automaticLayout: true,
  })
}

void createEditor()
```

## 다크모드와 같이 테마를 동적으로 변경하고 싶다면

Shiki에 여러 테마를 미리 등록했다면 Monaco 테마로 바로 전환이 가능합니다.

```javascript
function applyTheme(isDark: boolean) {
monaco.editor.setTheme(isDark ? 'github-dark' : 'github-light');
}
```

중요한 점은 `monaco.editor.defineTheme()` 를 별도로 호출할 필요가 없으며, `shikiToMonaco()` 가 Shiki에서 로드한 테마를 Moaco 테마로 자동 등록해줍니다.

## 커스텀 언어셋을 나만의 언어로 하이라이팅 하고싶다면

만약 사내에서 커스텀 언어 (ex. 보안 위협 탐지룰 등) 으로 기본 언어 목록에 없는 문법으로 강조를 해야한다면, TextMate Grammer 파일, 즉 `.tmLanguage.json` 을 추가하여 로드해야합니다.

```javascript
import myDslGrammar from './my-dsl.tmLanguage.json'
import * as monaco from 'monaco-editor'
import { createHighlighter } from 'shiki'
import { shikiToMonaco } from '@shikijs/monaco'

const highlighter = await createHighlighter({
  themes: ['github-dark'],
  langs: [myDslGrammar],
})

monaco.languages.register({ id: 'my-dsl' })
shikiToMonaco(highlighter, monaco)
```

그리고 이후 모델 또는 에디터의 언어를 `my-dsl` 로 지정해주면 설정이 완료됩니다.

```javascript
editor.setModelLanguage(editor.getModel()!, 'my-dsl')
```

`tmLanguage.json` 은 색상 값을 담는 파일이 아니라, 어떤 문자열을 키워드, 문자열, 주석, 함수 명으로 볼지 정의하는 정규식 기반의 문법 파일입니다.

반면 실제 색상은 Shiki 하이라이팅의 테마가 결정합니다.

```javascript
{
  "name": 'my-dsl',
  "scopeName": "source.my-dsl",
  "patterns": [
    {
      "name": "keyword.control.my-dsl",
      "match": "\\b(task|when|end)\\b"
    },
    {
      "name": "string.quoted.double.my-dsl",
      "begin": "\"",
      "end": "\""
    }
  ]
}
```

문법을 처음부터 만드는 일은 생각보다 유지보수 비용이 많이 듭니다. 기존 VSCode 확장에 같은 언어의 TextMate Grammer가 있다면 그것을 재사용하는 것이 기회 비용을 줄일 수 있는 방법 중 하나이니 참고하면 좋을 것 같습니다.

For the site tree, see the [root Markdown](https://slashpage.com/timmy.md).
