Setup
Required tools for TinyGo WebAssembly Component Model support:
- TinyGo compiler v0.39.0+: Compiles Go code to WebAssembly components
-
wkg: Resolves and bundles WIT dependencies into a single package -
wasm-tools: Converts WebAssembly modules to components
Build Process
Go plugins require a specific build process due to TinyGo's Component Model requirements:
1. Prepare WIT files for TinyGo
TinyGo has specific requirements that differ from other languages. The prepare-wit-files.sh script (a custom script for this project) copies WIT files from crates/pluginlab/wit to go_modules/wit and uncomments TinyGo-specific lines:
./scripts/prepare-wit-files.sh -i crates/pluginlab/wit -o go_modules/wit -s "SPECIFIC TinyGo" Why this workflow exists: The go_modules/wit directory is gitignored and contains modified versions of the WIT files specifically for TinyGo. This workflow maintains crates/pluginlab/wit as the single source of truth for all WIT definitions, while automatically generating TinyGo-compatible versions.
Why this is needed: The wasip2 target of TinyGo assumes that component is targeting wasi:cli/command@0.2.0 and requires explicit inclusion of WASI imports. The script searches for lines containing "SPECIFIC TinyGo" and uncomments them:
world plugin-api { // The wasip2 target of TinyGo assumes that the component is targeting // wasi:cli/command@0.2.0 world (part of wasi:cli), so it needs to // include the imports from that world. // It's only included for the versions of the wit files destined for TinyGo. - // include wasi:cli/imports@0.2.0; // SPECIFIC TinyGo - DO NOT CHANGE THIS LINE + include wasi:cli/imports@0.2.0; // SPECIFIC TinyGo - DO NOT CHANGE THIS LINE import http-client; import host-state-plugin; export plugin; } 2. Bundle WIT dependencies with wkg
Why wkg is used: TinyGo cannot resolve WIT imports on its own - it needs all dependencies to be either bundled into a single WASM file or pre-resolved into a local directory. For example, when TinyGo sees include wasi:cli/imports@0.2.0;, it doesn't know where to find that external package or what's inside it. The wkg tool handles dependency resolution by fetching all imported WIT interfaces and creating a complete WIT package:
cd go_modules/ wkg wit build This creates repl:api.wasm containing all the WIT definitions needed by the plugin. The command also generates a wkg.lock file that locks the dependency versions for reproducible builds.
3. Generate Go bindings
Use wit-bindgen-go to generate Go code from the bundled WIT package:
cd go_modules/plugin-name/ go tool wit-bindgen-go generate --world plugin-api --out internal ../repl:api.wasm This creates the internal/ directory with all the generated Go bindings for the plugin interfaces.
4. Compile with TinyGo
TinyGo compiles the Go code directly to a WebAssembly component:
cd go_modules/plugin-name/ tinygo build -target=wasip2 --wit-package ../repl:api.wasm --wit-world plugin-api -o plugin-name-go.wasm main.go File Structure
Project organization: Go plugins follow this structure in the repo:
go_modules/ wit/ # Modified WIT files for TinyGo plugin-api.wit # Contains TinyGo-specific includes shared.wit # Shared types and interfaces host-api.wit # Host API definitions repl:api.wasm # Bundled WIT package (from wkg) wkg.lock # Lock file for WIT dependencies (from wkg) plugin-echo/ # Plugin directory go.mod # Go module with wit-bindgen-go tool main.go # Plugin implementation internal/ # Generated bindings (from wit-bindgen-go) repl/api/plugin/ # Plugin interface bindings repl/api/transport/ # Transport type bindings wasi/ # WASI interface bindings plugin-echo-go.wasm # Final WebAssembly component Plugin Implementation
Standard pattern: Go plugins use the init() function to register their exports, following the WebAssembly Component Model pattern:
package main import ( "webassembly-repl/plugin-echo/internal/repl/api/plugin" "webassembly-repl/plugin-echo/internal/repl/api/transport" "go.bytecodealliance.org/cm" ) func init() { plugin.Exports.Name = func() string { return "echogo" } plugin.Exports.Man = func() string { return `... some man text ...` } plugin.Exports.Run = func(payload string) cm.Result[plugin.PluginResponse, plugin.PluginResponse, struct{}] { response := plugin.PluginResponse{ Status: transport.ReplStatusSuccess, Stdout: cm.Some(payload), Stderr: cm.None[string](), } return cm.OK[cm.Result[plugin.PluginResponse, plugin.PluginResponse, struct{}]](response) } } // main is required for the wasip2 target func main() {} Go Module Configuration
Required setup: The go.mod file must include the wit-bindgen-go tool as a Go tool dependency:
module webassembly-repl/plugin-echo go 1.24 tool go.bytecodealliance.org/cmd/wit-bindgen-go // ... other dependencies This allows the project to use go tool wit-bindgen-go commands for generating bindings.
📎 Here are links to:
- the go_modules directory that contains the Go plugins
- the justfile file that contains the build commands for Go plugins
- PR#16 - Add Go Language Plugin Support
Top comments (0)