Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
nanocomponent
Advanced tools
Native DOM components that pair nicely with DOM diffing algorithms.
nanocomponent@5
and cache-component@5
.// button.js
var Nanocomponent = require('nanocomponent')
var html = require('bel')
function Button () {
if (!(this instanceof Button)) return new Button()
this.color = null
Nanocomponent.call(this)
}
Button.prototype = Object.create(Nanocomponent.prototype)
Button.prototype.createElement = function (color) {
this.color = color
return html`
<button style="background-color: ${color}">
Click Me
</button>
`
}
// Implement conditional rendering
Button.prototype.update = function (newColor) {
return newColor !== this.color
}
// index.js
var choo = require('choo')
var Button = require('./button.js')
var button = Button()
var app = choo()
app.route('/', mainView)
app.mount('body')
function mainView (state, emit) {
return html`
<body>
${button.render(state.color)}
</body>
`
}
app.use(function (state, emitter) {
state.color = 'green'
})
These are some common patterns you might encounter when writing components.
Nanocomponents is part of the choo ecosystem, but works great standalone!
var Button = require('./button.js')
var button = new Button()
// Attach to DOM
document.body.appendChild(button.render('green'))
// Update mounted component
button.render('green')
button.render('red')
// Log a reference to the mounted dom node
console.log(button.element)
Sometimes it's useful to be pass around prototype methods into other functions. This can be done by binding the method that's going to be passed around:
var Nanocomponent = require('nanocomponent')
var html = require('bel')
function Component () {
if (!(this instanceof Button)) return new Component()
Nanocomponent.call(this)
// Bind the method so it can be passed around
this.handleClick = this.handleClick.bind(this)
}
Component.prototype = Object.create(Nanocomponent.prototype)
Component.prototype.handleClick = function (ev) {
console.log('element is', this.element)
}
Component.prototype.createElement = function () {
return html`<button onClick=${this.handleClick}>
My component
</button>`
}
Component.prototype.update = function () {
return false // Never re-render
}
Because Class syntax is just sugar for prototype code, Nanocomponent can be written using Classes too:
var Nanocomponent = require('nanocomponent')
var html = require('bel')
class Component extends Nanocomponent {
constructor () {
super()
this.color = null
}
createElement (color) {
this.color = color
return html`
<div style="background-color: ${color}">
Color is ${color}
</div>
`
}
update (newColor) {
return newColor !== this.color
}
}
Sometimes you might want to mutate the element that's currently mounted, rather than performing DOM diffing. Think cases like third party widgets that manage themselves.
var Nanocomponent = require('nanocomponent')
var html = require('bel')
function Component () {
if (!(this instanceof Button)) return new Component()
Nanocomponent.call(this)
this.text = ''
}
Component.prototype = Object.create(Nanocomponent.prototype)
Component.prototype.createElement = function (text) {
this.text = text
return html`<h1>${text}</h1>`
}
Component.prototype.update = function (text) {
if (text !== this.text) {
this.text = text
this.element.innerText = this.text // Directly update the element
}
return false // Don't call createElement again
}
Component.prototype.unload = function (text) {
console.log('No longer mounted on the DOM!')
}
Components nest and can skip renders at intermediary levels. Components can also act as containers that shape app data flowing into view specific components.
var Nanocomponent = require('nanocomponent')
var html = require('bel')
var Button = require('./button.js')
function Component () {
if (!(this instanceof Button)) return new Component()
Nanocomponent.call(this)
this.button1 = new Button ()
this.button2 = new Button ()
this.button3 = new Button ()
}
Component.prototype = Object.create(Nanocomponent.prototype)
Component.prototype.createElement = function (state) {
var colorArray = shapeData(state)
return html`
<div>
${this.button1.render(colorArray[0])}
${this.button2.render(colorArray[1])}
${this.button3.render(colorArray[2])}
</div>
`
}
Component.prototype.update = function (state) {
var colorArray = shapeData(state) // process app specific data in a container
this.button1.render(colorArray[0]) // pass processed data to owned children components
this.button2.render(colorArray[1])
this.button3.render(colorArray[2])
return false // always return false when mounted
}
// Some arbitrary data shaping function
function shapeData (state) {
return [state.colors.color1, state.colors.color2, state.colors.color3]
}
Nanocomponent was written to work well with choo, but it also works well
with DOM diffing engines that check .isSamNode()
like nanomorph and
morphdom. It is designed and documented in isolation however, so it also
works well on it's own if you are careful. You can even embed it in other SPA
frameworks like React or Preact with the use of nanocomponent-adapters which
enable framework-free components! 😎
It's a node that overloads Node.isSameNode()
to compare it to another node.
This is needed because a given DOM node can only exist in one DOM tree at the
time, so we need a way to reference mounted nodes in the tree without actually
using them. Hence the proxy pattern, and the recently added support for it in
certain diffing engines:
var html = require('bel')
var el1 = html`<div>pink is the best</div>`
var el2 = html`<div>blue is the best</div>`
// let's proxy el1
var proxy = html`<div></div>`
proxy.isSameNode = function (targetNode) {
return (targetNode === el1)
}
el1.isSameNode(el1) // true
el1.isSameNode(el2) // false
proxy.isSameNode(el1) // true
proxy.isSameNode(el2) // false
nanomorph
is a diffing engine that diffs real DOM trees. It runs a series
of checks between nodes to see if they should either be replaced, removed,
updated or reordered. This is done using a series of property checks on the
nodes.
nanomorph
runs Node.isSameNode(otherNode)
when diffing two DOM trees. This
allows us to override the function and replace it with a custom function that
proxies an existing node. Check out the code to see how it works. The result is
that if every element in our tree uses nanocomponent
, only elements that have
changed will be recomputed and re-rendered making things very fast.
nanomorph
, which saw first use in choo 5, has supported isSameNode
since
its conception. morphdom
has supported .isSameNode
since v2.1.0.
react-create-class
?nanocomponent
is very similar to react-create-class
, but it leaves more decisions up
to you. For example, there is no built in props
or state
abstraction in nanocomponent
but you can do something similar with arguments
(perhaps passing a single props
object
to .render
e.g. .render({ foo, bar })
and assigning internal state to this
however
you want (perhaps this.state = { fizz: buzz }
).
component = Nanocomponent()
Create a new Nanocomponent instance. Additional methods can be set on the prototype.
component.render([arguments…])
Render the component. Returns a proxy node if already mounted on the DOM. Proxy nodes make it so DOM diffing algorithms leave the element alone when diffing.
component.element
A getter
property that returns the component's DOM node if its mounted in the page and
null
when its not.
DOMNode = Nanocomponent.prototype.createElement([arguments…])
Must be implemented. Component specific render function. Optionally cache
argument values here. Run anything here that needs to run along side node
rendering. Must return a DOMNode. Use beforerender
to run code after
createElement
when the component is unmounted. Previously named _render
. Arguments that passed to render
are passed to createElement
. Elements returned from createElement
must always return the same root node type.
Boolean = Nanocomponent.prototype.update([arguments…])
Must be implemented. Return a boolean to determine if
prototype.createElement()
should be called. The update
method is analogous to
React's shouldComponentUpdate
. Called only when the component is mounted in
the DOM tree. Arguments passed to render
are passed to update
.
Nanocomponent.prototype.beforerender(el)
A function called right after createElement
returns with el
, but before the fully rendered
element is returned to the render
caller. Run any first render hooks here. The load
and
unload
hooks are added at this stage.
Nanocomponent.prototype.load(el)
Called when the component is mounted on the DOM. Uses on-load under the hood.
Nanocomponent.prototype.unload(el)
Called when the component is removed from the DOM. Uses on-load under the hood.
Nanocomponent.prototype.afterupdate(el)
Called after a mounted component updates (e.g. update
returns true). You can use this hook to call
element.scrollIntoView
or other dom methods on the mounted component.
$ npm install nanocomponent
You can add even more lifecycle events to your components by attatching the following modules
in the beforerender
hook.
6.0.0 - 2017-08-09
🎉 nanocomponent and [cache-component][cc] are merged into one module: nanocomponent@6.0.0
🎉.
Be sure to read the README so that you get an understanding of the new API, but here is a quick summary of what has changed from the perspective of both modules:
cache-component@5
nanocomponent@6
is mostly the same as cache-component@5
except a few methods are renamed and everything you interact with has had the _
prefix removed.
_element
[getter][getter] is renamed to element
._willMount
is renamed to beforerender
because DOM mounting can't be guaranteed from the perspective of a component._didMount
is removed. Consider using load
instead now._update
is renamed to update
and should always be implemented. Instead of the old default shallow compare, not implementing update
throws. You can require('nanocomponent/compare')
to implement the shallow compare if you want that still. See below._args
is removed. arguments
in createElement
and update
are already "sliced", so you can simply capture a copy in update
and createElement
and use it for comparison at a later time._willUpdate
is removed. Anything you could do in _willUpdate
you can just move to update
._didUpdate
is renamed to afterupdate
. It also receives an element argument el
e.g. afterupdate(el)
. This makes its argument signature consistent with the other life-cycle methods.load
and unload
. [on-load][ol] listeners only get added when one or both of the hooks are implemented on a component making the mutation observers optional.cache-component@5
to nanocomponent@6
upgrade guide:_render
to createElement
.update
now. Rename existing _update
method to update
. Here is an example of doing shallow compare on components that didn't implement their own update function previously:var html = require('choo/html')
var Component = require('nanocomponent')
var compare = require('nanocomponent/compare')
class Meta extends Component {
constructor () {
super()
this.arguments = []
}
createElement (title, artist, album) {
this.arguments = arguments // cache a copy of arguments
return html`
<div>
<p>${title}</p>
<p>
${artist} - ${album}
</p>
</div>
`
}
// Implement this to recreate cache-component@5
// behavior when update was not implemented
update () {
return compare(arguments, this.arguments)
}
}
_willMount
to beforerender
_didMount
implementations into load
or a window.requestAnmimationFrame
inside of beforerender
._willUpdate
implementations into update
._didUpdate
to afterupdate
.load
and unload
for DOM insertion aware node interactions 🙌nanocomponent@5
nanocomponent@6
has some subtle but important differences from nanocompnent@5
. Be sure to read the README and check out the examples to get an understanding of the new API.
_element
property is removed. A [getter][getter] called element
is now used instead. Since this is now a read-only getter, you must not assign anything to this property or else bad things will happen. The element
getter returns the component's DOM node if mounted in the page, and undefined
otherwise. You are allowed to mutate that DOM node by hand however. Just don't reassign the property on the component instance.nanocomponent@5
._render
is renamed to createElement
and must now return a DOM node always. In earlier versions you could get away with not returning from _render
and assigning nodes to _element
. No longer! Also, you should move your DOM mutations into update
.createElement
or return false to skip a call to createElement
when render
is called. If you decide to mutate element
"by hand" on updates, do that here (rather than conditional paths inside createElement
)._load
and _unload
renamed to load
and unload
. They have always been optional, but now the mutation observers are only added if at least one of these methods are implemented prior to component instantiation.beforerender
lifecycle hook. Its similar to load
but runs before the function call to render
returns on unmounted component instances. This is where the [on-load][ol] listeners are added and is a good opportunity to add any other lifecycle hooks.afterupdate
runs after update
returns true and the results of createElement
is mutated over the mounted component. Useful for adjusting scroll position.nanocomponent@5
to nanocomponent@6
upgrade guide:_render
to createElement
and _update
to update
.createElement
into update
.createElement
returns a DOM node always. (You will get warnings if you don't and it probably won't work)_load
and _unload
to load
and unload
.load
actions into beforerender
if they don't depend on the newly rendered node being mounted in a DOM tree yet.afterupdate
allowing you to interact with your component after createElement
is called on mounted components 🙌FAQs
Native DOM components that pair nicely with DOM diffing algorithms
The npm package nanocomponent receives a total of 417 weekly downloads. As such, nanocomponent popularity was classified as not popular.
We found that nanocomponent demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 30 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.