Library / SDK
withastro/compiler avatar
withastro/compiler

The Astro compiler is a Go program shipped to JavaScript as WASM

The Astro compiler. Written in Go. Distributed as WASM.

658 stars90 forksGoNOASSERTION

At a glance

What is it?
withastro/compiler is the Go implementation behind Astro's .astro files, distributed to npm as @astrojs/compiler with the Go binary compiled to WebAssembly. It offers two entry points, transform for turning a component into a TypeScript module and parse for an AST, and the documentation is candid that position data is still incomplete and that the Node runtime server has to be supplied by other runtimes themselves.
Who is it for?
Tooling authors who need to read, transform or analyse Astro components will get a small, well typed API with a transform path and an AST path, and a WASM distribution that runs anywhere Node runs. It is the wrong tool if you need stable position data, since the documentation says it is currently incomplete and sometimes incorrect, and it is the wrong tool outside the Node ecosystem unless you implement a runtime server yourself and pass it through `internalURL`.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 36 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Go binary with a WebAssembly wrapper

The Astro compiler is the Go plus WASM compiler behind Astro. The Go side lives in `internal/` and `internal_wasm/` with the command entry points in `cmd/`, and the JavaScript side is published from `packages/` inside a pnpm workspace. The build makes the packaging explicit: the Makefile compiles the Go code with `CGO_ENABLED=0 GOOS=js GOARCH=wasm` and writes the result to `./packages/compiler/wasm/astro.wasm`, and the `publish-node` target then builds the npm package from that directory. The flags also carry reproducibility, since the build uses `-trimpath` to avoid embedding the build path in the executable and `-ldflags=-s -w` to strip symbols. The Go module is `github.com/withastro/compiler` requiring Go 1.21, with dependencies that read like a parser toolchain, `tdewolff/parse/v2` for parsing, `strcase` for case conversion, `dedent` for template handling, `go-snaps` for snapshot testing and `go-cmp` for comparisons. The npm side of the workspace uses pnpm 10.22.0, Biome for formatting and linting, uvu with tsx for the test runner, and changesets for releases.

transform turns a component into a TypeScript module

The primary API converts `.astro` syntax into a TypeScript module whose default export generates the HTML:

js
import { transform, type TransformResult } from "@astrojs/compiler";

const result = await transform(source, {
  filename: "/Users/astro/Code/project/src/pages/index.astro",
  sourcemap: "both",
  internalURL: "astro/runtime/server/index.js",
});

The options are minimal and each one has a purpose. The filename identifies the component being compiled, the sourcemap option here is set to `both`, and `internalURL` points at the runtime the output will import. Two caveats come with it. The first is that TypeScript is valid `.astro` syntax, which means the output code may need an additional post-processing step to become valid JavaScript, so a consumer of this API has to handle that itself. The second is the runtime dependency described below, which is where most of the integration friction sits for anyone using the compiler outside the Node ecosystem.

The runtime server dependency, and the internalURL escape hatch

A compiled `.astro` file is not standalone. The documentation states that `.astro` files rely on a server implementation exposed as `astro/runtime/server/index.js` in the Node ecosystem, and that other runtimes currently need to bring their own rendering implementation and reference it via `internalURL`. The documentation is blunt about the status of this, calling it a pain point that is being looked into. For anyone building tooling on top of the compiler, this is the single most important sentence in the README, because it decides whether the compiler is usable in your target environment at all. In Node, the runtime is already there and `internalURL` can point at the default. Somewhere else, you are expected to supply a rendering implementation and pass its location, which means the compiler's output is only as portable as the runtime you give it. The `internalURL` option in the transform call above is that switch, and it is the reason the option exists at all.

parse returns an AST that the utils entrypoint can walk

The second entry point parses a component and returns an abstract syntax tree:

js
import { parse } from "@astrojs/compiler";
import { walk, walkAsync, is } from "@astrojs/compiler/utils";

const result = await parse(source, {
  position: false, // defaults to `true`
});

walk(result.ast, (node) => {
  // `tag` nodes are `element` | `custom-element` | `component`
  if (is.tag(node)) {
    console.log(node.name);
  }
});

The helpers come from a separate entry point, `@astrojs/compiler/utils`, which exposes `walk` and `walkAsync` for traversal and the `is` helper, which works as a guard to narrow a node to its proper type. The comment in the example is the useful part for anyone writing analysis tooling: a `tag` node is one of `element`, `custom-element` or `component`, so the `is` guard is what turns an untyped node into a discriminated union member. There is also an async walk, shown in the documentation with a callback that awaits an expensive calculation and assigns the result to `node.value`, which is what you want for a linter or a codemod that has to fetch or compute data per node. The `position` option defaults to `true` and can be turned off, which is the hint that position data is the weak point.

