TypeScriptでJupyter

「TypeScriptで[Jupyter](https://jupyter.org/)を使えないか」と気になってGemini DeepResearchに聞いてみたところ、[itypescript](https://github.com/winnekes/itypescript)や[tslab](https://github.com/yunabe/tslab)などを挙げてくれたのですが、それらに並んで**Deno Kernel**というのを教えてくれました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/058c4d06-f14b-492e-9910-53a0713b4b17.png)

DenoにJupyter Kernelが組み込まれているようです。驚きました。見てみたら本当にありました。

https://docs.deno.com/runtime/reference/cli/jupyter/

DenoはTypeScriptを第一級言語として直接実行できます。Jupyterのセルにコードを入力したらすぐ実行できます。これはPythonと同じであり、Jupyterと相性が良いと思います。

また、Denoは`npm install`をしなくても依存関係をインストールして利用できます。これはJupyterのセルにコードを書いてインタラクティブに実行できる仕組みや、単一の`.ipynb`ファイルでコードを共有しやすい仕組みと相性が良いです。Pythonには無いDenoの特徴です。
他にも細かいことで言うと、Pythonと違ってJavaScriptは変数への代入が式（expression）なので、セル末尾で代入を記述すれば出力される[^expression-statement]という特徴もあります。もしかして、PythonよりもDenoのほうがJupyterと相性が良いのでは...？

[^expression-statement]: JavaScriptにおいて変数代入は式だが、変数宣言は文なので値が返らない。

## Deno+Jupyter環境構築
### miseの場合
[mise](https://mise.jdx.dev/)を使っている場合、環境構築は簡単です。miseを使っていない場合は、[Deno](https://docs.deno.com/runtime/getting_started/installation/)と[uv](https://github.com/astral-sh/uv?tab=readme-ov-file#installation)を手動でインストールしてから2番目以降のコマンドを実行してください。

```
$ mise use deno uv
$ uv add jupyterlab
$ deno jupyter --install
$ uv run jupyter lab
```

localhost:8888 にアクセスすると画面が表示されます。

### Dockerの場合
:::note
このDockerfileでは最低限の記述のみをしているため、`apt-get`の処理などは適宜ベストプラクティスに従ってください。
:::

```Dockerfile:Dockerfile
FROM python:3.13-slim

WORKDIR /app

RUN pip install jupyterlab

# add Deno
RUN apt-get update && apt-get install -y curl zip unzip
RUN curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh

RUN deno jupyter --install

CMD [ "jupyter", "lab", "--no-browser", "--ip=0.0.0.0", "--allow-root", "--NotebookApp.token=''" ]
```

```compose.yaml
services:
  app:
    build: .
    volumes:
      - .:/app
    ports:
      - "8888:8888"
```

`$ docker compose up`して localhost:8888 にアクセスすれば表示されます。

Python（ipykernel）に加えてDenoのカーネルが選択できるようになっていれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/c9bac3cf-7842-4a6d-afeb-b1d984be15cb.png)

## 実践
Jupyterでは色々できますが、メインでやることはデータの分析や可視化と、実験的なコードの記述だと思います。ひとつずつ試していきます。

### 実践1: Deno+Jupyterで可視化
構造化データをグラフにして可視化してみます。

Polarsには公式のNode.jsバインディングがあるので、これを使います。

https://github.com/pola-rs/nodejs-polars

```typescript
import pl from "npm:nodejs-polars";
let df

df = pl.DataFrame([
  {"月":3, "日":1, "最高気温":18.2, "最低気温":7},
  {"月":3, "日":2, "最高気温":15.5, "最低気温":12.1},
  {"月":3, "日":3, "最高気温":12.2, "最低気温":5.7},
  {"月":3, "日":4, "最高気温":8.7,  "最低気温":3.7},
  {"月":3, "日":5, "最高気温":11.2, "最低気温":8.2},
  {"月":3, "日":6, "最高気温":11.7, "最低気温":4.7},
  {"月":3, "日":7, "最高気温":9.8,  "最低気温":3.8},
]);
```

Pythonの時と同様、ちゃんとリッチに表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/4275a0a7-3bfe-4806-a219-c468e7401ba0.png)

