Compo is a JavaScript Web UI tiny library powering Web Components with a functional API and a Virtual DOM rendering.
You have to compo·se your compo·nents by enriching them with each feature through a central composing function. Markup and Style are considered as a feature you can add to your components.
npm install compo
yarn add compo
import {
html,
css,
createStore,
component,
withProp,
withStore,
withStyle,
withMarkup,
} from 'compo';
createStore((state, action) => {
switch (action.type) {
case 'ADD': return state + 1;
case 'SUB': return state - 1;
default: return state;
}
}, 0);
component(
'my-counter-label',
withProp('value'),
withStyle(({ value }) => css`
:host {
color: ${value < 1 ? 'red' : 'black'};
}
`,),
);
component(
'my-counter',
withStore(({ getState, dispatch }) => ({
counter: getState(),
add: () => dispatch({ type: 'ADD' }),
sub: () => dispatch({ type: 'SUB' }),
})),
withMarkup(({ counter, add, sub }) => html`
<div>
<my-counter-label value=${counter}>${counter}</my-counter-label>
<button onclick=${add}>+</button>
<button onclick=${sub}>-</button>
</div>
`),
);
Define a Custom Element with named name
and enhanced by each enhancers.
name
is directly passed to customElement.define()
so you have to follow Web Components constraints such as a -
in the name and only used once in the application.enhancers
are function taking in parameter a component class definition and returning a new one, most often my extending it. You can create your own but you can use all the with
prefixed enhancers provided in the framework.component(
'my-component',
withProp('my-prop')
);
Define an enhancer which will render the Markup
returned in the component and will re-render on every change detection.
You'll obtain an Markup
object by using the html
tagged template described bellow.
component(
'my-component',
withMarkup(() => html`<div>Hello World</div>`)
);
Define an enhancer which will add a style
block with the Style
returned and will update the style on every change detection.
The Style
object can be either a standard string
or an object using the css
tagged template described bellow.
component(
'my-component',
withStyle(() => css`:host { color: red; }`)
);
Define an enhancer which will instrument and trigger an update on modification on the component property name
.
component(
'my-component',
withProp('my-prop')
);
Define an enhancer which will add a name
property to the component with handler
returned to be used in the markup.
component(
'my-component',
withHandler(() => event => console.log('my handler', event))
)
Define an enhancer which will run the function in parameter when the component is connected corresponding to the Custom Element standard connectedCallback
function.
component(
'my-component',
withConnected(() => console.log('component connected'))
)
Define an enhancer which will run the function in parameter at every store updates and assign all return object properties to the component object.
The store must be created beforehand by using createStore
described bellow.
component(
"my-component",
withStore(({ getState, dispatch }) => {
myData: getState().my.data,
myAction: () => dispatch({ type: "MY_ACTION" })
})
)
ES2015 tagged template allowing to create DOM templates with rich interpolations.
html`
<my-component my-prop=${prop}>
${content}
</my-component>
`
Known limitation: you currently can't use serveral interpolations in a single DOM node or property.
ES2015 tagged template allowing to create CSS content.
To be perfectly honest it does absolutely nothing right now! Still reserving the API can be good and it triggers syntax highlighting in many editors.
css`
my-component {
color: red;
}
`
Initialize the internal store with the reducer in argument.
In contrary to Redux, you don't always need to get the Store
returned. It's automatically passed to the withStore
enhancer.
createStore((state, action) => {
switch (action.type) {
case 'ADD': return state + 1;
case 'SUB': return state - 1;
default: return state;
}
}, 0);
Allow the component to have a callback on every url changes.
component(
'my-component',
withRouteEvent((url) => console.log('new url', url))
)
Add a handlerName
handler in the component which allow to trigger a routing to the url in parameter.
component(
'my-component',
withRouteAction('navigate'),
withHandler(({ navigate }) => (event) => navigate("/my-route")),
)
compo-path
Built-in component allowing to insert a component depending on the current path.
path
: the path which trigger the component.component
: the Web Component to use.<my-container>
<compo-path path=${"/my-route-1"} component=${"my-component-1"}></compo-path>
<compo-path path=${"/my-route-2"} component=${"my-component-2"}></compo-path>
</my-container>
Most basic example exactly the same as above in this readme.
Try it in CodeSanbox: https://codesandbox.io/s/yv5y14o6pj
Advanced example implementing the popular 2048 game.
Try it in CodeSanbox: https://codesandbox.io/s/k55w33zvkv
Same as 2048 but with polyfill loaded to be tested on other browsers than Chrome
Strangely doesn't work yet on CodeSanbox
Basic routing example using the integrated router
It started with the exploration of the Web Components and Shadow DOM APIs and followed by the willing to use v-dom concepts in this contexts.
Based upon that foundations, the objective was to have a functional API like recompose to power Web Components.
Minimalism and staying close and bounded to the standards.
Compo is not transpiled to old JavaScript and really based upon Web Components so it only works out of the box on recent Chrome. It's also working quite well on Firefox 63.0 without any flag.
It's planned to have a compatibility build using polyfills.
Compo is MIT licensed. See LICENSE.