Position data is incomplete, and TextNode carries two meanings

The notes attached to the parse method set out the two limitations clearly. Position data is currently incomplete and in some cases incorrect, and the project says it is working on it. For a formatter or an editor integration, that is the limitation that matters, because source mapping back to a location in the file is usually the whole point of parsing. The second note is a design detail with wider consequences: a `TextNode` can represent both HTML `text` and JavaScript or TypeScript source code. That is a consequence of the format itself, where the frontmatter and the template are part of the same document, and it means a consumer walking the tree has to decide which kind of text it is looking at rather than assuming template content. Neither limitation is hidden, and both are the kind of thing to check against your own use case before you build a tool on the AST.

Building the WASM artifact yourself

Building from source is a make target rather than a long list of steps, and the target is the same one the release process uses:

bash
CGO_ENABLED=0 GOOS=js GOARCH=wasm go build $(GO_FLAGS) -o ./packages/compiler/wasm/astro.wasm ./cmd/astro-wasm/astro-wasm.go

The npm package is then built from `packages/compiler`, and the root package.json exposes that as `pnpm run build:compiler`, with `pnpm run build` producing the WASM first and `pnpm run build:all` doing both. The test setup is worth noting as well, because it is unusual for a compiler written in Go to be tested from JavaScript. The `test` script runs uvu through tsx across the `packages` directory while ignoring the `utils` and `stress` directories, there is a separate `test:only` target for the packages alone, a `test:stress` script that runs a dedicated stress harness, and `test:ci` that chains the two. A devcontainer configuration is provided for VSCode Remote Development and GitHub CodeSpaces, and there is a Gitpod configuration, so the intended contributor loop is to bring your own environment. The repository also carries a SYNTAX_SPEC.md, which is the file to read if you are implementing the format rather than consuming it.

A 4.x line, a September push and a custom licence

The package versions tell the story of the project. The recent releases are @astrojs/compiler 3.0.0 on 2026-03-06, 3.0.1 on 2026-03-16 and 4.0.0 on 2026-04-27, and the last push to the repository was on 2026-09-17 with the repository not archived. A major version bump inside a compiler is worth a look before you depend on it, because the output shape of `transform` and the shape of the AST are the contract other tools build on. The licence is recorded as a custom one that GitHub cannot classify, with the terms in the LICENSE file at the repository root, so anyone embedding the compiler in a product should read it rather than assume a permissive default. The rest of the repository follows a normal modern layout, with `.changeset/` for release notes, `.golangci.yml` and `biome.json` for the two languages, a `Makefile` for the WASM build, `pnpm-workspace.yaml` and `pnpm-lock.yaml` for the JavaScript side, and `go.mod` and `go.sum` for the Go side.

Editorial conclusion

Tooling authors who need to read, transform or analyse Astro components will get a small, well typed API with a transform path and an AST path, and a WASM distribution that runs anywhere Node runs. It is the wrong tool if you need stable position data, since the documentation says it is currently incomplete and sometimes incorrect, and it is the wrong tool outside the Node ecosystem unless you implement a runtime server yourself and pass it through `internalURL`. Before you depend on it, pin the package version you tested, since the 4.0.0 release moved the line, and check whether your tool needs a separate post-processing step to turn the TypeScript output into runnable JavaScript.

Frequently asked questions

How do I install the Astro compiler?

The package is published on npm as @astrojs/compiler and installs with `npm install @astrojs/compiler`. It is a Go program distributed as WebAssembly, so there is no native binary to compile on your side.

What is the difference between transform and parse in the Astro compiler?

The transform method converts `.astro` syntax into a TypeScript module whose default export generates the HTML, while the parse method emits an AST. The AST can be traversed with walk and walkAsync from the @astrojs/compiler/utils entrypoint, with the `is` helper narrowing node types.

Can I use the Astro compiler outside Node?

Not without extra work. The documentation says other runtimes currently need to bring their own rendering implementation and reference it via the internalURL option, and it calls that a pain point the project is looking into fixing.

Is the position data from the Astro compiler's parser reliable?

The documentation says position data is currently incomplete and in some cases incorrect, and that the project is working on it. It also notes that a TextNode can represent both HTML text and JavaScript or TypeScript source code.

How is the WASM build produced?

The Makefile target `wasm` runs the Go build with CGO_ENABLED=0, GOOS=js and GOARCH=wasm, writing the artifact to ./packages/compiler/wasm/astro.wasm, and the publish-node target then builds the npm package from packages/compiler. The root package.json exposes these as the build and build:compiler scripts.

Official sources

  1. Issues
  2. README
  3. Releases
  4. withastro/compiler on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/withastro-compiler.svg)](https://hysenlabs.com/projects/withastro-compiler)

Was this useful?