Vanilla Prime: A simpler, more sustainable way of web development
github.com
github.com
In my very humble opinion, vanilla js is esmodules, html, fetch and good old plain css. Basically a no-node environment, static hosting and no transpilation.
I do like the "plus" idea, now that you mention it! Will think about it :)
Goes back to 2012 unchanged (hence the selection of libraries compared against): https://web.archive.org/web/20120825232614/http://vanilla-js...
Edit to add: Having been around in that era, I see "Vanilla Prime" as a whole new framework, nothing vanilla about it.
I think the name already acknowledges it isn't quite vanilla.
Linting can be setup globally instead of per project
> It has to be somewhere.
I tend to agree; I think I'm just trying to not shift it to the user device's runtime.
Typically, then you get extremely rich and complex user interfaces with less work. The problem is though that this is still janky. It's not a smooth, or even viable, experience sometimes.
But it goes to show that making applications via the Web IS unnecessarily complex. Because the tools, being HTML, JS, and CSS were never intended for applications but rather documents. Personally, I think it's a sliding-scale type thing between documents and applications. Anyway, the standards have been expanded to try to address the application use cases but it's all "bolted on" stuff. And the document stuff also has to stay because that's still a primary use case.
Like, I would rather shoot myself than try to replicate photoshop in the Web. I'd much rather use Qt for an application like that.
1. Vue seems to follow most of these paradigms, including "pass data down, bubble events up" and directory structure.
2. This is a stupid complaint, but putting *.ts files in a directory named "js" bothers me. Of course, it makes more sense if one doesn't like typescript and just uses ES6+ as I am wont to do.
The "src/js" directory was initially called "src/scripts" but it kind of clashes with the top-level "scripts" for the shell scripts. Agreed it's not ideal though.
> No frameworks, no bundlers, no required dependencies. Just web standards, patterns, a unix-style toolset, and some elbow grease.
vs > Install Node.js >= 20.
this is then followed by a "Toolset" section listing a bunch of other tools. I don't understand what is going on here.Node is a runtime with a large standard library. The Java equivalent is the JVM plus some file system and network handling libraries.
A framework is a rich set of various abstractions designed to integrate in support of a design methodology.
Packing your basic web site with 50,000+ SLOC worth of run time dependencies is quite a different story. When you use a framework like react, or vue, or svelte, you're bringing in a massive dependency tree, and you're also changing the semantics of the application code from web standard code to framework specific code. Instead of being an expert on the standards, you have to become an expert in the framework. There are compromises being made either way.
I think that one important thing missing from this project is a templating engine of some kind. Questionable addition though, since you're then going to stop writing HTML and start writing whatever template language. And it's a greased path toward going with a framework.
"A simpler, more sustainable way of web development"
vs.
"Vanilla Prime is not meant to be easy"
I think it's a little naive to believe the JS build scene is somehow divorced from the JS runtime scene, when there's just as much complexity, churn, deep dependency chains and rapid obsolescence.
https://www.infoq.com/presentations/Simple-Made-Easy/
I'm not sure if the author succeeded or not, but at least the goal is clear.
I agree that the JS build scene can be churning a lot as well (e.g. ESLint's recent config changes), that's why I tried to make everything as orthogonal and disposable as possible.
> quick start
> Install Node.js >= 20
IMO all the extra flavoring on the front end comes from everyone having their own approach to modeling UIs and managing state for UIs. I don’t see anything similar on the backend - HTTP server packages seem mostly similar, data access packages seem to be a fairly small/thin layer.
I’m not deep into the node ecosystem, help me see how Ben & Jerry have contributed to the backend :-D
- main.ts file contains multiple small functions
- tests test the same thing twice: once with a unit test, and once with an integration test
But those are minor pet peeves. My main one is: what is this, exactly? Neither the project itself nor the proposed combination of tools and directory structures show that this is somehow better or more sustainable than any other approach.
It's purely demo code. I personally subscribe to the "modules should be deep" school of thought (Ousterhout).
> tests test the same thing twice: once with a unit test, and once with an integration test
Again, purely a demonstration that Playwright handles E2E, integration, and unit tests equally well, not an endorsement of testing the same thing twice. Note the absence of jest, mocha, etc.
> somehow better
Hopefully, you can achieve similar gains to [0], e.g. 50% less time to load and 95% less bandwidth in that case, by avoiding frameworks and other runtime dependencies.
> more sustainable
Hopefully, code produced this way is less susceptible to framework churn and other changes in the environment. Also, knowledge gained by following the approach will be about the standard web platform, not about a particular framework (and version thereof).
This is a nice project, but the comments here show that the author is in a different world if they consider it a "no frameworks, no bundlers, no required dependencies. Just web standards, patterns, a unix-style toolset, and some elbow grease" or "simple" or "more sustainable".
It's got like 7 dotfiles and a 3500 line package-lock.json.
I don’t mean to dismiss the authors efforts here, but if you actually value your time, go with standard tooling and don’t overdo it with the runtime dependencies. There, saved you an afternoon.
FWIW Vite would indeed handle the responsibilities of sass, typescript (partly), s4d, terser, cbst, vendoring (but not eslint, stylelint, prettier, playwright, c8, exdom). It's certainly a good option!
One thing to consider is that Vite promotes a plug-in ecosystem, of which many plug-ins are maintained by individuals. I think this increases the chance for incompatibilities over time significantly.
Also, having more documentation is not necessarily good. Each tool in Vanilla Prime has its own documentation, but they are self-sufficient. Vite combines lots of things in the background, which already have documentation, then adds their own documentation, and often requires additional plug-ins that take care of integration.
Maybe Vanilla Prime could be the car you can repair yourself, whereas Vite is the modern car that is so tightly engineered that repairing without external support becomes difficult.
-- frontend --
ES6 Javascript with Modules
CSS 3
VueJs
-- backend --
asp.net core
Done, you can pretty much create any site you want from simple to fully scaled out. No build or transpiling needed for frontend.
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...