moulinette
TypeScript icon, indicating that this package has built-in type declarations

1.2.6 • Public • Published

Moulinette

A thin wrapper around your functions to provide you with the tools to build a component system.

Installation

npm install moulinette

Creating a system

The createSystem function takes a builder function as parameter. A builder receives a moulinette function as its single argument and returns a system definition.

The moulinette parameter is a function that performs the computations registered with the .with() method. It's where the magic happens.

A system definition is basically what you want your system to be at its core and is the value that createSystem will return. So if for example you want your systems to behave like functions, your builder should return a function.

import createSystem from 'moulinette'
 
// define the general behavior of your system and what it actually builds
const System = createSystem(moulinette =>
  function System(props) {
    return moulinette(props)
  }
)
 
// call the generated System function
const computed = System({ test: true })

A builder function can return anything you want so you can get creative and use it to for example generate React components:

import React from 'react'
import createSystem from 'moulinette'
 
const System = createSystem(moulinette =>
  class System extends React.Component {
    render() {
      return <div {...moulinette(this.props)} />
    }
  }
)
 
const App = props => {
  return <System {...props} />
}

Setting defaults

A system component comes with a static method .with() that allows you to define a more specific version of the original system. If you pass it an object, it will define the default props of your specialized component.

// using the System defined in the very first example:
 
const A = System.with({ value: 'A' })
 
A({}) === { value: 'A' }
 
// passed props always have precedence against defaults
A({ value: 'B' }) === { value: 'B' }

Every component generated with .with() will also be a system component so it'll also have access to this method. Defaults will stack in the order they were added, meaning values defined the latest will overwrite any previous one.

const Value = System.with({ value: null, isValue: true })
const A = Value.with({ value: 'A' })
const B = Value.with({ value: 'B' })
 
Value({}) === { value: null, isValue: true }
A({}) === { value: 'A', isValue: true }
B({}) === { value: 'B', isValue: true }
 

Using a moulinette

A moulinette is a function that takes props as input and returns a modified version of those props. They are very useful if you want to control your component's final props depending on what was passed to it on render.

// take a and b out of the props
function addAtoB({ a = 0, b = 0, ...props }) {
  return {
    // do not forget to pass down the rest of props for the next moulinettes
    ...props,
 
    // compute value from a and b
    value: a + b
  }
}
 
const Value = System.with({ value: null, isValue: true })
const Add = Value.with(addAtoB)
 
Add({}) === { value: 0, isValue: true }
Add({ a: 10 }) === { value: 10, isValue: true }
Add({ a: 10, b: 100 }) === { value: 110, isValue: true }

For convenience, you can pass many parameters to the .with() method, it's equivalent to chaining the calls.

const Add = System.with({ value: null, isValue: true }, addAtoB)

When you add many moulinettes to a component, the last added one will always be executed first, its result will be passed to the one added before and so on until everything is processed. This means that in a given moulinette, you'll only be able to access props generated by moulinettes added after this one.

function multiply({ n = 1, value = 0, ...props }) {
  return {
    ...props,
 
    // use the value generated by add()
    value: n * value
  }
}
 
function add({ a = 0, b = 0, ...props }) {
  return {
    ...props,
 
    // compute value from a and b
    value: a + b
  }
}
 
const AddMul = System.with(multiply, add)
 
AddMul({}) === { value: 0 }
AddMul({ a: 10, b: 100 }) === { value: 110 }
AddMul({ a: 10, b: 100, n: 2 }) === { value: 220 }

As the orders matters, if in the example above add() would have been added before multiply(), the multiplication would happen first and then be overwritten by the addition.

Adding mixins

You can extend the static API of your system components to create helpers that encapsulate common operations made with your systems. A mixin is a function that takes the currently built component as parameter and mutates it by adding it static methods.

import { createSystem, merge } from 'moulinette'
 
function mixin(System) {
  // shorthand for setting the system default value
  System.value = value => System.with({ value })
 
  // shorthand to merge an object generated by a chunk function to the current props
  System.chunk = chunk => System.with(props => {
    const chunkProps = chunk(props)
    return merge(props, chunkProps)
  })
}
 
const System = createSystem(/* ... */).extend(mixin)
 
const A = System.value('A')
 
const Chunked = System.chunk(({ ok = false }) => ({
  isOK: ok,
  message: ok ? 'chunk is ok' : 'chunk is NOT ok'
}))
 
A({}) === { value: 'A' }
Chunked({}) === { isOK: false, message: 'chunk is NOT ok' }
Chunked({ ok: false }) === { ok: false, isOK: false, message: 'chunk is NOT ok' }
Chunked({ ok: true }) === { ok: true, isOK: true, message: 'chunk is ok' }

Readme

Keywords

none

Package Sidebar

Install

npm i moulinette

Weekly Downloads

0

Version

1.2.6

License

none

Unpacked Size

11.8 kB

Total Files

7

Last publish

Collaborators

  • jflx