Viemの基礎学習5日目(ブラウザからの対話(TypeScript))
Viteを使用したTypeScriptフロントエンド開発環境の構築
日付: 2025年11月10日
学習内容: Viteを使用したTypeScriptプロジェクトの作成、Viemライブラリのインストール、フロントエンドからスマートコントラクトとのインタラクション、デプロイワークフローの理解
1. プロジェクト構造の設計
1.1 全体的なディレクトリ構成
viem/
├── foundry/ # Foundryプロジェクト
│ ├── src/
│ │ └── SomeContract.sol
│ └── foundry.toml
├── frontend/ # TypeScriptフロントエンド
│ ├── src/
│ │ └── main.ts # メインTypeScriptファイル
│ ├── index.html
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── foundry.toml
└── README.md
ポイント:
- Foundryプロジェクトとフロントエンドプロジェクトを分離
- それぞれ独立した設定ファイルを持つ
- モノレポ構成で管理しやすい構造
2. Viteプロジェクトの作成
2.1 プロジェクトの初期化
# プロジェクトルートで実行
npm create vite@latest frontend -- --template vanilla-ts
--template vanilla-ts の意味:
-
vanilla: フレームワーク(React/Vue等)を使わない素のJavaScript/TypeScript -
ts: TypeScriptを使用
2.2 生成されるファイル構造
frontend/
├── index.html # エントリーポイント
├── package.json # 依存関係とスクリプト
├── tsconfig.json # TypeScript設定
├── vite.config.ts # Vite設定
└── src/
├── main.ts # メインのTypeScriptファイル
├── style.css # スタイルシート
└── vite-env.d.ts # Vite型定義
2.3 他のテンプレートオプション
# React + TypeScript
npm create vite@latest frontend -- --template react-ts
# Vue + TypeScript
npm create vite@latest frontend -- --template vue-ts
# Svelte + TypeScript
npm create vite@latest frontend -- --template svelte-ts
選択の指針:
- シンプルなインタラクション →
vanilla-ts - リアクティブなUI →
react-tsまたはvue-ts - 軽量なフレームワーク →
svelte-ts
2.4 依存関係のインストール
cd frontend
npm install
# viemをインストール
npm install viem
依存関係のインストールとは:
プロジェクトで必要なパッケージ(ライブラリやツール)をnode_modulesフォルダにダウンロードし、使用可能にする作業です。
2.4.1 npm install の役割
package.jsonに記載されているすべての依存関係をインストールします。
インストールされるパッケージ:
-
devDependencies(開発時のみ必要なツール):
-
typescript: TypeScriptコンパイラ(.tsファイルを.jsに変換) -
vite: 開発サーバーとビルドツール(高速な開発環境を提供)
-
-
dependencies(実行時にも必要なライブラリ):
-
viem: Ethereumブロックチェーンとやり取りするためのTypeScriptライブラリ
-
2.4.2 npm install viem の役割
viemパッケージを明示的にインストールします。package.jsonに既に記載されている場合は、最初のnpm installで自動的にインストールされますが、明示的に実行することで確実にインストールできます。
2.4.3 インストール後の状態
-
node_modules/フォルダ: すべてのパッケージがダウンロードされる場所 -
package-lock.json: インストールしたパッケージの正確なバージョンが記録されるファイル -
使用可能になる機能:
- TypeScriptでコードを書けるようになる
- Viteで開発サーバーを起動できるようになる
- Viemでスマートコントラクトとやり取りできるようになる
Viemとは:
- TypeScript向けのEthereumライブラリ
- 型安全性が高い
- 軽量で高速
- Web3.jsやEthers.jsの代替
3. ファイルの設定
3.1 frontend/index.htmlの編集
HTMLファイルにスクリプトを読み込む設定を追加:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Viem + Foundry Demo (TypeScript)</title>
</head>
<body>
<h1>Viem + Foundry Demo (TypeScript)</h1>
<p>Open the Developer Console (F12) and try the commands below!</p>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
ポイント:
-
type="module"でES6モジュールを有効化 -
/src/main.tsがエントリーポイント - Viteが自動的にTypeScriptをトランスパイル
Viteが自動的にTypeScriptをトランスパイルとは:
トランスパイル(Transpile)は、TypeScriptコードをJavaScriptコードに変換する処理です。
3.1.7 トランスパイルとは
-
トランスパイル(Transpile): ある言語のコードを、同じ抽象度の別の言語に変換すること
- TypeScript(
.ts)→ JavaScript(.js)
- TypeScript(
-
コンパイルとの違い:
- コンパイル: 高級言語を機械語(バイナリ)に変換
- トランスパイル: 高級言語を別の高級言語に変換
3.1.8 なぜトランスパイルが必要か
ブラウザはTypeScriptを直接実行できません。JavaScriptのみを実行できます。
変換の例:
変換前(TypeScript):
const CONTRACT_ADDRESS: Address = '0x5FbDB2315678afecb367f032d93F642f64180aa3'
async function readMyUint(): Promise<bigint | undefined> {
const result = await publicClient.readContract({
address: CONTRACT_ADDRESS,
abi: contractABI,
functionName: 'myUint'
})
return result
}
変換後(JavaScript):
const CONTRACT_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3'
async function readMyUint() {
const result = await publicClient.readContract({
address: CONTRACT_ADDRESS,
abi: contractABI,
functionName: 'myUint'
})
return result
}
主な変換内容:
- 型注釈(
: Address、: Promise<bigint | undefined>)の削除 - TypeScript固有の構文をJavaScriptに変換
- 型チェックの情報は削除されるが、実行時の動作は同じ
3.1.9 Viteの自動トランスパイル機能
Viteは開発サーバー起動時に、以下の処理を自動的に行います:
-
ファイルの監視:
.tsファイルの変更を検知 - オンデマンド変換: ブラウザが要求したファイルだけを変換
- 高速変換: esbuildという高速なツールを使用
- メモリ上での変換: ファイルシステムに書き込まず、メモリ上で変換してブラウザに送信
メリット:
-
開発者の手動操作が不要:
tscコマンドを実行する必要がない - 高速: 変更したファイルだけを変換するため、ビルド時間が短い
- ホットリロード: ファイルを保存すると、自動的にブラウザが更新される
- 型チェック: TypeScriptの型エラーも検出してくれる
3.1.10 実際の動作フロー
- 開発者が
main.tsを編集して保存 - Viteが変更を検知
-
Viteが
main.tsをJavaScriptに変換(トランスパイル) - 変換されたJavaScriptがブラウザに送信
- ブラウザがJavaScriptを実行
注意: 本番環境(npm run build)では、すべてのファイルが事前にトランスパイルされ、最適化されたJavaScriptファイルが生成されます。
/src/main.ts がエントリーポイントとは:
エントリーポイント(Entry Point)は、プログラムの実行が始まる最初のファイルのことです。
3.1.4 エントリーポイントの役割
- プログラムの開始地点: ブラウザがこのファイルからコードの実行を開始します
-
依存関係の起点: このファイルから
import文を使って他のファイルやライブラリを読み込みます - アプリケーションの初期化: アプリケーション全体の設定や初期化処理を行います
3.1.5 このプロジェクトでの動作フロー
-
HTMLファイル(
index.html)が読み込まれる<script type="module" src="/src/main.ts"></script> -
ブラウザが
/src/main.tsを読み込む(エントリーポイント)- ViteがTypeScriptをJavaScriptに変換
-
main.ts内のコードが実行されるimport { createPublicClient, ... } from 'viem' // Viemライブラリをインポート const publicClient = createPublicClient({...}) // クライアントの初期化 window.readMyUint = readMyUint // グローバル関数の登録 -
他のファイルやライブラリが順次読み込まれる
-
main.tsのimport文に基づいて、必要なモジュールが自動的に読み込まれる
-
3.1.6 エントリーポイントの重要性
- 単一の開始点: アプリケーション全体の実行が1つのファイルから始まることで、管理が容易になる
- 依存関係の明確化: どのファイルが最初に読み込まれるかが明確になる
- デバッグの容易さ: 問題が発生した場合、エントリーポイントから追跡できる
例え: エントリーポイントは、本の「目次」や「最初のページ」のようなものです。そこから他の章(他のファイル)へと進んでいきます。
type="module" でES6モジュールを有効化とは:
type="module"を指定することで、JavaScriptファイルをES6モジュール(ECMAScript 2015モジュール)として扱うことができます。
3.1.1 ES6モジュールとは
ES6モジュールは、JavaScriptコードを複数のファイルに分割し、importとexportを使って機能を共有する仕組みです。
従来のスクリプト(<script>)との違い:
| 項目 | 従来の<script>
|
type="module" |
|---|---|---|
import/export
|
使用不可 | 使用可能 |
| スコープ | グローバル | モジュールスコープ |
| 実行タイミング | 即座に実行 | 遅延実行(defer) |
| 厳格モード | オプション | 自動的に有効 |
3.1.2 なぜ必要か
このプロジェクトでは、main.tsで以下のようにimportを使用しています:
import {
createPublicClient,
createWalletClient,
// ...
} from 'viem'
-
import文を使用: Viemライブラリから機能をインポート - モジュール分割: コードを複数のファイルに分割して管理
- 型安全性: TypeScriptの型チェックが正しく動作
3.1.3 実際の動作
- ブラウザが
<script type="module">を読み込む - ES6モジュールとして解釈される
-
import文が解決され、必要なモジュールが読み込まれる - ViteがTypeScriptをJavaScriptに変換して実行
注意: type="module"がないと、import文がエラーになり、Viemライブラリを使用できません。
3.2 frontend/src/main.tsの編集
Viemを使用してコントラクトとインタラクションする設定:
import {
createPublicClient,
createWalletClient,
http,
parseAbi,
defineChain,
type PublicClient,
type Address,
type Hash
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
// Anvilのチェーン設定(chain ID: 31337)
const anvil = defineChain({
id: 31337,
name: 'Anvil Localhost',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: {
default: {
http: ['http://127.0.0.1:8545'],
},
},
})
// コントラクト設定(デプロイしたアドレスに置き換えてください)
const CONTRACT_ADDRESS: Address = '0x5fbdb2315678afecb367f032d93f642f64180aa3'
const contractABI = parseAbi([
'function myUint() view returns (uint256)',
'function setUint(uint256 _myUint) public'
])
// Anvilのデフォルトアカウント(最初のアカウント)
const account = privateKeyToAccount(
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
)
// Public Client(読み取り専用)
const publicClient: PublicClient = createPublicClient({
chain: anvil,
transport: http('http://localhost:8545')
})
// Wallet Client(書き込み用)
const walletClient = createWalletClient({
account,
chain: anvil,
transport: http('http://localhost:8545')
})
重要な設定:
-
Chain ID: Anvilは
31337を使用 - CONTRACT_ADDRESS: デプロイ後のアドレスに更新が必要
- Private Key: Anvilのデフォルトアカウントの秘密鍵
3.3 frontend/tsconfig.jsonの編集
TypeScriptの設定ファイル(通常は自動生成されたものをそのまま使用可能)
3.4 frontend/vite.config.tsの編集
Viteの開発サーバー設定:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
port: 3000,
open: true
}
})
設定の説明:
-
port: 3000: 開発サーバーのポート番号 -
open: true: サーバー起動時に自動的にブラウザを開く
ポート番号とは:
ポート番号は、コンピュータ上で動作する複数のサービス(プログラム)を区別するための番号です。
-
例え: マンションの部屋番号のようなもの
- IPアドレス(例:
localhost)が建物の住所 - ポート番号(例:
3000)が部屋番号 - 同じコンピュータ上で複数のサービスを同時に動かす際に、どのサービスに接続するかを指定する
- IPアドレス(例:
-
このプロジェクトでの使用例:
-
ポート3000: Vite開発サーバー(フロントエンド)
- URL:
http://localhost:3000
- URL:
-
ポート8545: Anvil(ローカルブロックチェーン)
- URL:
http://localhost:8545
- URL:
-
ポート3000: Vite開発サーバー(フロントエンド)
-
よく使われるポート番号:
-
3000: 開発サーバー(React、Viteなど) -
8080: 開発サーバーの代替 -
8545: Ethereumノード(Anvil、Gethなど) -
80: HTTP(Webサーバー) -
443: HTTPS(セキュアなWebサーバー)
-
-
ポート番号の範囲: 0〜65535の整数
3.5 package.jsonの確認
{
"name": "viem-foundry-frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "^5.2.2",
"vite": "^5.0.8"
},
"dependencies": {
"viem": "^2.21.53"
}
}
4. 実行手順
4.1 Anvilの起動
anvil
確認事項:
- Listening on 127.0.0.1:8545
- Chain ID: 31337
- デフォルトアカウントが表示される
4.2 スマートコントラクトのデプロイ
方法1: forge createを使用
cd foundry
forge create src/SomeContract.sol:SomeContract \
--rpc-url http://localhost:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
方法2: Viemのデプロイスクリプトを使用
npx tsx src/deploy.ts
出力例:
スクリプト開始...
=== Deploy Starting ===
Deploying SomeContract...
Transaction hash: 0x4251fe030990dcc04c73ae3018c9beb2cd0643a12bae8481534bf3fcabb7135b
Contract deployed at: 0x5fbdb2315678afecb367f032d93f642f64180aa3
Block number: 1
重要: デプロイされたコントラクトアドレスを frontend/src/main.ts の CONTRACT_ADDRESS に設定する必要があります。
4.3 開発サーバーの起動
cd frontend
npm run dev
起動確認:
- サーバーが http://localhost:3000 で起動
- ブラウザが自動的に開く(
open: true設定の場合)
4.4 ブラウザでのアクセス
自動的に開かない場合は、以下のURLにアクセス:
http://localhost:3000
5. ブラウザコンソールでの実行
5.1 開発者コンソールを開く
-
Windows:
F12 -
Mac:
Cmd + Option + I
5.2 コマンドの実行例
アカウント情報の確認
await getAccounts()
出力例:
Available accounts: ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266']
Using account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
myUintの値を読み取る
await readMyUint()
出力例:
myUint value: 10
myUintの値を更新する
await updateMyUint(50)
出力例:
Updating myUint to 50...
Transaction hash: 0x...
Transaction confirmed!
myUint value: 50
更新後の値を確認
await readMyUint()
出力例:
myUint value: 50
6. デプロイワークフローの理解
6.1 Anvilの状態管理
重要な注意点:
- Anvilを再起動すると、ブロックチェーンの状態がリセットされる
- デプロイされたコントラクトも消去される
- 再起動後は、再度コントラクトをデプロイする必要がある
6.2 デプロイ後の更新フロー
-
コントラクトをデプロイ
npx tsx src/deploy.ts -
デプロイされたアドレスをコピー
Contract deployed at: 0x5fbdb2315678afecb367f032d93f642f64180aa3 -
フロントエンドのCONTRACT_ADDRESSを更新
const CONTRACT_ADDRESS: Address = '0x5fbdb2315678afecb367f032d93f642f64180aa3' -
Viteがホットリロードで自動更新(ブラウザのリロードは通常不要)
6.3 デプロイ方法の比較
| 項目 | forge create | npx tsx src/deploy.ts |
|---|---|---|
| ツール | Foundry | Viem (TypeScript) |
| 実行速度 | 高速 | 標準 |
| 柔軟性 | 低 | 高 |
| TypeScript統合 | なし | あり |
| 推奨用途 | シンプルなデプロイ | 複雑なデプロイやスクリプト |
7. トラブルシューティング
7.1 よくあるエラーと解決方法
エラー1: ContractFunctionExecutionError
エラーメッセージ:
Error reading myUint: ContractFunctionExecutionError: The contract function "myUint" returned no data ("0x").
原因:
- コントラクトがデプロイされていない
- コントラクトアドレスが間違っている
- Anvilが起動していない
解決方法:
- Anvilが起動しているか確認
- コントラクトを再デプロイ
- 正しいアドレスを
CONTRACT_ADDRESSに設定
エラー2: favicon.ico 404エラー
エラーメッセージ:
Failed to load resource: the server responded with a status of 404 (Not Found)
http://localhost:3000/favicon.ico
原因:
- faviconファイルが存在しない
解決方法:
- このエラーは無視してOK(アプリの動作には影響しない)
- 気になる場合は、index.htmlに以下を追加:
<link rel="icon" href="data:," />
エラー3: Connection refused
原因:
- Anvilが起動していない
解決方法:
anvil
エラー4: Module not found
原因:
- 依存関係がインストールされていない
解決方法:
cd frontend
npm install
7.2 デバッグのヒント
- Anvilのログを確認: トランザクションが到達しているか
- ブラウザコンソールを確認: JavaScriptエラーを確認
- Networkタブを確認: HTTPリクエストの状態を確認
- コントラクトアドレスを確認: 正しいアドレスが設定されているか
8. まとめ
8.1 重要なポイント
-
Viteの利点:
- 高速な開発サーバー
- ホットモジュールリプレースメント(HMR)
- TypeScriptのネイティブサポート
- シンプルな設定
-
Viemの利点:
- 型安全性が高い
- モダンなTypeScript設計
- 軽量で高速
- Anvilとの相性が良い
-
開発フロー:
- Anvilを起動
- コントラクトをデプロイ
- フロントエンドのアドレスを更新
- 開発サーバーを起動
- ブラウザコンソールで操作
-
注意点:
- Anvil再起動時はコントラクトの再デプロイが必要
- Chain IDは
31337を使用 - デプロイ後は必ずアドレスを更新
8.2 学習成果
- Viteを使用したTypeScriptプロジェクトの作成方法を習得
- Viemライブラリのインストールと基本的な使用方法を理解
- フロントエンドからスマートコントラクトとインタラクションする方法を習得
- デプロイワークフローの理解
- トラブルシューティングのスキルを習得
8.3 今後の展開
- より複雑なコントラクトとのインタラクション
- UIコンポーネントの追加
- ウォレット接続(MetaMask等)の実装
- イベントリスニング機能の追加
- マルチチェーン対応
- テストの自動化
Discussion