├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── lib ├── index.js ├── node.js └── text.js ├── package.json └── test └── main.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | ## SUBLIME 2 | *.sublime* 3 | 4 | # OSX 5 | *.DS_Store 6 | 7 | # NPM 8 | node_modules 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Tests 2 | test 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## 4.2.0 - 2017-04-01 8 | ### Changed 9 | - Perf optimizations. 10 | - Non virtual children are now automatically cast as text nodes. 11 | 12 | ## 4.1.1 - 2017-03-20 13 | ### Changed 14 | - Improved inline docs. 15 | 16 | ## 4.1.0 - 2016-08-29 17 | ### Changed 18 | - Components now receive `props.children` instead of just second argument. 19 | 20 | ## 4.0.1, 4.0.2 - 2016-08-29 21 | ### Added 22 | - CHANGELOG.md 23 | 24 | ### Changed 25 | - Updated dev dependencies. 26 | - Removed gulp.js for testing. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) 2 | ![npm](https://img.shields.io/npm/dm/vdo.svg) 3 | 4 | # VDO 5 | 6 | The lightweight JSX compatible templating engine. 7 | Perfect for creating html strings server side or in browser. 8 | 9 | Check out [set-dom](https://github.com/DylanPiercey/set-dom), [diffhtml](https://github.com/tbranyen/diffhtml) or [morphdom](https://github.com/patrick-steele-idem/morphdom) for React style diffing. 10 | 11 | # Why 12 | JSX is powerful compared to other templating engines but it has some warts. 13 | It has some abstractions that simply don't make sense for creating html (ala className). 14 | "VDO" provides a JSX interface that is specifically designed for rendering html, not DOM. 15 | 16 | ### Features 17 | * Minimal API. 18 | * ~1kb min/gzip. 19 | * No extra "data-react-id". 20 | * No random span's. 21 | * Allows any custom attribute (react only allows data-). 22 | * Render nested arrays. 23 | * Optimized for rendering html. 24 | * Escaped values by default. 25 | * JSX compatible. 26 | 27 | # Installation 28 | 29 | #### Npm 30 | ```console 31 | npm install vdo 32 | ``` 33 | 34 | # Example 35 | 36 | ```javascript 37 | /** @jsx vdo */ 38 | const vdo = require('vdo'); 39 | 40 | function MyPartial (attrs, children) { 41 | return ; 42 | } 43 | 44 | const html = ( 45 |
46 | 47 |
48 | ); 49 | 50 | document.body.innerHTML = html; 51 | 52 | ``` 53 | 54 | # API 55 | + **isElement(element)** : Tests if given `element` is a vdo virtual element. 56 | 57 | 58 | ```javascript 59 | vdo.isElement(
); // true 60 | ``` 61 | 62 | + **markSafe(html)** : Marks html as safe as a VDO child node. 63 | 64 | 65 | ```javascript 66 | // Use #markSafe instead of "innerHTML" when coming from react. 67 | // This allows for mixes of safe and unsafe html in the same node. 68 | const myHTMLStr = "
"; 69 | const vNode =
{ vdo.markSafe(myHTMLStr) }
; 70 | String(vNode); //->

