月曜日, 8月 25, 2025
月曜日, 8月 25, 2025
- Advertisment -
ホームニューステックニュースC# × WASM化を使用してVS Code の拡張機能を作りたい(VS Code for Web)

C# × WASM化を使用してVS Code の拡張機能を作りたい(VS Code for Web)


以前の記事では、VS Codeローカルの拡張機能をC#(.NET)で作ったWASMを使用して作成しました。
今回は念願のVS Code for Web用のweb拡張機能をWASMを含めて作成する記事となります。

そもそもなぜ、こんなにもVS Code for Webの拡張機能を作りたいかと申しますと、諸般の事情によりVS Code ローカルを使用できない環境で作業することが多いからです。そんななか、何とか手軽に使えるコードエディタはないものかと探した結果、VS Code for Webに出会ったわけです。が、、如何せん、webブラウザ上で動くものなので、使える拡張機能がVS Codeローカル版よりも圧倒的に少ないです。というかほとんどがweb版では使用できない、といった形相です。AIと連携できる拡張機能もVS Code for Webではほとんどインストール不可。。

だったら、作ればいいじゃない、すごいものは作れなさそうだけど、ちょっとしたものは作れそう!という邪な発想のもと本記事の作成に至ります。

VS code のweb拡張機能をC# × WASMを使用して作成し、 VS Code for Webで自作拡張機能を動かしたいです。

開発するとなれば、やっぱり慣れ親しんだ.NETファミリーで開発したい、そういう年頃です。
しかしながら、web拡張機能ということもあり、直接.NETで開発するアプローチはなさそう、、と思っていたんですが、Blazor ではWASMを使用しているので、なんとかweb拡張もいけるのでは?と考えたのがはじまりです。

以下は簡単な構成図です。

Visual Studio Code for the Webは、Microsoftが提供するVisual Studio Codeのブラウザベースのバージョンです。
インストールなしで使えるコードエディターで、基本的なVisual Studio Codeの機能を備えています。

VS Codeオンラインエディタ

VS Codeのオンラインエディタです。PWAで使用するとローカル版とぱっとみ見分けがつかないですね。良き。

以前の記事と内容は同じですが、、定義は大事なので、再度記載します。

https://webassembly.org/

WebAssembly(略称Wasm)は、スタックベースの仮想マシンのバイナリ命令形式です。Wasmは、プログラミング言語のポータブルコンパイルターゲットとして設計されており、クライアントおよびサーバーアプリケーション用のWeb上での展開を可能にします。

https://lethediana.sakura.ne.jp/tech/archives/summary-ja/2093/


Lethediana Tech様よりイメージ図を引用

要するに、「Webブラウザなどで高速に動く、コンパクトなバイナリ形式の実行コード」という理解です。
ブラウザ上で動作するVS Code for WebでWASMを動かしますので真価を発揮できると思います。

開発環境

以下は開発環境にインストールされているものとします。

https://code.visualstudio.com/download

https://dotnet.microsoft.com/ja-jp/download/dotnet/9.0

https://nodejs.org/ja/download

npm関連の以下はインストール済みとします。インストール詳細は以前の記事をご参照。

VS Code 拡張機能

以下は、VS Code拡張機能を開発するために必要なVS Codeの拡張機能です。残念ながら、web拡張機能の開発自体はVS Code ローカルで実施が必要です。

https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint

  • TypeScript + Webpack Problem Matchers

https://marketplace.visualstudio.com/items?itemName=amodio.tsl-problem-matcher

https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner

構築作業を行います。VS Code for Webのweb拡張機能のためにVS Code ローカルを使用するわけなので、なんだかやるせないですが、進めていきます。

1. VS Code web拡張機能ひな形を作成

VS Code web拡張機能のひな形を作成し、サンプルプロジェクトのコンパイルを確かめます。

Yeoman Generator-Codeでひな形作成

まずは本プロジェクトを格納するフォルダを作成します。

yo code でgeneratorを起動します。コマンド実行は作成したフォルダC:\src\vscode-extension-test7で行います。

今回はweb拡張機能ですので、New Web Extension (TypeScript)を選択します。

