Skip to content

Commit

Permalink
Merge pull request #395 from 0a-/master
Browse files Browse the repository at this point in the history
a way to handle pre-rendered HTML (including the option to do checksum comparison)
  • Loading branch information
anthonyshort committed Jan 30, 2016
2 parents 3f93d39 + 67f8c43 commit 5c2fbef
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 21 deletions.
9 changes: 6 additions & 3 deletions docs/api/create-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ render(<App size="large" />)

### Notes

The container DOM element should:

* **Not be the document.body**. You'll probably run into problems with other libraries. They'll often add elements to the `document.body` which can confuse the diff algorithm.
* **Be empty**. All elements inside of the container will be removed when a virtual element is rendered into it. The renderer needs to have complete control of all of the elements within the container.
* You should **avoid using document.body as the container element**. You'll probably run into problems with other libraries. They'll often add elements to the `document.body` which can confuse the diff algorithm.

* When the container element is not empty, **deku would assume that the HTML elements inside the container are the pre-rendered elements**. Read [this page](/deku/docs/tips/pre-rendered.md) to learn more about working with pre-rendered elements.


. All elements inside of the container will be removed when a virtual element is rendered into it. The renderer needs to have complete control of all of the elements within the container.
35 changes: 35 additions & 0 deletions docs/tips/pre-rendered.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Pre-rendered HTML Elements

When the browser requests the HTML file, some of the elements may have been pre-rendered on the server-side. This can be done using deku's [`string.render`](/deku/docs/api/string.html).


```html
<div id="container"> <p>pre-rendered text</p> </div>
```

On the client side, if we just create a render function as usual, the first call to the render function would not do anything. This is because deku would assume that the container's pre-rendered content is properly rendered.

```js
var render = createApp(document.getElementById("container"))
render(<p>pre-rendered text</p>) //do nothing
```

This means if the virtualDOM describes a different HTML element, deku is not going to fix it for you.

```js
var render = createApp(document.getElementById("container"))
render(<p>Meow!</p>) //do nothing
```

Therefore, to be 100% safe, one may want to do a [checksum comparison](https://en.wikipedia.org/wiki/Checksum) between the pre-rendered (on the server-side) and to-be-rendered (from the virtualDOM) HTML elements. This can be done by setting the attribute `checksum` for the container element.

```html
<div id="container" checksum> <p>pre-rendered text</p> </div>
```

In this case, on the first call to the render function, deku would destroy and recreate the elements inside the container, if there is a difference between the pre-rendered and to-be-rendered versions:

```js
var render = createApp(document.getElementById("container"))
render(<p>Meow!</p>) //re-rendered the HTML due to difference in checksums
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"zuul": "3.9.0"
},
"dependencies": {
"adler-32": "^0.3.0",
"dift": "0.1.12",
"index-of": "0.2.0",
"is-svg-element": "1.0.1",
Expand Down
20 changes: 15 additions & 5 deletions src/app/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as dom from '../dom'
import {diffNode} from '../diff'
import {str as adler32} from 'adler-32'

/**
* Create a DOM renderer using a container element. Everything will be rendered
Expand All @@ -12,10 +13,6 @@ export function create (container, dispatch, options = {}) {
let node = null
let rootId = options.id || '0'

if (container && container.childNodes.length > 0) {
container.innerHTML = ''
}

let update = (newVnode, context) => {
let changes = diffNode(oldVnode, newVnode, rootId)
node = changes.reduce(dom.update(dispatch, context), node)
Expand All @@ -25,7 +22,20 @@ export function create (container, dispatch, options = {}) {

let create = (vnode, context) => {
node = dom.create(vnode, rootId, dispatch, context)
if (container) container.appendChild(node)
if (container){
if(container.childNodes.length === 0){
container.appendChild(node)
}else{
if (container.attributes.checksum){
let preRendered = adler32(container.innerHTML)
let toBeRendered = adler32(node.outerHTML)
if(preRendered != toBeRendered){
container.innerHTML = ''
container.appendChild(node)
}
}
}
}
oldVnode = vnode
return node
}
Expand Down
56 changes: 43 additions & 13 deletions test/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,6 @@ test('moving elements using keys', t => {
t.end()
})

test('emptying the container', t => {
let el = document.createElement('div')
el.innerHTML = '<div></div>'
let render = createDOMRenderer(el)
render(<span></span>)
t.equal(
el.innerHTML,
'<span></span>',
'container emptied'
)
t.end()
})

test('context should be passed down all elements', t => {
let Form = {
render ({ props, context }) {
Expand Down Expand Up @@ -283,3 +270,46 @@ test('rendering and updating null', t => {

t.end()
})

test('rendering in a container with pre-rendered HTML', t => {
/*
In the first call to render:
For container with pre-rendered HTML (i.e. childNodes.length > 0)
If the container has attribute `checksum `:
we compute checksum for both rendered the to-be-rendered HTML,
destroy-and-recreate if there's difference in the checksums
Otherwise:
we assume there are no errors in the pre-rendered HTML
*/

let el = document.createElement('div')

el.innerHTML = '<div><span id="1"></span><span id="2"></span></div>'
let render = createDOMRenderer(el)
render(<div><span id="2"></span></div>)
t.equal(
el.innerHTML,
'<div><span id="1"></span><span id="2"></span></div>',
'no comparison of checksums occurs (nothing should happen)'
)

el.attributes.checksum = ' '
el.innerHTML = '<div><span>Meow</span></div>'
render = createDOMRenderer(el)
render(<div><span>Thrr</span></div>)
t.equal(
el.innerHTML,
'<div><span>Thrr</span></div>',
'destory and re-rendered due to checksums difference'
)

el.innerHTML = '<div><span>Cat</span></div>'
render(<div><span>Neko</span></div>)
t.equal(
el.innerHTML,
'<div><span>Cat</span></div>',
'nothing should happen because this is not the first call to render'
)

t.end()
})

0 comments on commit 5c2fbef

Please sign in to comment.