https://github.com/kennyfrc/cami.js
Skip to content Toggle navigation
Sign up
* Product
+
Actions
Automate any workflow
+
Packages
Host and manage packages
+
Security
Find and fix vulnerabilities
+
Codespaces
Instant dev environments
+
Copilot
Write better code with AI
+
Code review
Manage code changes
+
Issues
Plan and track work
+
Discussions
Collaborate outside of code
Explore
+ All features
+ Documentation
+ GitHub Skills
+ Blog
* Solutions
For
+ Enterprise
+ Teams
+ Startups
+ Education
By Solution
+ CI/CD & Automation
+ DevOps
+ DevSecOps
Resources
+ Learning Pathways
+ White papers, Ebooks, Webinars
+ Customer Stories
+ Partners
* Open Source
+
GitHub Sponsors
Fund open source developers
+
The ReadME Project
GitHub community articles
Repositories
+ Topics
+ Trending
+ Collections
* Pricing
Search or jump to...
Search code, repositories, users, issues, pull requests...
Search
[ ]
Clear
Search syntax tips
Provide feedback
We read every piece of feedback, and take your input very seriously.
[ ] [ ] Include my email address so I can be
contacted
Cancel Submit feedback
Saved searches
Use saved searches to filter your results more quickly
Name [ ]
Query [ ]
To see all available qualifiers, see our documentation.
Cancel Create saved search
Sign in
Sign up
You signed in with another tab or window. Reload to refresh your
session. You signed out in another tab or window. Reload to refresh
your session. You switched accounts on another tab or window. Reload
to refresh your session.
Dismiss alert
{{ message }}
kennyfrc / cami.js Public
* Notifications
* Fork 0
* Star 19
A minimalist & flexible toolkit for interactive islands & state
management in hypermedia-driven web applications.
License
MIT license
19 stars 0 forks Activity
Star
Notifications
* Code
* Issues 0
* Pull requests 0
* Discussions
* Actions
* Security
* Insights
More
* Code
* Issues
* Pull requests
* Discussions
* Actions
* Security
* Insights
kennyfrc/cami.js
This commit does not belong to any branch on this repository, and may
belong to a fork outside of the repository.
master
Switch branches/tags
[ ]
Branches Tags
Could not load branches
Nothing to show
{{ refName }} default View all branches
Could not load tags
Nothing to show
{{ refName }} default
View all tags
Name already in use
A tag already exists with the provided branch name. Many Git commands
accept both tag and branch names, so creating this branch may cause
unexpected behavior. Are you sure you want to create this branch?
Cancel Create
1 branch 4 tags
Code
* Local
* Codespaces
*
Clone
HTTPS GitHub CLI
[https://github.com/k]
Use Git or checkout with SVN using the web URL.
[gh repo clone kennyf]
Work fast with our official CLI. Learn more about the CLI.
* Open with GitHub Desktop
* Download ZIP
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
@kennyfrc
kennyfrc add builds
...
4998b86 Nov 4, 2023
add builds
4998b86
Git stats
* 65 commits
Files
Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
build
add builds
November 5, 2023 02:02
examples
add builds
November 5, 2023 02:02
src
jsdocs update
November 5, 2023 00:04
.gitignore
add builds
November 5, 2023 02:02
LICENSE
Create LICENSE
October 27, 2023 01:56
README.md
tweak: readme
November 5, 2023 01:29
build_examples.sh
docs update
November 4, 2023 23:57
build_readme.sh
tweak: readme
November 4, 2023 23:58
bun.lockb
docs update
November 4, 2023 23:57
gzip.js
gzip script
November 3, 2023 10:59
jsdoc.json
jsdocs: generate /docs
October 27, 2023 15:19
package.json
docs update
November 4, 2023 23:57
tsconfig.json
v0.0.3
October 27, 2023 01:41
View code
[ ]
[?] Cami.js Motivation Key Features: Who is this for? Get Started &
View Examples Key Concepts / API ReactiveElement Class, Observable
Objects, and HTML Tagged Templates Basics of Observables & Templates
Basics of Computed Properties & Effects createStore(initialState)
html Examples Dev Usage Install Dependencies Building Typechecking
Testing Prior Art Why "Cami"? Roadmap
README.md
[?] Cami.js
[?][?] Expect API changes until v1.0.0 [?][?]
Current version: 0.0.9. Bundle Size: 8kb minified & gzipped.
A minimalist & flexible toolkit for interactive islands & state
management in hypermedia-driven web applications.
Motivation
I wanted a minimalist javascript library that has no build steps,
great debuggability, and didn't take over my front-end.
My workflow is simple: I want to start any application with normal
HTML/CSS, and if there were fragments or islands that needed to be
interactive (such as dashboards & calculators), I needed a powerful
enough library that I could easily drop in without rewriting my whole
front-end. Unfortunately, the latter is the case for the majority of
javascript libraries out there.
That said, I like the idea of declarative templates, uni-directional
data flow, time-travel debugging, and fine-grained reactivity. But I
wanted no build steps (or at least make 'no build' the default). So I
created Cami.
Key Features:
* Reactive Web Components: We suggest to start any web application
with normal HTML/CSS, then add interactive islands with Cami's
reactive web components. Uses fine-grained reactivity with
observables, computed properties, and effects. Also supports for
deeply nested updates. Uses the Light DOM instead of Shadow DOM.
* Tagged Templates: Declarative templates with lit-html. Supports
event handling, attribute binding, composability, caching, and
expressions.
* Store / State Management: When you have multiple islands, you can
use a singleton store to share state between them, and it acts as
a single source of truth for your application state. Redux
DevTools compatible.
* Easy Immutable Updates: Uses Immer under the hood, so you can
update your state immutably without excessive boilerplate.
* Anti-Features: You can't be everything to everybody. So we made
some hard choices: No Build Steps, No Client-Side Router, No JSX,
No Shadow DOM. We want you to build an MPA, with mainly HTML/CSS,
and return HTML responses instead of JSON. Then add interactivity
as needed.
Who is this for?
* Lean Teams or Solo Devs: If you're building a small to
medium-sized application, I built Cami with that in mind. You can
start with ReactiveElement, and once you need to share state
between components, you can add our store. It's a great choice
for rich data tables, dashboards, calculators, and other
interactive islands. If you're working with large applications
with large teams, you may want to consider other frameworks.
* Developers of Multi-Page Applications: For folks who have an
existing server-rendered application, you can use Cami to add
interactivactivity to your application, along with other
MPA-oriented libraries like HTMX, Unpoly, Turbo, or TwinSpark.
Get Started & View Examples
To see some examples, just do the following:
git clone git@github.com:kennyfrc/cami.js.git
cd cami.js
bun install --global serve
bunx serve
Open http://localhost:3000 in your browser, then navigate to the
examples folder. In the examples folder, you will find a series of
examples that illustrate the key concepts of Cami.js. These examples
are numbered & ordered by complexity.
Key Concepts / API
ReactiveElement Class, Observable Objects, and HTML Tagged Templates
ReactiveElement is a class that extends HTMLElement to create
reactive web components. These components can automatically update
their view (the template) when their state changes.
Automatic updates are done by observables. An observable is an object
that can be observed for state changes, and when it changes, it
triggers an effect (a function that runs in response to changes in
observables).
Cami's observables have the following characteristics:
* It has a value property that holds the current value of the
observable.
* It has an update method that allows you to update the value of
the observable.
When you update the value of an observable, it will automatically
trigger a re-render of the component's html tagged template. This is
what makes the component reactive.
Let's illustrate these three concepts with an example. Here's a
simple counter component:
Count: ${this.count.value}
`; } In this example, html is a function that gets called with the template literal. It processes the template literal and creates a template instance that can be efficiently updated and rendered. The ${} syntax inside the template literal is used to embed JavaScript expressions. These expressions can be variables, properties, or even functions. In the example above, $ {this.count.value} will be replaced with the current value of the count observable, and ${() => this.count.update(value => value + 1)} is a function that increments the count when the button is clicked. The @click syntax is used to attach event listeners to elements. In this case, a click event listener is attached to the button element. Basics of Computed Properties & Effects Computed Properties: Computed properties are a powerful feature in Cami.js that allow you to create properties that are derived from other observables. These properties automatically update whenever their dependencies change. This is particularly useful for calculations that depend on one or more parts of the state. For instance, in the CounterElement example from _001_counter.html, a computed property countSquared is defined as the square of the count observable: this.countSquared = this.computed(() => this.count.value * this.count.value); In this case, countSquared will always hold the square of the current count value, and will automatically update whenever count changes. This is ideal for calculations like this, but can also be used for other derived values such as total price in a shopping cart (based on quantities and individual prices), or a boolean flag indicating if a form is valid (based on individual field validations). Effects: Effects in Cami.js are functions that run in response to changes in observable properties. They are a great way to handle side effects in your application, such as integrating with non-reactive components, emitting custom events, or logging/debugging. For example, in the CounterElement example, an effect is defined to log the current count and its square whenever either of them changes: this.effect(() => console.log(`Count: ${this.count.value} & Count Squared: ${this.countSquared.value}`)); This effect will run whenever count or countSquared changes, logging the new values to the console. This can be particularly useful for debugging. Effects can also be used to emit custom events after specific state changes. For instance, you could emit a custom event whenever the count reaches a certain value. Here's a great essay on this topic: Hypermedia-Friendly Scripting this.effect(() => { if (this.count.value === 10) { this.dispatchEvent(new CustomEvent('count-reached-ten', { detail: this.count.value })); } }); In this example, a 'count-reached-ten' event is dispatched whenever the count reaches 10. This can be useful for integrating with non-reactive parts of your application or for triggering specific actions in response to state changes like with ReactiveElement Methods: * observable(initialValue): Defines an observable property with an initial value. Returns an object with value property and update method. * subscribe(key, store): Subscribes to a store and links it to an observable property. Returns the observable. * computed(computeFn): Defines a computed property that depends on other observables. Returns an object with a value getter. * effect(effectFn): Defines an effect that is triggered when an observable changes. The effect function can optionally return a cleanup function. * dispatch(action, payload): Dispatches an action to the store. * template(): A method that should be implemented to return the template to be rendered. * connectedCallback(): Lifecycle method called each time the element is added to the document. Sets up initial state and triggers initial rendering. * disconnectedCallback(): Lifecycle method called each time the element is removed from the document. Cleans up listeners and effects. * adoptedCallback(): Lifecycle method called each time the element is moved to a new document. Can be used to reset or reinitialize internal state. * attributeChangedCallback(name, oldValue, newValue): Lifecycle method called when an attribute of the element is added, removed, or changed. Useful for reacting to changes in attributes. * static get observedAttributes(): Static getter that returns an array of attribute names to monitor for changes. Used in conjunction with attributeChangedCallback. Note: Lifecycle methods are part of the Light DOM. We do not implement the Shadow DOM in this library. While Shadow DOM provides style and markup encapsulation, there are drawbacks if we want this library to interoperate with other libs. createStore(initialState) The createStore function is a fundamental part of Cami.js. It creates a new store with the provided initial state. The store is a singleton, meaning that if it has already been created, the existing instance will be returned. This store is a central place where all the state of your application lives. It's like a data warehouse where different components of your application can communicate and share data. This concept is particularly useful in scenarios where multiple components need to share and manipulate the same state. A classic example of this is a shopping cart in an e-commerce application, where various components like product listing, cart summary, and checkout need access to the same cart state. The store follows a flavor of the Flux architecture, which promotes unidirectional data flow. The cycle goes as follows: dispatch an action -> update the store -> reflect changes in the view -> dispatch another action. In addition, as we adhere to many of Redux's principles, our store is compatible with the Redux DevTools Chrome extension, which allows for time-travel debugging. Parameters: * initialState (Object): The initial state of the store. This is the starting point of your application state and can be any valid JavaScript object. Returns: A store object with the following methods: * state: The current state of the store. It represents the current snapshot of your application state. * subscribe(listener): Adds a listener to the store. This listener is a function that gets called whenever the state changes. It also returns an unsubscribe function to stop listening to state changes. * register(action, reducer): Adds a reducer to the store. A reducer is a function that knows how to update the state based on an action. * dispatch(action, payload): Adds an action to the dispatch queue and starts processing if not already doing so. An action is a description of what happened, and the payload is the data associated with this action. * use(middleware): Adds a middleware to the store. Middleware is a way to extend the store's capabilities and handle asynchronous actions or side effects. html The html function in Cami.js is a tagged template literal, based on lit-html, that allows for the creation of declarative templates. It provides several powerful features that make it effective in the context of Cami.js: 1. Event Handling: It supports event handling with directives like @click, which can be used to bind DOM events to methods in your components. For example: html`` In this example, the increment method is called when the button is clicked. 2. Attribute Binding: It allows for attribute binding, which means you can dynamically set the attributes of your HTML elements based on your component's state. For example: html`` In this example, the class of the div is dynamically set based on the isActive property of the component. 3. Composability: It supports composability, which means you can easily include one template inside another. For example: html`${this.headerTemplate()}