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)