なお、このデータは[気象庁ホームページ](https://www.data.jma.go.jp/stats/etrn/view/daily_s1.php?prec_no=62&block_no=47772&year=2025&month=3&day=&view=)より、大阪府 2025年3月 日ごとの値 から引用しました。本来はCSVやJSONなどから読み込むものです。

このデータフレームを[Observable Plot](https://observablehq.com/plot/)でグラフにしてみます。

```typescript
import * as Plot from "npm:@observablehq/plot";
import { document } from "jsr:@ry/jupyter-helper";

Plot.plot({
  marks: [
    Plot.lineY(df.toRecords(), {x: "日", y: "最高気温"}),
  ],
  document,
});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/d89e6f28-e4c7-4e1e-a6e7-8a81e86562a4.png)

グラフが描画できました。PythonでPandasやmatplotlibを使うときと同じように、簡単なコードでデータの可視化ができました。

`jsr:@ry/jupyter-helper`から`document`をimportしていますが、これはブラウザ上のJavaScript開発でよく使う`window.document`オブジェクトに代わるもののようです。

グラフに色を付けたりもできます。

```typescript
Plot.plot({
  color: {legend: true},
  grid: true,
  marks: [
    Plot.ruleY([0]),
    Plot.lineY(df.toRecords(), {x: "日", y: "最高気温", stroke: "red"}),
    Plot.lineY(df.toRecords(), {x: "日", y: "最低気温", stroke: "blue"}),
    Plot.frame(),
  ],
  document,
});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/b05116d3-fb7b-4677-94a8-811d87cf0e90.png)


### 実践2: Deno+Jupyterで実験
Deno+Jupyterで実験的なコードを書いてみましょう。
実験といえばAI、AIといえばOpenAIです。

[openai-node](https://github.com/openai/openai-node)を使って、LLMを呼び出すコードを書いてみます。

```typescript
import OpenAI from 'jsr:@openai/openai';
const client = new OpenAI({
  apiKey: process.env['OPENAI_API_KEY'],
});

const response = await client.responses.create({
  model: 'gpt-4.1-mini',
  input: '日本で一番高い山は？関西弁で',
});

console.log(response.output_text);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/f539973e-8d41-47be-90d5-11087342cab1.png)

Jupyterは、この出力を見てからコードを少し変えて繰り返し実行するような実験的な開発をしやすくなっています。プロンプトやパラメータの試行錯誤が必要な生成AIとも相性が良いです。また、コードと出力を保存できるので、そのまま記録にもなります。「新しいモデルが出てきたから、ちょっと試したい」といったおためし的なコードとも相性が良いです。

## VS CodeでDeno+Jupyter
VS CodeにはMicrosoft公式Jupyterプラグインがあり、Denoカーネルでも動かすことができます。.ipynbファイルを開いたら、カーネルからDenoを選択すればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/7e14a1a4-de2d-478b-927f-fbfe2ef43903.png)

なお、データフレームを表示するにはセルのプレゼンテーションを`text/html`に変更する必要があるようです。
参考: https://github.com/microsoft/vscode-notebook-renderers/issues/189#issuecomment-2451818378

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/694a8418-ab83-47d1-bd9a-447405816f9a.png)

VS Codeで動かせるということは、VS Codeがデフォルトで持っている強力なTypeScriptの静的解析も使えますし、各種拡張機能なども有効です。GitHub CopilotやClineなどを使うこともできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/b77f1772-9000-42c7-91fb-a392354dedc7.png)

## まとめ
DenoにJupyterカーネルが組み込まれていて、TypeScriptでJupyterが使えました。

Jupyter+Denoを使うことで...

- データを分析・可視化したりしやすい
- アドホックなコードで実験したりしやすい
- コードと実行結果を記録して共有したりしやすい

ひとことで言うと「雑な用途で気軽に使ってOK」な環境が手に入ります。

一方で、Jupyter周辺の機能はPython（ipykernel）を前提としているものも多くDenoではうまく利用できないこともあり、Python+Jupyterに比べて万能というわけではありません。DenoカーネルはGoogle ColabのようなJupyterのクラウド環境では基本的に使えません。TypeScriptには[CodeSandbox](https://codesandbox.io/)のようなJupyterではない手軽な環境も色々あります。必要に応じて好きな方法を使うのが良いと思います。

本稿で使った実際のコードはGitHubでも公開しています。

https://github.com/s2terminal/typescript-jupyter-demo