├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── .zuul.yml ├── README.md ├── book.json ├── docs ├── README.md ├── advanced │ ├── context.md │ ├── dispatcher.md │ ├── hmr.md │ ├── index.md │ ├── keys.md │ ├── lifecycle.md │ └── model.md ├── api │ ├── create-app.md │ ├── diff.md │ ├── dom.md │ ├── element.md │ ├── index.md │ ├── string.md │ └── vnode.md ├── basics │ ├── components.md │ ├── elements.md │ ├── events.md │ ├── getting-started.md │ ├── index.md │ ├── jsx.md │ └── migrating.md ├── contributing.md ├── motivation.md └── tips │ └── innerhtml.md ├── examples └── basic │ ├── app.js │ └── index.js ├── package.json ├── src ├── app │ └── index.js ├── diff │ └── index.js ├── dom │ ├── create.js │ ├── events.js │ ├── index.js │ ├── setAttribute.js │ └── update.js ├── element │ └── index.js ├── index.js └── string │ ├── index.js │ └── renderString.js └── test ├── app ├── index.js └── thunk.js ├── diff ├── attributes.js ├── children.js └── node.js ├── dom ├── create.js ├── setAttribute.js └── update.js ├── element └── index.js ├── index.js └── string └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["stage-2"], 3 | "sourceMaps": true, 4 | "plugins": [ 5 | ["transform-react-jsx", {"pragma": "h"}], 6 | "transform-es2015-template-literals", 7 | "transform-es2015-literals", 8 | "transform-es2015-function-name", 9 | "transform-es2015-arrow-functions", 10 | "transform-es2015-block-scoped-functions", 11 | "transform-es2015-classes", 12 | "transform-es2015-object-super", 13 | "transform-es2015-shorthand-properties", 14 | "transform-es2015-duplicate-keys", 15 | "transform-es2015-computed-properties", 16 | "transform-es2015-for-of", 17 | "transform-es2015-sticky-regex", 18 | "transform-es2015-unicode-regex", 19 | "check-es2015-constants", 20 | "transform-es2015-spread", 21 | "transform-es2015-parameters", 22 | "transform-es2015-destructuring", 23 | "transform-es2015-block-scoping", 24 | "transform-es2015-typeof-symbol", 25 | ["transform-regenerator", { "async": false, "asyncGenerators": false }], 26 | ], 27 | "env": { 28 | "commonjs": { 29 | "plugins": [ 30 | ["transform-es2015-modules-commonjs", { "loose": true }] 31 | ] 32 | }, 33 | "es": {} 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | _book 4 | dist 5 | lib 6 | es 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | test 3 | src 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | script: 5 | - npm run test:lint 6 | - npm run test:headless 7 | addons: 8 | apt: 9 | packages: 10 | - xvfb 11 | install: 12 | - export DISPLAY=':99.0' 13 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 14 | - npm install 15 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: tape 2 | concurrency: 5 3 | browsers: 4 | - name: chrome 5 | version: latest 6 | - name: firefox 7 | version: latest 8 | - name: iphone 9 | version: latest 10 | - name: ipad 11 | version: latest 12 | - name: android 13 | version: latest 14 | - name: ie 15 | version: 10..latest 16 | - name: microsoftedge 17 | version: latest 18 | - name: safari 19 | version: latest 20 | browserify: 21 | - transform: babelify 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deku 2 | 3 | [![version](https://img.shields.io/travis/dekujs/deku.svg?style=flat-square)](https://travis-ci.org/dekujs/deku) 4 | [![version](https://img.shields.io/npm/v/deku.svg?style=flat-square)](https://www.npmjs.com/package/deku) 5 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) 6 | [![npm downloads](https://img.shields.io/npm/dm/deku.svg?style=flat-square)](https://www.npmjs.com/package/deku) 7 | [![discord](https://img.shields.io/badge/Discord-Join%20Chat%20→-738BD7.svg?style=flat-square)](https://discord.gg/0gNkyCAVkDYsBaFe) 8 | 9 | Deku is a library for rendering interfaces using pure functions and virtual DOM. 10 | 11 | Instead of using classes and local state, Deku just uses functions and pushes the responsibility of all state management and side-effects onto tools like [Redux](http://redux.js.org/). It also aims to support only modern browsers to keep things simple. 12 | 13 | It can be used in place of libraries like React and works well with Redux and other libraries in the React ecosystem. 14 | 15 | Deku consists of 5 modules packaged together for convenience: 16 | 17 | * `element`: Create virtual elements. 18 | * `diff`: Compute the difference between two virtual elements. You can use this if you're creating a custom renderer. 19 | * `dom`: Create DOM elements from virtual elements and update them using the result of a diff. You'll only use this directly if you're building your own app creator. 20 | * `string`: Render a HTML string from virtual elements. 21 | * `createApp`: Kickstart an app for the browser. 22 | 23 | ### Installation 24 | 25 | ``` 26 | npm install --save deku 27 | ``` 28 | 29 | We support the latest two versions of each browser. This means we only support IE10+. 30 | 31 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/deku.svg)](https://saucelabs.com/u/deku) 32 | 33 | ### Example 34 | 35 | ```js 36 | /** @jsx element */ 37 | import {element, createApp} from 'deku' 38 | import {createStore} from 'redux' 39 | import reducer from './reducer' 40 | 41 | // Dispatch an action when the button is clicked 42 | let log = dispatch => event => { 43 | dispatch({ 44 | type: 'CLICKED' 45 | }) 46 | } 47 | 48 | // Define a state-less component 49 | let MyButton = { 50 | render: ({ props, children, dispatch }) => { 51 | return 52 | } 53 | } 54 | 55 | // Create a Redux store to handle all UI actions and side-effects 56 | let store = createStore(reducer) 57 | 58 | // Create an app that can turn vnodes into real DOM elements 59 | let render = createApp(document.body, store.dispatch) 60 | 61 | // Update the page and add redux state to the context 62 | render( 63 | Hello World!, 64 | store.getState() 65 | ) 66 | ``` 67 | 68 | ### Documentation 69 | 70 | You can [read the documentation online](http://anthonyshort.me/deku/). 71 | 72 | ### License 73 | 74 | The MIT License (MIT) Copyright (c) 2015 Anthony Short 75 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": "2.x.x", 3 | "structure": { 4 | "summary": "docs/README.md" 5 | }, 6 | "plugins": ["edit-link", "prism", "-highlight"], 7 | "pluginsConfig": { 8 | "edit-link": { 9 | "base": "https://github.com/anthonyshort/deku/tree/master", 10 | "label": "Edit" 11 | }, 12 | "github": { 13 | "url": "https://github.com/anthonyshort/deku/" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Readme](/README.md) 4 | * [Motivation](/docs/motivation.md) 5 | * [Contributing](/docs/contributing.md) 6 | * [Basics](/docs/basics/index.md) 7 | * [Migrating from v1](/docs/basics/migrating.md) 8 | * [Elements](/docs/basics/elements.md) 9 | * [JSX](/docs/basics/JSX.md) 10 | * [Events](/docs/basics/events.md) 11 | * [Components](/docs/basics/components.md) 12 | * [Advanced](/docs/advanced/index.md) 13 | * [Lifecycle Hooks](/docs/advanced/lifecycle.md) 14 | * [Keys](/docs/advanced/keys.md) 15 | * [Context](/docs/advanced/context.md) 16 | * [Dispatcher](/docs/advanced/dispatcher.md) 17 | * [Hot Module Replacement](/docs/advanced/hmr.md) 18 | * Tips 19 | * [innerHTML](/docs/tips/innerhtml.md) 20 | * [API Reference](/docs/api/index.md) 21 | * [createApp](/docs/api/create-app.md) 22 | * [dom](/docs/api/dom.md) 23 | * [string](/docs/api/string.md) 24 | * [element](/docs/api/element.md) 25 | * [diff](/docs/api/diff.md) 26 | * [vnode](/docs/api/vnode.md) 27 | -------------------------------------------------------------------------------- /docs/advanced/context.md: -------------------------------------------------------------------------------- 1 | # Context 2 | 3 | Context is an object you can set when you call render. It will be accessible from every component in the tree. Unlike React you can't modify the context within the tree. 4 | 5 | You can use `context` to: 6 | 7 | * Access your Redux state throughout the tree. 8 | * Pass in `window` properties, like the size of the browser window. 9 | * View device information like the user agent, or the orientation. 10 | * Set global theme variables. 11 | * Store local state for components using their `path` as the key. 12 | 13 | There are some things to remember when using `context`: 14 | 15 | * Context should be **immutable**. Never mutate the object directly. The renderer can perform additional optimizations if you use a different object each time. If you're using Redux, your state should be immutable anyway. You can also use a library to help with immutable structures like [Immutable.js], [Mori] or [Scour]. 16 | * Context can't be modified by components. There's no `getChildContext`. 17 | 18 | [Immutable.js]: https://github.com/facebook/immutable-js 19 | [Mori]: https://github.com/swannodette/mori 20 | [Scour]: http://ricostacruz.com/scour/ 21 | 22 | ## Example 23 | 24 | We can access the `context` on the model: 25 | 26 | ```js 27 | function render ({ context }) { 28 | return
Hi
29 | } 30 | 31 | export default { 32 | render 33 | } 34 | ``` 35 | 36 | Your context object might look something like this: 37 | 38 | ```js 39 | let context = { 40 | theme: { 41 | background: 'red' 42 | }, 43 | device: { 44 | width: 1000, 45 | height: 1000, 46 | orientation: 'landscape' 47 | }, 48 | browser: { 49 | type: 'IE', 50 | version: 10 51 | }, 52 | collections: { 53 | todos: [], 54 | projects: [] 55 | }, 56 | user: { 57 | name: 'Anthony Short', 58 | username: 'anthoney' 59 | } 60 | } 61 | ``` 62 | 63 | Which you can set when you call [`render`](../api/dom): 64 | 65 | ```js 66 | render(, context) 67 | ``` 68 | 69 | ## How do I change context and re-render? 70 | 71 | Context can change in a few ways. You should listen for changes at the top level of your application rather than within components where possible. To change any type of context value from within components, you should trigger actions using `dispatch`. 72 | 73 | * You can listen to your Redux store using `subscribe` 74 | * You can listen for `window` events like `resize` 75 | * Trigger actions using the `dispatcher` 76 | 77 | The easiest way to use context is to use the Redux state. It's almost guaranteed to be immutable and you can easily keep `window` and device information in your Redux store. 78 | 79 | ```js 80 | render(, store.getState()) 81 | ``` 82 | 83 | You would modify context by dispatching actions and reducing them with Redux: 84 | 85 | ```js 86 | function render ({ dispatch }) { 87 | return 88 | } 89 | 90 | function resize (dispatch, width, height) { 91 | return () => dispatch({ 92 | type: 'RESIZE', 93 | width, 94 | height 95 | }) 96 | } 97 | 98 | export default { 99 | render 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/advanced/dispatcher.md: -------------------------------------------------------------------------------- 1 | # Dispatcher 2 | 3 | The `dispatcher` is a function you can pass to `createRenderer` that will handle actions triggered within the UI. You will typically use a library like Redux to handle reducing actions into state and side-effects. 4 | 5 | * All actions a user or program trigger should be dispatched 6 | * Side-effects, like DOM manipulation, should also be dispatched as actions and handled by this function 7 | * Access the dispatch function within components as `model.dispatch` 8 | 9 | ## Example 10 | 11 | Here's a custom `dispatch` function being passed into the `createRenderer` function. 12 | 13 | ```js 14 | // Custom dispatch method 15 | let render = createRenderer(document.body, action => { 16 | // action.type = 'ADD_TODO' 17 | }) 18 | 19 | // Using Redux 20 | let render = createRenderer(document.body, store.dispatch) 21 | ``` 22 | 23 | You will be able to access `dispatch` from any component as `model.dispatch`: 24 | 25 | ```js 26 | function render ({ dispatch }) { 27 | return 28 | } 29 | 30 | function addTodo (dispatch) { 31 | return event => { 32 | dispatch({ 33 | type: 'ADD_TODO' 34 | }) 35 | } 36 | } 37 | 38 | export default { 39 | render 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/advanced/hmr.md: -------------------------------------------------------------------------------- 1 | # Hot Module Replacement 2 | 3 | Being purely functional, Hot Module Replacement (HMR), i.e. code swapping at runtime, comes with virtually no cost while using **deku**. 4 | 5 | * [Webpack](#webpack) 6 | * [Browserify](#browserify) 7 | 8 | ## Webpack 9 | 10 | ### 1. Install dependencies 11 | 12 | First we need to create a project: 13 | 14 | ```bash 15 | mkdir project 16 | cd project 17 | npm init 18 | ``` 19 | 20 | Then to install some dependencies: 21 | 22 | ```bash 23 | npm install --save deku 24 | npm install --save-dev babel-core babel-loader babel-plugin-transform-react-jsx babel-preset-es2015 express webpack webpack-dev-middleware webpack-hot-middleware 25 | ``` 26 | 27 | ### 2. Create a webpack config 28 | 29 | The following webpack config will enable HMR and compile ES2015/JSX code. 30 | 31 | ```js 32 | // file: webpack.config.js 33 | 34 | var webpack = require('webpack'), 35 | path = require('path'); 36 | 37 | module.exports = { 38 | devtool: '#source-map', 39 | entry: [ 40 | 'webpack-hot-middleware/client', 41 | './main.jsx' 42 | ], 43 | output: { 44 | path: path.join(__dirname, 'build'), 45 | filename: 'bundle.js', 46 | library: 'app', 47 | publicPath: '/build/' 48 | }, 49 | plugins: [ 50 | new webpack.optimize.OccurrenceOrderPlugin(), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NoErrorsPlugin() 53 | ], 54 | module: { 55 | loaders: [ 56 | { 57 | test: /\.jsx?$/, 58 | exclude: /node_modules/, 59 | loader: 'babel', 60 | query: { 61 | presets: ['es2015'], 62 | plugins: [['transform-react-jsx', {pragma: 'element'}]] 63 | } 64 | } 65 | ] 66 | } 67 | }; 68 | 69 | ``` 70 | 71 | ### 3. Create a development server 72 | 73 | We need to setup a little server that will serve our code and assets while being able to send HMR updates. 74 | 75 | ```js 76 | // file: server.js 77 | 78 | var webpack = require('webpack'), 79 | config = require('./webpack.config.js'), 80 | dev = require('webpack-dev-middleware'), 81 | hot = require('webpack-hot-middleware'), 82 | express = require('express'), 83 | path = require('path'); 84 | 85 | var compiler = webpack(config); 86 | 87 | var app = express(); 88 | 89 | app.use(dev(compiler, {publicPath: config.output.publicPath})); 90 | app.use(hot(compiler)); 91 | 92 | app.get('*', function(req, res) { 93 | res.sendFile(path.join(__dirname, 'index.html')); 94 | }); 95 | 96 | console.log('Compiling...'); 97 | app.listen(3000, 'localhost', function (err) { 98 | if (err) return console.err(err); 99 | }); 100 | ``` 101 | 102 | ### 4. Creating our HTML file, JS entry and a root component 103 | 104 | ```html 105 | 106 | 107 | 108 | Deku HMR Example 109 | 110 | 111 |
112 | 113 | 114 | 115 | ``` 116 | 117 | ```js 118 | // file: components/Application.jsx 119 | 120 | import {element} from 'deku'; 121 | 122 | export default { 123 | render() { 124 | return ( 125 |
126 |

Hello World!

127 | 128 |
129 | ); 130 | } 131 | } 132 | ``` 133 | 134 | ```js 135 | // file: main.jsx 136 | 137 | import {dom, element} from 'deku'; 138 | import Application from './components/Application.jsx'; 139 | 140 | const render = dom.createRenderer(document.getElementById('mount')); 141 | 142 | // Rendering function 143 | function update (Component) { 144 | render() 145 | } 146 | 147 | // First render 148 | update(Application); 149 | 150 | // Hooking into HMR 151 | // This is the important part as it will reload your code and re-render the app accordingly 152 | if (module.hot) { 153 | module.hot.accept('./components/Application.jsx', function() { 154 | const nextApplication = require('./components/Application.jsx').default; 155 | update(nextApplication); 156 | }); 157 | } 158 | ``` 159 | 160 | ### 5. Starting the development server 161 | 162 | Now, our working directory should look like the following: 163 | 164 | ``` 165 | project/ 166 | components/ 167 | Application.jsx 168 | index.html 169 | main.jsx 170 | package.json 171 | server.js 172 | webpack.config.js 173 | ``` 174 | 175 | Let's launch the dev server and code: 176 | 177 | ```bash 178 | node server.js 179 | ``` 180 | 181 | Wait for webpack to perform the initial compilation and let's visit `localhost:3000`. 182 | 183 | You should now be able to edit your components without reloading the browser! 184 | 185 | ## Browserify 186 | 187 | ### 1. Install dependencies 188 | 189 | We'll use [watchify] to watch for code changes. 190 | 191 | ```sh 192 | npm install --save-dev browserify-hmr watchify 193 | ``` 194 | 195 | [watchify]: https://github.com/substack/watchify 196 | 197 | ### 2. Set up HMR hooks 198 | 199 | Use the HMR API's `module.hot.accept()` to re-run `render()` when code updates happen. 200 | 201 | ```js 202 | // The root deku component. 203 | let App = require('./components/app') 204 | 205 | // The deku renderer. 206 | let store = createStore(/* ... */) 207 | let render = createRenderer(document.body, store.dispatch) 208 | 209 | // Running this will refresh your page with the latest 210 | // components and the latest state. 211 | function update () { 212 | render(, store.getState()) 213 | } 214 | 215 | update() 216 | 217 | // This is the important part. It will re-render in place 218 | // after updating your code. 219 | if (module.hot) { 220 | module.hot.accept('./components/app', function () { 221 | App = require('./components/app') 222 | update() 223 | } 224 | } 225 | ``` 226 | 227 | [browserify-hmr]: https://github.com/AgentME/browserify-hmr 228 | 229 | ### 3. Run watchify 230 | 231 | Use watchify with `-p browserify-hmr` to enable hot module replacement. 232 | 233 | ```sh 234 | watchify -p browserify-hmr index.js -o public/application.js 235 | ``` 236 | -------------------------------------------------------------------------------- /docs/advanced/index.md: -------------------------------------------------------------------------------- 1 | # Advanced 2 | 3 | Learn how to integrate Deku into your application and improve performance. 4 | 5 | * [Lifecycle Hooks](./hooks.md) 6 | * [Keys](./keys.md) 7 | * [Context](./context.md) 8 | * [Dispatcher](./dispatcher.md) 9 | * [Hot Module Replacement](./hmr.md) 10 | -------------------------------------------------------------------------------- /docs/advanced/keys.md: -------------------------------------------------------------------------------- 1 | # Keys 2 | 3 | Elements can have a special attribute named `key`. This lets the renderer know how to identify an element amongst sibling elements so it can determine if an element has just been moved, instead of removing and replacing DOM nodes unnecessarily. 4 | 5 | * `key` is a special attribute that won't be passed in as a prop or rendered in the DOM. 6 | * They increase the rendering performance of large lists. 7 | * They only need to be unique to sibling elements. 8 | * They can be used to maintain state of form elements, like `input` and `select`. 9 | * Not every sibling needs a key. You can add a `key` to the elements you want to be moved around. 10 | 11 | ## Example 12 | 13 | In this example, we use the key attribute to make sure the input element is moved instead of replaced whenever the hint is added and removed: 14 | 15 | ```js 16 |
17 | 18 | {showHint ? Enter your full name : null} 19 | 20 |
21 | ``` 22 | 23 | In the next example, we use keys to improve the rendering performance of a list. You can use the `id`, `name`, or other unique field for the list item. 24 | 25 | ```js 26 | let people = [ 27 | { id: 1, name: 'Tom' }, 28 | { id: 2, name: 'Dick' }, 29 | { id: 3, name: 'Harry' } 30 | ] 31 | 32 | let el = 33 |
    34 | {people.map(person =>
  • {person.name}
  • )} 35 |
