Skip to content

Repository files navigation

Tiny (~4kB), super-fast, refined, reactive, fractal, unidirectional, isomorphic, pro-standards, declarative, Just Works™ framework for frontend development. There's only one API:

importripplefrom'rijs.minimal'ripple(name)// getterripple(name,body)// setterripple.on('change',(name,change)=>{})

Get and set things in single store. A change event is emitted when something is updated, enabling reactive updates.


## Components

Define a component:

ripple('my-component',()=>{})

Use it on the page:

<my-component>

Ripple is agnostic to how you write your components, they should just be idempotent (a single render function). This is fine:

ripple('my-app',(node,data)=>node.innerHTML='Hello World')

Or using some DOM-diff helper:

ripple('my-app',(node,data)=>jsx(node)`<h1>Hello World</h1>`)

Or using once/D3 joins:

ripple('my-app',(node,data)=>{once(node)('h1',1).text('Hello World')})

For more info about writing idempotent components, see this spec.


## State/Data

The first parameter of the component is the node to update.

The second parameter contains all the state and data the component needs to render:

exportdefaultfunctioncomponent(node,data){ ... }

You can pass down data by adding the name of the resources to the data attribute:

<my-shopdata="stock">
exportdefaultfunctionshop({ stock }){ ... }

Declaring the data needed on a component is used to reactively rerender it when the data changes.

The other option is to explicitly pass down data to the component using the (D3) data binding:

once(node)('my-shop',{ stock })

If you want to just use DOM, you can invoke .draw() on a custom element to redraw it:

constshop=document.createElement('my-shop')document.body.appendChild(shop)shop.state={ stock }shop.draw()

## Defaults

You can set defaults using the ES6 syntax:

exportdefaultfunctionshop({ stock =[]}){ ... }

If you need to persist defaults on the component's state object, you can use a small helper function:

exportdefaultfunctionshop(state){conststock=defaults(state,'stock',[])}

## Updates

Local state

Whenever you need to update local state, just change the state and invoke a redraw (like a game loop):

exportdefaultfunctionshop(state,i,el){consto=once(el),{ counter =0}=stateo('span',1).text(counter)o('button',1).text('increment').on('click'd=>{state.counter++o.draw()})}

Global state

Whenever you need to update global state, you can simply compute the new value and register it again which will trigger an update:

ripple('stock',{apples: 10,oranges: 20,pomegranates: 30})

Or if you just want to change a part of the resource, use a functional operator to apply a finer-grained diff and trigger an update:

update('pomegranates',20)(ripple('stock'))// same as: set({ type: 'update', key: 'pomegranate', value: 20 })(ripple('stock'))

Using logs of atomic diffs combines the benefits of immutability with a saner way to synchronise state across a distributed environment.

You can also use the list of all relevant changes since the last render in your component via element.changes to make it more performant.


## Events

Dispatch an event on the root element to communicate changes to parents (node.dispatchEvent).


## Routing

Just invoke a redraw of your application when the route has changed:

exportfunctionapp(node,data){consto=once(node)o('h1',1).text('You are currently on: '+location.pathname)window.on('change',d=>node.draw())}

Decouter emitterifies window to give you the change event, go(url) for navigating, and sets location.params with current route parameters.


## Bundling

Ripple does not care how you load/bundle your resources. You only just need to register them at some point. This means you are free to use whatever tool chain you like:

// index.jsripple('my-app',require('./resources/my-app'))ripple('my-app.css',file('./resources/my-app.css'))ripple('somedata',require('./resources/data/some'))
$ browserify index.js > app.js
<scriptsrc="app.js"></script><my-app></my-app>

An application is just a component that composes other components, so you shouldn't need any other scripts.


## Folder Convention

I recommend using the folder convention: a resources directory, with a folder for each component, and a data folder for data resources.

resources
├── data
│ ├── stock.js
│ └── order.js
└── my-app
│ ├── my-app.js
│ ├── my-app.css
│ └── test.js
└── another-component
├── another-component.js
├── another-component.css
└── test.js

You can then use a helper script to automatically generate a single requireable index.js from a directory of resources.


## Debugging
  • Check ripple.resources for a snapshot of your application. Resources are in the tuple format{ name, body, headers }.

  • Check $0.state on an element to see the state object it was last rendered with or manipulate it.


## Middleware

By default the draw function just invokes the function on an element. You can extend this without any framework hooks using the explicit decorator pattern:

// in componentexportdefaultfunctioncomponent(node,data){middleware(d,i,el)}// around componentexportdefaultmiddleware(functioncomponent(node,data){})// for all componentsripple.draw=middleware(ripple.draw)

A couple of useful middleware included in this build are:

Needs

This middleware reads the needs header and applies the attributes onto the element. The component does not render until all dependencies are available. This is useful when a component needs to define its own dependencies.

exportdefault{name: 'my-component',body: function(){},headers: {needs: '[css=..][data=..]'}}

Helpers

This middleware makes the specified helper functions available from the resource (hidden properties). This is useful to co-locate all logic for each resource in one place.

exportdefault{name: 'stock',body: {},headers: {helpers: { addNewStock, removeStock }}}

Styling

Stylesheet(s) can be modularly applied to an element: This middlware simply reads the css attribute and inserts them in the shadow root or scopes them and adds to head:

ripple('some.css',`:host { background: red }`)
<head><style>my-shop { background: red }</style></head><my-shopcss="some.css">

## Fullstack

If you have a backend for your frontend, checkout rijs/fullstack which transparently adds a few more modules to synchronise state between client-server or for more docs.

You can also adjust your own framework by adding/removing modules.


## Flavours

dist/ripple.js provides ripple and also some small, useful, high power-to-weight ratio functions that enriches the language grammar. If you don't want the helper functions, use dist/ripple.pure.js. Add .min for prod. Minified and gzipped the sizes are ~12kB and ~4kB respectively.

About

Tiny (~4kB), super-fast, refined, reactive, fractal, unidirectional, isomorphic, pro-standards, declarative, Just Works™ framework for frontend development

Resources

Stars

9 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages