├── book.json ├── docs ├── examples │ ├── todo │ │ ├── index.css │ │ ├── app.js │ │ ├── todo.js │ │ ├── list.js │ │ └── index.js │ ├── counter │ │ ├── index.css │ │ ├── react.js │ │ └── index.js │ ├── iteration │ │ ├── index.css │ │ ├── index.js │ │ └── react.js │ ├── hello-world │ │ ├── index.css │ │ └── index.js │ ├── like-button │ │ ├── index.css │ │ ├── react.js │ │ └── index.js │ ├── events │ │ ├── index.css │ │ └── index.js │ ├── index.html │ └── server.js ├── hints │ ├── document-body.md │ ├── array-children.md │ ├── empty-container.md │ └── no-type.md ├── guides │ ├── install.md │ ├── jsx.md │ └── components.md └── deku.d.ts ├── .gitignore ├── component.json ├── test ├── index.js ├── string │ └── index.js └── dom │ └── index.js ├── .npmignore ├── lib ├── svg.js ├── index.js ├── node-type.js ├── application.js ├── events.js ├── stringify.js └── render.js ├── .zuul.yml ├── LICENSE.md ├── package.json ├── Makefile ├── README.md └── History.md /book.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /docs/examples/todo/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/counter/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/iteration/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/hello-world/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/examples/like-button/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | components 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deku", 3 | "main": "build/deku.js" 4 | } 5 | -------------------------------------------------------------------------------- /docs/examples/events/index.css: -------------------------------------------------------------------------------- 1 | 2 | .active { 3 | background-color: yellow; 4 | } 5 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('babelify/polyfill') 2 | require('./dom/index') 3 | require('./string') 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/ 3 | docs/ 4 | .npmignore 5 | .zuul 6 | History.md 7 | LICENSE.md 8 | Makefile -------------------------------------------------------------------------------- /lib/svg.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | isElement: require('is-svg-element').isElement, 3 | isAttribute: require('is-svg-attribute'), 4 | namespace: 'http://www.w3.org/2000/svg' 5 | } 6 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: tape 2 | concurrency: 2 3 | browsers: 4 | - name: chrome 5 | version: 43..latest 6 | - name: firefox 7 | version: 38..latest 8 | - name: iphone 9 | version: latest 10 | - name: ipad 11 | version: latest 12 | - name: ie 13 | version: 10..latest 14 | - name: safari 15 | version: 7..latest 16 | browserify: 17 | - transform: babelify 18 | - transform: envify 19 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create the application. 3 | */ 4 | 5 | exports.tree = 6 | exports.scene = 7 | exports.deku = require('./application') 8 | 9 | /** 10 | * Render scenes to the DOM. 11 | */ 12 | 13 | if (typeof document !== 'undefined') { 14 | exports.render = require('./render') 15 | } 16 | 17 | /** 18 | * Render scenes to a string 19 | */ 20 | 21 | exports.renderString = require('./stringify') 22 | -------------------------------------------------------------------------------- /lib/node-type.js: -------------------------------------------------------------------------------- 1 | var type = require('component-type') 2 | 3 | /** 4 | * Returns the type of a virtual node 5 | * 6 | * @param {Object} node 7 | * @return {String} 8 | */ 9 | 10 | module.exports = function nodeType (node) { 11 | var v = type(node) 12 | if (v === 'null' || node === false) return 'empty' 13 | if (v !== 'object') return 'text' 14 | if (type(node.type) === 'string') return 'element' 15 | return 'component' 16 | } 17 | -------------------------------------------------------------------------------- /docs/examples/hello-world/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx element */ 2 | 3 | import element from 'dekujs/virtual-element' 4 | import { render, tree } from 'dekujs/deku' 5 | 6 | // Create a component 7 | var HelloWorld = { 8 | render(component) { 9 | let {props,state} = component 10 | return ( 11 |
12 | {props.text} 13 |
14 | ) 15 | } 16 | } 17 | 18 | // Create a tree 19 | var app = tree() 20 | 21 | // Render the tree to the DOM 22 | render(app, document.body) 23 | -------------------------------------------------------------------------------- /docs/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Deku Examples 6 | 7 | 8 |

Deku Examples

