I work on a bunch of Pick systems :) Love it all and we're still doing active development. (Feel free to send me an e-mail, we pick up orphaned systems)
If anyone wants to take a look, here are some links:
Is this aimed at being in the same role as petite-vue and alpinejs? They also don't have a build step.
I've started to think something like petite-vue and twind would let you build small internal tools quickly, there are some major downsides to it which is why I haven't committed yet.
Using the full vue.js doesn't have a build step either.
I started out developing UIs using petite-vue, unfortunately ended up rewriting it to use full Vue.js after running into too many of its limitations and issues, which given that it's abandon-ware wont ever be resolved. As such I'd strongly recommend against starting projects with PetiteVue and just use the ESM build of Vue.js which also doesn't require a build step, has the full feature-set, is actively used and maintained, etc.
Either way developing Web Apps (using Vue) without a build step using JS Modules is now my preference which I've written about in my Simple, Modern JavaScript post [1]
That’s a good point — the ESM build of Vue is solid, actively maintained, and does give you a no-build setup with the full feature set.
dagger.js aims a bit differently: it’s even smaller in scope (no virtual DOM, no reactivity system, no SFCs), and is designed to pair directly with Web Components and plain HTML snippets. The trade-off is fewer features, but also less surface area and almost zero “lock-in” — you can literally view-source and drop it into a page.
So I see them as different tiers on the same spectrum: Vue (ESM) if you want a mature, batteries-included framework without a bundler; Dagger.js if you just want minimal runtime directives and WC interop with almost no moving parts.
Appreciate you sharing the blog link — I’ll check it out. Always happy to see more people pushing the “modern JS without a toolchain” approach.
> The trade-off is fewer features, but also less surface area and almost zero “lock-in” — you can literally view-source and drop it into a page.
You should be able to drop in code dynamically on any existing website with JS Modules, I show an example of this in one of our release notes to showcase the versatility of JS modules [1], which:
- Dynamically adds an Import Map with references to Vue, an external lib + Vue component library
- Dynamically creates and mounts a new Vue Component on the fly
- Drops in a fully functional and editable Data Grid with API bound forms, API + form validation, etc
Basically you should be able to do most things on a deployed page as can be done in your "no-build" ESM Web App.
I did try building web components with Lit [2], unfortunately WC's encapsulation and shadow dom made it difficult to enable theming and maintain shared global tailwind styles, so ditched it and went back to Vue. Although it's a good option for creating encapsulated components that don't need to share styles.
That’s a great example — I agree, with JS Modules and import maps you can already do a lot of dynamic composition in the browser, even with heavier frameworks like Vue. dagger.js isn’t trying to claim something impossible with ESM; the goal is more to make the “drop-in” workflow the default, without needing to wire up import maps or component bootstrapping code yourself. It’s about reducing the ceremony, not redefining what modules can do.
On the styling point: totally hear you. Shadow DOM can be a blessing or a curse depending on whether you want strict encapsulation or global theming. dagger.js doesn’t enforce Shadow DOM, so you can share global Tailwind styles when you need them, while still having the option to encapsulate components.
If you do want to explore Web Components without the pain of rolling everything yourself, I’d recommend taking a look at Shoelace
— it’s a really nice library of styled Web Components that play well with dagger.js. You get encapsulated, accessible components, but with good customization hooks for theming. Could be a good middle ground between Lit’s strict encapsulation and Vue’s more global styling.
If I had to use one of these modern JS frameworks, I think Vue without build step would be one of the candidates. No shitty webpack configuring, no minimizer, no bundler, no friggin uglyfier, no juggling modules. None of the crap, just write your JS and serve the script, done.
Totally agree — the ESM build of Vue gives you a great “no-bundler” experience with a full framework behind it.
dagger.js sits in the same no-build space, but deliberately strips it down even further: no VDOM, no reactive system, no SFCs. Just HTML with attributes like +click / +load, and it plays nicely with native Web Components. The trade-off is fewer features, but also less surface area and almost nothing to configure.
So if Vue ESM is “full-featured without the tooling overhead,” dagger.js is more like “minimal glue you can drop in via <script> when you want to stay as close to plain HTML/JS as possible.”
my framework of choice is aurelia. it is probably as fully featured as vue, but at a glance its templating and minimal need for glue code makes it look more similar to dagger.js than vue, to the point that i think it should be easy to convert from aurelia to dagger.js and back.
like vue, by default aurelia uses a build step, but serving it directly from a CDN or your own server is possible. i am actually working on a site that does that right now.
one thing i like about aurelia is that a template and js code are associated by name, so <this-view></this-view> translates to this-view.js: class ThisView {}, this-view.html, this-view.css, so they all form one unit, and i only need to import the js and specify the class name to load and have everything else defined automatically.
Aurelia is a great framework — I really like the way it ties template, JS, and CSS together into one unit. That convention makes it very natural to organize components and reduces boilerplate.
You’re right that dagger.js takes a different approach: it treats HTML, JS, and CSS as independent modules that you explicitly wire together. The reason is to keep everything buildless and decoupled — you can serve each piece directly from a CDN, mix and match across projects, and avoid any assumptions about file structure or bundling.
The trade-off is exactly as you noticed: dagger.js doesn’t auto-bind a .js class to a .html and .css file. It gives up that convenience in exchange for transparency and flexibility in how modules are loaded.
That said, I think your idea of a convention layer on top of dagger.js (similar to Aurelia’s “unit” model) is interesting — it could make sense as an optional helper for people who want stronger coupling between files.
dagger.js takes a different approach: it treats HTML, JS, and CSS as independent modules that you explicitly wire together. The reason is to keep everything buildless
aurelia doesn't need a build step to wire things together.
nor does it remove transparency or flexibility. because you can still specify all parts explicitly if you want to.
it should not be hard to create a function that does the same for dagger.js:
That’s a really good point — thanks for laying out the example so clearly.
You’re right: Aurelia can avoid a build step and still preserve flexibility, and the kind of helper you sketched (load_modules("thispage")) would definitely make the default case much less verbose while still allowing people to override pieces explicitly when they need to.
With dagger.js the initial choice was to keep everything explicit to reinforce the “HTML/JS/CSS are just independent modules” idea, but I agree that adding a convention-based shortcut on top could make the developer experience smoother without removing transparency.
In future iterations, I plan to add more flexible usage patterns to better meet diverse development needs.
my main interests in a framework is minimalized boilerplate code that only exists to make the framework functions work, so that i can reuse code without the framework without much change or any change at all. and a html syntax that follows the standards. your syntax here has a bit of an issue because characters like * or + may be ok in HTML, but they are not valid in XML or XHTML, so they limit the usability in systems where interoprability with XML or XHTML is needed.
by far the biggest point is however is buildless application. while aurelia and vue and others can be used buildless, it's always a chore to get there because using build tools is still the default. buildless first, or buildless only is a big win. also because it encourages different optimizations that are more suitable for a buildless application. aurelia or vue are not optimized for buildless usage.
Thanks for laying this out — I share your priorities on minimal boilerplate and a buildless-first workflow. dagger.js is designed around that: no bundler by default, just HTML + JS modules, so most code stays reusable outside the framework.
On syntax: you’re right that +/* aren’t XML/XHTML-friendly. The short, stackable directives were chosen for ergonomics, but I’m considering offering a dg-* variant to improve standards compliance where XML/XHTML interoperability matters.
Appreciate the push here — keeping the buildless path the default while tightening standards compliance is exactly where I want to take dagger.js next.
if i may make a suggestion here, you could use the type as part of the name:
so +load becomes lifecycle.load
*value would be control.value
+click would be event.click
@raw would be meta.raw
. because i think only _ . and - are allowed. you want to keep - for complex names: control.some-value and _ doesn't feel right.
maybe you want to choose other names. aurelia uses this approach with different names. i didn't want to influence you to choose aurelias names, so i picked those from the table on https://daggerjs.org/#/directive/introduction
keeping the names in line with the terminology you use makes it easy to remember them and also to look them up. (what is a lifecycle? i can just search for it. the only downside is that lifecycle in particular is long, so if that is used frequently, a shorter name might be preferable. maybe just 'cycle')
oh and while we are talking about about verbosity. i saw one router example that looked a bit verbose. but maybe that's because it tries to show all the options you can set on a route.
a minimalist example would be nice to see there in contrast. (maybe there is one and i just didn't find it)
Thanks a lot for the detailed suggestions — this is super helpful.
You’re right that symbols like +//@ aren’t valid in XML/XHTML, and the original idea was just to keep things as short and stackable as possible. But I see the value in a more standard, semantic form like lifecycle.load, control.value, event.click, meta.raw. It’s clearer, easier to search, and plays nicely with stricter environments.
I’m leaning toward offering both: the current short symbols for quick prototyping, and a more verbose but standard form (potentially with dg- or dot-namespaced attributes) for projects that need stronger compatibility. Your idea of aligning the names with the terminology we already use makes a lot of sense.
And thanks for pointing out the router example — you’re right, the docs only show the “all options” version. I’ll add a minimalist example alongside it so people can see the simple path too.
Appreciate the thoughtful feedback — this gives me a clear direction for making dagger.js easier to use while staying standards-compliant.
your positive responses make me wish i had an opportunity to build my next site with it. not ready for that yet, but hopefully when i get some time to work on my aurelia side project again, i can try making a second version using dagger.js.
actually in recent weeks i have been thinking about building simple sites with just vanilla js, but with some custom functions to enable binding and routing which is the most important stuff. but then i'll probably need iteration, click events, and a few other things, and when i saw dagger.js i realized that my own collection of features would pretty much get me to where you are now...
Thank you — that really means a lot to me. One of the best parts of sharing dagger.js has been hearing from people like you who’ve been down the same path and thought about building the same set of features. It makes me feel like the project is on the right track.
What you described with vanilla JS plus a few helpers is exactly how dagger.js started — I just wanted to save myself (and hopefully others) from having to reinvent the same binding and routing layer every time.
If you ever try it out on your side project, I’d be truly grateful to hear how it feels. Feedback from thoughtful developers like you is what helps me make it better.
Your post and comments definitely made me interested in trying it out! Usually I use as little JS as possible, but maybe I have a need for something soon, and then I might try your library/framework!
Really glad to hear that — thanks for the kind words! dagger.js is meant for exactly that use case: if you normally avoid heavy JS but occasionally need a bit of interactivity, you can drop in a <script> and use just what you need without a build step or big framework overhead.
If you give it a try, I’d love to hear how it works out for you. Feedback from people who prefer “as little JS as possible” is especially valuable.
Yeah, that’s a fair comparison — Dagger.js lives in a similar space to petite-vue / Alpine: no build step, HTML-first, sprinkle behaviors where you need them.
The main differences are:
pure declarative mode → there is zero API and third-party code & tool dependency.
Web Components first → you can wrap functionality as native Custom Elements and Dagger will happily coexist.
Directive model → simple +click, +load, etc., instead of reactivity syntax or x- attributes.
Distributed modules → small script modules you can drop in from a CDN when you need them.
So it’s playing the same role (buildless, quick internal tools / embeds), but with a slightly different philosophy: keep everything view-source-able and make WC interop a first-class story.
The reason programming is fun is because it represents a counterpoint to reality. If you have a bad relationship and you invest energy in it, focus your attention on debugging it, it's likely to get worse, not better. But a computer program (once you have enough experience and skill) gradually get better, in direct proportion to your efforts.
This makes programming a make-believe replacement for reality, one that represents many people's fantasy about how reality should work. It's like a drug, except that you can hide your addiction more easily -- maybe even get paid to be a programming addict.
reply