惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
Google DeepMind News
Google DeepMind News
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
L
LangChain Blog
IT之家
IT之家
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog
Hugging Face - Blog
Hugging Face - Blog
G
Google Developers Blog
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
Recent Announcements
Recent Announcements
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
What We Lose When Everything Is a Wrapper
Viktor Lázár · 2026-05-18 · via DEV Community

What a 14-year-old HTML5 Wolfenstein 3D port taught me about owning the stack

A recent Primeagen video sent me back into an old feeling: the strange discomfort of realizing how much of modern software is now built around things we do not really own.

Not in the legal sense. In the practical sense.

We install a package, which installs other packages, which wrap lower-level packages, which hide runtime behavior behind conventions, build steps, plugins, adapters, loaders, and generated code. The result works. Often it works beautifully. But there is a quiet moment, usually during debugging, where the shape of the thing becomes hard to hold in your head. You are no longer asking, "What does my program do?" You are asking, "Which layer is currently doing this on my behalf?"

That is not always a problem. Abstraction is one of the reasons software can grow at all. Nobody should rewrite a font renderer to build a settings page. Nobody should reimplement TLS because they want an API endpoint. Most dependency-free manifestos eventually collapse into a kind of performance art where the programmer is very proud, very tired, and still missing Unicode.

This is not that argument.

This is not an argument against dependencies. It is an argument for knowing when they are helping you build, and when they are quietly becoming the thing you are building around.

The Game With No Dependencies

Fourteen years ago, I ported Wolfenstein 3D to HTML5.

No engine. No framework. No package manager. No third-party runtime dependencies.

Just JavaScript, canvas, and a very specific obsession.

There was a production build step, because the web has always had its rituals. The release build went through Google's old Java-based Closure Compiler. But that was packaging, not architecture. It made the output smaller. It did not define the shape of the program.

That distinction matters. It was not a small raycasting demo inspired by Wolfenstein. It was an actual browser port of the game, built directly on the platform the browser gave me at the time. This is the same idea I was circling around in The Browser Was the Engine: for that project, the browser was not just a deployment target. It was the engine.

I am open-sourcing it now, and the thing that feels most surprising in 2026 is not that it still exists. It is that there is almost nothing underneath it that can have gone stale. There is no abandoned package to replace. No transitive dependency tree to audit. The old minifier is a historical detail, not a living dependency graph. No ecosystem era is trapped in amber around it.

It is just code.

That sounds smaller than it is. A complete game is not a toy problem. Even a simple raycasting engine needs a loop, input, collision, map parsing, rendering, textures, sprites, timing, state, audio, UI, and all the little bits of glue that turn a technical demo into something that feels like a game. You do not get to skip complexity by avoiding dependencies. You only choose where the complexity lives.

In that project, the complexity lived in the project.

That made the code less general. It was not a reusable game engine. It did not have an extension system, a plugin API, a renderer abstraction, or a roadmap. It was not trying to become a platform. It was trying to be one thing.

That narrowness was the feature.

The renderer did not need to serve every possible game. The input layer did not need to become an input library. The map representation did not need to model the universe. Every piece of the program could be shaped around the exact problem in front of it. The code had no ambition beyond the game, and because of that, the game could be completely itself.

Fourteen years later, that still matters.

This used to be much more normal. On the C64 and the Amiga, on MS-DOS machines, and especially on old consoles from Nintendo, Sega, and the rest of that era, software was often written as a direct answer to a specific machine. A game was not usually a thin layer over a general engine over a general runtime over a general abstraction of hardware. It was a peculiar little organism fitted to the memory layout, graphics modes, timing behavior, controller shape, and limits of the target. The constraints were brutal, but they made the program concrete. You could feel the machine in the code.

I do not want to pretend that era was better in every way. It was harder, narrower, less portable, and full of tricks nobody should be forced to rediscover for ordinary application work. But it had one quality that is easy to miss now: the program and the platform had an intimate relationship. The software did not float above the machine. It negotiated with it directly.

The Shelf Life of Small Surfaces

Software ages in strange ways.

