Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

nanocomponent

Package Overview
Dependencies
Maintainers
3
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nanocomponent - npm Package Compare versions

Comparing version 2.0.3 to 3.0.0

example/index.js

196

index.js

@@ -1,164 +0,60 @@

var observeResize = require('observe-resize')
var onIntersect = require('on-intersect')
var politeEl = require('polite-element')
'use strict'
var onload = require('on-load')
var assert = require('assert')
var html = require('bel')
module.exports = nanocomponent
module.exports = Nanocomponent
// Create performant HTML elements
// (DOMElement|fn|obj) -> fn
function nanocomponent (val) {
assert.equal(typeof val, 'object', 'nanocomponent: val should type function')
assert.equal(typeof val.render, 'function', 'nanocomponent: needs a .render function')
function Nanocomponent (val) {
this._hasWindow = typeof window !== undefined
this._placeholder = null
this._onload = onload
this._element = null
this._loaded = false
}
var placeholderHandler = val.placeholder
var onunloadHandler = val.onunload
var onresizeHandler = val.onresize
var onenterHandler = val.onenter
var updateHandler = val.onupdate
var onexitHandler = val.onexit
var onloadHandler = val.onload
var renderHandler = val.render
Nanocomponent.prototype.render = function () {
assert.equal(typeof this._render, 'function', 'nanocomponent: this._render should be implemented')
var stopPlaceholderResize = null
var stopRenderResize = null
var enableIntersect = null
var enableResize = null
var self = this
var len = arguments.length
var args = new Array(len)
for (var i = 0; i < len; i++) args[i] = arguments[i]
if (onresizeHandler) applyResize()
if (onenterHandler || onexitHandler) applyOnintersect()
applyOnloadhandler()
if (placeholderHandler) applyPlaceholder()
return renderHandler
function applyOnloadhandler () {
var _render = renderHandler
createOnunload()
createOnload()
renderHandler = createElement(_render, updateHandler, onloadHandler, onunloadHandler)
if (!this._hasWindow) {
this._element = this._render.apply(this, args)
return this._element
} else if (!this._element) {
this._element = this._render.apply(this, args)
this._onload(this._element, function () {
self._loaded = true
if (self._load) self._load()
}, function () {
self._placeholder = null
self._element = null
self._loaded = false
if (self._unload) self._unload()
})
return this._element
} else {
var shouldUpdate = this._update.apply(this, args)
if (shouldUpdate) this._render.apply(this, args)
if (!this._placeholder) this._placeholder = this._createPlaceholder()
return this._placeholder
}
function applyOnintersect () {
enableIntersect = function (el) {
onIntersect(el, onenterHandler, onexitHandler)
}
}
function applyPlaceholder () {
var _render = renderHandler
var placeholderRendered = null
renderHandler = function () {
var args = new Array(arguments.length)
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i]
}
if (placeholderRendered) return _render.apply(_render, args)
var el = politeEl(function applyPlaceholderHandler () {
placeholderRendered = true
return placeholderHandler.apply(placeholderHandler, args)
}, function applyRenderHandler () {
return _render.apply(_render, args)
})
var ret = el.apply(el, args)
return ret
}
}
function applyResize () {
enableResize = function (el) {
stopRenderResize = observeResize(el, onresizeHandler)
}
}
function createOnunload () {
var _onunload = onunloadHandler
onunloadHandler = function (el) {
if (stopPlaceholderResize) {
stopPlaceholderResize()
stopPlaceholderResize = null
}
if (stopRenderResize) {
stopRenderResize()
stopRenderResize = null
}
if (_onunload) _onunload(el)
}
}
function createOnload () {
var _onload = onloadHandler
onloadHandler = function (el) {
if (_onload) _onload(el)
if (enableResize) enableResize(el)
if (enableIntersect) enableIntersect(el)
}
}
}
function createElement (render, update, _onload, _onunload) {
var isRendered = false
var isMounted = false
var isProxied = false
var element = null
var proxy = null
var args = null
return function () {
var _args = new Array(arguments.length)
for (var i = 0; i < _args.length; i++) {
_args[i] = arguments[i]
}
if (!isRendered) {
isRendered = true
args = _args
element = render.apply(render, args)
onload(element, handleLoad, handleUnload)
}
if (!isMounted) return element
if (!compare(_args, args)) {
args = _args
if (update) {
// call update with element as the first arg
args.unshift(element)
update.apply(render, args.slice(0))
}
}
if (!isProxied) {
proxy = html`<div></div>`
proxy.isSameNode = function (el) {
return (el === element)
}
}
return proxy
}
function handleLoad (el) {
isMounted = true
if (_onload) _onload(el)
}
function handleUnload (el) {
isMounted = false
isProxied = false
proxy = null
if (_onunload) _onunload(el)
}
// default ._update method - should be replaced with custom logic
Nanocomponent.prototype._update = function () {
return true
}
function compare (args1, args2) {
var length = args1.length
if (length !== args2.length) return false
for (var i = 0; i < length; i++) {
if (args1[i] !== args2[i]) return false
Nanocomponent.prototype._createPlaceholder = function () {
var el = document.createElement('div')
el.setAttribute('data-nanocomponent', '')
var self = this
el.isSameNode = function (el) {
return el === self._element
}
return true
return el
}
{
"name": "nanocomponent",
"version": "2.0.3",
"version": "3.0.0",
"description": "Create performant HTML elements",

@@ -16,23 +16,18 @@ "main": "index.js",

"node",
"perf",
"universal"
],
"license": "MIT",
"browserify": {
"transform": [
"yo-yoify"
]
},
"dependencies": {
"observe-resize": "^1.1.0",
"on-intersect": "^1.1.0",
"on-load": "^3.2.0",
"polite-element": "^1.0.2",
"yo-yoify": "^3.5.0"
"on-load": "^3.2.0"
},
"devDependencies": {
"bankai": "^5.2.1",
"bel": "^4.5.1",
"bankai": "^5.5.2",
"bel": "^4.6.0",
"choo": "^5.1.4",
"choo-log": "^6.0.0",
"dependency-check": "^2.7.0",
"inherits": "^2.0.3",
"istanbul": "^0.4.5",
"leaflet": "^1.0.3",
"nanologger": "^1.0.2",
"standard": "^8.6.0",

@@ -42,4 +37,5 @@ "tachyons": "^4.6.1",

"uglifyify": "^3.0.4",
"unassertify": "^2.0.4"
"unassertify": "^2.0.4",
"yo-yoify": "^3.5.0"
}
}

@@ -5,218 +5,77 @@ # nanocomponent [![stability][0]][1]

Create performant HTML elements.
`1kb` library to wrap native DOM libraries to work with DOM diffing algorithms.
## Features
- works with [virtually every framework][adapt]
- speeds up perceived performance
- improves rendering performance
- graciously falls back if APIs are not available
- weighs `~4kb`
## Usage
### Defer rendering until the browser has spare time available
```js
var politeElement = component({
placeholder: function () {
return html`
<p>lol not loaded yet</p>
`
},
render: function () {
return html`
<p>HOW'S IT GOING CHAP</p>
`
}
})
// Implementer API
var Nanocomponent = require('nanocomponent')
var html = require('bel')
console.log(politeElement())
setTimeout(function () {
console.log(politeElement())
}, 1000)
```
function MyButton () {
if (!(this instanceof MyButton)) return new MyButton()
this._color = null
Nanocomponent.call(this)
}
MyButton.prototype = Object.create(Nanocomponent)
### Trigger lifecycle events when loading and unloading elements to the DOM
```js
var widgetElement = component({
onload: function (el) {
el.textContent = 'totally loaded now'
},
onunload: function (el) {
el.textContent = 'no more free lunch'
},
onupdate: function (el, verb) {
el.textContent = `totally ${verb}ing now`
},
render: function (verb) {
return html`
<p>lol not ${verb}ed yet</p>
`
}
})
console.log(widgetElement('load'))
var el = widgetElement('blep')
document.body.appendChild(el)
document.body.removeChild(el)
```
MyButton.prototype._render = function (color) {
this._color = color
return html`
<button style="background-color: ${color}">
Click Me
</button>
`
}
### Trigger lifecycle events when coming in and out of view
```js
var viewportElement = component({
onenter: function (el) {
el.textContent = 'BEHOLD THE GOBLIN'
},
onexit: function (el) {
el.textContent = 'THE PONIES HAVE COME'
},
render: function () {
return html`
<h1>WHO COULD IT BE</h1>
`
}
})
console.log(viewportElement())
MyButton.prototype._update = function (newColor) {
return newColor !== this._color
}
```
### Trigger lifecycle events when resizing
```js
var resizeElement = component({
onresize: function (el) {
var parent = el.parentNode
console.log('element dimensions', el.getBoundingRectangle())
console.log('parent dimensions', parent.getBoundingRectangle())
},
render: function () {
return html`
<div>hello planet</div>
`
}
})
console.log(resizeElement())
// Consumer API
var MyButton = require('./my-button.js')
var myButton = MyButton()
document.body.appendChild(myButton.render())
```
### Rendering inside frameworks
Check out [nanocomponent-adapters][adapt] on how to use `nanocomponent` in
your fav framework.
## API
### render = nanocomponent(HtmlOrFunctionOrObject)
Create an object with different methods attached. Cached until new arguments
are passed in or when it's removed from the DOM. Availble methods are:
- __render(...args):__ (required) Render DOM elements.
- __placeholder(..args)__ Render DOM elements and delegate the `render` call to
the next `requestIdleCallback` tick. This is useful to spread CPU intensive
work over more time and not block the render loop. When executed on the
server `placeholder` will always be rendered in favor of `render`. This makes
it easier for client-side JS to pick up where the server left off
(rehydration).
- __onupdate(el, ...args):__ Allows you to change the internal DOM state when
new arguments are passed in. It's called when the returned `render()` call is
called after an initial render with different arguments. Argument equality is
shallowly checked using a `===` check on each argument. The first argument is
the currently rendered argument.
- __onenter:__ called when the element comes into view, relies on
`window.IntersectionObserver`
- __onexit:__ Called when the element goes out of view, relies on
`window.IntersectionObserver`
- __onload(el):__ Called when the element is appended onto the DOM
- __onunload(el):__ Called when the element is removed from the DOM
- __onresize:__ Called when the element changes size. :warning: This method can
be called in high frequency and can cause strain on your CPU. Caution and/or
debounce methods are advised.
### `Nanocomponent.prototype()`
Inheritable Nanocomponent prototype. Should be inherited from using
`Nanococomponent.call(this)` and `prototype = Object.create(Nanocomponent)`.
### el = render(...args)
Call the corresponding `render` function an receive DOM elements. As long as an
element exists on the DOM, subsequent calls to `render` will return an empty
element with a `.isSameNode()` method on it which can be used as a caching hint
for HTML diffing trees.
Internal properties are:
- __this._placeholder:__ placeholder element that's returned on subsequent
`render()` calls that don't pass the `._update()` check.
- __this._element:__ rendered element that should be returned from the first
`._render()` call. Used to apply `._load()` and `._unload()` listeners on.
- __this._hasWindow:__ boolean if `window` exists. Can be used to create
elements that render both in the browser and in Node.
- __this._loaded:__ boolean if the element is currently loaded on the DOM.
- __this._onload:__ reference to the [on-load][on-load] library.
## FAQ
### Why'd you build this package?
I've been building web stuff for a while now, and have seen a fair share of
frameworks become popular, take over developer mindshare and then disappear
again a few years later. With each framework iteration the basic libraries tend
to be rewritten from scratch: form validation, modals, infinite scolls, charts.
The list is long.
### `DOMNode|placeholder = Nanocomponent.prototype.render()`
Create an instance of the component. Calls `prototype._render()` if
`prototype._update()` returns `true`. As long as the element is mounted on the
DOM, subsequent calls to `.render()` will return a placeholder element with a
`.isSameNode()` method that compares arguments with the previously rendered
node. This is useful for diffing algorithms like
[nanomorph](https://github.com/yoshuawuyts/nanomorph) which use this method to
determine if a portion of the tree should be walked.
I think it'd be cool if we could create generic JS components that work
natively with any JS framework through slim bindings. This would encourage
reusability between frameworks, which means it becomes easier to pick up
different frameworks (no need to relearn the ecosystem) and bring new
frameworks to maturity (less new code to implement).
### `Nanocomponent.prototype._render([arguments])`
Render an HTML node with arguments. For `prototype._load()` and
`prototype._unload()` to work, make sure you return the same node on subsequent
renders. The Node that's initially returned is saved as `this._element`.
### This sounds a lot like WebComponents, how is this different?
WebComponents are a specification by the W3C that's been in the works for a
while now. Certain parts have been put on hold by browser vendors until kinks
are ironed out, and the some of the available parts are not widely adopted - or
at least not the way they were meant to be used.
### `Nanocomponent.prototype._update([arguments])`
Return a boolean to determine if `prototype._render()` should be called.
Evaluates to `true` if not implemented. Not called on the first render.
When people talk about WebComponents they usually refer to the Custom Elements
specification. This spec allows you to create new HTML tags and provides you
with a set of lifecycle events. The `onload` / `onunload` / `render` /
`onupdate` events are indeed quite similar to Nanocomponent. The biggest
difference however, is the way in which elements are registered. Custom
Elements are globally scoped in the browser and must have unique names.
Nanocomponent is a plain JS function and will not run into namespacing issues.
It's quite feasible to wrap a Nanocomponent instance to create a Webcomponent.
The other way around is harder. Nanocomponent also exposes more events.
### `Nanocomponent.prototype._load()`
Called when the component is mounted on the DOM.
### I read somewhere that Nanocomponent uses some of the same techniques as React Fiber / React Stack. Could you talk some more about this?
Sure! React Fiber (or React Stack, I'm not sure what name they're ending up
with) is using the same APIs under the hood as Nanocomponent, but approaches it
from the other side of the spectrum.
### `Nanocomponent.prototype._unload()`
Called when the component is removed from the DOM.
Nanocomponent is intended to create individual components that can run in any
framework and have tight control over their performance. React Fiber is
a framework where the whole render tree is optimized on each loop.
Nanocomponent operates on the component layer, React Fiber at the framework
layer.
This doesn't mean that either one is "better" - every abstraction carries
overhead, and different situations require different solutions. I think it's
great performance is being tackled from multiple sides of the spectrum.
### What do you mean by "Nanocomponent works with any framework"?
Nanocomponent returns DOM elements that work in any framework that knows how to
render raw DOM nodes. This includes pretty much every popular framework and
compile-to-js language. Because the lifecycle events are self-encapsulated and
we don't expose globals this means Nanocomponent doesn't have any problems
running inside any other framework. I think Nanocomponent is quite similar in
browser framework land to how C / CPP packages operate
### I don't believe in silver bullets, tell me about the tradeoffs
All abstractions in JS come with a cost. Luckily the cost of Nanomorph is
fairly low (4kb, CPU and memory seem to be cool - haven't noticed any
significant repaint costs or anything), but it should not be neglected. Measure
and inform yourself.
Nanomorph relies on fairly new DOM APIs to do what it does. If you're running
v. old browsers this package won't help you - we're sorry. As long as
`window.MutationObserver` is available we should be good; the others are
optimizations on top. These are the fancy APIs we're using:
- `window.MutationObserver`
- `window.IntersectionObserver`
- `window.requestAnimationFrame`
- `window.requestIdleCallback`
Third of all this package is optimized for an environment that supports
`require()`. I believe in iterating on proven ideas, and given the stability of
`require()` and the tooling & community around it I'm betting that it'll stick
around for a while.
Some frameworks (like React Fiber / React Stack) might have some cool
optimizations that allow super fine grained control over each and every element
in the tree - nanocomponent is similar but has its own rules to wait for
resources, so it might turn into an interesting situation where everyone is
super politely waiting for resources and like is nice to each other. I don't
know, but I'm guessing it'll be fun hah.
Also these components won't work in environments that don't have a DOM, but I
bet it'd be cool to look at those environments and figure out which primitives
they have and create equivalent functionality through generic components.
### Uahahghhhhblll
Yes!
### Does this render on the server?
Yup, it does. If it doesn't for your particular setup we'd like to hear.
## Installation

@@ -259,1 +118,3 @@ ```sh

[adapt]: https://github.com/yoshuawuyts/nanocomponent-adapters/
[on-load]: https://github.com/shama/on-load
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc