@ishibashi0112/webview2-bridge-gen
v0.5.0
Published
zod contract -> JSON Schema -> TypeScript types + VB.NET DTO / Interface / Dispatcher generator for WinForms (.NET Framework 4.8) + WebView2 + Vite/React apps
Maintainers
Readme
@ishibashi0112/webview2-bridge-gen
Code generator for webview2-bridge: write the contract between a Vite/React UI and a VB.NET WinForms (.NET Framework 4.8) host once, in zod, and generate both sides.
contract.ts (zod) ──> contract.schema.json ──> contract-types.ts (TypeScript, for the UI)
├─> Dto.vb / Interfaces.vb / Dispatcher.Generated.vb / Events.vb (VB.NET)
└─> openapi.json (OpenAPI 3.1, optional: the same contract served over HTTP)The front-end runtime is @ishibashi0112/webview2-bridge-client;
the VB.NET runtime is the NuGet package WebView2Bridge.Runtime (plus WebView2Bridge.WinForms for the host).
Install
pnpm add -D @ishibashi0112/webview2-bridge-gen zodRequires Node.js 20.19+ and zod 4.
Start a new app
pnpm create webview2-bridge my-app --name MyInventory # = pnpm dlx @ishibashi0112/webview2-bridge-gen init my-app --name MyInventory
cd my-app && pnpm install && pnpm gen:checkinit writes a complete skeleton (32 files): the zod contract, a Vite + React app with a mock transport,
and three VB.NET projects (<Name>.Contract netstandard2.0, <Name>.Impl net48, <Name>.Host WinForms net48)
including the generated code and the bundled VB runtime. --name becomes the VB namespace / project names
(PascalCase; defaults to the directory name). The generated README walks through the dev loop and the Windows checks.
Define the contract
// contract/contract.ts
import { z } from "zod";
import { defineContract } from "@ishibashi0112/webview2-bridge-gen";
// Give shared objects / enums a name with .meta({ id }) — it becomes the VB class name and the TS type name
const Part = z
.object({ partNo: z.string(), name: z.string(), qty: z.number().int(), updatedAt: z.string() })
.meta({ id: "Part" });
export const contract = defineContract({
methods: {
parts: {
search: {
input: z.object({ keyword: z.string().min(1), limit: z.number().int().optional() }),
output: z.object({ items: z.array(Part) }),
},
},
},
events: {
progress: z.object({ percent: z.number(), message: z.string().optional() }),
},
});
export type Contract = typeof contract;Configure and run
// webview2-bridge.gen.json (repository root)
{
"contract": "contract/contract.ts",
"schemaOut": "contract/contract.schema.json",
"ts": { "outDir": "apps/web/src/generated", "contractImport": "@webview2-bridge/contract" },
"vb": { "outDir": "dotnet/MyApp.Contract/Generated", "namespace": "MyApp.Contract" },
"openapi": { "out": "contract/openapi.json" } // optional
}webview2-bridge-gen # generate (uses ./webview2-bridge.gen.json)
webview2-bridge-gen --check # CI: exit 1 if the generated files are out of date
webview2-bridge-gen --config path/to/config.jsonGenerated files start with an <auto-generated> header; the generator only overwrites / removes files carrying that header.
Commit the generated files.
What is generated
TypeScript (contract-types.ts): PartsSearchInput / PartsSearchOutput, ProgressEvent, named types (Part),
MethodMap / EventMap, methodNames / eventNames. Types are derived from the zod contract with z.input / z.output.
VB.NET (into Namespace Global.<namespace>, referencing the WebView2Bridge.Runtime NuGet package):
| File | Content |
|---|---|
| Dto.vb | one Public Class per object with <JsonProperty("camelCase")>; string enums as NotInheritable Class with Public Const |
| Interfaces.vb | Public Interface IPartsApi with Function Search(req As PartsSearchRequest) As Task(Of PartsSearchResponse) |
| Dispatcher.Generated.vb | DispatcherExtensions.Register(dispatcher, api) extension methods wiring "parts.search" to IPartsApi.Search |
| Events.vb | BridgeEvents with Sub Progress(payload As ProgressEvent) emitting "event.progress" |
OpenAPI 3.1 (openapi.json, when openapi.out is set): the same contract as an HTTP API, so the VB host can later be
replaced by any server without touching the UI (the client runtime's HttpTransport speaks this shape):
| Contract | HTTP |
|---|---|
| method parts.search | POST /parts/search, request body = input, 200 body = output (plain JSON, no JSON-RPC envelope) |
| error | 400 (-32602 / -32600 / -32700), 404 (-32601), 500 (-32000 and other codes); body = JSON-RPC error object { code, message, data } |
| events | GET /events as Server-Sent Events; each data: line is one JSON-RPC notification { "jsonrpc": "2.0", "method": "event.progress", "params": {...} } |
| .meta({ id }) schemas, <Ns><Method>Request / Response, <Name>Event | components/schemas under the same names as the VB DTOs |
Options: title, version (of the contract, info), servers (default ["/"]), basePath (prefix for every path, default ""),
eventsPath (default "/events", false to omit). Authentication is deliberately not part of the contract (security: []).
Type mapping: string→String, number→Double, int→Integer (.meta({ format: "int64" })→Long), boolean→Boolean,
array→List(Of T), record→Dictionary(Of String, T), unknown→JToken, X | null→nullable, optional value types→Nullable(Of T).
Optional properties get NullValueHandling.Ignore so Nothing is omitted from JSON (zod .optional() rejects null).
Unions, intersections, dates and bigints are rejected with a clear error.
Naming: .meta({ id }) wins; otherwise <Namespace><Method>Request / Response, <Event>Event,
nested objects <Owner><Prop>, array items singularized (items → Item).
VB runtime without NuGet
The VB.NET runtime (Dispatcher, JsonRpc, IBridgeEmitter) and the WinForms glue (WebViewBridge) are bundled
in this package. Point vb.runtime.outDir / vb.winforms.outDir at your projects and the generator writes them as
auto-generated files, so no custom NuGet package is needed — your Contract project only references Newtonsoft.Json
and your Host only Microsoft.Web.WebView2.
"vb": {
"outDir": "dotnet/MyApp.Contract/Generated",
"namespace": "MyApp.Contract",
"runtime": { "outDir": "dotnet/MyApp.Contract/Runtime" },
"winforms": { "outDir": "dotnet/MyApp.Host/Bridge" }
}The files keep the namespaces WebView2Bridge.Runtime / WebView2Bridge.WinForms, so the code you write is identical
whether the runtime comes from these files or from the NuGet packages of the same name. Upgrading the runtime = upgrading
this npm package and running the generator again (--check reports outdated copies).
Options (vb)
| Option | Default | Meaning |
|---|---|---|
| namespace | required | namespace of the generated code |
| runtimeNamespace | WebView2Bridge.Runtime | namespace of Dispatcher / IBridgeEmitter (NuGet WebView2Bridge.Runtime) |
| dispatcherClass | Dispatcher | runtime dispatcher class name |
| eventsClass | BridgeEvents | generated events helper class name |
| emitterInterface | IBridgeEmitter | runtime emitter interface name |
| runtime.outDir | — | write the bundled VB runtime (3 files) here; omit when using the NuGet package or a ProjectReference |
| winforms.outDir | — | write the bundled WebViewBridge.vb here; omit when using the NuGet package or a ProjectReference |
Programmatic use
import { defineContract, toSchema, emitTs, emitVb } from "@ishibashi0112/webview2-bridge-gen";
import { generate, emitVbRuntime } from "@ishibashi0112/webview2-bridge-gen/generate"; // Node only (fs)MIT
