Skip to content

Commit

Permalink
wip: dirty compiler to output create static fragment
Browse files Browse the repository at this point in the history
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](caolan/async@v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

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 611fc34.

* 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 72afdc0.

* 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 <p.dartus@salesforce.com>

* fix: remove semi

* fix: remove "used" flag

* fix: refactor

* fix: refactor

* fix: bring back "used" flag

* fix: typo

Co-authored-by: Pierre-Marie Dartus <p.dartus@salesforce.com>

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 <ekashida@gmail.com>

Co-authored-by: Eugene Kashida <ekashida@gmail.com>

chore: fix lint

test: refactor test, remove test covered in #2859

test: on second thought, bring test back
  • Loading branch information
jodarove authored and nolanlawson committed Jun 7, 2022
1 parent cc0cd60 commit 454fd74
Show file tree
Hide file tree
Showing 139 changed files with 1,527 additions and 1,476 deletions.
15 changes: 15 additions & 0 deletions packages/@lwc/engine-core/src/framework/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
VComment,
VElementData,
VNodeType,
VStatic,
Key,
} from './vnodes';

const SymbolIterator: typeof Symbol.iterator = Symbol.iterator;
Expand All @@ -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()!;
Expand Down Expand Up @@ -546,6 +560,7 @@ const api = ObjectFreeze({
co,
dc,
ti,
st,
gid,
fid,
shc,
Expand Down
74 changes: 74 additions & 0 deletions packages/@lwc/engine-core/src/framework/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
VComment,
VElement,
VCustomElement,
VStatic,
} from './vnodes';

import { patchProps } from './modules/props';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<Element>(vnode, elm, EnvNodeTypes.ELEMENT, renderer) ||
Expand Down Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions packages/@lwc/engine-core/src/framework/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export {
getAssociatedVMIfPresent,
} from './vm';

export { parseFragment, parseSVGFragment } from './template';
export { hydrateRoot } from './hydration';

// Internal APIs used by compiled code -------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions packages/@lwc/engine-core/src/framework/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
40 changes: 40 additions & 0 deletions packages/@lwc/engine-core/src/framework/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
keys,
SVG_NAMESPACE,
KEY__SHADOW_RESOLVER,
KEY__SHADOW_STATIC,
} from '@lwc/shared';

import { RendererAPI } from './renderer';
Expand Down Expand Up @@ -48,6 +49,7 @@ import {
isVBaseElement,
isSameVnode,
VNodeType,
VStatic,
} from './vnodes';

import { patchAttributes } from './modules/attrs';
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
82 changes: 77 additions & 5 deletions packages/@lwc/engine-core/src/framework/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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('<svg>' + html + '</svg>');

return getFirstChild(fragment);
});

export function evaluateTemplate(vm: VM, html: Template): VNodes {
if (process.env.NODE_ENV !== 'production') {
assert.isTrue(
Expand Down
2 changes: 1 addition & 1 deletion packages/@lwc/engine-core/src/framework/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,4 +752,4 @@ export function forceRehydration(vm: VM) {
markComponentAsDirty(vm);
scheduleRehydration(vm);
}
}
}
10 changes: 9 additions & 1 deletion packages/@lwc/engine-core/src/framework/vnodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Array<VNode | null>>;

Expand All @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions packages/@lwc/engine-dom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import './polyfills/aria-properties/main';
// Tests -------------------------------------------------------------------------------------------
import './testFeatureFlag.ts';

// Tests -------------------------------------------------------------------------------------------
import './testFeatureFlag.ts';

// Engine-core public APIs -------------------------------------------------------------------------
export {
createContextProvider,
Expand All @@ -30,6 +33,8 @@ export {
setHooks,
getComponentDef,
isComponentConstructor,
parseFragment,
parseSVGFragment,
swapComponent,
swapStyle,
swapTemplate,
Expand Down
Loading

0 comments on commit 454fd74

Please sign in to comment.