├── examples ├── .gitignore ├── package.json ├── simple │ ├── browser.json │ ├── content-after.marko │ ├── content-before.marko │ ├── style.css │ ├── template.marko │ └── client.js ├── lifecycle-events │ ├── browser.json │ ├── remove.svg │ ├── todos.marko │ ├── template.marko │ ├── style.css │ └── client.js ├── README.md ├── index.marko └── server.js ├── test ├── mocha-headless │ ├── .gitignore │ ├── mocha-browser-setup.js │ ├── browser.json │ ├── style.css │ ├── test-page.marko │ └── run.js ├── fixtures │ ├── autotest │ │ ├── tag-to-text │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── text-to-text │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── text-to-tag │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── change-tagname │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── one │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── simple │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── textarea │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── svg-append-new │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── ids-nested │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── ids-nested-3 │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── ids-nested-7 │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── id-change-tag-name │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── ids-prepend │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── two │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── ids-nested-2 │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── input-element-enabled │ │ │ ├── to.html │ │ │ ├── from.html │ │ │ └── index.js │ │ ├── change-tagname-ids │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── input-element-disabled │ │ │ ├── from.html │ │ │ ├── to.html │ │ │ └── index.js │ │ ├── reverse │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── simple-text-el │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── attr-value-empty-string │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── ids-nested-4 │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── input-element │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── reverse-ids │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── ids-nested-5 │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── ids-nested-6 │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── todomvc2 │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── shorten │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── svg-xlink │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── lengthen │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── svg │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── svg-append │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── simple-ids │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── select-element │ │ │ ├── from.html │ │ │ ├── index.js │ │ │ └── to.html │ │ ├── select-element-default │ │ │ ├── to.html │ │ │ ├── from.html │ │ │ └── index.js │ │ ├── svg-no-default-namespace │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── data-table │ │ │ ├── from.html │ │ │ └── to.html │ │ ├── equal │ │ │ ├── to.html │ │ │ └── from.html │ │ ├── todomvc │ │ │ ├── from.html │ │ │ └── to.html │ │ └── data-table2 │ │ │ ├── from.html │ │ │ └── to.html │ ├── change-types │ │ ├── to.html │ │ └── from.html │ └── reverse-ids │ │ ├── to.html │ │ └── from.html ├── browser │ ├── test-result.marko │ ├── benchmark-results.marko │ └── benchmark.js └── .jshintrc ├── factory.js ├── .travis.yml ├── src ├── index.js ├── .jshintrc ├── morphAttrs.js ├── util.js ├── specialElHandlers.js └── morphdom.js ├── .npmignore ├── .gitignore ├── index.d.ts ├── .jshintrc ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── package.json ├── docs ├── virtual-dom.md └── old-benchmark.md ├── CHANGELOG.md ├── dist └── morphdom-umd.min.js └── README.md /examples/.gitignore: -------------------------------------------------------------------------------- 1 | /static -------------------------------------------------------------------------------- /test/mocha-headless/.gitignore: -------------------------------------------------------------------------------- 1 | /generated -------------------------------------------------------------------------------- /test/fixtures/autotest/tag-to-text/to.html: -------------------------------------------------------------------------------- 1 | world -------------------------------------------------------------------------------- /test/fixtures/autotest/text-to-text/to.html: -------------------------------------------------------------------------------- 1 | world -------------------------------------------------------------------------------- /test/fixtures/change-types/to.html: -------------------------------------------------------------------------------- 1 | bold -------------------------------------------------------------------------------- /test/fixtures/autotest/text-to-tag/from.html: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /test/fixtures/autotest/text-to-text/from.html: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /test/fixtures/change-types/from.html: -------------------------------------------------------------------------------- 1 | italics -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./server.js" 3 | } -------------------------------------------------------------------------------- /test/fixtures/autotest/change-tagname/to.html: -------------------------------------------------------------------------------- 1 | bold -------------------------------------------------------------------------------- /test/fixtures/autotest/one/to.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/text-to-tag/to.html: -------------------------------------------------------------------------------- 1 |
world
-------------------------------------------------------------------------------- /factory.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/morphdom-factory'); -------------------------------------------------------------------------------- /test/fixtures/autotest/change-tagname/from.html: -------------------------------------------------------------------------------- 1 | italics -------------------------------------------------------------------------------- /test/fixtures/autotest/one/from.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/simple/from.html: -------------------------------------------------------------------------------- 1 |
bold
-------------------------------------------------------------------------------- /test/fixtures/autotest/tag-to-text/from.html: -------------------------------------------------------------------------------- 1 |
hello
-------------------------------------------------------------------------------- /test/fixtures/autotest/textarea/to.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/svg-append-new/from.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/textarea/from.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested/to.html: -------------------------------------------------------------------------------- 1 |
foo
-------------------------------------------------------------------------------- /test/fixtures/autotest/simple/to.html: -------------------------------------------------------------------------------- 1 |
italicsbold
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-3/to.html: -------------------------------------------------------------------------------- 1 |
strikethrough
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-7/from.html: -------------------------------------------------------------------------------- 1 |
foo
-------------------------------------------------------------------------------- /test/fixtures/autotest/id-change-tag-name/to.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-7/to.html: -------------------------------------------------------------------------------- 1 |
foobar
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested/from.html: -------------------------------------------------------------------------------- 1 |
foobar
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-prepend/from.html: -------------------------------------------------------------------------------- 1 |
W1
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-prepend/to.html: -------------------------------------------------------------------------------- 1 |
W1
-------------------------------------------------------------------------------- /test/fixtures/autotest/two/from.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/two/to.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-2/to.html: -------------------------------------------------------------------------------- 1 |
strikethrough
-------------------------------------------------------------------------------- /test/fixtures/autotest/input-element-enabled/to.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/change-tagname-ids/from.html: -------------------------------------------------------------------------------- 1 |