Sometimes code ages because the problem changes. Sometimes it ages because the platform changes. But a huge amount of modern code ages because the stack around it moves.

The package manager changes. The bundler changes. The framework changes. The framework's compiler changes. The plugin ecosystem changes. The lockfile format changes. The version of Node you need becomes a small archaeological site. You do not just maintain the application. You maintain the ability to assemble the application.

There is a real cost hidden there.

Dependencies do not only add code. They add motion. They add release notes, security advisories, migration guides, incompatible peer ranges, deprecated APIs, and small future obligations that are easy to accept because they arrive wrapped in convenience.

That convenience is often worth it. But it is not free.

The dependency-free Wolfenstein source has a different aging profile. It depends on the browser, of course. Every web program does. The production build once depended on Closure Compiler. But the game itself does not depend on a cultural moment in the JavaScript ecosystem. The old build step can be replaced, skipped, or recreated because it was only an output step. The source does not need a 2012 toolchain to remember how to be itself. Its surface area is small enough that the future had fewer places to break it.

This is one of the underrated virtues of writing something directly: you reduce the number of external clocks your project has to keep time with.

Every dependency is a clock. Some tick slowly and responsibly. Some tick chaotically. Some stop. Some are taken over by someone else. Some are still correct but no longer fashionable. Some become infrastructure so deeply embedded that nobody remembers choosing them.

The fewer clocks you attach to a project, the longer it can sit quietly without turning into an incident.

Generality Has a Price

A dependency is usually a package of generality.

That is its value. The author solved a problem broadly enough that you can reuse the solution in a context they did not know about. A good library is a small miracle of compression: years of thought hidden behind a function call.

But generality has a shape, and that shape enters your program.

If you use a game engine, your game starts to inherit the engine's idea of a game. If you use a framework, your application inherits the framework's idea of an application. If you use a router, a state manager, an ORM, a component library, a testing framework, and a build system, your program becomes partly an expression of all their assumptions.

Again, this can be good. Shared assumptions are how teams move. Conventions are how projects become legible. A framework can save you from wasting your life on plumbing that has nothing to do with the thing you are trying to build.

But every general-purpose layer also asks you to pay for cases that are not yours.

Sometimes you pay in bytes. Sometimes in indirection. Sometimes in concepts. Sometimes in the strange helplessness of reading code that looks simple while knowing that the real behavior lives somewhere else.

This is the wrapper feeling.

Not abstraction itself. Abstraction is a tool. The wrapper feeling is what happens when the abstraction no longer disappears. It stays visible. It stands between you and the material. You can feel yourself programming the wrapper's model of the problem instead of the problem.

At that point, the dependency may still be doing useful work, but it has also become a design constraint. You are building with it, but you are also building around it.

The Good Kind of Dependency

I should be careful here, because I build dependencies too.

I work on react-server. At Level 0x40 Labs, I build open-source tools and primitives, including things like virtual-frame. Some of my work is explicitly about making reusable layers for other programs. I care about composable runtimes, shared primitives, and the boring miracle of code that lets other code exist more easily.

So no, I do not believe the purest program is the one that imports nothing.

The good dependency does something more interesting than save keystrokes. It gives you a stable primitive. It makes a hard boundary understandable. It removes accidental complexity without stealing the shape of the thing you are building.

The best dependencies feel less like wrappers and more like materials.

Canvas is a material. SQLite is a material. A focused parser can be a material. A well-designed runtime primitive can be a material. You can build with them while still feeling that the program is yours.

The dangerous dependency is different. It does not merely provide capability. It brings a worldview. It decides the architecture, the vocabulary, the lifecycle, the extension points, the error model, the deployment model, and the kinds of solutions that feel natural. You can still build something good inside it, but you are no longer only building your thing. You are also participating in its theory of software.

That may be exactly what you want.

If I am building a product with a team, I want shared machinery. I want boring defaults. I want the dependency graph to absorb problems that are not central to the product. I want the project to move because not every layer deserves to be handmade.

But if I am building the core mechanic of a game, or the central abstraction of a runtime, or the part of a system that defines what the system is, I want a different relationship to the code.

I want ownership.

Ownership Is a Design Constraint