C:\src\vscode-extension-test7>npx yo code

     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

? What type of extension do you want to create? (Use arrow keys)
  New Extension (TypeScript)
  New Extension (JavaScript)
  New Color Theme
  New Language Support
  New Code Snippets
  New Keymap
  New Extension Pack
  New Language Pack (Localization)
> New Web Extension (TypeScript)
  New Notebook Renderer (TypeScript)

generateする過程での質問は以下のように回答しています。

? What type of extension do you want to create? New Web Extension (TypeScript)
? What's the name of your extension? vscode-my-extension7
? What's the identifier of your extension? vscode-my-extension7
? What's the description of your extension?
? Initialize a git repository? No
? Which bundler to use? webpack
? Which package manager to use? npm

Writing in C:\src\vscode-extension-test7\vscode-my-extension7...

? Which bundler to use? webpack
について、今回はweb拡張機能なので、webpackとする必要があります。

無事、web拡張機能プロジェクトのひな型が作成されました。

以下は拡張機能のひな形プロジェクトの概要です。

vscode-my-extension7/
├── .vscode/
│   ├── launch.json               // Web拡張のデバッグ構成(Web Extension Development Host で起動)
│   └── tasks.json                // ビルドや型チェックなどのタスク定義
├── src/
│   └── web/
│       └── extension.ts          // Web拡張のエントリポイント
├── dist/
│   └── web/
│       └── extension.js          // ビルド後のブラウザ向けバンドル(配布物)
├── package.json                  // 拡張の定義・依存関係・コマンド・貢献点
│                                 // Web拡張では "browser": "dist/web/extension.js" を指定するのが一般的
├── vsc-extension-quickstart.md   // 開発の基本的な流れや実行方法のガイド
├── webpack.config.js             // ビルド設定(例:webpack)。esbuild 等の場合は対応する設定ファイル
├── tsconfig.json                 // TypeScript のコンパイル設定(出力先やターゲットなど)
├── .eslintrc.js                  // コード品質ルール(ESLint 設定)
├── .vscodeignore                 // vsix パッケージに含めないファイルの指定
├── README.md                     // 拡張の概要・使い方・コマンド説明
├── CHANGELOG.md                  // バージョンごとの変更履歴
├── package-lock.json             // 依存関係のロックファイル(npm 使用時)
└── test/
    ├── web.test.ts               // Web 拡張の基本テスト(例)
    └── fixtures/                 // テスト用の補助データ

以降の操作はプロジェクトフォルダ(本記事の内容ですと、vscode-my-extension7フォルダ)の直下で行います。

web拡張機能のコンパイル

一度、サンプルのHelloworldが実行できるはずですので、コンパイルと実行確認を行います。

web拡張ホストのコンパイル自体は以下コマンドで行います。

無事、コンパイルの完了を確認できました。

実行確認は以下コマンドを実行することで、テスト用のブラウザが立ち上がりますので、そちらで動作確認可能です。(詳しくは後述)

コンパイルを行った拡張機能を含むchromiumブラウザとDevelopment Host VS Codeが立ち上がります。

コマンドパレットより、Hello Worldコマンドを実行します。

無事、サンプルのHello Worldメッセージが表示されました。

コマンドパレットの定義コマンドについて補足

以前の記事と同じ内容の補足ですが、web拡張機能でもコマンドのリンクは同様です。

package.jsonのcontributes:commandsのtitleとexport function activateメソッド内のvscode.commands.registerCommandの第一引数が対応しているようです。

package.json

{

  "contributes"https://zenn.dev/srtia2318/articles/: {
    "commands"https://zenn.dev/srtia2318/articles/: [
      {
        "command"https://zenn.dev/srtia2318/articles/: "vscode-my-extension7.helloWorld"https://zenn.dev/srtia2318/articles/,
        "title"https://zenn.dev/srtia2318/articles/: "Hello World"
      }
    ]
  },

}

extension.ts