2 | italics 3 |

-------------------------------------------------------------------------------- /test/fixtures/autotest/change-tagname-ids/to.html: -------------------------------------------------------------------------------- 1 |
2 | italics 3 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/id-change-tag-name/from.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/input-element-disabled/from.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/reverse/from.html: -------------------------------------------------------------------------------- 1 |
2 | 0 3 | bold 4 | 1 5 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/input-element-enabled/from.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/simple-text-el/from.html: -------------------------------------------------------------------------------- 1 |
2 | 0 3 | bold 4 | 1 5 |
-------------------------------------------------------------------------------- /test/mocha-headless/mocha-browser-setup.js: -------------------------------------------------------------------------------- 1 | window.mocha.ui('bdd'); 2 | window.mocha.reporter('html'); -------------------------------------------------------------------------------- /test/fixtures/autotest/attr-value-empty-string/from.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/input-element-disabled/to.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/autotest/simple-text-el/to.html: -------------------------------------------------------------------------------- 1 |
2 | italics 3 | bold 4 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-2/from.html: -------------------------------------------------------------------------------- 1 |
strongtestbar
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-4/from.html: -------------------------------------------------------------------------------- 1 |
bold
-------------------------------------------------------------------------------- /test/fixtures/autotest/attr-value-empty-string/to.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-3/from.html: -------------------------------------------------------------------------------- 1 |
strongtestbar
-------------------------------------------------------------------------------- /test/fixtures/autotest/input-element/from.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/input-element/to.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /test/fixtures/reverse-ids/to.html: -------------------------------------------------------------------------------- 1 |
w2
w1
-------------------------------------------------------------------------------- /test/fixtures/reverse-ids/from.html: -------------------------------------------------------------------------------- 1 |
w1
w2
-------------------------------------------------------------------------------- /test/fixtures/autotest/reverse-ids/to.html: -------------------------------------------------------------------------------- 1 |
w2
w1
-------------------------------------------------------------------------------- /test/fixtures/autotest/reverse/to.html: -------------------------------------------------------------------------------- 1 |
2 | 0 3 | italics 4 | 1 5 | span 6 | 2 7 |
-------------------------------------------------------------------------------- /examples/simple/browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "./style.css", 4 | "require-run: ./client.js" 5 | ] 6 | } -------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-5/from.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | bold 4 |
5 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-6/from.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | foo 4 |
5 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/reverse-ids/from.html: -------------------------------------------------------------------------------- 1 |
w1
w2
-------------------------------------------------------------------------------- /examples/lifecycle-events/browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "./style.css", 4 | "require-run: ./client.js" 5 | ] 6 | } -------------------------------------------------------------------------------- /test/fixtures/autotest/todomvc2/from.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-4/to.html: -------------------------------------------------------------------------------- 1 |
foo
bar
-------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-6/to.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | foo 5 |
6 |
7 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/shorten/to.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 0 4 |
5 |
6 | 1 7 |
8 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/svg-xlink/from.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/svg-xlink/to.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/lengthen/from.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 0 4 |
5 |
6 | 1 7 |
8 |
-------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | node_js: 6 | - "node" 7 | - "10" 8 | 9 | install: 10 | - npm install 11 | 12 | script: "npm run test" 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import morphAttrs from './morphAttrs'; 2 | import morphdomFactory from './morphdom'; 3 | 4 | var morphdom = morphdomFactory(morphAttrs); 5 | 6 | export default morphdom; -------------------------------------------------------------------------------- /test/fixtures/autotest/svg/from.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/autotest/svg/to.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/autotest/input-element-disabled/index.js: -------------------------------------------------------------------------------- 1 | exports.verify = function (context, expect) { 2 | var rootNode = context.rootNode; 3 | expect(rootNode.disabled).to.equal(true); 4 | }; -------------------------------------------------------------------------------- /test/fixtures/autotest/input-element-enabled/index.js: -------------------------------------------------------------------------------- 1 | exports.verify = function (context, expect) { 2 | var rootNode = context.rootNode; 3 | expect(rootNode.disabled).to.equal(false); 4 | }; -------------------------------------------------------------------------------- /examples/simple/content-after.marko: -------------------------------------------------------------------------------- 1 | div#container 2 | h2 - This is the after DOM 3 | input type="text" value="bar" 4 | div.animated.after - This DIV uses CSS transitions -------------------------------------------------------------------------------- /test/fixtures/autotest/svg-append/from.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /examples/simple/content-before.marko: -------------------------------------------------------------------------------- 1 | div#container 2 | h2 - This is the before DOM 3 | input type="text" value="foo" 4 | div.animated.before - This DIV uses CSS transitions -------------------------------------------------------------------------------- /test/fixtures/autotest/ids-nested-5/to.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | foo 4 |
5 |
6 | bar 7 |
8 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/lengthen/to.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 0 4 |
5 |
6 | 1 7 |
8 | After Bar 9 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/shorten/from.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 0 4 |
5 |
6 | 1 7 |
8 | After Bar 9 |
-------------------------------------------------------------------------------- /examples/lifecycle-events/remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test 2 | .* 3 | /node_modules 4 | /work 5 | /build 6 | /.idea/ 7 | /npm-debug.log 8 | /node_modules 9 | /*.sublime-workspace 10 | *.orig 11 | .DS_Store 12 | coverage 13 | *.marko.js 14 | /.cache 15 | /examples 16 | ~* -------------------------------------------------------------------------------- /test/fixtures/autotest/todomvc2/to.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/autotest/simple-ids/from.html: -------------------------------------------------------------------------------- 1 |
2 | 1 3 |
4 | 2 5 |
6 | 3 7 | 8 | 4 9 | 10 | 5 11 |
-------------------------------------------------------------------------------- /test/browser/test-result.marko: -------------------------------------------------------------------------------- 1 |
2 |

${data.name}

3 |

From:

4 | $!{data.fromHtml} 5 |

Expected:

6 | $!{data.expectedHtml} 7 |

Actual:

8 | $!{data.actualHtml} 9 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /work 2 | /build 3 | /.idea/ 4 | /npm-debug.log 5 | /node_modules 6 | /*.sublime-workspace 7 | *.orig 8 | .DS_Store 9 | coverage 10 | *.marko.js 11 | /.cache 12 | /test/mocha-headless/generated 13 | /test/mocha-headless/.cache 14 | .cache 15 | ~* 16 | -------------------------------------------------------------------------------- /test/fixtures/autotest/select-element/from.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/select-element-default/to.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /test/fixtures/autotest/select-element-default/from.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /test/fixtures/autotest/simple-ids/to.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | before 4 | 5 | 1 6 |
7 | 2 8 |
9 | 3 10 | 11 | 4 12 | 13 | 5 14 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/select-element/index.js: -------------------------------------------------------------------------------- 1 | exports.verify = function (context, expect) { 2 | var rootNode = context.rootNode; 3 | var selectNode = rootNode.querySelector('select'); 4 | expect(selectNode.selectedIndex).to.equal(2); 5 | expect(selectNode.children[2].selected).to.equal(true); 6 | }; 7 | -------------------------------------------------------------------------------- /test/mocha-headless/browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "mocha/mocha.js", 4 | "mocha/mocha.css", 5 | "require-run: ./mocha-browser-setup", 6 | "require-run: ../browser/test.js", 7 | "require-run: ../browser/benchmark.js", 8 | "./style.css" 9 | ] 10 | } -------------------------------------------------------------------------------- /test/fixtures/autotest/select-element/to.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/select-element-default/index.js: -------------------------------------------------------------------------------- 1 | exports.verify = function (context, expect) { 2 | var rootNode = context.rootNode; 3 | var selectNode = rootNode.querySelector('select'); 4 | expect(selectNode.selectedIndex).to.equal(0); 5 | expect(selectNode.firstElementChild.selected).to.equal(true); 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/autotest/svg-no-default-namespace/from.html: -------------------------------------------------------------------------------- 1 |
2 |

SVG embedded inline in XHTML

3 | 4 | 5 | 6 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/svg-append/to.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | /svg/index.html 7 | 8 | -------------------------------------------------------------------------------- /test/mocha-headless/style.css: -------------------------------------------------------------------------------- 1 | .results TABLE { 2 | border-collapse: collapse; 3 | } 4 | 5 | .results TD { 6 | border: 1px solid black; 7 | padding: 5px; 8 | text-align: right; 9 | font-family: sans-serif; 10 | } 11 | 12 | .results TABLE THEAD TD, .results TD.test-name { 13 | background-color: #2c3e50; 14 | color: white; 15 | } -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | morphdom examples 2 | ================= 3 | 4 | # Installation 5 | 6 | ``` 7 | git clone https://github.com/patrick-steele-idem/morphdom 8 | cd morphdom 9 | npm install 10 | ``` 11 | 12 | # Running 13 | 14 | ``` 15 | node examples/server.js 16 | ``` 17 | 18 | And then open [http://localhost:8080/](http://localhost:8080/) in your web browser. 19 | -------------------------------------------------------------------------------- /test/fixtures/autotest/svg-no-default-namespace/to.html: -------------------------------------------------------------------------------- 1 |
2 |

SVG embedded inline in XHTML

3 | 4 | 5 | 6 | 7 |
-------------------------------------------------------------------------------- /test/fixtures/autotest/svg-append-new/to.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | /svg/index.html 7 | 8 | 9 |
-------------------------------------------------------------------------------- /examples/simple/style.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | font-family: Helvetica,Arial,sans-serif; 4 | font-size: 16px; 5 | } 6 | 7 | .animated { 8 | transition: all 0.3s ease-in-out; 9 | width: 300px; 10 | height: 100px; 11 | background-color: #2ecc71; 12 | overflow: auto; 13 | } 14 | 15 | .animated.after { 16 | width: 600px; 17 | height: 100px; 18 | background-color: #f1c40f; 19 | } -------------------------------------------------------------------------------- /examples/index.marko: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | morphdom examples 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/lifecycle-events/todos.marko: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /examples/simple/template.marko: -------------------------------------------------------------------------------- 1 | lasso-page package-path="./browser.json" 2 | 3 | 4 | 5 | 6 | 7 | Lifecycle Events | morphdom 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/simple/client.js: -------------------------------------------------------------------------------- 1 | var morphdom = require('../../'); 2 | var contentBeforeTemplate = require('./content-before.marko'); 3 | var contentAfterTemplate = require('./content-after.marko'); 4 | 5 | var state = 'before'; 6 | 7 | window.updateDOM = function() { 8 | var updatedHTML; 9 | 10 | if (state === 'before') { 11 | state = 'after'; 12 | updatedHTML = contentAfterTemplate.renderSync(); 13 | } else { 14 | state = 'before'; 15 | updatedHTML = contentBeforeTemplate.renderSync(); 16 | } 17 | 18 | morphdom(document.getElementById('container'), updatedHTML); 19 | 20 | }; -------------------------------------------------------------------------------- /examples/lifecycle-events/template.marko: -------------------------------------------------------------------------------- 1 | lasso-page package-path="./browser.json" 2 | 3 | 4 | 5 | 6 | 7 | Lifecycle Events | morphdom 8 | 9 | 10 | 11 | 12 | 13 |

Add todo:

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/mocha-headless/test-page.marko: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Morphdom Tests 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | interface MorphDomOptions { 2 | getNodeKey?: (node: Node) => any; 3 | onBeforeNodeAdded?: (node: Node) => false | Node; 4 | onNodeAdded?: (node: Node) => void; 5 | onBeforeElUpdated?: (fromEl: HTMLElement, toEl: HTMLElement) => boolean; 6 | onElUpdated?: (el: HTMLElement) => void; 7 | onBeforeNodeDiscarded?: (node: Node) => boolean; 8 | onNodeDiscarded?: (node: Node) => void; 9 | onBeforeElChildrenUpdated?: (fromEl: HTMLElement, toEl: HTMLElement) => boolean; 10 | skipFromChildren?: (fromEl: HTMLElement) => boolean; 11 | addChild?: (parent: HTMLElement, child: HTMLElement) => void; 12 | childrenOnly?: boolean; 13 | } 14 | 15 | declare function morphdom( 16 | fromNode: Node, 17 | toNode: Node | string, 18 | options?: MorphDomOptions, 19 | ): Node; 20 | 21 | export default morphdom; 22 | -------------------------------------------------------------------------------- /examples/lifecycle-events/style.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | font-family: Helvetica,Arial,sans-serif; 4 | } 5 | 6 | ul { 7 | display: block; 8 | } 9 | 10 | .todo { 11 | font-size: 20px; 12 | opacity: 0; 13 | transition: all 0.3s ease-in-out; 14 | 15 | list-style: none; 16 | background: #16a085; 17 | border-bottom: 0 solid #fff; 18 | color: #fff; 19 | padding: 0 0.5em; 20 | margin: 0; 21 | margin-bottom: 4px; 22 | overflow: hidden; 23 | line-height: 2em; 24 | width: 10em; 25 | vertical-align: middle; 26 | height: 0; 27 | } 28 | 29 | .todo-visible { 30 | opacity: 1; 31 | height: 2em; 32 | } 33 | 34 | 35 | .todo-remove { 36 | background-image: url('./remove.svg'); 37 | height: 16px; 38 | width: 16px; 39 | display: inline-block; 40 | margin-right: 1em; 41 | cursor: pointer; 42 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | 4 | ], 5 | 6 | "globals": { 7 | "define": true, 8 | "require": true 9 | }, 10 | 11 | "node" : true, 12 | "esversion" : 6, 13 | "browser" : true, 14 | "boss" : false, 15 | "curly": false, 16 | "debug": false, 17 | "devel": false, 18 | "eqeqeq": true, 19 | "evil": true, 20 | "forin": false, 21 | "immed": true, 22 | "laxbreak": false, 23 | "newcap": true, 24 | "noarg": true, 25 | "noempty": false, 26 | "nonew": true, 27 | "nomen": false, 28 | "onevar": false, 29 | "plusplus": false, 30 | "regexp": false, 31 | "undef": true, 32 | "sub": false, 33 | "white": false, 34 | "eqeqeq": false, 35 | "latedef": false, 36 | "unused": "vars", 37 | "jquery": false, 38 | "strict": false, 39 | "eqnull": true 40 | } 41 | -------------------------------------------------------------------------------- /src/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | 4 | ], 5 | 6 | "globals": { 7 | "define": true, 8 | "require": true 9 | }, 10 | 11 | "node" : true, 12 | "esnext" : true, 13 | "browser" : true, 14 | "boss" : false, 15 | "curly": false, 16 | "debug": false, 17 | "devel": false, 18 | "eqeqeq": true, 19 | "evil": true, 20 | "forin": false, 21 | "immed": true, 22 | "laxbreak": false, 23 | "newcap": true, 24 | "noarg": true, 25 | "noempty": false, 26 | "nonew": true, 27 | "nomen": false, 28 | "onevar": false, 29 | "plusplus": false, 30 | "regexp": false, 31 | "undef": true, 32 | "sub": false, 33 | "white": false, 34 | "eqeqeq": false, 35 | "latedef": false, 36 | "unused": "vars", 37 | "jquery": true, 38 | "strict": false, 39 | "eqnull": true 40 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | npm_test: 11 | name: npm test 12 | 13 | strategy: 14 | matrix: 15 | include: 16 | - node: 16.x 17 | 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up Node.js ${{ matrix.node }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node }} 27 | 28 | - name: Restore npm cache 29 | uses: actions/cache@v4 30 | with: 31 | path: ~/.npm 32 | key: ${{ runner.os }}-${{ matrix.node }}-node-${{ hashFiles('**/package-lock.json') }} 33 | restore-keys: | 34 | ${{ runner.os }}-${{ matrix.node }}-node- 35 | 36 | - name: npm install and test 37 | run: | 38 | npm install 39 | npm test 40 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "jasmine", 4 | "spyOn", 5 | "it", 6 | "xit", 7 | "console", 8 | "describe", 9 | "xdescribe", 10 | "beforeEach", 11 | "before", 12 | "after", 13 | "waits", 14 | "waitsFor", 15 | "runs", 16 | "$", 17 | "unescape" 18 | ], 19 | 20 | "globals": { 21 | "define": true, 22 | "require": true 23 | }, 24 | 25 | "node" : true, 26 | "es5" : false, 27 | "browser" : true, 28 | "boss" : false, 29 | "curly": false, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": true, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": true, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": false, 47 | "white": false, 48 | "eqeqeq": false, 49 | "latedef": false, 50 | "unused": "vars", 51 | "eqnull": true 52 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Patrick Steele-Idem (psteeleidem.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /test/browser/benchmark-results.marko: -------------------------------------------------------------------------------- 1 | 2 | ${data.getAverageTimeForTest(moduleName, testName)} 3 | 4 | 5 |
6 |
    7 | 8 |
  • 9 | Total time for ${moduleName}: ${data.getTotalTime(moduleName)} 10 | 11 | (winner) 12 | 13 |
  • 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 42 | 43 | 44 | 45 | 46 |