Ownership is not the same as authorship.

You can write every line of a program and still not own it, because the design is copied from a framework you are no longer using. You can also depend on a library and still own your system, because the boundary is clear and the library is just a tool in your hand.

Ownership means you understand the important consequences of the system's shape.

You know where time is spent. You know where state lives. You know what happens when input arrives. You know which layer can fail and how failure moves. You know the constraints because you chose them, or at least because you have looked at them directly enough to accept them.

This is what dependency-heavy development can erode. Not skill in the macho sense. Not the ability to write a linked list on a whiteboard. Something more practical: the habit of tracing behavior all the way down until the system becomes concrete again.

When everything is a wrapper, the bottom keeps moving away.

You learn the public API. Then the plugin API. Then the framework convention. Then the generated output. Then the bundler behavior. Then the runtime behavior. Every layer may be reasonable on its own, but the stack as a whole can become a place where nobody quite knows what is happening. The program works because the ecosystem works. Until it does not.

The cost is not only reliability. It is imagination.

If you only build inside other people's abstractions, you start to think in the shapes they make convenient. Your ideas arrive pre-filtered by the tools. You ask, "How do I do this in this framework?" before you ask, "What is this thing, actually?"

That question matters.

The New Game

The game I am working on now also uses no third-party code.

Not because I have become allergic to packages. Not because I think every line must be sacredly handcrafted. It is simpler than that: the game has a specific shape, and a purpose-built implementation fits that shape better than a general engine would.

A game engine would give me a thousand solved problems. Some of those solutions would be useful. Some would be irrelevant. Some would slowly bend the game toward the engine's strengths. I would move faster at the beginning, and then spend part of that saved time negotiating with decisions made for other games.

For this project, that trade does not feel right.

I want the renderer to know exactly what this game is. I want the update loop to carry exactly the concepts the game needs. I want the asset pipeline, the state model, and the interaction rules to be small enough that they can stay in my head. I want the implementation to be shaped by the game, not by the possibility space of all games.

That is not a universal recommendation. It is a local one.

The mistake is treating dependency decisions as identity. Dependency-free does not mean serious. Dependency-heavy does not mean sloppy. The useful question is always more specific:

What part of this system needs to be mine?

Choosing the Layer

There are layers where depending on other people is a sign of maturity.

Cryptography. Databases. Protocol implementations. Image codecs. Font shaping. Accessibility primitives. Hard-won platform knowledge that would be arrogant or irresponsible to casually recreate.

There are layers where depending on other people is mostly a business decision.

Admin UI, routing, deployment glue, test runners, analytics, documentation tooling, forms, dashboards, the parts of a product whose correctness matters but whose originality does not.

And there are layers where depending too early can cost you the thing you are trying to discover.

The core interaction of a game. The execution model of a runtime. The data model of a creative tool. The editor behavior of a text surface. The small strange thing that made the project worth building in the first place.

Those layers deserve suspicion. Not because dependencies are bad, but because premature generality can erase the local shape before you have understood it.

Sometimes the right move is to build the first version directly. Learn the material. Discover the constraints. Let the project teach you what abstractions it actually wants. Then, if a dependency fits, you will know where it fits. You will be choosing it from knowledge rather than reaching for it from reflex.

That difference is everything.

The Muscle

The reason I still care about from-scratch projects is not nostalgia.

It is maintenance of a muscle.

The muscle is the ability to look at a problem without immediately asking which package owns it. The ability to draw the smaller machine inside the larger machine. The ability to build a narrow implementation without apologizing for its lack of generality. The ability to recognize when a dependency is a material, when it is a tool, and when it is slowly becoming the architecture.

Modern software needs dependencies. It also needs people who remember what the layers are made of.

That is what the old Wolfenstein project gives me now. It is not impressive because it avoided packages. It is not morally superior because the dependency list is empty. It is useful because it is a reminder that a complete thing can still be built as a complete thing.

No engine. No framework. No package graph.

A game.

That possibility is worth keeping alive.

Not for every project. Not for every layer. Not as a purity test.

Because sometimes the only way to know what you are building is to build enough of it yourself that it can answer back.