🕌

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に記載されているすべての依存関係をインストールします。

インストールされるパッケージ:

  1. devDependencies(開発時のみ必要なツール):

    • typescript: TypeScriptコンパイラ(.tsファイルを.jsに変換)
    • vite: 開発サーバーとビルドツール(高速な開発環境を提供)
  2. 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
  • コンパイルとの違い:
    • コンパイル: 高級言語を機械語(バイナリ)に変換
    • トランスパイル: 高級言語を別の高級言語に変換

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は開発サーバー起動時に、以下の処理を自動的に行います:

  1. ファイルの監視: .tsファイルの変更を検知
  2. オンデマンド変換: ブラウザが要求したファイルだけを変換
  3. 高速変換: esbuildという高速なツールを使用
  4. メモリ上での変換: ファイルシステムに書き込まず、メモリ上で変換してブラウザに送信

メリット:

  • 開発者の手動操作が不要: tscコマンドを実行する必要がない
  • 高速: 変更したファイルだけを変換するため、ビルド時間が短い
  • ホットリロード: ファイルを保存すると、自動的にブラウザが更新される
  • 型チェック: TypeScriptの型エラーも検出してくれる

3.1.10 実際の動作フロー

  1. 開発者がmain.tsを編集して保存
  2. Viteが変更を検知
  3. Viteがmain.tsをJavaScriptに変換(トランスパイル)
  4. 変換されたJavaScriptがブラウザに送信
  5. ブラウザがJavaScriptを実行

注意: 本番環境(npm run build)では、すべてのファイルが事前にトランスパイルされ、最適化されたJavaScriptファイルが生成されます。

/src/main.ts がエントリーポイントとは:

エントリーポイント(Entry Point)は、プログラムの実行が始まる最初のファイルのことです。

3.1.4 エントリーポイントの役割

  • プログラムの開始地点: ブラウザがこのファイルからコードの実行を開始します
  • 依存関係の起点: このファイルからimport文を使って他のファイルやライブラリを読み込みます
  • アプリケーションの初期化: アプリケーション全体の設定や初期化処理を行います

3.1.5 このプロジェクトでの動作フロー

  1. HTMLファイル(index.html)が読み込まれる

    <script type="module" src="/src/main.ts"></script>
    
  2. ブラウザが/src/main.tsを読み込む(エントリーポイント)

    • ViteがTypeScriptをJavaScriptに変換
  3. main.ts内のコードが実行される

    import { createPublicClient, ... } from 'viem'
    // Viemライブラリをインポート
    
    const publicClient = createPublicClient({...})
    // クライアントの初期化
    
    window.readMyUint = readMyUint
    // グローバル関数の登録
    
  4. 他のファイルやライブラリが順次読み込まれる

    • main.tsimport文に基づいて、必要なモジュールが自動的に読み込まれる

3.1.6 エントリーポイントの重要性

  • 単一の開始点: アプリケーション全体の実行が1つのファイルから始まることで、管理が容易になる
  • 依存関係の明確化: どのファイルが最初に読み込まれるかが明確になる
  • デバッグの容易さ: 問題が発生した場合、エントリーポイントから追跡できる

例え: エントリーポイントは、本の「目次」や「最初のページ」のようなものです。そこから他の章(他のファイル)へと進んでいきます。

type="module" でES6モジュールを有効化とは:

type="module"を指定することで、JavaScriptファイルをES6モジュール(ECMAScript 2015モジュール)として扱うことができます。

3.1.1 ES6モジュールとは

ES6モジュールは、JavaScriptコードを複数のファイルに分割し、importexportを使って機能を共有する仕組みです。

従来のスクリプト(<script>)との違い:

項目 従来の<script> type="module"
import/export 使用不可 使用可能
スコープ グローバル モジュールスコープ
実行タイミング 即座に実行 遅延実行(defer)
厳格モード オプション 自動的に有効

3.1.2 なぜ必要か

このプロジェクトでは、main.tsで以下のようにimportを使用しています:

import {
  createPublicClient,
  createWalletClient,
  // ...
} from 'viem'
  • import文を使用: Viemライブラリから機能をインポート
  • モジュール分割: コードを複数のファイルに分割して管理
  • 型安全性: TypeScriptの型チェックが正しく動作

3.1.3 実際の動作

  1. ブラウザが<script type="module">を読み込む
  2. ES6モジュールとして解釈される
  3. import文が解決され、必要なモジュールが読み込まれる
  4. 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)が部屋番号
    • 同じコンピュータ上で複数のサービスを同時に動かす際に、どのサービスに接続するかを指定する
  • このプロジェクトでの使用例:

    • ポート3000: Vite開発サーバー(フロントエンド)
      • URL: http://localhost:3000
    • ポート8545: Anvil(ローカルブロックチェーン)
      • URL: http://localhost:8545
  • よく使われるポート番号:

    • 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.tsCONTRACT_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 デプロイ後の更新フロー

  1. コントラクトをデプロイ

    npx tsx src/deploy.ts
    
  2. デプロイされたアドレスをコピー

    Contract deployed at: 0x5fbdb2315678afecb367f032d93f642f64180aa3
    
  3. フロントエンドのCONTRACT_ADDRESSを更新

    const CONTRACT_ADDRESS: Address = '0x5fbdb2315678afecb367f032d93f642f64180aa3'
    
  4. 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が起動していない

解決方法:

  1. Anvilが起動しているか確認
  2. コントラクトを再デプロイ
  3. 正しいアドレスを 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 デバッグのヒント

  1. Anvilのログを確認: トランザクションが到達しているか
  2. ブラウザコンソールを確認: JavaScriptエラーを確認
  3. Networkタブを確認: HTTPリクエストの状態を確認
  4. コントラクトアドレスを確認: 正しいアドレスが設定されているか

8. まとめ

8.1 重要なポイント

  1. Viteの利点:

    • 高速な開発サーバー
    • ホットモジュールリプレースメント(HMR)
    • TypeScriptのネイティブサポート
    • シンプルな設定
  2. Viemの利点:

    • 型安全性が高い
    • モダンなTypeScript設計
    • 軽量で高速
    • Anvilとの相性が良い
  3. 開発フロー:

    1. Anvilを起動
    2. コントラクトをデプロイ
    3. フロントエンドのアドレスを更新
    4. 開発サーバーを起動
    5. ブラウザコンソールで操作
  4. 注意点:

    • Anvil再起動時はコントラクトの再デプロイが必要
    • Chain IDは 31337 を使用
    • デプロイ後は必ずアドレスを更新

8.2 学習成果

  • Viteを使用したTypeScriptプロジェクトの作成方法を習得
  • Viemライブラリのインストールと基本的な使用方法を理解
  • フロントエンドからスマートコントラクトとインタラクションする方法を習得
  • デプロイワークフローの理解
  • トラブルシューティングのスキルを習得

8.3 今後の展開

  • より複雑なコントラクトとのインタラクション
  • UIコンポーネントの追加
  • ウォレット接続(MetaMask等)の実装
  • イベントリスニング機能の追加
  • マルチチェーン対応
  • テストの自動化

Discussion