xinjs v0.7.2
xinjs
xinjs.net | xinjs-ui | docs | github | npm | cdn | react-xinjs | discord
Path-based State for Web Apps
- simple, efficient observer pattern
- written in TypeScript
- lightweight
- works anywhere (browsers, node, bun, electron etc.)
If you want to build a web-application that's performant, robust, and maintainable,
xinjs lets you:
- implement your business logic however you like (or reuse existing code),
- build your UI with pure
Reactcomponents (usinguseXin) - and/or
web-components, - and neatly bind the state of your business objects to the user-interface directly.
In general, xinjs is able to accomplish the same or better compactness, expressiveness,
and simplicity as you get with highly-refined React-centric toolchains, but without transpilation,
domain-specific-languages, or other tricks that provide convenience at the cost of becoming locked-in
to React, a specific state-management system (which permeats your business logic), and UI framework.
Here's the usual codesandbox React Typescript boilerplate converted to xinjs.
The standard React Todo List Example
becomes shorter and simpler with xinjs and cleanly separates business logic from presentation. xinjs paths route data to/from UI elements, and events from the UI to methods, and those paths are exactly what you expect.
But xinjs lets you work with pure HTML components as cleanly—more cleanly—and efficiently than React toolchains let you work with JSX.
export default function App() {
return (
<div className="App">
<h1>Hello React</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}Becomes:
const { div, h1, h2 } = elements // exported from xinjs
export const App = () => div(
{ class: 'App' },
h1('Hello xinjs'),
h2('Start editing to see some magic happen!')
)Except this reusable component outputs native DOM nodes. No transpilation, spooky magic at a distance, or virtual DOM required. And it all works just as well with web-components. This is you get when you run App() in the console:
▼ <div class="App">
<h1>Hello xinjs</h1>
<h2>Start editing to see some magic happen!</h2>
</div>The ▼ is there to show that's DOM nodes, not HTML.
xinjs lets you lean into web-standards and native browser functionality while writing less code that's
easier to run, debug, deploy, and maintain. Bind data direct to standard input elements—without having
to fight their basic behavior—and now you're using native functionality with deep accessibility support
as opposed to whatever the folks who wrote the library you're using have gotten around to implementing.
Aside:
xinjswill also probably work perfectly well withAngular,Vue, et al, but I haven't bothered digging into it and don't want to deal withngZonestuff unless someone is paying me.
If you want to build your own web-components versus use something off-the-rack like
Shoelace, xinjs offers a Component base class that, along with
its elements and css libraries allows you to implement component views in pure Javascript
more compactly than with jsx (and without a virtual DOM).
import { Component, elements, css } from 'xinjs'
const { style, h1, slot } = elements
export class MyComponent extends Component {
styleNode = style(css({
h1: {
color: 'blue'
}
}))
content = [ h1('hello world'), slot() ]
}The difference is that web-components are drop-in replacements for standard HTML elements
and interoperate happily with one-another and other libraries, load asynchronously,
and are natively supported by all modern browsers.
What xinjs does
Observe Object State
xinjs tracks the state of objects you assign to it using paths allowing economical
and direct updates to application state.
import { xinProxy, observe } from 'xinjs'
const { app } = xinProxy({
app: {
prefs: {
darkmode: false
},
docs: [
{
id: 1234,
title: 'title',
body: 'markdown goes here'
}
]
}
})
observe('app.prefs.darkmode', () => {
document.body.classList.toggle('dark-mode', app.prefs.darkmode)
})
observe('app.docs', () => {
// render docs
})What does
xinProxydo, and what is aXinProxy?
xinProxyis syntax sugar for assigning something toxin(which is aXinProxyObject) and then getting it back out again.A
XinProxyis an ES Proxy wrapped around anobject(which in Javascript means anything that has aconstructorwhich in particular includesArrays,classinstances,functions and so on, but not "scalars" likenumbers,strings,booleans,null, andundefined)All you need to know about a
XinProxyis that it's Proxy wrapped around your original object that allows you to interact with the object normally, but which allowsxinjsto observe changes made to the wrapped object and tell interested parties about the changes.If you want to original object back you can just hold on to a reference or use
xinValue(someProxy)to unwrap it.
No Tax, No Packaging
xinjs does not modify the stuff you hand over to it… it just wraps objects
with a Proxy and then if you use xin to make changes to those objects,
xinjs will notify any interested observers.
Note xinProxy({foo: {...}}) is syntax sugar for xin.foo = {...}.
import { xinProxy, observe } from 'xinjs'
const { foo } = xinProxy({
foo: {
bar: 17
}
})
observe('foo.bar', v => {
console.log('foo.bar was changed to', xin.foo.bar)
})
foo.bar = 17 // does not trigger the observer
foo.bar = Math.PI // triggers the observerPaths are like JavaScript
xin is designed to behave just like a JavaScript Object. What you put
into it is what you get out of it:
import { xin, xinValue } from 'xinjs'
const foo = {bar: 'baz'}
xin.foo = foo
// xin.foo returns a Proxy wrapped around foo (without touching foo)
xinValue(xin.foo) === foo
// really, it's just the original object
xin.foo.bar = 'lurman'
foo.bar === 'lurman' // true
// seriously, it's just the original object
foo.bar = 'luhrman'
xin.foo.bar === 'luhrman' // true…but better!
It's very common to deal with arrays of objects that have unique id values,
so xinjs supports the idea of id-paths
import { xinProxy, xin } from 'xinjs
const { app } = xinProxy ({
app: {
list: [
{
id: '1234abcd',
text: 'hello world'
},
{
id: '5678efgh',
text: 'so long, redux'
}
]
}
})
console.log(app.list[0].text) // hello world
console.log(app.list['id=5678efgh']) // so long, redux
console.log(xin['app.list[id=1234abcd']) // hello worldTelling xin about changes using touch()
Sometimes you will modify an object behind xin's back (e.g. for efficiency).
When you want to trigger updates, simply touch the path.
import { xin, observe, touch } from 'xinjs'
const foo = { bar: 17 }
xin.foo = foo
observe('foo.bar', path => console.log(path, '->', xin[path])
xin.foo.bar = -2 // console will show: foo.bar -> -2
foo.bar = 100 // nothing happens
touch('foo.bar') // console will show: foo.bar -> 100CSS
xinjs includes utilities for working with css.
import {css, vars, initVars, darkMode} from 'xinjs'
const cssVars = {
textFont: 'sans-serif'
color: '#111'
}initVars() processes an object changing its keys from camelCase to --kabob-case:
initVars(cssVars) // emits { --text-font: "sans-serif", --color: "#111" }darkMode() processes an object, taking only the color properties and inverting their luminance values:
darkMode(cssVars) // emits { color: '#ededed' }
The vars simply converts its camelCase properties into css variable references
vars.fooBar // emits 'var(--foo-bar)'
calc(`${vars.width} + 2 * ${vars.spacing}`) // emits 'calc(var(--width) + 2 * var(--spacing))'css() processes an object, rendering it as CSS
css({
'.container': {
'position', 'relative'
}
}) // emits .container { position: relative; }Color
xinjs includes a powerful Color class for manipulating colors.
import {Color} from 'xinjs
const translucentBlue = new Color(0, 0, 255, 0.5) // r, g, b, a parameters
const postItBackground = Color.fromCss('#e7e79d')
const darkGrey = Color.fromHsl(0, 0, 0.2)The color objects have computed properties for rendering the color in different ways, making adjustments, blending colors, and so forth.
Hot Reload
One of the nice things about working with the React toolchain is hot reloading.
xinjs supports hot reloading (and not just in development!) via the hotReload()
function:
import {xin, hotReload} from 'xinjs'
xin.app = {
...
}
hotReload()hotReload stores serializable state managed by xin in localStorage and restores
it (by overlay) on reload. Because any functions (for example) won't be persisted,
simply call hotReload after initializing your app state and you're good to go.
hotReload accepts a test function (path => boolean) as a parameter.
Only top-level properties in xin that pass the test will be persisted.
To completely reset the app, run localStorage.clear() in the console.
Types
xinjs type-by-example has been
broken out into a separate standalone library. (Naturally it works very well with
xinjs but they are completely independent.)
Development Notes
You'll need to install bun and nodejs),
and then run npm install and bun install. bun is used because it's
fast and is a really nice test-runner.
To work interactively on the demo code, use bun start. This runs the demo
site on localhost.
To build everything run bun run make which builds production versions of the
demo site (in www) and the dist and cdn directories.
To create a local package (for experimenting with a build) run bun pack.
Parcel Occasionally Gets Screwed Up
- remove all the parcel transformer dependencies @parcel/*
- rm -rf node_modules
- run the update script
- npx parcel build (which restores needed parcel transformers)
Credits
xinjs is in essence a highly incompatible update to b8rjs with the goal
of removing cruft, supporting more use-cases, and eliminating functionality
that has been made redundant by improvements to the JavaScript language and
DOM APIs.
xinjs is being developed using bun.
bun is crazy fast (based on Webkit's JS engine, vs. V8), does a lot of stuff
natively, and runs TypeScript (with import and require) directly.
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
2 years ago
3 years ago
2 years ago
2 years ago
3 years ago
2 years ago
3 years ago
3 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago