Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { LightningElement as Component } from "lwc";

export default class Test extends Component {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"enableSyntheticElementInternals": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import _tmpl from "./test.html";
import { LightningElement as Component, registerComponent as _registerComponent } from "lwc";
class Test extends Component {
/*LWC compiler vX.X.X*/
}
const __lwc_component_class_internal = _registerComponent(Test, {
tmpl: _tmpl,
sel: "lwc-test",
apiVersion: 9999999,
enableSyntheticElementInternals: true
});
export default __lwc_component_class_internal;
28 changes: 21 additions & 7 deletions packages/@lwc/babel-plugin-component/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
TEMPLATE_KEY,
API_VERSION_KEY,
COMPONENT_CLASS_ID,
SYNTHETIC_ELEMENT_INTERNALS_KEY,
} from './constants';
import type { types, NodePath, Visitor } from '@babel/core';
import type { BabelAPI, BabelTypes, LwcBabelPluginPass } from './types';
Expand Down Expand Up @@ -81,15 +82,28 @@ export default function ({ types: t }: BabelAPI): Visitor<LwcBabelPluginPass> {
// sel: 'x-foo',
// apiVersion: '58'
// })
const properties = [
t.objectProperty(t.identifier(TEMPLATE_KEY), templateIdentifier),
t.objectProperty(t.identifier(COMPONENT_NAME_KEY), componentRegisteredName),
// It's important that, at this point, we have an APIVersion rather than just a number.
// The client needs to trust the server that it's providing an actual known API version
t.objectProperty(t.identifier(API_VERSION_KEY), t.numericLiteral(apiVersion)),
];
// Only include enableSyntheticElementInternals if set to true
if (state.opts.enableSyntheticElementInternals === true) {
const supportsSyntheticElementInternals = t.booleanLiteral(
state.opts.enableSyntheticElementInternals || false
);
properties.push(
t.objectProperty(
t.identifier(SYNTHETIC_ELEMENT_INTERNALS_KEY),
supportsSyntheticElementInternals
)
);
}
const registerComponentExpression = t.callExpression(registerComponentId, [
node as types.Expression,
t.objectExpression([
t.objectProperty(t.identifier(TEMPLATE_KEY), templateIdentifier),
t.objectProperty(t.identifier(COMPONENT_NAME_KEY), componentRegisteredName),
// It's important that, at this point, we have an APIVersion rather than just a number.
// The client needs to trust the server that it's providing an actual known API version
t.objectProperty(t.identifier(API_VERSION_KEY), t.numericLiteral(apiVersion)),
]),
t.objectExpression(properties),
]);

// Example:
Expand Down
2 changes: 2 additions & 0 deletions packages/@lwc/babel-plugin-component/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const TEMPLATE_KEY = 'tmpl';
const COMPONENT_NAME_KEY = 'sel';
const API_VERSION_KEY = 'apiVersion';
const COMPONENT_CLASS_ID = '__lwc_component_class_internal';
const SYNTHETIC_ELEMENT_INTERNALS_KEY = 'enableSyntheticElementInternals';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 if this were written 8 years ago, we'd probably call it sei or something silly. I don't think we still care about that, though...


export {
DECORATOR_TYPES,
Expand All @@ -46,4 +47,5 @@ export {
COMPONENT_NAME_KEY,
API_VERSION_KEY,
COMPONENT_CLASS_ID,
SYNTHETIC_ELEMENT_INTERNALS_KEY,
};
1 change: 1 addition & 0 deletions packages/@lwc/babel-plugin-component/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface LwcBabelPluginOptions {
name: string;
instrumentation?: InstrumentationObject;
apiVersion?: number;
enableSyntheticElementInternals?: boolean;
}

export interface LwcBabelPluginPass extends PluginPass {
Expand Down
3 changes: 3 additions & 0 deletions packages/@lwc/compiler/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export interface TransformOptions {
experimentalDynamicDirective?: boolean;
/** Flag to enable usage of dynamic component(lwc:is) directive in HTML template */
enableDynamicComponents?: boolean;
/** Flag to enable usage of ElementInternals in synthetic shadow DOM */
enableSyntheticElementInternals?: boolean;
// TODO [#3370]: remove experimental template expression flag
/** Flag to enable use of (a subset of) JavaScript expressions in place of template bindings. Passed to `@lwc/template-compiler`. */
experimentalComplexExpressions?: boolean;
Expand Down Expand Up @@ -153,6 +155,7 @@ type OptionalTransformKeys =
| 'enableLwcOn'
| 'enableLightningWebSecurityTransforms'
| 'enableDynamicComponents'
| 'enableSyntheticElementInternals'
| 'experimentalDynamicDirective'
| 'experimentalDynamicComponent'
| 'instrumentation';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ import {
} from '../libs/reflection';

import { HTMLElementOriginalDescriptors } from './html-properties';
import { getComponentAPIVersion, getWrappedComponentsListener } from './component';
import {
getComponentAPIVersion,
getWrappedComponentsListener,
supportsSyntheticElementInternals,
} from './component';
import { isBeingConstructed, isInvokingRender, vmBeingConstructed } from './invoker';
import { associateVM, getAssociatedVM, RenderMode, ShadowMode } from './vm';
import { componentValueObserved } from './mutation-tracker';
Expand Down Expand Up @@ -493,6 +497,7 @@ function warnIfInvokedDuringConstruction(vm: VM, methodOrPropName: string) {
attachInternals(): ElementInternals {
const vm = getAssociatedVM(this);
const {
def: { ctor },
elm,
apiVersion,
renderer: { attachInternals },
Expand All @@ -506,11 +511,26 @@ function warnIfInvokedDuringConstruction(vm: VM, methodOrPropName: string) {
);
}

if (vm.shadowMode === ShadowMode.Synthetic) {
throw new Error('attachInternals API is not supported in synthetic shadow.');
const internals = attachInternals(elm);
if (vm.shadowMode === ShadowMode.Synthetic && supportsSyntheticElementInternals(ctor)) {
const handler: ProxyHandler<ElementInternals> = {
get(target: ElementInternals, prop: keyof ElementInternals) {
if (prop === 'shadowRoot') {
return vm.shadowRoot;
}
const value = Reflect.get(target, prop);
if (typeof value === 'function') {
return value.bind(target);
}
return value;
},
set(target: ElementInternals, prop: keyof ElementInternals, value: any) {
return Reflect.set(target, prop, value);
},
};
return new Proxy(internals, handler);
}

return attachInternals(elm);
return internals;
},

get isConnected(): boolean {
Expand Down
5 changes: 5 additions & 0 deletions packages/@lwc/engine-core/src/framework/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type ComponentConstructorMetadata = {
tmpl: Template;
sel: string;
apiVersion: APIVersion;
enableSyntheticElementInternals?: boolean | undefined;
};
const registeredComponentMap: Map<LightningElementConstructor, ComponentConstructorMetadata> =
new Map();
Expand Down Expand Up @@ -76,6 +77,10 @@ export function getComponentAPIVersion(Ctor: LightningElementConstructor): APIVe
return apiVersion;
}

export function supportsSyntheticElementInternals(Ctor: LightningElementConstructor): boolean {
return registeredComponentMap.get(Ctor)?.enableSyntheticElementInternals || false;
}

export function getTemplateReactiveObserver(vm: VM): ReactiveObserver {
const reactiveObserver = createReactiveObserver(() => {
const { isDirty } = vm;
Expand Down
8 changes: 0 additions & 8 deletions packages/@lwc/engine-core/src/framework/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,14 +972,6 @@ export function forceRehydration(vm: VM) {
}

export function runFormAssociatedCustomElementCallback(vm: VM, faceCb: () => void, args?: any[]) {
const { renderMode, shadowMode } = vm;

if (shadowMode === ShadowMode.Synthetic && renderMode !== RenderMode.Light) {
throw new Error(
'Form associated lifecycle methods are not available in synthetic shadow. Please use native shadow or light DOM.'
);
}

invokeComponentCallback(vm, faceCb, args);
}

Expand Down
1 change: 1 addition & 0 deletions packages/@lwc/integration-not-karma/helpers/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ hijackGlobal('afterEach', (afterEach) => {
// Ensure the DOM is in a clean state
document.body.replaceChildren();
document.head.replaceChildren();
window.__lwcResetGlobalStylesheets();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without this the stylesheet was removed after the first test, but since it was cached it wouldn't get re-added - meaning that the styles were only actually present for the first test

});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<template></template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { LightningElement, api } from 'lwc';

export default class extends LightningElement {
internals;

connectedCallback() {
this.internals = this.attachInternals();
}

@api
callAttachInternals() {
this.internals = this.attachInternals();
}

@api
hasElementInternalsBeenSet() {
return Boolean(this.internals);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createElement } from 'lwc';

import ShadowDomCmp from 'ai/shadowDom';
import SyntheticShadowDomCmp from 'ai/syntheticShadowDom';
import LightDomCmp from 'ai/lightDom';
import BasicCmp from 'ai/basic';
import {
Expand Down Expand Up @@ -67,13 +68,7 @@ describe.runIf(ENABLE_ELEMENT_INTERNALS_AND_FACE)('ElementInternals', () => {
});

describe.skipIf(process.env.NATIVE_SHADOW)('synthetic shadow', () => {
it('should throw error when used inside a component', () => {
const elm = createElement('synthetic-shadow', { is: ShadowDomCmp });
testConnectedCallbackError(
elm,
'attachInternals API is not supported in synthetic shadow.'
);
});
attachInternalsSanityTest('synthetic-shadow', SyntheticShadowDomCmp);
});

describe('light DOM', () => {
Expand Down
Loading