71 | ``` 72 | 73 | + **with(context, renderer)** : Gives all components inside a render function some external `context`. 74 | 75 | 76 | ```javascript 77 | // renderer must be a function that returns a virtual node. 78 | function MyComponent (props, children, context) { 79 | return ( 80 |
External data: { context }
81 | ); 82 | } 83 | 84 | String(vdo.with(1, ()=> )); 85 | //-> "
External Data: 1
" 86 | ``` 87 | 88 | + **createElement(type, props, children...)** : Create a virtual node/component. 89 | 90 | ```javascript 91 | // Automatically called when using JSX. 92 | let vNode = vdo.createElement("div", { editable: true }, "Hello World"); 93 | // Or call vdo directly 94 | let vNode = vdo("div", { editable: true }, "Hello World"); 95 | 96 | // Render to string on the server. 97 | vNode.toString(); // '
Hello World
'; 98 | 99 | /** 100 | * @params type can also be a function (shown in example above). 101 | */ 102 | ``` 103 | 104 | --- 105 | 106 | ### Contributions 107 | 108 | * Use `npm test` to run tests. 109 | 110 | Please feel free to create a PR! 111 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var Node = require('./node') 4 | var Text = require('./text') 5 | var _context 6 | 7 | // Expose module. 8 | module.exports = vdo['default'] = vdo.createElement = vdo 9 | 10 | /** 11 | * Utility to create virtual elements. 12 | * If the given type is a string then the resulting virtual node will be created with a tagname of that string. 13 | * Otherwise if the type is a function it will be invoked, and the returned nodes used. 14 | * 15 | * @example 16 | * vdo("div", { id: "example" }, ...); // -> Node 17 | * 18 | * @param {string|Function} type - A nodeName or a function that returns a Node. 19 | * @param {object} attributes - The events and attributes for the resulting element. 20 | * @param {Node[]} childNodes - The childNodes for the resulting element. 21 | * @throws {TypeError} type must be a function or a string. 22 | * @return {Node} 23 | */ 24 | function vdo (type, attributes /* ...childNodes: Node[] */) { 25 | attributes = attributes || {} 26 | 27 | // Convert child arguments to an array. 28 | var childNodes = new Array(Math.max(arguments.length - 2, 0)) 29 | for (var i = childNodes.length; i--;) childNodes[i] = arguments[i + 2] 30 | childNodes = normalizeChildren(childNodes, []) 31 | 32 | switch (typeof type) { 33 | case 'string': 34 | return new Node(type, attributes, childNodes) 35 | case 'function': 36 | attributes.children = childNodes 37 | return type(attributes, childNodes, _context) 38 | default: 39 | throw new TypeError('VDO: Invalid virtual node type.') 40 | } 41 | } 42 | 43 | /** 44 | * Check if an element is a virtual vdo element. 45 | * 46 | * @example 47 | * vdo.isElement(Hello World!); //-> true 48 | * 49 | * @param {*} node - The node to test. 50 | * @returns {Boolean} 51 | */ 52 | vdo.isElement = function (node) { 53 | return Boolean(node && node.isVirtual) 54 | } 55 | 56 | /** 57 | * Marks a string of html as safe to be used as a child node. 58 | * 59 | * @example 60 | * const html = "" 61 | * const node = {vdo.markSafe(html)} 62 | * String(node) //-> "" 63 | * 64 | * @param {*} html - The html to mark as safe. 65 | * @returns {Safe} 66 | */ 67 | vdo.markSafe = function (html) { 68 | return new Text(html, true) 69 | } 70 | 71 | /** 72 | * Utility to attach context to #createElement for sideways data loading. 73 | * The provided renderer will be immediately invoked. 74 | * 75 | * @example 76 | * let MyComponent = function (attributes, childNodes, context) { 77 | * return ( 78 | * 79 | * Counter: { context.counter } 80 | * 81 | * ) 82 | * } 83 | * 84 | * let html = vdo.with({ counter: 1 }, function () { 85 | * return ( 86 | * 87 | * ) 88 | * }) 89 | * 90 | * html; //-> "Counter: 1" 91 | * 92 | * @param {*} context - A value that any custom render functions will be invoked with. 93 | * @param {Function} renderer - Any nodes rendered within the function will be called with the context. 94 | * @throws {TypeError} The result of renderer must be a Node. 95 | * @returns {Node} 96 | */ 97 | vdo.with = function (context, renderer) { 98 | if (typeof renderer !== 'function') throw new TypeError('VDO: renderer should be a function.') 99 | 100 | _context = context 101 | var node = renderer(context) 102 | _context = undefined 103 | return node 104 | } 105 | 106 | /** 107 | * Flattens an arbitrarily deep array and casts non virtual nodes to text nodes. 108 | * 109 | * @param {*} arr - the nested array. 110 | * @param {Array} result - the array to add the results to. 111 | * @return {Array} 112 | */ 113 | function normalizeChildren (arr, result) { 114 | for (var i = 0, len = arr.length, item; i < len; i++) { 115 | item = arr[i] 116 | // Skip nullish or false items. 117 | if (item == null || item === false) continue 118 | else if (Array.isArray(item)) normalizeChildren(item, result) 119 | else result.push(item.isVirtual ? item : new Text(item)) 120 | } 121 | 122 | return result 123 | } 124 | -------------------------------------------------------------------------------- /lib/node.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var escape = require('escape-html') 4 | var CLOSED = { 5 | 'area': 1, 6 | 'base': 1, 7 | 'br': 1, 8 | 'col': 1, 9 | 'command': 1, 10 | 'embed': 1, 11 | 'hr': 1, 12 | 'img': 1, 13 | 'input': 1, 14 | 'keygen': 1, 15 | 'link': 1, 16 | 'meta': 1, 17 | 'param': 1, 18 | 'source': 1, 19 | 'track': 1, 20 | 'wbr': 1 21 | } 22 | 23 | // Expose module. 24 | module.exports = Node 25 | 26 | /** 27 | * @private 28 | * @class Node 29 | * Creates a virtual node that can be converted into html. 30 | * 31 | * @param {string} nodeName - The tagname of the element. 32 | * @param {object} attributes - An object containing events and attributes. 33 | * @param {Array} childNodes - The child nodeList for the element. 34 | */ 35 | function Node (nodeName, attributes, childNodes) { 36 | this.nodeName = nodeName 37 | this.attributes = attributes 38 | this.childNodes = CLOSED[nodeName] ? false : childNodes 39 | } 40 | 41 | /** 42 | * @private 43 | * @constant 44 | * Node type for diffing algorithms. 45 | * Regular nodes are "ELEMENT_TYPE". 46 | */ 47 | Node.prototype.nodeType = 1 48 | 49 | /** 50 | * @private 51 | * @constant 52 | * Mark instances as VDO nodes. 53 | */ 54 | Node.prototype.isVirtual = true 55 | 56 | /** 57 | * Generate valid html for the virtual node. 58 | * 59 | * @returns {string} 60 | */ 61 | Node.prototype.toString = function () { 62 | var key, attr 63 | var attributes = '' 64 | var nodeName = this.nodeName 65 | var childNodes = this.childNodes 66 | 67 | // Build attributes string. 68 | for (key in this.attributes) { 69 | attr = this.attributes[key] 70 | if (attr == null || attr === false) continue 71 | if (attr === true) attributes += ' ' + key 72 | else attributes += ' ' + key + '="' + escape(attr) + '"' 73 | } 74 | 75 | return ( 76 | '<' + nodeName + attributes + '>' + ( 77 | // Self closing nodes will not have childNodes or an end tag. 78 | childNodes === false 79 | ? '' 80 | : childNodes.join('') + '' 81 | ) 82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /lib/text.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var escape = require('escape-html') 4 | 5 | // Expose module. 6 | module.exports = Text 7 | 8 | /** 9 | * @private 10 | * @class Text 11 | * Creates a virtual text node. 12 | * 13 | * @param {string} html - the html to be used in vdo. 14 | * @param {boolean} [raw] - if true raw html will be used (escaped by default). 15 | */ 16 | function Text (html, raw) { 17 | if (html != null) { 18 | this.nodeValue = String(html) 19 | if (!raw) this.nodeValue = escape(this.nodeValue) 20 | } 21 | } 22 | 23 | /** 24 | * @private 25 | * @constant 26 | * Mark instances as VDO nodes. 27 | */ 28 | Text.prototype.isVirtual = true 29 | 30 | /** 31 | * @private 32 | * @constant 33 | * Node type for diffing algorithms. 34 | * Text nodes are "TEXT_TYPE". 35 | */ 36 | Text.prototype.nodeType = 3 37 | 38 | /** 39 | * Return the stored safe html. 40 | * 41 | * @returns {String} 42 | */ 43 | Text.prototype.toString = function () { 44 | return this.nodeValue 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vdo", 3 | "description": "Minimal JSX compatible html focused templating engine.", 4 | "version": "4.2.0", 5 | "author": "Dylan Piercey ", 6 | "bugs": "https://github.com/DylanPiercey/vdo/issues", 7 | "dependencies": { 8 | "escape-html": "^1.0.3", 9 | "flatten": "^1.0.2" 10 | }, 11 | "devDependencies": { 12 | "mocha": "^3.2.0", 13 | "snazzy": "^6.0.0", 14 | "standard": "^9.0.2" 15 | }, 16 | "homepage": "https://github.com/DylanPiercey/vdo", 17 | "keywords": [ 18 | "jsx", 19 | "react", 20 | "render", 21 | "string", 22 | "template" 23 | ], 24 | "license": "MIT", 25 | "main": "lib/index.js", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/DylanPiercey/vdo" 29 | }, 30 | "scripts": { 31 | "test": "standard --verbose | snazzy && mocha ./test/**/*.test.js" 32 | }, 33 | "standard": { 34 | "globals": [ 35 | "describe", 36 | "it", 37 | "before", 38 | "beforeEach", 39 | "after", 40 | "afterEach" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/main.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var vdo = require('../lib') 3 | 4 | describe('Function', function () { 5 | it('should test for valid vdo elements', function () { 6 | assert.equal(vdo.isElement(vdo('div')), true) 7 | assert.equal(vdo.isElement(1), false) 8 | }) 9 | it('should be able to create', function () { 10 | var ChildComponent, MyComponent 11 | ChildComponent = function (props, children) { 12 | assert.deepEqual(props.children, children) 13 | return vdo('h1', null, children.map(function (child, i) { 14 | child.attributes = { 15 | 'class': 'child-' + i 16 | } 17 | return child 18 | })) 19 | } 20 | MyComponent = function (attrs, children) { 21 | var value = attrs.value 22 | return vdo('div', null, 23 | vdo(ChildComponent, { value: value * 2 }, 24 | Array.from(Array(value + 1)).map(function (j, i) { 25 | return vdo('span', null, i) 26 | }) 27 | ) 28 | ) 29 | } 30 | assert.equal( 31 | String(vdo(MyComponent, { value: 5 })), 32 | '