export function activate(context: vscode.ExtensionContext) {
	const disposable = vscode.commands.registerCommand('vscode-my-extension7.helloWorld"https://zenn.dev/srtia2318/articles/, () => {
		vscode.window.showInformationMessage('Hello World from vscode-my-extension7!"https://zenn.dev/srtia2318/articles/);
	});
	context.subscriptions.push(disposable);
}

2. .NET SDKでWASM化するC#プロジェクトを作成

C# プロジェクトの作成

以下のdotnetコマンドでWASM用のC#プロジェクトを準備します。

dotnet new console `
  -n MyWasmApp `
  --framework net9.0 `
  --use-program-main true

wasmへのビルドは最低限exeがないとダメなようなので、consoleアプリとしています。

dotnet new コマンド実行後、無事C#のconsoleプロジェクトが作成されました。

呼び出されるC#側処理を作成

Typescriptから呼び出される処理を記述します。実際の実装では本項目部分を作りこむことになるかと思います。

ポイントは以下です。

  • Typescript側から呼び出すため、JSExport属性を付与します。
  • Typescript側から呼び出すため、静的クラス、静的メソッドとします。
  • JSExport属性使用の関係上、partialクラスとします。
  • JSExport属性はunsafeブロックとなるため、ビルド時、またはcsprojでAllowUnsafeBlocks=trueを指定します。(後述)

今回は、Class1.csを追加し、サンプル的なメソッドとして、以下を実装しています。

  • GetGreeting()
    : 名前に基づいて挨拶メッセージを生成します。

Class1.cs

using System.Threading.Tasks;
using System.Runtime.InteropServices.JavaScript;

namespace MyWasmApp;

public static partial class Class1
{
    
    
    
    

デバッグ等はC# Dev kit拡張機能でも構いませんし、別途Visual Studio を開いてデバッグをしても良いかと思います。

VS Code 用 C# 開発キット拡張機能の使用について

3. .NET SDKでC#プロジェクトをWASMにビルド

wasm-toolsをインストール

C#プロジェクトをWASMにビルドするためのwasm-toolsをインストールします。すでにインストール済みの場合はスキップいただいて構いません。Visual Studio インストーラーの変更オプションでもインストール可能です。

dotnet workload install wasm-tools

WASMにビルド

以下のdotnetコマンドでC#プロジェクトをWASMで発行します。
発行先のフォルダは、本拡張機能直下のmediaフォルダを想定としています。
ビルドも同時に行うので、この時点でビルドエラーがある場合はターミナルに表示されるかと思います。

dotnet publish .\MyWasmApp\MyWasmApp.csproj `
  -c Release `
  -r browser-wasm `
  --self-contained true `
  /p:RunAOTCompilation=true `
  /p:InvariantGlobalization=true `
  /p:StripSymbols=true `
  /p:AllowUnsafeBlocks=true `
  -o .\media

publishのパラメータとして以下を指定しています。csprojで指定しても構いません。

  • -c Release
    : Build Configuration を Release モードに指定(最適化有効)
  • -r browser-wasm
    : 実行ターゲットを WebAssembly(ブラウザ)ランタイム に指定
  • –self-contained true
    : .NET ランタイムを同梱した自己完結型ビルドにする(ユーザー環境に .NET がなくても動作)
  • RunAOTCompilation=true
    : Ahead-of-Time コンパイルを有効化(事前にネイティブコード化しパフォーマンス改善)
  • InvariantGlobalization=true
    : グローバル化サポートを簡略化(カルチャ依存機能を無効化)してサイズ縮小
  • StripSymbols=true
    : デバッグシンボルを除去してファイルサイズ削減
  • AllowUnsafeBlocks=true
    : unsafe コードブロックのコンパイルを許可

無事、発行が完了し、mediaフォルダ配下にwasm一式のファイルが作成されました。

4. TypescriptでWASMの呼び出し処理作成

TypescriptでのWASMの呼び出し処理、およびWASM内のC#で作成した処理の実行処理を作成します。
ここで、TypeScript 側のコードは、単に WASM を置くだけでは実行できないので、
「WASM ランタイムを起動 → DLL を見つけてロード → メソッドを JS に公開」
という処理が必要です。
コード上の流れとしては以下です。


  1. media フォルダの実ファイルパスを取得する

    • WASM を配置しているパスを解決する。
  2. dotnet.js を import する

    • WASM ランタイムを起動するための「入り口」を読み込む。
  3. ビルダー API を取得する

    • ランタイムを設定・起動するためのオブジェクトを取り出す。
    • この API がなければ WASM を動かすことができない。
  4. リソースローダーを定義する

    • ランタイムが要求する DLL や WASM をどのパスから取るかを教える。
    • これがないとファイルが見つからず、アプリが起動できない。
  5. config(構成情報)を用意する

    • メインのアセンブリ名、読み込む DLL 群、ICU 設定などを指定。
    • CLR に「何をどう実行するか」を知らせる。
  6. dotnet.create() でランタイムを起動する

    • この瞬間に WASM がロードされ、.NET CLR が動き出す。
  7. getAssemblyExports で DLL 内の関数をラップする

    • JS から直接呼べる関数として公開。
  8. 呼び出し

    • 起動したランタイムgetAssemblyExports(‘MyWasmApp’)から呼び出し。

以下は依存関係を図示したものです。

しかしながら、2025年8月時点、dotnet のブラウザ向けローダは以下の課題があるようです。

従いまして、runtimeの初期化処理は main window / WebView / iframe に置くことで回避するつくりとします。

package.jsonの編集

まずはpackage.jsonに本機能の処理をトリガーするイベントを追加するとします。
Activateイベントは他にもonNotebook(特定の文書を開いたとき)などたくさんあるのですが、今回はHelloWorldサンプルを踏襲して、コマンドパレットで入力キックできるコマンドを追加することとします。

package.json

{
//省略
  "contributes"https://zenn.dev/srtia2318/articles/: {
    "commands"https://zenn.dev/srtia2318/articles/: [
      {
        "command"https://zenn.dev/srtia2318/articles/: "vscode-my-extension7.helloWorld"https://zenn.dev/srtia2318/articles/,
        "title"https://zenn.dev/srtia2318/articles/: "Hello World"
      },
+     {
+       "command"https://zenn.dev/srtia2318/articles/: "vscode-my-extension7.callWasm.greeting"https://zenn.dev/srtia2318/articles/,
+       "title"https://zenn.dev/srtia2318/articles/: "WASM: Class1.GetGreeting"
+     }
    ]
  },
//省略
}

webpack.config.jsの編集

WebView用のファイルをtsで追加するため、webpack.config.jsにwebview-mainというentryを追加します。

webpack.config.js

//省略
/** @type WebpackConfig */
const webExtensionConfig = {
	mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
	target: 'webworker', // extensions run in a webworker context
	entry: {
		'extension': './src/web/extension.ts',
		'test/suite/index': './src/web/test/suite/index.ts',
+ 		'webview-main"https://zenn.dev/srtia2318/articles/: './src/web/webview-main.ts'
	},
//省略
}

webview-main.tsを./src/web/に追加します。

tsconfig.jsonの編集

WebViewを使用するため、tsconfig.jsonのlibにDOMを追記します。

tsconfig.json

{
	"compilerOptions": {
		"module": "Node16",
		"target": "ES2020",
		"outDir": "dist",
		"lib": [
			"ES2020",
+ 			"WebWorker"https://zenn.dev/srtia2318/articles/,
+ 			"DOM"
		],
		"sourceMap": true,
		"rootDir": "src",
		"strict": true,   /* enable all strict type-checking options */
		/* Additional Checks */
		// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
		// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
		// "noUnusedParameters": true,  /* Report errors on unused parameters. */
	}
}

typescript処理の編集

extension.tsを以下のように編集します。webの呼び出しとトーストメッセージを表示することが期待動作です。

extension.ts

import * as vscode from 'vscode"https://zenn.dev/srtia2318/articles/;

export function activate(context: vscode.ExtensionContext) {

	console.log('Congratulations, your extension "vscode-my-extension7" is now active in the web extension host!"https://zenn.dev/srtia2318/articles/);
	const disposable = vscode.commands.registerCommand('vscode-my-extension7.helloWorld"https://zenn.dev/srtia2318/articles/, () => {
		vscode.window.showInformationMessage('Hello World from vscode-my-extension7 in a web extension host!"https://zenn.dev/srtia2318/articles/);
	});

	context.subscriptions.push(disposable);

	
	
	const callWasmGreeting = vscode.commands.registerCommand('vscode-my-extension7.callWasm.greeting"https://zenn.dev/srtia2318/articles/, async () => {
		
		
		
		const panel = vscode.window.createWebviewPanel(
			'wasmGreeting"https://zenn.dev/srtia2318/articles/,
			'WASM: Class1.GetGreeting"https://zenn.dev/srtia2318/articles/,
			vscode.ViewColumn.One,
			{
				enableScripts: true,
				retainContextWhenHidden: false,
				localResourceRoots: [
					vscode.Uri.joinPath(context.extensionUri, 'media"https://zenn.dev/srtia2318/articles/),
					vscode.Uri.joinPath(context.extensionUri, 'dist"https://zenn.dev/srtia2318/articles/, 'web"https://zenn.dev/srtia2318/articles/)
				]
			}
		);

		
		
		
		
		const mediaDir = vscode.Uri.joinPath(context.extensionUri, 'media"https://zenn.dev/srtia2318/articles/);
		const mediaBase = panel.webview.asWebviewUri(mediaDir).toString();
		const webviewMain = panel.webview.asWebviewUri(
			vscode.Uri.joinPath(context.extensionUri, 'dist"https://zenn.dev/srtia2318/articles/, 'web"https://zenn.dev/srtia2318/articles/, 'webview-main.js"https://zenn.dev/srtia2318/articles/)
		);

		
		const csp = [
			"default-src 'none'"https://zenn.dev/srtia2318/articles/,
			`img-src ${panel.webview.cspSource} blob:`,
			`script-src ${panel.webview.cspSource} 'wasm-unsafe-eval"https://zenn.dev/srtia2318/articles/`,
			`style-src ${panel.webview.cspSource}`,
			`connect-src ${panel.webview.cspSource}`
		].join('; "https://zenn.dev/srtia2318/articles/);

		
		panel.webview.html = `
		
		
		
		${csp}">
		
		WASM Greeting
		
		${mediaBase}/">
		
		
		
		
		webviewMain}">
		
		`;

		
		
		
		const sub = panel.webview.onDidReceiveMessage(msg => {
			if (msg?.type === 'result"https://zenn.dev/srtia2318/articles/) {
				
				vscode.window.showInformationMessage(msg.result ?? 'No result from WASM"https://zenn.dev/srtia2318/articles/);
				sub.dispose();
			}
			if (msg?.type === 'error"https://zenn.dev/srtia2318/articles/) {
				
				vscode.window.showErrorMessage(`WASM error: ${msg.message}`);
				sub.dispose();
			}
		});
	});

	context.subscriptions.push(callWasmGreeting);
}


