Skip to content

Commit 4aa2ad9

Browse files
AndyOGogoto-bus-stop
authored andcommitted
Feature/add custom element built in support (#137)
fixes #136 * adds template literal support for extended built-in elements (custom elements V1 spec) * browserify and babel transform upgrades
1 parent d17a07b commit 4aa2ad9

File tree

8 files changed

+101
-5
lines changed

8 files changed

+101
-5
lines changed

lib/babel.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ module.exports = (babel) => {
5151
const t = babel.types
5252
const nanohtmlModuleNames = ['nanohtml', 'bel', 'yo-yo', 'choo/html']
5353

54+
/**
55+
* Returns an object which specifies the custom elements by which a built-in is extended.
56+
*/
57+
const createExtendsObjectExpression = (is) =>
58+
t.objectExpression([t.objectProperty(t.identifier('is'), t.stringLiteral(is))])
59+
5460
/**
5561
* Returns a node that creates a namespaced HTML element.
5662
*/
@@ -60,6 +66,15 @@ module.exports = (babel) => {
6066
[ns, t.stringLiteral(tag)]
6167
)
6268

69+
/**
70+
* Returns a node that creates a extended namespaced HTML element.
71+
*/
72+
const createNsCustomBuiltIn = (ns, tag, is) =>
73+
t.callExpression(
74+
t.memberExpression(t.identifier('document'), t.identifier('createElementNS')),
75+
[ns, t.stringLiteral(tag), createExtendsObjectExpression(is)]
76+
)
77+
6378
/**
6479
* Returns a node that creates an element.
6580
*/
@@ -69,6 +84,15 @@ module.exports = (babel) => {
6984
[t.stringLiteral(tag)]
7085
)
7186

87+
/**
88+
* Returns a node that creates an extended element.
89+
*/
90+
const createCustomBuiltIn = (tag, is) =>
91+
t.callExpression(
92+
t.memberExpression(t.identifier('document'), t.identifier('createElement')),
93+
[t.stringLiteral(tag), createExtendsObjectExpression(is)]
94+
)
95+
7296
/**
7397
* Returns a node that creates a comment.
7498
*/
@@ -195,10 +219,20 @@ module.exports = (babel) => {
195219

196220
const result = []
197221

222+
var isCustomElement = props.is
223+
delete props.is
224+
198225
// Use the SVG namespace for svg elements.
199226
if (SVG_TAGS.includes(tag)) {
200227
state.svgNamespaceId.used = true
201-
result.push(t.assignmentExpression('=', id, createNsElement(state.svgNamespaceId, tag)))
228+
229+
if (isCustomElement) {
230+
result.push(t.assignmentExpression('=', id, createNsCustomBuiltIn(state.svgNamespaceId, tag, isCustomElement)))
231+
} else {
232+
result.push(t.assignmentExpression('=', id, createNsElement(state.svgNamespaceId, tag)))
233+
}
234+
} else if (isCustomElement) {
235+
result.push(t.assignmentExpression('=', id, createCustomBuiltIn(tag, isCustomElement)))
202236
} else {
203237
result.push(t.assignmentExpression('=', id, createElement(tag)))
204238
}

lib/browser.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,24 @@ function nanoHtmlCreateElement (tag, props, children) {
2525
delete props.namespace
2626
}
2727

28+
// If we are extending a builtin element
29+
var isCustomElement = false
30+
if (props.is) {
31+
isCustomElement = props.is
32+
delete props.is
33+
}
34+
2835
// Create the element
2936
if (ns) {
30-
el = document.createElementNS(ns, tag)
37+
if (isCustomElement) {
38+
el = document.createElementNS(ns, tag, { is: isCustomElement })
39+
} else {
40+
el = document.createElementNS(ns, tag)
41+
}
3142
} else if (tag === COMMENT_TAG) {
3243
return document.createComment(props.comment)
44+
} else if (isCustomElement) {
45+
el = document.createElement(tag, { is: isCustomElement })
3346
} else {
3447
el = document.createElement(tag)
3548
}

lib/browserify-transform.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,19 @@ function processNode (node, args) {
136136
namespace = SVGNS
137137
}
138138

139+
// Whether this element is extended
140+
var isCustomElement = props.is
141+
delete props.is
142+
139143
// Create the element
140144
if (namespace) {
141-
res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ')')
145+
if (isCustomElement) {
146+
res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ', { is: ' + JSON.stringify(isCustomElement) + ' })')
147+
} else {
148+
res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ')')
149+
}
150+
} else if (isCustomElement) {
151+
res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ', { is: ' + JSON.stringify(isCustomElement) + ' })')
142152
} else {
143153
res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ')')
144154
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
var _div,
2+
_appendChild = require('nanohtml/lib/append-child');
3+
4+
_div = document.createElement('div', {
5+
is: 'my-div'
6+
}), _appendChild(_div, ['\n Hello world\n ']), _div;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import html from 'nanohtml'
2+
3+
html`
4+
<div is="my-div">
5+
Hello world
6+
</div>
7+
`

tests/babel/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function testFixture (name, opts) {
3737
}
3838

3939
testFixture('simple')
40+
testFixture('custom-build-in')
4041
testFixture('empty')
4142
testFixture('this')
4243
testFixture('variableNames')

tests/browser/elements.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,27 @@ test('allow objects to be passed', function (t) {
185185
t.ok(result.outerHTML.indexOf('<div foo="bar">hey</div>') !== -1, 'contains foo="bar"')
186186
t.end()
187187
})
188+
189+
test('supports extended build-in elements', function (t) {
190+
t.plan(1)
191+
192+
var originalCreateElement = document.createElement
193+
var optionsArg
194+
195+
// this iife is a must to avoid illegal invocation type errors, caused by transformed nanohtml tests
196+
(function () {
197+
document.createElement = function () {
198+
optionsArg = arguments[1]
199+
return originalCreateElement.apply(this, arguments)
200+
}
201+
})()
202+
203+
;html`<div is="my-div"></div>`
204+
205+
t.ok(typeof optionsArg === 'object' && optionsArg.is === 'my-div', 'properly passes optional extends object')
206+
207+
// revert to original prototype method
208+
delete document.createElement
209+
210+
t.end()
211+
})

tests/transform/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ var path = require('path')
66
var FIXTURE = path.join(__dirname, 'fixture.js')
77

88
test('works', function (t) {
9-
t.plan(4)
10-
var src = 'var html = require(\'nanohtml\')\n module.exports = function (data) {\n var className = \'test\'\n return html`<div class="${className}">\n <h1>${data}</h1>\n </div>`\n }' // eslint-disable-line
9+
t.plan(5)
10+
var src = 'var html = require(\'nanohtml\')\n module.exports = function (data) {\n var className = \'test\'\n return html`<div class="${className}">\n <h1>${data}</h1>\n <div is="my-div"></div> </div>`\n }' // eslint-disable-line
1111
fs.writeFileSync(FIXTURE, src)
1212
var b = browserify(FIXTURE, {
1313
browserField: false,
@@ -19,6 +19,7 @@ test('works', function (t) {
1919
var result = src.toString()
2020
t.ok(result.indexOf('var html = {}') !== -1, 'replaced html dependency with {}')
2121
t.ok(result.indexOf('document.createElement("h1")') !== -1, 'created an h1 tag')
22+
t.ok(result.indexOf('document.createElement("div", { is: "my-div" })') !== -1, 'created an extended build-in element')
2223
t.ok(result.indexOf('setAttribute("class", arguments[1])') !== -1, 'set a class attribute')
2324
t.end()
2425
})

0 commit comments

Comments
 (0)