@muze-labs/simplyflow
v0.10.2
Published
Reactive browser applications with signals, effects, data binding and an app layer.
Readme
SimplyFlow
SimplyFlow is an experimental browser library for building small reactive web applications with ordinary HTML and ordinary JavaScript data.
The intended beginner-facing API is:
import { app } from '@muze-labs/simplyflow'
const counter = app({
container: document.getElementById('counter'),
data: {
count: 0
},
commands: {
add1() {
this.data.count++
}
}
})<div id="counter">
<button data-simply-command="add1">+</button>
<span data-simply-field="count"></span>
</div>For editable values, use data-simply-edit:
<input data-simply-edit="name">
<span data-simply-field="name"></span>The page updates automatically whenever app.data changes. Use data-simply-edit on form fields when the user should be able to edit a value directly. Text inputs, textareas and selects edit string values; checkboxes edit booleans or toggle values in arrays; radio buttons edit the selected value.
Buttons inside list templates can pass the current item or one of its fields to a command:
<ul data-simply-list="todos">
<template>
<li>
<button data-simply-command="removeTodo" data-simply-value=":value.id">Remove</button>
<span data-simply-field="text"></span>
</li>
</template>
</ul>Custom top-level options become app properties, so you can add services without extra ceremony:
const contacts = simply.app({
data: { contacts: [] },
api: metro.jsonApi('/api/'),
actions: {
async loadContacts() {
this.data.contacts = await this.api.get('contacts.json')
}
}
})If an unknown option looks like a typo of a built-in app option, SimplyFlow logs a warning, but still adds the option to the app.
Reusable element behavior can be attached with data-simply-behavior:
<div data-simply-behavior="tabs"></div>const page = simply.app({
data: {},
behaviors: {
tabs(element) {
// Set up the tabs element.
}
}
})HTML fragments can be included inside an app container without a build step:
<link rel="simply-include" href="header.html">The include observer is scoped to the app and stops when app.destroy() is called.
Keyboard shortcuts can be added with the shortcuts option:
const notes = simply.app({
data: {},
shortcuts: {
'Control+s'() {
this.actions.save()
}
}
})Install
npm install @muze-labs/simplyflowor using Git:
git clone https://github.com/muze-labs/simplyflow.gitBrowser bundle
<script src="https://cdn.jsdelivr.net/npm/@muze-labs/simplyflow/dist/simply.flow.js"></script>Then use the beginner-facing simply.app() API:
const counter = simply.app({
data: { count: 0 },
commands: {
add1() {
this.data.count++
}
}
})The tutorials focus on simply.app(), but the browser global also exposes the lower-level APIs directly for projects that use script tags and do not want a build step:
const data = simply.signal({ title: 'Hello' })
simply.bind({ root: data })
const table = simply.model({ data: [] })
table.addEffect(simply.model.sort({ property: 'title' }))The browser bundle also intentionally provides global html and css template tags. They return strings, but many code editors recognize these tag names and provide syntax highlighting inside template literals:
const page = simply.app({
templates: {
card: html`<article data-simply-field=":value.title"></article>`
},
styles: {
card: css`.selected { font-weight: bold; }`
}
})For no-build module examples that use an import map, prefer mapping the complete browser bundle instead of every split workspace package:
<script type="importmap">
{
"imports": {
"@muze-labs/simplyflow": "https://cdn.jsdelivr.net/npm/@muze-labs/simplyflow/dist/simply.flow.js",
"@muze-nl/metro": "https://cdn.jsdelivr.net/npm/@muze-nl/[email protected]/src/everything.mjs"
}
}
</script>
<script type="module">
import '@muze-labs/simplyflow'
import '@muze-nl/metro'
const hnpwa = simply.app({
data: { items: [] }
})
</script>This is the recommended beginner/example path: import the complete SimplyFlow bundle, plus any external library the example directly uses. Advanced users can still use the split packages or subpath imports below when they want smaller, tree-shakeable bundles with a build step.
Module imports are still available when you prefer explicit imports:
import { signal, effect, batch } from '@muze-labs/simplyflow/state'
import { bind } from '@muze-labs/simplyflow/bind'
import { model, paging, sort, filter, columns } from '@muze-labs/simplyflow/model'
const data = signal({ title: 'Hello' })
bind({ root: data })Package structure and tree-shakeable imports
SimplyFlow is maintained as a small monorepo. The main @muze-labs/simplyflow package is a convenience package for beginners and script-tag users. The implementation is split into smaller ESM packages that can also be imported directly:
import { signal, effect } from '@muze-labs/simplyflow-state'
import { bind } from '@muze-labs/simplyflow-bind'
import { model } from '@muze-labs/simplyflow-model'
import { app } from '@muze-labs/simplyflow-app'For projects that prefer one dependency, the main package keeps stable subpath exports:
import { signal, effect } from '@muze-labs/simplyflow/state'
import { bind } from '@muze-labs/simplyflow/bind'The state, bind, model, and app packages are pure ESM and marked as side-effect-free so bundlers can tree-shake unused exports. The main @muze-labs/simplyflow entry point still intentionally initializes the browser global API and registers <simply-render>, so use subpath or direct package imports when minimum bundle size matters.
Documentation
Or check the examples for more information.
Development
This repository is a private npm workspace root. The published main package lives in packages/simplyflow; the smaller layer packages live in packages/state, packages/bind, packages/model, and packages/app.
npm install
npm test
npm run buildWhen publishing the split packages, publish the dependency packages first (state, then bind/model, then app, then simplyflow) so the main package can resolve its package dependencies.
License
MIT © Muze.nl
Contributions
Contributions are welcome, but make sure that all code is MIT licensed. If you want to send a merge request, please make sure that there is a ticket that shows the bug/feature and reference it. If you find any problem, please do file a ticket, but you should not expect a timely resolution. This project is still very experimental, don't use it in production unless you are ready to fix problems yourself.
