TL;DR: I built mysite — a full Markdown-to-website generator, Node's standard library only, dependencies: {} — for the Zero Dependency Hackathon (Track A). Fifteen substitutions, 35 tests, zero third-party imports across 30 source files. But the actually interesting problem wasn't building the thing. It was realizing that "I didn't add a dependency" is a claim you can't demonstrate the normal way — you can't run it and watch it work, because the thing you're proving is that something isn't there.
Every other feature proves a capability. This hackathon asks you to prove a lack
Most of what you build in a hackathon demo runs and does something visible. Parse this file. Render that page. Here's the output. mysite does all of that too — but its actual headline claim, dependencies: {}, isn't something you can demo by running it. An empty object is invisible whether it's true or whether you just forgot to check. Nobody watching a live demo can tell the difference between "genuinely zero dependencies" and "the presenter didn't look closely."
So the interesting engineering problem wasn't the Markdown parser or the template engine. It was: how do you make an absence checkable in under five seconds by someone who doesn't trust you yet?
✓ package.json dependencies: {} (empty)
✓ package.json devDependencies: {} (empty)
✓ scanned 30 source files — 0 non-stdlib imports found
✓ zero third-party runtime dependencies confirmed
That's npm run verify-zero-dep — a program whose entire job is to look at every one of my own source files and confirm none of them cheated. I had to build a second tool just to audit the first one's promise. That's not a normal feature. That's the hackathon's actual ask, made literal.
The other half of "prove it": can the same source produce the same bytes, twice?
The second proof problem is subtler. "Zero dependencies" tells you what went into the build. It says nothing about whether the build itself is honest — whether running it again gives you the same thing, or something that happens to look the same but isn't provably identical.
Build 1 hash: 94f69ed94e7ac0f05a3cc585330cc6e343e7c44875cc89ecb065409027b795c0
Build 2 hash: 94f69ed94e7ac0f05a3cc585330cc6e343e7c44875cc89ecb065409027b795c0
✓ builds are byte-identical (deterministic)
Getting to that pair of matching hashes meant hashing the entire output tree, sorted, before comparing — and I want to be precise about what that sentence is and isn't. It reads like the aftermath of a bug I caught. I'm not going to dress it up as one, because it isn't — I built the sort in from the first version of the verifier, not after watching an unsorted hash flip between two runs. The reason I built it in preemptively is boring and specific: directory enumeration order on a filesystem is not guaranteed stable across runs or across operating systems, and a "reproducible build" that silently depends on readdir() returning files in the same order every time isn't actually reproducible — it's reproducible on your machine, today, by luck. Sorting the file list before hashing turns a coincidence into a guarantee.
I'd rather tell you plainly that this was foresight, not a war story, than borrow the shape of a bug-hunt post I haven't actually earned this round.
Package Killer: the one substitution that isn't just an exercise
Fifteen packages get replaced in this project, but one of them is the reason the whole hackathon exists in the first place. chalk — the terminal colorizer, 2.6 billion weekly downloads between it and debug — was the exact package whose maintainer got phished in September 2025, shipping a crypto-clipper straight into a huge share of the JavaScript ecosystem's dependency tree. src/cli/colors.js replaces it in about 30 lines: raw ANSI escape codes, a NO_COLOR check, a TTY check so piped/CI output doesn't come out full of escape sequences. No chaining API, no 24-bit TrueColor. Eight colors and bold/dim is what a build tool's CLI output actually needs, and every line of it is one I wrote and can read end to end.
Replacing an arbitrary dependency is an exercise. Replacing that specific one is the thesis of the entire event, applied to itself.
What fifteen substitutions actually cost, honestly
The parts of this project I'm most confident are correct are the parts where I wrote down exactly what I gave up, rather than the parts where I claimed feature parity with the library I replaced.
The JS minifier strips comments and whitespace only — deliberately no AST-based symbol mangling, because silently breaking someone's variable scoping to save a few hundred bytes is a worse trade than shipping a slightly larger file. The YAML frontmatter parser handles nested maps and simple lists via indentation matching, but not folded multi-line scalars or anchors — a real subset, not a full implementation wearing a YAML-shaped mask. The binary image-header probe reads PNG, JPEG, GIF, and WebP headers directly off the byte stream to auto-inject width/height attributes — genuinely useful, genuinely a real binary parser, and it doesn't touch EXIF orientation metadata, so a rotated JPEG will report its unrotated dimensions.
None of those are things I discovered by them breaking. They're scope decisions I made and wrote down before shipping, which is a different kind of honesty than "here's the bug that got past me" — quieter, but just as much the point of a zero-dependency build. The corners you choose on purpose are exactly as important as the ones you find by accident.
Was the search index and link checker scope creep?
Worth addressing directly rather than pretending the question doesn't apply. A TF-IDF full-text search index and a broken-link checker that issues real HTTP HEAD requests are a lot of surface area for a project whose one-line pitch is "Markdown to website."
Here's the honest argument for why they're not extra: the pitch was never "Markdown to HTML." It's a complete website generator — and a modern static site that ships without search or link validation isn't complete, it's a demo. Every one of those two features is normally the reason someone reaches for a specific, well-known package (lunr/flexsearch for search, broken-link-checker for links) — cutting them wouldn't have made the project more focused, it would have made the "normally assembled from ten-plus npm packages" claim in the README false by omission. They stayed in scope because they were load-bearing for the actual claim, not because I got attached to them.
By the numbers
✓ package.json dependencies: {} (empty)
✓ package.json devDependencies: {} (empty)
✓ scanned 30 source files — 0 non-stdlib imports found
✓ zero third-party runtime dependencies confirmed
0 third-party runtime dependencies, 30 source files scanned to confirm it
35 tests via Node's built-in node:test — no test framework installed to get there
15 documented stdlib-for-package substitutions in STDLIB.md, each with its own honest trade-off, not just a feature checklist
1 reproducible build, byte-identical hash across two independent builds
11 bonus points claimed: Reproducible Build (+5), Package Killer (+3, chalk), STDLIB Log (+3)
The actual takeaway
Most of engineering is proving you built something. This hackathon's real test is proving you didn't — that nothing snuck in, that the build isn't lying to you by coincidence, that every corner you cut is one you chose rather than one you missed. The two verifier scripts in this repo aren't bonus features. They're the only way an absence becomes something someone else can actually check instead of just take your word for.
Built solo for the Zero Dependency Hackathon 2026, Track A (Developer Tools & CLI).
Top comments (0)