International Morse code encoder/decoder, written in Rust and compiled to WebAssembly. Built as a learning vehicle for the Rust → wasm pipeline; intended to be reusable from any web app that needs Morse encoding/decoding without a JS implementation.
morse-wasm-encoder-decoder.demo.mov
- 7 unit tests passing on the native target.
- Builds cleanly to
wasm32-unknown-unknownviawasm-pack. - Output bundle: ~23 KB
.wasm+ ~7 KB JS glue.
Rust toolchain and wasm-pack. See INSTALL.md for the exact setup. Quick sanity check:
rustc --version
cargo --version
wasm-pack --versionmorse-wasm/
├── Cargo.toml # crate manifest, declares cdylib for wasm
├── INSTALL.md # toolchain setup notes
├── README.md # this file
├── src/
│ └── lib.rs # encode/decode + tests + alphabet table
├── demo/
│ └── index.html # self-contained demo page
└── pkg/ # generated by wasm-pack, committed for direct consumption
├── morse_wasm.js
├── morse_wasm_bg.wasm
├── morse_wasm.d.ts
└── package.json
From the repo root:
wasm-pack build --target web --releaseThis emits the pkg/ directory. Default --out-dir is pkg, so no flag needed.
What it does, end to end:
- Compiles the crate for
wasm32-unknown-unknown(auto-installs the target if missing). - Runs
wasm-bindgento generate the JS glue that marshals strings across the JS↔Wasm boundary. - Optimizes the
.wasmwithwasm-opt. - Drops everything in
pkg/.
--target web emits an ES module loadable by <script type="module"> directly, no bundler required. Other options if needed: bundler (webpack/rollup), nodejs, no-modules. See wasm-pack build --help.
After build, pkg/ contains:
| File | Purpose |
|---|---|
morse_wasm.js |
ES module with init() + encode() + decode() exports |
morse_wasm_bg.wasm |
the compiled WebAssembly binary |
morse_wasm.d.ts |
TypeScript declarations for both files |
morse_wasm_bg.wasm.d.ts |
type info for the raw wasm exports |
package.json |
npm manifest, ready to publish |
cargo testTests run on the native target (no wasm involved), validating the encode/decode logic. Browser-targeted tests would use wasm-bindgen-test and wasm-pack test --headless --chrome; not wired up yet.
The demo at demo/index.html is self-contained — no Express, no bundler. But because it loads a .wasm file, browsers refuse to load it from a file:// URL. Serve it over HTTP:
wasm-pack build --target web --release # only needed once, or after src/ changes
python3 -m http.server -d . 8000 # any static server worksThen open http://localhost:8000/demo/.
The demo imports ../pkg/morse_wasm.js, so pkg/ must exist alongside demo/.
Both functions are #[wasm_bindgen]-exported and callable from JS as plain functions taking and returning strings.
- Maps each character to its Morse representation.
- Letters within a word are separated by a single space.
- Words are separated by
/(slash with surrounding spaces). - Input is uppercased before lookup, so case is ignored.
- Whitespace runs collapse to a single word boundary.
- Characters not in the alphabet are silently skipped.
encode("Hello, world!");
// → ".... . .-.. .-.. --- --..-- / .-- --- .-. .-.. -.. -.-.--"- Splits on
/to find word boundaries, then on whitespace within each word for letter boundaries. - Each token is looked up in the reverse alphabet.
- Unknown tokens are silently skipped.
- Output is uppercase ASCII; words are joined with single spaces.
decode(".... . .-.. .-.. --- / .-- --- .-. .-.. -..");
// → "HELLO WORLD"decode(encode(s)) equals s.toUpperCase() whenever every character of s is in the alphabet (after uppercasing). Whitespace runs are collapsed.
encode(decode(m)) equals the canonical form of m (single spaces, / between words) when every token of m is a valid Morse code in the alphabet.
53 entries: A–Z, 0–9, and 17 punctuation marks (. , ? ' ! / ( ) & : ; = + - _ " @). The full table lives in src/lib.rs.
The built artifacts in pkg/ are committed, so consumers can use them without a Rust toolchain installed. Pick one of these:
A. Clone next to the consumer app. Clone this repo as a sibling of your app, then reference pkg/morse_wasm.js directly. Either copy pkg/ into your app's static dir, or set up your build to symlink/serve it.
B. Git submodule. Add this repo as a submodule of the consumer repo. The consumer references pkg/morse_wasm.js from there.
C. Rebuild into the consumer. If you have Rust and wasm-pack available, you can rebuild on demand:
wasm-pack build --target web --release --out-dir ../my-app/static/morse-wasmThat bypasses the committed pkg/ and emits a fresh build into the consumer's static dir.
D. Future: npm publish. cd pkg && npm publish would put it on npm under the name in pkg/package.json. Consumers then npm install morse-wasm and import { encode, decode } from 'morse-wasm'. Requires an npm account.
- The crate sets
crate-type = ["cdylib", "rlib"]in Cargo.toml.cdylibis whatrustcneeds to emit a standalone wasm module on thewasm32-unknown-unknowntarget;rlibletscargo testlink it normally on the host. #[wasm_bindgen]macros onencodeanddecodecause the macro expansion to emit metadata describing the JS-facing signatures (string in, string out).- After
cargo build,wasm-packinvokes thewasm-bindgenCLI, which reads that metadata and generatesmorse_wasm.js— a thin JS module that knows how to copy strings into the wasm linear memory, call the underlying numeric exports, and read result strings back out. wasm-optshrinks the binary.- In the browser,
import init, { encode, decode } from './morse_wasm.js'loads the JS glue.await init()fetches and instantiates the.wasmand wires up the exports. After that,encode(s)looks like a normal JS function call.
The Rust code has no idea it is talking to JS — it just takes a &str and returns a String. All the marshalling lives in the generated glue.
git clone <this repo>
cd morse-wasm
# Set up Rust + wasm-pack per INSTALL.md
cargo test
wasm-pack build --target web --release
python3 -m http.server -d . 8000
# open http://localhost:8000/demo/