Show HN: Cami.js – A no build, web component based reactive framework (github.com)
Hi Everyone! My main motivation for making this was that I wanted something as powerful as Svelte or React but I wanted no build steps, no JSON API, and I wanted something as close to vanilla js as much as possible. I'm mainly a backend developer, and I wanted to simply return html with some interactive islands when I need to (whose components get 'hydrated' with by backend language's templates).
Some key ideas:
• It's centered around light dom web components • Uses a "reactive element", which uses observables for fine-grained reactivity • Rendering is done through lit-html's tagged templates • A pub/sub store • Easy immutability using immer (it powers the observable updates & also the reducers)
It's my first 'serious' library that I'm using in some work prototypes, and it's also my first 'real' open source project, so comments & feedback would be great!
35 comments
[ 3.5 ms ] story [ 75.7 ms ] threadDoes this support SSR for custom component ?
It doesn’t support SSR as it aims to be backend-agnostic (i.e. you can use python/ruby/haskell, and you can copy+paste the Cami module with no build step and you can start using it).
If you want SSR for the SEO benefits, I think it’s better to render the text-heavy parts as normal HTML for indexing with Google, and then mount the interactive parts with Cami / web components (i.e. “islands architecture”: https://www.patterns.dev/posts/islands-architecture)
Things get hairy in both, but redux pattern feels so ridiculously ceremonial all to effectively manage a huge global state object with a false sense of "purity".
Observables otoh say "fuck it, I'm mutating everything, do what you want with it".
```
// define observable
this.count = this.observable(0);
// getting
this.count.value;
// setting
this.count.update(value => value + 1);
```
But it also has a redux-like pattern where you can dispatch actions and register reducers with far less ceremony:
```
// define store
const todoStore = createStore({ todos: [], });
// register reducer / producer
todoStore.register('add', (store, payload) => { store.todos.push(payload); });
// subscribe component to store
this.todos = this.subscribe(todoStore, 'todos');
// dispatch
this.dispatch("add", newTodo);
```
It looks like it’s mutating, but both the reducers and update() uses immer* under the hood, so we still respect immutability under the hood.
Cami supports redux devtools so you can use that for time-travel debugging too.
—-
* https://github.com/immerjs/immer
Will the V8 JIT optimize the hash based function table loop into a constant integer array lookup?
Cami loses out on slots & style encapsulation, but you can style Cami components with normal / global css like it’s part of the normal dom. And since there’s no shadow dom overhead, it’s more performant and there is no FOUC if you load CSS in <head>.
Some feedback from first glance is “type inference” could be better, which would help prevent a lot of foot-guns. Consider the example code here:
Does this allow “count” to be inferred as a “number” type in the rest of the code? There might be a way to allow JS to easily infer the types, without requiring a build step…Question regarding interop with other web components: I don't see how to create reactive properties on elements. Can they receive data from parents via property bindings? Ie, could a Lit element pass data to a Cami element?
Unfortunately I haven't thought much yet about interoperability with other web components libraries like lit. I imagined folks would choose just one web component library over the other.
That said, you can initialize reactive properties(1), but property bindings won't work if there's a parent LitElement (as my reactive properties need to be called with either a .value method or an .update method for getting and setting respectively).
As of the moment, what's possible is interop with other cami elements using a store, and in a future version, i'm considering a richer event system for external javascript code to listen to.
---
(1) Initializing is possible with observerableAttr: https://github.com/kennyfrc/cami.js/blob/master/examples/008...
You can typecheck pure js files using JSDoc.
https://www.typescriptlang.org/docs/handbook/jsdoc-supported...
https://saasufy.com/
I built a whole chat app with authentication, blockchain and GitHub login with just 120 lines of HTML markup no front end or backend code (all the logic is abstracted away via generic, easy-to-use components provided by the platform).
You can also build apps which display filtered views by category or with text search with custom parameters and it does so efficiently with indexing and all updates in real time. Real time updates are delivered efficiently only to relevant components. Components automatically subscribe and unsubscribe to and from realtime changefeeds in an efficient way. Access control can be specified at the object/model level or on individual fields.
WebComponents give us so much, but just not quite enough to be usable on their own without a little bit more on top. Reactivity with observable store and templating with event binding are the big missing pieces.
[0]: https://github.com/frameable/el
Thanks for sharing.
[1]: https://lit.dev/docs/api/directives/#unsafeHTML
Example: https://api.rubyonrails.org/classes/ActionView/Helpers/Sanit...