From 2c5c7093851e058aeddea6c92ad5cd202fdc95c7 Mon Sep 17 00:00:00 2001 From: Jose David Rodriguez Velasco Date: Tue, 29 Mar 2022 15:49:22 -0400 Subject: [PATCH] wip: dirty compiler to output create static fragment wip: add a static vnode type wip: needs key and patch wip: all tests passing exept a few because of simple things wip: set style tokens for fragments during template evaluation wip: set element shadow token for fragments during template evaluation wip: propagate the shadow resolver key of static fragments wip: do not gen static nodes for text or comments wip: use tagedTemplate expression to replate stylesheetToken wip: use cloneNode from renderer wip: treeWalker to work in ie11 refactor: do not strip empty attr or empty class attr fix: using incorrect key wip: trim value of textNodes and review feedback fix: hydration feat: custom static element serializer wip: remove unessesary import fix: hydration fix: snapshot tests fix: missing karma test fix: test due rebase test: add test for static content needing nodeOwner fix: escape strings in serializer refactor: remove unused apis on generated code refactor: review suggestions fix: support mixed mode wip: fix compilation snapshots fix: increase 0.5kb bundlesize for engine dom fix: flapper wip: helpers.ts review wip: codegen.ts review wip: missing items from pm review wip: review comments fix: respect preserveComments and fuse $1,2 into 3 fix: svg content with the correct namespace feat(template-compiler): add option to disable static content optimizations wip: remove invalid comment chore: bump version to v2.13.0 (#2784) chore: dependencies upgrade (#2785) test: fix Node warning about event emitters (#2789) chore: run karma and integration tests in parallel (#2792) * chore: run karma and integration tests in parallel * fix: remove log lines fix(babel-plugin-component): remove import validation (#2719) test: remove flakey IE integration test (#2796) test: update test to use lwc imports (#2794) chore: Restrict further import order (#2795) chore: bump version to v2.13.1 (#2804) refactor(engine): moving vm references from dom into core (#2801) * refactor(engine): moving vm references from dom into core chore(nucleus): remove salesforcedevs/developer-website (#2807) test(integration-karma): small quality-of-life improvements (#2809) chore(deps): bump ejs from 3.1.6 to 3.1.7 (#2810) chore: weekly dependencies upgrade (#2816) * chore: weekly dependencies upgrade * fix: update yarn.lock` refactor(engine): optimize computation of transitive shadow mode (#2803) chore(deps): bump async from 2.6.3 to 2.6.4 (#2815) Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4. - [Release notes](https://github.com/caolan/async/releases) - [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md) - [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4) --- updated-dependencies: - dependency-name: async dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> chore: bump version to v2.13.2 (#2819) chore: retry failed Circle CI tests (#2814) * chore: retry failed Circle CI tests W-10946477 * chore: fix * chore: fix * chore: fix * chore: fix * chore: fix * chore: fix * chore: fix * chore: deliberately fail a test to see what happens * chore: improve retry script * fix: whitespace * Revert "chore: deliberately fail a test to see what happens" This reverts commit 611fc34b754b6859a50951fe0b97c08cd185080a. * chore: rename to retry_karma fix(engine-core): add shim for old stylesheetTokens internal API (#2821) W-11093934 chore: bump version to v2.13.3 (#2823) fix(build): remove swc, switch back to babel and terser (#2818) feat: add freezeTemplate() API, warn on mutation (#2825) * feat: add freezeTemplate() API, warn on mutation * fix: warn on slots/renderMode as well * fix: add comment * fix: remove duplicate process.env.NODE_ENV check fix(engine-dom): refactor stylesheet API for consistency (#2827) * fix(engine-dom): refactor stylesheet API for consistency * fix: remove useless code comment * test: remove unnecessary test * test: remove unnecessary test * refactor: slight refactor * fix: add code comments * fix: add code comments * fix: add better comment fix: relax static id validation in iterations (#2830) fix(rollup-plugin): emit warnings during compilation (#2833) * fix(rollup-plugin): emit warnings during compilation Fixes #2771 W-10930894 * fix: add code comment fix(engine-dom): make feature flags work (#2812) * fix(engine-dom): make feature flags work Fixes #2811 * fix: license headers * test: fix jest tests * test: fix test * test: fix test * fix: use Eugene's technique instead * Revert "fix: use Eugene's technique instead" This reverts commit 72afdc050129e7491d7a96ff30bc32e08b1f9f0b. * fix: use Eugene's technique instead * fix: revert unnecessary test change * fix: revert, use the elaborate test instead * fix: fix feature flags in engine-server as well perf(engine-dom): refactor style cache to reduce lookups (#2832) * perf(engine-dom): refactor style cache to reduce lookups * fix: tidy up comments * fix: update packages/@lwc/engine-dom/src/styles.ts Co-authored-by: Pierre-Marie Dartus * fix: remove semi * fix: remove "used" flag * fix: refactor * fix: refactor * fix: bring back "used" flag * fix: typo Co-authored-by: Pierre-Marie Dartus chore: update deps (#2838) test: run feature flag test code only in karma (#2835) fix: trigger slotchange event on removing slot (#2840) test(integration-karma): silence lwc rollup plugin warnings (#2836) * test(integration-karma): silence lwc rollup plugin warnings * fix: use warn API v2.11.7 (#2842) chore: release v2.14.0 (#2846) fix: only remove slot children in synthetic shadow (#2843) * fix: only remove slot children in synthetic shadow * fix: use case block fix: only add version mismatch test code in karma (#2852) test(integration-karma): ensure constructable stylesheets are re-used (#2844) * test(integration-karma): ensure constructable stylesheets are re-used * test: add test for shared style chore(nucleus): remove more downstreams (#2855) chore(nucleus): remove another downstream (#2857) docs: fix typo in template compiler readme (#2848) * docs: fix typo in template compiler readme * docs: rewording usage of lwc dynamic directive Co-authored-by: Eugene Kashida Co-authored-by: Eugene Kashida chore: fix lint test: refactor test, remove test covered in #2859 test: on second thought, bring test back --- .../@lwc/engine-core/src/framework/api.ts | 15 + .../engine-core/src/framework/hydration.ts | 74 +++ .../@lwc/engine-core/src/framework/main.ts | 1 + .../engine-core/src/framework/renderer.ts | 2 + .../engine-core/src/framework/rendering.ts | 40 ++ .../engine-core/src/framework/template.ts | 82 ++- packages/@lwc/engine-core/src/framework/vm.ts | 2 +- .../@lwc/engine-core/src/framework/vnodes.ts | 10 +- packages/@lwc/engine-dom/src/index.ts | 5 + packages/@lwc/engine-dom/src/renderer.ts | 10 + .../fixtures/attribute-static/expected.html | 2 +- .../fixtures/attribute-style/expected.html | 8 +- .../src/__tests__/fixtures/svgs/expected.html | 18 + .../src/__tests__/fixtures/svgs/index.js | 2 + .../fixtures/svgs/modules/x/svgs/svgs.html | 15 + .../fixtures/svgs/modules/x/svgs/svgs.js | 3 + packages/@lwc/engine-server/src/renderer.ts | 16 +- packages/@lwc/engine-server/src/serializer.ts | 3 +- .../src/utils => shared/src}/html-escape.ts | 6 +- packages/@lwc/shared/src/index.ts | 1 + packages/@lwc/shared/src/keys.ts | 2 + .../@lwc/synthetic-shadow/src/env/document.ts | 2 + .../src/faux-shadow/shadow-token.ts | 32 ++ .../src/typings/documentOverrides.d.ts | 14 + packages/@lwc/template-compiler/README.md | 1 + .../attributes/attribute-allow/expected.js | 13 +- .../attribute-crossorigin/expected.js | 34 +- .../hidden-global-attr/expected.js | 71 ++- .../boolean-attribute/required/expected.js | 87 ++-- .../boolean-attributes-valid/expected.js | 23 +- .../fixtures/attributes/boolean/expected.js | 15 +- .../fixtures/attributes/class/expected.js | 43 +- .../attributes/dataset-complex/expected.js | 16 +- .../fixtures/attributes/dataset/expected.js | 17 +- .../attributes/error-empty-value/expected.js | 18 +- .../attributes/html-input/expected.js | 20 +- .../attributes/html-tag-invalid/expected.js | 22 +- .../fixtures/attributes/html-tag/expected.js | 19 +- .../attributes/mixed-props-attrs/expected.js | 57 +-- .../fixtures/attributes/style/config.json | 3 + .../expected.js | 13 +- .../__tests__/fixtures/base/class/expected.js | 15 +- .../fixtures/base/style-important/expected.js | 15 +- .../fixtures/base/style-static/expected.js | 26 +- .../fixtures/base/template/expected.js | 10 +- .../fixtures/comments/basic/expected.js | 10 +- .../comments/directive-if/expected.js | 18 +- .../preserve-html-comments-option/expected.js | 10 +- .../fixtures/comments/slots/expected.js | 11 +- .../flatten-child/expected.js | 15 +- .../directive-for-each/children/expected.js | 53 +- .../inline-sibling/expected.js | 9 +- .../directive-for-each/inline/expected.js | 15 +- .../directive-if/inline-multiple/expected.js | 22 +- .../inline-sibling-static/expected.js | 21 +- .../directive-if/strict-true/expected.js | 10 +- .../directive-if/template-if-else/expected.js | 16 +- .../template-multiple/expected.js | 22 +- .../template-sibiling/expected.js | 22 +- .../outside-iterator/expected.js | 10 +- .../shadow-template/expected.js | 10 +- .../template/expected.js | 23 +- .../fixtures/expression/escaped/expected.js | 13 +- .../html-tags/unkown-element/expected.js | 24 +- .../double-close-div/expected.js | 10 +- .../double-close-template/expected.js | 10 +- .../single-attribute/expected.js | 1 - .../regression/dashed-html-tag/expected.js | 16 +- .../invalid-html-recovery/expected.js | 16 +- .../slot-name-with-dash/expected.js | 10 +- .../regression/table-with-tr/expected.js | 20 +- .../definition-sibiling-slot/expected.js | 23 +- .../definition-sibling-static/expected.js | 23 +- .../fixtures/slots/definition/expected.js | 15 +- .../fixtures/slots/mixed-1/expected.js | 56 +-- .../fixtures/slots/mixed/expected.js | 15 +- .../fixtures/slots/usage-named/expected.js | 10 +- .../static-content/attr-escaping/actual.html | 4 + .../static-content/attr-escaping/expected.js | 9 + .../attr-escaping/metadata.json | 3 + .../class-attr-escaping/actual.html | 4 + .../class-attr-escaping/expected.js | 9 + .../class-attr-escaping/metadata.json | 3 + .../comment-escaping/actual.html | 3 + .../comment-escaping/expected.js | 9 + .../comment-escaping/metadata.json | 3 + .../no-escaping-tags/actual.html | 16 + .../no-escaping-tags/expected.js | 17 + .../no-escaping-tags/metadata.json | 37 ++ .../preserve-comments-off/actual.html | 6 + .../preserve-comments-off/expected.js | 9 + .../preserve-comments-off/metadata.json | 3 + .../preserve-comments-on/actual.html | 6 + .../preserve-comments-on/expected.js | 12 + .../preserve-comments-on/metadata.json | 3 + .../static-content/text-escaping/actual.html | 3 + .../static-content/text-escaping/expected.js | 9 + .../text-escaping/metadata.json | 3 + .../__tests__/fixtures/svg/filter/expected.js | 465 ++---------------- .../fixtures/svg/foreign-object/expected.js | 56 +-- .../fixtures/svg/linear-gradient/expected.js | 47 +- .../fixtures/svg/simple-svg/expected.js | 54 +- .../svg/stranded-open-path/expected.js | 18 +- .../fixtures/svg/valid-image/expected.js | 26 +- .../fixtures/svg/valid-svg/expected.js | 119 +---- .../template-compiler/src/codegen/codegen.ts | 73 ++- .../src/codegen/formatters/module.ts | 9 +- .../template-compiler/src/codegen/helpers.ts | 77 ++- .../template-compiler/src/codegen/index.ts | 34 +- .../src/codegen/static-element-serializer.ts | 112 +++++ packages/@lwc/template-compiler/src/config.ts | 7 + .../template-compiler/src/parser/index.ts | 4 +- .../@lwc/template-compiler/src/shared/ast.ts | 10 +- .../template-compiler/src/shared/constants.ts | 2 + .../template-compiler/src/shared/estree.ts | 22 + .../template-compiler/src/shared/types.ts | 2 + .../static-same-different-order/index.spec.js | 12 +- .../static-different-priority/index.spec.js | 2 +- .../static-extra-from-client/index.spec.js | 2 +- .../static-extra-from-server/index.spec.js | 2 +- .../static-same-different-order/index.spec.js | 12 +- .../light-dom/synthetic-shadow/index.spec.js | 5 + .../test/static-content/index.spec.js | 93 ++++ .../static-content/x/component/component.css | 3 + .../static-content/x/component/component.html | 3 + .../static-content/x/component/component.js | 3 + .../static-content/x/container/container.html | 22 + .../static-content/x/container/container.js | 5 + .../static-content/x/multipleStyles/a.css | 3 + .../static-content/x/multipleStyles/a.html | 3 + .../static-content/x/multipleStyles/b.html | 3 + .../x/multipleStyles/b.scoped.css | 3 + .../x/multipleStyles/multipleStyles.js | 27 + .../test/static-content/x/native/native.html | 3 + .../test/static-content/x/native/native.js | 5 + .../test/static-content/x/svgNs/svgNs.html | 15 + .../test/static-content/x/svgNs/svgNs.js | 3 + .../template/attribute-style/index.spec.js | 2 +- scripts/bundlesize/bundlesize.config.json | 2 +- 139 files changed, 1527 insertions(+), 1476 deletions(-) create mode 100644 packages/@lwc/engine-server/src/__tests__/fixtures/svgs/expected.html create mode 100644 packages/@lwc/engine-server/src/__tests__/fixtures/svgs/index.js create mode 100755 packages/@lwc/engine-server/src/__tests__/fixtures/svgs/modules/x/svgs/svgs.html create mode 100755 packages/@lwc/engine-server/src/__tests__/fixtures/svgs/modules/x/svgs/svgs.js rename packages/@lwc/{engine-server/src/utils => shared/src}/html-escape.ts (63%) create mode 100644 packages/@lwc/synthetic-shadow/src/typings/documentOverrides.d.ts create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/attributes/style/config.json create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/attr-escaping/actual.html create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/attr-escaping/expected.js create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/attr-escaping/metadata.json create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/class-attr-escaping/actual.html create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/class-attr-escaping/expected.js create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/class-attr-escaping/metadata.json create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/comment-escaping/actual.html create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/comment-escaping/expected.js create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/comment-escaping/metadata.json create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/no-escaping-tags/actual.html create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/no-escaping-tags/expected.js create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/no-escaping-tags/metadata.json create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/preserve-comments-off/actual.html create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/preserve-comments-off/expected.js create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/preserve-comments-off/metadata.json create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/preserve-comments-on/actual.html create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/preserve-comments-on/expected.js create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/preserve-comments-on/metadata.json create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/text-escaping/actual.html create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/text-escaping/expected.js create mode 100644 packages/@lwc/template-compiler/src/__tests__/fixtures/static-content/text-escaping/metadata.json create mode 100644 packages/@lwc/template-compiler/src/codegen/static-element-serializer.ts create mode 100644 packages/integration-karma/test/static-content/index.spec.js create mode 100644 packages/integration-karma/test/static-content/x/component/component.css create mode 100644 packages/integration-karma/test/static-content/x/component/component.html create mode 100644 packages/integration-karma/test/static-content/x/component/component.js create mode 100644 packages/integration-karma/test/static-content/x/container/container.html create mode 100644 packages/integration-karma/test/static-content/x/container/container.js create mode 100644 packages/integration-karma/test/static-content/x/multipleStyles/a.css create mode 100644 packages/integration-karma/test/static-content/x/multipleStyles/a.html create mode 100644 packages/integration-karma/test/static-content/x/multipleStyles/b.html create mode 100644 packages/integration-karma/test/static-content/x/multipleStyles/b.scoped.css create mode 100644 packages/integration-karma/test/static-content/x/multipleStyles/multipleStyles.js create mode 100644 packages/integration-karma/test/static-content/x/native/native.html create mode 100644 packages/integration-karma/test/static-content/x/native/native.js create mode 100644 packages/integration-karma/test/static-content/x/svgNs/svgNs.html create mode 100644 packages/integration-karma/test/static-content/x/svgNs/svgNs.js diff --git a/packages/@lwc/engine-core/src/framework/api.ts b/packages/@lwc/engine-core/src/framework/api.ts index 5dbe86c078..cfa6974298 100644 --- a/packages/@lwc/engine-core/src/framework/api.ts +++ b/packages/@lwc/engine-core/src/framework/api.ts @@ -41,6 +41,8 @@ import { VComment, VElementData, VNodeType, + VStatic, + Key, } from './vnodes'; const SymbolIterator: typeof Symbol.iterator = Symbol.iterator; @@ -49,6 +51,18 @@ function addVNodeToChildLWC(vnode: VCustomElement) { ArrayPush.call(getVMBeingRendered()!.velements, vnode); } +// [st]atic node +function st(fragment: Element, key: Key): VStatic { + return { + type: VNodeType.Static, + sel: undefined, + key, + elm: undefined, + fragment, + owner: getVMBeingRendered()!, + }; +} + // [h]tml node function h(sel: string, data: VElementData, children: VNodes = EmptyArray): VElement { const vmBeingRendered = getVMBeingRendered()!; @@ -546,6 +560,7 @@ const api = ObjectFreeze({ co, dc, ti, + st, gid, fid, shc, diff --git a/packages/@lwc/engine-core/src/framework/hydration.ts b/packages/@lwc/engine-core/src/framework/hydration.ts index 627a34ae2f..2109322568 100644 --- a/packages/@lwc/engine-core/src/framework/hydration.ts +++ b/packages/@lwc/engine-core/src/framework/hydration.ts @@ -29,6 +29,7 @@ import { VComment, VElement, VCustomElement, + VStatic, } from './vnodes'; import { patchProps } from './modules/props'; @@ -81,6 +82,11 @@ function hydrateNode(node: Node, vnode: VNode, renderer: RendererAPI): Node | nu hydratedNode = hydrateComment(node, vnode, renderer); break; + case VNodeType.Static: + // VStatic are cacheable and cannot have custom renderer associated to them + hydratedNode = hydrateStaticElement(node, vnode, renderer); + break; + case VNodeType.Element: hydratedNode = hydrateElement(node, vnode, vnode.data.renderer ?? renderer); break; @@ -137,6 +143,16 @@ function hydrateComment(node: Node, vnode: VComment, renderer: RendererAPI): Nod return node; } +function hydrateStaticElement(elm: Node, vnode: VStatic, renderer: RendererAPI): Node | null { + if (!areCompatibleNodes(vnode.fragment, elm, vnode, renderer)) { + return handleMismatch(elm, vnode, renderer); + } + + vnode.elm = elm; + + return elm; +} + function hydrateElement(elm: Node, vnode: VElement, renderer: RendererAPI): Node | null { if ( !hasCorrectNodeType(vnode, elm, EnvNodeTypes.ELEMENT, renderer) || @@ -481,3 +497,61 @@ function validateStyleAttr(vnode: VBaseElement, elm: Element, renderer: Renderer return nodesAreCompatible; } + +function areCompatibleNodes(client: Node, ssr: Node, vnode: VNode, renderer: RendererAPI) { + const { getProperty, getAttribute } = renderer; + if (getProperty(client, 'nodeType') === EnvNodeTypes.TEXT) { + if (!hasCorrectNodeType(vnode, ssr, EnvNodeTypes.TEXT, renderer)) { + return false; + } + + return getProperty(client, 'nodeValue') === getProperty(ssr, 'nodeValue'); + } + + if (getProperty(client, 'nodeType') === EnvNodeTypes.COMMENT) { + if (!hasCorrectNodeType(vnode, ssr, EnvNodeTypes.COMMENT, renderer)) { + return false; + } + + return getProperty(client, 'nodeValue') === getProperty(ssr, 'nodeValue'); + } + + if (!hasCorrectNodeType(vnode, ssr, EnvNodeTypes.ELEMENT, renderer)) { + return false; + } + + let isCompatibleElements = true; + if (getProperty(client, 'tagName') !== getProperty(ssr, 'tagName')) { + if (process.env.NODE_ENV !== 'production') { + logError( + `Hydration mismatch: expecting element with tag "${getProperty( + client, + 'tagName' + ).toLowerCase()}" but found "${getProperty(ssr, 'tagName').toLowerCase()}".`, + vnode.owner + ); + } + + return false; + } + + const clientAttrsNames: string[] = getProperty(client, 'getAttributeNames').call(client); + + clientAttrsNames.forEach((attrName) => { + if (getAttribute(client, attrName) !== getAttribute(ssr, attrName)) { + logError( + `Mismatch hydrating element <${getProperty( + client, + 'tagName' + ).toLowerCase()}>: attribute "${attrName}" has different values, expected "${getAttribute( + client, + attrName + )}" but found "${getAttribute(ssr, attrName)}"`, + vnode.owner + ); + isCompatibleElements = false; + } + }); + + return isCompatibleElements; +} diff --git a/packages/@lwc/engine-core/src/framework/main.ts b/packages/@lwc/engine-core/src/framework/main.ts index b7f87ea1c1..07c9e0ec3b 100644 --- a/packages/@lwc/engine-core/src/framework/main.ts +++ b/packages/@lwc/engine-core/src/framework/main.ts @@ -26,6 +26,7 @@ export { getAssociatedVMIfPresent, } from './vm'; +export { parseFragment, parseSVGFragment } from './template'; export { hydrateRoot } from './hydration'; // Internal APIs used by compiled code ------------------------------------------------------------- diff --git a/packages/@lwc/engine-core/src/framework/renderer.ts b/packages/@lwc/engine-core/src/framework/renderer.ts index 65940508aa..7dddb2e7f5 100644 --- a/packages/@lwc/engine-core/src/framework/renderer.ts +++ b/packages/@lwc/engine-core/src/framework/renderer.ts @@ -18,6 +18,8 @@ export interface RendererAPI { isHydrating: () => boolean; insert: (node: N, parent: E, anchor: N | null) => void; remove: (node: N, parent: E) => void; + cloneNode: (node: N, deep: boolean) => N; + createFragment: (html: string) => N | null; createElement: (tagName: string, namespace?: string) => E; createText: (content: string) => N; createComment: (content: string) => N; diff --git a/packages/@lwc/engine-core/src/framework/rendering.ts b/packages/@lwc/engine-core/src/framework/rendering.ts index ed6da6915e..e3813fd0b4 100644 --- a/packages/@lwc/engine-core/src/framework/rendering.ts +++ b/packages/@lwc/engine-core/src/framework/rendering.ts @@ -16,6 +16,7 @@ import { keys, SVG_NAMESPACE, KEY__SHADOW_RESOLVER, + KEY__SHADOW_STATIC, } from '@lwc/shared'; import { RendererAPI } from './renderer'; @@ -48,6 +49,7 @@ import { isVBaseElement, isSameVnode, VNodeType, + VStatic, } from './vnodes'; import { patchAttributes } from './modules/attrs'; @@ -98,6 +100,10 @@ function patch(n1: VNode, n2: VNode, renderer: RendererAPI) { patchComment(n1 as VComment, n2, renderer); break; + case VNodeType.Static: + n2.elm = n1.elm; + break; + case VNodeType.Element: patchElement(n1 as VElement, n2, n2.data.renderer ?? renderer); break; @@ -120,6 +126,11 @@ export function mount(node: VNode, parent: ParentNode, renderer: RendererAPI, an mountComment(node, parent, anchor, renderer); break; + case VNodeType.Static: + // VStatic cannot have a custom renderer associated to them, using owner's renderer + mountStatic(node, parent, anchor, renderer); + break; + case VNodeType.Element: // If the vnode data has a renderer override use it, else fallback to owner's renderer mountElement(node, parent, anchor, node.data.renderer ?? renderer); @@ -208,6 +219,35 @@ function patchElement(n1: VElement, n2: VElement, renderer: RendererAPI) { patchChildren(n1.children, n2.children, elm, renderer); } +function mountStatic( + vnode: VStatic, + parent: ParentNode, + anchor: Node | null, + renderer: RendererAPI +) { + const { owner } = vnode; + const { cloneNode, isSyntheticShadowDefined, insertNode } = renderer; + const elm = (vnode.elm = cloneNode(vnode.fragment, true)); + + linkNodeToShadow(elm, owner, renderer); + + // Marks this node as Static to propagate the shadow resolver. must happen after elm is assigned to the proper shadow + const { renderMode, shadowMode } = owner; + + if (isSyntheticShadowDefined) { + if (shadowMode === ShadowMode.Synthetic || renderMode === RenderMode.Light) { + (elm as any)[KEY__SHADOW_STATIC] = true; + } + } + + if (process.env.NODE_ENV !== 'production') { + const isLight = renderMode === RenderMode.Light; + patchElementWithRestrictions(elm, { isPortal: false, isLight }); + } + + insertNode(elm, parent, anchor); +} + function mountCustomElement( vnode: VCustomElement, parent: ParentNode, diff --git a/packages/@lwc/engine-core/src/framework/template.ts b/packages/@lwc/engine-core/src/framework/template.ts index f0db9d77dc..02084027ff 100644 --- a/packages/@lwc/engine-core/src/framework/template.ts +++ b/packages/@lwc/engine-core/src/framework/template.ts @@ -14,31 +14,32 @@ import { isNull, isTrue, isUndefined, - toString, KEY__SCOPED_CSS, + toString, } from '@lwc/shared'; import { logError } from '../shared/logger'; import { getComponentTag } from '../shared/format'; - +import { createFragment, getFirstChild } from '../renderer'; import api, { RenderAPI } from './api'; import { + RenderMode, resetComponentRoot, runWithBoundaryProtection, + ShadowMode, SlotSet, TemplateCache, VM, - RenderMode, } from './vm'; import { EmptyArray } from './utils'; import { defaultEmptyTemplate, isTemplateRegistered } from './secure-template'; import { - TemplateStylesheetFactories, createStylesheet, getStylesheetsContent, + TemplateStylesheetFactories, updateStylesheetToken, } from './stylesheet'; -import { logOperationStart, logOperationEnd, OperationId } from './profiler'; +import { logOperationEnd, logOperationStart, OperationId } from './profiler'; import { getTemplateOrSwappedTemplate, setActiveVM } from './hot-swaps'; import { VNodes } from './vnodes'; @@ -113,6 +114,77 @@ function validateLightDomTemplate(template: Template, vm: VM) { } } +const enum FragmentCache { + HAS_SCOPED_STYLE = 1 << 0, + SHADOW_MODE_SYNTHETIC = 1 << 1, +} + +function buildParseFragmentFn( + createFragmentFn: (html: string) => Element +): (strings: string[], ...keys: number[]) => () => Element { + return (strings: string[], ...keys: number[]) => { + const cache = create(null); + + return function (): Element { + const { + context: { hasScopedStyles, stylesheetToken }, + shadowMode, + } = getVMBeingRendered()!; + const hasStyleToken = !isUndefined(stylesheetToken); + const isSyntheticShadow = shadowMode === ShadowMode.Synthetic; + + let cacheKey = 0; + if (hasStyleToken && hasScopedStyles) { + cacheKey |= FragmentCache.HAS_SCOPED_STYLE; + } + if (hasStyleToken && isSyntheticShadow) { + cacheKey |= FragmentCache.SHADOW_MODE_SYNTHETIC; + } + + if (!isUndefined(cache[cacheKey])) { + return cache[cacheKey]; + } + + const classToken = hasScopedStyles && hasStyleToken ? ' ' + stylesheetToken : ''; + const classAttrToken = + hasScopedStyles && hasStyleToken ? ` class="${stylesheetToken}"` : ''; + const attrToken = hasStyleToken && isSyntheticShadow ? ' ' + stylesheetToken : ''; + + let htmlFragment = ''; + for (let i = 0, n = keys.length; i < n; i++) { + switch (keys[i]) { + case 0: // styleToken in existing class attr + htmlFragment += strings[i] + classToken; + break; + case 1: // styleToken for added class attr + htmlFragment += strings[i] + classAttrToken; + break; + case 2: // styleToken as attr + htmlFragment += strings[i] + attrToken; + break; + case 3: // ${1}${2} + htmlFragment += strings[i] + classAttrToken + attrToken; + break; + } + } + + htmlFragment += strings[strings.length - 1]; + + cache[cacheKey] = createFragmentFn(htmlFragment); + + return cache[cacheKey]; + }; + }; +} + +// Note: at the moment this code executes, createFragment have not being set. +export const parseFragment = buildParseFragmentFn((html) => createFragment(html)); +export const parseSVGFragment = buildParseFragmentFn((html) => { + const fragment = createFragment('' + html + ''); + + return getFirstChild(fragment); +}); + export function evaluateTemplate(vm: VM, html: Template): VNodes { if (process.env.NODE_ENV !== 'production') { assert.isTrue( diff --git a/packages/@lwc/engine-core/src/framework/vm.ts b/packages/@lwc/engine-core/src/framework/vm.ts index ee9f43f09a..d8cc25db89 100644 --- a/packages/@lwc/engine-core/src/framework/vm.ts +++ b/packages/@lwc/engine-core/src/framework/vm.ts @@ -752,4 +752,4 @@ export function forceRehydration(vm: VM) { markComponentAsDirty(vm); scheduleRehydration(vm); } -} +} \ No newline at end of file diff --git a/packages/@lwc/engine-core/src/framework/vnodes.ts b/packages/@lwc/engine-core/src/framework/vnodes.ts index e93fc611f7..e3fc2dc9dc 100644 --- a/packages/@lwc/engine-core/src/framework/vnodes.ts +++ b/packages/@lwc/engine-core/src/framework/vnodes.ts @@ -15,9 +15,10 @@ export const enum VNodeType { Comment, Element, CustomElement, + Static, } -export type VNode = VText | VComment | VElement | VCustomElement; +export type VNode = VText | VComment | VElement | VCustomElement | VStatic; export type VParentElement = VElement | VCustomElement; export type VNodes = Readonly>; @@ -29,6 +30,13 @@ export interface BaseVNode { owner: VM; } +export interface VStatic extends BaseVNode { + type: VNodeType.Static; + sel: undefined; + key: Key; + fragment: Element; +} + export interface VText extends BaseVNode { type: VNodeType.Text; sel: undefined; diff --git a/packages/@lwc/engine-dom/src/index.ts b/packages/@lwc/engine-dom/src/index.ts index fec06f876c..510c5a6064 100644 --- a/packages/@lwc/engine-dom/src/index.ts +++ b/packages/@lwc/engine-dom/src/index.ts @@ -11,6 +11,9 @@ import './polyfills/aria-properties/main'; // Tests ------------------------------------------------------------------------------------------- import './testFeatureFlag.ts'; +// Tests ------------------------------------------------------------------------------------------- +import './testFeatureFlag.ts'; + // Engine-core public APIs ------------------------------------------------------------------------- export { createContextProvider, @@ -30,6 +33,8 @@ export { setHooks, getComponentDef, isComponentConstructor, + parseFragment, + parseSVGFragment, swapComponent, swapStyle, swapTemplate, diff --git a/packages/@lwc/engine-dom/src/renderer.ts b/packages/@lwc/engine-dom/src/renderer.ts index 313d642e1d..e2d6f5c052 100644 --- a/packages/@lwc/engine-dom/src/renderer.ts +++ b/packages/@lwc/engine-dom/src/renderer.ts @@ -100,6 +100,14 @@ export const isSyntheticShadowDefined: boolean = hasOwnProperty.call( KEY__SHADOW_TOKEN ); +function cloneNode(node: Node, deep: boolean): Node { + return node.cloneNode(deep); +} + +function createFragment(html: string): Node | null { + return document.createRange().createContextualFragment(html).firstChild; +} + function createElement(tagName: string, namespace?: string): Element { return isUndefined(namespace) ? document.createElement(tagName) @@ -286,6 +294,8 @@ export const renderer = { isHydrating, insert, remove, + cloneNode, + createFragment, createElement, createText, createComment, diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-static/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-static/expected.html index 04a6a3b5e2..f80834225d 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-static/expected.html +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-static/expected.html @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-style/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-style/expected.html index ab7774509d..55f49c209f 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-style/expected.html +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/attribute-style/expected.html @@ -1,12 +1,12 @@