Vibe Codingした後に、Claude Codeと学習しよう!
はじめに
先日、MCP (Model Context Protocol) ServerのStreamable HTTPトランスポートを最小構成で実装しました。もちろん、Claude Codeと一緒に!ここで生成されたソースコード群(プロジェクト)について、Claude Codeと対話しながら、Node.jsやTypeScript、Dockerの理解を深めました。
Vibe Codingだけではもったいないと思っていて、Claude Codeと一緒に生成したアウトプットを見ていくと学びが多くスキルアップに繋がるのではと!
具体的なやり取りを記事にしました。私の質問の仕方など参考になるところがあるかもしれません。ご覧頂けると幸いです!
Claude Codeの画面
題材にしたプロジェクトの構成
Azureにコンテナを作って、MCP Clientからリモート経由で接続する構成となっています。
┌─────────────────────────────────────────────────────────────────┐
│ Azure Cloud │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Azure Container │ │ Azure Container Instances (ACI) │ │
│ │ Registry (ACR) │ │ │ │
│ │ │ │ ┌─────────────────────────────┐ │ │
│ │ - Docker Image │───▶│ │ MCP Streamable HTTP Server │ │ │
│ │ Storage │ │ │ - Node.js 20 │ │ │
│ │ - Private Registry │ │ │ - Express.js │ │ │
│ │ │ │ │ - Port 8080 │ │ │
│ └─────────────────────┘ │ │ - Stateless Mode │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ │ │
│ │ Public IP: xxx.xxx.xxx.xxx │ │
│ │ FQDN: server.region.azure... │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▲
│ HTTP/HTTPS
│
┌─────────────────────────────────────────────────────────────────┐
│ Client Side │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │
│ │ MCP Inspector │ │ Future: ChatGPT Integration │ │
│ │ (Testing Tool) │ │ (MCP Custom Connector) │ │
│ │ │ │ │ │
│ │ - Streamable HTTP │ │ - When officially supported │ │
│ │ - URL: server/mcp │ │ - Custom Connector API │ │
│ │ - Tool Testing │ │ - Remote MCP Server │ │
│ └─────────────────────┘ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
TypeScriptで実装しています。
mcp-streamable-http-server/
├── src/
│ └── server.ts # メインサーバー実装 (Claude Codeとの対話で不要になった)
├── dist/ # TypeScriptコンパイル結果
│ └── server.js # コンパイル済みJavaScript
├── package.json # Node.js依存関係とスクリプト
├── tsconfig.json # TypeScript設定
├── Dockerfile # コンテナイメージ定義
├── .dockerignore # Docker除外ファイル設定
└── README.md # プロジェクト説明(オプション)
🧑💻 私とClaude Codeの対話記録
📁 プロジェクト構造について
🧑💻 私: このプロジェクトについて、 @src と @mcp-streamable-http-server の2つのフォルダがある。どちらもtsファイルが存在するが違いはなんだろうか。
🤖 Claude Code: 構造から見ると、srcフォルダは単体のTypeScriptファイルがあるだけのシンプルな構成で、mcp-streamable-http-serverフォルダは完全なNode.jsプロジェクトの構造(package.json、node_modules、dist、Dockerfileなど)を持っています。
[server.tsファイルの内容を確認...]
両方のserver.tsファイルは全く同じ内容です。違いは:
- src/server.ts: ルートのpackage.jsonと一緒に使用される開発用ファイル
- mcp-streamable-http-server/src/server.ts: 完全なNode.jsプロジェクトとして独立して動作し、Dockerfileもあるため本番デプロイ用
mcp-streamable-http-serverフォルダの方が依存関係(dependencies)も含まれており、ビルド済みのdistフォルダもあるため、実際に動作させる環境として整備されています。
🧑💻 私: 開発の経緯を失念してしまったのですが、この2つのフォルダを残したほうが良いですかね?消してもよいのかなと思いつつ、今後保守開発の可能性もあり残したほうが良いのかなとも思い。
🤖 Claude Code: 現状を見ると、両方とも同じコードで冗長になっています。整理することをお勧めします:
推奨案:
-
mcp-streamable-http-serverフォルダを残す
- 完全なプロジェクト構造(package.json、依存関係、Dockerfile)がある
- 本番デプロイ用に整備されている
- Azure向けのStreamable HTTPトランスポートという明確な用途がある
-
ルートのsrc/server.tsは削除
- 単体ファイルで依存関係の管理がない
- mcp-streamable-http-serverと完全に重複
この方法なら、今後の保守開発時も:
- 依存関係が明確
- ビルド・デプロイ手順が整備済み
- Dockerでの動作確認も可能
ルートのpackage.jsonとtsconfig.jsonも削除して、プロジェクトをmcp-streamable-http-serverに集約するのが良いでしょう。
🧑💻 私: jsonファイルも重複ですか?
🤖 Claude Code: [tsconfig.jsonとpackage.jsonの内容を確認...]
はい、JSONファイルも重複しています:
- package.json: 両方とも同じ名前、バージョン、説明を持っているが、mcp-streamable-http-serverの方には依存関係(dependencies)が含まれている
- tsconfig.json: 完全に同一の内容
つまり、ルートにある3つのファイル(src/server.ts、package.json、tsconfig.json)は全てmcp-streamable-http-serverフォルダ内のファイルと重複しているか、その簡略版です。
mcp-streamable-http-serverフォルダの方が完全なプロジェクト構造を持っているので、ルートのファイルは削除して問題ありません。
🧑💻 私: OK。削除の対応しました。すっきりした。
🤖 Claude Code: よかったです!プロジェクトが整理されてすっきりしましたね。
これで:
- mcp-streamable-http-serverに全てが集約
- 依存関係やビルド設定も明確
- Dockerfileもあるので本番デプロイも簡単
今後の保守開発もやりやすくなったと思います。
📂 Node.jsプロジェクト構造の理解
🧑💻 私: @mcp-streamable-http-server についてさらに見ていきたいです。 @src/はtypescript fileが格納されていて、 @dist/ はビルド後のjava script fileが格納されている認識です。 @node_modules は、pythonでいうvenvの仮想環境内で使えるモジュールということ?
🤖 Claude Code: その理解でほぼ正しいです!少し補足します:
src/: TypeScriptのソースコード(開発用)
dist/: コンパイル後のJavaScriptファイル(実行用)
node_modules/: Pythonのvenvとは少し違います
- venvは「仮想環境」を作る
- node_modulesは「プロジェクトの依存パッケージの実体」を保存
[dist/フォルダの内容を確認...]
Pythonとの違い:
- Python (venv): 環境を分離するだけで、パッケージ自体は別の場所(site-packages)に保存
- Node.js (node_modules): 各プロジェクトのフォルダ内に依存パッケージの実体を全て保存
つまり、node_modulesは:
- package.jsonで指定された全ての依存パッケージ(@modelcontextprotocol/sdk、expressなど)
- それらの依存パッケージが依存するパッケージ(間接的な依存)
- 全てがこのフォルダに物理的にダウンロードされている
これがNode.jsプロジェクトが巨大になりやすい理由です。各プロジェクトが独自のnode_modulesを持つので。
🧑💻 私: pythonのvenvも、requirements.txtで指定された依存パッケージの情報が、.venv/Libにダウンロードされている認識です。
環境分離という概念についてはPythonのvenv独自かもしれません。
🤖 Claude Code: おっしゃる通りです!私の説明が不正確でした。
Python (venv):
- .venv/lib/(またはLib/)に依存パッケージの実体がダウンロードされる
- requirements.txtやpyproject.tomlで依存関係を管理
- 環境分離:システムのPython環境から独立した環境を作る
Node.js (node_modules):
- node_modules/に依存パッケージの実体がダウンロードされる
- package.jsonで依存関係を管理
- 環境分離の概念なし:プロジェクトごとに独立してパッケージを持つのがデフォルト
つまり:
- 共通点: どちらもプロジェクトローカルに依存パッケージをダウンロード
- 違い: Pythonは「環境の切り替え」という概念があるが、Node.jsは最初からプロジェクトごとに独立
Node.jsの方がシンプルな仕組みで、各プロジェクトが完全に独立したパッケージセットを持つという設計思想ですね。
📦 package-lock.jsonの役割
🧑💻 私: package.jsonが依存関係を管理と認識できました。一方で、package-lock.jsonはどんな役割りですか?lockと書いてありますがイメージがつかなくて。何かを固定する?
🤖 Claude Code: package-lock.jsonは依存関係のバージョンを完全に固定するファイルです。
[package-lock.jsonの内容を確認...]
役割の違い:
- package.json: 「express: ^5.1.0」(5.1.0以上の5.x.xならOK)
- package-lock.json: 「express: 5.1.0」(必ず5.1.0を使う)
なぜ必要?
開発者A: express ^5.1.0でインストール → 5.1.0が入る
開発者B: 1週間後に同じpackage.jsonでインストール → 5.1.2が入る(新版がリリースされた)
これだと環境によって微妙に違うバージョンが入り、バグの原因になる。
package-lock.jsonがあれば:
- 全員が同じバージョンをインストール
- 依存の依存(間接的な依存)も全て固定
- 「私の環境では動くのに...」を防ぐ
Pythonでいうとpip freeze > requirements.txtで厳密なバージョンを記録するのに似ています。
🧑💻 私: 環境構築、例えばDocker containerを構築するときに、package.jsonとpackage-lock.jsonはどのような挙動になりますか?package.jsonで依存関係を把握してパッケージをインストールするがその際package-lock.jsonを確認してバージョンを固定するとか?
🤖 Claude Code: [Dockerfileの内容を確認...]
まさにその通りです!Dockerfileを見ると:
重要な部分:
- COPY package*.json ./ → package.jsonとpackage-lock.json両方をコピー
- npm ci --only=production → npm ciを使用
npm installとnpm ciの違い:
-
npm install:
- package.jsonを見て依存関係を解決
- package-lock.jsonがあれば参考にするが、更新することもある
- 開発時によく使う
-
npm ci (Clean Install):
- 必ずpackage-lock.jsonを使用
- package-lock.jsonに記載された正確なバージョンをインストール
- package-lock.jsonがないとエラーになる
- node_modulesを削除してから新規インストール
- CI/CD環境やDocker構築で推奨
つまり、Docker構築時の流れ:
- package-lock.jsonで固定されたバージョンを確認
- その通りにインストール(一切の変更なし)
- 全ての環境で同じバージョンが保証される
これが「再現性のあるビルド」を実現する仕組みです。
📋 package.jsonの継続的な役割
🧑💻 私: ありがとう。となるとpackage.jsonってどのような役割りになりますか?初期構築のときに使って、その時にpackage-lock.jsonが出力されてそれ以降は使われない?
🤖 Claude Code: いえ、package.jsonは継続的に重要な役割を持っています!
package.jsonの役割:
-
プロジェクトのメタ情報
- 名前、バージョン、説明
- スクリプト(npm run build、npm startなど)
- ライセンス情報
-
依存関係の「意図」を表現
"express": "^5.1.0" // 5.x.xの最新を使いたい "zod": "~3.22.0" // 3.22.xの範囲で "@types/node": "*" // 最新版でOK
-
新しいパッケージ追加時
npm install axios # package.jsonに追加される # package-lock.jsonも更新される
使い分け:
- 開発者: package.jsonを編集、npm installで更新
- CI/本番: package-lock.jsonを使ってnpm ciで固定版をインストール
流れ:
- 初回: package.json → npm install → package-lock.json生成
- 追加: npm install xxx → 両方更新
- 他の開発者: npm ci → package-lock.jsonの通りインストール
package.jsonは「設計図」、package-lock.jsonは「詳細な施工図」のような関係です。
⚙️ tsconfig.jsonとtscコマンド
🧑💻 私: tsconfig.jsonはどのような役割り?
🤖 Claude Code: tsconfig.jsonはTypeScriptコンパイラの設定ファイルです。
[tsconfig.jsonの内容を確認...]
主な設定内容:
-
コンパイル設定
- target: "ES2022" → 出力するJavaScriptのバージョン
- module: "NodeNext" → モジュールシステム(import/export)の形式
- outDir: "./dist" → コンパイル結果の出力先
- rootDir: "./src" → ソースコードの場所
-
型チェックの厳しさ
- strict: true → 厳密な型チェック有効
- skipLibCheck: true → 外部ライブラリの型定義はスキップ
-
開発支援
- declaration: true → 型定義ファイル(.d.ts)を生成
- sourceMap: true → デバッグ用のマップファイル生成
-
対象ファイル
- include: ["src/**/*"] → srcフォルダ内の全TSファイル
- exclude: ["node_modules", "dist"] → 除外するフォルダ
役割:
npm run build # tscコマンドがtsconfig.jsonを読んでコンパイル
↓
src/server.ts → dist/server.js(ES2022準拠のJSに変換)
PythonでいうとPylintやmypyの設定ファイルに近い役割です。
🧑💻 私: tscコマンドってどういう役割り?
🤖 Claude Code: tscはTypeScript Compilerの略で、TypeScriptをJavaScriptに変換するコマンドです。
基本的な動作:
# TypeScript → JavaScript
tsc src/server.ts
↓
src/server.ts → src/server.js
tsconfig.jsonと組み合わせると:
# tsconfig.jsonの設定に従ってコンパイル
tsc
↓
src/server.ts → dist/server.js(設定通りの場所に出力)
🧑💻 私: npm run buildが裏ではtscコマンドを実行している?
🤖 Claude Code: はい、その通りです!
[package.jsonのscriptsセクションを確認...]
package.jsonの8行目を見ると:
"build": "tsc",
つまり:
- npm run build → tscを実行
他のスクリプトも:
- npm start → node dist/server.jsを実行
- npm run dev → tsx watch src/server.tsを実行
流れ:
- npm run buildを実行
- npmがpackage.jsonのscriptsを確認
- "build"に対応する"tsc"を実行
- tscがtsconfig.jsonを読んでTypeScriptをコンパイル
これにより、チームメンバーが同じコマンドで作業できるようになります。
🛠️ npmコマンド一覧
🧑💻 私: ありがとうnpmって、npm install、さらにはnpxってコマンドもあるよね。この辺の有名なコマンドと機能概要、実態(npm start -> node dist/server.js)を一覧で教えて。
🤖 Claude Code: NPMコマンド一覧:
コマンド | 機能概要 | 実態・詳細 |
---|---|---|
npm install | パッケージをインストール | package.jsonの依存関係を全てnode_modulesにダウンロード |
npm install [pkg] | 特定パッケージを追加 | npm install express → package.jsonに追加&インストール |
npm install -D [pkg] | 開発用パッケージを追加 | devDependenciesに追加(本番環境では不要) |
npm install -g [pkg] | グローバルインストール | システム全体で使えるようにインストール |
npm ci | クリーンインストール | package-lock.jsonから正確にインストール(CI/CD用) |
npm run [script] | スクリプト実行 | package.jsonのscriptsセクションのコマンドを実行 |
npm start | startスクリプト実行 | npm run startの省略形 |
npm test | testスクリプト実行 | npm run testの省略形 |
npm init | 新規プロジェクト作成 | package.jsonを対話的に作成 |
npm update | パッケージ更新 | package.jsonの範囲内で最新版に更新 |
npm uninstall [pkg] | パッケージ削除 | node_modulesとpackage.jsonから削除 |
npm list | インストール済み一覧 | 依存関係ツリーを表示 |
npm outdated | 古いパッケージ確認 | 更新可能なパッケージを表示 |
npx [command] | パッケージを一時実行 | インストールせずに実行(例: npx create-react-app) |
npxの特徴:
# 従来の方法
npm install -g create-react-app
create-react-app my-app
# npxを使うと
npx create-react-app my-app # インストール不要で実行
🧑💻 私: npmにinstallやstart, testってあるけど、package.jsonにそれぞれ実行すべきコマンドが書いてあって呼ばれるのね。
🤖 Claude Code: はい、まさにその通りです!ただし、少し違いがあります:
特別扱いされるスクリプト:
- npm start → scripts.startを実行
- npm test → scripts.testを実行
- npm stop → scripts.stopを実行
- npm restart → scripts.restartを実行
それ以外はrunが必要:
- npm run build → scripts.buildを実行
- npm run dev → scripts.devを実行
installは特殊:
- npm installはpackage.jsonのscriptsではなく、npmの組み込みコマンド
- ただし、インストール前後に実行されるフックはある:
"scripts": {
"preinstall": "echo インストール前",
"postinstall": "echo インストール後"
}
例:
"scripts": {
"start": "node dist/server.js", // npm start
"test": "jest", // npm test
"build": "tsc", // npm run build ← runが必要
"dev": "nodemon src/index.js" // npm run dev ← runが必要
}
この仕組みにより、プロジェクトごとに異なるコマンドを統一的な方法で実行できます。
👀 watchモードの便利さ
🧑💻 私: npm run buildとnpm run devの違いはなに?watchが含まれているか?だけだけどwatchの役割りは?
🤖 Claude Code: watchはファイルの変更を監視して自動的に再実行する機能です。
違いを見てみましょう:
"build": "tsc",
"dev": "tsx watch src/server.ts"
npm run build (本番ビルド用):
- 1回だけコンパイル実行
- src/server.ts → dist/server.js
- 完了したら終了
npm run dev (開発用):
- ファイルを監視し続ける
- server.tsを編集・保存するたびに:
- 自動的に再コンパイル
- サーバーを自動再起動
- Ctrl+Cで停止するまで動き続ける
開発時の流れ:
- npm run dev 実行
- "Watching for changes..." と表示
- server.tsを編集して保存
- 自動的に再起動 → すぐに動作確認できる
- また編集 → また自動再起動
これにより、いちいち手動でビルド・再起動する手間が省けて、開発効率が大幅に向上します。
PythonでいうとFlaskのapp.run(debug=True)やFastAPIの--reloadオプションと同じような機能です。
🧑💻 私: 開発においてはめちゃ便利じゃん
🤖 Claude Code: まさにその通りです!開発効率が全然違います。
watchなし:
編集 → 保存 → Ctrl+C → npm run build → npm start → 確認
↓
また編集 → 保存 → Ctrl+C → npm run build → npm start → 確認
毎回この繰り返し...
watchあり:
npm run dev (1回だけ)
↓
編集 → 保存 → 自動で再起動 → すぐ確認
編集 → 保存 → 自動で再起動 → すぐ確認
さらに便利な点:
- エラーがあったらすぐターミナルに表示
- 構文エラーも即座にフィードバック
- APIの動作確認がリアルタイムでできる
現代の開発では必須の機能で、ほぼ全てのフレームワークが対応しています:
- React: npm start(Create React App)
- Next.js: npm run dev
- Vue: npm run serve
- Python: Flask --debug、FastAPI --reload
一度この便利さに慣れると、もう戻れないですね!
📦 TypeScriptコンパイルの必要性
🧑💻 私: ありがとう。理解が進んだよ。次にsrcからdistへコンパイルする必要性を教えて。
🤖 Claude Code: TypeScriptからJavaScriptへのコンパイルが必要な理由:
- ブラウザ/Node.jsはTypeScriptを直接実行できない
// TypeScript(src/server.ts)
const greet = (name: string): string => {
return `Hello, ${name}!`;
}
↓
// JavaScript(dist/server.js)
const greet = (name) => {
return `Hello, ${name}!`;
}
- 型情報は実行時には不要
- TypeScript: 開発時の安全性(型チェック)
- JavaScript: 実行時は型情報を削除
- 最適化とパフォーマンス
- 開発時: 読みやすさ重視(コメント、空白、型注釈)
- 本番時: ファイルサイズ最小化、実行速度優先
- 環境の違いに対応
// 最新のTypeScript構文
const result = array?.filter(x => x > 0) ?? [];
↓
// 古い環境でも動くJavaScript
const result = array ? array.filter(x => x > 0) : [];
なぜsrc/distに分ける?
- src/: 人間が読み書きするコード
- dist/: マシンが実行するコード
- .gitignoreでdist/を除外(自動生成されるため)
これにより、開発時は型安全性を享受しつつ、実行時は最適化されたコードが動きます。
🌐 TypeScriptと各フレームワークの関係
🧑💻 私: ありがとう。type scriptには、node.js、next.js、vue.jsがあるけど、このtypescriptの書き方が違うってこと?
🤖 Claude Code: いえ、少し整理が必要です!
言語とフレームワークの関係:
JavaScript/TypeScript(言語)
├── Node.js(実行環境)
├── React(UIライブラリ)
│ └── Next.js(Reactのフレームワーク)
└── Vue.js(UIフレームワーク)
TypeScriptの書き方は基本的に同じ:
// どの環境でも同じTypeScript
const add = (a: number, b: number): number => {
return a + b;
}
違うのはフレームワーク固有の部分:
// React/Next.js
const Component: React.FC = () => {
return <div>Hello</div>;
}
// Vue.js
export default defineComponent({
setup() {
return { message: 'Hello' };
}
});
// Node.js (Express)
app.get('/', (req: Request, res: Response) => {
res.send('Hello');
});
つまり:
- TypeScript自体は同じ
- フレームワークのAPI/書き方が違う
- JSX(React)、SFC(Vue)、APIルート(Express)など
PythonでDjango、Flask、FastAPIの書き方が違うのと同じですね。
🧑💻 私: 実行環境(Node.js)上に、React(Next.js)やVue.jsが場合によって実行されるイメージ?となると、npmでnext.jsやvue.jsをインストールする?
🤖 Claude Code: はい、その理解で正しいです!
構造:
Node.js(JavaScriptの実行環境)
└── npm(パッケージマネージャー)
├── React/Next.js
├── Vue.js
└── Express(今回のプロジェクト)
インストール方法:
# Expressプロジェクト(今回のMCPサーバー)
npm install express
# Reactプロジェクト
npm install react react-dom
# Next.jsプロジェクト(Reactを含む)
npm install next react react-dom
# Vue.jsプロジェクト
npm install vue
または新規プロジェクト作成:
# Next.js
npx create-next-app@latest my-app
# Vue.js
npm create vue@latest my-app
# Express
npm init -y && npm install express
[package.jsonの依存関係を確認...]
今回のプロジェクトはExpressを使ったAPIサーバーです。React/Vue等のUIフレームワークは使わず、純粋なバックエンドサーバーとして動作します。
🧑💻 私: なるほどね。node.jsがtype scriptの根幹をなす実行環境となるんだね。
🤖 Claude Code: 正確には、Node.jsはJavaScriptの実行環境です!
正しい関係:
JavaScript(元の言語)
↓
TypeScript(JavaScriptの拡張版)
↓ コンパイル(tsc)
JavaScript
↓ 実行
Node.js または ブラウザ
整理すると:
- Node.js: JavaScriptを実行する環境(V8エンジン搭載)
- TypeScript: JavaScriptに型を追加した言語
- tsc: TypeScript → JavaScript変換
実行の流れ:
- TypeScriptで開発(src/server.ts)
- tscでJavaScriptに変換(dist/server.js)
- Node.jsで実行(node dist/server.js)
開発時の便利ツール:
- tsx: TypeScriptを直接実行(内部で変換)
tsx src/server.ts # コンパイル不要で実行
つまり、Node.jsはあくまでJavaScriptの実行環境で、TypeScriptは開発を便利にするための「上位言語」という位置づけです。
🛠️ tscコンパイラの詳細
🧑💻 私: これまでの会話を考えると、tscでvue.jsやreact(next.js)のtype scriptもコンパイルできるよね?tscはNode.jsが提供するコンパイル機能?
🤖 Claude Code: はい、tscは全てのTypeScriptをコンパイルできます!ただし、tscはNode.jsではなくTypeScript自体が提供するコンパイラです。
整理:
# TypeScriptをインストールすると、tscコマンドが使える
npm install -D typescript
↓
node_modules/.bin/tsc # ここにtscがインストールされる
tscがコンパイルできるもの:
- React/Next.jsのTypeScript ✅
- Vue.jsのTypeScript ✅
- Node.js用のTypeScript ✅
- どんなTypeScriptでも ✅
ただし、フレームワーク特有の構文は別途処理が必要:
// JSX(React)- tscだけでもOK(設定次第)
const App = () => <div>Hello</div>
// Vue SFC - tscだけでは不可、別途vue-loaderが必要
<template>
<div>{{ message }}</div>
</template>
<script lang="ts">
// TypeScript部分
</script>
まとめ:
- tsc = TypeScriptプロジェクトが提供
- Node.js = JavaScriptの実行環境を提供
- tscはNode.js上で動くツール(npmでインストール)
関係性:TypeScript(言語) → tsc(コンパイラ) → JavaScript → Node.js(実行)
🧑💻 私: なるほど。node.js自体はjavascriptの実行環境にすぎない。node.jsを人間が開発しやすいようにtype scriptというパッケージがありnpmコマンド(実態はtsc)でnode.jsが実行できるjavascriptに変換ができる。tscコマンドでコンパイルする場合はvue.jsやreact(next.js)、expressを利用する場合パッケージのインストールが必要でこのパッケージも使ってjavascriptを生成する。ということかな?
🤖 Claude Code: ほぼ正しいですが、少し整理させてください:
正確な関係:
-
Node.js = JavaScriptの実行環境(正しい✅)
-
TypeScript = MicrosoftがJavaScriptを拡張した言語
- npmでインストール: npm install -D typescript
- tscコマンドが付属(npmコマンドではない)
-
コンパイルの仕組み:
TypeScript → tsc → JavaScript → Node.jsで実行
-
フレームワークの扱い:
- Express: tscだけでOK(追加ツール不要)
- React/Next.js: tscだけでOK(JSXもtscが処理可能)
- Vue.js: Vue特有の.vueファイルは専用ツールが必要
重要な修正点:
- tscはnpmコマンドの実態ではなく、TypeScriptパッケージに含まれる独立したコンパイラ
- Express等のパッケージは実行時に必要だが、コンパイル時には型定義(@types/express)があれば十分
流れ:
# 開発
npm install express # 実行時に必要
npm install -D typescript # tscコンパイラ
npm install -D @types/express # 型定義
# コンパイル
tsc # TypeScript → JavaScript変換
# 実行
node dist/server.js # Expressを使ったJSコードが動く
🧑💻 私: ただ、npm run buildでtscが実行されるんだよね。あぁ、でもこれはpackage.jsonに定義しているにすぎないのか。
🤖 Claude Code: はい、まさにその通りです!
npmは単なる「タスクランナー」として動作:
"scripts": {
"build": "tsc", // npmがtscを呼び出すだけ
"compile": "tsc", // 同じことを別名でも定義可能
"typescript": "tsc" // 名前は自由
}
npmの役割:
- package.jsonのscriptsを読む
- 指定されたコマンドを実行
- それだけ!
直接実行との違い:
# 直接実行
./node_modules/.bin/tsc
# npmスクリプト経由(便利)
npm run build
なぜnpm runを使う?
- パスを覚えなくていい(node_modules/.bin/を自動で見る)
- チーム全員が同じコマンドを使える
- 複雑なコマンドも簡潔に
"build": "tsc && cp -r public dist/" // 複数コマンドも可能
つまり、npmは「便利なショートカット機能」を提供しているだけで、実際の処理はtscやnode等の各ツールが行っています。
📁 dist/フォルダの扱い
🧑💻 私: ありがとう。一方で、 @mcp-streamable-http-server/dist/ のなかってコンパイル結果が出力されるけど、これは原則触らないであっているかな。あくまでもtscのコンパイラを信頼して、我々はtsファイルを更新していく考え方?
🤖 Claude Code: はい、その考え方で完全に正しいです!
dist/フォルダの原則:
- ❌ 手動で編集しない
- ❌ gitにコミットしない(.gitignoreに追加)
- ✅ 自動生成されるものと理解
- ✅ 削除してもnpm run buildで再生成可能
dist/内のファイル:
- server.js - コンパイル済みJavaScript
- server.d.ts - 型定義ファイル(他のTSプロジェクトが使う用)
- *.map - デバッグ用マップ(TSとJSの対応関係)
開発フロー:
- src/server.ts を編集
- npm run build でコンパイル
- dist/server.js が自動生成/更新
- 繰り返し
もしdist/を手動編集すると:
- 次回のビルドで上書きされる
- TypeScriptとの整合性が崩れる
- バグの温床になる
Pythonで例えると、.pycファイルや__pycache__を触らないのと同じ考え方です。ソースコードを信頼し、コンパイラに任せるのが正解です。
🐳 Dockerfileの詳細解説
🧑💻 私: 次に、Dockerfileと.dockerignoreについて教えて。これは手順として、tscでコンパイルしたあとの資源とpackage.jsonを使ってコンテナイメージを作成するかな?
🤖 Claude Code: [Dockerfileと.dockerignoreの内容を確認...]
まさにその通りです!手順を整理すると:
ビルド手順:
-
ローカルでTypeScriptをコンパイル
npm run build # src/ → dist/
-
Dockerイメージ作成時の流れ:
- package*.jsonをコピー(依存関係の定義)
- npm ci --only=productionで本番用パッケージのみインストール
- コンパイル済みのdist/をコピー
- 実行コマンドはnode dist/server.js
.dockerignoreの役割:
node_modules # 大きいし、コンテナ内で再インストールするから不要
src # TypeScriptソースは不要(distがあれば十分)
*.ts # TypeScriptファイルは全て不要
tsconfig.json # コンパイル設定も不要
なぜこの方式?
- 軽量化: 実行に必要なものだけ含める
- 高速化: node_modulesをコピーせず、コンテナ内でインストール
- セキュリティ: ソースコードを含めない
つまり、Dockerイメージには「実行に必要な最小限」だけを含めるという設計です。
🧑💻 私: Dockerfileのそれぞれのコマンドについておしえて。
FROM node:20-slim
WORKDIR /app
# package.jsonとpackage-lock.jsonをコピー
COPY package*.json ./
# 本番依存関係のみインストール
RUN npm ci --only=production
# ビルド済みのアプリケーションコードをコピー
COPY dist ./dist
# ポート公開
EXPOSE 8080
# 環境変数設定
ENV NODE_ENV=production
ENV PORT=8080
# アプリケーション起動
CMD ["node", "dist/server.js"]
また、アプリケーション起動が開発環境のコマンドと異なると思っているけど、違いを教えて。--only=productionの役割りも知りたい。
🤖 Claude Code: Dockerfileの各コマンド解説:
コマンド | 説明 |
---|---|
FROM node:20-slim | ベースイメージ。Node.js v20の軽量版を使用 |
WORKDIR /app | コンテナ内の作業ディレクトリを/appに設定 |
COPY package*.json ./ | package.jsonとpackage-lock.jsonをコンテナにコピー |
RUN npm ci --only=production | 本番用の依存関係のみインストール(後述) |
COPY dist ./dist | ビルド済みJSファイルをコンテナにコピー |
EXPOSE 8080 | コンテナが8080ポートを使うことを宣言(ドキュメント的) |
ENV NODE_ENV=production | 環境変数設定(本番モード) |
ENV PORT=8080 | ポート番号を環境変数で設定 |
CMD ["node", "dist/server.js"] | コンテナ起動時の実行コマンド |
開発環境と本番環境の違い:
# 開発環境
npm run dev # tsx watch src/server.ts
↓
- TypeScriptを直接実行
- ファイル変更を監視
- 自動再起動
# 本番環境(Docker)
node dist/server.js
↓
- コンパイル済みJSを実行
- 監視なし(安定性重視)
- 高速起動
--only=productionの役割:
"dependencies": {
"express": "^5.1.0" // 本番で必要 ✅
},
"devDependencies": {
"typescript": "^5.8.3", // 開発時のみ ❌
"tsx": "^4.19.4" // 開発時のみ ❌
}
--only=productionにより、devDependenciesはインストールされず、イメージサイズが大幅に削減されます。
🧑💻 私: node dist/server.js とnpm startとの違いはpackage.jsonを使うか使わないか。となると、Dockerfile`ni
🤖 Claude Code: その通りです!実は同じことをしています。
[package.jsonのstartスクリプトを確認...]
見ての通り:
- npm start → node dist/server.jsを実行
- DockerfileのCMD ["node", "dist/server.js"] → 直接実行
Dockerfileを以下のように変更することも可能:
CMD ["npm", "start"]
どちらが良い?
方式 | メリット | デメリット |
---|---|---|
node dist/server.js | 直接的で高速、npmプロセスが不要 | package.jsonの変更が反映されない |
npm start | package.jsonに従う、変更に柔軟 | npmプロセスが1つ余分に起動 |
一般的には本番環境ではnode直接実行が推奨されます:
- わずかに高速
- プロセスが1つ少ない
- シンプルで明確
ただし、複雑な起動スクリプトがある場合はnpm startの方が管理しやすいこともあります。
🚀 devDependenciesとDockerの関係
🧑💻 私: なるほどね。あと、 --only=productionにより、devDependenciesはインストールされずとあります。これは下記がインストールされないということ?これはdistにjavascriptファイルがあるからだね。
"devDependencies": {
"@types/express": "^5.0.2",
"@types/node": "^22.15.30",
"tsx": "^4.19.4",
"typescript": "^5.8.3"
🤖 Claude Code: はい、まさにその通りです!
インストールされないdevDependencies:
- typescript - コンパイラ(すでにdist/にJS生成済み)
- tsx - TS直接実行ツール(本番では不要)
- @types/express - 型定義(コンパイル時のみ必要)
- @types/node - 型定義(コンパイル時のみ必要)
なぜ不要?
開発時の流れ:
TypeScript + 型定義 → tscでコンパイル → JavaScript
本番時の流れ:
JavaScript(dist/)→ node実行(型情報は不要)
サイズの違い:
# 全部インストール
npm install # 約100MB(仮)
# 本番用のみ
npm ci --only=production # 約20MB(仮)
つまり:
- dist/に完成品のJSがある
- 型定義やコンパイラは実行時に不要
- expressなど実行時に必要なものだけインストール
これにより、Dockerイメージが軽量化され、デプロイも高速化されます。開発ツールを本番環境に持ち込まないという、セキュリティ面でも良い習慣です。
Discussion