From 2be98636eaacf60710ca5f5c0ecf0abfcb61d175 Mon Sep 17 00:00:00 2001
From: Patrick Steele-Idem
Date: Thu, 3 Aug 2017 17:00:44 -0600
Subject: [PATCH] Fixes #817 - Support dynamic root elements
[Optimizations] Simplified bookkeeping for component tree
A component stack is no longer used
Small API improvements
Improved how component boundaries are managed
Assign keys to all HTML elements and custom tags for better diffing
Checking in progress
Just build the src when calculating size
---
benchmark/size/minify.js | 10 +-
benchmark/size/package.json | 2 +-
package.json | 1 +
scripts/build.js | 58 +-
src/compiler/Builder.js | 21 +-
src/compiler/CompileContext.js | 5 +-
src/compiler/Parser.js | 8 +
src/compiler/ast/DocumentType.js | 4 +-
src/compiler/ast/HtmlComment.js | 19 +-
src/compiler/ast/HtmlElement/index.js | 10 +
.../ast/HtmlElement/vdom/HtmlElementVDOM.js | 98 +-
.../ast/HtmlElement/vdom/generateCode.js | 12 +-
src/compiler/ast/Node.js | 7 +
src/compiler/util/vdom/VDOMOptimizer.js | 21 +-
src/components/Component.js | 256 +-
src/components/ComponentDef.js | 57 +-
src/components/ComponentsContext.js | 120 +-
src/components/GlobalComponentsContext.js | 18 +
src/components/KeySequence.js | 27 +
src/components/attach-detach.js | 11 +-
src/components/beginComponent-browser.js | 17 +-
src/components/beginComponent.js | 41 +-
src/components/endComponent-browser.js | 5 +
src/components/endComponent.js | 7 +
src/components/index.js | 94 +-
src/components/init-components-browser.js | 160 +-
src/components/jquery.js | 6 +-
src/components/legacy/renderer-legacy.js | 68 +-
src/components/package.json | 1 +
src/components/renderer.js | 95 +-
.../taglib/TransformHelper/ComponentArgs.js | 47 +-
.../TransformHelper/assignComponentId.js | 105 +-
.../TransformHelper/convertToComponent.js | 158 ++
.../TransformHelper/handleComponentBind.js | 287 ---
.../handleComponentKeyAttrs.js | 51 -
.../handleComponentPreserve.js | 11 +-
.../TransformHelper/handleIncludeNode.js | 75 -
.../TransformHelper/handleLegacyBind.js | 155 ++
.../taglib/TransformHelper/handleRootNodes.js | 173 +-
.../TransformHelper/handleScopedAttrs.js | 75 +
.../taglib/TransformHelper/index.js | 54 +-
.../taglib/components-transformer.js | 27 +-
.../taglib/helpers/getCurrentComponent.js | 8 +-
src/components/taglib/helpers/markoKeyAttr.js | 9 +
src/components/taglib/include-tag-browser.js | 19 -
src/components/taglib/include-tag.js | 5 -
src/components/taglib/init-components-tag.js | 2 +-
src/components/taglib/marko.json | 12 +-
src/components/taglib/preserve-tag-browser.js | 54 +-
src/components/util-browser.js | 104 +-
src/components/util.js | 8 +-
src/morphdom/index.js | 730 ++++--
src/morphdom/specialElHandlers.js | 12 +-
src/runtime/RenderResult.js | 6 +-
src/runtime/dom-insert.js | 12 +-
src/runtime/html/AsyncStream.js | 1 +
src/runtime/vdom/AsyncVDOMBuilder.js | 103 +-
src/runtime/vdom/VComment.js | 3 +-
src/runtime/vdom/VComponent.js | 16 +
src/runtime/vdom/VDocumentFragment.js | 3 +-
src/runtime/vdom/VElement.js | 165 +-
src/runtime/vdom/VNode.js | 29 +-
src/runtime/vdom/helpers.js | 4 +-
src/runtime/vdom/preserve-attrs.js | 6 +-
src/runtime/vdom/vdom.js | 44 +-
src/taglibs/core/marko.json | 5 +-
src/taglibs/html/marko.json | 3 +-
.../components-await-title/expected.html | 2 +-
.../dynamic-tag-name/expected.js | 2 +-
.../compiler-browser/simple/expected.js | 12 +-
.../compiler-browser/svg-anchor/expected.js | 6 +-
.../svg-dynamic-tag-name/expected.js | 4 +-
.../compiler-browser/svg/expected.js | 6 +-
.../for-key-repeated}/index.marko | 4 +-
.../for-key-repeated/test.js | 31 +
.../repeated-with-label-ref/test.js | 7 +-
.../widget-jQuery-proxy/test.js | 1 -
.../widget-legacy-render-same-id/test.js | 4 +-
.../widget-rerender-init-order/test.js | 6 +-
.../widget-rerender-reuse-stateful/test.js | 6 +-
.../widget-stateful-reuse-widgets/test.js | 6 +-
.../components/inner/index.marko | 3 +
.../index.marko | 9 +
.../test.js | 21 +
.../components/bar/index.marko | 3 +
.../components/foo/index.marko | 3 +
.../index.marko | 19 +
.../test.js | 21 +
.../components/bar/index.marko | 3 +
.../components/foo/index.marko | 3 +
.../diffpatch-component-mismatch/index.marko | 19 +
.../diffpatch-component-mismatch/test.js | 16 +
.../components/bar/index.marko | 16 +
.../components/foo/index.marko | 3 +
.../index.marko | 12 +
.../test.js | 10 +
.../components/hello/index.marko | 5 +
.../diffpatch-destroy-child/index.marko | 15 +
.../diffpatch-destroy-child/test.js | 21 +
.../components/hello/index.marko | 5 +
.../index.marko | 21 +
.../test.js | 17 +
.../components/hello/index.marko | 5 +
.../index.marko | 19 +
.../test.js | 17 +
.../components/hello/index.marko | 5 +
.../index.marko | 19 +
.../test.js | 17 +
.../components/hello/index.marko | 3 +
.../index.marko | 9 +
.../test.js | 96 +
.../diffpatch-rearrange-keyed-els/index.marko | 6 +
.../diffpatch-rearrange-keyed-els/test.js | 69 +
.../diffpatch-remove-all-els/index.marko | 12 +
.../diffpatch-remove-all-els/test.js | 16 +
.../diffpatch-remove-end-el/index.marko | 12 +
.../diffpatch-remove-end-el/test.js | 14 +
.../diffpatch-remove-start-el/index.marko | 12 +
.../diffpatch-remove-start-el/test.js | 14 +
.../diffpatch-simple/index.marko | 13 +
.../diffpatch-simple/test.js | 14 +
.../components/hello/index.marko | 5 +
.../components/world/index.marko | 5 +
.../index.marko | 19 +
.../diffpatch-swap-components-dynamic/test.js | 24 +
.../components/hello/index.marko | 5 +
.../components/world/index.marko | 5 +
.../index.marko | 19 +
.../test.js | 32 +
.../components/hello/index.marko | 5 +
.../components/world/index.marko | 5 +
.../index.marko | 18 +
.../diffpatch-swap-components-keyed/test.js | 27 +
.../diffpatch-swap-keyed-el/index.marko | 18 +
.../diffpatch-swap-keyed-el/test.js | 18 +
.../diffpatch-swap-unkeyed-el/index.marko | 18 +
.../diffpatch-swap-unkeyed-el/test.js | 26 +
.../event-attach-el/test.js | 20 +-
.../extend-component/test.js | 12 +-
.../forceUpdate/index.marko | 2 +-
.../include-root/index.marko | 13 +
.../include-root/modal.marko | 9 +
.../components-browser/include-root/test.js | 7 +
.../label-for-scoped-repeated/index.marko | 12 +
.../label-for-scoped-repeated/test.js | 32 +
.../components/foo/index.marko | 6 +-
.../lifecyle-hooks-destroy/index.marko | 6 +-
.../morphdom-node-added-nested-keyed/test.js | 7 +-
.../repeated-with-label-ref/test.js | 20 -
.../rerender-multiple-roots/index.marko | 4 +-
.../rerender-multiple-roots/test.js | 2 -
.../rerender-same-id/index.marko | 3 -
.../component.js | 0
.../rerender-same-root/index.marko | 3 +
.../test.js | 6 +-
.../components/app-button/index.marko | 29 -
.../index.marko | 9 -
.../transclusion-include-from-state/test.js | 31 -
.../widget-include-ref/index.marko | 3 +-
.../widget-jQuery-proxy/test.js | 1 -
.../widget-rerender-init-order/test.js | 6 +-
.../component.js | 11 -
.../app-stateful-button/component.js | 16 -
.../app-stateful-button/index.marko | 4 -
.../app-stateful-button/marko-tag.json | 19 -
.../components/hello/index.marko | 13 +
.../index.marko | 16 +-
.../widget-rerender-reuse-stateful/test.js | 47 +-
.../component.js | 13 -
.../app-stateful-button/component.js | 8 -
.../app-stateful-button/index.marko | 4 -
.../app-stateful-button/marko-tag.json | 19 -
.../widget-stateful-reuse-widgets/index.marko | 4 -
.../widget-stateful-reuse-widgets/test.js | 36 -
.../bind-component/expected.js | 10 +-
.../bind-widget/expected.js | 10 +-
.../component-include-attr/expected.js | 35 +-
.../component-template-entry/expected.js | 31 +-
.../widget-types/expected.js | 10 +-
.../components/my-component/index.marko | 0
.../boundary-el-if-el/expected.js | 51 +
.../boundary-el-if-el/index.marko | 7 +
.../boundary-el-if/expected.js | 46 +
.../boundary-el-if/index.marko | 6 +
.../components/test-no-output/marko-tag.json | 4 +
.../components/test-no-output/renderer.js | 3 +
.../boundary-el-no-output-tag/expected.js | 46 +
.../boundary-el-no-output-tag/index.marko | 4 +
.../boundary-html-tag/expected.js | 71 +
.../boundary-html-tag/index.marko | 9 +
.../boundary-if-el/expected.js | 46 +
.../boundary-if-el/index.marko | 6 +
.../components/my-component/index.marko | 0
.../boundary-if-root/expected.js | 41 +
.../boundary-if-root/index.marko | 5 +
.../components/my-component/index.marko | 0
.../expected.js | 46 +
.../index.marko | 3 +
.../components/my-component/index.marko | 0
.../expected.js | 51 +
.../index.marko | 3 +
.../expected.js | 38 +
.../index.marko | 3 +
.../expected.js | 48 +
.../index.marko | 3 +
.../expected.js | 39 +
.../index.marko | 3 +
.../expected.js | 39 +
.../index.marko | 3 +
.../boundary-multi-root-html-els/expected.js | 44 +
.../boundary-multi-root-html-els/index.marko | 3 +
.../components/test-no-output/marko-tag.json | 4 +
.../components/test-no-output/renderer.js | 3 +
.../boundary-no-output-tag-el/expected.js | 46 +
.../boundary-no-output-tag-el/index.marko | 4 +
.../components/my-component/index.marko | 0
.../expected.js | 41 +
.../index.marko | 2 +
.../components/my-component/index.marko | 0
.../expected.js | 41 +
.../index.marko | 2 +
.../expected.js | 34 +
.../index.marko | 2 +
.../expected.js | 41 +
.../index.marko | 2 +
.../boundary-single-root-html-el/expected.js | 39 +
.../boundary-single-root-html-el/index.marko | 2 +
.../component-with-import-static/expected.js | 38 +
.../component-with-import-static/index.marko | 8 +
.../auto-key-els-renderBody/component.js | 1 +
.../components/another-component/index.marko | 0
.../auto-key-els-renderBody/expected.js | 53 +
.../auto-key-els-renderBody/index.marko | 9 +
.../auto-key-els/component.js | 1 +
.../auto-key-els/expected.js | 78 +
.../auto-key-els/foo.marko | 0
.../auto-key-els/index.marko | 11 +
.../bind-component/expected.js | 8 +-
.../bind-widget/expected.js | 8 +-
.../components/my-component/index.marko | 0
.../boundary-el-if-el/expected.js | 38 +
.../boundary-el-if-el/index.marko | 7 +
.../components/my-component/index.marko | 0
.../boundary-el-if/expected.js | 36 +
.../boundary-el-if/index.marko | 6 +
.../components/test-no-output/marko-tag.json | 4 +
.../components/test-no-output/renderer.js | 3 +
.../boundary-el-no-output-tag/expected.js | 40 +
.../boundary-el-no-output-tag/index.marko | 4 +
.../components/my-component/index.marko | 0
.../boundary-if-root/expected.js | 34 +
.../boundary-if-root/index.marko | 5 +
.../components/my-component/index.marko | 0
.../expected.js | 47 +
.../index.marko | 3 +
.../components/my-component/index.marko | 0
.../expected.js | 43 +
.../index.marko | 3 +
.../expected.js | 38 +
.../index.marko | 3 +
.../expected.js | 32 +
.../index.marko | 3 +
.../expected.js | 39 +
.../index.marko | 3 +
.../expected.js | 39 +
.../index.marko | 3 +
.../boundary-multi-root-html-els/expected.js | 32 +
.../boundary-multi-root-html-els/index.marko | 3 +
.../components/test-no-output/marko-tag.json | 4 +
.../components/test-no-output/renderer.js | 3 +
.../boundary-no-output-tag-el/expected.js | 40 +
.../boundary-no-output-tag-el/index.marko | 4 +
.../components/my-component/index.marko | 0
.../expected.js | 41 +
.../index.marko | 2 +
.../components/my-component/index.marko | 0
.../expected.js | 41 +
.../index.marko | 2 +
.../expected.js | 36 +
.../index.marko | 2 +
.../expected.js | 32 +
.../index.marko | 2 +
.../boundary-single-root-html-el/expected.js | 32 +
.../boundary-single-root-html-el/index.marko | 2 +
.../expected.js | 17 +-
.../child-tag-no-output/expected.js | 7 +-
.../class-method-empty-return/expected.js | 8 +-
.../component-include-attr/expected.js | 23 +-
.../component-inline-class/expected.js | 8 +-
.../expected.js | 16 +-
.../expected.js | 8 +-
.../component-template-entry/expected.js | 8 +-
.../component-template-non-index/expected.js | 8 +-
.../expected.js | 21 +-
.../include-whitespace-preserved/expected.js | 21 +-
.../index-widget-only/expected.js | 8 +-
.../key-colon-attr/expected.js | 6 +-
.../test.js | 2 +-
.../test.js | 2 +-
.../key-suffix/expected.js | 13 +-
.../components-compilation/key/expected.js | 12 +-
.../label-for/expected.js | 47 +
.../label-for/template.marko | 3 +
.../macro-widget/expected.js | 4 +-
.../diff-body-root/template.marko | 3 +
.../components-pages/diff-body/template.marko | 7 +-
.../diff-html-include-layout/layout.marko | 21 +
.../diff-html-include-layout/template.marko | 21 +
.../diff-html-include-layout/tests.js | 14 +
.../components/hello/index.marko | 10 +
.../getEl-no-rerender}/marko.json | 0
.../getEl-no-rerender/template.marko | 25 +
.../getEl-no-rerender/tests.js | 10 +
.../split-component/component-browser.js | 5 +
.../components/split-component/index.marko | 3 +
.../components-pages/getEl-split/marko.json | 3 +
.../getEl-split/template.marko | 23 +
.../components-pages/getEl-split/tests.js | 10 +
.../getRenderedWidgets/tests.js | 19 +-
.../components-pages/onInput/template.marko | 2 +
.../components-pages/widget-fixed-id/tests.js | 4 +-
.../test.js | 4 +-
.../hot-reload/component-to-template/test.js | 4 +-
.../template-export-component/test.js | 6 +-
.../hot-reload/template-to-component/test.js | 4 +-
.../expected.html | 8 +-
.../morphdom/add-rearrange/expected.html | 8 +-
.../attr-value-empty-string/expected.html | 3 +-
.../morphdom/change-tagname-ids/expected.html | 11 +-
.../morphdom/change-tagname/expected.html | 5 +-
.../morphdom/data-table/expected.html | 553 ++---
.../morphdom/data-table2/expected.html | 2197 +++++++++--------
test/autotests/morphdom/equal/expected.html | 287 +--
.../morphdom/id-change-tag-name/expected.html | 5 +-
.../morphdom/ids-nested-2/expected.html | 9 +-
.../morphdom/ids-nested-3/expected.html | 7 +-
.../morphdom/ids-nested-4/expected.html | 15 +-
.../morphdom/ids-nested-5/expected.html | 25 +-
.../morphdom/ids-nested-6/expected.html | 15 +-
.../morphdom/ids-nested-7/expected.html | 11 +-
.../morphdom/ids-nested/expected.html | 7 +-
.../morphdom/ids-prepend/expected.html | 9 +-
.../expected.html | 15 +-
.../incompatible-root-tag/expected.html | 15 +-
.../input-element-disabled/expected.html | 4 +-
.../input-element-enabled/expected.html | 3 +-
.../morphdom/input-element/expected.html | 9 +-
.../morphdom/keyed-incompatible/expected.html | 6 +-
test/autotests/morphdom/large/expected.html | 1653 ++++++-------
.../autotests/morphdom/lengthen/expected.html | 23 +-
test/autotests/morphdom/one/expected.html | 3 +-
.../morphdom/reverse-ids/expected.html | 11 +-
test/autotests/morphdom/reverse/expected.html | 17 +-
.../morphdom/select-element/expected.html | 33 +-
test/autotests/morphdom/shorten/expected.html | 17 +-
.../simple-div-one-to-one/expected.html | 6 +-
.../morphdom/simple-ids/expected.html | 23 +-
.../morphdom/simple-text-el/expected.html | 17 +-
test/autotests/morphdom/simple/expected.html | 11 +-
.../expected.html | 8 +-
.../expected.html | 8 +-
.../expected.html | 8 +-
.../morphdom/svg-append-new/expected.html | 20 +-
.../morphdom/svg-append/expected.html | 23 +-
.../svg-no-default-namespace/expected.html | 23 +-
.../morphdom/svg-xlink/expected.html | 5 +-
test/autotests/morphdom/svg/expected.html | 9 +-
.../morphdom/swap-keyed/expected.html | 8 +-
.../morphdom/tag-to-text/expected.html | 3 +-
.../tag-with-children-to-text/expected.html | 3 +-
.../morphdom/text-to-tag/expected.html | 5 +-
.../morphdom/text-to-text/expected.html | 3 +-
.../autotests/morphdom/textarea/expected.html | 5 +-
test/autotests/morphdom/todomvc/expected.html | 317 +--
.../autotests/morphdom/todomvc2/expected.html | 21 +-
test/autotests/morphdom/two/expected.html | 5 +-
.../render/component-elId/expected.html | 2 +-
test/autotests/render/component-elId/test.js | 1 +
.../component-file-export-class/expected.html | 2 +-
.../component-file-export-class/test.js | 1 +
.../render/component-getElId/expected.html | 2 +-
.../render/component-getElId/test.js | 1 +
.../component-inline-style-important/test.js | 4 +
.../render/component-label-for/expected.html | 1 +
.../render/component-label-for/template.marko | 3 +
.../render/component-label-for/test.js | 4 +
.../components/foo/components/bar/index.marko | 4 +
.../components/components/foo/index.marko | 10 +
.../components/split/component-browser.js} | 0
.../split-child/component-browser.js | 1 +
.../split/components/split-child/index.marko | 3 +
.../components/components/split/index.marko | 5 +
.../autotests/render/components/expected.html | 1 +
.../render/components/template.marko | 9 +
test/autotests/render/components/test.js | 4 +
.../template.marko | 5 -
.../test.js | 6 -
.../template.marko | 3 -
.../test.js | 6 -
.../include-dynamic/include-target.marko | 2 +-
.../render/include-dynamic/template.marko | 2 +-
.../render/whitespace/template.marko | 2 +-
.../taglib-lookup/forEachTag/expected.json | 2 +-
.../taglib-lookup/getTagsSorted/expected.json | 2 +-
.../attr-class-expression/expected.js | 2 +-
.../attrs-dynamic-object-literal/expected.js | 2 +-
.../vdom-compiler/attrs-dynamic/expected.js | 2 +-
.../vdom-compiler/custom-tag/expected.js | 4 +-
.../vdom-compiler/doctype/expected.js | 4 +-
.../dynamic-body-text/expected.js | 2 +-
.../vdom-compiler/simple/expected.js | 8 +-
.../static-element-nested/expected.js | 8 +-
.../static-element-root/expected.js | 4 +-
.../vdom-create/assignAttributes/index.js | 3 +-
.../expected.html | 6 +-
.../attributes-attr-collection/expected.html | 5 +-
.../attributes-null-false/expected.html | 5 +-
.../attributes-object/expected.html | 5 +-
.../vdom-create/attributes-true/expected.html | 4 +-
.../vdom-create/attributes-true/index.js | 8 +-
.../cloneNode-documentFragment/index.js | 2 +-
test/autotests/vdom-create/cloneNode/index.js | 6 +-
.../vdom-create/comment/expected.html | 5 +-
.../vdom-create/createAttributes/index.js | 10 +-
.../deeply-nested-element-2/expected.html | 5 +-
.../deeply-nested-element/expected.html | 5 +-
.../doc-fragment-deeply-nested-empty/index.js | 4 +-
.../doc-fragment-deeply-nested-text/index.js | 4 +-
.../hasAttributeNS-empty-string/expected.html | 2 +-
.../hasAttributeNS-empty-string/index.js | 2 +-
.../hasAttributeNS-false/expected.html | 2 +-
.../vdom-create/hasAttributeNS-false/index.js | 2 +-
.../hasAttributeNS-null/expected.html | 2 +-
.../vdom-create/hasAttributeNS-null/index.js | 2 +-
.../hasAttributeNS-number-zero/expected.html | 2 +-
.../hasAttributeNS-number-zero/index.js | 2 +-
.../hasAttributeNS-number/expected.html | 2 +-
.../hasAttributeNS-number/index.js | 2 +-
.../hasAttributeNS-true/expected.html | 2 +-
.../vdom-create/hasAttributeNS-true/index.js | 2 +-
.../hasAttributeNS-undefined/expected.html | 2 +-
.../hasAttributeNS-undefined/index.js | 2 +-
test/autotests/vdom-create/id/index.js | 6 +-
.../vdom-create/input-value/index.js | 6 +-
.../isSameNode-createElement/index.js | 17 +-
.../one-child-element/expected.html | 3 +-
.../vdom-create/static-tree-svg/expected.html | 8 +-
.../vdom-create/static-tree/index.js | 6 +-
.../vdom-create/svg-dynamic/expected.html | 4 +-
test/autotests/vdom-create/svg/expected.html | 4 +-
.../textarea-invalid-child/index.js | 4 +-
test/autotests/vdom-create/textarea/index.js | 4 +-
.../textarea/virtualized-expected.html | 2 +-
test/browser-tests-runner/index.js | 19 +-
test/components-compilation-vdom-tests.js | 58 +
test/morphdom-test.js | 137 +-
test/util/domToString.js | 8 +-
test/util/runRenderTest.js | 51 +-
test/vdom-create-test.js | 11 +-
test/vdom-virtualize-test.js | 6 -
460 files changed, 8362 insertions(+), 5388 deletions(-)
create mode 100644 src/components/GlobalComponentsContext.js
create mode 100644 src/components/KeySequence.js
create mode 100644 src/components/endComponent-browser.js
create mode 100644 src/components/endComponent.js
create mode 100644 src/components/taglib/TransformHelper/convertToComponent.js
delete mode 100644 src/components/taglib/TransformHelper/handleComponentBind.js
delete mode 100644 src/components/taglib/TransformHelper/handleComponentKeyAttrs.js
delete mode 100644 src/components/taglib/TransformHelper/handleIncludeNode.js
create mode 100644 src/components/taglib/TransformHelper/handleLegacyBind.js
create mode 100644 src/components/taglib/TransformHelper/handleScopedAttrs.js
create mode 100644 src/components/taglib/helpers/markoKeyAttr.js
delete mode 100644 src/components/taglib/include-tag-browser.js
delete mode 100644 src/components/taglib/include-tag.js
create mode 100644 src/runtime/vdom/VComponent.js
rename test/autotests/{components-browser/repeated-with-label-ref => components-browser-deprecated/for-key-repeated}/index.marko (90%)
create mode 100644 test/autotests/components-browser-deprecated/for-key-repeated/test.js
create mode 100644 test/autotests/components-browser/diffpatch-boundary-inner-component-only/components/inner/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-boundary-inner-component-only/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-boundary-inner-component-only/test.js
create mode 100644 test/autotests/components-browser/diffpatch-component-mismatch-append/components/bar/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-mismatch-append/components/foo/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-mismatch-append/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-mismatch-append/test.js
create mode 100644 test/autotests/components-browser/diffpatch-component-mismatch/components/bar/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-mismatch/components/foo/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-mismatch/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-mismatch/test.js
create mode 100644 test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/bar/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/foo/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-toplevel-surrounded/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-component-toplevel-surrounded/test.js
create mode 100644 test/autotests/components-browser/diffpatch-destroy-child/components/hello/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-destroy-child/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-destroy-child/test.js
create mode 100644 test/autotests/components-browser/diffpatch-insert-el-before-component/components/hello/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-insert-el-before-component/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-insert-el-before-component/test.js
create mode 100644 test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/components/hello/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/test.js
create mode 100644 test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/components/hello/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/test.js
create mode 100644 test/autotests/components-browser/diffpatch-rearrange-keyed-components/components/hello/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-rearrange-keyed-components/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-rearrange-keyed-components/test.js
create mode 100644 test/autotests/components-browser/diffpatch-rearrange-keyed-els/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-rearrange-keyed-els/test.js
create mode 100644 test/autotests/components-browser/diffpatch-remove-all-els/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-remove-all-els/test.js
create mode 100644 test/autotests/components-browser/diffpatch-remove-end-el/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-remove-end-el/test.js
create mode 100644 test/autotests/components-browser/diffpatch-remove-start-el/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-remove-start-el/test.js
create mode 100644 test/autotests/components-browser/diffpatch-simple/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-simple/test.js
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-dynamic/components/hello/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-dynamic/components/world/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-dynamic/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-dynamic/test.js
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/hello/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/world/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/test.js
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-keyed/components/hello/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-keyed/components/world/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-keyed/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-components-keyed/test.js
create mode 100644 test/autotests/components-browser/diffpatch-swap-keyed-el/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-keyed-el/test.js
create mode 100644 test/autotests/components-browser/diffpatch-swap-unkeyed-el/index.marko
create mode 100644 test/autotests/components-browser/diffpatch-swap-unkeyed-el/test.js
create mode 100644 test/autotests/components-browser/include-root/index.marko
create mode 100644 test/autotests/components-browser/include-root/modal.marko
create mode 100644 test/autotests/components-browser/include-root/test.js
create mode 100644 test/autotests/components-browser/label-for-scoped-repeated/index.marko
create mode 100644 test/autotests/components-browser/label-for-scoped-repeated/test.js
delete mode 100644 test/autotests/components-browser/repeated-with-label-ref/test.js
delete mode 100644 test/autotests/components-browser/rerender-same-id/index.marko
rename test/autotests/components-browser/{rerender-same-id => rerender-same-root}/component.js (100%)
create mode 100644 test/autotests/components-browser/rerender-same-root/index.marko
rename test/autotests/components-browser/{rerender-same-id => rerender-same-root}/test.js (70%)
delete mode 100644 test/autotests/components-browser/transclusion-include-from-state/components/app-button/index.marko
delete mode 100644 test/autotests/components-browser/transclusion-include-from-state/index.marko
delete mode 100644 test/autotests/components-browser/transclusion-include-from-state/test.js
delete mode 100644 test/autotests/components-browser/widget-rerender-reuse-stateful/component.js
delete mode 100644 test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/component.js
delete mode 100644 test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/index.marko
delete mode 100644 test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/marko-tag.json
create mode 100644 test/autotests/components-browser/widget-rerender-reuse-stateful/components/hello/index.marko
delete mode 100644 test/autotests/components-browser/widget-stateful-reuse-widgets/component.js
delete mode 100644 test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/component.js
delete mode 100644 test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/index.marko
delete mode 100644 test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/marko-tag.json
delete mode 100644 test/autotests/components-browser/widget-stateful-reuse-widgets/index.marko
delete mode 100644 test/autotests/components-browser/widget-stateful-reuse-widgets/test.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-if-el/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-if-el/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-if-el/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-if/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-if/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/marko-tag.json
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/renderer.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-no-output-tag/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-html-tag/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-html-tag/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-if-el/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-if-el/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-if-root/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-if-root/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-if-root/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-static/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-static/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els-keys-dynamic/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els-keys-dynamic/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els-keys/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els-keys/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-multi-root-html-els/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-no-output-tag-el/components/test-no-output/marko-tag.json
create mode 100644 test/autotests/components-compilation-vdom/boundary-no-output-tag-el/components/test-no-output/renderer.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-no-output-tag-el/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-no-output-tag-el/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-custom-tag-key/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-custom-tag-key/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-custom-tag-key/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-custom-tag-no-key/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-custom-tag-no-key/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-custom-tag-no-key/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-html-el-dynamic-id/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-html-el-dynamic-id/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-html-el-static-id/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-html-el-static-id/index.marko
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-html-el/expected.js
create mode 100644 test/autotests/components-compilation-vdom/boundary-single-root-html-el/index.marko
create mode 100644 test/autotests/components-compilation-vdom/component-with-import-static/expected.js
create mode 100644 test/autotests/components-compilation-vdom/component-with-import-static/index.marko
create mode 100644 test/autotests/components-compilation/auto-key-els-renderBody/component.js
create mode 100644 test/autotests/components-compilation/auto-key-els-renderBody/components/another-component/index.marko
create mode 100644 test/autotests/components-compilation/auto-key-els-renderBody/expected.js
create mode 100644 test/autotests/components-compilation/auto-key-els-renderBody/index.marko
create mode 100644 test/autotests/components-compilation/auto-key-els/component.js
create mode 100644 test/autotests/components-compilation/auto-key-els/expected.js
create mode 100644 test/autotests/components-compilation/auto-key-els/foo.marko
create mode 100644 test/autotests/components-compilation/auto-key-els/index.marko
create mode 100644 test/autotests/components-compilation/boundary-el-if-el/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation/boundary-el-if-el/expected.js
create mode 100644 test/autotests/components-compilation/boundary-el-if-el/index.marko
create mode 100644 test/autotests/components-compilation/boundary-el-if/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation/boundary-el-if/expected.js
create mode 100644 test/autotests/components-compilation/boundary-el-if/index.marko
create mode 100644 test/autotests/components-compilation/boundary-el-no-output-tag/components/test-no-output/marko-tag.json
create mode 100644 test/autotests/components-compilation/boundary-el-no-output-tag/components/test-no-output/renderer.js
create mode 100644 test/autotests/components-compilation/boundary-el-no-output-tag/expected.js
create mode 100644 test/autotests/components-compilation/boundary-el-no-output-tag/index.marko
create mode 100644 test/autotests/components-compilation/boundary-if-root/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation/boundary-if-root/expected.js
create mode 100644 test/autotests/components-compilation/boundary-if-root/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-el-custom-tag-no-key/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-el-custom-tag-no-key/expected.js
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-el-custom-tag-no-key/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-el-id-custom-tag-key/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-el-id-custom-tag-key/expected.js
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-el-id-custom-tag-key/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els-ids-dynamic/expected.js
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els-ids-dynamic/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els-ids-static/expected.js
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els-ids-static/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els-keys-dynamic/expected.js
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els-keys-dynamic/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els-keys/expected.js
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els-keys/index.marko
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els/expected.js
create mode 100644 test/autotests/components-compilation/boundary-multi-root-html-els/index.marko
create mode 100644 test/autotests/components-compilation/boundary-no-output-tag-el/components/test-no-output/marko-tag.json
create mode 100644 test/autotests/components-compilation/boundary-no-output-tag-el/components/test-no-output/renderer.js
create mode 100644 test/autotests/components-compilation/boundary-no-output-tag-el/expected.js
create mode 100644 test/autotests/components-compilation/boundary-no-output-tag-el/index.marko
create mode 100644 test/autotests/components-compilation/boundary-single-root-custom-tag-key/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation/boundary-single-root-custom-tag-key/expected.js
create mode 100644 test/autotests/components-compilation/boundary-single-root-custom-tag-key/index.marko
create mode 100644 test/autotests/components-compilation/boundary-single-root-custom-tag-no-key/components/my-component/index.marko
create mode 100644 test/autotests/components-compilation/boundary-single-root-custom-tag-no-key/expected.js
create mode 100644 test/autotests/components-compilation/boundary-single-root-custom-tag-no-key/index.marko
create mode 100644 test/autotests/components-compilation/boundary-single-root-html-el-dynamic-id/expected.js
create mode 100644 test/autotests/components-compilation/boundary-single-root-html-el-dynamic-id/index.marko
create mode 100644 test/autotests/components-compilation/boundary-single-root-html-el-static-id/expected.js
create mode 100644 test/autotests/components-compilation/boundary-single-root-html-el-static-id/index.marko
create mode 100644 test/autotests/components-compilation/boundary-single-root-html-el/expected.js
create mode 100644 test/autotests/components-compilation/boundary-single-root-html-el/index.marko
create mode 100644 test/autotests/components-compilation/label-for/expected.js
create mode 100644 test/autotests/components-compilation/label-for/template.marko
create mode 100644 test/autotests/components-pages/diff-html-include-layout/layout.marko
create mode 100644 test/autotests/components-pages/diff-html-include-layout/template.marko
create mode 100644 test/autotests/components-pages/diff-html-include-layout/tests.js
create mode 100644 test/autotests/components-pages/getEl-no-rerender/components/hello/index.marko
rename test/autotests/{components-browser/widget-stateful-reuse-widgets => components-pages/getEl-no-rerender}/marko.json (100%)
create mode 100644 test/autotests/components-pages/getEl-no-rerender/template.marko
create mode 100644 test/autotests/components-pages/getEl-no-rerender/tests.js
create mode 100644 test/autotests/components-pages/getEl-split/components/split-component/component-browser.js
create mode 100644 test/autotests/components-pages/getEl-split/components/split-component/index.marko
create mode 100644 test/autotests/components-pages/getEl-split/marko.json
create mode 100644 test/autotests/components-pages/getEl-split/template.marko
create mode 100644 test/autotests/components-pages/getEl-split/tests.js
create mode 100644 test/autotests/render/component-inline-style-important/test.js
create mode 100644 test/autotests/render/component-label-for/expected.html
create mode 100644 test/autotests/render/component-label-for/template.marko
create mode 100644 test/autotests/render/component-label-for/test.js
create mode 100644 test/autotests/render/components/components/foo/components/bar/index.marko
create mode 100644 test/autotests/render/components/components/foo/index.marko
rename test/autotests/render/{error-variable-id-root-node-external-component/template.component.js => components/components/split/component-browser.js} (100%)
create mode 100644 test/autotests/render/components/components/split/components/split-child/component-browser.js
create mode 100644 test/autotests/render/components/components/split/components/split-child/index.marko
create mode 100644 test/autotests/render/components/components/split/index.marko
create mode 100644 test/autotests/render/components/expected.html
create mode 100644 test/autotests/render/components/template.marko
create mode 100644 test/autotests/render/components/test.js
delete mode 100644 test/autotests/render/error-variable-id-root-node-component/template.marko
delete mode 100644 test/autotests/render/error-variable-id-root-node-component/test.js
delete mode 100644 test/autotests/render/error-variable-id-root-node-external-component/template.marko
delete mode 100644 test/autotests/render/error-variable-id-root-node-external-component/test.js
create mode 100644 test/components-compilation-vdom-tests.js
diff --git a/benchmark/size/minify.js b/benchmark/size/minify.js
index 3539b57c8..c1fead3dc 100644
--- a/benchmark/size/minify.js
+++ b/benchmark/size/minify.js
@@ -38,10 +38,10 @@ var minifiers = {
const out = gcc.compile(options);
- // if (out.errors && out.errors.length) {
- // console.error(out.errors);
- // throw new Error(`Minification failed for ${file}`);
- // }
+ if (out.errors && out.errors.length) {
+ console.error(out.errors);
+ throw new Error(`Minification failed for ${file}`);
+ }
return out.compiledCode;
},
uglify: function minifyUglifyJS(src, file) {
@@ -139,4 +139,4 @@ promiseChain.then(() => {
}
console.log('Minification complete.');
-});
\ No newline at end of file
+});
diff --git a/benchmark/size/package.json b/benchmark/size/package.json
index d9cbe6336..36dfff9bb 100644
--- a/benchmark/size/package.json
+++ b/benchmark/size/package.json
@@ -11,7 +11,7 @@
"build-react": "npm run bundle-react --silent && node minify.js react",
"build-inferno": "npm run bundle-inferno --silent && node minify.js inferno",
"bundle": "mkdir -p build/bundles && npm run bundle-marko && npm run bundle-react && npm run bundle-vue && npm run bundle-preact && npm run bundle-inferno",
- "bundle-marko": "node ../../scripts/build && NODE_ENV=production rollup -c marko/rollup.config.js",
+ "bundle-marko": "node ../../scripts/build src && NODE_ENV=production rollup -c marko/rollup.config.js",
"bundle-react": "NODE_ENV=production rollup -c react/rollup.config.js",
"bundle-preact": "NODE_ENV=production rollup -c preact/rollup.config.js",
"bundle-vue": "NODE_ENV=production rollup -c vue/rollup.config.js",
diff --git a/package.json b/package.json
index a72c1d3af..471a4a612 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
},
"scripts": {
"build": "node scripts/build.js",
+ "build-src": "node scripts/build.js src",
"prepublish": "npm run build",
"test": "npm run jshint -s && npm run mocha -s && npm run test-components -s && npm run test-components-deprecated -s",
"test-ci": "npm run test-generate-coverage && npm run build && NODE_ENV=production npm run test",
diff --git a/scripts/build.js b/scripts/build.js
index a16a6ce53..f04c6621e 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -16,34 +16,44 @@ const babelOptions = {
]
};
-buildDir('src', 'dist', {
- babelExclude: [
- '/taglibs/async/client-reorder-runtime.min.js'
- ],
- babelOptions
-});
-var buildConstants = {
- isDebug: false
-};
+var target = process.argv[2];
+
+var shouldBuildSrc = true;
+var shouldBuildTest = true;
+
+if (target === 'src') {
+ shouldBuildTest = false;
+}
+
+if (shouldBuildSrc) {
+ buildDir('src', 'dist', {
+ babelExclude: [
+ '/taglibs/async/client-reorder-runtime.min.js'
+ ],
+ babelOptions
+ });
+}
fs.writeFileSync(
path.join(__dirname, '../dist/build.json'),
JSON.stringify({ isDebug: false }, null, 4),
{ encoding: 'utf8' });
-buildDir('test', 'test-dist', {
- babelExclude: [
- '*expected*.*',
- 'input.js*'
- ],
- exclude: [
- '/generated',
- '*.marko.js',
- '*.skip',
- '*.generated.*',
- '*actual*.*',
- 'actualized-expected.html*'
- ],
- babelOptions
-});
+if (shouldBuildTest) {
+ buildDir('test', 'test-dist', {
+ babelExclude: [
+ '*expected*.*',
+ 'input.js*'
+ ],
+ exclude: [
+ '/generated',
+ '*.marko.js',
+ '*.skip',
+ '*.generated.*',
+ '*actual*.*',
+ 'actualized-expected.html*'
+ ],
+ babelOptions
+ });
+}
diff --git a/src/compiler/Builder.js b/src/compiler/Builder.js
index 47b264909..949ebb3fc 100644
--- a/src/compiler/Builder.js
+++ b/src/compiler/Builder.js
@@ -130,15 +130,22 @@ class Builder {
return new MemberExpression({object, property, computed});
}
+ /**
+ * Generates code that joins all of the arguments using `+` (BinaryExpression)
+ *
+ * @param {Array} args If the args object is not an array then `arguments` is used
+ * @return {Node} The resulting Node
+ */
concat(args) {
var prev;
let operator = '+';
+ args = Array.isArray(args) ? args : Array.prototype.slice.call(arguments, 0);
- for (var i=1; i {
+ this.body.appendChild(node);
+ });
+ }
+
insertBefore(newNode, referenceNode) {
ok(this.body, 'Node does not support child nodes: ' + this);
this.body.insertBefore(newNode, referenceNode);
diff --git a/src/compiler/util/vdom/VDOMOptimizer.js b/src/compiler/util/vdom/VDOMOptimizer.js
index 47fb8fed0..e07da4e3d 100644
--- a/src/compiler/util/vdom/VDOMOptimizer.js
+++ b/src/compiler/util/vdom/VDOMOptimizer.js
@@ -53,19 +53,23 @@ class OptimizerContext {
}
class NodeVDOM extends Node {
- constructor(variableIdentifier) {
+ constructor(variableIdentifier, isComponent) {
super('NodeVDOM');
this.variableIdentifier = variableIdentifier;
+ this.isComponent = isComponent;
}
writeCode(writer) {
var builder = writer.builder;
+ var funcCallArgs = [ this.variableIdentifier ];
+
+ if (this.isComponent) {
+ funcCallArgs.push(builder.identifier('component'));
+ }
let funcCall = builder.functionCall(
builder.identifier('n'),
- [
- this.variableIdentifier
- ]);
+ funcCallArgs);
if (this.isChild) {
writer.write('.');
@@ -89,16 +93,16 @@ function generateNodesForArray(nodes, context, options) {
function generateStaticNode(node) {
if (node.type === 'HtmlElementVDOM') {
node.createElementId = context.helper('createElement');
+
+ node.setConstId(builder.functionCall(optimizerContext.nextConstIdFunc, []));
}/* else {
node.createTextId = context.importModule('marko_createText', 'marko/vdom/createText');
}*/
- node.nextConstId = builder.functionCall(optimizerContext.nextConstIdFunc, []);
-
node.isStaticRoot = true;
let staticNodeId = context.addStaticVar('marko_node' + (optimizerContext.nextNodeId++), node);
- return new NodeVDOM(staticNodeId);
+ return new NodeVDOM(staticNodeId, context.isComponent);
}
function handleStaticAttributes(node) {
@@ -131,6 +135,7 @@ function generateNodesForArray(nodes, context, options) {
finalNodes.push(node);
}
+ node.finalizeProperties(context);
} else {
finalNodes.push(node);
}
@@ -157,4 +162,4 @@ class VDOMOptimizer {
}
}
-module.exports = VDOMOptimizer;
\ No newline at end of file
+module.exports = VDOMOptimizer;
diff --git a/src/components/Component.js b/src/components/Component.js
index 257704198..533511b3e 100644
--- a/src/components/Component.js
+++ b/src/components/Component.js
@@ -7,53 +7,27 @@ var getComponentsContext = require('./ComponentsContext').___getComponentsContex
var componentsUtil = require('./util');
var componentLookup = componentsUtil.___componentLookup;
var emitLifecycleEvent = componentsUtil.___emitLifecycleEvent;
-var destroyComponentForEl = componentsUtil.___destroyComponentForEl;
-var destroyElRecursive = componentsUtil.___destroyElRecursive;
-var getElementById = componentsUtil.___getElementById;
+var destroyNodeRecursive = componentsUtil.___destroyNodeRecursive;
var EventEmitter = require('events-light');
var RenderResult = require('../runtime/RenderResult');
var SubscriptionTracker = require('listener-tracker');
var inherit = require('raptor-util/inherit');
var updateManager = require('./update-manager');
var morphdom = require('../morphdom');
-var eventDelegation = require('./event-delegation');
var slice = Array.prototype.slice;
-var MORPHDOM_SKIP = true;
-
var COMPONENT_SUBSCRIBE_TO_OPTIONS;
var NON_COMPONENT_SUBSCRIBE_TO_OPTIONS = {
addDestroyListener: false
};
-function outNoop() { /* jshint -W040 */ return this; }
-
var emit = EventEmitter.prototype.emit;
function removeListener(removeEventListenerHandle) {
removeEventListenerHandle();
}
-function checkCompatibleComponent(globalComponentsContext, el) {
- var component = el._w;
- while(component) {
- var id = component.id;
- var newComponentDef = globalComponentsContext.___componentsById[id];
- if (newComponentDef && component.___type == newComponentDef.___component.___type) {
- break;
- }
-
- var rootFor = component.___rootFor;
- if (rootFor) {
- component = rootFor;
- } else {
- component.___destroyShallow();
- break;
- }
- }
-}
-
function handleCustomEventWithMethodListener(component, targetMethodName, args, extraArgs) {
// Remove the "eventType" argument
args.push(component);
@@ -72,16 +46,12 @@ function handleCustomEventWithMethodListener(component, targetMethodName, args,
targetMethod.apply(targetComponent, args);
}
-function getElIdHelper(component, componentElId, index) {
- var id = component.id;
+function resolveKeyHelper(key, index) {
+ return index ? key + '_' + index : key;
+}
- var elId = componentElId != null ? id + '-' + componentElId : id;
-
- if (index != null) {
- elId += '[' + index + ']';
- }
-
- return elId;
+function resolveComponentIdHelper(component, key, index) {
+ return component.id + '-' + resolveKeyHelper(key, index);
}
/**
@@ -158,46 +128,10 @@ function checkInputChanged(existingComponent, oldInput, newInput) {
return false;
}
-function onNodeDiscarded(node) {
- if (node.nodeType === 1) {
- destroyComponentForEl(node);
- }
-}
-
-function onBeforeNodeDiscarded(node) {
- return eventDelegation.___handleNodeDetach(node);
-}
-
-function onBeforeElUpdated(fromEl, key, globalComponentsContext) {
- if (key) {
- var preserved = globalComponentsContext.___preserved[key];
-
- if (preserved === true) {
- // Don't morph elements that are associated with components that are being
- // reused or elements that are being preserved. For components being reused,
- // the morphing will take place when the reused component updates.
- return MORPHDOM_SKIP;
- } else {
- // We may need to destroy a Component associated with the current element
- // if a new UI component was rendered to the same element and the types
- // do not match
- checkCompatibleComponent(globalComponentsContext, fromEl);
- }
- }
-}
-
-function onBeforeElChildrenUpdated(el, key, globalComponentsContext) {
- if (key) {
- var preserved = globalComponentsContext.___preservedBodies[key];
- if (preserved === true) {
- // Don't morph the children since they are preserved
- return MORPHDOM_SKIP;
- }
- }
-}
-
-function onNodeAdded(node, globalComponentsContext) {
- eventDelegation.___handleNodeAttach(node, globalComponentsContext.___out);
+function getNodes(component) {
+ var nodes = [];
+ component.___forEachNode(nodes.push.bind(nodes));
+ return nodes;
}
var componentProto;
@@ -210,9 +144,9 @@ var componentProto;
function Component(id) {
EventEmitter.call(this);
this.id = id;
- this.el = null;
this.___state = null;
- this.___roots = null;
+ this.___startNode = null;
+ this.___endNode = null;
this.___subscriptions = null;
this.___domEventListenerHandles = null;
this.___bubblingDomEvents = null; // Used to keep track of bubbling DOM events for components rendered on the server
@@ -229,6 +163,8 @@ function Component(id) {
this.___settingInput = false;
this.___document = undefined;
+
+ this.___keyedElements = {};
}
Component.prototype = componentProto = {
@@ -264,63 +200,57 @@ Component.prototype = componentProto = {
return emit.apply(this, arguments);
}
},
- getElId: function (componentElId, index) {
- return getElIdHelper(this, componentElId, index);
+ getElId: function (key, index) {
+ return resolveComponentIdHelper(this, key, index);
},
- getEl: function (componentElId, index) {
- var doc = this.___document;
-
- if (componentElId != null) {
- return getElementById(doc, getElIdHelper(this, componentElId, index));
+ getEl: function (key, index) {
+ if (key) {
+ return this.___keyedElements[resolveKeyHelper(key, index)];
} else {
- return this.el || getElementById(doc, getElIdHelper(this));
+ return this.___startNode;
}
},
- getEls: function(id) {
+ getEls: function(key) {
+ key = key + '[]';
+
var els = [];
var i = 0;
var el;
- while((el = this.getEl(id, i))) {
+ while((el = this.getEl(key, i))) {
els.push(el);
i++;
}
return els;
},
- getComponent: function(id, index) {
- return componentLookup[getElIdHelper(this, id, index)];
+ getComponent: function(key, index) {
+ return componentLookup[resolveComponentIdHelper(this, key, index)];
},
- getComponents: function(id) {
+ getComponents: function(key) {
+ key = key + '[]';
+
var components = [];
var i = 0;
var component;
- while((component = componentLookup[getElIdHelper(this, id, i)])) {
+ while((component = componentLookup[resolveComponentIdHelper(this, key, i)])) {
components.push(component);
i++;
}
return components;
},
- destroy: function() {
+ destroy: function(onBeforeNodeDiscarded) {
if (this.___destroyed) {
return;
}
- var els = this.els;
+ var nodes = getNodes(this);
this.___destroyShallow();
- var rootComponents = this.___rootComponents;
- if (rootComponents) {
- rootComponents.forEach(function(rootComponent) {
- rootComponent.___destroy();
- });
- }
+ nodes.forEach(function(node) {
+ destroyNodeRecursive(node);
- els.forEach(function(el) {
- destroyElRecursive(el);
-
- var parentNode = el.parentNode;
- if (parentNode) {
- parentNode.removeChild(el);
+ if (!onBeforeNodeDiscarded || onBeforeNodeDiscarded(node) != false) {
+ node.parentNode.removeChild(node);
}
});
},
@@ -333,7 +263,9 @@ Component.prototype = componentProto = {
emitLifecycleEvent(this, 'destroy');
this.___destroyed = true;
- this.el = null;
+ this.___startNode.___markoComponent = undefined;
+
+ this.___startNode = this.___endNode = null;
// Unsubscribe from all DOM events
this.___removeDOMEventListeners();
@@ -452,6 +384,7 @@ Component.prototype = componentProto = {
___queueUpdate: function() {
if (!this.___updateQueued) {
+ this.___updateQueued = true;
updateManager.___queueComponentUpdate(this);
}
},
@@ -512,7 +445,10 @@ Component.prototype = componentProto = {
if (!renderer) {
throw TypeError();
}
- var fromEls = self.___getRootEls({});
+
+ var startNode = this.___startNode;
+ var endNodeNextSibling = this.___endNode.nextSibling;
+
var doc = self.___document;
var input = this.___renderInput || this.___input;
var globalData = this.___global;
@@ -523,18 +459,6 @@ Component.prototype = componentProto = {
out.sync();
out.___document = self.___document;
- if (isRerenderInBrowser === true) {
- out.e =
- out.be =
- out.ee =
- out.t =
- out.h =
- out.w =
- out.write =
- out.html =
- outNoop;
- }
-
var componentsContext = getComponentsContext(out);
var globalComponentsContext = componentsContext.___globalContext;
globalComponentsContext.___rerenderComponent = self;
@@ -544,68 +468,40 @@ Component.prototype = componentProto = {
var result = new RenderResult(out);
- if (isRerenderInBrowser !== true) {
- var targetNode = out.___getOutput();
+ var targetNode = out.___getOutput();
- var fromEl;
-
- var targetEl = targetNode.___firstChild;
- while (targetEl) {
- var nodeName = targetEl.___nodeName;
-
- if (nodeName === 'HTML') {
- fromEl = document.documentElement;
- } else if (nodeName === 'BODY') {
- fromEl = document.body;
- } else if (nodeName === 'HEAD') {
- fromEl = document.head;
- } else {
- fromEl = fromEls[targetEl.id];
- }
-
- if (fromEl) {
- morphdom(
- fromEl,
- targetEl,
- globalComponentsContext,
- onNodeAdded,
- onBeforeElUpdated,
- onBeforeNodeDiscarded,
- onNodeDiscarded,
- onBeforeElChildrenUpdated);
- }
-
- targetEl = targetEl.___nextSibling;
- }
- }
+ morphdom(
+ startNode.parentNode,
+ startNode,
+ endNodeNextSibling,
+ targetNode,
+ doc,
+ componentsContext);
result.afterInsert(doc);
-
- out.emit('___componentsInitialized');
});
this.___reset();
},
- ___getRootEls: function(rootEls) {
- var i, len;
+ ___detach: function() {
+ var fragment = this.___document.createDocumentFragment();
+ this.___forEachNode(fragment.appendChild.bind(fragment));
+ return fragment;
+ },
- var componentEls = this.els;
+ ___forEachNode: function(callback) {
+ var currentNode = this.___startNode;
+ var endNode = this.___endNode;
- for (i=0, len=componentEls.length; i 1) {
- var fragment = component.___document.createDocumentFragment();
- els.forEach(function(el) {
- fragment.appendChild(el);
- });
- return fragment;
- } else {
- return els[0];
- }
+ return component.___detach();
},
function afterInsert(component) {
return component;
diff --git a/src/components/ComponentDef.js b/src/components/ComponentDef.js
index e1ef621ee..b005d376b 100644
--- a/src/components/ComponentDef.js
+++ b/src/components/ComponentDef.js
@@ -3,48 +3,50 @@ var repeatedRegExp = /\[\]$/;
var componentUtil = require('./util');
var attachBubblingEvent = componentUtil.___attachBubblingEvent;
var extend = require('raptor-util/extend');
+var KeySequence = require('./KeySequence');
+
+/*
+var FLAG_WILL_RERENDER_IN_BROWSER = 1;
+var FLAG_HAS_BODY_EL = 2;
+var FLAG_HAS_HEAD_EL = 4;
+*/
/**
* A ComponentDef is used to hold the metadata collected at runtime for
* a single component and this information is used to instantiate the component
* later (after the rendered HTML has been added to the DOM)
*/
-function ComponentDef(component, componentId, globalComponentsContext, componentStack, componentStackLen) {
+function ComponentDef(component, componentId, globalComponentsContext) {
this.___globalComponentsContext = globalComponentsContext; // The AsyncWriter that this component is associated with
- this.___componentStack = componentStack;
- this.___componentStackLen = componentStackLen;
this.___component = component;
this.id = componentId;
- this.___roots = null; // IDs of root elements if there are multiple root elements
- this.___children = null; // An array of nested ComponentDef instances
this.___domEvents = undefined; // An array of DOM events that need to be added (in sets of three)
this.___isExisting = false;
- this.___willRerenderInBrowser = false;
+ this.___renderBoundary = false;
+ this.___flags = 0;
this.___nextIdIndex = 0; // The unique integer to use for the next scoped ID
+
+ this.___keySequence = null;
+
+ this.___preservedDOMNodes = null;
}
ComponentDef.prototype = {
- ___end: function() {
- this.___componentStack.length = this.___componentStackLen;
+
+ ___nextKey: function(key) {
+ var keySequence = this.___keySequence || (this.___keySequence = new KeySequence());
+ return keySequence.___nextKey(key);
},
- /**
- * Register a nested component for this component. We maintain a tree of components
- * so that we can instantiate nested components before their parents.
- */
- ___addChild: function (componentDef) {
- var children = this.___children;
-
- if (children) {
- children.push(componentDef);
- } else {
- this.___children = [componentDef];
- }
+ ___preserveDOMNode: function(key, bodyOnly) {
+ var lookup = this.___preservedDOMNodes || (this.___preservedDOMNodes = {});
+ lookup[key] = bodyOnly ? 2 : 1;
},
+
/**
* This helper method generates a unique and fully qualified DOM element ID
* that is unique within the scope of the current component. This method prefixes
@@ -88,15 +90,15 @@ ComponentDef.prototype = {
* Returns the next auto generated unique ID for a nested DOM element or nested DOM component
*/
___nextComponentId: function() {
- var id = this.id;
-
- return id === null ?
- this.___globalComponentsContext.___nextComponentId(this.___out) :
- id + '-c' + (this.___nextIdIndex++);
+ return this.id + '-c' + (this.___nextIdIndex++);
},
d: function(handlerMethodName, extraArgs) {
return attachBubblingEvent(this, handlerMethodName, extraArgs);
+ },
+
+ get ___type() {
+ return this.___component.___type;
}
};
@@ -146,10 +148,11 @@ ComponentDef.___deserialize = function(o, types, globals, registry) {
component.___global = globals;
return {
+ id: id,
___component: component,
- ___roots: extra.r,
+ ___boundary: extra.r,
___domEvents: extra.d,
- ___willRerenderInBrowser: extra._ === 1
+ ___flags: extra.f || 0
};
};
diff --git a/src/components/ComponentsContext.js b/src/components/ComponentsContext.js
index abf7545eb..32a6f4fd7 100644
--- a/src/components/ComponentsContext.js
+++ b/src/components/ComponentsContext.js
@@ -1,117 +1,47 @@
'use strict';
+var GlobalComponentsContext = require('./GlobalComponentsContext');
-var ComponentDef = require('./ComponentDef');
-var componentsUtil = require('./util');
+function ComponentsContext(out, parentComponentsContext) {
+ var globalComponentsContext;
+ var componentDef;
+ var components;
-var beginComponent = require('./beginComponent');
+ if (parentComponentsContext) {
+ components = parentComponentsContext.___components;
+ globalComponentsContext = parentComponentsContext.___globalContext;
+ componentDef = parentComponentsContext.___componentDef;
+ } else {
+ globalComponentsContext = out.global.___components;
+ if (globalComponentsContext === undefined) {
+ out.global.___components = globalComponentsContext = new GlobalComponentsContext(out);
+ }
+ components = [];
+ }
-var EMPTY_OBJECT = {};
-
-function GlobalComponentsContext(out) {
- this.___roots = [];
- this.___preserved = EMPTY_OBJECT;
- this.___preservedBodies = EMPTY_OBJECT;
- this.___componentsById = {};
+ this.___globalContext = globalComponentsContext;
+ this.___components = components;
this.___out = out;
- this.___rerenderComponent = undefined;
- this.___nextIdLookup = null;
- this.___nextComponentId = componentsUtil.___nextComponentIdProvider(out);
+ this.___componentDef = componentDef;
}
-GlobalComponentsContext.prototype = {
+ComponentsContext.prototype = {
___initComponents: function(doc) {
- var topLevelComponentDefs = null;
+ var componentDefs = this.___components;
- this.___roots.forEach(function(root) {
- var children = root.___children;
- if (children) {
- // NOTE: ComponentsContext.___initClientRendered is provided by
- // index-browser.js to avoid a circular dependency
- ComponentsContext.___initClientRendered(children, doc);
- if (topLevelComponentDefs === null) {
- topLevelComponentDefs = children;
- } else {
- topLevelComponentDefs = topLevelComponentDefs.concat(children);
- }
- }
- });
+ ComponentsContext.___initClientRendered(componentDefs, doc);
- this.___roots = null;
+ this.___out.emit('___componentsInitialized');
// Reset things stored in global since global is retained for
// future renders
this.___out.global.___components = undefined;
- return topLevelComponentDefs;
+ return componentDefs;
},
- ___preserveDOMNode: function(elId, bodyOnly) {
- var preserved = bodyOnly === true ? this.___preservedBodies : this.___preserved;
- if (preserved === EMPTY_OBJECT) {
- if (bodyOnly === true) {
- preserved = this.___preservedBodies = {};
- } else {
- preserved = this.___preserved = {};
- }
- }
- preserved[elId] = true;
- },
- ___nextRepeatedId: function(parentId, id) {
- var nextIdLookup = this.___nextIdLookup || (this.___nextIdLookup = {});
-
- var indexLookupKey = parentId + '-' + id;
- var currentIndex = nextIdLookup[indexLookupKey];
- if (currentIndex == null) {
- currentIndex = nextIdLookup[indexLookupKey] = 0;
- } else {
- currentIndex = ++nextIdLookup[indexLookupKey];
- }
-
- return indexLookupKey.slice(0, -2) + '[' + currentIndex + ']';
- }
-};
-
-function ComponentsContext(out, parentComponentsContext, shouldAddGlobalRoot) {
- var root;
-
- var globalComponentsContext;
-
- if (parentComponentsContext === undefined) {
- globalComponentsContext = out.global.___components;
- if (globalComponentsContext === undefined) {
- out.global.___components = globalComponentsContext = new GlobalComponentsContext(out);
- }
-
- root = new ComponentDef(null, null, globalComponentsContext);
-
- if (shouldAddGlobalRoot !== false) {
- globalComponentsContext.___roots.push(root);
- }
- } else {
- globalComponentsContext = parentComponentsContext.___globalContext;
- var parentComponentStack = parentComponentsContext.___componentStack;
- root = parentComponentStack[parentComponentStack.length-1];
- }
-
- this.___globalContext = globalComponentsContext;
- this.___out = out;
- this.___componentStack = [root];
-}
-
-ComponentsContext.prototype = {
- ___createNestedComponentsContext: function(nestedOut) {
- return new ComponentsContext(nestedOut, this);
- },
- ___beginComponent: beginComponent,
-
- ___nextComponentId: function() {
- var componentStack = this.___componentStack;
- var parentComponentDef = componentStack[componentStack.length - 1];
- return parentComponentDef.___nextComponentId();
- }
};
function getComponentsContext(out) {
- return out.data.___components || (out.data.___components = new ComponentsContext(out));
+ return out.___components || (out.___components = new ComponentsContext(out));
}
module.exports = exports = ComponentsContext;
diff --git a/src/components/GlobalComponentsContext.js b/src/components/GlobalComponentsContext.js
new file mode 100644
index 000000000..1e2188372
--- /dev/null
+++ b/src/components/GlobalComponentsContext.js
@@ -0,0 +1,18 @@
+var nextComponentIdProvider = require('./util').___nextComponentIdProvider;
+var KeySequence = require('./KeySequence');
+
+function GlobalComponentsContext(out) {
+ this.___preservedEls = {};
+ this.___preservedElBodies = {};
+ this.___renderedComponentsById = {};
+ this.___rerenderComponent = undefined;
+ this.___nextComponentId = nextComponentIdProvider(out);
+}
+
+GlobalComponentsContext.prototype = {
+ ___createKeySequence: function() {
+ return new KeySequence();
+ }
+};
+
+module.exports = GlobalComponentsContext;
diff --git a/src/components/KeySequence.js b/src/components/KeySequence.js
new file mode 100644
index 000000000..ed73040da
--- /dev/null
+++ b/src/components/KeySequence.js
@@ -0,0 +1,27 @@
+ function KeySequence() {
+ this.___lookup = {};
+}
+
+KeySequence.prototype = {
+ ___nextKey: function(key) {
+ // var len = key.length;
+ // var lastChar = key[len-1];
+ // if (lastChar === ']') {
+ // key = key.substring(0, len-2);
+ // }
+ var lookup = this.___lookup;
+
+ var currentIndex = lookup[key]++;
+ if (!currentIndex) {
+ lookup[key] = 1;
+ currentIndex = 0;
+ return key;
+ } else {
+ return key + '_' + currentIndex;
+ }
+
+
+ }
+};
+
+module.exports = KeySequence;
diff --git a/src/components/attach-detach.js b/src/components/attach-detach.js
index 21309a2b0..8d82e0eff 100644
--- a/src/components/attach-detach.js
+++ b/src/components/attach-detach.js
@@ -2,14 +2,15 @@ var eventDelegation = require('./event-delegation');
var delegateEvent = eventDelegation.___delegateEvent;
var getEventFromEl = eventDelegation.___getEventFromEl;
-var componentsUtil = require('./util');
-var destroyElRecursive = componentsUtil.___destroyElRecursive;
-var destroyComponentForEl = componentsUtil.___destroyComponentForEl;
+// var componentsUtil = require('./util');
+// var destroyNodeRecursive = componentsUtil.___destroyNodeRecursive;
+// var destroyComponentForNode = componentsUtil.___destroyComponentForNode;
-function handleNodeAttach(node, out) {
+function handleNodeAttach(node, componentsContext) {
if (node.nodeType === 1) {
var target = getEventFromEl(node, 'onattach');
if (target) {
+ var out = componentsContext.___out;
var data = out.data;
var attachTargets = data.___attachTargets;
@@ -39,8 +40,6 @@ function handleNodeDetach(node) {
delegateEvent(node, target, {
preventDefault: function() {
allowDetach = false;
- destroyComponentForEl(node);
- destroyElRecursive(node);
},
detach: function() {
var parentNode = node.parentNode;
diff --git a/src/components/beginComponent-browser.js b/src/components/beginComponent-browser.js
index c4ba643a9..b4e61922f 100644
--- a/src/components/beginComponent-browser.js
+++ b/src/components/beginComponent-browser.js
@@ -1,17 +1,14 @@
var ComponentDef = require('./ComponentDef');
-module.exports = function(component, isSplitComponent) {
- var componentStack = this.___componentStack;
- var origLength = componentStack.length;
- var parentComponentDef = componentStack[origLength - 1];
-
+module.exports = function beginComponent(componentsContext, component, isSplitComponen, parentComponentDeft) {
var componentId = component.id;
- var componentDef = new ComponentDef(component, componentId, this.___globalContext, componentStack, origLength);
- parentComponentDef.___addChild(componentDef);
- this.___globalContext.___componentsById[componentId] = componentDef;
-
- componentStack.push(componentDef);
+ var globalContext = componentsContext.___globalContext;
+ var componentDef = componentsContext.___componentDef = new ComponentDef(component, componentId, globalContext);
+ globalContext.___renderedComponentsById[componentId] = true;
+ componentsContext.___components.push(componentDef);
+ var out = componentsContext.___out;
+ out.bc(component);
return componentDef;
};
diff --git a/src/components/beginComponent.js b/src/components/beginComponent.js
index ee9f67b53..dba1c8766 100644
--- a/src/components/beginComponent.js
+++ b/src/components/beginComponent.js
@@ -3,8 +3,13 @@
const ComponentDef = require('./ComponentDef');
const hasRenderBodyKey = Symbol.for("hasRenderBody");
+var FLAG_WILL_RERENDER_IN_BROWSER = 1;
+// var FLAG_HAS_BODY_EL = 2;
+// var FLAG_HAS_HEAD_EL = 4;
+
function isInputSerializable(component) {
var input = component.___input;
+
if (!input) {
return true;
}
@@ -16,29 +21,33 @@ function isInputSerializable(component) {
}
}
-module.exports = function beginComponent(component, isSplitComponent) {
- var componentStack = this.___componentStack;
- var origLength = componentStack.length;
- var parentComponentDef = componentStack[origLength - 1];
+module.exports = function beginComponent(componentsContext, component, isSplitComponent, parentComponentDef) {
+ var globalContext = componentsContext.___globalContext;
var componentId = component.id;
- var componentDef = new ComponentDef(component, componentId, this.___globalContext, componentStack, origLength);
+ var componentDef = componentsContext.___componentDef = new ComponentDef(component, componentId, globalContext);
// On the server
- if (parentComponentDef.___willRerenderInBrowser === true) {
- componentDef.___willRerenderInBrowser = true;
- } else {
- parentComponentDef.___addChild(componentDef);
- if (isSplitComponent === false &&
- this.___out.global.noBrowserRerender !== true &&
- isInputSerializable(component)) {
-
- componentDef.___willRerenderInBrowser = true;
- }
+ if (parentComponentDef && (parentComponentDef.___flags & FLAG_WILL_RERENDER_IN_BROWSER)) {
+ componentDef.___flags |= FLAG_WILL_RERENDER_IN_BROWSER;
+ return componentDef;
}
- componentStack.push(componentDef);
+ componentsContext.___components.push(componentDef);
+
+ let out = componentsContext.___out;
+
+ componentDef.___renderBoundary = true;
+
+ if (isSplitComponent === false &&
+ out.global.noBrowserRerender !== true &&
+ isInputSerializable(component)) {
+ componentDef.___flags |= FLAG_WILL_RERENDER_IN_BROWSER;
+ out.w('');
+ } else {
+ out.w('');
+ }
return componentDef;
};
diff --git a/src/components/endComponent-browser.js b/src/components/endComponent-browser.js
new file mode 100644
index 000000000..eae21057a
--- /dev/null
+++ b/src/components/endComponent-browser.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = function endComponent(out) {
+ out.ee(); // endElement() (also works for VComponent nodes pushed on to the stack)
+};
diff --git a/src/components/endComponent.js b/src/components/endComponent.js
new file mode 100644
index 000000000..34d224b94
--- /dev/null
+++ b/src/components/endComponent.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = function endComponent(out, componentDef) {
+ if (componentDef.___renderBoundary) {
+ out.w('');
+ }
+};
diff --git a/src/components/index.js b/src/components/index.js
index 6da4a563d..dacf6d7d5 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -3,12 +3,34 @@
var warp10 = require('warp10');
var escapeEndingScriptTagRegExp = /<\//g;
-function flattenHelper(components, flattened, typesArray, typesLookup) {
- for (var i = 0, len = components.length; i < len; i++) {
+
+
+function getRenderedComponents(out, shouldIncludeAll) {
+ var componentsContext = out.___components;
+ if (componentsContext === null) {
+ return;
+ }
+
+ // console.log('componentsContext:', componentsContext);
+
+ var components = componentsContext.___components;
+ if (components.length === 0) {
+ return;
+ }
+
+ // console.log('components:', components.map((componentDef) => {
+ // return { id: componentDef.id, type: componentDef.type};
+ // }));
+
+ var componentsFinal = [];
+ var typesLookup = {};
+ var typesArray = [];
+
+ for (var i = components.length - 1; i >= 0; i--) {
var componentDef = components[i];
var id = componentDef.id;
var component = componentDef.___component;
- var rerenderInBrowser = componentDef.___willRerenderInBrowser;
+ var flags = componentDef.___flags;
var state = component.state;
var input = component.input;
var typeName = component.typeName;
@@ -36,14 +58,6 @@ function flattenHelper(components, flattened, typesArray, typesLookup) {
typesLookup[typeName] = typeIndex;
}
- var children = componentDef.___children;
-
- if (children !== null) {
- // Depth-first search (children should be initialized before parent)
- flattenHelper(children, flattened, typesArray, typesLookup);
- componentDef.___children = null;
- }
-
var hasProps = false;
let componentKeys = Object.keys(component);
@@ -81,71 +95,23 @@ function flattenHelper(components, flattened, typesArray, typesLookup) {
b: bubblingDomEvents,
d: componentDef.___domEvents,
e: customEvents,
+ f: flags ? flags : undefined,
p: customEvents && scope, // Only serialize scope if we need to attach custom events
- r: componentDef.___roots,
+ r: componentDef.___boundary,
s: state,
u: undefinedPropNames,
- w: hasProps ? component : undefined,
- _: rerenderInBrowser ? 1 : undefined
+ w: hasProps ? component : undefined
};
- flattened.push([
+ componentsFinal.push([
id, // 0 = id
typeIndex, // 1 = type
input, // 2 = input
extra // 3
]);
}
-}
-function getRenderedComponents(out, shouldIncludeAll) {
- var componentDefs;
- var globalComponentsContext;
- var outGlobal = out.global;
-
- if (shouldIncludeAll === true) {
- globalComponentsContext = outGlobal.___components;
-
- if (globalComponentsContext === undefined) {
- return undefined;
- }
- } else {
- let componentsContext = out.data.___components;
- if (componentsContext === undefined) {
- return undefined;
- }
- let rootComponentDef = componentsContext.___componentStack[0];
- componentDefs = rootComponentDef.___children;
-
- if (componentDefs === null) {
- return undefined;
- }
-
- rootComponentDef.___children = null;
- }
-
- var flattened = [];
- var typesLookup = {};
- var typesArray = [];
-
- if (shouldIncludeAll === true) {
- let roots = globalComponentsContext.___roots;
- for (let i=0, len=roots.length; i=0; i--) {
var componentDef = componentDefs[i];
-
- if (componentDef.___children) {
- initClientRendered(componentDef.___children, doc);
- }
-
initComponent(
componentDef,
doc);
@@ -172,9 +178,12 @@ function initServerRendered(renderedComponents, doc) {
return;
}
+
+ doc = doc || defaultDocument;
+
// Ensure that event handlers to handle delegating events are
// always attached before initializing any components
- eventDelegation.___init(doc || defaultDocument);
+ eventDelegation.___init(doc);
renderedComponents = warp10Finalize(renderedComponents);
@@ -188,6 +197,65 @@ function initServerRendered(renderedComponents, doc) {
componentDefs.forEach(function(componentDef) {
componentDef = ComponentDef.___deserialize(componentDef, typesArray, serverRenderedGlobals, registry);
+ var componentId = componentDef.id;
+ var component = componentDef.___component;
+
+ var startNode;
+ var endNode;
+ var flags = componentDef.___flags;
+ if ((flags & 6) === 6) {
+ startNode = document.head;
+ endNode = document.body;
+ } else if (flags & FLAG_HAS_BODY_EL) {
+ startNode = endNode = document.body;
+ } else if (flags & FLAG_HAS_HEAD_EL) {
+ startNode = endNode = document.head;
+ } else {
+ var startNodeComment = serverComponentStartNodes[componentId];
+ if (!startNodeComment) {
+ indexServerComponentBoundaries(doc);
+ startNodeComment = serverComponentStartNodes[componentId];
+ }
+ var endNodeComment = serverComponentEndNodes[componentId];
+
+ startNode = startNodeComment.nextSibling;
+
+ if (startNode === endNodeComment) {
+ // Component has no output nodes so just mount to the start comment node
+ // and we will remove the end comment node
+ startNode = endNode = startNodeComment;
+ } else {
+ startNodeComment.parentNode.removeChild(startNodeComment);
+
+ if (startNode.parentNode === document) {
+ endNode = startNode = document.documentElement;
+ } else {
+ // Remove the start and end comment nodes and use the inner nodes
+ // as the boundary
+ endNode = endNodeComment.previousSibling;
+ }
+ }
+
+ if (endNodeComment) {
+ endNodeComment.parentNode.removeChild(endNodeComment);
+ }
+ }
+
+ component.___keyedElements = keyedElementsByComponentId[componentId] || {};
+ component.___startNode = startNode;
+ component.___endNode = endNode;
+
+ delete keyedElementsByComponentId[componentId];
+
+ // Mark the start node so that we know we need to skip past this
+ // node when matching up children
+ startNode.___startNode = true;
+
+ // Mark the end node so that when we attempt to find boundaries
+ // for nested UI components we don't accidentally go outside the boundary
+ // of the parent component
+ endNode.___endNode = true;
+
initComponent(componentDef, doc || defaultDocument);
});
}
diff --git a/src/components/jquery.js b/src/components/jquery.js
index 33495b3b2..88d98126e 100644
--- a/src/components/jquery.js
+++ b/src/components/jquery.js
@@ -23,11 +23,11 @@ exports.patchComponent = function(jQuery) {
var match = idRegExp.exec(arg);
//Reset the search to 0 so the next call to exec will start from the beginning for the new string
if (match != null) {
- var componentElId = match[1];
+ var key = match[1];
if (match[2] == null) {
- return jQuery(self.getEl(componentElId));
+ return jQuery(self.getEl(key));
} else {
- return jQuery('#' + self.getElId(componentElId) + match[2]);
+ return jQuery(match[2].trim(), self.getEl(key));
}
} else {
var rootEl = self.getEl();
diff --git a/src/components/legacy/renderer-legacy.js b/src/components/legacy/renderer-legacy.js
index 62ce853ff..ae6202672 100644
--- a/src/components/legacy/renderer-legacy.js
+++ b/src/components/legacy/renderer-legacy.js
@@ -4,8 +4,9 @@ var componentLookup = componentsUtil.___componentLookup;
var registry = require('../registry');
var modernRenderer = require('../renderer');
var resolveComponentKey = modernRenderer.___resolveComponentKey;
-var preserveComponentEls = modernRenderer.___preserveComponentEls;
var handleBeginAsync = modernRenderer.___handleBeginAsync;
+var beginComponent = require('../beginComponent');
+var endComponent = require('../endComponent');
var WIDGETS_BEGIN_ASYNC_ADDED_KEY = '$wa';
@@ -13,6 +14,7 @@ function createRendererFunc(templateRenderFunc, componentProps) {
var typeName = componentProps.type;
var roots = componentProps.roots;
var assignedId = componentProps.id;
+ var isSplit = componentProps.split === true;
return function renderer(input, out, renderingLogic) {
var outGlobal = out.global;
@@ -44,39 +46,46 @@ function createRendererFunc(templateRenderFunc, componentProps) {
var globalComponentsContext = componentsContext.___globalContext;
var component = globalComponentsContext.___rerenderComponent;
- var fakeComponent;
+
var isRerender = component !== undefined;
var id = assignedId;
var isExisting;
var customEvents;
var scope;
+ var parentComponentDef;
if (component) {
id = component.id;
isExisting = true;
globalComponentsContext.___rerenderComponent = null;
} else {
+ parentComponentDef = componentsContext.___componentDef;
var componentArgs = out.___componentArgs;
if (componentArgs) {
+ scope = parentComponentDef.id;
out.___componentArgs = null;
- scope = componentArgs[0];
- if (scope) {
- scope = scope.id;
+ var key;
+
+ if (typeof componentArgs === 'string') {
+ key = componentArgs;
+ } else {
+ key = componentArgs[0];
+ customEvents = componentArgs[1];
}
- var ref = componentArgs[1];
- if (ref != null) {
- ref = ref.toString();
+ if (key != null) {
+ key = key.toString();
}
- id = id || resolveComponentKey(globalComponentsContext, ref, scope);
- customEvents = componentArgs[2];
+ id = id || resolveComponentKey(globalComponentsContext, key, parentComponentDef);
+ } else if (parentComponentDef) {
+ id = parentComponentDef.___nextComponentId();
+ } else {
+ id = globalComponentsContext.___nextComponentId();
}
}
- id = id || componentsContext.___nextComponentId();
-
if (registry.___isServer && typeName) {
component = { id:id, typeName:typeName };
} else {
@@ -101,6 +110,10 @@ function createRendererFunc(templateRenderFunc, componentProps) {
}
}
+ if (component) {
+ component.___updateQueued = true;
+ }
+
if (input) {
if (getWidgetConfig) {
// If getWidgetConfig() was implemented then use that to
@@ -151,14 +164,23 @@ function createRendererFunc(templateRenderFunc, componentProps) {
component.___setCustomEvents(customEvents, scope);
}
- preserveComponentEls(component, out, globalComponentsContext);
+ // We put a placeholder element in the output stream to ensure that the existing
+ // DOM node is matched up correctly when using morphdom. We flag the VElement
+ // node to track that it is a preserve marker
+ out.___preserveComponent(component);
+ globalComponentsContext.___renderedComponentsById[id] = true;
+ component.___reset(); // The component is no longer dirty so reset internal flags
return;
}
}
+ var isFakeComponent = false;
+
if (!component) {
- fakeComponent = {
- id: id
+ isFakeComponent = true;
+ component = {
+ id: id,
+ ___keyedElements: {}
};
} else {
componentState = component.___rawState || componentState;
@@ -168,17 +190,22 @@ function createRendererFunc(templateRenderFunc, componentProps) {
getTemplateData(componentState, input, out) :
componentState || input || {};
- var componentDef = componentsContext.___beginComponent(component || fakeComponent);
+ var componentDef = beginComponent(componentsContext, component, isSplit, parentComponentDef);
+
+ // This is a hack, but we have to swap out the component instance stored with this node
+ var vComponentNode = out.___parent;
+
componentDef.___roots = roots;
- componentDef.___component = fakeComponent ? null : component;
+ componentDef.___component = isFakeComponent ? null : component;
componentDef.___isExisting = isExisting;
componentDef.b = componentBody;
componentDef.c = function(widgetConfig) {
component.$c = widgetConfig;
};
+
componentDef.t = function(typeName) {
if (typeName) {
- this.___component = component = registry.___createComponent(typeName, fakeComponent.id);
+ vComponentNode.___component = this.___component = component = registry.___createComponent(typeName, component.id);
}
};
@@ -188,7 +215,7 @@ function createRendererFunc(templateRenderFunc, componentProps) {
// Render the template associated with the component using the final template
// data that we constructed
- templateRenderFunc(templateInput, out, componentDef, componentDef);
+ templateRenderFunc(templateInput, out, componentDef, componentDef, component);
if (customEvents && componentDef.___component) {
if (registry.___isServer) {
@@ -199,7 +226,8 @@ function createRendererFunc(templateRenderFunc, componentProps) {
}
}
- componentDef.___end();
+ endComponent(out, componentDef);
+ componentsContext.___componentDef = parentComponentDef;
};
}
diff --git a/src/components/package.json b/src/components/package.json
index 88c5cbfe6..180abc321 100644
--- a/src/components/package.json
+++ b/src/components/package.json
@@ -1,6 +1,7 @@
{
"browser": {
"./beginComponent.js": "./beginComponent-browser.js",
+ "./endComponent.js": "./endComponent-browser.js",
"./helpers.js": "./helpers-browser.js",
"./index.js": "./index-browser.js",
"./init-components.js": "./init-components-browser.js",
diff --git a/src/components/renderer.js b/src/components/renderer.js
index 68f08b5ad..b5ff19282 100644
--- a/src/components/renderer.js
+++ b/src/components/renderer.js
@@ -4,50 +4,26 @@ var emitLifecycleEvent = componentsUtil.___emitLifecycleEvent;
var ComponentsContext = require('./ComponentsContext');
var getComponentsContext = ComponentsContext.___getComponentsContext;
-var repeatedRegExp = /\[\]$/;
var registry = require('./registry');
var copyProps = require('raptor-util/copyProps');
var isServer = componentsUtil.___isServer === true;
+var beginComponent = require('./beginComponent');
+var endComponent = require('./endComponent');
var COMPONENT_BEGIN_ASYNC_ADDED_KEY = '$wa';
-function resolveComponentKey(globalComponentsContext, key, scope) {
- if (key[0] == '#') {
+function resolveComponentKey(globalComponentsContext, key, parentComponentDef) {
+ if (key[0] === '#') {
return key.substring(1);
} else {
- var resolvedId;
-
- if (repeatedRegExp.test(key)) {
- resolvedId = globalComponentsContext.___nextRepeatedId(scope, key);
- } else {
- resolvedId = scope + '-' + key;
- }
-
- return resolvedId;
+ return parentComponentDef.id + '-' + parentComponentDef.___nextKey(key);
}
}
-function preserveComponentEls(existingComponent, out, globalComponentsContext) {
- var rootEls = existingComponent.___getRootEls({});
-
- for (var elId in rootEls) {
- var el = rootEls[elId];
-
- // We put a placeholder element in the output stream to ensure that the existing
- // DOM node is matched up correctly when using morphdom.
- out.element(el.tagName, { id: elId });
-
- globalComponentsContext.___preserveDOMNode(elId); // Mark the element as being preserved (for morphdom)
- }
-
- existingComponent.___reset(); // The component is no longer dirty so reset internal flags
- return true;
-}
-
function handleBeginAsync(event) {
var parentOut = event.parentOut;
var asyncOut = event.out;
- var componentsContext = parentOut.data.___components;
+ var componentsContext = parentOut.___components;
if (componentsContext !== undefined) {
// All of the components in this async block should be
@@ -58,8 +34,7 @@ function handleBeginAsync(event) {
// stack (to begin with). This will result in top-level components
// of the async block being added as children of the component in the
// parent block.
- var nestedComponentsContext = componentsContext.___createNestedComponentsContext(asyncOut);
- asyncOut.data.___components = nestedComponentsContext;
+ asyncOut.___components = new ComponentsContext(asyncOut, componentsContext);
}
// Carry along the component arguments
asyncOut.___componentArgs = parentOut.___componentArgs;
@@ -69,8 +44,6 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
renderingLogic = renderingLogic || {};
var onInput = renderingLogic.onInput;
var typeName = componentProps.type;
- var roots = componentProps.roots;
- var assignedId = componentProps.id;
var isSplit = componentProps.split === true;
var shouldApplySplitMixins = isSplit;
@@ -89,38 +62,46 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
var component = globalComponentsContext.___rerenderComponent;
var isRerender = component !== undefined;
- var id = assignedId;
+ var id;
var isExisting;
var customEvents;
var scope;
+ var parentComponentDef;
if (component) {
id = component.id;
isExisting = true;
globalComponentsContext.___rerenderComponent = null;
} else {
+ parentComponentDef = componentsContext.___componentDef;
var componentArgs = out.___componentArgs;
-
if (componentArgs) {
+ // console.log('componentArgs:', componentArgs);
+ scope = parentComponentDef.id;
out.___componentArgs = null;
- scope = componentArgs[0];
+ var key;
- if (scope) {
- scope = scope.id;
+ if (typeof componentArgs === 'string') {
+ key = componentArgs;
+ } else {
+ key = componentArgs[0];
+ customEvents = componentArgs[1];
}
- var key = componentArgs[1];
if (key != null) {
key = key.toString();
+ id = resolveComponentKey(globalComponentsContext, key, parentComponentDef);
+ } else {
+ id = parentComponentDef.___nextComponentId();
}
- id = id || resolveComponentKey(globalComponentsContext, key, scope);
- customEvents = componentArgs[2];
+ } else if (parentComponentDef) {
+ id = parentComponentDef.___nextComponentId();
+ } else {
+ id = globalComponentsContext.___nextComponentId();
}
}
- id = id || componentsContext.___nextComponentId();
-
if (isServer) {
component = registry.___createComponent(
renderingLogic,
@@ -134,12 +115,10 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
component.___updatedInput = undefined; // We don't want ___updatedInput to be serialized to the browser
} else {
if (!component) {
- if (isRerender) {
- // Look in in the DOM to see if a component with the same ID and type already exists.
- component = componentLookup[id];
- if (component && component.___type !== typeName) {
- component = undefined;
- }
+ if (isRerender && (component = componentLookup[id]) && component.___type !== typeName) {
+ // Destroy the existing component since
+ component.destroy();
+ component = undefined;
}
if (component) {
@@ -178,7 +157,12 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
if (isExisting === true) {
if (component.___isDirty === false || component.shouldUpdate(input, component.___state) === false) {
- preserveComponentEls(component, out, globalComponentsContext);
+ // We put a placeholder element in the output stream to ensure that the existing
+ // DOM node is matched up correctly when using morphdom. We flag the VElement
+ // node to track that it is a preserve marker
+ out.___preserveComponent(component);
+ globalComponentsContext.___renderedComponentsById[id] = true;
+ component.___reset(); // The component is no longer dirty so reset internal flags
return;
}
}
@@ -189,15 +173,17 @@ function createRendererFunc(templateRenderFunc, componentProps, renderingLogic)
emitLifecycleEvent(component, 'render', out);
}
- var componentDef = componentsContext.___beginComponent(component, isSplit);
- componentDef.___roots = roots;
+ var componentDef =
+ beginComponent(componentsContext, component, isSplit, parentComponentDef);
+
componentDef.___isExisting = isExisting;
// Render the template associated with the component using the final template
// data that we constructed
templateRenderFunc(input, out, componentDef, component, component.___rawState);
- componentDef.___end();
+ endComponent(out, componentDef);
+ componentsContext.___componentDef = parentComponentDef;
};
}
@@ -205,5 +191,4 @@ module.exports = createRendererFunc;
// exports used by the legacy renderer
createRendererFunc.___resolveComponentKey = resolveComponentKey;
-createRendererFunc.___preserveComponentEls = preserveComponentEls;
createRendererFunc.___handleBeginAsync = handleBeginAsync;
diff --git a/src/components/taglib/TransformHelper/ComponentArgs.js b/src/components/taglib/TransformHelper/ComponentArgs.js
index 793331db9..dad09d7cf 100644
--- a/src/components/taglib/TransformHelper/ComponentArgs.js
+++ b/src/components/taglib/TransformHelper/ComponentArgs.js
@@ -2,23 +2,15 @@
class ComponentArgs {
constructor() {
- this.id = null;
+ this.key = null;
this.customEvents = null;
- this.empty = true;
}
- setId(id) {
- this.empty = false;
- this.id = id;
- }
-
- getId() {
- return this.id;
+ setKey(key) {
+ this.key = key;
}
addCustomEvent(eventType, targetMethod, extraArgs) {
- this.empty = false;
-
if (!this.customEvents) {
this.customEvents = [];
}
@@ -27,7 +19,7 @@ class ComponentArgs {
}
compile(transformHelper) {
- if (this.empty) {
+ if (!this.key && !this.customEvents) {
return;
}
@@ -35,29 +27,14 @@ class ComponentArgs {
var builder = transformHelper.builder;
- var id = this.id;
- var customEvents = this.customEvents;
+ var args;
- // Make sure the nested component has access to the ID of the containing
- // component if it is needed
- var shouldProvideScope = id || customEvents;
-
- var args = [];
-
- if (shouldProvideScope) {
- args.push(builder.identifier('__component'));
+ if (this.key && this.customEvents) {
+ args = builder.literal([ this.key, builder.literal(this.customEvents) ]);
+ } else if (this.customEvents) {
+ args = builder.literal([ builder.literalNull(), builder.literal(this.customEvents) ]);
} else {
- args.push(builder.literalNull());
- }
-
- if (id != null) {
- args.push(id);
- } else {
- args.push(builder.literalNull());
- }
-
- if (customEvents) {
- args.push(builder.literal(customEvents));
+ args = this.key;
}
if (el.type === 'CustomTag') {
@@ -66,7 +43,7 @@ class ComponentArgs {
el.generateRenderTagCode = function(codegen, tagVar, tagArgs) {
tagArgs = [tagVar].concat(tagArgs);
- tagArgs.push(builder.literal(args));
+ tagArgs.push(args);
return codegen.builder.functionCall(
renderComponentHelper,
@@ -75,7 +52,7 @@ class ComponentArgs {
} else {
el.onBeforeGenerateCode((event) => {
let funcTarget = builder.memberExpression(builder.identifierOut(), builder.identifier('c'));
- let funcArgs = [builder.literal(args)];
+ let funcArgs = [args];
event.insertCode(builder.functionCall(funcTarget, funcArgs));
});
diff --git a/src/components/taglib/TransformHelper/assignComponentId.js b/src/components/taglib/TransformHelper/assignComponentId.js
index aabb801da..c538587ec 100644
--- a/src/components/taglib/TransformHelper/assignComponentId.js
+++ b/src/components/taglib/TransformHelper/assignComponentId.js
@@ -12,7 +12,11 @@ module.exports = function assignComponentId(isRepeated) {
var context = this.context;
var builder = this.builder;
- let componentRef;
+ if (el.noOutput || (el.tagDef && el.tagDef.noOutput)) {
+ return;
+ }
+
+ let assignedKey;
var nestedIdExpression;
var idExpression;
@@ -27,6 +31,9 @@ module.exports = function assignComponentId(isRepeated) {
this.getMarkoComponentsRequirePath('marko/components/taglib/helpers/getCurrentComponent'));
context.addVar('__component', builder.functionCall(getCurrentComponentVar, [builder.identifierOut()]));
+ context.addVar('component', builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('___component')));
}
}
@@ -42,72 +49,80 @@ module.exports = function assignComponentId(isRepeated) {
// 3) The HTML does not have an "id" or "ref" attribute. We must add
// an "id" attribute with a unique ID.
- var isCustomTag = el.type !== 'HtmlElement';
+ var isHtmlElement = el.type === 'HtmlElement';
+ var isCustomTag = el.type === 'CustomTag';
- if (el.hasAttribute('key')) {
- componentRef = el.getAttributeValue('key');
- el.removeAttribute('key');
- } else if (el.hasAttribute('ref')) {
- context.deprecate('The "ref" attribute is deprecated. Please use "key" instead.');
- componentRef = el.getAttributeValue('ref');
- el.removeAttribute('ref');
+ // LEGACY -- Remove in Marko 5.0
+ if (!isCustomTag && el.tagName === 'invoke') {
+ isCustomTag = true;
+ }
+
+ if (!isCustomTag && !isHtmlElement) {
+ return;
}
if (el.hasAttribute('w-id')) {
context.deprecate('The "w-id" attribute is deprecated. Please use "key" instead.');
- if (componentRef) {
- this.addError('The "w-id" attribute cannot be used in conjuction with the "ref" or "key" attributes.');
+ if (el.hasAttribute('key')) {
+ this.addError('The "w-id" attribute cannot be used in conjunction with the "key" attributes.');
return;
}
- componentRef = el.getAttributeValue('w-id');
+ if (el.hasAttribute('ref')) {
+ this.addError('The "w-id" attribute cannot be used in conjunction with the "ref" attributes.');
+ return;
+ }
+
+ assignedKey = el.getAttributeValue('w-id');
el.removeAttribute('w-id');
+ } else if (el.hasAttribute('key')) {
+ assignedKey = el.getAttributeValue('key');
+ el.removeAttribute('key');
+ } else if (el.hasAttribute('ref')) {
+ context.deprecate('The "ref" attribute is deprecated. Please use "key" instead.');
+ assignedKey = el.getAttributeValue('ref');
+ el.removeAttribute('ref');
}
- if (componentRef) {
- idExpression = this.buildComponentElIdFunctionCall(componentRef);
-
- nestedIdExpression = componentRef;
+ if (assignedKey) {
+ nestedIdExpression = assignedKey;
if (isCustomTag) {
+ idExpression = this.buildComponentElIdFunctionCall(assignedKey);
// The element is a custom tag
- this.getComponentArgs().setId(nestedIdExpression);
+ this.getComponentArgs().setKey(nestedIdExpression);
} else {
- if (el.hasAttribute('id')) {
- this.addError('The "ref", "key", and "w-id" attributes cannot be used in conjuction with the "id" attribute.');
- return;
+ idExpression = assignedKey;
+ if (context.data.hasLegacyForKey && el.data.userAssignedKey !== false) {
+ el.setAttributeValue('id', this.buildComponentElIdFunctionCall(assignedKey));
}
- el.setAttributeValue('id', idExpression);
- }
- } else if (el.hasAttribute('id')) {
- idExpression = el.getAttributeValue('id');
- if (el.isFlagSet('hasComponentBind')) {
- // We have to attach a listener to the root element of the component
- // We will use an empty string as an indicator that it is the root component
- // element.
- nestedIdExpression = builder.literal('');
- } else {
- // Convert the raw String to a JavaScript expression. we need to prefix
- // with '#' to make it clear this is a fully resolved element ID
- nestedIdExpression = builder.concat(
- builder.literal('#'),
- idExpression);
+ if (context.isServerTarget()) {
+ var markoKeyAttrVar = context.importModule('marko_keyAttr',
+ this.getMarkoComponentsRequirePath('marko/components/taglib/helpers/markoKeyAttr'));
+
+ el.setAttributeValue('data-marko-key', builder.functionCall(markoKeyAttrVar, [
+ idExpression,
+ builder.identifier('__component')
+ ]));
+ }
+
+ el.setKey(assignedKey);
}
} else {
// Case 3 - We need to add a unique "id" attribute
let uniqueElId = this.nextUniqueId();
- nestedIdExpression = isRepeated ? builder.literal(uniqueElId + '[]') : builder.literal(uniqueElId);
+ nestedIdExpression = isRepeated ? builder.literal(uniqueElId + '[]') : builder.literal(uniqueElId.toString());
- idExpression = this.buildComponentElIdFunctionCall(nestedIdExpression);
+ idExpression = builder.literal(uniqueElId.toString());
if (isCustomTag) {
- this.getComponentArgs().setId(nestedIdExpression);
+ this.getComponentArgs().setKey(nestedIdExpression);
} else {
- el.setAttributeValue('id', idExpression);
+ el.setKey(idExpression);
}
}
@@ -123,13 +138,17 @@ module.exports = function assignComponentId(isRepeated) {
}
let uniqueElId = transformHelper.nextUniqueId();
- let idVarName = '__componentId' + uniqueElId;
+ let idVarName = '__key' + uniqueElId;
let idVar = builder.identifier(idVarName);
this.idVarNode = builder.vars([
{
id: idVarName,
- init: idExpression
+ init: builder.functionCall(
+ builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('___nextKey')),
+ [ idExpression ])
}
]);
@@ -140,9 +159,9 @@ module.exports = function assignComponentId(isRepeated) {
idVar);
if (isCustomTag) {
- transformHelper.getComponentArgs().setId(nestedIdExpression);
+ transformHelper.getComponentArgs().setKey(nestedIdExpression);
} else {
- el.setAttributeValue('id', idExpression);
+ el.setKey(idExpression);
}
return this.idVarNode;
diff --git a/src/components/taglib/TransformHelper/convertToComponent.js b/src/components/taglib/TransformHelper/convertToComponent.js
new file mode 100644
index 000000000..2d6ab557c
--- /dev/null
+++ b/src/components/taglib/TransformHelper/convertToComponent.js
@@ -0,0 +1,158 @@
+'use strict';
+const generateRegisterComponentCode = require('../util/generateRegisterComponentCode');
+
+// var FLAG_WILL_RERENDER_IN_BROWSER = 1;
+var FLAG_HAS_BODY_EL = 2;
+var FLAG_HAS_HEAD_EL = 4;
+
+module.exports = function handleComponentBind(options) {
+ if (this.firstBind) {
+ return;
+ }
+
+ this.firstBind = true;
+
+ let context = this.context;
+ let builder = this.builder;
+
+ let isLegacyComponent = this.isLegacyComponent = options.isLegacyComponent === true;
+ let componentModule = options.componentModule;
+ let rendererModule = options.rendererModule;
+ let componentProps = options.componentProps || {};
+ let rootNodes = options.rootNodes;
+ let isLegacyInnerBind = options.isLegacyInnerBind;
+
+ var isSplit = false;
+
+ if ((rendererModule && rendererModule !== componentModule) ||
+ (!rendererModule && componentModule)) {
+ componentProps.split = isSplit = true;
+ }
+
+ if (componentModule) {
+ let componentTypeNode;
+ let dependencyModule = isLegacyComponent || isSplit ? componentModule : this.getTemplateModule();
+
+ if (dependencyModule.requirePath) {
+ context.addDependency({ type:'require', path: dependencyModule.requirePath });
+ }
+
+ if (isSplit) {
+ context.addDependency({ type:'require', path: context.markoModulePrefix + 'components' });
+ }
+
+ componentTypeNode = context.addStaticVar(
+ 'marko_componentType',
+ generateRegisterComponentCode(componentModule, this, isSplit));
+
+ componentProps.type = componentTypeNode;
+ }
+
+ if (isLegacyInnerBind) {
+ let el = rootNodes[0];
+ el.setAttributeValue('id',
+ builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('id')));
+
+ // TODO Deprecation warning for inner binds
+ let componentNode = context.createNodeForEl('_component', {
+ props: builder.literal(componentProps)
+ });
+ el.wrapWith(componentNode);
+ return;
+ }
+
+ var flags = 0;
+
+
+
+ rootNodes.forEach((rootNode) => {
+ if (rootNode.type === 'HtmlElement') {
+ if (rootNode.tagName === 'body') {
+ flags |= FLAG_HAS_BODY_EL;
+ } else if (rootNode.tagName === 'head') {
+ flags |= FLAG_HAS_HEAD_EL;
+ }
+ }
+ });
+
+ if (flags) {
+ context.root.appendChild(
+ builder.assignment(
+ builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('___flags')),
+ builder.literal(flags),
+ '|='));
+ }
+
+ let markoComponentVar;
+
+ if (rendererModule) {
+ if (rendererModule.inlineId) {
+ markoComponentVar = rendererModule.inlineId;
+ } else {
+ markoComponentVar = context.addStaticVar('marko_component', builder.require(builder.literal(rendererModule.requirePath)));
+ }
+ }
+
+ this.setHasBoundComponentForTemplate();
+
+ var rendererHelper = isLegacyComponent ?
+ this.context.helper('rendererLegacy') :
+ this.context.helper('renderer');
+
+ var defineComponentHelper;
+
+ if (!isSplit && !isLegacyComponent) {
+ defineComponentHelper = this.context.helper('defineComponent');
+ }
+
+ this.context.on('beforeGenerateCode:TemplateRoot', function(eventArgs) {
+ eventArgs.node.addRenderFunctionParam(builder.identifier('__component'));
+
+ if (isLegacyComponent) {
+ eventArgs.node.addRenderFunctionParam(builder.identifier('widget'));
+ eventArgs.node.addRenderFunctionParam(builder.identifier('component'));
+ } else {
+ eventArgs.node.addRenderFunctionParam(builder.identifier('component'));
+ eventArgs.node.addRenderFunctionParam(builder.identifier('state'));
+ }
+
+ eventArgs.node.generateAssignRenderCode = (eventArgs) => {
+ const nodes = [];
+ const templateVar = eventArgs.templateVar;
+ const templateRendererMember = eventArgs.templateRendererMember;
+ const renderFunctionVar = eventArgs.renderFunctionVar;
+
+ const createRendererArgs = [
+ renderFunctionVar,
+ builder.literal(componentProps)
+ ];
+
+ if (markoComponentVar) {
+ createRendererArgs.push(markoComponentVar);
+ }
+
+ nodes.push(builder.assignment(
+ templateRendererMember,
+ builder.functionCall(
+ rendererHelper,
+ createRendererArgs)));
+
+ if (!isSplit && !isLegacyComponent) {
+ nodes.push(builder.assignment(
+ builder.memberExpression(templateVar, builder.identifier('Component')),
+ builder.functionCall(
+ defineComponentHelper,
+ [
+ markoComponentVar,
+ templateRendererMember
+ ])));
+ }
+
+ return nodes;
+ };
+ });
+};
diff --git a/src/components/taglib/TransformHelper/handleComponentBind.js b/src/components/taglib/TransformHelper/handleComponentBind.js
deleted file mode 100644
index 41c25d475..000000000
--- a/src/components/taglib/TransformHelper/handleComponentBind.js
+++ /dev/null
@@ -1,287 +0,0 @@
-'use strict';
-const resolveFrom = require('resolve-from');
-const generateRegisterComponentCode = require('../util/generateRegisterComponentCode');
-
-function legacyGetDefaultComponentModule(dirname) {
- var filename;
- var legacy = true;
-
- if ((filename = resolveFrom(dirname, './widget'))) {
- return {
- filename,
- requirePath: './widget',
- legacy
- };
- } else if ((filename = resolveFrom(dirname, './component'))) {
- return {
- filename,
- requirePath: './component',
- legacy
- };
- } else if ((filename = resolveFrom(dirname, './'))) {
- return {
- filename,
- requirePath: './',
- legacy
- };
- } else {
- return null;
- }
-}
-
-function checkIsInnerBind(el) {
- var curNode = el;
-
- while (true) {
- if (curNode.data.hasBoundComponent) {
- return true;
- }
-
- curNode = curNode.parentNode;
-
- if (!curNode) {
- break;
- }
- }
-
- return false;
-}
-
-module.exports = function handleComponentBind() {
- let el = this.el;
- let context = this.context;
- let builder = this.builder;
-
- let componentModule;
- let rendererModulePath;
- let rendererModule = this.getRendererModule();
- let isLegacyComponent = false;
-
- if (el.hasAttribute('w-bind')) {
- let bindAttr = el.getAttribute('w-bind');
-
- context.deprecate('Legacy components using w-bind and defineRenderer/defineComponent or defineComponent are deprecated. See: https://github.com/marko-js/marko/issues/421');
- this.isLegacyComponent = isLegacyComponent = true;
-
- // Remove the w-bind attribute since we don't want it showing up in the output DOM
- el.removeAttribute('w-bind');
-
- // Read the value for the w-bind attribute. This will be an AST node for the parsed JavaScript
- let bindAttrValue = bindAttr.value;
-
- const hasWidgetTypes = context.isFlagSet('hasWidgetTypes');
-
- if (hasWidgetTypes) {
- context.deprecate('The tag is deprecated. Please remove it. See: https://github.com/marko-js/marko/issues/514');
- }
-
- if (bindAttrValue == null) {
- componentModule = legacyGetDefaultComponentModule(this.dirname);
- if (!componentModule) {
- this.addError('No corresponding JavaScript module found in the same directory (either "component.js" or "index.js").');
- return;
- }
- } else if (bindAttr.isLiteralValue()) {
- if (typeof bindAttr.literalValue !== 'string') {
- this.addError('The value for the "w-bind" attribute should be a string. Actual: ' + componentModule);
- return;
- }
-
- let requirePath = bindAttr.literalValue;
- let filename = resolveFrom(this.dirname, requirePath);
-
- if (!filename) {
- this.addError('Target file not found: ' + requirePath + ' (from: ' + this.dirname + ')');
- return;
- }
-
- componentModule = {
- legacy: true,
- filename,
- requirePath
- };
- } else {
- // This is a dynamic expression. The should have been found.
- if (!hasWidgetTypes) {
- this.addError('The tag must be used to declare components when the value of the "w-bind" attribute is a dynamic expression.');
- return;
- }
-
- el.insertSiblingBefore(
- builder.functionCall(
- builder.memberExpression(builder.identifier('__component'), builder.identifier('t')),
- [
- builder.memberExpression(
- builder.identifier('marko_componentTypes'),
- bindAttrValue,
- true /* computed */)
- ]));
- }
- } else if (el.isFlagSet('hasComponentBind')) {
- componentModule = this.getComponentModule();
- rendererModulePath = this.getRendererModule();
-
-
- if (context.isFlagSet('hasWidgetTypes')) {
- context.addError('The tag is no longer supported. See: https://github.com/marko-js/marko/issues/514');
- }
- } else {
- return;
- }
-
- this.setHasBoundComponentForTemplate();
-
- let isInnerBind = checkIsInnerBind(el.parentNode);
-
- el.data.hasBoundComponent = true;
-
- // A component is bound to the el...
-
- var componentProps = isInnerBind ? {} : this.getComponentProps();
- let transformHelper = this;
-
- var isSplit = false;
-
- if ((rendererModule && rendererModule !== componentModule) ||
- (!rendererModule && componentModule)) {
- componentProps.split = isSplit = true;
- }
-
- if (componentModule) {
- let componentTypeNode;
- let dependencyModule = isLegacyComponent || isSplit ? componentModule : this.getTemplateModule();
-
- if (dependencyModule.requirePath) {
- context.addDependency({ type:'require', path: dependencyModule.requirePath });
- }
-
- if (isSplit) {
- context.addDependency({ type:'require', path: context.markoModulePrefix + 'components' });
- }
-
- componentTypeNode = context.addStaticVar(
- 'marko_componentType',
- generateRegisterComponentCode(componentModule, this, isSplit));
-
- componentProps.type = componentTypeNode;
- }
-
- if (el.hasAttribute('w-config')) {
- el.insertSiblingBefore(
- builder.functionCall(
- builder.memberExpression(builder.identifier('__component'), builder.identifier('c')),
- [
- el.getAttributeValue('w-config')
- ]));
-
- el.removeAttribute('w-config');
- }
-
- let id = el.getAttributeValue('id');
-
- if (id) {
- componentProps.id = id;
- }
-
- let markoComponentVar;
-
- if (rendererModule) {
- if (rendererModule.inlineId) {
- markoComponentVar = rendererModule.inlineId;
- } else {
- markoComponentVar = context.addStaticVar('marko_component', builder.require(builder.literal(rendererModule.requirePath)));
- }
- }
-
- if (isInnerBind) {
- el.setAttributeValue('id',
- builder.memberExpression(
- builder.identifier('__component'),
- builder.identifier('id')));
-
- // TODO Deprecation warning for inner binds
- let componentNode = context.createNodeForEl('_component', {
- props: builder.literal(componentProps)
- });
- el.wrapWith(componentNode);
- return;
- }
-
- if (this.firstBind) {
-
- var rendererHelper = isLegacyComponent ?
- transformHelper.context.helper('rendererLegacy') :
- transformHelper.context.helper('renderer');
-
- var defineComponentHelper;
-
- if (!isSplit && !isLegacyComponent) {
- defineComponentHelper = transformHelper.context.helper('defineComponent');
- }
-
- this.context.on('beforeGenerateCode:TemplateRoot', function(eventArgs) {
- eventArgs.node.addRenderFunctionParam(builder.identifier('__component'));
-
- if (isLegacyComponent) {
- eventArgs.node.addRenderFunctionParam(builder.identifier('widget'));
- } else {
- eventArgs.node.addRenderFunctionParam(builder.identifier('component'));
- eventArgs.node.addRenderFunctionParam(builder.identifier('state'));
- }
-
- eventArgs.node.generateAssignRenderCode = function(eventArgs) {
- let nodes = [];
- let templateVar = eventArgs.templateVar;
- let templateRendererMember = eventArgs.templateRendererMember;
- let renderFunctionVar = eventArgs.renderFunctionVar;
-
- let createRendererArgs = [
- renderFunctionVar,
- builder.literal(componentProps)
- ];
-
- if (markoComponentVar) {
- createRendererArgs.push(markoComponentVar);
- }
-
- nodes.push(builder.assignment(
- templateRendererMember,
- builder.functionCall(
- rendererHelper,
- createRendererArgs)));
-
- if (!isSplit && !isLegacyComponent) {
- nodes.push(builder.assignment(
- builder.memberExpression(templateVar, builder.identifier('Component')),
- builder.functionCall(
- defineComponentHelper,
- [
- markoComponentVar,
- templateRendererMember
- ])));
- }
-
- return nodes;
- };
- });
- }
-
- if (el.hasAttribute('key')) {
- if (!componentProps.roots) {
- componentProps.roots = [];
- }
- var key = el.getAttributeValue('key');
- componentProps.roots.push(key);
- } else if (el.hasAttribute('ref')) {
- if (!componentProps.roots) {
- componentProps.roots = [];
- }
- var ref = el.getAttributeValue('ref');
- componentProps.roots.push(ref);
- } else {
- el.setAttributeValue('id',
- builder.memberExpression(
- builder.identifier('__component'),
- builder.identifier('id')));
- }
-};
diff --git a/src/components/taglib/TransformHelper/handleComponentKeyAttrs.js b/src/components/taglib/TransformHelper/handleComponentKeyAttrs.js
deleted file mode 100644
index d0f2a8f01..000000000
--- a/src/components/taglib/TransformHelper/handleComponentKeyAttrs.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-const keySuffix = ':key';
-
-module.exports = function handleComponentKeyAttrs() {
- let el = this.el;
- let context = this.context;
-
- const filePosition = el.pos ? context.getPosInfo(el.pos) : context.filename;
-
- // BEGIN support for deprecated for attributes
-
- let deprecatedForAttributes = ['for-ref', 'for-key', 'w-for'];
-
- deprecatedForAttributes.forEach(attributeName => {
- if (el.hasAttribute(attributeName)) {
- context.deprecate(`The "${attributeName}" tag is deprecated. Please use "for:key" instead.`);
-
- let incompatibleAttributes = ['for', 'for:key']
- .concat(deprecatedForAttributes.filter(a => a != attributeName))
- .filter(a => el.hasAttribute(a));
-
- if (incompatibleAttributes.length) {
- this.addError(`The "${attributeName}" tag cannot be used with "${incompatibleAttributes.join('" or "')}". (${filePosition})`);
- } else {
- el.setAttributeValue('for:key', el.getAttributeValue(attributeName));
- }
-
- el.removeAttribute(attributeName);
- }
- });
-
- // END support for deprecated for attributes
-
- el.attributes.forEach(attribute => {
- const attributeName = attribute.name;
-
- if (attributeName && attributeName !== keySuffix && attributeName.endsWith(keySuffix)) {
- const unfixedName = attributeName.replace(keySuffix, '');
-
- el.removeAttribute(attributeName);
- if (el.hasAttribute(unfixedName)) {
- this.addError(`The "${attributeName}" attribute cannot be used in conjuction with the "${unfixedName}" attribute. (${filePosition})`);
- } else {
- el.setAttributeValue(
- unfixedName,
- this.buildComponentElIdFunctionCall(attribute.value));
- }
- }
- });
-};
diff --git a/src/components/taglib/TransformHelper/handleComponentPreserve.js b/src/components/taglib/TransformHelper/handleComponentPreserve.js
index ac9684f56..6ef5f32d4 100644
--- a/src/components/taglib/TransformHelper/handleComponentPreserve.js
+++ b/src/components/taglib/TransformHelper/handleComponentPreserve.js
@@ -15,12 +15,17 @@ function addPreserve(transformHelper, bodyOnly, condition) {
preserveAttrs['if'] = condition;
}
- let componentIdInfo = transformHelper.assignComponentId(true /* repeated */);
+ let componentIdInfo = transformHelper.assignComponentId();
let idVarNode = componentIdInfo.idVarNode ? null : componentIdInfo.createIdVarNode();
- preserveAttrs.id = transformHelper.getIdExpression();
+ if (el.type === 'HtmlElement') {
+ preserveAttrs.key = transformHelper.getIdExpression();
+ } else {
+ preserveAttrs.cid = transformHelper.getIdExpression();
+ }
- let preserveNode = context.createNodeForEl('w-preserve', preserveAttrs);
+
+ let preserveNode = context.createNodeForEl('_preserve', preserveAttrs);
let idVarNodeTarget;
if (bodyOnly) {
diff --git a/src/components/taglib/TransformHelper/handleIncludeNode.js b/src/components/taglib/TransformHelper/handleIncludeNode.js
deleted file mode 100644
index c9c9189e9..000000000
--- a/src/components/taglib/TransformHelper/handleIncludeNode.js
+++ /dev/null
@@ -1,75 +0,0 @@
-'use strict';
-
-var includeTagForComponents = require.resolve('../include-tag');
-
-module.exports = function(includeNode) {
- var context = this.context;
-
- if (!this.hasBoundComponentForTemplate()) {
- return;
- }
-
- var parentNode = includeNode.parentNode;
-
- if (!parentNode.hasAttribute) {
- return;
- }
-
- parentNode._normalizeChildTextNodes(context, true /* force trim */);
-
- if (parentNode.childCount === 1) {
- if (includeNode.hasAttribute('key') || includeNode.hasAttribute('ref')) {
- this.assignComponentId();
- }
-
- let parentTransformHelper = this.getTransformHelper(parentNode);
-
- if (includeNode.data.bodySlot) {
- parentTransformHelper.assignComponentId(false /* not repeated */);
- var componentProps = this.getComponentProps();
- componentProps.body = parentTransformHelper.getNestedIdExpression();
- } else {
- let componentIdInfo = parentTransformHelper.assignComponentId(true /* repeated */);
- if (!componentIdInfo.idVarNode) {
- let idVarNode = componentIdInfo.createIdVarNode();
- parentNode.onBeforeGenerateCode((event) => {
- event.insertCode(idVarNode);
- });
- }
- }
-
- includeNode.setRendererPath(includeTagForComponents);
-
- includeNode.onBeforeGenerateCode(function() {
- includeNode.addProp('_elId', parentTransformHelper.getIdExpression());
- });
- }
-
-
-
- // includeNode.generateCodeForDynamicInclude = (options, codegen) => {
- // var target = options.target;
- // var data = options.data;
- //
- // if (!data) {
- // data = builder.literal(null);
- // }
- //
- // let includeVar = context.importModule('marko_component_include', this.getMarkoComponentsRequirePath('marko/components/taglib/helpers/include'));
- //
- // let includeArgs = [
- // target,
- // builder.identifierOut(),
- // data
- // ];
- //
- // if (parentTransformHelper) {
- // includeArgs = includeArgs.concat([
- // parentTransformHelper.getIdExpression(),
- //
- // ]);
- // }
- //
- // return builder.functionCall(includeVar, includeArgs);
- // };
-};
diff --git a/src/components/taglib/TransformHelper/handleLegacyBind.js b/src/components/taglib/TransformHelper/handleLegacyBind.js
new file mode 100644
index 000000000..82897ed5a
--- /dev/null
+++ b/src/components/taglib/TransformHelper/handleLegacyBind.js
@@ -0,0 +1,155 @@
+'use strict';
+const resolveFrom = require('resolve-from');
+
+function legacyGetDefaultComponentModule(dirname) {
+ var filename;
+ var legacy = true;
+
+ if ((filename = resolveFrom(dirname, './widget'))) {
+ return {
+ filename,
+ requirePath: './widget',
+ legacy
+ };
+ } else if ((filename = resolveFrom(dirname, './component'))) {
+ return {
+ filename,
+ requirePath: './component',
+ legacy
+ };
+ } else if ((filename = resolveFrom(dirname, './'))) {
+ return {
+ filename,
+ requirePath: './',
+ legacy
+ };
+ } else {
+ return null;
+ }
+}
+
+function checkIsInnerBind(el) {
+ var curNode = el;
+
+ while (true) {
+ if (curNode.data.hasBoundComponent) {
+ return true;
+ }
+
+ curNode = curNode.parentNode;
+
+ if (!curNode) {
+ break;
+ }
+ }
+
+ return false;
+}
+
+module.exports = function handleLegacyBind() {
+ let el = this.el;
+ let context = this.context;
+ let builder = this.builder;
+
+ let componentModule;
+ let rendererModule;
+
+ let isLegacyComponent = false;
+
+ if (el.hasAttribute('w-bind')) {
+ let bindAttr = el.getAttribute('w-bind');
+
+ context.deprecate('Legacy components using w-bind and defineRenderer/defineComponent or defineComponent are deprecated. See: https://github.com/marko-js/marko/issues/421');
+ this.isLegacyComponent = isLegacyComponent = true;
+
+ // Remove the w-bind attribute since we don't want it showing up in the output DOM
+ el.removeAttribute('w-bind');
+
+ // Read the value for the w-bind attribute. This will be an AST node for the parsed JavaScript
+ let bindAttrValue = bindAttr.value;
+
+ const hasWidgetTypes = context.isFlagSet('hasWidgetTypes');
+
+ if (hasWidgetTypes) {
+ context.deprecate('The tag is deprecated. Please remove it. See: https://github.com/marko-js/marko/issues/514');
+ }
+
+ if (bindAttrValue == null) {
+ componentModule = legacyGetDefaultComponentModule(this.dirname);
+ if (!componentModule) {
+ this.addError('No corresponding JavaScript module found in the same directory (either "component.js" or "index.js").');
+ return;
+ }
+ } else if (bindAttr.isLiteralValue()) {
+ if (typeof bindAttr.literalValue !== 'string') {
+ this.addError('The value for the "w-bind" attribute should be a string. Actual: ' + componentModule);
+ return;
+ }
+
+ let requirePath = bindAttr.literalValue;
+ let filename = resolveFrom(this.dirname, requirePath);
+
+ if (!filename) {
+ this.addError('Target file not found: ' + requirePath + ' (from: ' + this.dirname + ')');
+ return;
+ }
+
+ componentModule = {
+ legacy: true,
+ filename,
+ requirePath
+ };
+ } else {
+ // This is a dynamic expression. The should have been found.
+ if (!hasWidgetTypes) {
+ this.addError('The tag must be used to declare components when the value of the "w-bind" attribute is a dynamic expression.');
+ return;
+ }
+
+ el.insertSiblingBefore(
+ builder.functionCall(
+ builder.memberExpression(builder.identifier('__component'), builder.identifier('t')),
+ [
+ builder.memberExpression(
+ builder.identifier('marko_componentTypes'),
+ bindAttrValue,
+ true /* computed */)
+ ]));
+ }
+ } else {
+ return;
+ }
+
+ let isLegacyInnerBind = checkIsInnerBind(el.parentNode);
+
+ el.data.hasBoundComponent = true;
+
+ // A component is bound to the el...
+
+ if (el.hasAttribute('w-config')) {
+ el.insertSiblingBefore(
+ builder.functionCall(
+ builder.memberExpression(builder.identifier('__component'), builder.identifier('c')),
+ [
+ el.getAttributeValue('w-config')
+ ]));
+
+ el.removeAttribute('w-config');
+ }
+
+ let componentProps = {};
+
+ let id = el.getAttributeValue('id');
+
+ if (id) {
+ componentProps.id = id;
+ }
+
+ this.convertToComponent({
+ isLegacyInnerBind,
+ componentModule,
+ rendererModule,
+ isLegacyComponent: true,
+ rootNodes: [el]
+ });
+};
diff --git a/src/components/taglib/TransformHelper/handleRootNodes.js b/src/components/taglib/TransformHelper/handleRootNodes.js
index d92631fde..0294de34f 100644
--- a/src/components/taglib/TransformHelper/handleRootNodes.js
+++ b/src/components/taglib/TransformHelper/handleRootNodes.js
@@ -1,7 +1,7 @@
'use strict';
-var path = require('path');
-var getComponentFiles = require('./getComponentFiles');
+let path = require('path');
+let getComponentFiles = require('./getComponentFiles');
const esprima = require('esprima');
const escodegen = require('escodegen');
@@ -12,16 +12,16 @@ function handleStyleElement(styleEl, transformHelper) {
return;
}
- var attrs = styleEl.attributes;
+ let attrs = styleEl.attributes;
- var styleCode;
- var lang = 'css';
+ let styleCode;
+ let lang = 'css';
- var hasStyleBlock = false;
+ let hasStyleBlock = false;
- for (var i=attrs.length-1; i>=0; i--) {
- var attr = attrs[i];
- var name = attr.name;
+ for (let i=attrs.length-1; i>=0; i--) {
+ let attr = attrs[i];
+ let name = attr.name;
if (name.startsWith('{')) {
hasStyleBlock = true;
@@ -47,7 +47,7 @@ function handleStyleElement(styleEl, transformHelper) {
styleEl.setFlag(FLAG_COMPONENT_STYLE);
if (styleCode) {
- var context = transformHelper.context;
+ let context = transformHelper.context;
context.addDependency({
type: lang,
code: styleCode,
@@ -95,15 +95,15 @@ function classToObject(cls, el, transformHelper) {
function handleClassDeclaration(classEl, transformHelper) {
let tree;
- var wrappedSrc = '('+classEl.tagString+'\n)';
+ let wrappedSrc = '('+classEl.tagString+'\n)';
try {
tree = esprima.parse(wrappedSrc);
} catch(err) {
- var message = 'Unable to parse JavaScript for component class. ' + err;
+ let message = 'Unable to parse JavaScript for component class. ' + err;
if (err.index != null) {
- var errorIndex = err.index;
+ let errorIndex = err.index;
// message += '\n' + err.description;
if (errorIndex != null && errorIndex >= 0) {
transformHelper.context.addError({
@@ -127,36 +127,28 @@ function handleClassDeclaration(classEl, transformHelper) {
let object = classToObject(expression, classEl, transformHelper);
let componentVar = transformHelper.context.addStaticVar('marko_component', escodegen.generate(object));
- if (transformHelper.getRendererModule() != null) {
- transformHelper.context.addError(classEl, 'The component has both an inline component `class` and a separate `component.js`. This is not allowed. See: https://github.com/marko-js/marko/wiki/Error:-Component-inline-and-external');
- return;
- }
-
- var moduleInfo = {
+ let moduleInfo = {
inlineId: componentVar,
filename: transformHelper.filename,
requirePath: './' + path.basename(transformHelper.filename)
};
- if (transformHelper.getComponentModule() == null) {
- transformHelper.setComponentModule(moduleInfo);
- }
-
- transformHelper.setRendererModule(moduleInfo);
-
classEl.detach();
+
+ return moduleInfo;
}
module.exports = function handleRootNodes() {
- var context = this.context;
- var builder = this.builder;
+ let context = this.context;
- var componentFiles = getComponentFiles(context.filename);
+ let componentFiles = getComponentFiles(context.filename);
if (!componentFiles) {
return;
}
- var dirname = context.dirname;
+ let componentModule;
+ let rendererModule;
+ let dirname = context.dirname;
if (componentFiles.package) {
context.addDependency('package: ./' + componentFiles.package);
@@ -174,67 +166,78 @@ module.exports = function handleRootNodes() {
requirePath: './'+file.slice(0, file.lastIndexOf('.'))
};
- this.setComponentModule(moduleInfo);
- this.setRendererModule(moduleInfo);
+ componentModule = rendererModule = moduleInfo;
}
if (componentFiles.browserFile) {
let file = componentFiles.browserFile;
- this.setComponentModule({
+
+ componentModule = {
filename: path.join(dirname, file),
requirePath: './' + file.slice(
0, file.lastIndexOf('.'))
- });
+ };
}
- var templateRoot = this.el;
+ let templateRoot = this.el;
- var rootNodes = [];
- var hasLegacyExplicitBind = false;
- var hasIdCount = 0;
- var nodeWithAssignedId;
- var assignedId;
- var transformHelper = this;
+ let rootNodes = [];
+ let hasIdCount = 0;
+ let nodeWithAssignedId;
+ let assignedId;
+ let transformHelper = this;
let walker = context.createWalker({
enter(node) {
let tagName = node.tagName && node.tagName.toLowerCase();
- let tag = node.tagName && context.taglibLookup.getTag(node.tagName);
if (node.type === 'TemplateRoot' || !node.type) {
- // Don't worry about the TemplateRoot or a Container node
- // But continue into the node to look at its children for root elements
- } else if (tag && tag.noOutput) {
- walker.skip();
+ // Don't worry about the TemplateRoot or an Container node
} else if (node.type === 'HtmlElement') {
- if (node.hasAttribute('w-bind')) {
- transformHelper.setHasBoundComponentForTemplate();
- hasLegacyExplicitBind = true;
- } else {
- if (node.hasAttribute('id')) {
- hasIdCount++;
- nodeWithAssignedId = node;
- assignedId = node.getAttributeValue('id');
- }
+ if (node.hasAttribute('id')) {
+ hasIdCount++;
+ nodeWithAssignedId = node;
+ assignedId = node.getAttributeValue('id');
+ }
- if (tagName === 'style') {
- handleStyleElement(node, transformHelper);
- }
+ if (tagName === 'style') {
+ handleStyleElement(node, transformHelper);
+ }
- if (!node.isFlagSet(FLAG_COMPONENT_STYLE)) {
- rootNodes.push(node);
- }
+ if (!node.isFlagSet(FLAG_COMPONENT_STYLE)) {
+ rootNodes.push(node);
+ }
+ walker.skip();
+
+ return;
+ } else if (node.type === 'CustomTag') {
+ let tag = context.taglibLookup.getTag(node.tagName);
+
+ if (!tag.noOutput) {
+ rootNodes.push(node);
}
walker.skip();
return;
- } else if (node.type === 'CustomTag') {
- rootNodes.push(node);
+ } else if (node.type === 'Scriptlet') {
walker.skip();
return;
} else {
if (tagName === 'class') {
- handleClassDeclaration(node, transformHelper);
+ let classComponentModule = handleClassDeclaration(node, transformHelper);
+
+ if (rendererModule != null) {
+ transformHelper.context.addError(node, 'The component has both an inline component `class` and a separate `component.js`. This is not allowed. See: https://github.com/marko-js/marko/wiki/Error:-Component-inline-and-external');
+ return;
+ }
+
+ if (componentModule == null) {
+ componentModule = classComponentModule;
+ }
+
+ rendererModule = classComponentModule;
+ } else if (!node.noOutput) {
+ rootNodes.push(node);
}
walker.skip();
@@ -245,43 +248,29 @@ module.exports = function handleRootNodes() {
walker.walk(templateRoot);
- if (hasLegacyExplicitBind) {
- //There is an explicit bind so nothing to do
+ if (!componentModule) {
return;
}
- if (!this.hasBoundComponentForTemplate()) {
- return;
+ if (context.isFlagSet('hasWidgetTypes')) {
+ context.addError('The tag is no longer supported. See: https://github.com/marko-js/marko/issues/514');
}
+ templateRoot._normalizeChildTextNodes(context, true);
+
+ // After normalizing the text nodes to remove whitespace we may have detached
+ // some of the root text nodes so remove those from our list
+ rootNodes = rootNodes.filter((rootNode) => {
+ return rootNode.isDetached() !== true;
+ });
+
if (rootNodes.length === 0) {
return;
}
- if (rootNodes.length > 1 && hasIdCount > 0) {
- // We can only bind a component to multiple top-level elements if we can assign
- // all of the IDs
- return;
- }
-
- transformHelper.setHasBoundComponentForTemplate();
-
- var nextKey = 0;
-
- rootNodes.forEach((curNode, i) => {
- let id = curNode.getAttributeValue('id');
-
- if (id && id.type !== 'Literal') {
- context.addError('Root HTML element should not have a dynamic `id` attribute. See: https://github.com/marko-js/marko/wiki/Error:-Dynamic-root-HTML-element-id-attribute');
- return;
- }
-
- curNode.setFlag('hasComponentBind');
-
- if (!curNode.hasAttribute('key') && !curNode.hasAttribute('ref')) {
- if (curNode.type === 'CustomTag' || rootNodes.length > 1) {
- curNode.setAttributeValue('key', builder.literal('_r' + (nextKey++)));
- }
- }
+ this.convertToComponent({
+ rootNodes,
+ componentModule,
+ rendererModule
});
};
diff --git a/src/components/taglib/TransformHelper/handleScopedAttrs.js b/src/components/taglib/TransformHelper/handleScopedAttrs.js
new file mode 100644
index 000000000..ae58d5a17
--- /dev/null
+++ b/src/components/taglib/TransformHelper/handleScopedAttrs.js
@@ -0,0 +1,75 @@
+'use strict';
+
+const deprecatedKeySuffix = ':key';
+const scopedSuffix = ':scoped';
+const deprecatedAttrs = {
+ 'for-ref': true,
+ 'for-key': true,
+ 'w-for': true
+};
+
+module.exports = function handleComponentKeyAttrs() {
+ let el = this.el;
+ let context = this.context;
+ let builder = this.builder;
+ const filePosition = el.pos ? context.getPosInfo(el.pos) : context.filename;
+
+ var attrs = el.attributes.concat([]);
+
+ attrs.forEach(attribute => {
+ const attributeName = attribute.name;
+ if (!attributeName) {
+ return;
+ }
+
+ let fixedAttributeName = attributeName;
+
+ // BEGIN support for deprecated for attributes
+ if (deprecatedAttrs[attributeName]) {
+ context.deprecate(`The "${attributeName}" attribute is deprecated. Please use "for:key" instead.`);
+
+ let incompatibleAttributes = ['for', 'for:key']
+ .concat(Object.keys(deprecatedAttrs).filter(a => a != attributeName))
+ .filter(a => el.hasAttribute(a));
+
+ if (incompatibleAttributes.length) {
+ this.addError(`The "${attributeName}" attribute cannot be used in conjunction with "${incompatibleAttributes.join('" or "')}". (${filePosition})`);
+ return;
+ } else {
+ fixedAttributeName = 'for:scoped';
+ }
+ } else if (attributeName !== deprecatedKeySuffix && attributeName.endsWith(deprecatedKeySuffix)) {
+ context.deprecate(`The "${attributeName}" attribute is deprecated. Please use "for:scoped" instead.`);
+ fixedAttributeName = attributeName.slice(0, 0-deprecatedKeySuffix.length) + ':scoped';
+ }
+ // END support for deprecated for attributes
+
+ if (fixedAttributeName !== scopedSuffix && fixedAttributeName.endsWith(scopedSuffix)) {
+ el.removeAttribute(attributeName);
+
+ let finalAttributeName = fixedAttributeName.slice(0, 0-scopedSuffix.length);
+
+ if (el.hasAttribute(finalAttributeName)) {
+ this.addError(`The "${attributeName}" attribute cannot be used in conjunction with the "${finalAttributeName}" attribute. (${filePosition})`);
+ return;
+ }
+
+ let uniqueElId = this.nextUniqueId();
+ let idVarName = 'marko_' + finalAttributeName + '_key' + uniqueElId;
+
+
+ let varNode = builder.var(idVarName, attribute.value);
+
+ el.insertSiblingBefore(varNode);
+ let varIdNode = builder.identifier(idVarName);
+
+ el.setAttributeValue(finalAttributeName, this.buildComponentElIdFunctionCall(varIdNode));
+
+ if (!el.hasAttribute('key') && !el.hasAttribute('w-id') && !el.hasAttribute('ref')) {
+ // The scoped attribute should be suitable for a key
+ el.setAttributeValue('key', varIdNode);
+ el.data.userAssignedKey = false;
+ }
+ }
+ });
+};
diff --git a/src/components/taglib/TransformHelper/index.js b/src/components/taglib/TransformHelper/index.js
index c699acd87..6167048da 100644
--- a/src/components/taglib/TransformHelper/index.js
+++ b/src/components/taglib/TransformHelper/index.js
@@ -24,25 +24,11 @@ class TransformHelper {
}
setHasBoundComponentForTemplate() {
+ this.context.isComponent = true;
+
this.context.data[HAS_COMPONENT_KEY] = true;
}
- setComponentModule(value) {
- this.context.data.componentModule = value;
- }
-
- getComponentModule() {
- return this.context.data.componentModule;
- }
-
- setRendererModule(value) {
- this.context.data.rendererModule = value;
- }
-
- getRendererModule() {
- return this.context.data.rendererModule;
- }
-
getTemplateModule() {
return {
requirePath:this.context.getRequirePath(this.filename)
@@ -55,15 +41,6 @@ class TransformHelper {
this.context.data[WIDGET_PROPS_KEY] != null;
}
- getComponentProps() {
- var componentProps = this.context.data[WIDGET_PROPS_KEY];
- if (!componentProps) {
- this.firstBind = true;
- componentProps = this.context.data[WIDGET_PROPS_KEY] = {};
- }
- return componentProps;
- }
-
addError(message, code) {
this.context.addError(this.el, message, code);
}
@@ -81,11 +58,6 @@ class TransformHelper {
return (this.context.data.componentNextElId++);
}
- getNestedIdExpression() {
- this.assignComponentId();
- return this.getComponentIdInfo().nestedIdExpression;
- }
-
getIdExpression() {
this.assignComponentId();
return this.getComponentIdInfo().idExpression;
@@ -129,11 +101,19 @@ class TransformHelper {
buildComponentElIdFunctionCall(id) {
var builder = this.builder;
- var componentElId = builder.memberExpression(
- builder.identifier('__component'),
- builder.identifier('elId'));
+ if (id.type === 'Literal' && id.value === '') {
+ let componentElId = builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('id'));
- return builder.functionCall(componentElId, arguments.length === 0 ? [] : [ id ]);
+ return componentElId;
+ } else {
+ let componentElId = builder.memberExpression(
+ builder.identifier('__component'),
+ builder.identifier('elId'));
+
+ return builder.functionCall(componentElId, arguments.length === 0 ? [] : [ id ]);
+ }
}
getTransformHelper(el) {
@@ -142,12 +122,12 @@ class TransformHelper {
}
TransformHelper.prototype.assignComponentId = require('./assignComponentId');
+TransformHelper.prototype.convertToComponent = require('./convertToComponent');
TransformHelper.prototype.handleRootNodes = require('./handleRootNodes');
-TransformHelper.prototype.handleIncludeNode = require('./handleIncludeNode');
TransformHelper.prototype.handleComponentEvents = require('./handleComponentEvents');
TransformHelper.prototype.handleComponentPreserve = require('./handleComponentPreserve');
TransformHelper.prototype.handleComponentPreserveAttrs = require('./handleComponentPreserveAttrs');
-TransformHelper.prototype.handleComponentBind = require('./handleComponentBind');
-TransformHelper.prototype.handleComponentKeyAttrs = require('./handleComponentKeyAttrs');
+TransformHelper.prototype.handleScopedAttrs = require('./handleScopedAttrs');
+TransformHelper.prototype.handleLegacyBind = require('./handleLegacyBind');
module.exports = TransformHelper;
diff --git a/src/components/taglib/components-transformer.js b/src/components/taglib/components-transformer.js
index 7df27ecb8..07996cd2e 100644
--- a/src/components/taglib/components-transformer.js
+++ b/src/components/taglib/components-transformer.js
@@ -23,10 +23,15 @@ module.exports = function transform(el, context) {
}
if (el.hasAttribute('w-body')) {
+ // This is a legacy code block and should be removed in Marko v5
context.deprecate('The "w-body" attribute is deprecated. Please use "": {
+ "<_preserve>": {
"renderer": "./preserve-tag.js",
- "@id": "string",
- "@if": "expression",
- "@body-only": "expression",
- "autocomplete": [],
- "deprecated": true
+ "@key": "string",
+ "@cid": "string",
+ "@if": "boolean",
+ "@body-only": "boolean",
+ "autocomplete": []
},
"": {
"renderer": "./preserve-tag.js",
diff --git a/src/components/taglib/preserve-tag-browser.js b/src/components/taglib/preserve-tag-browser.js
index 99d8631e4..c90d79fce 100644
--- a/src/components/taglib/preserve-tag-browser.js
+++ b/src/components/taglib/preserve-tag-browser.js
@@ -1,34 +1,46 @@
-var getElementById = require('../util').___getElementById;
+var componentsUtil = require('../util');
+var componentLookup = componentsUtil.___componentLookup;
module.exports = function render(input, out) {
- var globalComponentsContext = out.global.___components;
-
- if (globalComponentsContext && globalComponentsContext.___isRerenderInBrowser !== true && globalComponentsContext.___rerenderComponent !== undefined) {
- var id = input.id;
+ var componentsContext = out.___components;
+ if (componentsContext) {
// See if the DOM node with the given ID already exists.
// If so, then reuse the existing DOM node instead of re-rendering
// the children. We have to put a placeholder node that will get
// replaced out if we find that the DOM node has already been rendered
if (!('if' in input) || input['if']) {
- var existingEl = getElementById(out.___document, id);
- if (existingEl) {
- var bodyOnly = input.bodyOnly === true;
- // Don't actually render anything since the element is already in the DOM,
- // but keep track that the node is being preserved so that we can ignore
- // it while transforming the old DOM
+ var component = componentsContext.___componentDef.___component;
+ var globalComponentsContext = componentsContext.___globalContext;
+ var key = input.key;
+ var componentId;
- if (!bodyOnly) {
- var tagName = existingEl.tagName;
- // If we are preserving the entire DOM node (not just the body)
- // then that means that we have need to render a placeholder to
- // mark the target location. We can then replace the placeholder
- // node with the existing DOM node
- out.element(tagName, { id: id });
+ if (key) {
+ if (component.___keyedElements[key]) {
+ var bodyOnly = input.bodyOnly === true;
+ // Don't actually render anything since the element is already in the DOM,
+ // but keep track that the node is being preserved so that we can ignore
+ // it while transforming the old DOM
+ if (bodyOnly) {
+ globalComponentsContext.___preservedElBodies[key] = true;
+ } else {
+ // If we are preserving the entire DOM node (not just the body)
+ // then that means that we have need to render a placeholder to
+ // mark the target location. We can then replace the placeholder
+ // node with the existing DOM node
+ out.element('', null, key, null, 0, 8 /* FLAG_PRESERVE */);
+ globalComponentsContext.___preservedEls[key] = true;
+ }
+
+ return;
+ }
+ } else if ((componentId = input.cid)) {
+ var existingComponent = componentLookup[componentId];
+ if (existingComponent) {
+ out.___preserveComponent(existingComponent);
+ globalComponentsContext.___renderedComponentsById[componentId] = true;
+ return;
}
-
- globalComponentsContext.___preserveDOMNode(id, bodyOnly);
- return;
}
}
}
diff --git a/src/components/util-browser.js b/src/components/util-browser.js
index d4dd6417f..65c921130 100644
--- a/src/components/util-browser.js
+++ b/src/components/util-browser.js
@@ -1,12 +1,6 @@
-var extend = require('raptor-util/extend');
-var markoGlobal = extend(window.$MG, {
- uid: 0
-});
-
-window.$MG = markoGlobal;
-
-var runtimeId = markoGlobal.uid++;
+var markoUID = window.$MUID || (window.$MUID = { i: 0 });
+var runtimeId = markoUID.i++;
var componentLookup = {};
@@ -17,18 +11,7 @@ function getComponentForEl(el, doc) {
if (el) {
var node = typeof el == 'string' ? (doc || defaultDocument).getElementById(el) : el;
if (node) {
- var component = node._w;
-
- while(component) {
- var rootFor = component.___rootFor;
- if (rootFor) {
- component = rootFor;
- } else {
- break;
- }
- }
-
- return component;
+ return node.___markoComponent;
}
}
}
@@ -69,26 +52,26 @@ function emitLifecycleEvent(component, eventType, eventArg1, eventArg2) {
component.emit(eventType, eventArg1, eventArg2);
}
-function destroyComponentForEl(el) {
- var componentToDestroy = el._w;
+function destroyComponentForNode(node) {
+ var componentToDestroy = node.___markoComponent;
if (componentToDestroy) {
componentToDestroy.___destroyShallow();
- el._w = null;
-
- while ((componentToDestroy = componentToDestroy.___rootFor)) {
- componentToDestroy.___rootFor = null;
- componentToDestroy.___destroyShallow();
- }
}
}
-function destroyElRecursive(el) {
- var curChild = el.firstChild;
- while(curChild) {
- if (curChild.nodeType === 1) {
- destroyComponentForEl(curChild);
- destroyElRecursive(curChild);
+function destroyNodeRecursive(node, component) {
+ if (node.nodeType === 1) {
+ var key;
+
+ if (component && (key = node.___markoKey)) {
+ delete component.___keyedElements[key];
+ }
+
+ var curChild = node.firstChild;
+ while(curChild) {
+ destroyComponentForNode(curChild);
+ destroyNodeRecursive(curChild, component);
+ curChild = curChild.nextSibling;
}
- curChild = curChild.nextSibling;
}
}
@@ -97,52 +80,36 @@ function nextComponentId() {
// marko runtimes. This allows multiple instances of marko to be
// loaded in the same window and they should all place nice
// together
- return 'b' + ((markoGlobal.uid)++);
+ return 'b' + (markoUID.i++);
}
-function nextComponentIdProvider(out) {
+function nextComponentIdProvider() {
return nextComponentId;
}
-function getElementById(doc, id) {
- return doc.getElementById(id);
-}
-
function attachBubblingEvent(componentDef, handlerMethodName, extraArgs) {
if (handlerMethodName) {
- var id = componentDef.id;
+ var componentId = componentDef.id;
if (extraArgs) {
- var isRerenderInBrowser = componentDef.___globalComponentsContext.___isRerenderInBrowser;
-
- if (isRerenderInBrowser === true) {
- // If we are bootstrapping a page rendered on the server
- // we need to put the actual event args on the UI component
- // since we will not actually be updating the DOM
- var component = componentDef.___component;
-
- var bubblingDomEvents = component.___bubblingDomEvents ||
- ( component.___bubblingDomEvents = [] );
-
- bubblingDomEvents.push(extraArgs);
-
- return;
- } else {
- return [handlerMethodName, id, extraArgs];
- }
+ return [handlerMethodName, componentId, extraArgs];
} else {
- return [handlerMethodName, id];
+ return [handlerMethodName, componentId];
}
}
}
function getMarkoPropsFromEl(el) {
- var virtualProps = el._vprops;
- if (virtualProps === undefined) {
- virtualProps = el.getAttribute('data-marko');
- if (virtualProps) {
- virtualProps = JSON.parse(virtualProps);
+ var vElement = el.___markoVElement;
+ var virtualProps;
+
+ if (vElement) {
+ virtualProps = vElement.___properties;
+ } else {
+ virtualProps = el.___markoVProps;
+ if (!virtualProps) {
+ virtualProps = el.getAttribute('data-marko');
+ el.___markoVProps = virtualProps = virtualProps ? JSON.parse(virtualProps) : EMPTY_OBJECT;
}
- el._vprops = virtualProps = virtualProps || EMPTY_OBJECT;
}
return virtualProps;
@@ -152,9 +119,8 @@ exports.___runtimeId = runtimeId;
exports.___componentLookup = componentLookup;
exports.___getComponentForEl = getComponentForEl;
exports.___emitLifecycleEvent = emitLifecycleEvent;
-exports.___destroyComponentForEl = destroyComponentForEl;
-exports.___destroyElRecursive = destroyElRecursive;
+exports.___destroyComponentForNode = destroyComponentForNode;
+exports.___destroyNodeRecursive = destroyNodeRecursive;
exports.___nextComponentIdProvider = nextComponentIdProvider;
-exports.___getElementById = getElementById;
exports.___attachBubblingEvent = attachBubblingEvent;
exports.___getMarkoPropsFromEl = getMarkoPropsFromEl;
diff --git a/src/components/util.js b/src/components/util.js
index 401ec4096..532904bf4 100644
--- a/src/components/util.js
+++ b/src/components/util.js
@@ -1,3 +1,7 @@
+var FLAG_WILL_RERENDER_IN_BROWSER = 1;
+// var FLAG_HAS_BODY_EL = 2;
+// var FLAG_HAS_HEAD_EL = 4;
+
function nextComponentIdProvider(out) {
var prefix = out.global.componentIdPrefix || 's'; // "s" is for server (we use "b" for the browser)
var nextId = 0;
@@ -21,7 +25,7 @@ function attachBubblingEvent(componentDef, handlerMethodName, extraArgs) {
// where the extra args will be found when the UI component is
// rerendered in the browser
- if (componentDef.___willRerenderInBrowser === false) {
+ if (componentDef.___flags & FLAG_WILL_RERENDER_IN_BROWSER) {
if (eventIndex === 0) {
component.___bubblingDomEvents = [extraArgs];
} else {
@@ -40,3 +44,5 @@ function attachBubblingEvent(componentDef, handlerMethodName, extraArgs) {
exports.___nextComponentIdProvider = nextComponentIdProvider;
exports.___isServer = true;
exports.___attachBubblingEvent = attachBubblingEvent;
+exports.___destroyComponentForNode = function noop() {};
+exports.___destroyNodeRecursive = function noop() {};
diff --git a/src/morphdom/index.js b/src/morphdom/index.js
index 3758701c9..952b74371 100644
--- a/src/morphdom/index.js
+++ b/src/morphdom/index.js
@@ -1,325 +1,539 @@
'use strict';
-var defaultDoc = typeof document == 'undefined' ? undefined : document;
var specialElHandlers = require('./specialElHandlers');
-
-var morphAttrs = require('../runtime/vdom/VElement').___morphAttrs;
+var componentsUtil = require('../components/util');
+var existingComponentLookup = componentsUtil.___componentLookup;
+var destroyComponentForNode = componentsUtil.___destroyComponentForNode;
+var destroyNodeRecursive = componentsUtil.___destroyNodeRecursive;
+var VElement = require('../runtime/vdom/vdom').___VElement;
+var virtualizeElement = VElement.___virtualize;
+var morphAttrs = VElement.___morphAttrs;
+var eventDelegation = require('../components/event-delegation');
var ELEMENT_NODE = 1;
var TEXT_NODE = 3;
var COMMENT_NODE = 8;
+var COMPONENT_NODE = 2;
+
+// var FLAG_IS_SVG = 1;
+// var FLAG_IS_TEXTAREA = 2;
+// var FLAG_SIMPLE_ATTRS = 4;
+var FLAG_PRESERVE = 8;
function compareNodeNames(fromEl, toEl) {
- return fromEl.nodeName === toEl.___nodeName;
+ return fromEl.___nodeName === toEl.___nodeName;
}
+function onBeforeNodeDiscarded(node) {
+ return eventDelegation.___handleNodeDetach(node);
+}
-function getElementById(doc, id) {
- return doc.getElementById(id);
+function onNodeAdded(node, componentsContext) {
+ if (node.nodeType === 1) {
+ eventDelegation.___handleNodeAttach(node, componentsContext);
+ }
+}
+
+function insertBefore(node, referenceNode, parentNode) {
+ return parentNode.insertBefore(node, referenceNode);
+}
+
+function insertAfter(node, referenceNode, parentNode) {
+ return parentNode.insertBefore(node, referenceNode && referenceNode.nextSibling);
}
function morphdom(
- fromNode,
+ parentNode,
+ startNode,
+ endNode,
toNode,
- context,
- onNodeAdded,
- onBeforeElUpdated,
- onBeforeNodeDiscarded,
- onNodeDiscarded,
- onBeforeElChildrenUpdated
+ doc,
+ componentsContext
) {
+ var globalComponentsContext;
+ var isRerenderInBrowser = false;
- var doc = fromNode.ownerDocument || defaultDoc;
-
- // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
- var removalList = [];
- var foundKeys = {};
-
- function walkDiscardedChildNodes(node) {
- onNodeDiscarded(node);
- var curChild = node.firstChild;
-
- while (curChild) {
- walkDiscardedChildNodes(curChild);
- curChild = curChild.nextSibling;
- }
+ if (componentsContext) {
+ globalComponentsContext = componentsContext.___globalContext;
+ isRerenderInBrowser = globalComponentsContext.___isRerenderInBrowser;
}
+ function createMarkerComment(referenceNode, parentNode) {
+ return doc.createComment('$marko');
+ }
- function addVirtualNode(vEl, parentEl) {
- var realEl = vEl.___actualize(doc);
+ function insertVirtualNodeBefore(vNode, key, referenceEl, parentEl, component, keySequence) {
+ var realNode = vNode.___actualize(doc);
+ insertBefore(realNode, referenceEl, parentEl);
- if (parentEl) {
- parentEl.appendChild(realEl);
- }
-
- onNodeAdded(realEl, context);
-
- var vCurChild = vEl.___firstChild;
- while (vCurChild) {
- var realCurChild = null;
-
- var key = vCurChild.id;
+ if (vNode.___nodeType === ELEMENT_NODE) {
if (key) {
- var unmatchedFromEl = getElementById(doc, key);
- if (unmatchedFromEl && compareNodeNames(vCurChild, unmatchedFromEl)) {
- morphEl(unmatchedFromEl, vCurChild, false);
- realEl.appendChild(realCurChild = unmatchedFromEl);
- }
+ realNode.___markoKey = key;
+ (component = vNode.___component || component).___keyedElements[key] = realNode;
}
- if (!realCurChild) {
- addVirtualNode(vCurChild, realEl);
- }
-
- vCurChild = vCurChild.___nextSibling;
+ morphChildren(realNode, null, null, vNode, component, keySequence);
}
- if (vEl.___nodeType === 1) {
- var elHandler = specialElHandlers[vEl.nodeName];
- if (elHandler !== undefined) {
- elHandler(realEl, vEl);
- }
- }
-
- return realEl;
+ onNodeAdded(realNode, componentsContext);
}
- function morphEl(fromEl, toEl, childrenOnly) {
- var toElKey = toEl.id;
- var nodeName = toEl.___nodeName;
+ function insertVirtualComponentBefore(vComponent, referenceNode, referenceNodeParentEl) {
+ var component = vComponent.___component;
+ component.___startNode = component.___endNode = insertBefore(createMarkerComment(), referenceNode, referenceNodeParentEl);
+ morphComponent(referenceNodeParentEl, component, vComponent);
+ }
- if (childrenOnly === false) {
- if (toElKey) {
- // If an element with an ID is being morphed then it is will be in the final
- // DOM so clear it out of the saved elements collection
- foundKeys[toElKey] = true;
+ function resolveComponentEndNode(startNode, vChild, parentNode) {
+ var endNode = startNode;
+
+ // We track text nodes because multiple adjacent VText nodes should
+ // be treated as a single VText node for purposes of pairing with HTML
+ // that was rendered on the server since browsers will only see
+ // a single text node
+ var isPrevText = vChild.___nodeType === TEXT_NODE;
+
+ while((vChild = vChild.___nextSibling)) {
+ var nextRealNode = endNode.nextSibling;
+
+ // We stop when there are no more corresponding real nodes or when
+ // we reach the end boundary for our UI component
+ if (!nextRealNode || nextRealNode.___endNode) {
+ break;
}
+ var isText = vChild.___nodeType === TEXT_NODE;
+ if (isText && isPrevText) {
+ // Pretend like we didn't see this VText node since it
+ // the previous vnode was also a VText node
+ continue;
+ }
+ endNode = nextRealNode;
+ isPrevText = isText;
+ }
- var constId = toEl.___constId;
- if (constId !== undefined) {
- var otherProps = fromEl._vprops;
- if (otherProps !== undefined && constId === otherProps.c) {
- return;
+ return endNode;
+ }
+
+ function morphComponent(parentFromNode, component, vComponent) {
+ // We create a key sequence to generate unique keys since a key
+ // can be repeated
+ var keySequence = globalComponentsContext.___createKeySequence();
+
+ var startNode = component.___startNode;
+ var endNode = component.___endNode;
+ startNode.___markoComponent = undefined;
+ endNode.___endNode = undefined;
+
+ var beforeChild = startNode.previousSibling;
+ var afterChild = endNode.nextSibling;
+
+ morphChildren(parentFromNode, startNode, afterChild, vComponent, component, keySequence);
+
+ endNode = undefined;
+
+ if (beforeChild) {
+ startNode = beforeChild.nextSibling;
+ if (!startNode || startNode === afterChild) {
+ startNode = endNode = insertAfter(createMarkerComment(), beforeChild, parentFromNode);
+ }
+ } else {
+ startNode = parentFromNode.firstChild;
+ if (!startNode) {
+ startNode = endNode = insertAfter(createMarkerComment(), beforeChild, parentFromNode);
+ }
+ }
+
+ if (!endNode) {
+ if (afterChild) {
+ endNode = afterChild.previousSibling;
+ } else {
+ endNode = parentFromNode.lastChild;
+ }
+ }
+
+ // Make sure we don't use a detached node as the component boundary and
+ // we can't use a node that is already the boundary node for another component
+ if (startNode.___markoDetached !== undefined || startNode.___markoComponent) {
+ startNode = insertBefore(createMarkerComment(), startNode, parentFromNode);
+ }
+
+ if (endNode.___markoDetached !== undefined || endNode.___endNode) {
+ endNode = insertAfter(createMarkerComment(), endNode, parentFromNode);
+ }
+
+ startNode.___markoComponent = component;
+ endNode.___endNode = true;
+
+ component.___startNode = startNode;
+ component.___endNode = endNode;
+
+ return afterChild;
+ }
+
+ var detachedNodes = [];
+
+ function detachNode(node, parentNode, component) {
+ if (node.nodeType === ELEMENT_NODE) {
+ detachedNodes.push(node);
+ node.___markoDetached = component || true;
+ } else {
+ destroyNodeRecursive(node);
+ parentNode.removeChild(node);
+ }
+ }
+
+ function destroyComponent(component) {
+ component.destroy(onBeforeNodeDiscarded);
+ }
+
+ function morphChildren(parentFromNode, startNode, endNode, toNode, component, keySequence) {
+ var curFromNodeChild = startNode;
+ var curToNodeChild = toNode.___firstChild;
+
+ var curToNodeKey;
+ var curFromNodeKey;
+ var curToNodeType;
+
+ var fromNextSibling;
+ var toNextSibling;
+ var matchingFromEl;
+ var matchingFromComponent;
+ var toComponent;
+ var curVFromNodeChild;
+ var fromComponent;
+
+ outer: while (curToNodeChild) {
+ toNextSibling = curToNodeChild.___nextSibling;
+ curToNodeType = curToNodeChild.___nodeType;
+
+ if (curToNodeType === COMPONENT_NODE) {
+ toComponent = curToNodeChild.___component;
+
+ if ((matchingFromComponent = existingComponentLookup[toComponent.id]) === undefined) {
+ if (isRerenderInBrowser === true) {
+ var firstVChild = curToNodeChild.___firstChild;
+ if (firstVChild) {
+ if (!curFromNodeChild) {
+ curFromNodeChild = insertBefore(createMarkerComment(), null, parentFromNode);
+ }
+
+ toComponent.___startNode = curFromNodeChild;
+ toComponent.___endNode = resolveComponentEndNode(curFromNodeChild, firstVChild, parentFromNode);
+
+ } else {
+ toComponent.___startNode = toComponent.___endNode = insertBefore(createMarkerComment(), curFromNodeChild, parentFromNode);
+ }
+
+ curFromNodeChild = morphComponent(parentFromNode, toComponent, curToNodeChild);
+ } else {
+ insertVirtualComponentBefore(curToNodeChild, curFromNodeChild, parentFromNode);
+ }
+ } else {
+ if (matchingFromComponent.___startNode !== curFromNodeChild) {
+ if (curFromNodeChild &&
+ (fromComponent = curFromNodeChild.___markoComponent) &&
+ globalComponentsContext.___renderedComponentsById[fromComponent.id] === undefined) {
+
+ // The component associated with the current real DOM node was not rendered
+ // so we should just remove it out of the real DOM by destroying it
+ curFromNodeChild = fromComponent.___endNode.nextSibling;
+ destroyComponent(fromComponent);
+ continue;
+ }
+
+ // We need to move the existing component into
+ // the correct location
+ insertBefore(matchingFromComponent.___detach(), curFromNodeChild, parentFromNode);
+ }
+
+ if (curToNodeChild.___preserve) {
+ curFromNodeChild = matchingFromComponent.___endNode.nextSibling;
+ } else {
+ curFromNodeChild = morphComponent(parentFromNode, toComponent, curToNodeChild);
+ }
}
- }
- if (onBeforeElUpdated(fromEl, toElKey, context) === true) {
- return;
- }
+ curToNodeChild = toNextSibling;
+ continue;
+ } else if ((curToNodeKey = curToNodeChild.___key)) {
+ curVFromNodeChild = undefined;
+ curFromNodeKey = undefined;
- morphAttrs(fromEl, toEl);
- }
+ // We have a keyed element. This is the fast path for matching
+ // up elements
+ curToNodeKey = keySequence.___nextKey(curToNodeKey);
- if (onBeforeElChildrenUpdated(fromEl, toElKey, context) === true) {
- return;
- }
+ if (curFromNodeChild) {
+ if (curFromNodeChild !== endNode) {
+ curFromNodeKey = curFromNodeChild.___markoKey;
+ curVFromNodeChild = curFromNodeChild.___markoVElement;
+ fromNextSibling = curFromNodeChild.nextSibling;
+ }
+ }
- if (nodeName !== 'TEXTAREA') {
- var curToNodeChild = toEl.___firstChild;
- var curFromNodeChild = fromEl.firstChild;
- var curToNodeKey;
- var curFromNodeKey;
-
- var fromNextSibling;
- var toNextSibling;
- var matchingFromEl;
-
- outer: while (curToNodeChild) {
- toNextSibling = curToNodeChild.___nextSibling;
- curToNodeKey = curToNodeChild.id;
-
- while (curFromNodeChild) {
- fromNextSibling = curFromNodeChild.nextSibling;
-
- curFromNodeKey = curFromNodeChild.id;
-
- var curFromNodeType = curFromNodeChild.nodeType;
-
- var isCompatible = undefined;
-
- if (curFromNodeType === curToNodeChild.___nodeType) {
- if (curFromNodeType === ELEMENT_NODE) {
- // Both nodes being compared are Element nodes
-
- if (curToNodeKey) {
- // The target node has a key so we want to match it up with the correct element
- // in the original DOM tree
- if (curToNodeKey !== curFromNodeKey) {
- // The current element in the original DOM tree does not have a matching key so
- // let's check our lookup to see if there is a matching element in the original
- // DOM tree
- if ((matchingFromEl = getElementById(doc, curToNodeKey))) {
- if (curFromNodeChild.nextSibling === matchingFromEl) {
- // Special case for single element removals. To avoid removing the original
- // DOM node out of the tree (since that can break CSS transitions, etc.),
- // we will instead discard the current node and wait until the next
- // iteration to properly match up the keyed target element with its matching
- // element in the original tree
- isCompatible = false;
- } else {
- // We found a matching keyed element somewhere in the original DOM tree.
- // Let's moving the original DOM node into the current position and morph
- // it.
-
- // NOTE: We use insertBefore instead of replaceChild because we want to go through
- // the `removeNode()` function for the node that is being discarded so that
- // all lifecycle hooks are correctly invoked
+ if (curFromNodeKey === curToNodeKey) {
+ // Elements line up. Now we just have to make sure they are compatible
+ if ((curToNodeChild.___flags & FLAG_PRESERVE) === 0) {
+ // We just skip over the fromNode if it is preserved
- fromEl.insertBefore(matchingFromEl, curFromNodeChild);
+ if (compareNodeNames(curToNodeChild, curVFromNodeChild)) {
+ morphEl(curFromNodeChild, curVFromNodeChild, curToNodeChild, component, keySequence);
+ } else {
+ // Remove the old node
+ detachNode(curFromNodeChild, parentFromNode, component);
- fromNextSibling = curFromNodeChild.nextSibling;
- removalList.push(curFromNodeChild);
-
- curFromNodeChild = matchingFromEl;
- }
- } else {
- // The nodes are not compatible since the "to" node has a key and there
- // is no matching keyed node in the source tree
- isCompatible = false;
- }
- }
- } else if (curFromNodeKey) {
- // The original has a key
- isCompatible = false;
- }
-
- isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild) === true;
-
- if (isCompatible === true) {
- // We found compatible DOM elements so transform
- // the current "from" node to match the current
- // target DOM node.
- morphEl(curFromNodeChild, curToNodeChild, false);
- }
-
- } else if (curFromNodeType === TEXT_NODE || curFromNodeType === COMMENT_NODE) {
- // Both nodes being compared are Text or Comment nodes
- isCompatible = true;
- // Simply update nodeValue on the original node to
- // change the text value
- curFromNodeChild.nodeValue = curToNodeChild.___nodeValue;
+ // Incompatible nodes. Just move the target VNode into the DOM at this position
+ insertVirtualNodeBefore(curToNodeChild, curToNodeKey, curFromNodeChild, parentFromNode, component, keySequence);
}
}
-
- if (isCompatible === true) {
- // Advance both the "to" child and the "from" child since we found a match
- curToNodeChild = toNextSibling;
- curFromNodeChild = fromNextSibling;
- continue outer;
- }
-
- // No compatible match so remove the old node from the DOM and continue trying to find a
- // match in the original DOM. However, we only do this if the from node is not keyed
- // since it is possible that a keyed node might match up with a node somewhere else in the
- // target tree and we don't want to discard it just yet since it still might find a
- // home in the final DOM tree. After everything is done we will remove any keyed nodes
- // that didn't find a home
- removalList.push(curFromNodeChild);
-
- curFromNodeChild = fromNextSibling;
- }
-
- // If we got this far then we did not find a candidate match for
- // our "to node" and we exhausted all of the children "from"
- // nodes. Therefore, we will just append the current "to" node
- // to the end
- if (curToNodeKey && (matchingFromEl = getElementById(doc, curToNodeKey)) && compareNodeNames(matchingFromEl, curToNodeChild)) {
- fromEl.appendChild(matchingFromEl);
- morphEl(matchingFromEl, curToNodeChild, false);
} else {
- addVirtualNode(curToNodeChild, fromEl);
+ if ((matchingFromEl = component.___keyedElements[curToNodeKey]) === undefined) {
+ if (isRerenderInBrowser === true && curFromNodeChild &&
+ curFromNodeChild.nodeType === ELEMENT_NODE &&
+ curFromNodeChild.nodeName === curToNodeChild.___nodeName) {
+ curVFromNodeChild = virtualizeElement(curFromNodeChild);
+ curFromNodeChild.___markoKey = curToNodeKey;
+ morphEl(curFromNodeChild, curVFromNodeChild, curToNodeChild, component, keySequence);
+ curToNodeChild = toNextSibling;
+ curFromNodeChild = fromNextSibling;
+ continue;
+ }
+
+ insertVirtualNodeBefore(curToNodeChild, curToNodeKey, curFromNodeChild, parentFromNode, component, keySequence);
+ fromNextSibling = curFromNodeChild;
+ } else {
+ if (matchingFromEl.___markoDetached !== undefined) {
+ matchingFromEl.___markoDetached = undefined;
+ }
+ curVFromNodeChild = matchingFromEl.___markoVElement;
+
+ if (compareNodeNames(curVFromNodeChild, curToNodeChild)) {
+ if (fromNextSibling === matchingFromEl) {
+ // Single element removal:
+ // A <-> A
+ // B <-> C <-- We are here
+ // C D
+ // D
+ //
+ // Single element swap:
+ // A <-> A
+ // B <-> C <-- We are here
+ // C B
+
+ if (toNextSibling && toNextSibling.___key === curFromNodeKey) {
+ // Single element swap
+
+ // We want to stay on the current real DOM node
+ fromNextSibling = curFromNodeChild;
+
+ // But move the matching element into place
+ insertBefore(matchingFromEl, curFromNodeChild, parentFromNode);
+ } else {
+ // Single element removal
+
+ // We need to remove the current real DOM node
+ // and the matching real DOM node will fall into
+ // place. We will continue diffing with next sibling
+ // after the real DOM node that just fell into place
+ fromNextSibling = fromNextSibling.nextSibling;
+
+ if (curFromNodeChild) {
+ detachNode(curFromNodeChild, parentFromNode, component);
+ }
+ }
+
+ } else {
+ // A <-> A
+ // B <-> D <-- We are here
+ // C
+ // D
+
+ // We need to move the matching node into place
+ insertAfter(matchingFromEl, curFromNodeChild, parentFromNode);
+
+ if (curFromNodeChild) {
+ detachNode(curFromNodeChild, parentFromNode, component);
+ }
+ }
+
+ if ((curToNodeChild.___flags & FLAG_PRESERVE) === 0) {
+ morphEl(matchingFromEl, curVFromNodeChild, curToNodeChild, component, keySequence);
+ }
+
+
+ } else {
+ insertVirtualNodeBefore(curToNodeChild, curToNodeKey, curFromNodeChild, parentFromNode, component, keySequence);
+ detachNode(matchingFromEl, parentFromNode, component);
+ }
+ }
}
curToNodeChild = toNextSibling;
curFromNodeChild = fromNextSibling;
+ continue;
}
- // We have processed all of the "to nodes". If curFromNodeChild is
- // non-null then we still have some from nodes left over that need
- // to be removed
- while (curFromNodeChild) {
- removalList.push(curFromNodeChild);
- curFromNodeChild = curFromNodeChild.nextSibling;
+ // The know the target node is not a VComponent node and we know
+ // it is also not a preserve node. Let's now match up the HTML
+ // element, text node, comment, etc.
+ while (curFromNodeChild && curFromNodeChild !== endNode) {
+ if ((fromComponent = curFromNodeChild.___markoComponent) && fromComponent !== component) {
+ // The current "to" element is not associated with a component,
+ // but the current "from" element is associated with a component
+
+ // Even if we destroy the current component in the original
+ // DOM or not, we still need to skip over it since it is
+ // not compatible with the current "to" node
+ curFromNodeChild = fromComponent.___endNode.nextSibling;
+
+ if (!globalComponentsContext.___renderedComponentsById[fromComponent.id]) {
+ destroyComponent(fromComponent);
+ }
+
+ continue; // Move to the next "from" node
+ }
+
+ fromNextSibling = curFromNodeChild.nextSibling;
+
+ var curFromNodeType = curFromNodeChild.nodeType;
+
+ var isCompatible = undefined;
+
+ if (curFromNodeType === curToNodeType) {
+ if (curFromNodeType === ELEMENT_NODE) {
+ // Both nodes being compared are Element nodes
+ curVFromNodeChild = curFromNodeChild.___markoVElement;
+ if (curVFromNodeChild === undefined) {
+ if (isRerenderInBrowser === true) {
+ curVFromNodeChild = virtualizeElement(curFromNodeChild);
+ } else {
+ // Skip over nodes that don't look like ours...
+ curFromNodeChild = fromNextSibling;
+ continue;
+ }
+ } else if ((curFromNodeKey = curVFromNodeChild.___key)) {
+ // We have a keyed element here but our target VDOM node
+ // is not keyed so this not doesn't belong
+ isCompatible = false;
+ }
+
+ isCompatible = isCompatible !== false && compareNodeNames(curVFromNodeChild, curToNodeChild) === true;
+
+ if (isCompatible === true) {
+ // We found compatible DOM elements so transform
+ // the current "from" node to match the current
+ // target DOM node.
+ morphEl(curFromNodeChild, curVFromNodeChild, curToNodeChild, component, keySequence);
+ }
+
+ } else if (curFromNodeType === TEXT_NODE || curFromNodeType === COMMENT_NODE) {
+ // Both nodes being compared are Text or Comment nodes
+ isCompatible = true;
+ // Simply update nodeValue on the original node to
+ // change the text value
+ curFromNodeChild.nodeValue = curToNodeChild.___nodeValue;
+ }
+ }
+
+ if (isCompatible === true) {
+ // Advance both the "to" child and the "from" child since we found a match
+ curToNodeChild = toNextSibling;
+ curFromNodeChild = fromNextSibling;
+ continue outer;
+ }
+
+ if (curFromNodeKey) {
+ if (globalComponentsContext.___preservedEls[curFromNodeKey] === undefined) {
+ detachNode(curFromNodeChild, parentFromNode, component);
+ }
+ } else {
+ detachNode(curFromNodeChild, parentFromNode, component);
+ }
+
+ curFromNodeChild = fromNextSibling;
+ } // END: while (curFromNodeChild)
+
+ // If we got this far then we did not find a candidate match for
+ // our "to node" and we exhausted all of the children "from"
+ // nodes. Therefore, we will just append the current "to" node
+ // to the end
+ insertVirtualNodeBefore(curToNodeChild, curToNodeKey, curFromNodeChild, parentFromNode, component, keySequence);
+
+ curToNodeChild = toNextSibling;
+ curFromNodeChild = fromNextSibling;
+ }
+
+ // We have processed all of the "to nodes". If curFromNodeChild is
+ // non-null then we still have some from nodes left over that need
+ // to be removed
+ while (curFromNodeChild && (endNode === null || curFromNodeChild !== endNode)) {
+ fromNextSibling = curFromNodeChild.nextSibling;
+
+ if ((fromComponent = curFromNodeChild.___markoComponent)) {
+ if (globalComponentsContext.___renderedComponentsById[fromComponent.id]) {
+ // Skip over this component since it was rendered in the target VDOM
+ // and will be moved into place later
+ curFromNodeChild = fromComponent.___endNode.nextSibling;
+ continue;
+ }
}
+
+ detachNode(curFromNodeChild, parentFromNode, component);
+
+ curFromNodeChild = fromNextSibling;
+ }
+ }
+
+ function morphEl(fromEl, vFromEl, toEl, component, keySequence) {
+ var toElKey = toEl.___key;
+ var nodeName = toEl.___nodeName;
+
+ component = toEl.___component || component;
+
+ if (isRerenderInBrowser === true && toElKey) {
+ component.___keyedElements[toElKey] = fromEl;
+ }
+
+ var constId = toEl.___constId;
+ if (constId !== undefined && vFromEl.___constId === constId) {
+ return;
+ }
+
+ morphAttrs(fromEl, vFromEl, toEl);
+
+ if (toElKey && globalComponentsContext.___preservedElBodies[toElKey] === true) {
+ // Don't morph the children since they are preserved
+ return;
+ }
+
+ if (nodeName !== 'TEXTAREA') {
+ morphChildren(fromEl, fromEl.firstChild, null, toEl, component, keySequence);
}
var specialElHandler = specialElHandlers[nodeName];
- if (specialElHandler) {
+ if (specialElHandler !== undefined) {
specialElHandler(fromEl, toEl);
}
} // END: morphEl(...)
- var morphedNode = fromNode;
- var fromNodeType = morphedNode.nodeType;
- var toNodeType = toNode.___nodeType;
- var morphChildrenOnly = false;
- var shouldMorphEl = true;
- var newNode;
+ morphChildren(parentNode, startNode, endNode, toNode);
- // Handle the case where we are given two DOM nodes that are not
- // compatible (e.g. -->
or --> TEXT)
- if (fromNodeType == ELEMENT_NODE) {
- if (toNodeType == ELEMENT_NODE) {
- if (!compareNodeNames(fromNode, toNode)) {
- newNode = toNode.___actualize(doc);
- morphChildrenOnly = true;
- removalList.push(fromNode);
- }
- } else {
- // Going from an element node to a text or comment node
- removalList.push(fromNode);
- newNode = toNode.___actualize(doc);
- shouldMorphEl = false;
- }
- } else if (fromNodeType == TEXT_NODE || fromNodeType == COMMENT_NODE) { // Text or comment node
- if (toNodeType == fromNodeType) {
- morphedNode.nodeValue = toNode.___nodeValue;
- return morphedNode;
- } else {
- // Text node to something else
- removalList.push(fromNode);
- newNode = addVirtualNode(toNode);
- shouldMorphEl = false;
- }
- }
+ detachedNodes.forEach(function(node) {
+ var detachedFromComponent = node.___markoDetached;
- if (shouldMorphEl === true) {
- morphEl(newNode || morphedNode, toNode, morphChildrenOnly);
- }
+ if (detachedFromComponent !== undefined) {
+ node.___markoDetached = undefined;
- if (newNode) {
- if (fromNode.parentNode) {
- fromNode.parentNode.replaceChild(newNode, fromNode);
- }
- }
+ destroyComponentForNode(node);
+ destroyNodeRecursive(node, detachedFromComponent !== true && detachedFromComponent);
- // We now need to loop over any keyed nodes that might need to be
- // removed. We only do the removal if we know that the keyed node
- // never found a match. When a keyed node is matched up we remove
- // it out of fromNodesLookup and we use fromNodesLookup to determine
- // if a keyed node has been matched up or not
- for (var i=0, len=removalList.length; i
= 0; --i) {
- var attr = oldAttributesList[i];
-
- if (attr.specified !== false) {
- attrName = attr.name;
- if (attrName !== 'data-marko') {
- var attrNamespaceURI = attr.namespaceURI;
- if (attrNamespaceURI === NS_XLINK) {
- oldAttrs[ATTR_XLINK_HREF] = attr.value;
- } else {
- oldAttrs[attrName] = attr.value;
- }
- }
- }
- }
-
- // We don't want preserved attributes to show up in either the old
- // or new attribute map.
- removePreservedAttributes(oldAttrs, props, false);
}
- fromEl._vattrs = attrs;
-
var attrValue;
- var flags = toEl.___flags;
- var oldFlags;
+ var toFlags = toEl.___flags;
- if (flags & FLAG_SIMPLE_ATTRS && ((oldFlags = fromEl._vflags) & FLAG_SIMPLE_ATTRS)) {
+
+ if (toFlags & FLAG_SIMPLE_ATTRS && fromFlags & FLAG_SIMPLE_ATTRS) {
if (oldAttrs['class'] !== (attrValue = attrs['class'])) {
fromEl.className = attrValue;
}
@@ -309,6 +331,7 @@ VElement.___morphAttrs = function(fromEl, toEl) {
return;
}
+
// In some cases we only want to set an attribute value for the first
// render or we don't want certain attributes to be touched. To support
// that use case we delete out all of the preserved attributes
@@ -352,7 +375,7 @@ VElement.___morphAttrs = function(fromEl, toEl) {
// was not a virtualized node (i.e., a node that was not rendered by a
// Marko template, but rather a node that was created from an HTML
// string or a real DOM node).
- if (!attrs.id || props.___virtualized === true) {
+ if (toEl.___key === null) {
for (attrName in oldAttrs) {
if (!(attrName in attrs)) {
if (attrName === ATTR_XLINK_HREF) {
diff --git a/src/runtime/vdom/VNode.js b/src/runtime/vdom/VNode.js
index 01e161a9a..937a2c706 100644
--- a/src/runtime/vdom/VNode.js
+++ b/src/runtime/vdom/VNode.js
@@ -1,6 +1,4 @@
/* jshint newcap:false */
-var specialElHandlers = require('../../morphdom/specialElHandlers');
-
function VNode() {}
VNode.prototype = {
@@ -13,6 +11,8 @@ VNode.prototype = {
this.___nextSiblingInternal = null;
},
+ ___component: null,
+
get ___firstChild() {
var firstChild = this.___firstChildInternal;
@@ -49,10 +49,10 @@ VNode.prototype = {
___appendChild: function(child) {
this.___childCount++;
- if (this.___isTextArea) {
+ if (this.___isTextArea === true) {
if (child.___Text) {
var childValue = child.___nodeValue;
- this.___value = (this.___value || '') + childValue;
+ this.___valueInternal = (this.___valueInternal || '') + childValue;
} else {
throw TypeError();
}
@@ -74,32 +74,13 @@ VNode.prototype = {
},
___finishChild: function finishChild() {
- if (this.___childCount == this.___finalChildCount && this.___parentNode) {
+ if (this.___childCount === this.___finalChildCount && this.___parentNode) {
return this.___parentNode.___finishChild();
} else {
return this;
}
},
- actualize: function(doc) {
- var actualNode = this.___actualize(doc);
-
- var curChild = this.___firstChild;
-
- while(curChild) {
- actualNode.appendChild(curChild.actualize(doc));
- curChild = curChild.___nextSibling;
- }
-
- if (this.___nodeType === 1) {
- var elHandler = specialElHandlers[this.___nodeName];
- if (elHandler !== undefined) {
- elHandler(actualNode, this);
- }
- }
-
- return actualNode;
- }
// ,toJSON: function() {
// var clone = Object.assign({
diff --git a/src/runtime/vdom/helpers.js b/src/runtime/vdom/helpers.js
index 5561bf765..e6ae716b3 100644
--- a/src/runtime/vdom/helpers.js
+++ b/src/runtime/vdom/helpers.js
@@ -10,8 +10,8 @@ var extend = require('raptor-util/extend');
var classList = commonHelpers.cl;
var helpers = extend({
- e: function(tagName, attrs, childCount, flags, props) {
- return new VElement(tagName, attrs, childCount, flags, props);
+ e: function(tagName, attrs, key, component, childCount, flags, props) {
+ return new VElement(tagName, attrs, key, component, childCount, flags, props);
},
t: function(value) {
diff --git a/src/runtime/vdom/preserve-attrs.js b/src/runtime/vdom/preserve-attrs.js
index 6704b654a..314078786 100644
--- a/src/runtime/vdom/preserve-attrs.js
+++ b/src/runtime/vdom/preserve-attrs.js
@@ -1,11 +1,9 @@
var extend = require('raptor-util/extend');
-function removePreservedAttributes(attrs, props, clone) {
+function removePreservedAttributes(attrs, props) {
var preservedAttrs = props && props.noupdate;
if (preservedAttrs) {
- if (clone) {
- attrs = extend({}, attrs);
- }
+ attrs = extend({}, attrs);
preservedAttrs.forEach(function(preservedAttrName) {
delete attrs[preservedAttrName];
});
diff --git a/src/runtime/vdom/vdom.js b/src/runtime/vdom/vdom.js
index e13d938ac..111e533db 100644
--- a/src/runtime/vdom/vdom.js
+++ b/src/runtime/vdom/vdom.js
@@ -3,12 +3,11 @@ var VComment = require('./VComment');
var VDocumentFragment = require('./VDocumentFragment');
var VElement = require('./VElement');
var VText = require('./VText');
+var VComponent = require('./VComponent');
-var FLAG_IS_TEXTAREA = 2;
var defaultDocument = typeof document != 'undefined' && document;
var specialHtmlRegexp = /[&<]/;
-var xmlnsRegExp = /^xmlns(:|$)/;
-var virtualizedProps = { ___virtualized: true };
+
function virtualizeChildNodes(node, vdomParent) {
var curChild = node.firstChild;
@@ -18,44 +17,10 @@ function virtualizeChildNodes(node, vdomParent) {
}
}
-function virtualize(node) {
+function virtualize(node, shallow) {
switch(node.nodeType) {
case 1:
- var attributes = node.attributes;
- var attrCount = attributes.length;
-
- var attrs;
-
- if (attrCount) {
- attrs = {};
- for (var i=0; i": {
"code-generator": "./import-tag",
+ "no-output": true,
"parse-options": {
"relaxRequireCommas": true
}
@@ -135,7 +136,8 @@
{
"descriptionMoreURL": "http://markojs.com/docs/marko/language-guide/#macros"
}
- ]
+ ],
+ "no-output": true
},
"": {
"code-generator": "./macro-body-tag",
@@ -167,6 +169,7 @@
},
"": {
"code-generator": "./static-tag",
+ "no-output": true,
"parse-options": {
"ignoreAttributes": true
}
diff --git a/src/taglibs/html/marko.json b/src/taglibs/html/marko.json
index f1437d13a..0fbb84080 100644
--- a/src/taglibs/html/marko.json
+++ b/src/taglibs/html/marko.json
@@ -1127,7 +1127,8 @@
"html": true,
"attribute-groups": [
"html-attributes"
- ]
+ ],
+ "openTagOnly": true
},
"attribute-groups": {
"html-attributes": {
diff --git a/test/autotests/async-render/components-await-title/expected.html b/test/autotests/async-render/components-await-title/expected.html
index aeec1ed10..3b556a035 100644
--- a/test/autotests/async-render/components-await-title/expected.html
+++ b/test/autotests/async-render/components-await-title/expected.html
@@ -1 +1 @@
-Welcome Frank Hello
+Welcome Frank Hello
\ No newline at end of file
diff --git a/test/autotests/compiler-browser/dynamic-tag-name/expected.js b/test/autotests/compiler-browser/dynamic-tag-name/expected.js
index f4fa9a269..084c32b24 100644
--- a/test/autotests/compiler-browser/dynamic-tag-name/expected.js
+++ b/test/autotests/compiler-browser/dynamic-tag-name/expected.js
@@ -5,7 +5,7 @@ var marko_template = module.exports = require("marko/src/vdom").t();
function render(input, out) {
var data = input;
- out.ed(foo ? "foo" : "bar", null, 0);
+ out.ed(foo ? "foo" : "bar", null, null, null, 0);
}
marko_template._ = render;
diff --git a/test/autotests/compiler-browser/simple/expected.js b/test/autotests/compiler-browser/simple/expected.js
index 74b988d42..1728e7744 100644
--- a/test/autotests/compiler-browser/simple/expected.js
+++ b/test/autotests/compiler-browser/simple/expected.js
@@ -6,12 +6,12 @@ var marko_template = module.exports = require("marko/src/vdom").t(),
marko_createElement = marko_helpers.e,
marko_const = marko_helpers.const,
marko_const_nextId = marko_const("295cea"),
- marko_node0 = marko_createElement("DIV", null, 1, 0, {
- c: marko_const_nextId()
+ marko_node0 = marko_createElement("DIV", null, null, null, 1, 0, {
+ i: marko_const_nextId()
})
.t("No colors!"),
- marko_node1 = marko_createElement("DIV", null, 1, 0, {
- c: marko_const_nextId()
+ marko_node1 = marko_createElement("DIV", null, null, null, 1, 0, {
+ i: marko_const_nextId()
})
.t("No colors!");
@@ -28,7 +28,7 @@ function render(input, out) {
out.be("UL");
marko_forEach(input.colors, function(color) {
- out.e("LI", null, 1)
+ out.e("LI", null, null, null, 1)
.t(color);
});
@@ -41,7 +41,7 @@ function render(input, out) {
out.be("UL");
marko_forEach(input.colors, function(color) {
- out.e("LI", null, 1)
+ out.e("LI", null, null, null, 1)
.t(color);
});
diff --git a/test/autotests/compiler-browser/svg-anchor/expected.js b/test/autotests/compiler-browser/svg-anchor/expected.js
index b07a88e5a..affb7cd0f 100644
--- a/test/autotests/compiler-browser/svg-anchor/expected.js
+++ b/test/autotests/compiler-browser/svg-anchor/expected.js
@@ -8,13 +8,13 @@ var marko_template = module.exports = require("marko/src/vdom").t(),
marko_node0 = marko_createElement("svg", {
width: "140",
height: "30"
- }, 1, 1, {
- c: marko_const_nextId()
+ }, null, null, 1, 1, {
+ i: marko_const_nextId()
})
.e("a", {
"xlink:href": "https://developer.mozilla.org/en-US/docs/SVG",
target: "_blank"
- }, 0, 1);
+ }, null, null, 0, 1);
function render(input, out) {
var data = input;
diff --git a/test/autotests/compiler-browser/svg-dynamic-tag-name/expected.js b/test/autotests/compiler-browser/svg-dynamic-tag-name/expected.js
index 7cb14e5c5..2ab10723f 100644
--- a/test/autotests/compiler-browser/svg-dynamic-tag-name/expected.js
+++ b/test/autotests/compiler-browser/svg-dynamic-tag-name/expected.js
@@ -15,8 +15,8 @@ function render(input, out) {
var isCircle = true;
- out.e("svg", marko_attrs0, 1, 1)
- .e(isCircle ? "circle" : "square", marko_attrs1, 0, 1);
+ out.e("svg", marko_attrs0, null, null, 1, 1)
+ .e(isCircle ? "circle" : "square", marko_attrs1, null, null, 0, 1);
}
marko_template._ = render;
diff --git a/test/autotests/compiler-browser/svg/expected.js b/test/autotests/compiler-browser/svg/expected.js
index da0db4cea..8fac00093 100644
--- a/test/autotests/compiler-browser/svg/expected.js
+++ b/test/autotests/compiler-browser/svg/expected.js
@@ -8,14 +8,14 @@ var marko_template = module.exports = require("marko/src/vdom").t(),
marko_node0 = marko_createElement("svg", {
viewBox: "0 0 200 200",
xmlns: "http://www.w3.org/2000/svg"
- }, 1, 1, {
- c: marko_const_nextId()
+ }, null, null, 1, 1, {
+ i: marko_const_nextId()
})
.e("circle", {
cx: "100",
cy: "100",
r: "100"
- }, 0, 1);
+ }, null, null, 0, 1);
function render(input, out) {
var data = input;
diff --git a/test/autotests/components-browser/repeated-with-label-ref/index.marko b/test/autotests/components-browser-deprecated/for-key-repeated/index.marko
similarity index 90%
rename from test/autotests/components-browser/repeated-with-label-ref/index.marko
rename to test/autotests/components-browser-deprecated/for-key-repeated/index.marko
index a87bf72d7..6bf35bc95 100644
--- a/test/autotests/components-browser/repeated-with-label-ref/index.marko
+++ b/test/autotests/components-browser-deprecated/for-key-repeated/index.marko
@@ -2,11 +2,11 @@ class {
}
-
+
\ No newline at end of file
+
diff --git a/test/autotests/components-browser-deprecated/for-key-repeated/test.js b/test/autotests/components-browser-deprecated/for-key-repeated/test.js
new file mode 100644
index 000000000..8451257d3
--- /dev/null
+++ b/test/autotests/components-browser-deprecated/for-key-repeated/test.js
@@ -0,0 +1,31 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var fields = [
+ {
+ value: 'name',
+ label: 'Name'
+ },
+ {
+ value: 'age',
+ label: 'Age'
+ }
+ ];
+
+ var component = helpers.mount(require('./index'), {
+ fields: fields
+ });
+
+ var inputs = component.getEl('root').querySelectorAll('input');
+ var labels = component.getEl('root').querySelectorAll('label');
+
+ expect(inputs.length).to.equal(fields.length);
+
+ for (var i=0; i${input.count}
diff --git a/test/autotests/components-browser/diffpatch-boundary-inner-component-only/index.marko b/test/autotests/components-browser/diffpatch-boundary-inner-component-only/index.marko
new file mode 100644
index 000000000..c320bebf8
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-boundary-inner-component-only/index.marko
@@ -0,0 +1,9 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 0
+ }
+ }
+}
+
+
diff --git a/test/autotests/components-browser/diffpatch-boundary-inner-component-only/test.js b/test/autotests/components-browser/diffpatch-boundary-inner-component-only/test.js
new file mode 100644
index 000000000..f000b76e8
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-boundary-inner-component-only/test.js
@@ -0,0 +1,21 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var innerComponent = component.getComponent('inner');
+
+ expect(innerComponent.___startNode.className).to.equal('inner');
+ expect(component.___startNode).to.not.equal(innerComponent.___startNode);
+ expect(component.___endNode).to.not.equal(innerComponent.___endNode);
+ expect(helpers.targetEl.querySelector('.inner').innerHTML).to.equal('0');
+
+ component.state.count++;
+ component.update();
+
+ innerComponent = component.getComponent('inner');
+
+ expect(innerComponent.___startNode.className).to.equal('inner');
+ expect(component.___startNode).to.not.equal(innerComponent.___startNode);
+ expect(component.___endNode).to.not.equal(innerComponent.___endNode);
+ expect(helpers.targetEl.querySelector('.inner').innerHTML).to.equal('1');
+};
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch-append/components/bar/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch-append/components/bar/index.marko
new file mode 100644
index 000000000..c991c39bb
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch-append/components/bar/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+${input.name}
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch-append/components/foo/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch-append/components/foo/index.marko
new file mode 100644
index 000000000..ccb76c5f3
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch-append/components/foo/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+${input.name}
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch-append/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch-append/index.marko
new file mode 100644
index 000000000..9face8ab1
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch-append/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 0
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch-append/test.js b/test/autotests/components-browser/diffpatch-component-mismatch-append/test.js
new file mode 100644
index 000000000..bb602878f
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch-append/test.js
@@ -0,0 +1,21 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ var children;
+
+ children = component.getEl('root').children;
+ expect(children.length).to.equal(2);
+ expect(children[0].innerHTML).to.equal('foo-a');
+ expect(children[1].innerHTML).to.equal('bar-b');
+
+ component.state.count++;
+ component.update();
+
+ children = component.getEl('root').children;
+ expect(children.length).to.equal(3);
+ expect(children[0].innerHTML).to.equal('foo-a');
+ expect(children[1].innerHTML).to.equal('foo-b');
+ expect(children[2].innerHTML).to.equal('bar-c');
+};
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch/components/bar/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch/components/bar/index.marko
new file mode 100644
index 000000000..3aeda96a5
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch/components/bar/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+bar ${component.id}
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch/components/foo/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch/components/foo/index.marko
new file mode 100644
index 000000000..f8cf2747c
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch/components/foo/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+foo ${component.id}
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch/index.marko b/test/autotests/components-browser/diffpatch-component-mismatch/index.marko
new file mode 100644
index 000000000..8b50860b8
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 0
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-component-mismatch/test.js b/test/autotests/components-browser/diffpatch-component-mismatch/test.js
new file mode 100644
index 000000000..ce46d6e7e
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-mismatch/test.js
@@ -0,0 +1,16 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ var children;
+
+ children = component.getEl('root').children;
+ expect(children.length).to.equal(3);
+
+ component.state.count++;
+ component.update();
+
+ children = component.getEl('root').children;
+ expect(children.length).to.equal(2);
+};
diff --git a/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/bar/index.marko b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/bar/index.marko
new file mode 100644
index 000000000..0da5dadae
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/bar/index.marko
@@ -0,0 +1,16 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 1
+ }
+ }
+
+ increment() {
+ this.state.count++;
+ }
+}
+
+
+
+ ${i}
+
diff --git a/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/foo/index.marko b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/foo/index.marko
new file mode 100644
index 000000000..5b678b3d0
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/components/foo/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+foo ${component.id}
diff --git a/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/index.marko b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/index.marko
new file mode 100644
index 000000000..583b55637
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/index.marko
@@ -0,0 +1,12 @@
+class {
+ incrementBar() {
+ this.getComponent('bar').increment();
+ this.getComponent('bar').update();
+ }
+}
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/test.js b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/test.js
new file mode 100644
index 000000000..ba78313e0
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-component-toplevel-surrounded/test.js
@@ -0,0 +1,10 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ component.incrementBar();
+
+ var children = component.getEl('root').children;
+ expect(children[children.length-1].nodeName).to.equal('SPAN');
+};
diff --git a/test/autotests/components-browser/diffpatch-destroy-child/components/hello/index.marko b/test/autotests/components-browser/diffpatch-destroy-child/components/hello/index.marko
new file mode 100644
index 000000000..34c5c995f
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-destroy-child/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello${input.count}
diff --git a/test/autotests/components-browser/diffpatch-destroy-child/index.marko b/test/autotests/components-browser/diffpatch-destroy-child/index.marko
new file mode 100644
index 000000000..0c06259d6
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-destroy-child/index.marko
@@ -0,0 +1,15 @@
+class {
+ onCreate() {
+ this.state = {
+ count: 0,
+ renderHello: true
+ };
+ }
+}
+
+
+ [ROOT]
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-destroy-child/test.js b/test/autotests/components-browser/diffpatch-destroy-child/test.js
new file mode 100644
index 000000000..350d682af
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-destroy-child/test.js
@@ -0,0 +1,21 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+ var helloCountEl = rootEl.querySelector('span.hello-count');
+ var helloComponent = component.getComponent('hello');
+
+ component.state.count = 1;
+ component.update();
+
+ expect(component.el).to.equal(rootEl);
+ expect(rootEl.querySelector('span.hello-count').innerHTML).to.equal('1');
+ expect(rootEl.querySelector('span.hello-count')).to.equal(helloCountEl);
+
+ component.state.renderHello = false;
+ component.update();
+
+ expect(helloComponent.isDestroyed()).to.equal(true);
+ expect(helloComponent.el == null).to.equal(true);
+};
diff --git a/test/autotests/components-browser/diffpatch-insert-el-before-component/components/hello/index.marko b/test/autotests/components-browser/diffpatch-insert-el-before-component/components/hello/index.marko
new file mode 100644
index 000000000..285be66ab
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-el-before-component/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-insert-el-before-component/index.marko b/test/autotests/components-browser/diffpatch-insert-el-before-component/index.marko
new file mode 100644
index 000000000..2c560f54b
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-el-before-component/index.marko
@@ -0,0 +1,21 @@
+class {
+ onCreate() {
+ this.state = {
+ insertEl: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+ New element
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-insert-el-before-component/test.js b/test/autotests/components-browser/diffpatch-insert-el-before-component/test.js
new file mode 100644
index 000000000..915f2df74
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-el-before-component/test.js
@@ -0,0 +1,17 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ // var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+ // expect(helloComponent.el.parentNode).to.equal(rootEl);
+
+ component.state.insertEl = true;
+ component.state.count++;
+ component.update();
+
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+};
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/components/hello/index.marko b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/components/hello/index.marko
new file mode 100644
index 000000000..285be66ab
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/index.marko b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/index.marko
new file mode 100644
index 000000000..8f94ed243
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ insertEl: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+ $!{'
'}
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/test.js b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/test.js
new file mode 100644
index 000000000..915f2df74
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-component/test.js
@@ -0,0 +1,17 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ // var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+ // expect(helloComponent.el.parentNode).to.equal(rootEl);
+
+ component.state.insertEl = true;
+ component.state.count++;
+ component.update();
+
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+};
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/components/hello/index.marko b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/components/hello/index.marko
new file mode 100644
index 000000000..285be66ab
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/index.marko b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/index.marko
new file mode 100644
index 000000000..826e713d9
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ insertEl: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+ $!{'
'}
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/test.js b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/test.js
new file mode 100644
index 000000000..915f2df74
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-insert-unkeyed-el-before-preserved-component/test.js
@@ -0,0 +1,17 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ // var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+ // expect(helloComponent.el.parentNode).to.equal(rootEl);
+
+ component.state.insertEl = true;
+ component.state.count++;
+ component.update();
+
+ expect(component.getEl('root').querySelector('.hello') != null).to.equal(true);
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+};
diff --git a/test/autotests/components-browser/diffpatch-rearrange-keyed-components/components/hello/index.marko b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/components/hello/index.marko
new file mode 100644
index 000000000..aab3a0e19
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/components/hello/index.marko
@@ -0,0 +1,3 @@
+class {}
+$ var value = input.value;
+${value}
diff --git a/test/autotests/components-browser/diffpatch-rearrange-keyed-components/index.marko b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/index.marko
new file mode 100644
index 000000000..e9eee07ae
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/index.marko
@@ -0,0 +1,9 @@
+class {
+}
+
+
+
Nested components:
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-rearrange-keyed-components/test.js b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/test.js
new file mode 100644
index 000000000..c0af89149
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-rearrange-keyed-components/test.js
@@ -0,0 +1,96 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), {});
+
+ var previousComponents = {};
+
+ function checkOrder(letters) {
+ component.input = { letters: letters };
+ component.update();
+
+ var divs = component.getEl('root').querySelectorAll('div');
+
+ expect(divs.length).to.equal(letters.length);
+
+ var newComponents = {};
+
+ for(var i=0; i
+ ${letter}
+
diff --git a/test/autotests/components-browser/diffpatch-rearrange-keyed-els/test.js b/test/autotests/components-browser/diffpatch-rearrange-keyed-els/test.js
new file mode 100644
index 000000000..ff80815e5
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-rearrange-keyed-els/test.js
@@ -0,0 +1,69 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), {});
+ expect(helpers.targetEl.querySelectorAll('div').length).to.equal(0);
+
+ function checkOrder(letters) {
+ component.input = { letters: letters };
+ component.update();
+
+ var divs = helpers.targetEl.querySelectorAll('div');
+
+ expect(divs.length).to.equal(letters.length);
+
+ for(var i=0; i
+ Header
+ Body
+
diff --git a/test/autotests/components-browser/diffpatch-remove-all-els/test.js b/test/autotests/components-browser/diffpatch-remove-all-els/test.js
new file mode 100644
index 000000000..617564fc0
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-all-els/test.js
@@ -0,0 +1,16 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ expect(helpers.targetEl.querySelector('h1') != null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+
+ component.state.show = false;
+ component.update();
+
+ expect(helpers.targetEl.querySelector('h1') == null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') == null).to.equal(true);
+
+ expect(component.isDestroyed()).to.equal(false);
+};
diff --git a/test/autotests/components-browser/diffpatch-remove-end-el/index.marko b/test/autotests/components-browser/diffpatch-remove-end-el/index.marko
new file mode 100644
index 000000000..c8be2bb7c
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-end-el/index.marko
@@ -0,0 +1,12 @@
+class {
+ onCreate() {
+ this.state = {
+ showFooter: true
+ };
+ }
+}
+
+Body
+
diff --git a/test/autotests/components-browser/diffpatch-remove-end-el/test.js b/test/autotests/components-browser/diffpatch-remove-end-el/test.js
new file mode 100644
index 000000000..6fca56434
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-end-el/test.js
@@ -0,0 +1,14 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ expect(helpers.targetEl.querySelector('footer') != null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+
+ component.state.showFooter = false;
+ component.update();
+
+ expect(helpers.targetEl.querySelector('footer') == null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+};
diff --git a/test/autotests/components-browser/diffpatch-remove-start-el/index.marko b/test/autotests/components-browser/diffpatch-remove-start-el/index.marko
new file mode 100644
index 000000000..8eea05059
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-start-el/index.marko
@@ -0,0 +1,12 @@
+class {
+ onCreate() {
+ this.state = {
+ showHeader: true
+ };
+ }
+}
+
+
+ Header
+
+Body
diff --git a/test/autotests/components-browser/diffpatch-remove-start-el/test.js b/test/autotests/components-browser/diffpatch-remove-start-el/test.js
new file mode 100644
index 000000000..a4c277797
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-remove-start-el/test.js
@@ -0,0 +1,14 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ expect(helpers.targetEl.querySelector('h1') != null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+
+ component.state.showHeader = false;
+ component.update();
+
+ expect(helpers.targetEl.querySelector('h1') == null).to.equal(true);
+ expect(helpers.targetEl.querySelector('div') != null).to.equal(true);
+};
diff --git a/test/autotests/components-browser/diffpatch-simple/index.marko b/test/autotests/components-browser/diffpatch-simple/index.marko
new file mode 100644
index 000000000..44e7a2bdf
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-simple/index.marko
@@ -0,0 +1,13 @@
+class MyComponent {
+ onCreate() {
+ this.state = {
+ count: 0
+ };
+ }
+}
+
+
+
+ ${state.count}
+
+
diff --git a/test/autotests/components-browser/diffpatch-simple/test.js b/test/autotests/components-browser/diffpatch-simple/test.js
new file mode 100644
index 000000000..a6951610b
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-simple/test.js
@@ -0,0 +1,14 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+ var spanEl = rootEl.querySelector('span');
+
+ component.state.count = 1;
+ component.update();
+
+ expect(component.el).to.equal(rootEl);
+ expect(rootEl.querySelector('span').innerHTML).to.equal('1');
+ expect(rootEl.querySelector('span')).to.equal(spanEl);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/hello/index.marko b/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/hello/index.marko
new file mode 100644
index 000000000..44a3156d1
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/world/index.marko b/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/world/index.marko
new file mode 100644
index 000000000..d72af3219
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-dynamic/components/world/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-dynamic/index.marko b/test/autotests/components-browser/diffpatch-swap-components-dynamic/index.marko
new file mode 100644
index 000000000..f444872ad
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-dynamic/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-components-dynamic/test.js b/test/autotests/components-browser/diffpatch-swap-components-dynamic/test.js
new file mode 100644
index 000000000..66e5151b8
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-dynamic/test.js
@@ -0,0 +1,24 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+
+ var nestedDivs = rootEl.querySelectorAll('div');
+ expect(nestedDivs[0].className).to.equal('hello');
+ expect(nestedDivs[1].className).to.equal('world');
+ expect(nestedDivs[0].innerHTML).to.equal('0');
+ expect(nestedDivs[1].innerHTML).to.equal('0');
+
+ component.state.swapped = true;
+ component.state.count = 1;
+ component.update();
+
+ var nestedDivsAfter = rootEl.querySelectorAll('div');
+ expect(nestedDivsAfter[0].className).to.equal('world');
+ expect(nestedDivsAfter[1].className).to.equal('hello');
+ expect(nestedDivsAfter[0].innerHTML).to.equal('1');
+ expect(nestedDivsAfter[1].innerHTML).to.equal('1');
+ expect(nestedDivsAfter[0]).to.not.equal(nestedDivs[1]);
+ expect(nestedDivsAfter[1]).to.not.equal(nestedDivs[0]);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/hello/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/hello/index.marko
new file mode 100644
index 000000000..44a3156d1
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/world/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/world/index.marko
new file mode 100644
index 000000000..d72af3219
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/components/world/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/index.marko
new file mode 100644
index 000000000..bb9cda28e
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/index.marko
@@ -0,0 +1,19 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false,
+ count: 0
+ };
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/test.js b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/test.js
new file mode 100644
index 000000000..0c952b174
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed-dynamic/test.js
@@ -0,0 +1,32 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ var worldComponent = component.getComponent('world');
+ expect(helloComponent.el.parentNode).to.equal(rootEl);
+ expect(worldComponent.el.parentNode).to.equal(rootEl);
+
+ var nestedDivs = rootEl.querySelectorAll('div');
+ expect(nestedDivs[0].className).to.equal('hello');
+ expect(nestedDivs[1].className).to.equal('world');
+ expect(nestedDivs[0].innerHTML).to.equal('0');
+ expect(nestedDivs[1].innerHTML).to.equal('0');
+
+ component.state.swapped = true;
+ component.state.count = 1;
+ component.update();
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+ expect(component.getComponent('world')).to.equal(worldComponent);
+
+ var nestedDivsAfter = rootEl.querySelectorAll('div');
+ expect(nestedDivsAfter[0].className).to.equal('world');
+ expect(nestedDivsAfter[1].className).to.equal('hello');
+ expect(nestedDivs[0].innerHTML).to.equal('1');
+ expect(nestedDivs[1].innerHTML).to.equal('1');
+
+ expect(nestedDivsAfter[0]).to.equal(nestedDivs[1]);
+ expect(nestedDivsAfter[1]).to.equal(nestedDivs[0]);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed/components/hello/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed/components/hello/index.marko
new file mode 100644
index 000000000..8d6867d77
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed/components/hello/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+Hello: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed/components/world/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed/components/world/index.marko
new file mode 100644
index 000000000..556f14608
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed/components/world/index.marko
@@ -0,0 +1,5 @@
+class {
+
+}
+
+World: ${input.count}
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed/index.marko b/test/autotests/components-browser/diffpatch-swap-components-keyed/index.marko
new file mode 100644
index 000000000..5f75fff2e
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed/index.marko
@@ -0,0 +1,18 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false
+ };
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-components-keyed/test.js b/test/autotests/components-browser/diffpatch-swap-components-keyed/test.js
new file mode 100644
index 000000000..c22e4e10c
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-components-keyed/test.js
@@ -0,0 +1,27 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var rootEl = component.el;
+ var helloComponent = component.getComponent('hello');
+ var worldComponent = component.getComponent('world');
+ expect(helloComponent.el.parentNode).to.equal(rootEl);
+ expect(worldComponent.el.parentNode).to.equal(rootEl);
+
+ var nestedDivs = rootEl.querySelectorAll('div');
+ expect(nestedDivs[0].className).to.equal('hello');
+ expect(nestedDivs[1].className).to.equal('world');
+
+ component.state.swapped = true;
+ component.update();
+
+ expect(component.getComponent('hello')).to.equal(helloComponent);
+ expect(component.getComponent('world')).to.equal(worldComponent);
+
+ var nestedDivsAfter = rootEl.querySelectorAll('div');
+ expect(nestedDivsAfter[0].className).to.equal('world');
+ expect(nestedDivsAfter[1].className).to.equal('hello');
+
+ expect(nestedDivsAfter[0]).to.equal(nestedDivs[1]);
+ expect(nestedDivsAfter[1]).to.equal(nestedDivs[0]);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-keyed-el/index.marko b/test/autotests/components-browser/diffpatch-swap-keyed-el/index.marko
new file mode 100644
index 000000000..fe6606330
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-keyed-el/index.marko
@@ -0,0 +1,18 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false
+ }
+ }
+}
+
+
+
+ bar
+ foo
+
+
+ foo
+ bar
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-keyed-el/test.js b/test/autotests/components-browser/diffpatch-swap-keyed-el/test.js
new file mode 100644
index 000000000..82f943243
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-keyed-el/test.js
@@ -0,0 +1,18 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+
+ var fooEl = component.getEl('foo');
+ var barEl = component.getEl('bar');
+
+ expect(fooEl.nextSibling).to.equal(barEl);
+
+ component.state.swapped = true;
+ component.update();
+
+ expect(component.getEl('foo')).to.equal(fooEl);
+ expect(component.getEl('bar')).to.equal(barEl);
+
+ expect(barEl.nextSibling).to.equal(fooEl);
+};
diff --git a/test/autotests/components-browser/diffpatch-swap-unkeyed-el/index.marko b/test/autotests/components-browser/diffpatch-swap-unkeyed-el/index.marko
new file mode 100644
index 000000000..e89dd1feb
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-unkeyed-el/index.marko
@@ -0,0 +1,18 @@
+class {
+ onCreate() {
+ this.state = {
+ swapped: false
+ }
+ }
+}
+
+
+
+ bar
+ foo
+
+
+ foo
+ bar
+
+
diff --git a/test/autotests/components-browser/diffpatch-swap-unkeyed-el/test.js b/test/autotests/components-browser/diffpatch-swap-unkeyed-el/test.js
new file mode 100644
index 000000000..7b6916948
--- /dev/null
+++ b/test/autotests/components-browser/diffpatch-swap-unkeyed-el/test.js
@@ -0,0 +1,26 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ var children = component.getEl('root').children;
+ expect(children.length).to.equal(2);
+
+ expect(children[0].dataset.foo).to.equal('true');
+ expect(children[0].dataset.bar).to.equal(undefined);
+
+ expect(children[1].dataset.foo).to.equal(undefined);
+ expect(children[1].dataset.bar).to.equal('true');
+
+ component.state.swapped = true;
+ component.update();
+
+ children = component.getEl('root').children;
+
+ expect(children.length).to.equal(2);
+
+ expect(children[0].dataset.foo).to.equal(undefined);
+ expect(children[0].dataset.bar).to.equal('true');
+
+ expect(children[1].dataset.foo).to.equal('true');
+ expect(children[1].dataset.bar).to.equal(undefined);
+};
diff --git a/test/autotests/components-browser/event-attach-el/test.js b/test/autotests/components-browser/event-attach-el/test.js
index 665f8ce57..25d46323a 100644
--- a/test/autotests/components-browser/event-attach-el/test.js
+++ b/test/autotests/components-browser/event-attach-el/test.js
@@ -5,7 +5,11 @@ module.exports = function(helpers) {
colors: ['red']
});
- expect(component.events.length).to.equal(0);
+ expect(component.events.length).to.equal(1);
+
+
+ expect(component.events[0].color).to.equal('red');
+ expect(component.events[0].node).to.equal(component.el.querySelectorAll('li')[0]);
component.input = {
@@ -14,9 +18,9 @@ module.exports = function(helpers) {
component.update();
- expect(component.events.length).to.equal(1);
- expect(component.events[0].color).to.equal('blue');
- expect(component.events[0].node).to.equal(component.el.querySelectorAll('li')[1]);
+ expect(component.events.length).to.equal(2);
+ expect(component.events[1].color).to.equal('blue');
+ expect(component.events[1].node).to.equal(component.el.querySelectorAll('li')[1]);
component.input = {
colors: ['red', 'green', 'blue']
@@ -24,7 +28,7 @@ module.exports = function(helpers) {
component.update();
- expect(component.events.length).to.equal(2);
- expect(component.events[1].color).to.equal('green');
- expect(component.events[1].node).to.equal(component.el.querySelectorAll('li')[1]);
-};
\ No newline at end of file
+ expect(component.events.length).to.equal(3);
+ expect(component.events[2].color).to.equal('green');
+ expect(component.events[2].node).to.equal(component.el.querySelectorAll('li')[1]);
+};
diff --git a/test/autotests/components-browser/extend-component/test.js b/test/autotests/components-browser/extend-component/test.js
index 87fac4731..53c2e9c65 100644
--- a/test/autotests/components-browser/extend-component/test.js
+++ b/test/autotests/components-browser/extend-component/test.js
@@ -1,17 +1,15 @@
var expect = require('chai').expect;
-var markoComponents = require('marko/components');
module.exports = function(helpers) {
- var checkboxComponent = helpers.mount(require('./components/app-checkbox'), {
+ helpers.mount(require('./components/app-checkbox'), {
checked: true,
'class': 'my-checkbox',
data: 123
});
+ expect(helpers.targetEl.children.length).to.equal(1);
+ expect(helpers.targetEl.children[0].nodeName).to.equal('BUTTON');
+
var el = helpers.targetEl.querySelector('.my-checkbox');
expect(el != null).to.equal(true);
-
- var componentForEl = markoComponents.getComponentForEl(el);
-
- expect(componentForEl).to.equal(checkboxComponent);
-};
\ No newline at end of file
+};
diff --git a/test/autotests/components-browser/forceUpdate/index.marko b/test/autotests/components-browser/forceUpdate/index.marko
index fc8fce307..cbef375a3 100644
--- a/test/autotests/components-browser/forceUpdate/index.marko
+++ b/test/autotests/components-browser/forceUpdate/index.marko
@@ -17,4 +17,4 @@ class {
}
}
-${counter++}
\ No newline at end of file
+${counter++}
diff --git a/test/autotests/components-browser/include-root/index.marko b/test/autotests/components-browser/include-root/index.marko
new file mode 100644
index 000000000..1ef76f2e6
--- /dev/null
+++ b/test/autotests/components-browser/include-root/index.marko
@@ -0,0 +1,13 @@
+class {
+ onClick() {
+ this.clicked = true;
+ }
+}
+
+
+ <@body>
+
+ Test link
+
+ @body>
+
diff --git a/test/autotests/components-browser/include-root/modal.marko b/test/autotests/components-browser/include-root/modal.marko
new file mode 100644
index 000000000..380096be1
--- /dev/null
+++ b/test/autotests/components-browser/include-root/modal.marko
@@ -0,0 +1,9 @@
+class {
+
+}
+
+
diff --git a/test/autotests/components-browser/include-root/test.js b/test/autotests/components-browser/include-root/test.js
new file mode 100644
index 000000000..7afdb7e3b
--- /dev/null
+++ b/test/autotests/components-browser/include-root/test.js
@@ -0,0 +1,7 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var component = helpers.mount(require('./index'), { });
+ helpers.triggerMouseEvent(component.getEl('button'), 'click');
+ expect(component.clicked).to.equal(true);
+};
diff --git a/test/autotests/components-browser/label-for-scoped-repeated/index.marko b/test/autotests/components-browser/label-for-scoped-repeated/index.marko
new file mode 100644
index 000000000..608aa5f10
--- /dev/null
+++ b/test/autotests/components-browser/label-for-scoped-repeated/index.marko
@@ -0,0 +1,12 @@
+class {
+
+}
+
+
+
+
+
+ ${field.label}
+
+
+
diff --git a/test/autotests/components-browser/label-for-scoped-repeated/test.js b/test/autotests/components-browser/label-for-scoped-repeated/test.js
new file mode 100644
index 000000000..aca3601b6
--- /dev/null
+++ b/test/autotests/components-browser/label-for-scoped-repeated/test.js
@@ -0,0 +1,32 @@
+var expect = require('chai').expect;
+
+module.exports = function(helpers) {
+ var fields = [
+ {
+ value: 'name',
+ label: 'Name'
+ },
+ {
+ value: 'age',
+ label: 'Age'
+ }
+ ];
+
+ var component = helpers.mount(require('./index'), {
+ fields: fields
+ });
+
+ var inputs = component.getEl('root').querySelectorAll('input');
+ var labels = component.getEl('root').querySelectorAll('label');
+
+ expect(inputs.length).to.equal(fields.length);
+
+ for (var i=0; i
+
Hello ${input.name}!
diff --git a/test/autotests/components-browser/lifecyle-hooks-destroy/index.marko b/test/autotests/components-browser/lifecyle-hooks-destroy/index.marko
index d735e05cf..a2076e1e3 100644
--- a/test/autotests/components-browser/lifecyle-hooks-destroy/index.marko
+++ b/test/autotests/components-browser/lifecyle-hooks-destroy/index.marko
@@ -3,11 +3,13 @@ import { expect } from 'chai';
class {
onDestroy(){
- expect(document.getElementById(this.id)).to.equal(this.el);
+ // The nodes should still be attached
+ var rootNode = this.getEl('root');
+ expect(rootNode.parentNode != null).to.equal(true);
hooks.record('root:destroy');
}
}
-
+
diff --git a/test/autotests/components-browser/morphdom-node-added-nested-keyed/test.js b/test/autotests/components-browser/morphdom-node-added-nested-keyed/test.js
index 1681d86f1..5b0db4845 100644
--- a/test/autotests/components-browser/morphdom-node-added-nested-keyed/test.js
+++ b/test/autotests/components-browser/morphdom-node-added-nested-keyed/test.js
@@ -2,13 +2,12 @@ var expect = require('chai').expect;
module.exports = function(helpers) {
var component = helpers.mount(require('./index'), { });
- var nameInput = component.getEl('nameInput');
+
+ expect(component.getEl('nameInput').parentNode.nodeName).to.equal('DIV');
component.state.renderCount++;
-
component.update();
- expect(component.getEl('nameInput')).to.equal(nameInput);
- expect(component.getEl('nameInput').value).to.equal('2');
expect(component.getEl('nameInput').parentNode.nodeName).to.equal('P');
+ expect(component.getEl('nameInput').value).to.equal('2');
};
diff --git a/test/autotests/components-browser/repeated-with-label-ref/test.js b/test/autotests/components-browser/repeated-with-label-ref/test.js
deleted file mode 100644
index 3d524eecb..000000000
--- a/test/autotests/components-browser/repeated-with-label-ref/test.js
+++ /dev/null
@@ -1,20 +0,0 @@
-var expect = require('chai').expect;
-
-module.exports = function(helpers) {
- var component = helpers.mount(require('./index'), {
- fields: [
- {
- value: 'name',
- label: 'Name'
- },
- {
- value: 'age',
- label: 'Age'
- }
- ]
- });
-
- var inputs = component.getEls('field');
- expect(inputs.length).to.equal(2);
- expect(inputs[0].value).to.equal('name');
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/rerender-multiple-roots/index.marko b/test/autotests/components-browser/rerender-multiple-roots/index.marko
index d11f27edc..cf03f9871 100644
--- a/test/autotests/components-browser/rerender-multiple-roots/index.marko
+++ b/test/autotests/components-browser/rerender-multiple-roots/index.marko
@@ -4,5 +4,5 @@ class {
}
}
-
--- in that case - i have error
+
${state.count}
+-- [${state.count}]
diff --git a/test/autotests/components-browser/rerender-multiple-roots/test.js b/test/autotests/components-browser/rerender-multiple-roots/test.js
index a40a16937..bda02db5f 100644
--- a/test/autotests/components-browser/rerender-multiple-roots/test.js
+++ b/test/autotests/components-browser/rerender-multiple-roots/test.js
@@ -1,5 +1,3 @@
-var expect = require('chai').expect;
-
// Test for issues #749 #690. Using multiple root nodes or style tag at the root would
// cause an infinite loop on rerender. This code does not perform any assertions
// but it will get caught in an infinite loop without the changes in PR #751
diff --git a/test/autotests/components-browser/rerender-same-id/index.marko b/test/autotests/components-browser/rerender-same-id/index.marko
deleted file mode 100644
index aae6c994d..000000000
--- a/test/autotests/components-browser/rerender-same-id/index.marko
+++ /dev/null
@@ -1,3 +0,0 @@
-
- ${input.label}
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/rerender-same-id/component.js b/test/autotests/components-browser/rerender-same-root/component.js
similarity index 100%
rename from test/autotests/components-browser/rerender-same-id/component.js
rename to test/autotests/components-browser/rerender-same-root/component.js
diff --git a/test/autotests/components-browser/rerender-same-root/index.marko b/test/autotests/components-browser/rerender-same-root/index.marko
new file mode 100644
index 000000000..bec7fe120
--- /dev/null
+++ b/test/autotests/components-browser/rerender-same-root/index.marko
@@ -0,0 +1,3 @@
+
+ ${input.label}
+
diff --git a/test/autotests/components-browser/rerender-same-id/test.js b/test/autotests/components-browser/rerender-same-root/test.js
similarity index 70%
rename from test/autotests/components-browser/rerender-same-id/test.js
rename to test/autotests/components-browser/rerender-same-root/test.js
index 8d2c9b598..29c666d6a 100644
--- a/test/autotests/components-browser/rerender-same-id/test.js
+++ b/test/autotests/components-browser/rerender-same-root/test.js
@@ -5,12 +5,12 @@ module.exports = function(helpers) {
label: 'Foo'
});
- var oldId = component.id;
+ var rootEl = component.getEl('root');
component.input = {
label: 'Bar'
};
component.update();
- expect(component.el.id).to.equal(oldId);
-};
\ No newline at end of file
+ expect(component.getEl('root')).to.equal(rootEl);
+};
diff --git a/test/autotests/components-browser/transclusion-include-from-state/components/app-button/index.marko b/test/autotests/components-browser/transclusion-include-from-state/components/app-button/index.marko
deleted file mode 100644
index 7c90ae6e2..000000000
--- a/test/autotests/components-browser/transclusion-include-from-state/components/app-button/index.marko
+++ /dev/null
@@ -1,29 +0,0 @@
-class {
- onInput(input) {
- this.state = {
- size: input.size || 'normal',
- variant: input.variant || 'primary',
- body: input.label || input.renderBody
- };
- }
-
- // Add any other methods here
- setVariant(variant) {
- this.state.variant = variant;
- }
-
- setSize(size) {
- this.state.size = size;
- }
-
- setLabel(label) {
- this.state.label = label;
- }
-}
-
-$ var variantClassName=(state.variant !== 'primary' && 'app-button-' + state.variant)
-$ var sizeClassName=(state.size !== 'normal' && 'app-button-' + state.size)
-
-
-
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/transclusion-include-from-state/index.marko b/test/autotests/components-browser/transclusion-include-from-state/index.marko
deleted file mode 100644
index 23def10ab..000000000
--- a/test/autotests/components-browser/transclusion-include-from-state/index.marko
+++ /dev/null
@@ -1,9 +0,0 @@
-class {
-
-}
-
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/transclusion-include-from-state/test.js b/test/autotests/components-browser/transclusion-include-from-state/test.js
deleted file mode 100644
index bcc5ce2ad..000000000
--- a/test/autotests/components-browser/transclusion-include-from-state/test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-var expect = require('chai').expect;
-
-module.exports = function(helpers) {
- var component = helpers.mount(require('./index'), {
- name: 'Frank'
- });
-
- var buttonComponent = component.getComponent('button');
- expect(buttonComponent.el.innerHTML).to.contain('Frank');
- expect(buttonComponent.el.className).to.equal('app-button app-button-small');
-
- component.input = { name: 'John '};
- component.update();
-
- expect(buttonComponent.el.innerHTML).to.contain('John');
-
- buttonComponent.setSize('large');
- buttonComponent.update();
- expect(buttonComponent.el.innerHTML).to.contain('John');
- expect(buttonComponent.el.className).to.equal('app-button app-button-large');
-
- buttonComponent.input = {
- size: 'small',
- variant: 'secondary'
- // NOTE: We aren't including renderBody() but we expect that content to be preserved
- };
- buttonComponent.update();
-
- expect(buttonComponent.el.innerHTML).to.contain('John');
- expect(buttonComponent.el.className).to.equal('app-button app-button-secondary app-button-small');
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-include-ref/index.marko b/test/autotests/components-browser/widget-include-ref/index.marko
index f31c7b095..a0d8cfbdc 100644
--- a/test/autotests/components-browser/widget-include-ref/index.marko
+++ b/test/autotests/components-browser/widget-include-ref/index.marko
@@ -2,4 +2,5 @@ import barComponent from './components/app-bar';
-
\ No newline at end of file
+
+
diff --git a/test/autotests/components-browser/widget-jQuery-proxy/test.js b/test/autotests/components-browser/widget-jQuery-proxy/test.js
index cfdf1fe4c..af0665ce7 100644
--- a/test/autotests/components-browser/widget-jQuery-proxy/test.js
+++ b/test/autotests/components-browser/widget-jQuery-proxy/test.js
@@ -6,7 +6,6 @@ module.exports = function(helpers, done) {
try {
var component = helpers.mount(require('./index'), {});
- expect(component.$().attr('id')).to.equal(component.id);
expect(component.$().attr('class')).to.equal('app-jquery-proxy');
expect(component.$('#foo').html()).to.equal('foo');
expect(component.$('#fooText').html()).to.equal('fooText');
diff --git a/test/autotests/components-browser/widget-rerender-init-order/test.js b/test/autotests/components-browser/widget-rerender-init-order/test.js
index 5cc96ec70..eef53d019 100644
--- a/test/autotests/components-browser/widget-rerender-init-order/test.js
+++ b/test/autotests/components-browser/widget-rerender-init-order/test.js
@@ -7,7 +7,7 @@ module.exports = function(helpers) {
version: 0
});
- expect(window.rerenderInitOrder).to.deep.equal(['childA', 'childB', 'parent']);
+ expect(window.rerenderInitOrder).to.deep.equal(['childB', 'childA', 'parent']);
window.rerenderInitOrder = [];
@@ -15,7 +15,7 @@ module.exports = function(helpers) {
component.update();
// console.log('ACTUAL ORDER: ', window.rerenderInitOrder);
- expect(window.rerenderInitOrder).to.deep.equal(['childA', 'childB', 'parent']);
+ expect(window.rerenderInitOrder).to.deep.equal(['childB', 'childA', 'parent']);
delete window.rerenderInitOrder;
-};
\ No newline at end of file
+};
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/component.js b/test/autotests/components-browser/widget-rerender-reuse-stateful/component.js
deleted file mode 100644
index b8e3e8e4e..000000000
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/component.js
+++ /dev/null
@@ -1,11 +0,0 @@
-module.exports = {
- onInput: function(input) {
- this.state = {
- buttonSize: input.buttonSize || 'normal'
- };
- },
-
- setButtonSize: function(size) {
- this.setState('buttonSize', size);
- }
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/component.js b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/component.js
deleted file mode 100644
index dac980872..000000000
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/component.js
+++ /dev/null
@@ -1,16 +0,0 @@
-module.exports = {
- onInput: function(input) {
- this.state = {
- size: input.size || 'normal',
- label: input.label || '(no label)'
- };
- },
-
- setSize: function(newSize) {
- this.state.size = newSize;
- },
-
- setLabel: function(newLabel) {
- this.state.label = newLabel;
- }
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/index.marko b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/index.marko
deleted file mode 100644
index f81857540..000000000
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/index.marko
+++ /dev/null
@@ -1,4 +0,0 @@
-
- ${__filename}
- ${state.label}
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/marko-tag.json b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/marko-tag.json
deleted file mode 100644
index 1937298d5..000000000
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/app-stateful-button/marko-tag.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "attributes": {
- "label": "string",
- "href": "string",
- "variant": {
- "type": "string",
- "description": "primary | secondary"
- },
- "size": {
- "type": "string",
- "description": "small | normal | large"
- },
- "class": {
- "type": "string",
- "description": "Additional CSS class names"
- },
- "*": "string"
- }
-}
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/components/hello/index.marko b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/hello/index.marko
new file mode 100644
index 000000000..5482964d8
--- /dev/null
+++ b/test/autotests/components-browser/widget-rerender-reuse-stateful/components/hello/index.marko
@@ -0,0 +1,13 @@
+class {
+ onCreate() {
+ window.helloInstances.push(this);
+ }
+
+ onInput(input) {
+ this.state = {
+ name: input.name
+ }
+ }
+}
+
+Hello ${state.name}
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/index.marko b/test/autotests/components-browser/widget-rerender-reuse-stateful/index.marko
index f9d168d61..582b566a8 100644
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/index.marko
+++ b/test/autotests/components-browser/widget-rerender-reuse-stateful/index.marko
@@ -1,4 +1,12 @@
-
-
${state.buttonSize}
-
button2
-
\ No newline at end of file
+class {
+ onCreate() {
+ this.state = {
+ count: 0
+ };
+ }
+}
+
+
+
+
+
diff --git a/test/autotests/components-browser/widget-rerender-reuse-stateful/test.js b/test/autotests/components-browser/widget-rerender-reuse-stateful/test.js
index 6ccdf55b2..6a7dcf839 100644
--- a/test/autotests/components-browser/widget-rerender-reuse-stateful/test.js
+++ b/test/autotests/components-browser/widget-rerender-reuse-stateful/test.js
@@ -1,38 +1,35 @@
var expect = require('chai').expect;
module.exports = function(helpers) {
-
+ window.helloInstances = [];
var component = helpers.mount(require('./index'), {});
- var oldButton1Component = component.getComponent('button1');
- var oldButton2Component = component.getEl('button2').__component;
- var oldButton1El = oldButton1Component.el;
- var oldButton2El = component.getEl('button2');
+ expect(window.helloInstances.length).to.equal(2);
- expect(component.getComponent('button1').el.className).to.contain('normal');
+ var helloEls = component.getEl('root').querySelectorAll('.hello');
+ expect(helloEls[0].innerHTML).to.equal('Hello Jane');
+ expect(helloEls[1].innerHTML).to.equal('Hello John0');
- var self = component;
+ var hello1 = component.getComponent('hello1');
+ var hello2 = component.getComponent('hello2');
- self.setButtonSize('small');
- self.update();
+ component.state.count++;
+ component.update();
- var newButton1El = component.getComponent('button1').el;
- var newButton2El = component.getEl('button2');
+ // Make sure no more instances of the nested components were created
+ expect(window.helloInstances.length).to.equal(2);
- // // Both button components should be reused
- expect(component.getComponent('button1')).to.equal(oldButton1Component);
- expect(component.getEl('button2').__component).to.equal(oldButton2Component);
+ // Make sure the UI components received the new state as part of onInput()
+ expect(hello1.state.name).to.equal('Jane');
+ expect(hello2.state.name).to.equal('John1');
- expect(component.getComponent('button1').el.className).to.contain('small');
+ // Make sure the HTML elements were reused
+ var helloElsAfter = component.getEl('root').querySelectorAll('.hello');
+ expect(helloElsAfter[0]).to.equal(helloEls[0]);
+ expect(helloElsAfter[1]).to.equal(helloEls[1]);
-
- // // State changed for button1 so it should have a new el
- // // since it re-renders to update its view
- // console.log('newButton1El: ', newButton1El);
- expect(newButton1El === oldButton1El).to.equal(true);
-
- //
- // // State didn't change for button2 so it should be the same el
- expect(newButton2El).to.equal(oldButton2El);
-};
\ No newline at end of file
+ // Make sure the DOM was updated
+ expect(helloElsAfter[0].innerHTML).to.equal('Hello Jane');
+ expect(helloElsAfter[1].innerHTML).to.equal('Hello John1');
+};
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/component.js b/test/autotests/components-browser/widget-stateful-reuse-widgets/component.js
deleted file mode 100644
index 8d60154ca..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/component.js
+++ /dev/null
@@ -1,13 +0,0 @@
-module.exports = {
- onInput: function(input) {
- this.state = {
- buttonSize: input.buttonSize || 'normal'
- };
- },
-
- setButtonSize: function(size) {
- this.setState('buttonSize', size);
- },
- onMount: function() {
- }
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/component.js b/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/component.js
deleted file mode 100644
index 42ca16331..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/component.js
+++ /dev/null
@@ -1,8 +0,0 @@
-module.exports = {
- onInput: function(input) {
- this.state = {
- size: input.size || 'normal',
- label: input.label || '(no label)'
- };
- }
-};
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/index.marko b/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/index.marko
deleted file mode 100644
index f81857540..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/index.marko
+++ /dev/null
@@ -1,4 +0,0 @@
-
- ${__filename}
- ${state.label}
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/marko-tag.json b/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/marko-tag.json
deleted file mode 100644
index 1937298d5..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/components/app-stateful-button/marko-tag.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "attributes": {
- "label": "string",
- "href": "string",
- "variant": {
- "type": "string",
- "description": "primary | secondary"
- },
- "size": {
- "type": "string",
- "description": "small | normal | large"
- },
- "class": {
- "type": "string",
- "description": "Additional CSS class names"
- },
- "*": "string"
- }
-}
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/index.marko b/test/autotests/components-browser/widget-stateful-reuse-widgets/index.marko
deleted file mode 100644
index f9d168d61..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/index.marko
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
${state.buttonSize}
-
button2
-
\ No newline at end of file
diff --git a/test/autotests/components-browser/widget-stateful-reuse-widgets/test.js b/test/autotests/components-browser/widget-stateful-reuse-widgets/test.js
deleted file mode 100644
index e3c464e45..000000000
--- a/test/autotests/components-browser/widget-stateful-reuse-widgets/test.js
+++ /dev/null
@@ -1,36 +0,0 @@
-var expect = require('chai').expect;
-
-module.exports = function(helpers) {
- var component = helpers.mount(require('./index'), {});
-
- var oldButton1Component = component.getComponent('button1');
- var oldButton2Component = component.getEl('button2').__component;
- var oldButton1El = oldButton1Component.el;
- var oldButton2El = component.getEl('button2');
-
- expect(component.getComponent('button1').el.className).to.contain('normal');
-
- var self = component;
-
- self.setButtonSize('small');
- self.update();
-
- var newButton1El = component.getComponent('button1').el;
- var newButton2El = component.getEl('button2');
-
- // // Both button components should be reused
- expect(component.getComponent('button1')).to.equal(oldButton1Component);
- expect(component.getEl('button2').__component).to.equal(oldButton2Component);
-
- expect(component.getComponent('button1').el.className).to.contain('small');
-
-
- // // State changed for button1 so it should have a new el
- // // since it re-renders to update its view
- // console.log('newButton1El: ', newButton1El);
- expect(newButton1El === oldButton1El).to.equal(true);
-
- //
- // // State didn't change for button2 so it should be the same el
- expect(newButton2El).to.equal(oldButton2El);
-};
\ No newline at end of file
diff --git a/test/autotests/components-compilation-deprecated/bind-component/expected.js b/test/autotests/components-compilation-deprecated/bind-component/expected.js
index 8c28af17b..1dc5efce5 100644
--- a/test/autotests/components-compilation-deprecated/bind-component/expected.js
+++ b/test/autotests/components-compilation-deprecated/bind-component/expected.js
@@ -8,16 +8,12 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/bind-component/index", function() {
return marko_defineWidget_legacy(require("./"));
}),
- marko_rendererLegacy = legacy_helpers.r,
- marko_helpers = require("marko/src/runtime/html/helpers"),
- marko_attr = marko_helpers.a;
+ marko_rendererLegacy = legacy_helpers.r;
-function render(input, out, __component, widget) {
+function render(input, out, __component, widget, component) {
var data = input;
- out.w("
");
+ out.w("
");
}
marko_template._ = marko_rendererLegacy(render, {
diff --git a/test/autotests/components-compilation-deprecated/bind-widget/expected.js b/test/autotests/components-compilation-deprecated/bind-widget/expected.js
index 0add5bad9..16fee9562 100644
--- a/test/autotests/components-compilation-deprecated/bind-widget/expected.js
+++ b/test/autotests/components-compilation-deprecated/bind-widget/expected.js
@@ -8,16 +8,12 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/bind-widget/widget", function() {
return marko_defineWidget_legacy(require("./widget"));
}),
- marko_rendererLegacy = legacy_helpers.r,
- marko_helpers = require("marko/src/runtime/html/helpers"),
- marko_attr = marko_helpers.a;
+ marko_rendererLegacy = legacy_helpers.r;
-function render(input, out, __component, widget) {
+function render(input, out, __component, widget, component) {
var data = input;
- out.w("
");
+ out.w("
");
}
marko_template._ = marko_rendererLegacy(render, {
diff --git a/test/autotests/components-compilation-deprecated/component-include-attr/expected.js b/test/autotests/components-compilation-deprecated/component-include-attr/expected.js
index 1a11c21fb..be8699cca 100644
--- a/test/autotests/components-compilation-deprecated/component-include-attr/expected.js
+++ b/test/autotests/components-compilation-deprecated/component-include-attr/expected.js
@@ -9,23 +9,30 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
return marko_defineWidget_legacy(require("./"));
}),
marko_rendererLegacy = legacy_helpers.r,
+ marko_renderComponent = require("marko/src/components/taglib/helpers/renderComponent"),
marko_helpers = require("marko/src/runtime/html/helpers"),
marko_loadTag = marko_helpers.t,
- include_tag = marko_loadTag(require("marko/src/components/taglib/include-tag")),
- marko_attr = marko_helpers.a;
+ include_tag = marko_loadTag(require("marko/src/taglibs/core/include-tag")),
+ _preserve_tag = marko_loadTag(require("marko/src/components/taglib/preserve-tag"));
-function render(input, out, __component, widget) {
+function render(input, out, __component, widget, component) {
var data = input;
- out.w("Header ");
+ out.w("
Header ");
- include_tag({
- _target: __component.b,
- _elId: __component.elId(0)
+ var __key3 = __component.___nextKey("2");
+
+ out.w("
");
+
+ _preserve_tag({
+ bodyOnly: true,
+ if: !__component.b,
+ key: __key3,
+ renderBody: function renderBody(out) {
+ marko_renderComponent(include_tag, {
+ _target: __component.b
+ }, out, "4");
+ }
}, out);
out.w("
");
@@ -33,8 +40,7 @@ function render(input, out, __component, widget) {
marko_template._ = marko_rendererLegacy(render, {
split: true,
- type: marko_componentType,
- body: 0
+ type: marko_componentType
});
marko_template.meta = {
@@ -49,6 +55,7 @@ marko_template.meta = {
}
],
tags: [
- "marko/src/components/taglib/include-tag"
+ "marko/src/taglibs/core/include-tag",
+ "marko/src/components/taglib/preserve-tag"
]
};
diff --git a/test/autotests/components-compilation-deprecated/component-template-entry/expected.js b/test/autotests/components-compilation-deprecated/component-template-entry/expected.js
index f407a1a04..e83cfb82c 100644
--- a/test/autotests/components-compilation-deprecated/component-template-entry/expected.js
+++ b/test/autotests/components-compilation-deprecated/component-template-entry/expected.js
@@ -3,31 +3,36 @@
var marko_template = module.exports = require("marko/src/html").t(__filename),
components_helpers = require("marko/src/components/helpers"),
marko_registerComponent = components_helpers.rc,
- legacy_helpers = require("marko/src/components/legacy/helpers"),
- marko_defineWidget_legacy = legacy_helpers.w,
- marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/component-template-entry/component", function() {
- return marko_defineWidget_legacy(require("./component"));
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/component-template-entry/index.marko", function() {
+ return module.exports;
}),
marko_component = require("./component"),
- marko_rendererLegacy = legacy_helpers.r,
- marko_helpers = require("marko/src/runtime/html/helpers"),
- marko_attr = marko_helpers.a;
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ legacy_helpers = require("marko/src/components/legacy/helpers"),
+ marko_defineWidget_legacy = legacy_helpers.w,
+ marko_componentType2 = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/component-template-entry/component", function() {
+ return marko_defineWidget_legacy(require("./component"));
+ }),
+ marko_rendererLegacy = legacy_helpers.r;
-function render(input, out, __component, widget) {
+function render(input, out, __component, component, state, __component, widget, component) {
var data = input;
- out.w("
");
+ out.w("
");
}
marko_template._ = marko_rendererLegacy(render, {
split: true,
- type: marko_componentType
- }, marko_component);
+ type: marko_componentType2
+ });
marko_template.meta = {
deps: [
+ {
+ type: "require",
+ path: "./"
+ },
{
type: "require",
path: "./component"
diff --git a/test/autotests/components-compilation-deprecated/widget-types/expected.js b/test/autotests/components-compilation-deprecated/widget-types/expected.js
index df1ce7369..526558488 100644
--- a/test/autotests/components-compilation-deprecated/widget-types/expected.js
+++ b/test/autotests/components-compilation-deprecated/widget-types/expected.js
@@ -13,18 +13,14 @@ var marko_template = module.exports = require("marko/src/html").t(__filename),
mobile: marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-deprecated/widget-types/component-mobile", function() {
return marko_defineWidget_legacy(require("./component-mobile"));
})
- },
- marko_helpers = require("marko/src/runtime/html/helpers"),
- marko_attr = marko_helpers.a;
+ };
-function render(input, out, __component, widget) {
+function render(input, out, __component, widget, component) {
var data = input;
__component.t(marko_componentTypes[data.isMobile ? "default" : "mobile"]);
- out.w("
");
+ out.w("
");
}
marko_template._ = marko_rendererLegacy(render, {});
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if-el/components/my-component/index.marko b/test/autotests/components-compilation-vdom/boundary-el-if-el/components/my-component/index.marko
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if-el/expected.js b/test/autotests/components-compilation-vdom/boundary-el-if-el/expected.js
new file mode 100644
index 000000000..69ba27647
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-if-el/expected.js
@@ -0,0 +1,51 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-el-if-el/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("441cd5"),
+ marko_node0 = marko_createElement("H1", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ }),
+ marko_node1 = marko_createElement("DIV", null, "2", null, 0, 0, {
+ i: marko_const_nextId()
+ }),
+ marko_node2 = marko_createElement("SPAN", null, "1", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.n(marko_node0, component);
+
+ if (input.someCondition) {
+ out.n(marko_node2, component);
+ }
+
+ out.n(marko_node1, component);
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if-el/index.marko b/test/autotests/components-compilation-vdom/boundary-el-if-el/index.marko
new file mode 100644
index 000000000..2cbc93319
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-if-el/index.marko
@@ -0,0 +1,7 @@
+class {}
+
+
+
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if/expected.js b/test/autotests/components-compilation-vdom/boundary-el-if/expected.js
new file mode 100644
index 000000000..ca09539ef
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-if/expected.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-el-if/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("ee4c02"),
+ marko_node0 = marko_createElement("H1", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ }),
+ marko_node1 = marko_createElement("SPAN", null, "1", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.n(marko_node0, component);
+
+ if (input.someCondition) {
+ out.n(marko_node1, component);
+ }
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-el-if/index.marko b/test/autotests/components-compilation-vdom/boundary-el-if/index.marko
new file mode 100644
index 000000000..cd6729875
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-if/index.marko
@@ -0,0 +1,6 @@
+class {}
+
+
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/marko-tag.json b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/marko-tag.json
new file mode 100644
index 000000000..1dc5f764d
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/marko-tag.json
@@ -0,0 +1,4 @@
+{
+ "renderer": "./renderer",
+ "noOutput": true
+}
\ No newline at end of file
diff --git a/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/renderer.js b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/renderer.js
new file mode 100644
index 000000000..2d87648d8
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/components/test-no-output/renderer.js
@@ -0,0 +1,3 @@
+module.exports = function(input, out) {
+
+};
\ No newline at end of file
diff --git a/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/expected.js b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/expected.js
new file mode 100644
index 000000000..7b806c3a3
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/expected.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_loadTag = marko_helpers.t,
+ test_no_output_tag = marko_loadTag(require("./components/test-no-output/renderer")),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("4529ef"),
+ marko_node0 = marko_createElement("DIV", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.n(marko_node0, component);
+
+ test_no_output_tag({}, out);
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ],
+ tags: [
+ "./components/test-no-output/renderer"
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko
new file mode 100644
index 000000000..2dee9522a
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-el-no-output-tag/index.marko
@@ -0,0 +1,4 @@
+class {}
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-html-tag/expected.js b/test/autotests/components-compilation-vdom/boundary-html-tag/expected.js
new file mode 100644
index 000000000..2251795e8
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-html-tag/expected.js
@@ -0,0 +1,71 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-html-tag/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_renderComponent = require("marko/src/components/taglib/helpers/renderComponent"),
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_loadTag = marko_helpers.t,
+ component_globals_tag = marko_loadTag(require("marko/src/components/taglib/component-globals-tag")),
+ init_components_tag = marko_loadTag(require("marko/src/components/taglib/init-components-tag")),
+ await_reorderer_tag = marko_loadTag(require("marko/src/taglibs/async/await-reorderer-tag")),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("5b1bc3"),
+ marko_node0 = marko_createElement("HEAD", null, "1", null, 1, 0, {
+ i: marko_const_nextId()
+ })
+ .e("TITLE", null, "2", null, 1)
+ .t("Hello"),
+ marko_node1 = marko_createElement("H1", null, "4", null, 1, 0, {
+ i: marko_const_nextId()
+ })
+ .t("Hello");
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.be("HTML", null, "0", component);
+
+ out.n(marko_node0, component);
+
+ out.be("BODY", null, "3", component);
+
+ component_globals_tag({}, out);
+
+ out.n(marko_node1, component);
+
+ init_components_tag({}, out);
+
+ marko_renderComponent(await_reorderer_tag, {}, out, "5");
+
+ out.ee();
+
+ out.ee();
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ],
+ tags: [
+ "marko/src/components/taglib/component-globals-tag",
+ "marko/src/components/taglib/init-components-tag",
+ "marko/src/taglibs/async/await-reorderer-tag"
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-html-tag/index.marko b/test/autotests/components-compilation-vdom/boundary-html-tag/index.marko
new file mode 100644
index 000000000..f2353b0c6
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-html-tag/index.marko
@@ -0,0 +1,9 @@
+class {}
+
+
+
Hello
+
+
+
Hello
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-if-el/expected.js b/test/autotests/components-compilation-vdom/boundary-if-el/expected.js
new file mode 100644
index 000000000..0ff64302f
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-if-el/expected.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-if-el/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("5854ef"),
+ marko_node0 = marko_createElement("H1", null, "1", null, 0, 0, {
+ i: marko_const_nextId()
+ }),
+ marko_node1 = marko_createElement("SPAN", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ if (input.someCondition) {
+ out.n(marko_node1, component);
+ }
+
+ out.n(marko_node0, component);
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-if-el/index.marko b/test/autotests/components-compilation-vdom/boundary-if-el/index.marko
new file mode 100644
index 000000000..a47f7c3e9
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-if-el/index.marko
@@ -0,0 +1,6 @@
+class {}
+
+
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-if-root/components/my-component/index.marko b/test/autotests/components-compilation-vdom/boundary-if-root/components/my-component/index.marko
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/autotests/components-compilation-vdom/boundary-if-root/expected.js b/test/autotests/components-compilation-vdom/boundary-if-root/expected.js
new file mode 100644
index 000000000..ab8c6ed96
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-if-root/expected.js
@@ -0,0 +1,41 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-if-root/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("0ec49e"),
+ marko_node0 = marko_createElement("DIV", null, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ if (input.someCondition) {
+ out.n(marko_node0, component);
+ }
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-if-root/index.marko b/test/autotests/components-compilation-vdom/boundary-if-root/index.marko
new file mode 100644
index 000000000..1f11313dc
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-if-root/index.marko
@@ -0,0 +1,5 @@
+class {}
+
+
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/components/my-component/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/components/my-component/index.marko
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js
new file mode 100644
index 000000000..7afe5ff3e
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/expected.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_keyAttr = require("marko/src/components/taglib/helpers/markoKeyAttr"),
+ marko_renderComponent = require("marko/src/components/taglib/helpers/renderComponent"),
+ marko_loadTemplate = require("marko/src/runtime/helper-loadTemplate"),
+ my_component_template = marko_loadTemplate(require.resolve("./components/my-component")),
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_loadTag = marko_helpers.t,
+ my_component_tag = marko_loadTag(my_component_template);
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.e("H1", {
+ "data-marko-key": marko_keyAttr(input.myStartKey, __component)
+ }, input.myStartKey, component, 0);
+
+ marko_renderComponent(my_component_tag, {}, out, "0");
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ],
+ tags: [
+ "./components/my-component"
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko
new file mode 100644
index 000000000..76eda8c84
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-custom-tag-no-key/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/components/my-component/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/components/my-component/index.marko
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/expected.js b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/expected.js
new file mode 100644
index 000000000..9c6525237
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/expected.js
@@ -0,0 +1,51 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c,
+ marko_renderComponent = require("marko/src/components/taglib/helpers/renderComponent"),
+ marko_loadTemplate = require("marko/src/runtime/helper-loadTemplate"),
+ my_component_template = marko_loadTemplate(require.resolve("./components/my-component")),
+ marko_helpers = require("marko/src/runtime/vdom/helpers"),
+ marko_loadTag = marko_helpers.t,
+ my_component_tag = marko_loadTag(my_component_template),
+ marko_createElement = marko_helpers.e,
+ marko_const = marko_helpers.const,
+ marko_const_nextId = marko_const("0a6612"),
+ marko_node0 = marko_createElement("H1", {
+ id: "myStart"
+ }, "0", null, 0, 0, {
+ i: marko_const_nextId()
+ });
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.n(marko_node0, component);
+
+ marko_renderComponent(my_component_tag, {}, out, "myEnd");
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ],
+ tags: [
+ "./components/my-component"
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko
new file mode 100644
index 000000000..561428614
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-el-id-custom-tag-key/index.marko
@@ -0,0 +1,3 @@
+class {}
+
+
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/expected.js b/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/expected.js
new file mode 100644
index 000000000..fa9938877
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/expected.js
@@ -0,0 +1,38 @@
+"use strict";
+
+var marko_template = module.exports = require("marko/src/vdom").t(__filename),
+ marko_component = {},
+ components_helpers = require("marko/src/components/helpers"),
+ marko_registerComponent = components_helpers.rc,
+ marko_componentType = marko_registerComponent("/marko-test$1.0.0/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko", function() {
+ return module.exports;
+ }),
+ marko_renderer = components_helpers.r,
+ marko_defineComponent = components_helpers.c;
+
+function render(input, out, __component, component, state) {
+ var data = input;
+
+ out.e("H1", {
+ id: input.myStart
+ }, "0", component, 0, 4);
+
+ out.e("DIV", {
+ id: input.myEnd
+ }, "1", component, 0, 4);
+}
+
+marko_template._ = marko_renderer(render, {
+ type: marko_componentType
+ }, marko_component);
+
+marko_template.Component = marko_defineComponent(marko_component, marko_template._);
+
+marko_template.meta = {
+ deps: [
+ {
+ type: "require",
+ path: "./"
+ }
+ ]
+ };
diff --git a/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko b/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko
new file mode 100644
index 000000000..b74e6c740
--- /dev/null
+++ b/test/autotests/components-compilation-vdom/boundary-multi-root-html-els-ids-dynamic/index.marko
@@ -0,0 +1,3 @@
+class {}
+