Comparing version 0.0.2 to 0.0.3
@@ -12,6 +12,6 @@ { | ||
"dependencies": { | ||
"typescript": "beta", | ||
"snabbdom": "0.4.0", | ||
"typescript": "1.8.7", | ||
"dompteuse": "0.0.3", | ||
"abyssa": "7.2.7", | ||
"fluxx": "0.13.0", | ||
"fluxx": "0.14.1", | ||
"immupdate": "0.2.0", | ||
@@ -18,0 +18,0 @@ "gsap": "1.18.2" |
@@ -1,4 +0,3 @@ | ||
import h = require('snabbdom/h'); | ||
import { api as router } from 'abyssa'; | ||
import { component } from 'dompteuse'; | ||
import { component, h } from 'dompteuse'; | ||
@@ -5,0 +4,0 @@ import { State as GlobalState } from './store'; |
@@ -1,4 +0,3 @@ | ||
import h = require('snabbdom/h'); | ||
import { api as router } from 'abyssa'; | ||
import { component } from 'dompteuse'; | ||
import { component, h } from 'dompteuse'; | ||
@@ -5,0 +4,0 @@ import { contentAnimation } from './animation'; |
@@ -1,3 +0,2 @@ | ||
import h = require('snabbdom/h'); | ||
import { component } from 'dompteuse'; | ||
import { component, h } from 'dompteuse'; | ||
@@ -4,0 +3,0 @@ import { contentAnimation } from './animation'; |
@@ -6,3 +6,2 @@ import './logger'; | ||
import patch from './snabbdom'; | ||
import app from './app'; | ||
@@ -26,2 +25,2 @@ import { routeChanged } from './action'; | ||
startApp({ app, store, patch, elm: document.body }); | ||
startApp({ app, store, elm: document.body }); |
@@ -1,5 +0,4 @@ | ||
import h = require('snabbdom/h'); | ||
import update from 'immupdate'; | ||
import { LocalStore, Action, NoArgAction } from 'fluxx'; | ||
import { component } from 'dompteuse'; | ||
import { component, h } from 'dompteuse'; | ||
@@ -6,0 +5,0 @@ import { contentAnimation } from './animation'; |
@@ -21,12 +21,9 @@ { | ||
"src/gsap.ts", | ||
"src/Index.ts", | ||
"src/index.ts", | ||
"src/logger.ts", | ||
"src/main.ts", | ||
"src/red.ts", | ||
"src/snabbdom.ts", | ||
"src/store.ts", | ||
"typings/gsap.d.ts", | ||
"typings/snabbdom/h.d.ts", | ||
"typings/snabbdom/snabbdom.d.ts" | ||
"typings/gsap.d.ts" | ||
] | ||
} |
@@ -6,4 +6,3 @@ | ||
export function startApp<S>(options: { | ||
app: () => any; // TODO: type this as a vnode | ||
patch: any; // TODO: Reuse snabbdom's patch type | ||
app: Vnode; | ||
store: GlobalStore<S>; | ||
@@ -26,8 +25,44 @@ elm: HTMLElement; | ||
pullState?: <S>(state: S) => PS; | ||
render: (options: RenderOptions<P, PS, LS, AS>) => any; // TODO: type this as a vnode | ||
}): any; // TODO: type this as a vnode | ||
render: (options: RenderOptions<P, PS, LS, AS>) => Vnode; | ||
}): Vnode; | ||
export var Render: { | ||
log: boolean; | ||
} | ||
interface Vnode { | ||
sel: string; | ||
data: Object; | ||
children?: Array<Vnode>; | ||
text?: string; | ||
elm?: HTMLElement; | ||
key?: string; | ||
} | ||
type Node = Vnode | string; | ||
interface Hooks { | ||
pre?: () => void; | ||
init?: (node: Vnode) => void; | ||
create?: (emptyNode: any, node: Vnode) => void; | ||
insert?: (node: Vnode) => void; | ||
prepatch?: (oldVnode: Vnode, node: Vnode) => void; | ||
update?: (oldVnode: Vnode, node: Vnode) => void; | ||
postpatch?: (oldVnode: Vnode, node: Vnode) => void; | ||
destroy?: (node: Vnode) => void; | ||
remove?: (node: Vnode, cb: () => void) => void; | ||
post?: () => void; | ||
} | ||
type VnodeData = { key?: string } & ( | ||
{ class: {} } | | ||
{ attrs: {} } | | ||
{ on: {} } | | ||
{ props: {} } | | ||
{ style: {} } | | ||
{ hook: Hooks } | ||
); | ||
export function h(sel: string): Vnode; | ||
export function h(sel: string, dataOrChildren: VnodeData | Array<Node> | string): Vnode; | ||
export function h(sel: string, data: VnodeData, children: Array<Node> | string): Vnode; |
'use strict'; | ||
exports.__esModule = true; | ||
exports.Render = exports.startApp = exports.component = undefined; | ||
exports.h = exports.Render = exports.startApp = exports.component = undefined; | ||
var _snabbdom = require('snabbdom'); | ||
var _h = require('snabbdom/h'); | ||
var _h2 = _interopRequireDefault(_h); | ||
var _render = require('./render'); | ||
@@ -18,5 +24,6 @@ | ||
var app = _ref.app; | ||
var patch = _ref.patch; | ||
var elm = _ref.elm; | ||
var patch = (0, _snabbdom.init)([require('snabbdom/modules/class'), require('snabbdom/modules/props'), require('snabbdom/modules/attributes'), require('snabbdom/modules/eventlisteners'), require('snabbdom/modules/style')]); | ||
_render2.default.patch = patch; | ||
@@ -33,2 +40,3 @@ | ||
exports.startApp = startApp; | ||
exports.Render = _render2.default; | ||
exports.Render = _render2.default; | ||
exports.h = _h2.default; |
{ | ||
"name": "dompteuse", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"main": "lib/dompteuse", | ||
@@ -5,0 +5,0 @@ "typings": "lib/dompteuse.d.ts", |
104
README.md
# dompteuse | ||
Fast Virtual Dom with Reactive updating. | ||
![© DC Comics](http://i171.photobucket.com/albums/u320/boubiyeah/Original_Catwoman_Design_zpsokgquwmu.jpg) | ||
Work in progress. | ||
Fast Virtual DOM with Reactive updating. | ||
TODO: | ||
- Fast (thanks to [snabbdom](https://github.com/paldepind/snabbdom), subtree redraw optimizations and RAF) | ||
- Has the same programming model for global app state and components' local state | ||
- Flux-like data flow. Gentle learning curve and more escape hatches compared to most approaches based on streams. | ||
- No JS `class` / `this` nonsense | ||
- Tiny size in KB | ||
- Comes with useful logs | ||
- Very typescript friendly (Check the [example](https://github.com/AlexGalays/dompteuse/tree/master/example/src)) | ||
- Documentation | ||
- Better typescript typings, generally increase type safety | ||
# Componentization | ||
`dompteuse` brings the concept of encapsulated components to pure functional virtual dom. | ||
At a high level, these are the different kinds of components and nodes you may compose in a typical application: | ||
## Virtual nodes | ||
Most of your application will still be made up of plain old virtual dom nodes without any internal state. | ||
```javascript | ||
import h from ('snabbdom/h'); | ||
function button(props) { | ||
return h('button', props); | ||
} | ||
``` | ||
## Stateless component | ||
Pulls state from the global store then pass props derived from that state to its children. | ||
These components are optimized as they only redraw if the pulled state changed. | ||
It also remove the cumbersome act of passing props several layer down, so it's easier to maintain/refactor. | ||
Typically, you use stateless components at every url route / sub-route level or as an optimization | ||
for deeply nested components that need a piece of data its parents don't care about. | ||
```javascript | ||
import h from ('snabbdom/h'); | ||
import { component } from 'dompteuse'; | ||
export default function() { | ||
return component({ | ||
key: 'users', | ||
pullState, | ||
render | ||
}); | ||
}; | ||
function pullState(state) { | ||
return { users: state.users }; | ||
} | ||
function render({ users }) { | ||
return h('ul', users.map(user => h('li', user.name))); | ||
} | ||
``` | ||
## Stateful components | ||
Maintains a state locally by using a transient fluxx store. While keeping state in the global store is nice | ||
to synchronize multiple components together, keeping everything in the global store is a maintenance hazard and leads | ||
to a lot of code repetition. You typically want to keep very transient state as local as possible so that it remains encapsulated and do not leak up (ex: Whether a select dropdown is opened, a component has focus, which grid row is highlighted, basically any state that resets if the user navigate away then come back) | ||
```javascript | ||
import h from ('snabbdom/h'); | ||
import update from 'immupdate'; | ||
import { component } from 'dompteuse'; | ||
import { LocalStore, Action } from 'fluxx'; | ||
export default function(props) { | ||
return component({ | ||
key: 'select', | ||
localStore, | ||
props, | ||
render | ||
}); | ||
}; | ||
function localStore(props) { | ||
const initialState = { opened: props.openedByDefault }; | ||
const actions = { | ||
toggle: Action('toggle') | ||
}; | ||
const store = LocalStore(initialState, on => { | ||
on(actions.toggle, state => update(state, { opened: !state.opened })) | ||
}); | ||
return { store, actions }; | ||
} | ||
function render({ props, localState, actions }) { | ||
const { opened } = localState; | ||
return h('div.select', { class: { opened }, on: { click: actions.toggle } }); | ||
} | ||
``` | ||
Components can also be stateful AND pull state from the global store. Ex: The global store maintains | ||
a cached list of all users, while the local store knows which filter is currently active for this particular screen. |
import { init } from 'snabbdom'; | ||
import h from 'snabbdom/h'; | ||
import Render from './render'; | ||
@@ -6,3 +8,11 @@ import component from './component'; | ||
function startApp({ app, patch, elm }) { | ||
function startApp({ app, elm }) { | ||
const patch = init([ | ||
require('snabbdom/modules/class'), | ||
require('snabbdom/modules/props'), | ||
require('snabbdom/modules/attributes'), | ||
require('snabbdom/modules/eventlisteners'), | ||
require('snabbdom/modules/style') | ||
]); | ||
Render.patch = patch; | ||
@@ -20,3 +30,4 @@ | ||
startApp, | ||
Render | ||
Render, | ||
h | ||
}; |
@@ -6,4 +6,3 @@ | ||
export function startApp<S>(options: { | ||
app: () => any; // TODO: type this as a vnode | ||
patch: any; // TODO: Reuse snabbdom's patch type | ||
app: Vnode; | ||
store: GlobalStore<S>; | ||
@@ -26,8 +25,46 @@ elm: HTMLElement; | ||
pullState?: <S>(state: S) => PS; | ||
render: (options: RenderOptions<P, PS, LS, AS>) => any; // TODO: type this as a vnode | ||
}): any; // TODO: type this as a vnode | ||
render: (options: RenderOptions<P, PS, LS, AS>) => Vnode; | ||
}): Vnode; | ||
export var Render: { | ||
log: boolean; | ||
} | ||
interface Vnode { | ||
sel: string; | ||
data: VnodeData; | ||
children?: Array<Vnode>; | ||
text?: string; | ||
elm?: HTMLElement; | ||
key?: string; | ||
} | ||
type Node = Vnode | string; | ||
interface Hooks { | ||
pre?: () => void; | ||
init?: (node: Vnode) => void; | ||
create?: (emptyNode: any, node: Vnode) => void; | ||
insert?: (node: Vnode) => void; | ||
prepatch?: (oldVnode: Vnode, node: Vnode) => void; | ||
update?: (oldVnode: Vnode, node: Vnode) => void; | ||
postpatch?: (oldVnode: Vnode, node: Vnode) => void; | ||
destroy?: (node: Vnode) => void; | ||
remove?: (node: Vnode, cb: () => void) => void; | ||
post?: () => void; | ||
} | ||
// This weird union type is here to differentiate from the Array children type. | ||
// If an empty object was allowed, [vnode, wrongValue] could be assigned to it. | ||
type VnodeData = { key?: string } & ( | ||
{ class: {} } | | ||
{ attrs: {} } | | ||
{ on: {} } | | ||
{ props: {} } | | ||
{ style: {} } | | ||
{ hook: Hooks } | ||
); | ||
export function h(sel: string): Vnode; | ||
export function h(sel: string, dataOrChildren: VnodeData | Array<Node> | string): Vnode; | ||
export function h(sel: string, data: VnodeData, children: Array<Node> | string): Vnode; |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
877991
7721
105
0
36