From cargo build to a running page

The command that turns this Rust crate into something a browser can run is wasm-pack build --target web. It produces a directory called pkg containing seven files: the WebAssembly binary, a JavaScript loader, TypeScript declarations for both, a package manifest, a copy of the README, and a gitignore file whose entire contents are a single asterisk. That last file is the reason none of the build output is committed, and the reason the host has to run the build itself rather than serving files from the repository.

The seven files

That gitignore is generated by wasm-pack itself, and it means the whole pkg directory excludes itself from version control. The consequence is worth stating plainly: cloning this repository does not give you a runnable site. You get the source and the static pages, and you have to build the WebAssembly module before the calculator will do anything. The build page gives the exact commands.

Why the web target

The web target emits a loader that is a plain ES module with no bundler step in between. The page imports it directly with a module script tag, which is why the calculator has no build tooling on the JavaScript side at all: no webpack, no rollup, no node_modules. The single import in the page's script pulls in the default export, which is the initialiser, plus the named exports for the calculator class and the three standalone functions.

The other targets would each add a step. A bundler target expects a packaging tool downstream; a Node target emits CommonJS that a browser cannot load. For a static site served straight off disk, the web target is the one that needs nothing else.

How the binary is fetched and instantiated

Calling the initialiser with no arguments makes the loader resolve the binary relative to its own module URL, so the WebAssembly file is found next to the JavaScript that loads it without any path being hardcoded. The loader then tries the streaming instantiation path, which compiles the module as the bytes arrive rather than waiting for the whole download.

Streaming instantiation has one requirement that trips people up: the server must send the binary with a content type of application/wasm. The generated loader handles a server that does not, but not silently. It catches the failure, checks whether the content type was wrong, prints a console warning naming that as the cause, and falls back to the non-streaming path, which downloads the bytes fully and then compiles them. The fallback works; it is just slower. This site's origin does serve the binary as application/wasm, so the streaming path is the one that runs.

The initialiser returns a promise. The page awaits it before constructing the calculator, which is why the display shows a zero placeholder in the shipped HTML and only becomes live once the module is ready. Everything that reads or writes calculator state is behind that await.

What the release profile does

The crate's release profile sets the optimisation level to size rather than speed and enables link-time optimisation. Size wins here because the heaviest thing the module computes is a loop of at most twenty multiplications, so the difference between size-optimised and speed-optimised code is far below anything a person could perceive, while the difference in download size is paid by every visitor before they can add two numbers. The reasoning is set out at greater length on the why Rust and WebAssembly page.

Questions and answers

Why is the pkg directory not in the repository?

Because wasm-pack generates a gitignore inside it containing a single asterisk, which excludes the whole directory. The build output is regenerated by whoever builds the site rather than committed.

What does wasm-pack build --target web produce?

Seven files in a directory called pkg: the WebAssembly binary, a JavaScript loader, TypeScript declarations for the loader and for the raw module, a package manifest, a copy of the README, and a gitignore.

Why does WebAssembly need the application/wasm content type?

Streaming instantiation compiles the module as it downloads, and it refuses to run unless the server declares the bytes as application/wasm. The generated loader detects that case, warns in the console, and falls back to downloading the file fully before compiling it.

Does the calculator need a JavaScript bundler?

No. The web target emits a plain ES module, which the page imports directly with a module script tag. There is no bundler, no node_modules directory and no JavaScript build step.