導入
BlazorでTypeScriptを使いたい、よくある希望かと存じます。
かと言ってここでNodeJSをただただ導入しようとするとビルドの構成だとか、Blazor特有の事情(wwwrootとPages/xxx.razor.jsの2か所にJavaScriptを配置できる)を考慮する必要があるなど以外と面倒です。
今回はこれをなるべくVisual Studioと統合した形で自然に管理する方法ができたので皆様にご紹介いたします。
何をやるのか?
-
Microsoft.TypeScript.MSBuildでTypeScriptをコンパイル -
Microsoft.TypeScript.MSBuildとtsconfig.jsonを使ってwwwroot上のTSとコンポーネントに紐づくxxx.razor.ts間で自動補間を使えるようにする -
esbuildを使ってnpmパッケージをwwwroot上に展開し、wwwroot上のTSやxxx.razor.tsファイルから参照できるようにする(追加でMSBuildから呼び出せるようにする)
事前準備
-
nodeとnpmのインストール
やり方
1. TypeScript コンパイラを追加してTSファイルからBlazorで使えるJSファイルへコンパイルする仕組みを用意する
自身のTSを使いたいBlazorプロジェクト(以下プロジェクトと呼称)にMicrosoft.TypeScript.MSBuildを追加します。
dotnet add package Microsoft.TypeScript.MSBuild
次にtsconfig.jsonファイルを、プロジェクトのルートフォルダーに作成します。
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"removeComments": false,
"moduleResolution": "Bundler",
"skipLibCheck": true,
"noImplicitAny": true,
"strict": true,
"lib": [
"DOM",
"ESNext"
],
"sourceMap": true,
"baseUrl": ".",
"paths": {
"/js/*": [
"./wwwroot/js/*"
],
"/*": [
"./*"
]
}
},
"exclude": [
"node_modules",
"bin",
"obj"
]
}
こちらを使うで
アプリをビルドするたびに、
プロジェクトに配置したTypeScriptファイルの隣に
JSファイルが生成されるようになります。
ComponentとまとめるタイプのJSの場合
wwwrootに配置するタイプのJSの場合
※今回は、wwwroot上のTSファイルは、wwroot/jsフォルダーへ配置する形で作ります。
PJのフォルダー構成が異なる場合は適宜読み替えてください。
重要なポイントを紹介します。
"paths": {
"/js/*": [
"./wwwroot/js/*"
],
"/*": [
"./*"
]
}
},
"exclude": [
"node_modules",
"bin",
"obj"
]
Blazorプロジェクトは通常のJSのプロジェクトと違い
Blazorコンポーネントとまとめて配置するタイプのJSとwwwroot上に配置するJSの二つがあります。
そしてこれらはパスの解決方法も微妙に異なります。
| フォルダー階層 | サーバー上のパス |
|---|---|
| wwwroot/js/util.js | js/util.js |
| Pages/Home.razor.js | Pages/Home.razor.js |
そこで今回は、パス解決ルールを定義し、import文を書く際のパスを実際のサーバー上に配置されたときのパスと一致するように設定しました。
"/js/*": [
"./wwwroot/js/*"
],
"/*": [
"./*"
]
これでこんな感じに書いたwwwroot/js上のtsファイルが
/**
* 値を===で囲んだ文字列に変換する
* @param value 値
* @returns === ${値} ===
*/
export const cover = (value: string) => ` === ${value} ===`;
こんな感じに補間が効いた状態かつビルド後のパスで呼び出せるようになります。
import { cover } from '/js/util.js' // ← js拡張子必須
export const hello = (name: string) => {
console.log(cover(`${name} こんにちは`));
}
ちゃんとドキュメントコメントもついてます。
2. esbuildを使ってnpmライブラリーを参照できるようにする
赤がこれから編集するファイル、
青がそれによって自動生成されるファイルです。
まずはpackage.jsonを用意します。
esbuildが使いたいだけなので最小です。
package.jsonでesbuildを用意する
{
"scripts": {
"build:libs": "esbuild JsLibraries/libs.ts --bundle --format=esm --outfile=wwwroot/js/vendor/libs.js"
},
"dependencies": {
"esbuild": "^0.27.1"
}
}
コマンドを実行してesbuildをインストールします。
npm install
JsLibraries/libs.tsに記載した内容をesm形式で wwwroot/js/vendor/libs.jsに書きだすスクリプトを記載しています。
npmでライブラリーを追加する
次にライブラリーをインポートします。
なんでも良いので、今回はnanoidというユニークなIDを作れるライブラリーを追加してみます。
npm install nanoid
次にこのnanoidをBlazorで読み込むためフォルダーを用意しましょう。
プロジェクトのルートフォルダーにJsLibrariesフォルダーを作成、その中にlibs.tsというファイルを作成します。
こんな形で追加したライブラリーを再exportします。
export { nanoid } from 'nanoid';
tsconfig.jsonに設定を追加します。
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"removeComments": false,
"moduleResolution": "Bundler",
"skipLibCheck": true,
"noImplicitAny": true,
"strict": true,
"lib": [
"DOM",
"ESNext"
],
"sourceMap": true,
"baseUrl": ".",
"paths": {
+ "/js/vendor/libs.js": [
+ "./JsLibraries/libs.ts"
+ ],
"/js/*": [
"./wwwroot/js/*"
],
"/*": [
"./*"
]
}
},
"exclude": [
"node_modules",
+ "JsLibraries/libs.ts",
"bin",
"obj"
]
}
最後にプロジェクトの設定ファイルに以下の設定を追加し、Microsoft.TypeScript.MSBuildのビルドシステムが動く前にesbuildが先ほどのlibs.tsをビルドするように設定します。
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
+ <Target Name="BuildJsLibraries" BeforeTargets="CompileTypeScript">
+ <Exec Command="npm run build:libs" />
+ </Target>
この状態でビルドするとファイルが生成されます。
あとはこのファイルをwwwroot上のTSファイルなんかで読み込めば完了です。
ちゃんとコメントも見られます。
先ほどのutil.tsで読み込んでみましょう。
loggerというメソッドを作成しました。
import { nanoid } from '/js/vendor/libs.js'
/**
* 値を===で囲んだ文字列に変換する
* @param value 値
* @returns === ${値} ===
*/
export const cover = (value: string) => ` === ${value} ===`;
+ /**
+ * ログ形式の文字列を作成する
+ * @param category カテゴリー
+ * @param value 出力
+ * @returns 成形されたログ文字列
+ */
+ export const logger = (category: string, value: string) => `${nanoid} [${category}] ${value}`;
このファイルをいくつかの方法で読み込んでみます。
import { logger } from './js/util.js' // ← js拡張子必須
console.log(logger("lib.module.ts", "初期化されました"))
import { logger } from '/js/util.js'
export const hello = (name: string) => {
console.log(logger("Hello", `${name} こんにちは`));
}
@page "/"
@inject IJSRuntime JS
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) { return; }
// Home.razor.ts
await using var helloModule = await JS.InvokeAsync<IJSObjectReference>("import", "/Pages/Hello.razor.js");
await helloModule.InvokeVoidAsync("hello", "世界");
// util.ts
await using var utilModule = await JS.InvokeAsync<IJSObjectReference>("import", "/js/util.js");
var loggerString = await utilModule.InvokeAsync<string>("logger", "Hello.razor", "util直接呼出し");
Console.WriteLine(loggerString);
// libs.ts
await using var libModule = await JS.InvokeAsync<IJSObjectReference>("import", "/js/vendor/libs.js");
var nanoid = await libModule.InvokeAsync<string>("nanoid");
Console.WriteLine($"nanoid = {nanoid}");
}
}
上から順に
LessenCode2.lib.module.ts
↓
Home.razor.ts(Home.razorファイルから呼び出し)
↓
util.ts(Home.razorファイルから呼び出し)
↓
libs.ts(Home.razorファイルから呼び出し)
の3パターンでnanoidが読み込まれている事を確認できました。
解決できていない点
libs.tsが、esbuildとMicrosoft.TypeScript.MSBuild両方からビルドされてしまってます。
Microsoft.TypeScript.MSBuildからは、こんな形でビルドされます。
export { nanoid } from 'nanoid';
//# sourceMappingURL=libs.js.map
こちらに関して、wwwroot上の生成物はesbuildによって生成されたwwwroot/js/vendor/libs.jsであり、
Microsoft.TypeScript.MSBuild製の生成物はwwwroot上には残らず、ビルド成果物に公開されないため無視してます。
まとめ
今回はBlazorでTypeScriptを使う際にパスを通しつつ、npmまで使えるようにできました。
個人的に補完ができる自然な使い勝手で統合ができたところはうまくできたと自負しております。
一方でesbuildとMicrosoft.TypeScript.MSBuild両方からビルドされてしまったのは何とかしたかったなと思うところです。
今回の記事は下記の参考文献がきっかけで生まれました。
先駆者様、そしてこのアドベントカレンダーを主催していただきましたjsakamoto氏、そしてこの記事を読んで下さった皆様に感謝を表し終わりたいと思います。
ありがとうございました。
参考文献










