-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for custom elements #121
base: master
Are you sure you want to change the base?
Changes from all commits
e90ec6e
62fe714
5a5c31d
1895e41
2bf9f8b
8048e07
7695232
02dbc74
5589e24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,6 @@ | ||
var hyperx = require('hyperx') | ||
var appendChild = require('./append-child') | ||
var SVG_TAGS = require('./svg-tags') | ||
var BOOL_PROPS = require('./bool-props') | ||
var nanoHtmlCreateElement = require('./createElement') | ||
|
||
var SVGNS = 'http://www.w3.org/2000/svg' | ||
var XLINKNS = 'http://www.w3.org/1999/xlink' | ||
|
||
var COMMENT_TAG = '!--' | ||
|
||
function nanoHtmlCreateElement (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) | ||
} | ||
|
||
// 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.indexOf(key) !== -1) { | ||
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) | ||
} | ||
} | ||
} | ||
} | ||
|
||
appendChild(el, children) | ||
return el | ||
} | ||
|
||
module.exports = hyperx(nanoHtmlCreateElement, {comments: true}) | ||
module.exports = hyperx(nanoHtmlCreateElement, { comments: true }) | ||
module.exports.default = module.exports | ||
module.exports.createElement = nanoHtmlCreateElement |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,25 @@ | ||
var convertSourceMap = require('convert-source-map') | ||
var transformAst = require('transform-ast') | ||
var through = require('through2') | ||
var hyperx = require('hyperx') | ||
var acorn = require('acorn') | ||
var path = require('path') | ||
var SVG_TAGS = require('./svg-tags') | ||
|
||
var SUPPORTED_VIEWS = ['nanohtml', 'bel', 'yo-yo', 'choo', 'choo/html'] | ||
var DELIM = '~!@|@|@!~' | ||
var VARNAME = 'nanohtml' | ||
var SVGNS = 'http://www.w3.org/2000/svg' | ||
var XLINKNS = '"http://www.w3.org/1999/xlink"' | ||
var BOOL_PROPS = require('./bool-props').reduce(function (o, key) { | ||
o[key] = 1 | ||
return o | ||
}, {}) | ||
var transform = require('./transform') | ||
|
||
module.exports = function yoYoify (file, opts) { | ||
var SUPPORTED_VIEWS = ['nanohtml', 'bel', 'yo-yo', 'choo/html'] | ||
|
||
module.exports = function (file, opts) { | ||
if (/\.json$/.test(file)) return through() | ||
var bufs = [] | ||
var viewVariables = [] | ||
var babelTemplateObjects = Object.create(null) | ||
return through(write, end) | ||
|
||
function write (buf, enc, next) { | ||
bufs.push(buf) | ||
next() | ||
} | ||
|
||
function end (cb) { | ||
var src = Buffer.concat(bufs).toString('utf8') | ||
var res | ||
|
@@ -42,13 +36,16 @@ module.exports = function yoYoify (file, opts) { | |
this.push(res) | ||
this.push(null) | ||
} | ||
|
||
function walk (node) { | ||
var res | ||
|
||
if (isSupportedView(node)) { | ||
if (node.arguments[0].value === 'bel' || | ||
node.arguments[0].value === 'choo/html' || | ||
node.arguments[0].value === 'nanohtml') { | ||
// html and choo/html have no other exports that may be used | ||
node.edit.update('{}') | ||
node.edit.update('{ createElement: require("' + path.join(node.arguments[0].value, '/lib/createElement") }')) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it make sense to just do
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that was my first idea, but then I thought it wouldn't be a good idea to "reuse" an already existent api. I mean, one might want to: var html = require('nanohtml')
var foo = function (strings, ...keys) {
// do stuff
return html(strings, ...keys)
}
var bar = html`<span>HELLO</span>` You are absolutely right about |
||
} | ||
if (node.parent.type === 'VariableDeclarator') { | ||
viewVariables.push(node.parent.id.name) | ||
|
@@ -67,7 +64,8 @@ module.exports = function yoYoify (file, opts) { | |
if (node.type === 'TemplateLiteral' && node.parent.tag) { | ||
var name = node.parent.tag.name || (node.parent.tag.object && node.parent.tag.object.name) | ||
if (viewVariables.indexOf(name) !== -1) { | ||
processNode(node.parent, [ node.quasis.map(cooked) ].concat(node.expressions.map(expr))) | ||
res = apply(name, [ node.quasis.map(cooked) ].concat(node.expressions.map(expr))) | ||
node.parent.update(res) | ||
} | ||
} | ||
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && viewVariables.indexOf(node.callee.name) !== -1) { | ||
|
@@ -78,15 +76,17 @@ module.exports = function yoYoify (file, opts) { | |
// Emitted by Buble. | ||
template = node.arguments[0].elements.map(function (part) { return part.value }) | ||
expressions = node.arguments.slice(1).map(expr) | ||
processNode(node, [ template ].concat(expressions)) | ||
res = apply(node.callee.name, [ template ].concat(expressions)) | ||
node.update(res) | ||
} else if (node.arguments[0] && node.arguments[0].type === 'Identifier') { | ||
// Detect transpiled template strings like: | ||
// html(_templateObject, {id: "test"}) | ||
// Emitted by Babel. | ||
var templateObject = babelTemplateObjects[node.arguments[0].name] | ||
template = templateObject.elements.map(function (part) { return part.value }) | ||
expressions = node.arguments.slice(1).map(expr) | ||
processNode(node, [ template ].concat(expressions)) | ||
res = apply(node.callee.name, [ template ].concat(expressions)) | ||
node.update(res) | ||
|
||
// Remove the _taggedTemplateLiteral helper call | ||
templateObject.parent.edit.update('0') | ||
|
@@ -95,166 +95,31 @@ module.exports = function yoYoify (file, opts) { | |
} | ||
} | ||
|
||
function processNode (node, args) { | ||
var resultArgs = [] | ||
var argCount = 0 | ||
var tagCount = 0 | ||
|
||
var needsAc = false | ||
var needsSa = false | ||
|
||
var hx = hyperx(function (tag, props, children) { | ||
var res = [] | ||
|
||
var elname = VARNAME + tagCount | ||
tagCount++ | ||
|
||
if (tag === '!--') { | ||
return DELIM + [elname, 'var ' + elname + ' = document.createComment(' + JSON.stringify(props.comment) + ')', null].join(DELIM) + DELIM | ||
} | ||
|
||
// Whether this element needs a namespace | ||
var namespace = props.namespace | ||
if (!namespace && SVG_TAGS.indexOf(tag) !== -1) { | ||
namespace = SVGNS | ||
} | ||
|
||
// Create the element | ||
if (namespace) { | ||
res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ')') | ||
} else { | ||
res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ')') | ||
} | ||
|
||
function addAttr (to, key, val) { | ||
// Normalize className | ||
if (key.toLowerCase() === '"classname"') { | ||
key = '"class"' | ||
} | ||
// The for attribute gets transformed to htmlFor, but we just set as for | ||
if (key === '"htmlFor"') { | ||
key = '"for"' | ||
} | ||
// If a property is boolean, set itself to the key | ||
if (BOOL_PROPS[key.slice(1, -1)]) { | ||
if (val.slice(0, 9) === 'arguments') { | ||
if (namespace) { | ||
res.push('if (' + val + ' && ' + key + ') ' + to + '.setAttributeNS(null, ' + key + ', ' + key + ')') | ||
} else { | ||
res.push('if (' + val + ' && ' + key + ') ' + to + '.setAttribute(' + key + ', ' + key + ')') | ||
} | ||
return | ||
} else { | ||
if (val === 'true') val = key | ||
else if (val === 'false') return | ||
} | ||
} | ||
if (key.slice(1, 3) === 'on') { | ||
res.push(to + '[' + key + '] = ' + val) | ||
} else { | ||
if (key === '"xlink:href"') { | ||
res.push(to + '.setAttributeNS(' + XLINKNS + ', ' + key + ', ' + val + ')') | ||
} else if (namespace && key.slice(0, 1) === '"') { | ||
if (!/^xmlns($|:)/i.test(key.slice(1, -1))) { | ||
// skip xmlns definitions | ||
res.push(to + '.setAttributeNS(null, ' + key + ', ' + val + ')') | ||
} | ||
} else if (namespace) { | ||
res.push('if (' + key + ') ' + to + '.setAttributeNS(null, ' + key + ', ' + val + ')') | ||
} else if (key.slice(0, 1) === '"') { | ||
res.push(to + '.setAttribute(' + key + ', ' + val + ')') | ||
} else { | ||
needsSa = true | ||
res.push('sa(' + to + ', ' + key + ', ' + val + ')') | ||
} | ||
} | ||
} | ||
|
||
// Add properties to element | ||
Object.keys(props).forEach(function (key) { | ||
var prop = props[key] | ||
var ksrcs = getSourceParts(key) | ||
var srcs = getSourceParts(prop) | ||
var k, val | ||
if (srcs) { | ||
val = '' | ||
srcs.forEach(function (src, index) { | ||
if (src.arg) { | ||
if (index > 0) val += ' + ' | ||
if (src.before) val += JSON.stringify(src.before) + ' + ' | ||
val += 'arguments[' + argCount + ']' | ||
if (src.after) val += ' + ' + JSON.stringify(src.after) | ||
resultArgs.push(src.arg) | ||
argCount++ | ||
} | ||
}) | ||
} else { | ||
val = JSON.stringify(prop) | ||
} | ||
if (ksrcs) { | ||
k = '' | ||
ksrcs.forEach(function (src, index) { | ||
if (src.arg) { | ||
if (index > 0) val += ' + ' | ||
if (src.before) val += JSON.stringify(src.before) + ' + ' | ||
k += 'arguments[' + argCount + ']' | ||
if (src.after) k += ' + ' + JSON.stringify(src.after) | ||
resultArgs.push(src.arg) | ||
argCount++ | ||
} | ||
}) | ||
} else { | ||
k = JSON.stringify(key) | ||
} | ||
addAttr(elname, k, val) | ||
}) | ||
|
||
if (Array.isArray(children)) { | ||
var childs = [] | ||
children.forEach(function (child) { | ||
var srcs = getSourceParts(child) | ||
if (srcs) { | ||
var src = srcs[0] | ||
if (src.src) { | ||
res.push(src.src) | ||
} | ||
if (src.name) { | ||
childs.push(src.name) | ||
} | ||
if (src.arg) { | ||
var argname = 'arguments[' + argCount + ']' | ||
resultArgs.push(src.arg) | ||
argCount++ | ||
childs.push(argname) | ||
} | ||
} else { | ||
childs.push(JSON.stringify(child)) | ||
} | ||
}) | ||
if (childs.length > 0) { | ||
needsAc = true | ||
res.push('ac(' + elname + ', [' + childs.join(',') + '])') | ||
} | ||
} | ||
|
||
// Return delim'd parts as a child | ||
return DELIM + [elname, res.join('\n'), null].join(DELIM) + DELIM | ||
}, { comments: true }) | ||
|
||
// Run through hyperx | ||
var res = hx.apply(null, args) | ||
|
||
// Pull out the final parts and wrap in a closure with arguments | ||
var src = getSourceParts(res) | ||
if (src && src[0].src) { | ||
var params = resultArgs.join(',') | ||
|
||
node.edit.update('(function () {' + | ||
(needsAc ? '\n var ac = require(\'' + path.resolve(__dirname, 'append-child.js').replace(/\\/g, '\\\\') + '\')' : '') + | ||
(needsSa ? '\n var sa = require(\'' + path.resolve(__dirname, 'set-attribute.js').replace(/\\/g, '\\\\') + '\')' : '') + | ||
'\n ' + src[0].src + '\n return ' + src[0].name + '\n }(' + params + '))') | ||
var apply = transform.factory({ | ||
arrayExpression: function (elements) { | ||
return '[' + elements.join(',') + ']' | ||
}, | ||
objectExpression: function (properties) { | ||
return '{' + properties.join(',') + '}' | ||
}, | ||
objectProperty: function (key, value, computed) { | ||
return computed | ||
? ('[' + key + ']' + ':' + value) | ||
: (key + ':' + value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. choo aims to work in IE 11 + so we can't use ES6+ syntax in the transform output There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, will fix that |
||
}, | ||
stringLiteral: function (value) { | ||
return JSON.stringify(value) | ||
}, | ||
callCreateElement (html, tag, props, children) { | ||
return html + '.createElement(' + tag + ',' + props + ',' + children + ')' | ||
}, | ||
callObjectAssign (objects) { | ||
return 'Object.assign(' + objects.join(',') + ')' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Object.assign also doesn't exist yet in IE11 … choo uses the IE 11 will probably be dropped in choo 7 but not yet: choojs/choo#616 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, will use |
||
}, | ||
stringConcat: function (a, b) { | ||
return a + '+' + b | ||
} | ||
} | ||
}) | ||
|
||
function isSupportedView (node) { | ||
return (node.type === 'CallExpression' && | ||
|
@@ -269,32 +134,4 @@ function BabelTemplateDefinition (node) { | |
} | ||
|
||
function cooked (node) { return node.value.cooked } | ||
function expr (ex, idx) { | ||
return DELIM + [null, null, ex.source()].join(DELIM) + DELIM | ||
} | ||
function getSourceParts (str) { | ||
if (typeof str !== 'string') return false | ||
if (str.indexOf(DELIM) === -1) return false | ||
var parts = str.split(DELIM) | ||
|
||
var chunk = parts.splice(0, 5) | ||
var arr = [{ | ||
before: chunk[0], | ||
name: chunk[1], | ||
src: chunk[2], | ||
arg: chunk[3], | ||
after: chunk[4] | ||
}] | ||
while (parts.length > 0) { | ||
chunk = parts.splice(0, 4) | ||
arr.push({ | ||
before: '', | ||
name: chunk[0], | ||
src: chunk[1], | ||
arg: chunk[2], | ||
after: chunk[3] | ||
}) | ||
} | ||
|
||
return arr | ||
} | ||
function expr (ex) { return transform.expr(ex.source()) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good,
choo.view
is long gone afaik 👍