9 |
    10 |
  1. counter
  2. 11 |
  3. events
  4. 12 |
  5. hello-world
  6. 13 |
  7. iteration
  8. 14 |
  9. like-button
  10. 15 |
  11. todo
  12. 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/examples/todo/app.js: -------------------------------------------------------------------------------- 1 | /** @jsx element */ 2 | 3 | import element from 'dekujs/virtual-element' 4 | import List from './list' 5 | 6 | // Optionally set prop types that will validate 7 | // whenever props are changed 8 | let propTypes = { 9 | items: { 10 | type: 'array' 11 | } 12 | } 13 | 14 | // Render the list 15 | function render (component) { 16 | let {props,state} = component 17 | 18 | return ( 19 |
20 |

My Todos

21 | 22 |
23 | ) 24 | } 25 | 26 | export default {propTypes,render} 27 | -------------------------------------------------------------------------------- /docs/examples/todo/todo.js: -------------------------------------------------------------------------------- 1 | /** @jsx element */ 2 | 3 | import element from 'dekujs/virtual-element' 4 | 5 | let propTypes = { 6 | 'item': { 7 | 'type': 'object' 8 | }, 9 | 'remove': { 10 | source: 'removeTodo' 11 | } 12 | } 13 | 14 | function render (component) { 15 | let {props,state} = component 16 | let {item,remove} = props 17 | 18 | function onRemove () { 19 | remove(item) 20 | } 21 | 22 | return ( 23 |
24 | {item.text} 25 | Remove 26 |
27 | ) 28 | } 29 | 30 | export default {propTypes,render} 31 | -------------------------------------------------------------------------------- /docs/examples/like-button/react.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class Button extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | liked: false 8 | }; 9 | } 10 | 11 | onClick(e) { 12 | e.preventDefault(); 13 | let { liked } = this.state; 14 | this.setState({ 15 | liked: !liked 16 | }) 17 | } 18 | 19 | render() { 20 | let { liked } = this.state; 21 | return ( 22 | 25 | ) 26 | } 27 | } 28 | 29 | React.render( 25 | ) 26 | } 27 | } 28 | 29 | let button = tree( 30 | 31 | ); 32 | 33 | render(button, document.body); 34 | -------------------------------------------------------------------------------- /docs/examples/todo/list.js: -------------------------------------------------------------------------------- 1 | /** @jsx element */ 2 | 3 | import element from 'dekujs/virtual-element' 4 | import Todo from './todo' 5 | 6 | // These are shared across all instances on the page 7 | let defaultProps = { 8 | items: [] 9 | } 10 | 11 | // Optionally set prop types that will validate 12 | // whenever props are changed 13 | let propTypes = { 14 | items: { 15 | type: 'array' 16 | } 17 | } 18 | 19 | // Render the list 20 | function render (component) { 21 | let {props,state} = component 22 | 23 | let items = props.items.map(function (item) { 24 | return 25 | }) 26 | 27 | return ( 28 |
29 | {items} 30 |
31 | ) 32 | } 33 | 34 | export default {defaultProps,propTypes,render} 35 | -------------------------------------------------------------------------------- /docs/examples/todo/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx element */ 2 | 3 | import App from './app' 4 | import element from 'dekujs/virtual-element' 5 | import { render, tree } from 'dekujs/deku' 6 | 7 | // Initial todos 8 | var todos = [ 9 | { text: 'Hello!' }, 10 | { text: 'Buy milk' } 11 | ] 12 | 13 | // Create the app 14 | var app = tree() 15 | 16 | // Set values on the tree that can be accessed by 17 | // any component by using propTypes and setting the 'source' 18 | // option to be 'removeTodo' 19 | app.set('removeTodo', function(todo){ 20 | var newTodos = todos.filter(function(target){ 21 | return target !== todo 22 | }) 23 | app.mount( 24 | 25 | ) 26 | }) 27 | 28 | // Render into the DOM 29 | render(app, document.body) 30 | -------------------------------------------------------------------------------- /docs/hints/document-body.md: -------------------------------------------------------------------------------- 1 | # Rendering to document.body 2 | 3 | While it is possible to use `document.body` as the target container it can cause some issues if you're using third-party libraries that modify the DOM. 4 | 5 | ```js 6 | // This is dangerous 7 | var app = tree() 8 | render(app, document.body) 9 | ``` 10 | 11 | When the renderer diffs and touches the DOM it is expecting nodes to exist in a particular way. Adding or removing DOM nodes without the renderer knowing can break the diffing algorithm. 12 | 13 | You should consider all of the DOM nodes within your container untouchable. Any manual modifications can break your app. 14 | 15 | ## Solution 16 | 17 | Create another element inside of the `document.body` that is dedicated to your app: 18 | 19 | ```js 20 | 21 |
22 | 23 | ``` 24 | 25 | And use that element instead: 26 | 27 | ```js 28 | var app = tree() 29 | render(app, document.getElementById('app')) 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/hints/array-children.md: -------------------------------------------------------------------------------- 1 | # Virtual children as an array 2 | 3 | You're rendering virtual nodes with an array as a child node. Something like this: 4 | 5 | ```js 6 | element('div', null, [ 7 | [element('div', null, 'Hello World')] // This can't be an array! 8 | ]) 9 | ``` 10 | 11 | This often happens if you're using `props.children` directly in the virtual node, because it is an array: 12 | 13 | 14 | ```js 15 | element('div', null, [ 16 | element('div'), 17 | props.children // This will throw! 18 | ]) 19 | ``` 20 | 21 | ## Solution 22 | 23 | Flatten the array of children. There are a few ways to do this. One is using ES6 spread: 24 | 25 | ```js 26 | element('div', null, [ 27 | element('div'), 28 | ...props.children 29 | ]) 30 | ``` 31 | 32 | Another is to build up the arrays and join them together using an array method like `splice` or `concat`: 33 | 34 | ```js 35 | var children = [element('div')] 36 | element('div', null, children.concat(props.children)) 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/examples/server.js: -------------------------------------------------------------------------------- 1 | 2 | var babel = require('duo-babel'); 3 | var express = require('express'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var serve = require('duo-serve'); 7 | var app = module.exports = express(); 8 | 9 | app.get('/', function (req, res) { 10 | res.sendFile(path.resolve(__dirname, 'index.html')); 11 | }); 12 | 13 | var examples = fs.readdirSync(__dirname).filter(function (example) { 14 | return fs.statSync(path.resolve(__dirname, example)).isDirectory(); 15 | }); 16 | 17 | examples.forEach(function (example) { 18 | app.use('/' + example, subapp(example)); 19 | }); 20 | 21 | app.listen(3000, function () { 22 | console.log(); 23 | console.log(' > Deku examples server listening at http://localhost:3000'); 24 | console.log(); 25 | }); 26 | 27 | function subapp(example) { 28 | return serve(path.resolve(__dirname, example)) 29 | .title(example) 30 | .use(babel({ jsxPragma: 'element' })) 31 | .entry('index.js') 32 | .entry('index.css') 33 | .server(); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) Copyright (c) 2015 Anthony Short 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/examples/counter/react.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | class Counter extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | secondsElapsed: 0 8 | }; 9 | } 10 | 11 | render () { 12 | let { secondsElapsed } = this.state; 13 | return ( 14 | Seconds Elapsed: { secondsElapsed } 15 | ); 16 | } 17 | 18 | componentWillUpdate() { 19 | let { secondsElapsed } = this.state; 20 | let { onAnEmittedEventFromCounter } = this.props; 21 | if (secondsElapsed && secondsElapsed % 10 === 0) onAnEmittedEventFromCounter(); 22 | } 23 | 24 | componentDidMount () { 25 | let self = this; 26 | var counter = 0; 27 | this.interval = setInterval(() => { 28 | self.setState({ secondsElapsed: counter++ }) 29 | }, 1000); 30 | } 31 | 32 | componentWillUnmount () { 33 | clearInterval(this.interval); 34 | } 35 | } 36 | 37 | function doSomething() { 38 | console.log('I did something.'); 39 | } 40 | 41 | React.render(, document.body); 42 | -------------------------------------------------------------------------------- /docs/hints/empty-container.md: -------------------------------------------------------------------------------- 1 | # The container element is not empty 2 | 3 | This warning is shown when you try to render into an element that already has children inside of it. 4 | 5 | You might be rendering like this: 6 | 7 | ```js 8 | var app = tree() 9 | render(app, document.getElementById('app')) 10 | ``` 11 | 12 | And your HTML looks like this: 13 | 14 | ```html 15 | 16 |
17 |