22 | ${moduleName} 23 |
31 | ${testName} 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
47 |
48 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | require('marko/express'); //enable res.marko 2 | require('marko/node-require').install(); 3 | 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var express = require('express'); 7 | var template = require('./index.marko'); 8 | 9 | require('lasso').configure({ 10 | bundlingEnabled: false, 11 | minify: false, 12 | fingerprintsEnabled: false, 13 | outputDir: path.join(__dirname, 'static'), 14 | plugins: [ 15 | 'lasso-marko' 16 | ] 17 | }); 18 | 19 | var app = express(); 20 | 21 | app.use(require('lasso/middleware').serveStatic()); 22 | 23 | var examples = []; 24 | 25 | fs.readdirSync(__dirname).forEach(function(name) { 26 | var dir = path.join(__dirname, name); 27 | 28 | var stat = fs.statSync(dir); 29 | if (!stat.isDirectory()) { 30 | return; 31 | } 32 | 33 | var templatePath = path.join(dir, 'template.marko'); 34 | 35 | if (fs.existsSync(templatePath)) { 36 | var exampleTemplate = require(templatePath); 37 | 38 | examples.push({ 39 | name: name, 40 | route: '/' + name, 41 | controller: function(req, res) { 42 | res.marko(exampleTemplate, { 43 | examples: examples 44 | }); 45 | } 46 | }); 47 | } 48 | }); 49 | 50 | examples.forEach(function(example) { 51 | app.use(example.route, example.controller); 52 | }); 53 | 54 | app.get('/', function(req, res) { 55 | res.marko(template, { 56 | examples: examples 57 | }); 58 | }); 59 | 60 | app.listen(8080, function(err) { 61 | if (err) { 62 | throw err; 63 | } 64 | 65 | if (process.send) { 66 | process.send('online'); 67 | } 68 | console.log('Listening on port 8080'); 69 | }); -------------------------------------------------------------------------------- /examples/lifecycle-events/client.js: -------------------------------------------------------------------------------- 1 | var morphdom = require('../../'); 2 | var todosTemplate = require('./todos.marko'); 3 | var nextId = 0; 4 | 5 | var todos = [ 6 | { title: 'Wake up', visible: true, id: nextId++ }, 7 | { title: 'Eat', visible: true, id: nextId++ }, 8 | { title: 'Drink', visible: true, id: nextId++ }, 9 | { title: 'Sleep', visible: true, id: nextId++ } 10 | ]; 11 | 12 | function updateDOM() { 13 | var todosHtml = todosTemplate.renderSync({ todos: todos }); 14 | morphdom(document.getElementById('todos'), todosHtml, { 15 | onNodeAdded: function(el) { 16 | if (el.className === 'todo') { 17 | setTimeout(function() { 18 | el.className += ' todo-visible'; 19 | }, 10); 20 | } 21 | }, 22 | onBeforeNodeDiscarded: function(el) { 23 | if (el.className === 'todo todo-visible') { 24 | el.className = 'todo'; 25 | 26 | setTimeout(function() { 27 | el.parentNode.removeChild(el); 28 | }, 400); 29 | 30 | // Prevent the node from being discarded so that we can fade it out 31 | return false; 32 | } 33 | } 34 | }); 35 | } 36 | 37 | window.handleTodoFormSubmit = function(event) { 38 | var inputEl = document.getElementById('todoInput'); 39 | var newTodo = { title: inputEl.value, visible: false, id: nextId++ }; 40 | inputEl.value = ''; 41 | todos.push(newTodo); 42 | updateDOM(); 43 | newTodo.visible = true; // Make sure it renders as visible the next time 44 | event.preventDefault(); 45 | }; 46 | 47 | window.handleRemoveTodoClick = function(el) { 48 | var todoId = parseInt(el.getAttribute('data-id')); 49 | todos = todos.filter(function(todo) { 50 | return todo.id !== todoId; 51 | }); 52 | updateDOM(); 53 | }; 54 | 55 | updateDOM(); -------------------------------------------------------------------------------- /src/morphAttrs.js: -------------------------------------------------------------------------------- 1 | var DOCUMENT_FRAGMENT_NODE = 11; 2 | 3 | export default function morphAttrs(fromNode, toNode) { 4 | var toNodeAttrs = toNode.attributes; 5 | var attr; 6 | var attrName; 7 | var attrNamespaceURI; 8 | var attrValue; 9 | var fromValue; 10 | 11 | // document-fragments dont have attributes so lets not do anything 12 | if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) { 13 | return; 14 | } 15 | 16 | // update attributes on original DOM element 17 | for (var i = toNodeAttrs.length - 1; i >= 0; i--) { 18 | attr = toNodeAttrs[i]; 19 | attrName = attr.name; 20 | attrNamespaceURI = attr.namespaceURI; 21 | attrValue = attr.value; 22 | 23 | if (attrNamespaceURI) { 24 | attrName = attr.localName || attrName; 25 | fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName); 26 | 27 | if (fromValue !== attrValue) { 28 | if (attr.prefix === 'xmlns'){ 29 | attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix 30 | } 31 | fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue); 32 | } 33 | } else { 34 | fromValue = fromNode.getAttribute(attrName); 35 | 36 | if (fromValue !== attrValue) { 37 | fromNode.setAttribute(attrName, attrValue); 38 | } 39 | } 40 | } 41 | 42 | // Remove any extra attributes found on the original DOM element that 43 | // weren't found on the target element. 44 | var fromNodeAttrs = fromNode.attributes; 45 | 46 | for (var d = fromNodeAttrs.length - 1; d >= 0; d--) { 47 | attr = fromNodeAttrs[d]; 48 | attrName = attr.name; 49 | attrNamespaceURI = attr.namespaceURI; 50 | 51 | if (attrNamespaceURI) { 52 | attrName = attr.localName || attrName; 53 | 54 | if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) { 55 | fromNode.removeAttributeNS(attrNamespaceURI, attrName); 56 | } 57 | } else { 58 | if (!toNode.hasAttribute(attrName)) { 59 | fromNode.removeAttribute(attrName); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "morphdom", 3 | "description": "Morph a DOM tree to another DOM tree (no virtual DOM needed)", 4 | "main": "dist/morphdom.js", 5 | "module": "dist/morphdom-esm.js", 6 | "jsnext:main": "dist/morphdom-esm.js", 7 | "types": "./index.d.ts", 8 | "scripts": { 9 | "build": "npm run rollup && npm run minify", 10 | "test": "npm run build && npm run test-browser && npm run lint", 11 | "benchmark": "npm run benchmark-browser", 12 | "all": "npm run all-browser && npm run lint", 13 | "lint": "jshint src/", 14 | "minify": "uglifyjs ./dist/morphdom-umd.js -o ./dist/morphdom-umd.min.js", 15 | "rollup": "npm run rollup-default && npm run rollup-factory && npm run rollup-default-umd && npm run rollup-default-esm", 16 | "rollup-default": "rollup src/index.js -o dist/morphdom.js --format cjs", 17 | "rollup-default-umd": "rollup src/index.js -o dist/morphdom-umd.js --format umd --name morphdom", 18 | "rollup-default-esm": "rollup src/index.js -o dist/morphdom-esm.js --format es", 19 | "rollup-factory": "rollup src/morphdom.js -o dist/morphdom-factory.js --format cjs", 20 | "test-browser": "node test/mocha-headless/run.js test", 21 | "benchmark-browser": "node test/mocha-headless/run.js benchmark", 22 | "all-browser": "node test/mocha-headless/run.js test benchmark", 23 | "mocha-chrome": "node test/mocha-headless/run.js", 24 | "mocha-chrome-run": "mocha-chrome ./test/mocha-headless/generated/test-page.html", 25 | "prepublish": "npm run build", 26 | "start": "node examples/server.js" 27 | }, 28 | "author": "Patrick Steele-Idem ", 29 | "license": "MIT", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/patrick-steele-idem/morphdom.git" 33 | }, 34 | "devDependencies": { 35 | "@lasso/marko-taglib": "^1.0.15", 36 | "async": "^2.0.0", 37 | "browser-refresh-taglib": "^1.1.0", 38 | "chai": "^4.2.0", 39 | "diffhtml": "^1.0.0-beta.10", 40 | "express": "^4.14.0", 41 | "ignoring-watcher": "^1.0.5", 42 | "jshint": "^2.7.0", 43 | "lasso": "^3.2.9", 44 | "lasso-marko": "^2.4.7", 45 | "marko": "^4.1.3", 46 | "mocha": "^6.2.2", 47 | "mocha-chrome": "^2.2.0", 48 | "nanomorph": "^5.4.0", 49 | "rollup": "^1.4.1", 50 | "uglify-js": "^3.7.0", 51 | "vdom-virtualize": "2.0.0", 52 | "virtual-dom": "^2.1.1" 53 | }, 54 | "dependencies": {}, 55 | "version": "2.7.7", 56 | "keywords": [ 57 | "dom", 58 | "diff", 59 | "patch", 60 | "virtual", 61 | "browser" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /docs/virtual-dom.md: -------------------------------------------------------------------------------- 1 | Using `morphdom` with a virtual DOM 2 | =================================== 3 | 4 | `morphdom` has always supported diffing real DOM nodes with real DOM nodes and this will continue to be supported. Support for diffing real DOM with a _virtual_ DOM was introduced in `v2.1.0`. Virtual DOM nodes are expected to implement a minimal DOM API that consists of the following methods and properties: 5 | 6 | - `node.firstChild` 7 | - `node.nextSibling` 8 | - `node.nodeType` 9 | - `node.nodeName` 10 | - `node.namespaceURI` 11 | - `node.nodeValue` 12 | - `node.attributes` 13 | - `node.value` 14 | - `node.selected` 15 | - `node.disabled` 16 | - `node.hasAttributeNS(namespaceURI, name)` 17 | - `node.actualize(document)` [1] 18 | - `node.isSameNode(anotherNode)` [2] 19 | 20 | NOTES: 21 | 22 | 1. In addition to the standard DOM node methods and properties, a virtual DOM node must also provide a `node.actualize(document)` method. The `node.actualize(document)` will be called when the virtual DOM node needs to be upgraded to a real DOM node so that it can be moved into the real DOM. 23 | 2. A virtual DOM node may choose to implement `isSameNode(anotherNode)` to short-circuit diffing/patching a particular DOM subtree by treating two nodes as the "same" 24 | 3. A virtual DOM node may choose to implement the non-standard `assignAttributes(targetNode)` to optimize copying the attributes from the virtual DOM node to the target DOM node. If virtual DOM node implements `assignAttributes(targetNode)` then it is not necessary to implement `node.attributes`. 25 | 26 | [marko-vdom](https://github.com/marko-js/marko-vdom) is the first virtual DOM implementation that is compatible with `morphdom` and it can be used as a reference implementation. 27 | 28 | # FAQ 29 | 30 | ## Why support a virtual DOM? 31 | 32 | Working with real DOM nodes is fast, but real DOM nodes do tend to have more overhead (the amount of overhead associated with real DOM nodes will vary drastically by browser). In order to be 100% compliant with the DOM specification, real DOM nodes require a lot of internal "bookkeeping" and validation checks that slow certain operations down. Virtual DOM nodes have the advantage that they can be optimized to use less memory and enable better performance since they are not required to be compatible the entire DOM specification. 33 | 34 | When using `morphdom` to update the view, performance will be largely dictated by how much time it takes to render the view to a virtual DOM/real DOM and how long it takes to walk the tree (including iterating over attributes). We are seeing signficant performance improvements when utilizing a virtual DOM. Please see the [marko-vdom benchmarks](https://github.com/marko-js/marko-vdom#benchmarks) to better understand the performance characteristics of the virtual DOM and the real DOM. 35 | 36 | ## Can `morphdom` be used with any virtual DOM implementation? 37 | 38 | No, `morphdom` cannot be used with any virtual DOM implementation. To keep code size small and fast, morphdom requires that virtual DOM nodes implement the minimal set of methods and properties based described above. One of the goals of `morphdom` is to stay as close as possible to the real DOM while allowing for a more optimized virtual DOM when it makes sense. 39 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | var range; // Create a range object for efficently rendering strings to elements. 2 | var NS_XHTML = 'http://www.w3.org/1999/xhtml'; 3 | 4 | export var doc = typeof document === 'undefined' ? undefined : document; 5 | var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template'); 6 | var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange(); 7 | 8 | function createFragmentFromTemplate(str) { 9 | var template = doc.createElement('template'); 10 | template.innerHTML = str; 11 | return template.content.childNodes[0]; 12 | } 13 | 14 | function createFragmentFromRange(str) { 15 | if (!range) { 16 | range = doc.createRange(); 17 | range.selectNode(doc.body); 18 | } 19 | 20 | var fragment = range.createContextualFragment(str); 21 | return fragment.childNodes[0]; 22 | } 23 | 24 | function createFragmentFromWrap(str) { 25 | var fragment = doc.createElement('body'); 26 | fragment.innerHTML = str; 27 | return fragment.childNodes[0]; 28 | } 29 | 30 | /** 31 | * This is about the same 32 | * var html = new DOMParser().parseFromString(str, 'text/html'); 33 | * return html.body.firstChild; 34 | * 35 | * @method toElement 36 | * @param {String} str 37 | */ 38 | export function toElement(str) { 39 | str = str.trim(); 40 | if (HAS_TEMPLATE_SUPPORT) { 41 | // avoid restrictions on content for things like `Hi` which 42 | // createContextualFragment doesn't support 43 | //