README
¶
gotreesitter in the browser
gotreesitter has two JavaScript/WebAssembly entry points. Choose one target per
.wasm file; they intentionally expose different APIs.
| Target | Input | Best for |
|---|---|---|
./wasm/runtime |
A generated gotreesitter grammar blob | Production parsing, queries, and highlighting from pre-generated tables |
./wasm/grammargen |
Tree-sitter grammar.json |
Interactive grammar development and generation in the browser |
Both targets publish a synchronous globalThis.gotreesitter object after the
module starts. loader.js initializes either build.
Build
Build the targets with Go's JavaScript WebAssembly port:
mkdir -p dist
GOOS=js GOARCH=wasm go build -tags grammar_blobs_external \
-o dist/gotreesitter-runtime.wasm ./wasm/runtime
GOOS=js GOARCH=wasm go build -o dist/gotreesitter-grammargen.wasm ./wasm/grammargen
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" dist/wasm_exec.js
cp wasm/loader.js dist/loader.js
For a deployable single-language bundle, use the asset command instead. It:
- selects only the requested language's tables, registry, and scanner support;
- emits the browser-loaded grammar blob as a separate asset; and
- writes a digest manifest alongside the compiler's matching bootstrap.
go run ./cmd/wasmassets -language go -output dist/go -compiler go
# Or use TinyGo and its matching wasm_exec.js:
go run ./cmd/wasmassets -language go -output dist/go-tiny -compiler tinygo
Serve these files over HTTP. WebAssembly.instantiateStreaming normally
requires the server to send .wasm as application/wasm; the shared loader
falls back to fetching an ArrayBuffer when streaming instantiation is not
available.
wasm_exec.js must come from the same Go toolchain used to build the selected
.wasm file. Mixing versions is unsupported and can fail during startup. Go
1.24 and later install it at $(go env GOROOT)/lib/wasm/wasm_exec.js; older Go
toolchains place it under misc/wasm.
Runtime build
The runtime build consumes grammar blobs generated by gotreesitter. The
repository's built-in blobs live under grammars/grammar_blobs; applications
can serve only the languages they need. The grammar_blobs_external build tag
shown above keeps the complete built-in blob set out of the WASM module. If the
supported language set is known at build time, combine it with
grammar_subset and the matching grammar_subset_<language> tags. This
retains only that language's scanner and registry support.
When name resolves to a registered language with a custom token-source
factory, the runtime uses that factory consistently for parsing, queries, and
highlighting. Unknown out-of-tree names fall back to the blob's DFA tables.
API
loadBlob(name, blobUint8Array, highlightQuery, tagsQuery?)loads one grammar blob. Pass an empty string for either unavailable query.parse(name, source)returns{ok, sexp, hasError, tree}.treeis a JSON string containing the structured syntax tree; parse it once withJSON.parse. Empty source returns an empty S-expression,hasError: false, and the JSON string"null".query(name, source, queryText)compiles and runs a Tree-sitter query. It returns{ok, matches, truncated}; compile failures also includeerrorOffsetwhen the compiler reports one.highlight(name, source)returns{ok, ranges}for the highlight query supplied toloadBlob. Each range containsstartByte,endByte, andcapture.open(name, documentID, source)retains a UTF-16 document tree and returns its initial highlights and tags.update(documentID, source)computes a surrogate-safe minimal edit, incrementally reparses the document, and returns its new revision, highlights, tags, and edit span.queryDocument(documentID, queryText)runs a bounded query over the retained tree without reparsing source.close(documentID)releases the retained tree. Reopening an existing ID replaces and releases the prior document.
Tree nodes contain type, named, optional missing, error, and field
properties, plus nested children. Their start/end positions are canonical
UTF-8 byte offsets. start16/end16 are UTF-16 code-unit offsets suitable for
indexing JavaScript strings. Query captures provide the same two coordinate
systems along with name, type, and text. Highlight ranges are UTF-8 byte
offsets.
Structured trees retain at most 20,000 nodes. The root has truncated: true
only when additional nodes were omitted. Queries retain at most 500 matches
and set truncated: true when another match exists. Execution is bounded by
the streaming cursor rather than materializing every match first.
Browser example
<script src="/wasm/wasm_exec.js"></script>
<script src="/wasm/loader.js"></script>
<script type="module">
const api = await loadGotreesitter("/wasm/gotreesitter-runtime.wasm");
const blobResponse = await fetch("/grammars/go.bin");
const blob = new Uint8Array(await blobResponse.arrayBuffer());
const loaded = api.loadBlob("go", blob, "");
if (!loaded.ok) throw new Error(loaded.error);
const source = "package main\nfunc hello() {}\n";
const parsed = api.parse("go", source);
if (!parsed.ok) throw new Error(parsed.error);
const root = JSON.parse(parsed.tree);
console.log(root.type, root.start16, root.end16, parsed.sexp);
const queried = api.query(
"go",
source,
"(function_declaration name: (identifier) @name)",
);
if (!queried.ok) throw new Error(queried.error);
console.log(queried.matches[0].captures[0]);
</script>
To highlight, pass the language's highlights.scm contents as the third
loadBlob argument and then call api.highlight("go", source).
Persistent editor clients can share one tree across analysis operations:
const opened = api.open("go", "editor:main.go", source);
const updated = api.update("editor:main.go", source.replace("hello", "world"));
const matches = api.queryDocument(
"editor:main.go",
"(function_declaration name: (identifier) @name)",
);
api.close("editor:main.go");
Grammargen build
The grammargen target creates parse tables from grammar JSON inside the browser. It is useful for authoring and experiments; production applications that already have a generated blob should prefer the runtime target.
API
importGrammar(grammarJSON)imports a Tree-sitter grammar JSON string and returns its name.generateLanguage(name)generates and caches the parser tables.parse(name, source)returns{ok, sexp, hasError}; empty source succeeds with an empty S-expression andhasError: false.highlight(name, source, highlightQuery?)returns UTF-8 byte ranges. When a query is omitted, the build uses a cached generated query when available.highlightQueries(name)returns a cached generated highlight query.
Generate a language before parsing or highlighting it:
const api = await loadGotreesitter("/wasm/gotreesitter-grammargen.wasm");
const imported = api.importGrammar(grammarJSON);
if (!imported.ok) throw new Error(imported.error);
const generated = api.generateLanguage(imported.name);
if (!generated.ok) throw new Error(generated.error);
const parsed = api.parse(imported.name, source);
if (!parsed.ok) throw new Error(parsed.error);
console.log(parsed.sexp);
The grammargen target does not expose the runtime target's structured tree or query API. Build the runtime target and load the generated blob when those APIs are required.