export function deactivate() {}

新規追加のWebview-main.tsを以下のように編集します。WASMの読み込み、実行自体はこのWebViewで行います。WASM実行の結果は最終的にWorkerスレッド側に返します。コマンドを実行時はWebViewが表示され、WebView上に管理なログメッセージが表示されることが期待動作です。

webview-main.ts




const log = (m: string, ...a: any[]) => {
	console.log('[webview]"https://zenn.dev/srtia2318/articles/, m, ...a);
	const el = document.getElementById('log"https://zenn.dev/srtia2318/articles/);
	if (el) {
		el.textContent += `[webview] ${m} ${a.map(x => typeof x === 'string' ? x : JSON.stringify(x)).join(' "https://zenn.dev/srtia2318/articles/)}\n`;
	}
};

(async () => {
	try {
		
		const mediaBase = document.baseURI; 

		
		const dotnetMod: any = await import( (mediaBase + 'dotnet.js"https://zenn.dev/srtia2318/articles/));
		
		const dotnet = dotnetMod.dotnet as any; 
		log('dotnet module loaded"https://zenn.dev/srtia2318/articles/);

		
		const config = {
			mainAssemblyName: 'MyWasmApp"https://zenn.dev/srtia2318/articles/,
			globalizationMode: 'invariant"https://zenn.dev/srtia2318/articles/,
			resources: {
				wasmNative: { 'dotnet.native.wasm"https://zenn.dev/srtia2318/articles/: '' },
				jsModuleRuntime: { 'dotnet.runtime.js"https://zenn.dev/srtia2318/articles/: '' },
				jsModuleNative: { 'dotnet.native.js"https://zenn.dev/srtia2318/articles/: '' },
				assembly: {
					'MyWasmApp.dll"https://zenn.dev/srtia2318/articles/: '"https://zenn.dev/srtia2318/articles/,
					'System.Private.CoreLib.dll"https://zenn.dev/srtia2318/articles/: '"https://zenn.dev/srtia2318/articles/,
					'System.Runtime.InteropServices.JavaScript.dll"https://zenn.dev/srtia2318/articles/: '"https://zenn.dev/srtia2318/articles/,
					'System.Console.dll"https://zenn.dev/srtia2318/articles/: ''
				}
			}
		};

		
		const builder = dotnet
			.withDiagnosticTracing(true)
			.withResourceLoader((type: string, name: string) => {
				
				if (type === 'manifest' && name === 'blazor.boot.json"https://zenn.dev/srtia2318/articles/) {
					log('loadBootResource(manifest) -> empty manifest"https://zenn.dev/srtia2318/articles/);
					return 'data:application/json,%7B%7D"https://zenn.dev/srtia2318/articles/;
				}
				const mapped = mediaBase + name;
				log('loadBootResource"https://zenn.dev/srtia2318/articles/, { type, name, mapped });
				return mapped;
			})
			.withConfig(config);

		
		const rt = await builder.create();
		log('runtime created"https://zenn.dev/srtia2318/articles/);

		
		
		const exports = await rt.getAssemblyExports('MyWasmApp"https://zenn.dev/srtia2318/articles/);
		const result: string = exports?.MyWasmApp?.Class1?.GetGreeting?.('VS Code"https://zenn.dev/srtia2318/articles/);
		log('GetGreeting ="https://zenn.dev/srtia2318/articles/, result);

		
		const vscode = (globalThis as any).acquireVsCodeApi?.();
		vscode?.postMessage?.({ type: 'result"https://zenn.dev/srtia2318/articles/, result });
	} catch (err: any) {
		console.error(err);
		const vscode = (globalThis as any).acquireVsCodeApi?.();
		vscode?.postMessage?.({ type: 'error"https://zenn.dev/srtia2318/articles/, message: String(err?.message || err) });
	}
})();

フローを図示すると以下のようになります。

編集後は、コマンドnpm run compile-webで忘れずコンパイルを行います。

動作確認

web拡張ホストの動作確認のアプローチは、以下3パターンあります。

  1. デスクトップ版 VS Code で Web 拡張機能ホストを使用
  2. @vscode/test-web モジュールでブラウザ実行
  3. vscode.dev へのサイドロード

手軽なのは1.,2.です。公式にも記載がありますが、1.,2.を実施した後、最後に3.で最終の確認を行う、というストーリーが現実的です。

1.デスクトップ版 VS Code で Web 拡張機能ホストを使用

デスクトップ版 VS Code を起動し、–extensionDevelopmentKind=web オプションを付けて実行します。これにより、ローカル環境で Web 拡張機能ホストが立ち上がり、通常の拡張機能開発と同様にデバッグや動作確認が可能です。初期検証に適しています。

「実行とデバッグ」より、Run Web Extension を実行します。

VS Code ローカルが別プロセスで立ち上がりますので、
コマンドパレットより「WASM: Class1.GetGreeting」コマンドを実行します。
実行後はWebviewが画面に表示され、さらにトーストメッセージで「Hello VS Code, from WASM!」のメッセージが期待通り表示されました。

Run Web Extensionについて補足

Run Web Extensionのデバッグ実行についての記載は以下のファイルに記載があります。
./vacode/launch.json

launch.json

{

	"configurations"https://zenn.dev/srtia2318/articles/: [
		{
			"name"https://zenn.dev/srtia2318/articles/: "Run Web Extension "https://zenn.dev/srtia2318/articles/,
			"type"https://zenn.dev/srtia2318/articles/: "extensionHost"https://zenn.dev/srtia2318/articles/,
			"debugWebWorkerHost"https://zenn.dev/srtia2318/articles/: true,
			"request"https://zenn.dev/srtia2318/articles/: "launch"https://zenn.dev/srtia2318/articles/,
			"args"https://zenn.dev/srtia2318/articles/: [
				"--extensionDevelopmentPath=${workspaceFolder}"https://zenn.dev/srtia2318/articles/,
				"--extensionDevelopmentKind=web"
			],
			"outFiles"https://zenn.dev/srtia2318/articles/: [
				"${workspaceFolder}/dist/web/**/*.js"
			],
			"preLaunchTask"https://zenn.dev/srtia2318/articles/: "${defaultBuildTask}"
		},

}

他にもデバッグ方法を指定したい場合は本ファイルに追記すると良いようです。

2.@vscode/test-web モジュールでブラウザ実行

@vscode/test-web を使ってローカルサーバーを起動し、ブラウザ上で VS Code for the Web と拡張機能を読み込みます。Chromium・Firefox・WebKit など複数ブラウザでの動作確認が可能で、CLI やスクリプトから簡単にテスト環境を構築できます。

コマンドnpm run run-in-browserをVS Codeの本件プロジェクトのターミナルで実行します。

ChromiumブラウザがVS Code for Web (development Host)とともに立ち上がりますので、コマンドパレットより「WASM: Class1.GetGreeting」コマンドを実行します。

実行後はWebviewが画面に表示され、さらにトーストメッセージで「Hello VS Code, from WASM!」のメッセージが期待通り表示されました。

3.vscode.dev(VS Code for Web) へのサイドロード

実際の vscode.dev(VS Code for Web) 環境に拡張機能を読み込んで挙動を確認します。ローカルで HTTPS サーバーを立ち上げ、拡張機能を配信してインストールします。本番同等の環境での最終チェックに適しており、公開前の最終動作確認として有効です。

VSIXの作成

まずは、VSIXを作成します。
プロジェクト直下にて以下のコマンドを実行します。※readmeファイルはデフォルトのままだとエラーとなるので、編集しておく必要があります。

実行後、「vscode-my-extension7-0.0.1.vsix 」が作成されました。

さらに、作成した「vscode-my-extension7-0.0.1.vsix 」の拡張子をzipに変更(「「vscode-my-extension7-0.0.1.zip」」)し、解凍します。
解凍後のファイルの配置directoryは便宜上、C:\temp\unzipとしています。

※vscode.devはweb拡張機能をインストールする際、package.jsonが必要ですが、解凍していないとnot foundとなってしまうので注意です。

VSIXを配信するwebサーバーをローカルに立てる

配信用のwebサーバーを立てるために、mkcertを使用してオレオレ証明書を作成します。
mkcert は、ローカルで信頼できる開発証明書を作成するためのシンプルなツールです。

https://github.com/FiloSottile/mkcert#installation

※mkcertはインストール済みとします。

以下コマンドにて、Cドライブ直下certsフォルダを作成し、証明書を準備します。

mkdir C:\certs
cd C:\certs
mkcert -install
mkcert localhost

続いて、npx serveコマンドにて、mkcertで作成した証明書と解凍したVSIXのパスを指定して
web拡張機能の配信サーバーを起動します。ルートディレクトリはC:\temp\を指定します。ポートは5000としています。

npx serve --cors -l 5000 --ssl-cert C:\certs\localhost.pem --ssl-key C:\certs\localhost-key.pem C:\temp\

実行後は以下のように簡易なサーバーが立ち上がります。

※環境によるとは思いますが、必要に応じてポート5000のファイアウォールの解放などはご調整ください。

VS Code for Web での動作確認

いよいよVS Code for Web での動作確認を行います。
VS Code for Web をブラウザで開きます。

VS Code for Web 上のコマンドパレットで、「 [開発者: 場所から拡張機能をインストール…] 」を選択します。

web拡張機能の場所を聞かれるので、解凍したVSIXのpackage.jsonが存在するディレクトリを指定します。
https://localhost:5000/unzip/vscode-my-extension7-0.0.1/extension/

無事、VS Code for Web 上にweb拡張機能がインストールされました。

VS Code for Webのコマンドパレットより、コマンドパレットより「WASM: Class1.GetGreeting」コマンドを実行します。

実行後はWebviewが画面に表示され、さらにトーストメッセージで「Hello VS Code, from WASM!」のメッセージが期待通り表示されました。泣いた。


余談ですが、web拡張機能の配信をホストしているサーバー側でも、WASMを含め、ロードされていることがわかります。

PS C:\Users\Administrator> npx serve --cors -l 5000 --ssl-cert C:\certs\localhost.pem --ssl-key C:\certs\localhost-key.pem C:\temp\

   ┌────────────────────────────────────────────┐
   │                                            │
   │   Serving!                                 │
   │                                            │
   │   - Local:    https://localhost:5000       │
   │   - Network:  https://XXX.XXX.XXX.XXX:5000 │
   │                                            │
   │   Copied local address to clipboard!       │
   │                                            │
   └────────────────────────────────────────────┘

 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/dist/web/webview-main.js
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 12 ms
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/dist/web/webview-main.js
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 1 ms
 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/media/dotnet.js
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 2 ms
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/media/dotnet.js
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 1 ms
 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/media/dotnet.runtime.js
 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/media/dotnet.native.wasm
 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/media/MyWasmApp.dll
 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/media/dotnet.native.js
 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/media/System.Private.CoreLib.dll
 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/media/System.Runtime.InteropServices.JavaScript.dll
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 11 ms
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 7 ms
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 5 ms
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 10 ms
 HTTP  2025/8/24 16:39:07 ::1 OPTIONS /unzip/vscode-my-extension7-0.0.1/extension/media/System.Console.dll
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/media/MyWasmApp.dll
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/media/dotnet.runtime.js
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 2 ms
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 1 ms
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 8 ms
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/media/System.Runtime.InteropServices.JavaScript.dll
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/media/dotnet.native.js
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 2 ms
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 1 ms
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/media/System.Console.dll
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 1 ms
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 26 ms
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/media/System.Private.CoreLib.dll
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 2 ms
 HTTP  2025/8/24 16:39:07 ::1 Returned 200 in 35 ms
 HTTP  2025/8/24 16:39:07 ::1 GET /unzip/vscode-my-extension7-0.0.1/extension/media/dotnet.native.wasm
 HTTP  2025/8/24 16:39:07 ::1 Returned 304 in 1 ms

ということで、念願のVS Code for Web用のweb拡張機能をC# × WASMを使用して作成しました。
前回記事で記載の部分も含めて整理しますと、下記がポイントかと考えます。

  • .NET側
    • JS側から.NET コードを呼ぶ際はJSExport属性を付ける。
    • JSExportでは呼び出しは静的メソッドである必要がある。
  • Typescript側(JS側)
    • JS側からWASMを動かす際は、dotnet.jsでランタイムの起動が必要。
    • ランタイム実行は以下4点も必要。
      • dotnet.native.wasm
      • dotnet.runtime.js
      • dotnet.native.js
      • 他依存関係dllなど
    • Web拡張機能をVS Code for Web で実行する場合は、WASMランタイム起動はWeb Worker以外で起動が必要。
      ※Web Worker 内でWASMランタイムを起動すると asset のインスタンス化プロミスが解決されず初期化が止まる(ハング)ため。(2025年8月時点 )

開発環境もVS Code for Webで完結できるようにしていければと思う今日この頃です。
本記事の内容が何かしらのお役に立てば幸いです。

  • Visual Studio Code API ドキュメント Web拡張機能

https://code.visualstudio.com/api/extension-guides/web-extensions

  • Github Issues dotnet.jsは、onmessageハンドラでWebワーカーで実行するときに初期化に失敗します#114918

https://github.com/dotnet/runtime/issues/114918?utm_source=chatgpt.com



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -