diff --git a/bench/create-trees.js b/bench/create-trees.js index f5aa4e9..2e967c4 100644 --- a/bench/create-trees.js +++ b/bench/create-trees.js @@ -2,7 +2,6 @@ var bench = require('./bench') var bel = require('../') var vdom = require('virtual-dom') var h = vdom.h -var document = require('global/document') function raw (label, items) { var div = document.createElement('div') diff --git a/browser.js b/browser.js new file mode 100644 index 0000000..00b26e9 --- /dev/null +++ b/browser.js @@ -0,0 +1,174 @@ +var hyperx = require('hyperx') +var onload = require('on-load') + +var headRegex = /^\n[\s]+/ +var tailRegex = /\n[\s]+$/ + +var SVGNS = 'http://www.w3.org/2000/svg' +var XLINKNS = 'http://www.w3.org/1999/xlink' + +var BOOL_PROPS = { + autofocus: 1, + checked: 1, + defaultchecked: 1, + disabled: 1, + formnovalidate: 1, + indeterminate: 1, + readonly: 1, + required: 1, + selected: 1, + willvalidate: 1 +} +var COMMENT_TAG = '!--' +var SVG_TAGS = [ + 'svg', + 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate', 'animateColor', + 'animateMotion', 'animateTransform', 'circle', 'clipPath', 'color-profile', + 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColorMatrix', + 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', + 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', + 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', + 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', + 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'font', 'font-face', + 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', + 'foreignObject', 'g', 'glyph', 'glyphRef', 'hkern', 'image', 'line', + 'linearGradient', 'marker', 'mask', 'metadata', 'missing-glyph', 'mpath', + 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', + 'set', 'stop', 'switch', 'symbol', 'text', 'textPath', 'title', 'tref', + 'tspan', 'use', 'view', 'vkern' +] + +function belCreateElement (tag, props, children) { + var el + + // If an svg tag, it needs a namespace + if (SVG_TAGS.indexOf(tag) !== -1) { + props.namespace = SVGNS + } + + // If we are using a namespace + var ns = false + if (props.namespace) { + ns = props.namespace + delete props.namespace + } + + // Create the element + if (ns) { + el = document.createElementNS(ns, tag) + } else if (tag === COMMENT_TAG) { + return document.createComment(props.comment) + } else { + el = document.createElement(tag) + } + + // If adding onload events + if (props.onload || props.onunload) { + var load = props.onload || function () {} + var unload = props.onunload || function () {} + onload(el, function belOnload () { + load(el) + }, function belOnunload () { + unload(el) + }, + // We have to use non-standard `caller` to find who invokes `belCreateElement` + belCreateElement.caller.caller.caller) + delete props.onload + delete props.onunload + } + + // Create the properties + for (var p in props) { + if (props.hasOwnProperty(p)) { + var key = p.toLowerCase() + var val = props[p] + // Normalize className + if (key === 'classname') { + key = 'class' + p = 'class' + } + // The for attribute gets transformed to htmlFor, but we just set as for + if (p === 'htmlFor') { + p = 'for' + } + // If a property is boolean, set itself to the key + if (BOOL_PROPS[key]) { + if (val === 'true') val = key + else if (val === 'false') continue + } + // If a property prefers being set directly vs setAttribute + if (key.slice(0, 2) === 'on') { + el[p] = val + } else { + if (ns) { + if (p === 'xlink:href') { + el.setAttributeNS(XLINKNS, p, val) + } else if (/^xmlns($|:)/i.test(p)) { + // skip xmlns definitions + } else { + el.setAttributeNS(null, p, val) + } + } else { + el.setAttribute(p, val) + } + } + } + } + + function appendChild (childs) { + if (!Array.isArray(childs)) return + var hadText = false + for (var i = 0, len = childs.length; i < len; i++) { + var node = childs[i] + if (Array.isArray(node)) { + appendChild(node) + continue + } + + if (typeof node === 'number' || + typeof node === 'boolean' || + typeof node === 'function' || + node instanceof Date || + node instanceof RegExp) { + node = node.toString() + } + + var lastChild = el.childNodes[el.childNodes.length - 1] + if (typeof node === 'string') { + hadText = true + if (lastChild && lastChild.nodeName === '#text') { + lastChild.nodeValue += node + } else { + node = document.createTextNode(node) + el.appendChild(node) + lastChild = node + } + if (i === len - 1) { + hadText = false + var value = lastChild.nodeValue + .replace(headRegex, '') + .replace(tailRegex, '') + if (value !== '') lastChild.nodeValue = value + else el.removeChild(lastChild) + } + } else if (node && node.nodeType) { + if (hadText) { + hadText = false + var val = lastChild.nodeValue + .replace(headRegex, '') + .replace(tailRegex, '') + if (val !== '') lastChild.nodeValue = val + else el.removeChild(lastChild) + } + el.appendChild(node) + } + } + } + appendChild(children) + + return el +} + +module.exports = hyperx(belCreateElement, {comments: true}) +module.exports.default = module.exports +module.exports.createElement = belCreateElement diff --git a/index.js b/index.js index ae78ad4..ffa6f7d 100644 --- a/index.js +++ b/index.js @@ -1,154 +1,53 @@ -var document = require('global/document') -var hyperx = require('hyperx') -var onload = require('on-load') - -var SVGNS = 'http://www.w3.org/2000/svg' -var XLINKNS = 'http://www.w3.org/1999/xlink' - -var BOOL_PROPS = { - autofocus: 1, - checked: 1, - defaultchecked: 1, - disabled: 1, - formnovalidate: 1, - indeterminate: 1, - readonly: 1, - required: 1, - selected: 1, - willvalidate: 1 +// See https://github.com/shuhei/pelo/issues/5 +var isElectron = require('is-electron') +var browser = require('./browser') + +if (typeof window !== 'undefined' && isElectron()) { + module.exports = browser +} else { + module.exports = stringify } -var COMMENT_TAG = '!--' -var SVG_TAGS = [ - 'svg', - 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate', 'animateColor', - 'animateMotion', 'animateTransform', 'circle', 'clipPath', 'color-profile', - 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColorMatrix', - 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', - 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', - 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', - 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', - 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'font', 'font-face', - 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', - 'foreignObject', 'g', 'glyph', 'glyphRef', 'hkern', 'image', 'line', - 'linearGradient', 'marker', 'mask', 'metadata', 'missing-glyph', 'mpath', - 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', - 'set', 'stop', 'switch', 'symbol', 'text', 'textPath', 'title', 'tref', - 'tspan', 'use', 'view', 'vkern' -] -function belCreateElement (tag, props, children) { - var el - - // If an svg tag, it needs a namespace - if (SVG_TAGS.indexOf(tag) !== -1) { - props.namespace = SVGNS +function handleValue (value) { + if (Array.isArray(value)) { + // Suppose that each item is a result of html``. + return value.join('') } - - // If we are using a namespace - var ns = false - if (props.namespace) { - ns = props.namespace - delete props.namespace + // Ignore event handlers. + // onclick=${(e) => doSomething(e)} + // will become + // onclick="" + if (typeof value === 'function') { + return '""' } - - // Create the element - if (ns) { - el = document.createElementNS(ns, tag) - } else if (tag === COMMENT_TAG) { - return document.createComment(props.comment) - } else { - el = document.createElement(tag) + if (value === null || value === undefined) { + return '' } - - // If adding onload events - if (props.onload || props.onunload) { - var load = props.onload || function () {} - var unload = props.onunload || function () {} - onload(el, function belOnload () { - load(el) - }, function belOnunload () { - unload(el) - }, - // We have to use non-standard `caller` to find who invokes `belCreateElement` - belCreateElement.caller.caller.caller) - delete props.onload - delete props.onunload + if (value.__encoded) { + return value } + var str = value.toString() + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} - // Create the properties - for (var p in props) { - if (props.hasOwnProperty(p)) { - var key = p.toLowerCase() - var val = props[p] - // Normalize className - if (key === 'classname') { - key = 'class' - p = 'class' - } - // The for attribute gets transformed to htmlFor, but we just set as for - if (p === 'htmlFor') { - p = 'for' - } - // If a property is boolean, set itself to the key - if (BOOL_PROPS[key]) { - if (val === 'true') val = key - else if (val === 'false') continue - } - // If a property prefers being set directly vs setAttribute - if (key.slice(0, 2) === 'on') { - el[p] = val - } else { - if (ns) { - if (p === 'xlink:href') { - el.setAttributeNS(XLINKNS, p, val) - } else if (/^xmlns($|:)/i.test(p)) { - // skip xmlns definitions - } else { - el.setAttributeNS(null, p, val) - } - } else { - el.setAttribute(p, val) - } - } - } - } - - function appendChild (childs) { - if (!Array.isArray(childs)) return - for (var i = 0; i < childs.length; i++) { - var node = childs[i] - if (Array.isArray(node)) { - appendChild(node) - continue - } - - if (typeof node === 'number' || - typeof node === 'boolean' || - typeof node === 'function' || - node instanceof Date || - node instanceof RegExp) { - node = node.toString() - } - - if (typeof node === 'string') { - if (/^[\n\r\s]+$/.test(node)) continue - if (el.lastChild && el.lastChild.nodeName === '#text') { - el.lastChild.nodeValue += node - continue - } - node = document.createTextNode(node) - } - - if (node && node.nodeType) { - el.appendChild(node) - } +function stringify () { + var pieces = arguments[0] + var output = '' + for (var i = 0; i < pieces.length; i++) { + output += pieces[i] + if (i < pieces.length - 1) { + output += handleValue(arguments[i + 1]) } } - appendChild(children) - - return el + // HACK: Avoid double encoding by marking encoded string + // You cannot add properties to string literals + // eslint-disable-next-line no-new-wrappers + var wrapper = new String(output) + wrapper.__encoded = true + return wrapper } - -module.exports = hyperx(belCreateElement, {comments: true}) -module.exports.default = module.exports -module.exports.createElement = belCreateElement diff --git a/package.json b/package.json index 4dc806a..480bd61 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,10 @@ "version": "4.6.1", "description": "A simple extension to native elements", "main": "index.js", + "browser": "browser.js", "scripts": { "start": "wzrd test/index.js:bundle.js", - "test": "standard && node test/server.js && browserify test/index.js | testron", + "test": "standard && node test/server.js && browserify test/index.js | tape-run", "bench": "wzrd bench/index.js:bundle.js" }, "repository": { @@ -17,10 +18,6 @@ "element", "diffhtml" ], - "files": [ - "index.js", - "create.js" - ], "author": "Kyle Robinson Young (http://dontkry.com)", "license": "MIT", "bugs": { @@ -28,8 +25,8 @@ }, "homepage": "https://github.com/shama/bel", "dependencies": { - "global": "^4.3.0", "hyperx": "^2.3.0", + "is-electron": "^2.0.0", "on-load": "^3.2.0" }, "devDependencies": { @@ -39,7 +36,7 @@ "morphdom": "^2.1.1", "standard": "^9.0.2", "tape": "^4.6.0", - "testron": "^1.2.0", + "tape-run": "^3.0.0", "wzrd": "^1.4.0" } } diff --git a/test/api.js b/test/api.js index 8e4f3cf..a37638d 100644 --- a/test/api.js +++ b/test/api.js @@ -3,18 +3,26 @@ var bel = require('../') test('creates an element', function (t) { t.plan(3) - var button = bel`` - var result = bel`` + var button = bel` + + ` + + var result = bel` + + ` + function onselected (result) { t.equal(result, 'success') t.end() } + t.equal(result.tagName, 'UL') t.equal(result.querySelector('button').textContent, 'click me') + button.click() }) diff --git a/test/onload.js b/test/onload.js index 566c1aa..64a01b3 100644 --- a/test/onload.js +++ b/test/onload.js @@ -1,6 +1,5 @@ var test = require('tape') var bel = require('../') -var document = require('global/document') var morphdom = require('morphdom') test('fire onload and unload events', function (t) {