012345

' 33 | ) 34 | }) 35 | it('should be able to set a context', function () { 36 | assert.equal(vdo['with'](1, function () { 37 | return vdo(MyComponent) 38 | }).toString(), '
1
') 39 | 40 | function MyComponent (props, children, context) { 41 | return vdo('div', null, context) 42 | } 43 | }) 44 | }) 45 | 46 | describe('Node', function () { 47 | it('should be able to create', function () { 48 | assert.equal(vdo('div').nodeName, 'div') 49 | }) 50 | it('should be able to set attributes', function () { 51 | var node = vdo('div', { a: undefined, b: null, c: false, d: true, e: 1, f: 'hi' }) 52 | assert.equal(node.nodeName, 'div') 53 | assert.deepEqual(node.attributes, { a: undefined, b: null, c: false, d: true, e: 1, f: 'hi' }) 54 | assert.equal(node.toString(), '
') 55 | }) 56 | it('should set empty object when no attributes specified', function () { 57 | var node = vdo('div', null) 58 | assert.deepEqual(node.attributes, {}) 59 | assert.equal(node.toString(), '
') 60 | }) 61 | it('should add children', function () { 62 | var node = vdo('div', null, 1, 2, 3) 63 | assert.equal(node.nodeName, 'div') 64 | assert.equal(Object.keys(node.childNodes).length, 3) 65 | assert.equal(String(node), '
123
') 66 | }) 67 | it('should add child nodes', function () { 68 | var node = vdo('div', null, vdo('span'), vdo('span'), vdo('span')) 69 | assert.equal(node.nodeName, 'div') 70 | assert.equal(Object.keys(node.childNodes).length, 3) 71 | assert.equal(String(node), '
') 72 | }) 73 | it('should escape child strings', function () { 74 | var node = vdo('div', null, '') 75 | assert.equal(node.nodeName, 'div') 76 | assert.equal(String(node), '
<span></span>
') 77 | }) 78 | it('should ignore null and false children', function () { 79 | var node = vdo('div', null, [null, false, 'hello']) 80 | assert.equal(node.nodeName, 'div') 81 | assert.equal(String(node), '
hello
') 82 | }) 83 | it('should set safe html', function () { 84 | var node = vdo('div', null, vdo.markSafe('')) 85 | assert.equal(node.nodeName, 'div') 86 | assert.equal(String(node), '
') 87 | }) 88 | }) 89 | --------------------------------------------------------------------------------