Skip to content

Commit

Permalink
Feature/add custom element built in support (#137)
Browse files Browse the repository at this point in the history
fixes #136 

* adds template literal support for extended built-in elements (custom elements V1 spec)
* browserify and babel transform upgrades
  • Loading branch information
AndyOGo authored and goto-bus-stop committed Nov 16, 2018
1 parent d17a07b commit 4aa2ad9
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 5 deletions.
36 changes: 35 additions & 1 deletion lib/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ module.exports = (babel) => {
const t = babel.types
const nanohtmlModuleNames = ['nanohtml', 'bel', 'yo-yo', 'choo/html']

/**
* Returns an object which specifies the custom elements by which a built-in is extended.
*/
const createExtendsObjectExpression = (is) =>
t.objectExpression([t.objectProperty(t.identifier('is'), t.stringLiteral(is))])

/**
* Returns a node that creates a namespaced HTML element.
*/
Expand All @@ -60,6 +66,15 @@ module.exports = (babel) => {
[ns, t.stringLiteral(tag)]
)

/**
* Returns a node that creates a extended namespaced HTML element.
*/
const createNsCustomBuiltIn = (ns, tag, is) =>
t.callExpression(
t.memberExpression(t.identifier('document'), t.identifier('createElementNS')),
[ns, t.stringLiteral(tag), createExtendsObjectExpression(is)]
)

/**
* Returns a node that creates an element.
*/
Expand All @@ -69,6 +84,15 @@ module.exports = (babel) => {
[t.stringLiteral(tag)]
)

/**
* Returns a node that creates an extended element.
*/
const createCustomBuiltIn = (tag, is) =>
t.callExpression(
t.memberExpression(t.identifier('document'), t.identifier('createElement')),
[t.stringLiteral(tag), createExtendsObjectExpression(is)]
)

/**
* Returns a node that creates a comment.
*/
Expand Down Expand Up @@ -195,10 +219,20 @@ module.exports = (babel) => {

const result = []

var isCustomElement = props.is
delete props.is

// Use the SVG namespace for svg elements.
if (SVG_TAGS.includes(tag)) {
state.svgNamespaceId.used = true
result.push(t.assignmentExpression('=', id, createNsElement(state.svgNamespaceId, tag)))

if (isCustomElement) {
result.push(t.assignmentExpression('=', id, createNsCustomBuiltIn(state.svgNamespaceId, tag, isCustomElement)))
} else {
result.push(t.assignmentExpression('=', id, createNsElement(state.svgNamespaceId, tag)))
}
} else if (isCustomElement) {
result.push(t.assignmentExpression('=', id, createCustomBuiltIn(tag, isCustomElement)))
} else {
result.push(t.assignmentExpression('=', id, createElement(tag)))
}
Expand Down
15 changes: 14 additions & 1 deletion lib/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,24 @@ function nanoHtmlCreateElement (tag, props, children) {
delete props.namespace
}

// If we are extending a builtin element
var isCustomElement = false
if (props.is) {
isCustomElement = props.is
delete props.is
}

// Create the element
if (ns) {
el = document.createElementNS(ns, tag)
if (isCustomElement) {
el = document.createElementNS(ns, tag, { is: isCustomElement })
} else {
el = document.createElementNS(ns, tag)
}
} else if (tag === COMMENT_TAG) {
return document.createComment(props.comment)
} else if (isCustomElement) {
el = document.createElement(tag, { is: isCustomElement })
} else {
el = document.createElement(tag)
}
Expand Down
12 changes: 11 additions & 1 deletion lib/browserify-transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,19 @@ function processNode (node, args) {
namespace = SVGNS
}

// Whether this element is extended
var isCustomElement = props.is
delete props.is

// Create the element
if (namespace) {
res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ')')
if (isCustomElement) {
res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ', { is: ' + JSON.stringify(isCustomElement) + ' })')
} else {
res.push('var ' + elname + ' = document.createElementNS(' + JSON.stringify(namespace) + ', ' + JSON.stringify(tag) + ')')
}
} else if (isCustomElement) {
res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ', { is: ' + JSON.stringify(isCustomElement) + ' })')
} else {
res.push('var ' + elname + ' = document.createElement(' + JSON.stringify(tag) + ')')
}
Expand Down
6 changes: 6 additions & 0 deletions tests/babel/fixtures/custom-build-in.expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
var _div,
_appendChild = require('nanohtml/lib/append-child');

_div = document.createElement('div', {
is: 'my-div'
}), _appendChild(_div, ['\n Hello world\n ']), _div;
7 changes: 7 additions & 0 deletions tests/babel/fixtures/custom-build-in.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import html from 'nanohtml'

html`
<div is="my-div">
Hello world
</div>
`
1 change: 1 addition & 0 deletions tests/babel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function testFixture (name, opts) {
}

testFixture('simple')
testFixture('custom-build-in')
testFixture('empty')
testFixture('this')
testFixture('variableNames')
Expand Down
24 changes: 24 additions & 0 deletions tests/browser/elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,27 @@ test('allow objects to be passed', function (t) {
t.ok(result.outerHTML.indexOf('<div foo="bar">hey</div>') !== -1, 'contains foo="bar"')
t.end()
})

test('supports extended build-in elements', function (t) {
t.plan(1)

var originalCreateElement = document.createElement
var optionsArg

// this iife is a must to avoid illegal invocation type errors, caused by transformed nanohtml tests
(function () {
document.createElement = function () {
optionsArg = arguments[1]
return originalCreateElement.apply(this, arguments)
}
})()

;html`<div is="my-div"></div>`

t.ok(typeof optionsArg === 'object' && optionsArg.is === 'my-div', 'properly passes optional extends object')

// revert to original prototype method
delete document.createElement

t.end()
})
5 changes: 3 additions & 2 deletions tests/transform/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ var path = require('path')
var FIXTURE = path.join(__dirname, 'fixture.js')

test('works', function (t) {
t.plan(4)
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
t.plan(5)
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
fs.writeFileSync(FIXTURE, src)
var b = browserify(FIXTURE, {
browserField: false,
Expand All @@ -19,6 +19,7 @@ test('works', function (t) {
var result = src.toString()
t.ok(result.indexOf('var html = {}') !== -1, 'replaced html dependency with {}')
t.ok(result.indexOf('document.createElement("h1")') !== -1, 'created an h1 tag')
t.ok(result.indexOf('document.createElement("div", { is: "my-div" })') !== -1, 'created an extended build-in element')
t.ok(result.indexOf('setAttribute("class", arguments[1])') !== -1, 'set a class attribute')
t.end()
})
Expand Down

0 comments on commit 4aa2ad9

Please sign in to comment.