36 | ``` 37 | 38 | ## Why do we need keys? 39 | 40 | To see why we need to do this we need to explain how the renderer compares lists of elements. Imagine on the first render these elements are returned: 41 | 42 | ```html 43 |
44 | One 45 |
Two
46 | Three 47 |
48 | ``` 49 | 50 | Then on the next render this is returned: 51 | 52 | ```html 53 |
54 | One 55 | Three 56 |
Two
57 |
58 | ``` 59 | 60 | It is obvious to humans that the third element has moved up. But when the renderer is performing the diff it is just comparing the left and right sides one after the other: 61 | 62 | ``` 63 | One -> One // No change. Leave it. 64 |
Two
-> Three // Different element. Replace! 65 | Three ->
Two
// Different element. Replace! 66 | ``` 67 | 68 | Most of the time this won't affect you but there are a couple of cases where this becomes a problem: 69 | 70 | * When you have a large list of elements (thousands) that have moved around. The renderer will be removing and creating large sets of DOM elements. 71 | * When elements with hidden state, like `input` fields, are moved around. Their state will be lost because they've been destroyed and recreated. 72 | 73 | To get around this we can just add a `key` attribute: 74 | 75 | ```js 76 |
77 | One 78 |
Two
79 | Three 80 |
81 | ``` 82 | 83 | Now during the diff the renderer will know precisely which elements have just moved: 84 | 85 | ``` 86 | One -> One // No change. Leave it. 87 |
Two
->
Two
// Move to position 2! 88 | Three -> Three // Move to position 1! 89 | ``` 90 | 91 | **These keys only need to be unique to siblings.** They need not be globally unique like the `id` attribute. 92 | 93 | You can also add keys only to the elements that will be moved around if that makes things easier. 94 | 95 | ```js 96 |
97 | One 98 |
Two
99 | Three 100 |
101 | ``` 102 | -------------------------------------------------------------------------------- /docs/advanced/lifecycle.md: -------------------------------------------------------------------------------- 1 | # Lifecycle hooks 2 | 3 | These are functions you can add to your component to hook into different parts of the rendering process. You can use these to manipulate the DOM in some way or trigger an action to change state. 4 | 5 | | Name | Triggered | Arguments | 6 | |-------------|--------------------------------------------------------------------------|-------------| 7 | | `onCreate` | When the component is initially created | `model` | 8 | | `onUpdate` | After the component is re-rendered and the DOM is patched | `model` | 9 | | `onRemove` | When the DOM element has been removed from the DOM | `model` | 10 | 11 | ## Example 12 | 13 | ```js 14 | function render (model) { 15 | return
{model.props.text}
16 | } 17 | 18 | function onCreate (model) { 19 | console.log('A MyComponent entity was created!') 20 | } 21 | 22 | export default { 23 | render, 24 | onCreate 25 | } 26 | ``` 27 | 28 | ## How do I access the DOM element? 29 | 30 | In React, you can access the DOM element directly in the lifecycle hooks. In Deku, we only pass you the data model. If you need to access the DOM element this is a side-effect. You should dispatch an action and let another part of your application handle the side-effect. This allows you to keep your component completely DOM-free, which makes it easier to test and allows you to reuse side-effects. 31 | 32 | ```js 33 | function render ({ path }) { 34 | return 35 | } 36 | 37 | function onCreate ({ dispatch, path }) { 38 | dispatch({ 39 | type: 'FOCUS', 40 | selector: `#${path}` 41 | }) 42 | } 43 | 44 | export default { 45 | render, 46 | onCreate 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/advanced/model.md: -------------------------------------------------------------------------------- 1 | # Model 2 | 3 | The model is passed into the Component function and is essentially the state that you can use to create virtual nodes. The model is immutable so to make any changes to values you need modify the state somewhere else and call `render` again from the top level. 4 | 5 | ```js 6 | function App (model) { 7 | return

Hello World

8 | } 9 | ``` 10 | 11 | ## Properties 12 | 13 | ### `model.attributes` 14 | 15 | The values passed into your component as virtual element attributes. For example, passing these attributes into the component: 16 | 17 | ```js 18 | 19 | ``` 20 | 21 | will yield this as `model.attributes`: 22 | 23 | ```json 24 | { 25 | "count": 5, 26 | "type": "tasks" 27 | } 28 | ``` 29 | 30 | ### `model.children` 31 | 32 | The child nodes that are passed to the component. 33 | 34 | ```js 35 | 36 | 37 | Hello World 38 | 39 | ``` 40 | 41 | The `children` property will always be an array: 42 | 43 | ```jsx 44 | [ 45 | , 46 | Hello World 47 | ] 48 | ``` 49 | 50 | You can use this to render the passed in nodes somewhere within the rendered DOM. 51 | 52 | ```js 53 | function App (model) { 54 | return
{model.children}
55 | } 56 | ``` 57 | 58 | ### `model.path` 59 | 60 | The unique path to the component within the entire component tree. 61 | 62 | ```js 63 | function App (model) { 64 | return Save 65 | } 66 | 67 | function updateState (path) { 68 | return function (e) { 69 | dispatch('updateState', { 70 | path: path, 71 | value: e.target.value 72 | }) 73 | } 74 | } 75 | ``` 76 | 77 | Paths in a tree are strings, like `0.1.5.2`. Each number represents the index of the node in the parent node, so you can use this to find a node within a tree. 78 | 79 | If a node within that path has a `key` attribute, we use that instead so that the path is stable even if any elements move around. For example, `0.1.foo.2`. This allows you to maintain some state for a component even if it moves. 80 | -------------------------------------------------------------------------------- /docs/api/create-app.md: -------------------------------------------------------------------------------- 1 | # `createApp` 2 | 3 | Returns a `render` function that you can use to render elements within `DOMElement`. 4 | 5 | ### Arguments 6 | 7 | 1. `el` _(HTMLElement)_: A container element that will have virtual elements rendered inside of it. The element will never be touched. 8 | 2. `dispatch` _(Function)_: A function that can receive actions from the interface. This function will be passed into every component. It usually takes an [action](http://redux.js.org/docs/basics/Actions.html) that can be handled by a [store](http://redux.js.org/docs/basics/Store.html) 9 | 10 | ### Returns 11 | 12 | `render` _(Function)_: A function that will update the current virtual element. It accepts a new `vnode` and a `context` object that will be passed to every component. You can use the context object to send shared state to every component. The `context` object should be immutable if possible as internally the renderer can provide extra optimizations. 13 | 14 | ### Example 15 | 16 | ```js 17 | import {createApp, element} from 'deku' 18 | import {createStore} from 'redux' 19 | import reducer from './reducer' 20 | import App from './app' 21 | 22 | // Create a redux store to handle actions 23 | let store = createStore(reducer) 24 | 25 | // Create a renderer 26 | let render = createApp(document.body, store.dispatch) 27 | 28 | // This renders the content into document.body 29 | render() 30 | 31 | // Update the UI 32 | render() 33 | ``` 34 | 35 | ### Notes 36 | 37 | The container DOM element should: 38 | 39 | * **Not be the document.body**. You'll probably run into problems with other libraries. They'll often add elements to the `document.body` which can confuse the diff algorithm. 40 | * **Be empty**. All elements inside of the container will be removed when a virtual element is rendered into it. The renderer needs to have complete control of all of the elements within the container. 41 | -------------------------------------------------------------------------------- /docs/api/diff.md: -------------------------------------------------------------------------------- 1 | # `diff` 2 | 3 | The `diff` object provides functions for comparing two virtual nodes. Mostly useful if you're creating your own renderer. 4 | 5 | ```js 6 | import {diff} from 'deku' 7 | ``` 8 | 9 | ### Properties 10 | 11 | * [`diffNode`](#diffNode) 12 | * [`Actions`](#Actions) 13 | 14 | ## `diffNode(prevNode, nextNode)` 15 | 16 | Returns the difference between two virtual nodes as an array of actions that can be used by a renderer to patch a view. 17 | 18 | ### Arguments 19 | 20 | 1. `prevNode` _(VirtualElement)_: The current renderer vnode 21 | 2. `nextNode` _(VirtualElement)_: The updated vnode 22 | 23 | ### Returns 24 | 25 | `actions` _(Array)_: An array of `Action` objects that describe the change that needs to be made to update the view. 26 | 27 | ### Example 28 | 29 | ```js 30 | import {diff, element} from 'deku' 31 | 32 | const {diffNode, Actions} = diff 33 | 34 | let left = element('div', { class: 'foo' }) 35 | let right = element('div', { class: 'bar' }) 36 | 37 | let changes = diff(left, right) 38 | 39 | changes.forEach(action => { 40 | Actions.case({ 41 | setAttribute: (name, value, previousValue) => { 42 | // update the attribute 43 | } 44 | }, action) 45 | }) 46 | ``` 47 | 48 | ## `Actions` 49 | 50 | An object containing union types for actions produced during the diff. You can create new actions using this object, but this is usually handled by `diffNode`. 51 | 52 | ### Example 53 | 54 | ```js 55 | import {diff, element} from 'deku' 56 | 57 | const {Actions} = diff 58 | 59 | // Create a new action. 60 | let action = Actions.setAttribute('class', 'foo', 'bar') 61 | 62 | // Switch between the different action types 63 | Actions.case({ 64 | setAttribute: (name, value, previousValue) => { 65 | // Use the action 66 | } 67 | }, action) 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/api/dom.md: -------------------------------------------------------------------------------- 1 | # `dom` 2 | 3 | The `dom` object provides functions for rendering elements to the DOM. These are lower-level functions that you generally won't need to access unless you're building your own version of `createApp`. 4 | 5 | ```js 6 | import {dom} from 'deku' 7 | ``` 8 | 9 | ### Properties 10 | 11 | * `create` 12 | * `update` 13 | 14 | ## `create` 15 | `(vnode, path, dispatch, context) -> DOMElement` 16 | 17 | Create a DOM element from a virtual element. 18 | 19 | ## `update` 20 | `(dispatch, context) -> (DOMElement, action) -> DOMElement` 21 | 22 | Create a function to patch a DOM element using an action. 23 | -------------------------------------------------------------------------------- /docs/api/element.md: -------------------------------------------------------------------------------- 1 | # `element(type, attributes, children)` 2 | 3 | Returns a `render` function that you can use to render elements within `DOMElement`. The function signature is compatible with JSX. 4 | 5 | ### Arguments 6 | 7 | 1. `type` _(String|Object)_: Each node in a tree has a type. This is only used when rendering the tree. 8 | 2. `attributes` _(Object)_ (Optional): The attributes of this node, similar to HTML attributes. 9 | 3. `children` _(Array)_ (Optional): An array of children. Each child should also be an `element`. Strings are also accepted for text nodes. 10 | 11 | ### Returns 12 | 13 | `vnode` _(Object)_: An object representing a node within a tree. 14 | 15 | ### Example 16 | 17 | ```js 18 | import {element} from 'deku' 19 | 20 | // Native elements 21 | element('div', { class: "greeting" }, [ 22 | element('span', {}, ['Hello']) 23 | ]) 24 | 25 | // Components 26 | let App = { 27 | render: ({ props }) =>
Hello {props.name}!
28 | } 29 | 30 | element(App, { name: "Tom" }) 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | * [dom](dom.md) 4 | * [string](string.md) 5 | * [element](element.md) 6 | * [diff](diff.md) 7 | * [vnode](vnode.md) 8 | * [createApp](create-app.md) 9 | -------------------------------------------------------------------------------- /docs/api/string.md: -------------------------------------------------------------------------------- 1 | # `string` 2 | 3 | The `string` object provides functions for rendering virtual elements to a string. 4 | 5 | ```js 6 | import {string} from 'deku' 7 | ``` 8 | 9 | ### Properties 10 | 11 | * [`render`](#-render-vnode-context) 12 | 13 | ## `render(vnode, [context])` 14 | 15 | This function renders a virtual element to a string. It will render components and call all hooks as normal. This can be used on the server to pre-render an app as HTML, and then replaced on the client. 16 | 17 | The `context` object will be passed into each component function. 18 | 19 | ### Arguments 20 | 21 | 1. `vnode` _(VirtualElement)_: A virtual element to render as a string. 22 | 2. `context` _(Object)_: You can use the context object to send shared state to every component. The `context` object should be immutable if possible as internally the renderer can provide extra optimizations. 23 | 24 | ### Returns 25 | 26 | `html` _(String)_: A string of HTML that can be rendered on the server. 27 | 28 | ### Example 29 | 30 | ```js 31 | import {string, element} from 'deku' 32 | import Sidebar from './sidebar' 33 | import Header from './header' 34 | import App from './app' 35 | 36 | let html = string.render( 37 |
38 |
39 | 40 | 41 |
42 | ) 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/api/vnode.md: -------------------------------------------------------------------------------- 1 | # `vnode` 2 | 3 | Contains helper functions for working with virtual nodes. Mostly useful if you're creating your own renderer. 4 | 5 | ```js 6 | import {vnode} from 'deku' 7 | ``` 8 | 9 | ### Properties 10 | 11 | * create 12 | * createTextElement 13 | * createThunkElement 14 | * createEmptyElement 15 | * isThunk 16 | * isText 17 | * isEmpty 18 | * isSameThunk 19 | * isValidAttribute 20 | * createPath 21 | 22 | ## `create(type, attributes, children)` 23 | 24 | An alias for `element`. 25 | 26 | ## `createTextElement(text)` 27 | 28 | Create a virtual text element. 29 | 30 | ## `createThunkElement(component, key, props, children)` 31 | 32 | Create a virtual thunk element. These are lazily-rendered virtual elements. 33 | 34 | ## `createEmptyElement()` 35 | 36 | Create a virtual element that represents an empty space. 37 | 38 | ## `isThunk(vnode)` 39 | 40 | Check if a vnode is a thunk type. 41 | 42 | ## `isText(vnode)` 43 | 44 | Check if a vnode is a text type. 45 | 46 | ## `isEmpty(vnode)` 47 | 48 | Check if a vnode is an empty type. 49 | 50 | ## `isSameThunk(prevNode, nextNode)` 51 | 52 | Check to see if two virtual elements are both the same type of thunk. 53 | 54 | ## `isValidAttribute(value)` 55 | 56 | Check to see if an attribute is valid and should be rendered. 57 | 58 | ## `createPath(...paths)` 59 | 60 | Takes arguments and joins them with a `.` to produce a path. 61 | -------------------------------------------------------------------------------- /docs/basics/components.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | Components are a way to define logical, re-usable pieces of your interface - tabs, buttons, forms. In Deku, components are just a special type of virtual element that is not rendered until it's needed. Internally, components are known as "thunks". This means that only the minimal number of virtual elements need to be re-created and patched on each render. 4 | 5 | In addition to providing a performance boost, you are able to hook into "lifecycle events" when a component is created, updated, or removed. 6 | 7 | Components are: 8 | 9 | * Plain objects with at least `render` function; 10 | * Able to provide a performance boost by caching the result of each render; 11 | * Uniquely identifiable using the `path` value, allowing you to store local state; 12 | * Able to trigger actions when they are created, updated, or removed; 13 | * Used like custom elements within your app 14 | 15 | Unlike React, components in Deku are completely stateless, there's no `setState` function or `this` used. 16 | 17 | ## Example 18 | 19 | Here's a simple `` component: 20 | 21 | ```js 22 | function render ({ props, children, context, path }) { 23 | return ( 24 | 27 | ) 28 | } 29 | 30 | function onCreate ({ props, dispatch }) { 31 | dispatch({ 32 | type: 'APP_STARTED' 33 | }) 34 | } 35 | 36 | function onRemove ({ props, dispatch }) { 37 | dispatch({ 38 | type: 'APP_STOPPED' 39 | }) 40 | } 41 | 42 | export default { 43 | render, 44 | onCreate, 45 | onRemove 46 | } 47 | ``` 48 | 49 | To use this component within other components, you simply import and compose it: 50 | 51 | ```js 52 | import App from './app' 53 | 54 | function render (model) { 55 | return