Skip to content

Commit

Permalink
fix whitespace creation (#74)
Browse files Browse the repository at this point in the history
* fix whitespace creation

* fix browser rendering

* use pelo

* rm unused deps

* fixup! clean up files field

* fixup! pull in pelo code

* fixup! rm more global calls

* detect if running in Electron

* split off regexes
  • Loading branch information
yoshuawuyts authored May 19, 2017
1 parent 3a05345 commit 672ebab
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 159 deletions.
1 change: 0 additions & 1 deletion bench/create-trees.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
174 changes: 174 additions & 0 deletions browser.js
Original file line number Diff line number Diff line change
@@ -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
187 changes: 43 additions & 144 deletions index.js
Original file line number Diff line number Diff line change
@@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}

// 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
Loading

0 comments on commit 672ebab

Please sign in to comment.