> Can vendoring be reasonably automated?

This is where I find bundlers to still be useful and friendly. Rather than bundling everything, just spot bundle individual dependencies as necessary. The great thing is that individual dependency vendoring is especially where bundlers stop being massive balls of configuration and start being script friendly one-liners again.

A direct example I've used in several places is bundling all of the (top "barrelled") RxJS library into a single vendored ESM file with esbuild:

npx esbuild --bundle ./node_modules/rxjs/dist/esm/index.js --outfile=./vendor/rxjs.js --format=esm

It's also a useful counterpoint to the request for more library authors to publish to package managers single big ESM files, by taking that power back and doing it proactively with available modern bundlers in a nice-to-have middle ground between bundlers being too common and top-level and "no bundlers".

> Can routing be done reasonably well with vanilla means?

I think routing has a bad reputation for being more complex than it is, especially from a vanilla perspective. There's basically three pieces to routing: Event Handling, Data Structures, and often Regex. Individually all three are easy to do, it is often only in combining the three into the same problem/library that a lot of the unintentional complexity creeps in.

1. Event Handling:

If all you want is classic SPA hash-routing (using just the #fragment to route by), you only need to worry about the hashchange event [1]. It is simple and high-level. The classics are classics for a reason and a lot of SPAs frown on hash-routing, but it's still a great, simple option and should not be overlooked even if it is currently "unfashionable".

If you want to go down the road of fancier SPA navigation with fake but nice-looking URLs (https://myapp/about instead of https://myapp/#/about, for instance) there's only one Vanilla JS API to learn, the History API [2]. It's got a small learning curve and the popstate event could be a bit smarter, but it's still relatively easy to work with.

(One source of complexity in "modern routers" is that they often include or are prollyfills for the even higher-level, and still Experimental, Navigation API [3]. It is subtly nicer to work with than the History API, though yet another level of complexity/learning-curve, but for Vanilla/light-weight/dependency-free stuff learning the History API is "good enough" for today.)

2. Data Structures

You want a way to "register" routes and then iterate from them. Routing libraries will offer complex plugin systems and nested trees of routes, but you can always start simpler with hand edited Records or Lists.

3. Regex

You need a way to match a URL's path and capture variables from it, and Regex is ultimately the tool to do that. I'd argue there's nothing more Vanilla in JS than crafting your own Regexes. (And Typescript now has better than ever compile time checks for them.) But I also understand why a lot of people wish to avoid writing Regexes directly in routing. There are small dependency-free/light libraries just for that part of routing such as path-to-regexp [4]. (TIL there's also an Experimental attempt to provide such a library directly in the browser called the URL Pattern API [5].)

But you also might not need Regexes at all. If you don't have a lot of aliases and you don't need to capture variables, sometimes all you need is "just" a switch with string matching.

This is more "vanilla-adjacent" than "vanilla", but sometimes the entire "router library" can be this basic in today's Typescript: https://github.com/WorldMaker/macrotx/blob/main/src/router.t...

[1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/hash...

[2]: https://developer.mozilla.org/en-US/docs/Web/API/History_API...

[3]: https://developer.mozilla.org/en-US/docs/Web/API/Navigation_...

[4]: https://github.com/pillarjs/path-to-regexp

[5]: https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern...