You can just git submodule in the dependencies. Super easy. Also makes it straightforward to develop patches to send upstream from within your project. Or to replace a dependency with a private fork.
In my experience, this works great for libraries internal to an organization (UI components, custom file formats, API type definitions, etc.). I don't see why it wouldn't also work for managing public dependencies.
Plus it's ecosystem-agnostic. Git submodules work just as well for JS as they do for Go, sample data/binary assets, or whatever other dependencies you need to manage.
How do I decline it?? I keep clicking no, hide, not interested, cancel, etc. but it keeps showing up and activating...if I had a nickel for every time I clicked it on accident in Azure because a layout shift moved it under my mouse when trying to press a button I would have a lot of nickels. It even showed up as an app on my phone because I guess the Office 365 entry got hijacked...
I really think writing dependency-free JavaScript is the way to go nowadays. The standard library in JS/CSS is great. So are static analysis (TypeScript can check JSDoc), imports (ES modules), UI (web components), etc.
People keep telling me the approach I am taking won't scale or will be hard to maintain, yet my experience has been that things stay simple and easy to change in a way I haven't experienced in dependency-heavy projects.
I’ve been exploring this for years, even made a tutorial website about building sites and apps without dependencies (plainvanillaweb.com). What I’ve learned is that many of the things the frameworks, libraries and build tools do can be replaced by browser built-ins and vanilla patterns, but also that making things that way is at present an obscure domain of knowledge.
I think this is because the whole web dev knowledge ecosystem of youtubers and tutorial platforms is oriented around big frameworks and big tooling. People think it is much harder than it actually is to build without frameworks or build tools, or that the resulting web app will perform much worse than it actually will. A typical react codebase ported to a fully vanilla codebase ends up just as modular and around 1.5x the number of lines of code, and is tiny in total footprint due to the lack of dependencies so typically performs well.
To be clear though: I’m not arguing the dependencies are bad or don’t have any benefits at all or that vanilla coding is a superior way. Coding this way takes longer and the resulting codebase has more lines of code, and web components are “uglier” than framework components. What I’m saying is that most web developers are trapped in a mindset that these dependencies must be used when in reality they are optional and not always the best choice.
Thanks for creating and sharing that resource! I'm reading through it now, and it looks fantastic. I'll share it the next time someone asks where to get started with web dev.
Come to think of it, I should write up the techniques I use, too...e.g. I have simple wrappers around querySelector() and createElement() with a bit of TypeScript gymnastics in a JSDoc annotation to add intellisense + type checking for custom elements.
Would you be open to a pull request with a page on static analysis/type checking for vanilla JS? (intro to JSDoc, useful patterns for custom elements, etc.) If not, that's totally OK, but I figure it could be interesting to readers of the site.
And agreed on vanilla/dependency-free not being a silver bullet. There aren't really one-size-fits-all solutions in software, but I've found a vanilla approach (and then adding dependencies only if/when necessary) tends to help the software evolve in a natural way and stay simple where possible.
I think a blog post on jsdoc would be a better fit, with a link out from one of the main tutorial pages. Reach out to me over mail and we can work something out.
> To be clear though: I’m not arguing the dependencies are bad or don’t have any benefits at all or that vanilla coding is a superior way. Coding this way takes longer and the resulting codebase has more lines of code, and web components are “uglier” than framework components.
If you do it long enough, presumably you start to develop your own miniature "framework" (most likely really just some libraries, i.e. not inverting control and handing it over to your previous work). After all, it's still programming; JS isn't exceptional even if it has lots of quirks.
Anyway, love the website concept, just a quick petition: would it be possible to apply some progressive enhancement/graceful degradation love to <x-code-viewer> such that there's at least the basic preformatted text of the code displayed without JS?
Did this for a project in 2022. Haven't had any drama related to CVEs, hadn't had any issues related to migration from some version of something to another.
The client has not had to pay a cent for any sort of migration work.
There are certainly security benefits to keeping things in-house. Less exposure to supply-chain attacks (e.g. shai-hulud malware) and widespread security bugs (e.g. react server components server-side RCE). Plus it's much easier to do a complete audit and threat model of the application when you built and understand everything soup-to-nuts.
Of course, it also means you have to be cautious about problems that dependencies promise to solve (e.g. XSS), but at the same time, bringing in a bunch of third-party code isn't a substitute for fully understanding your own system.
Is the lack of CVE because the implementations you wrote are better written and safer than those in the standard libraries or because no one has checked?
Presumably the latter. However, mindlessly bumping package versions to fix bullshit security vulnerabilities is now industry standard practice. Once your client/company reaches a certain size, you will pretty much have to do it to satisfy the demands of some sort of security/compliance jarl.
Very laudable, though this is probably also part of the issue: If the client doesn't need any migration work, the dev doesn't get more money, which in turn might be phrased: "It is difficult to get a man to understand something, when his salary depends upon his not understanding it!" -- by someone other than me.
I have worked at employer, where one could have done the frontend easily in a traditional server side templating language since most of the pages where static information anyway and very little interactive. But instead of doing that and have 1 person do that, making an easily accessible and standard-conforming frontend, they decided to go with nextjs and required 3 people fulltime to maintain this, including all the usual churn and burn of updating dependencies and changing the "router" and stuff. Porting a menu from one instance of the frontend to another frontend took 3 weeks. Fixing a menu display bug after I reported it took 2 or 3 months.
web components are reactive. They have a render pattern similar to React's render function. Granted, web-components are much more wonky than React, but the functionality is there.
It seems best practice to use the component's attributes directly. So the component is subscribed to its attributes change lifecycle and renders updates.
been doing something similar. the projects ive been building recently use as few dependencies as possible and honestly the maintenance burden dropped significantly. when something breaks you actually know where to look instead of digging through 15 layers of node_modules. people said the same thing to me about it not scaling but the opposite turned out to be true.
yeah, plus stack traces, debuggers, and profiling tools are easier to use when all of the non-essential complexity is stripped out. which in turn means it's possible to work productively on software that solves more complex problems.
that's in contrast with the sort of stuff that invariably shows up when something falls over somewhere in a dependency:
cannot access property "apply" of null
at forEach()
at setTimeout()
at digest()
at callback()
at then()
...
it's not fun to step through or profile that sort of code either...
I've been doing JS for nearly a couple decades now (both front and back) and I landed on the same approach a few years ago. Pick your absolutely minimal set of dependencies, and then just make what you need for everything else. Maybe counter-intuitive to some, I feel like I'm more comfortable maintaining a larger codebase with less people.
What's more, given the tools we have today, it fits really well with agentic engineering. It's even easier to create and understand a homegrown version of a dependency you may have used before.
It depends a lot what kind of thing you're building. If it's a simple marketing website, sure. But any application that does stuff is likely using dependencies that provide a lot of functionality.
I make a lot of sites with maps. There's no real alternative to mapbox/maplibre/openlayers.
That should be self-evident and obvious, but apparently it still needs to be pointed out.
On one extreme, you have the current world where there are millions of 1-line or 1-function packages like leftPad and isArray.
On the other extreme, there are no packages and everyone builds everything from scratch, every single time.
It should not be controversial or groundbreaking to suggest that perhaps there is a reasonable middle ground in between! Someone builds a substantial piece of functionality, and it can be reused.
Yeah I've been doing this more and more. The friction of keeping dependencies updated has gotten worse than the friction of just maintaining the (often times) 20 or so lines you need to write yourself. Not to mention the pain you'll eventually find yourself when a bug isn't patched fast enough, or when you need to make a small change, or when a transitive dependency you've never heard of gets compromised...
Sure it's not officially called the "standard library," more precisely it would be "the parts of the ECMAScript and CSS standards implemented by all popular evergreen browsers," but "standard library" expresses this in the way people usually talk about programming languages.
This is one of the best parts of agentic development. I almost never had to reach for any npm package besides the foundational staples like the tailwinds and react queries. My pacakge.json dependency array vs project size ratio is just incredible nowadays
This is the way. Even more so now that LLMs can reliably write simple utilities, the kind of things a dependency would previously frag in hundreds of other utilities (that go unused) all while depending on another dozen dependencies
Depends on what cryptography you're talking about, the Web Crypto API exists for quite some time, so I'd say that fits in (usually) with "The standard library in JS/CSS is great".
Take async for example. You have to choose some third-party async runtime which may or may not work with other runtimes, libraries, platforms, etc.
With Go, async code written in Go 1.0 compiles and runs the same in Go 1.26, and there is no fragmentation or necessity to reach for third party components.
The places where it poses challenges in my experience are high quality typesetting/rich text, responsive UI that requires a wide range of presentations (i.e. different layout structures on mobile vs desktop), and granular control over rendering details.
But for functionality-oriented UI, it's hard to beat its simplicity and scalability, particularly when you want to develop on one platform (e.g. computer) and have everything "just work" identically on other form factors (web/tablet/etc.).
For example, Godot's editor is bootstrapped/built in Godot, and runs natively on Web, Android, and Quest XR among other platforms.
Godot is pretty lightweight (especially considering how powerful it is), generally about a second or less. But maybe they are looking for a fast enough startup time that the engine can be started when showing something onscreen and torn down when not visible. In which case, I can see the startup time being an issue.
Much of the problem stems from inconsistent application of icons. This would have gone better if Apple established (and followed!) clear guidelines for exactly which icon to use for which standard action (e.g. search).
That the icons exist is not necessarily a problem, since they can help teach users which buttons in the UI do which actions. (menu bar for discovery, app UI for less mouse travel + contextual options). But that requires consistency, which the current implementation lacks...
Accordion behavior is discussed in the article in the "Accordions / Expanding Content Panels" section:
> Use the same name attribute on all related details (like radio buttons) to restrict only one open panel at a time
And tabs can be a <details>-based accordion with some creative CSS to adjust the layout (left as an exercise for the reader, but I could write up an example if that would be helpful!)
Yes, the tabs in a tabs pattern should be keyboard navigated using arrow keys (ironically not the Tab key).
Also, the summary for the currently open details element will have the wrong state, 'expanded' instead of 'selected'. And while a set of details can now have a maximum of one open at a time, you can't ensure exactly one is always open (without JavaScript) as the tabs pattern requires.
They shared the polling code in the article. It doesn't request another jpeg until the previous one finishes downloading. UDP is not necessary to write a loop.
> They shared the polling code in the article. It doesn't request another jpeg until the previous one finishes downloading.
You're right, I don't know how I managed to skip over that.
> UDP is not necessary to write a loop.
True, but this doesn't really have anything to do with using JPEG either. They basically implemented a primitive form of rate control by only allowing a single frame to be in flight at once. It was easier for them to do that using JPEG because they (to their own admission) seem to have limited control over their encode pipeline.
> have limited control over their encode pipeline.
Frustratingly this seems common in many video encoding technologies. The code is opaque, often has special kernel, GPU and hardware interfaces which are often closed source, and by the time you get to the user API (native or browser) it seems all knobs have been abstracted away and simple things like choosing which frame to use as a keyframe are impossible to do.
I had what I thought was a simple usecase for a video codec - I needed to encode two 30 frame videos as small as possible, and I knew the first 15 frames were common between the videos so I wouldn't need to encode that twice.
I couldn't find a single video codec which could do that without extensive internal surgery to save all internal state after the 15th frame.
A 15 frame min anf max GOP size would do the trick, then you'd get two 15 frame GOPs. Each GOP can be concatenated with another GOP with the same properties (resolution, format, etc) as if they were independent streams. So there is actually a way to do this. This is how video splitting and joining without re encoding works, at GOP boundary.
In my case, bandwidth really mattered, so I wanted all one GOP.
Ended up making a bunch of patches o libx264 to do it, but the compute cost of all the encoding on CPU is crazy high. On the decode side (which runs on consumer devices), we just make the user decode the prefix many times.
A word processor can save it's state at an arbitrary point... That's what the save button is for, and it's functional at any point in the document writing process!
In fact, nearly everything in computing is serializable - or if it isn't, there is some other project with a similar purpose which is.
However this is not the case with video codecs - but this is just one of many examples of where the video codec landscape is limiting.
Another for example is that on the internet lots of videos have a 'poster frame' - often the first frame of the video. That frame for nearly all usecases ends up downloaded twice - once as a jpeg, and again inside the video content. There is no reasonable way to avoid that - but doing so would reduce the latency to play videos by quite a lot!
> A word processor can save it's state at an arbitrary point... That's what the save button is for, and it's functional at any point in the document writing process!
No, they generally can't save their whole internal state to be resumed later, and definitely not in the document you were editing. For example, when you save a document in vim it doesn't store the mode you were in, or the keyboard macro step that was executing, or the search buffer, or anything like that.
> In fact, nearly everything in computing is serializable - or if it isn't, there is some other project with a similar purpose which is.
Serializable in principle, maybe. Actually serializable in the sense that the code contains a way to dump to a file and back, absolutely not. It's extremely rare for programs to expose a way to save and restore from a mid-state in the algorithm they're implementing.
> Another for example is that on the internet lots of videos have a 'poster frame' - often the first frame of the video. That frame for nearly all usecases ends up downloaded twice - once as a jpeg, and again inside the video content.
Actually, it's extremely common for a video thumbnail to contain extra edits such as overlayed text and other graphics that don't end up in the video itself. It's also very common for the thumbnail to not be the first frame in the video.
> Serializable in principle, maybe. Actually serializable in the sense that the code contains a way to dump to a file and back, absolutely not. It's extremely rare for programs to expose a way to save and restore from a mid-state in the algorithm they're implementing.
If you should ever look for an actual example; Cubemap, my video reflector (https://manpages.debian.org/testing/cubemap/cubemap.1.en.htm...), works like that. It supports both config change and binary upgrade by serializing its entire state down to a file and then re-execing itself.
It's very satisfying; you can have long-running HTTP connections and upgrade everything mid-flight without a hitch (serialization, exec and deserialization typically takes 20–30 ms or so). But it means that I can hardly use any libraries at all; I have to use a library for TLS setup (the actual bytes are sent through kTLS, but someone needs to do the asymmetric crypto and I'm not stupid enough to do that myself), but it was a pain to find one that could serialize its state. TLSe, which I use, does, but not if you're at certain points in the middle of the key exchange.
Why not hand off the fd to the new process spawned as a child? That’s how a lot of professional 0 downtime upgrades work: spawn a process, hand off fd & state, exit.
> No, they generally can't save their whole internal state to be resumed later, and definitely not in the document you were editing.
I broadly agree, but I feel you chose a poor example - Vim.
> For example, when you save a document in vim it doesn't store the mode you were in,
Without user-mods, it does in fact start up in the mode that you were in when you saved, because you can only save in command/normal mode.
> or the keyboard macro step that was executing,
Without user-mods, you aren't able to interrupt a macro that is executing anyway, so if you cannot save mid-macro, why would you load mid-macro?
> or the search buffer,
Vim, by default, "remembers" all my previous searches, all the macros, and all my undos, even across sessions. The undo history is remembered per file.
> A word processor can save it's state at an arbitrary point...
As ENTIRE STATE. Video codecs operate on essentially full frame + stream of differences. You might say it's similar to git and you'd be incorrect again, because while with git you can take current state and "go back" using diffs, that is not the case for video, it alwasy go forward from the keyframe and resets on next frame.
It's fundamentally order of magnitude more complex problem to handle
I'm on a media engineering team and agree that applying the tech to a new use case often involves people with deep expertise spending a lot of time in the code.
I'd guess there are fewer media/codec engineers around today than there were web developers in 2006. In 2006, Gmail existed, but today's client- and server-side frameworks did not. It was a major bespoke lift to do many things which are "hello world" demos with a modern framework in 2025.
It'd be nice to have more flexible, orthogonal and adaptable interfaces to a lot of this tech, but I don't think the demand for it reaches critical mass.
> It was a major bespoke lift to do many things which are "hello world" demos with a modern framework in 2025.
This brings back a lot of memories -- I remember teaching myself how to use plain XMLHTTPRequest and PHP/MySQL to implement "AJAX" chat. Boy was that ugly JavaScript code. But on the other hand, it was so fast and cool and I could hardly believe that I had written that.
I started doing media/codec work around 2007 and finding experienced media engineers at the time was difficult and had been for quite some time. It's always been hard - super specialized knowledge that you can only really pick up working at a company that does it often enough to invest in folks learning it. In my case we were at a company that did desktop video editing software so it made sense, but that's obviously uncommon.
So US->Australia/Asia wouldn't that limit you to 6fps or so due half-rtt? Each time a frame finishes arriving you have 150ms or so for your new request to reach.
In my experience, this works great for libraries internal to an organization (UI components, custom file formats, API type definitions, etc.). I don't see why it wouldn't also work for managing public dependencies.
Plus it's ecosystem-agnostic. Git submodules work just as well for JS as they do for Go, sample data/binary assets, or whatever other dependencies you need to manage.
reply