App goes here

18 |
19 | 20 | ``` 21 | 22 | When calling `render` and using the `#app` as the container, it will remove the content of that container and replace it with the app. 23 | 24 | ## Server-rendering 25 | 26 | You'll get this warning if you're rendering your app first on the server. The HTML that is rendered by the server will be replaced with the new elements when you call `render`. 27 | 28 | ## Solution 29 | 30 | You can ignore this warning if you're just replacing content rendered on the server. 31 | 32 | Otherwise you should use an empty element that's only used to render your app into: 33 | 34 | ```html 35 | 36 |
37 | 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/hints/no-type.md: -------------------------------------------------------------------------------- 1 | # Element doesn't have a type 2 | 3 | This error can occur when a component object is `null` or `undefined`. 4 | 5 | When using JSX, you might write something like this: 6 | 7 | ```js 8 | function render (component) { 9 | return
Hello
10 | } 11 | ``` 12 | 13 | This is transformed into a function that creates virtual elements: 14 | 15 | ```js 16 | function render (component) { 17 | return element('div', {}, ['Hello']) 18 | } 19 | ``` 20 | 21 | When using components, the first attribute will be your component object: 22 | 23 | ```js 24 | function render (component) { 25 | return element(MyButton, {}, ['Hello']) 26 | } 27 | ``` 28 | 29 | ## Solution 30 | 31 | ### Make sure your `import`s are correct 32 | 33 | ```js 34 | // The file doesn't exist 35 | import Foo from './foo' 36 | 37 | // The file does not export 'Bar' 38 | import Bar from './bar' 39 | ``` 40 | 41 | When using ES6 modules with Babel if a module can't be found it will be `undefined` instead of throwing an error. When you try to render virtual elements, the first parameter will be null. Make sure you a importing the correct values from the module. 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deku", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "repository": "dekujs/deku", 6 | "description": "Create view components using a virtual DOM", 7 | "main": "lib/index.js", 8 | "devDependencies": { 9 | "babelify": "6.4.0", 10 | "bfc": "0.3.1", 11 | "browserify": "12.0.1", 12 | "bump": "0.2.5", 13 | "duo-babel": "^6.0.0", 14 | "duo-serve": "^0.14.5", 15 | "envify": "3.4.0", 16 | "express": "^4.13.3", 17 | "hihat": "2.5.0", 18 | "minify": "2.0.1", 19 | "snazzy": "2.0.1", 20 | "standard": "5.3.1", 21 | "tap-dev-tool": "1.3.0", 22 | "tape": "4.2.2", 23 | "trigger-event": "1.0.2", 24 | "virtual-element": "1.2.0", 25 | "zuul": "3.7.2" 26 | }, 27 | "dependencies": { 28 | "component-emitter": "1.2.0", 29 | "component-raf": "1.2.0", 30 | "component-type": "1.2.0", 31 | "fast.js": "0.1.1", 32 | "get-uid": "1.0.1", 33 | "is-dom": "1.0.5", 34 | "is-svg-attribute": "1.0.2", 35 | "is-svg-element": "1.0.1", 36 | "object-defaults": "0.1.0", 37 | "object-path": "0.9.2" 38 | }, 39 | "scripts": { 40 | "start": "node docs/examples/server.js", 41 | "test": "make test" 42 | } 43 | } -------------------------------------------------------------------------------- /docs/examples/counter/index.js: -------------------------------------------------------------------------------- 1 | import element from 'dekujs/virtual-element' 2 | import { render, tree } from 'dekujs/deku' 3 | 4 | const intervals = {}; 5 | 6 | let Counter = { 7 | initialState () { 8 | return { 9 | secondsElapsed: 0 10 | } 11 | }, 12 | 13 | render (component) { 14 | let { props, state } = component 15 | let { secondsElapsed } = state; 16 | return element('span', [ 'Seconds Elapsed: ' + secondsElapsed ]); 17 | }, 18 | 19 | afterUpdate (component) { 20 | let { props, state } = component; 21 | if (state.secondsElapsed && state.secondsElapsed % 10 === 0) props.onAnEmittedEventFromCounter(); 22 | }, 23 | 24 | afterMount (component, el, setState) { 25 | let counter = 0; 26 | intervals[component.id] = setInterval(() => { 27 | setState({ secondsElapsed: counter++ }) 28 | }, 1000); 29 | }, 30 | 31 | beforeUnmount (component) { 32 | clearInterval(intervals[component.id]); 33 | delete intervals[component.id]; 34 | } 35 | } 36 | 37 | function doSomething() { 38 | console.log('I did something.'); 39 | } 40 | 41 | let counter = tree( 42 | element(Counter, { onAnEmittedEventFromCounter: doSomething }) 43 | ); 44 | 45 | render(counter, document.body); 46 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Binaries. 3 | # 4 | 5 | export PATH := ./node_modules/.bin:${PATH} 6 | BIN := ./node_modules/.bin 7 | 8 | # 9 | # Wildcards. 10 | # 11 | 12 | src = $(shell find lib/*.js) 13 | tests = $(shell find test/**/*.js) 14 | 15 | # 16 | # Targets. 17 | # 18 | 19 | default: test 20 | $(src): node_modules 21 | $(tests): node_modules 22 | 23 | standalone: $(src) 24 | @mkdir -p build 25 | @NODE_ENV=production browserify \ 26 | --standalone deku \ 27 | -t envify \ 28 | -e lib/index.js | bfc > build/deku.js 29 | 30 | test: $(src) $(tests) 31 | @NODE_ENV=development hihat test/index.js -- \ 32 | --debug \ 33 | -t envify \ 34 | -t babelify \ 35 | -p tap-dev-tool 36 | 37 | test-cloud: node_modules 38 | @TRAVIS_BUILD_NUMBER=$(CIRCLE_BUILD_NUM) zuul -- ./test/index.js 39 | 40 | node_modules: package.json 41 | @npm install 42 | 43 | clean: 44 | @-rm -rf build build.js node_modules 45 | 46 | lint: $(src) $(tests) 47 | standard lib/**/*.js | snazzy 48 | 49 | size: standalone 50 | @minify build/deku.js | gzip -9 | wc -c 51 | 52 | # 53 | # Releases. 54 | # 55 | 56 | release: standalone 57 | bump $$VERSION && \ 58 | git changelog --tag $$VERSION && \ 59 | git commit --all -m "Release $$VERSION" && \ 60 | git tag $$VERSION && \ 61 | git push origin master --tags && \ 62 | npm publish 63 | 64 | # 65 | # These tasks will be run every time regardless of dependencies. 66 | # 67 | 68 | .PHONY: standalone 69 | .PHONY: clean 70 | .PHONY: lint 71 | .PHONY: size 72 | .PHONY: release 73 | .PHONY: test-cloud 74 | -------------------------------------------------------------------------------- /lib/application.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var Emitter = require('component-emitter') 6 | 7 | /** 8 | * Expose `scene`. 9 | */ 10 | 11 | module.exports = Application 12 | 13 | /** 14 | * Create a new `Application`. 15 | * 16 | * @param {Object} element Optional initial element 17 | */ 18 | 19 | function Application (element) { 20 | if (!(this instanceof Application)) return new Application(element) 21 | this.options = {} 22 | this.sources = {} 23 | this.element = element 24 | } 25 | 26 | /** 27 | * Mixin `Emitter`. 28 | */ 29 | 30 | Emitter(Application.prototype) 31 | 32 | /** 33 | * Add a plugin 34 | * 35 | * @param {Function} plugin 36 | */ 37 | 38 | Application.prototype.use = function (plugin) { 39 | plugin(this) 40 | return this 41 | } 42 | 43 | /** 44 | * Set an option 45 | * 46 | * @param {String} name 47 | */ 48 | 49 | Application.prototype.option = function (name, val) { 50 | this.options[name] = val 51 | return this 52 | } 53 | 54 | /** 55 | * Set value used somewhere in the IO network. 56 | */ 57 | 58 | Application.prototype.set = function (name, data) { 59 | this.sources[name] = data 60 | this.emit('source', name, data) 61 | return this 62 | } 63 | 64 | /** 65 | * Mount a virtual element. 66 | * 67 | * @param {VirtualElement} element 68 | */ 69 | 70 | Application.prototype.mount = function (element) { 71 | this.element = element 72 | this.emit('mount', element) 73 | return this 74 | } 75 | 76 | /** 77 | * Remove the world. Unmount everything. 78 | */ 79 | 80 | Application.prototype.unmount = function () { 81 | if (!this.element) return 82 | this.element = null 83 | this.emit('unmount') 84 | return this 85 | } 86 | -------------------------------------------------------------------------------- /lib/events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * All of the events can bind to 3 | */ 4 | 5 | module.exports = { 6 | onAbort: 'abort', 7 | onBlur: 'blur', 8 | onCanPlay: 'canplay', 9 | onCanPlayThrough: 'canplaythrough', 10 | onChange: 'change', 11 | onClick: 'click', 12 | onContextMenu: 'contextmenu', 13 | onCopy: 'copy', 14 | onCut: 'cut', 15 | onDoubleClick: 'dblclick', 16 | onDrag: 'drag', 17 | onDragEnd: 'dragend', 18 | onDragEnter: 'dragenter', 19 | onDragExit: 'dragexit', 20 | onDragLeave: 'dragleave', 21 | onDragOver: 'dragover', 22 | onDragStart: 'dragstart', 23 | onDrop: 'drop', 24 | onDurationChange: 'durationchange', 25 | onEmptied: 'emptied', 26 | onEncrypted: 'encrypted', 27 | onEnded: 'ended', 28 | onError: 'error', 29 | onFocus: 'focus', 30 | onInput: 'input', 31 | onInvalid: 'invalid', 32 | onKeyDown: 'keydown', 33 | onKeyPress: 'keypress', 34 | onKeyUp: 'keyup', 35 | onLoad: 'load', 36 | onLoadedData: 'loadeddata', 37 | onLoadedMetadata: 'loadedmetadata', 38 | onLoadStart: 'loadstart', 39 | onPause: 'pause', 40 | onPlay: 'play', 41 | onPlaying: 'playing', 42 | onProgress: 'progress', 43 | onMouseDown: 'mousedown', 44 | onMouseEnter: 'mouseenter', 45 | onMouseLeave: 'mouseleave', 46 | onMouseMove: 'mousemove', 47 | onMouseOut: 'mouseout', 48 | onMouseOver: 'mouseover', 49 | onMouseUp: 'mouseup', 50 | onPaste: 'paste', 51 | onRateChange: 'ratechange', 52 | onReset: 'reset', 53 | onScroll: 'scroll', 54 | onSeeked: 'seeked', 55 | onSeeking: 'seeking', 56 | onSubmit: 'submit', 57 | onStalled: 'stalled', 58 | onSuspend: 'suspend', 59 | onTimeUpdate: 'timeupdate', 60 | onTouchCancel: 'touchcancel', 61 | onTouchEnd: 'touchend', 62 | onTouchMove: 'touchmove', 63 | onTouchStart: 'touchstart', 64 | onVolumeChange: 'volumechange', 65 | onWaiting: 'waiting', 66 | onWheel: 'wheel' 67 | } 68 | -------------------------------------------------------------------------------- /docs/guides/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | There are multiple ways to install and use Deku. No matter which option you choose, you should probably look at using an ES6 transformer like [Babel](https://babeljs.io). 4 | 5 | ## Browserify 6 | 7 | ``` 8 | npm install deku 9 | ``` 10 | 11 | Browserify is the easiest way to use Deku if you're planning on doing server and client-side rendering. It also means it's very easy to use Babel to enable ES6 and JSX transforms using `babelify`. 12 | 13 | ``` 14 | browserify -t babelify main.js > build.js 15 | ``` 16 | 17 | ## Webpack 18 | 19 | ``` 20 | npm install deku 21 | ``` 22 | 23 | Like browserify, webpack analyzes all the require() calls in your app and builds a bundle that you can serve up to the browser. 24 | 25 | ``` 26 | webpack main.js build.js 27 | ``` 28 | 29 | The best way to configure webpack is with a `webpack.config.js`. 30 | 31 | ``` 32 | webpack --config webpack.config.js 33 | ``` 34 | 35 | Webpack uses loaders, and we can use the [babel loader](https://github.com/babel/babel-loader) to transform ES6 and JSX to ES5 as shown below in this example `webpack.config.js`. 36 | 37 | ```js 38 | module.exports = { 39 | entry: './main.js', 40 | output: { path: __dirname, filename: 'bundle.js' }, 41 | module: { 42 | loaders: [ 43 | { 44 | test: /\.jsx?$/, 45 | exclude: /(node_modules)/, 46 | loader: 'babel' 47 | } 48 | ] 49 | } 50 | } 51 | ``` 52 | 53 | ## Duo 54 | 55 | ``` 56 | import {tree,render} from 'dekujs/deku@0.5.0' 57 | import element from 'dekujs/virtual-element@1.1.1' 58 | ``` 59 | 60 | With Duo you can just import directly from Github, then build it: 61 | 62 | ``` 63 | duo main.js > build.js 64 | ``` 65 | 66 | To use ES6 and JSX you'll need to install the [duo-babel](https://github.com/babel/duo-babel) transform: 67 | 68 | ``` 69 | duo --use duo-babel main.js > build.js 70 | ``` 71 | 72 | ## Bower 73 | 74 | ``` 75 | bower install deku 76 | ``` 77 | 78 | ## Manual Download 79 | 80 | You can download the files manually from the [releases page](https://github.com/segmentio/deku/releases). 81 | -------------------------------------------------------------------------------- /docs/guides/jsx.md: -------------------------------------------------------------------------------- 1 | # Using JSX 2 | 3 | Without JSX, you'll use the `element` function to build up virtual elements that represent components and DOM elements: 4 | 5 | ```js 6 | export function render (component) { 7 | let {props, state, id} = component; 8 | return element('a', { class: "button", onClick: onClick }, [props.text]) 9 | } 10 | ``` 11 | 12 | JSX just makes this easier to write and read by making it read like HTML: 13 | 14 | ```js 15 | export function render (component) { 16 | let {props, state, id} = component; 17 | return {props.text}; 18 | } 19 | ``` 20 | 21 | There are a number of libraries around now that can transpile JSX into JS that aren't tied to React. The easiest way to use JSX with Deku is to use [Babel](https://github.com/babel/babel). 22 | 23 | ## .babelrc 24 | 25 | The easiest way is to install Babel's [React JSX Transform plugin](http://babeljs.io/docs/plugins/transform-react-jsx/): 26 | 27 | `npm install babel-plugin-transform-react-jsx` 28 | 29 | and then, include it via your `.babelrc` file: 30 | 31 | ``` 32 | { 33 | "plugins": [ 34 | ["transform-react-jsx", { "pragma": "element" }] 35 | ] 36 | } 37 | ``` 38 | 39 | See [here](https://github.com/dekujs/deku/blob/b4ebc98eb8eb295c59f0aa07bea2a8c3257ad827/docs/guides/jsx.md#babelrc) for Babel 5 instructions. 40 | 41 | Then make sure you import the `element` function: 42 | 43 | ```js 44 | import element from 'virtual-element' 45 | 46 | export function render (component) { 47 | let {props, state, id} = component; 48 | return {props.text} 49 | } 50 | ``` 51 | 52 | ## Comment 53 | 54 | You can also add a comment to the top of your files that tells babel which function to use when replacing JSX: 55 | 56 | ```js 57 | /** @jsx element */ 58 | import element from 'virtual-element' 59 | 60 | export function render (component) { 61 | let {props, state, id} = component; 62 | return {props.text}; 63 | } 64 | ``` 65 | 66 | ## Other transforms 67 | 68 | You can also use [jsx-transform](https://github.com/alexmingoia/jsx-transform) if you're looking for something simple. 69 | -------------------------------------------------------------------------------- /docs/examples/iteration/index.js: -------------------------------------------------------------------------------- 1 | import element from 'dekujs/virtual-element' 2 | import { render, tree } from 'dekujs/deku' 3 | 4 | var ListItem = { 5 | render (component) { 6 | let { props, state } = component; 7 | let { item } = props; 8 | return ( 9 |
  • 10 | { item.name } 11 | 12 |
  • 13 | ); 14 | } 15 | }; 16 | 17 | let List = { 18 | initialState() { 19 | return { 20 | items: [ 21 | {"name": "Barry"}, 22 | {"name": "Trevor"} 23 | ] 24 | } 25 | }, 26 | 27 | render(component, setState) { 28 | var { props, state } = component; 29 | 30 | function removeItem(item) { 31 | return function() { 32 | let updatedItems = state.items.filter(function(target){ 33 | return target !== item 34 | }); 35 | setState({ items: updatedItems }); 36 | } 37 | } 38 | 39 | function addItem(e){ 40 | e.preventDefault(); 41 | let val = component.input.value; 42 | if (!val) return; 43 | component.input.value = ""; 44 | 45 | let existingItems = state.items; 46 | existingItems.push({"name": val}); 47 | setState({ items: existingItems }); 48 | } 49 | 50 | function reverseItems(){ 51 | let reversed = state.items.reverse(); 52 | setState({ items: reversed }); 53 | } 54 | 55 | function sortItems(){ 56 | let sorted = state.items.sort(function(a, b){ 57 | a = a.name.toLowerCase(); 58 | b = b.name.toLowerCase(); 59 | if (a > b) return 1; 60 | if (a < b) return -1; 61 | return 0; 62 | }); 63 | setState({ items: sorted }); 64 | } 65 | 66 | let items = state.items.map((item, index) => { 67 | return ( 68 | 69 | ); 70 | }); 71 | 72 | return ( 73 |
    74 |
      75 | { items } 76 |
    77 | 78 | 79 | 80 | 81 |
    82 | ); 83 | }, 84 | 85 | afterRender(component, el) { 86 | component.input = el.querySelector('input'); 87 | } 88 | } 89 | 90 | let list = tree( 91 | 92 | ); 93 | 94 | render(list, document.body); 95 | -------------------------------------------------------------------------------- /docs/examples/iteration/react.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | /** 4 | * ListItem 5 | */ 6 | 7 | class ListItem extends React.Component { 8 | render() { 9 | let props = this.props; 10 | return ( 11 |
  • 12 | { props.item.name } 13 | 14 |
  • 15 | ); 16 | } 17 | }; 18 | 19 | 20 | /** 21 | * List 22 | */ 23 | 24 | class List extends React.Component { 25 | constructor(props) { 26 | super(props); 27 | this.state = { 28 | items: [ 29 | {"name": "Barry"}, 30 | {"name": "Trevor"} 31 | ] 32 | }; 33 | } 34 | 35 | componentDidMount() { 36 | this.input = this.refs.input.getDOMNode(); 37 | } 38 | 39 | removeItem(e) { 40 | e.preventDefault(); 41 | let el = e.target.parentElement; 42 | let item = el.getAttribute('data-item'); 43 | let { items } = this.state; 44 | 45 | let updatedItems = items.filter((target) => { 46 | return JSON.stringify(target) !== item; 47 | }); 48 | 49 | this.setState({ 50 | items: updatedItems 51 | }); 52 | } 53 | 54 | addItem(e){ 55 | e.preventDefault(); 56 | let val = this.input.value; 57 | if (!val) return; 58 | this.input.value = ""; 59 | 60 | let { items } = this.state; 61 | items.push({"name": val}) 62 | this.setState({ items: items }); 63 | } 64 | 65 | reverseItems(){ 66 | let { items } = this.state; 67 | this.setState({ items: items.reverse() }); 68 | } 69 | 70 | sortItems(){ 71 | let { items } = this.state; 72 | 73 | let sorted = items.sort((a, b) => { 74 | a = a.name.toLowerCase(); 75 | b = b.name.toLowerCase(); 76 | if (a > b) return 1; 77 | if (a < b) return -1; 78 | return 0; 79 | }); 80 | 81 | this.setState({ items: sorted }); 82 | } 83 | 84 | renderItem(item) { 85 | return ( 86 | 87 | ); 88 | } 89 | 90 | render() { 91 | let { items } = this.state; 92 | 93 | return ( 94 |
    95 |
      96 | { items.map(this.renderItem.bind(this)) } 97 |
    98 | 99 | 100 | 101 | 102 |
    103 | ); 104 | } 105 | }; 106 | 107 | React.render(, document.body); 108 | -------------------------------------------------------------------------------- /lib/stringify.js: -------------------------------------------------------------------------------- 1 | var defaults = require('object-defaults') 2 | var nodeType = require('./node-type') 3 | var type = require('component-type') 4 | 5 | /** 6 | * Expose `stringify`. 7 | */ 8 | 9 | module.exports = function (app) { 10 | if (!app.element) { 11 | throw new Error('No element mounted') 12 | } 13 | 14 | /** 15 | * Render to string. 16 | * 17 | * @param {Component} component 18 | * @param {Object} [props] 19 | * @return {String} 20 | */ 21 | 22 | function stringify (component, optProps, children) { 23 | var propTypes = component.propTypes || {} 24 | var props = defaults(optProps || {}, component.defaultProps || {}) 25 | var state = component.initialState ? component.initialState(props) : {} 26 | props.children = children 27 | 28 | for (var name in propTypes) { 29 | var options = propTypes[name] 30 | if (options.source) { 31 | props[name] = app.sources[options.source] 32 | } 33 | } 34 | 35 | if (component.beforeMount) component.beforeMount({ props: props, state: state }) 36 | if (component.beforeRender) component.beforeRender({ props: props, state: state }) 37 | var node = component.render({ props: props, state: state }) 38 | return stringifyNode(node, '0') 39 | } 40 | 41 | /** 42 | * Render a node to a string 43 | * 44 | * @param {Node} node 45 | * @param {Tree} tree 46 | * 47 | * @return {String} 48 | */ 49 | 50 | function stringifyNode (node, path) { 51 | switch (nodeType(node)) { 52 | case 'empty': return '