Skip to content
Permalink
Browse files

feat: Remove wrapping element

BREAKING CHANGE: Now <Hydrate> does not generate any extra DOM nodes.
  • Loading branch information
znck committed Aug 16, 2019
1 parent ff44288 commit 8c9af4ca3af2b0f8587b5188e363dc7ea30d0431
@@ -0,0 +1,9 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "2.0.0",
"configurations": [

]
}
@@ -0,0 +1,36 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build Component",
"type": "shell",
"command": "npm",
"args": ["run", ":build"],
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "Build Example",
"type": "shell",
"command": "npm",
"args": ["run", "build"],
"options": {
"cwd": "${workspaceFolder}/example"
}
},
{
"label": "Start Example Server",
"type": "shell",
"command": "node",
"args": [
"${workspaceFolder}/example/dist/ssr.js"
],
"dependsOrder": "sequence",
"dependsOn": [
"Build Component",
"Build Example"
]
}
]
}
@@ -0,0 +1,187 @@
/* eslint-disable no-console */
import PropTypes from '@znck/prop-types'

const isBrowser = typeof window !== 'undefined'

const io =
typeof IntersectionObserver !== 'undefined'
? new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting || entry.intersectionRatio > 0) {
entry.target.dispatchEvent(new CustomEvent('hydration:visible'))
}
})
})
: null

const asyncFactoryResolved = { resolved: true }
const asyncFactory = {}
const clientSideDivData = {
attrs: {
'data-force-hydrate': true,
},
}

export default {
props: {
on: PropTypes.oneOfType(String, PropTypes.arrayOf(String)),
onClick: PropTypes.bool,
onHover: PropTypes.bool,
onInteraction: PropTypes.bool,
whenVisible: PropTypes.bool,
whenIdle: PropTypes.bool,
withDelay: PropTypes.number,
ssrOnly: PropTypes.bool,
force: PropTypes.bool,
},
data: () => ({
hydrated: !isBrowser,
}),
created() {
PropTypes.validate(() => {
if (
!this.on &&
!this.onClick &&
!this.onHover &&
!this.onInteraction &&
!this.whenVisible &&
!this.whenIdle &&
!this.ssrOnly &&
this.force === undefined
) {
console.error(
`Select at least one trigger to enable hydration. If you don't want to hydrate at all use 'ssr-only'.`
)
}

if (this.withDelay && this.withDelay < 800) {
console.warn(
`Delay duration ${
this.withDelay
}ms is too low. A good choice would be around 2000ms. See https://github.com/znck/lazy-hydration.`
)
}
})
},
mounted() {
if (this.$el.dataset.forceHydrate) {
PropTypes.validate(() => {
console.log('No SSR rendered content found. Force Hydrate.')
})
// No SSR rendered content. Render now.
this.hydrate()

return
}

if (this.ssrOnly) return

let withDelay
const on = (Array.isArray(this.on) ? this.on : [this.on])
.slice()
.filter(name => typeof name !== 'string')

if (this.onClick) {
on.push('click')
}

if (this.onHover || this.onInteraction) {
on.push('mouseenter')

if (this.onInteraction) {
on.push('focus')
}
}

if (this.whenIdle) {
if (typeof requestIdleCallback !== 'undefined') {
const id = requestIdleCallback(
() => {
requestAnimationFrame(() => {
this.hydrate()
})
},
{ timeout: 500 }
)

this.idle = () => cancelIdleCallback(id)
} else withDelay = 2000
}

if (this.whenVisible) {
const el = this.$el
// As root node does not have any box model, it cannot intersect.
on.push('hydration:visible')
if (io) io.observe(el)
else {
withDelay = 2000
PropTypes.validate(() =>
console.warn('IntersectionObserver polyfill is required.')
)
}

this.visible = () => {
io && io.unobserve(el)
}
}

if (on.length) {
on.forEach(event =>
this.$el.addEventListener(event, this.hydrate, {
capture: true,
once: true,
})
)
this.off = () =>
on.forEach(event => this.$el.removeEventListener(event, this.hydrate))
}

if (this.withDelay || withDelay) {
const id = setTimeout(this.hydrate, this.withDelay || withDelay)
this.delay = () => clearTimeout(id)
}
},
beforeDestroy() {
this.cleanup()
},
methods: {
cleanup() {
const handlers = ['visible', 'idle', 'delay', 'off']

for (const handler of handlers) {
if (handler in this) {
this[handler]()
delete this[handler]
}
}
},
hydrate() {
this.hydrated = true
this.cleanup()
},
},
render(h) {
const vnode = this.hydrated
? this.$scopedSlots.default
? this.$scopedSlots.default({ hydrated: this.hydrated })[0]
: this.$slots.default[0]
: h('div', clientSideDivData)

vnode.asyncFactory = this.hydrated ? asyncFactoryResolved : asyncFactory
vnode.isComment = !this.hydrated

if (isBrowser) {
window.vnode = vnode
}

return vnode
},
watch: {
force: {
handler(value) {
if (value) this.hydrate()
},
immediate: true,
},
},
}

0 comments on commit 8c9af4c

Please sign in to comment.
You can’t perform that action at this time.