Skip to content

Commit

Permalink
fix: shrink custom element baseline a bit (#8858)
Browse files Browse the repository at this point in the history
- use shorter class property names as they are not minified
- reuse some dom helper methods

#8826
  • Loading branch information
dummdidumm authored Jul 3, 2023
1 parent 2f5e371 commit 4b3eb72
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 70 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-cows-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: shrink custom element baseline a bit
136 changes: 66 additions & 70 deletions packages/svelte/src/runtime/internal/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
start_hydrating,
end_hydrating,
get_custom_elements_slots,
insert
insert,
element,
attr
} from './dom.js';
import { transition_in } from './transitions.js';

Expand Down Expand Up @@ -157,23 +159,29 @@ export let SvelteElement;

if (typeof HTMLElement === 'function') {
SvelteElement = class extends HTMLElement {
$$componentCtor;
$$slots;
$$component;
$$connected = false;
$$data = {};
$$reflecting = false;
/** @type {Record<string, CustomElementPropDefinition>} */
$$props_definition = {};
/** @type {Record<string, Function[]>} */
$$listeners = {};
/** @type {Map<Function, Function>} */
$$listener_unsubscribe_fns = new Map();
/** The Svelte component constructor */
$$ctor;
/** Slots */
$$s;
/** The Svelte component instance */
$$c;
/** Whether or not the custom element is connected */
$$cn = false;
/** Component props data */
$$d = {};
/** `true` if currently in the process of reflecting component props back to attributes */
$$r = false;
/** @type {Record<string, CustomElementPropDefinition>} Props definition (name, reflected, type etc) */
$$p_d = {};
/** @type {Record<string, Function[]>} Event listeners */
$$l = {};
/** @type {Map<Function, Function>} Event listener unsubscribe functions */
$$l_u = new Map();

constructor($$componentCtor, $$slots, use_shadow_dom) {
super();
this.$$componentCtor = $$componentCtor;
this.$$slots = $$slots;
this.$$ctor = $$componentCtor;
this.$$s = $$slots;
if (use_shadow_dom) {
this.attachShadow({ mode: 'open' });
}
Expand All @@ -183,42 +191,42 @@ if (typeof HTMLElement === 'function') {
// We can't determine upfront if the event is a custom event or not, so we have to
// listen to both. If someone uses a custom event with the same name as a regular
// browser event, this fires twice - we can't avoid that.
this.$$listeners[type] = this.$$listeners[type] || [];
this.$$listeners[type].push(listener);
if (this.$$component) {
const unsub = this.$$component.$on(type, listener);
this.$$listener_unsubscribe_fns.set(listener, unsub);
this.$$l[type] = this.$$l[type] || [];
this.$$l[type].push(listener);
if (this.$$c) {
const unsub = this.$$c.$on(type, listener);
this.$$l_u.set(listener, unsub);
}
super.addEventListener(type, listener, options);
}

removeEventListener(type, listener, options) {
super.removeEventListener(type, listener, options);
if (this.$$component) {
const unsub = this.$$listener_unsubscribe_fns.get(listener);
if (this.$$c) {
const unsub = this.$$l_u.get(listener);
if (unsub) {
unsub();
this.$$listener_unsubscribe_fns.delete(listener);
this.$$l_u.delete(listener);
}
}
}

async connectedCallback() {
this.$$connected = true;
if (!this.$$component) {
this.$$cn = true;
if (!this.$$c) {
// We wait one tick to let possible child slot elements be created/mounted
await Promise.resolve();
if (!this.$$connected) {
if (!this.$$cn) {
return;
}
function create_slot(name) {
return () => {
let node;
const obj = {
c: function create() {
node = document.createElement('slot');
node = element('slot');
if (name !== 'default') {
node.setAttribute('name', name);
attr(node, 'name', name);
}
},
/**
Expand All @@ -239,74 +247,64 @@ if (typeof HTMLElement === 'function') {
}
const $$slots = {};
const existing_slots = get_custom_elements_slots(this);
for (const name of this.$$slots) {
for (const name of this.$$s) {
if (name in existing_slots) {
$$slots[name] = [create_slot(name)];
}
}
for (const attribute of this.attributes) {
// this.$$data takes precedence over this.attributes
const name = this.$$get_prop_name(attribute.name);
if (!(name in this.$$data)) {
this.$$data[name] = get_custom_element_value(
name,
attribute.value,
this.$$props_definition,
'toProp'
);
const name = this.$$g_p(attribute.name);
if (!(name in this.$$d)) {
this.$$d[name] = get_custom_element_value(name, attribute.value, this.$$p_d, 'toProp');
}
}
this.$$component = new this.$$componentCtor({
this.$$c = new this.$$ctor({
target: this.shadowRoot || this,
props: {
...this.$$data,
...this.$$d,
$$slots,
$$scope: {
ctx: []
}
}
});
for (const type in this.$$listeners) {
for (const listener of this.$$listeners[type]) {
const unsub = this.$$component.$on(type, listener);
this.$$listener_unsubscribe_fns.set(listener, unsub);
for (const type in this.$$l) {
for (const listener of this.$$l[type]) {
const unsub = this.$$c.$on(type, listener);
this.$$l_u.set(listener, unsub);
}
}
this.$$listeners = {};
this.$$l = {};
}
}

// We don't need this when working within Svelte code, but for compatibility of people using this outside of Svelte
// and setting attributes through setAttribute etc, this is helpful
attributeChangedCallback(attr, _oldValue, newValue) {
if (this.$$reflecting) return;
attr = this.$$get_prop_name(attr);
this.$$data[attr] = get_custom_element_value(
attr,
newValue,
this.$$props_definition,
'toProp'
);
this.$$component?.$set({ [attr]: this.$$data[attr] });
if (this.$$r) return;
attr = this.$$g_p(attr);
this.$$d[attr] = get_custom_element_value(attr, newValue, this.$$p_d, 'toProp');
this.$$c?.$set({ [attr]: this.$$d[attr] });
}

disconnectedCallback() {
this.$$connected = false;
this.$$cn = false;
// In a microtask, because this could be a move within the DOM
Promise.resolve().then(() => {
if (!this.$$connected) {
this.$$component.$destroy();
this.$$component = undefined;
if (!this.$$cn) {
this.$$c.$destroy();
this.$$c = undefined;
}
});
}

$$get_prop_name(attribute_name) {
$$g_p(attribute_name) {
return (
Object.keys(this.$$props_definition).find(
Object.keys(this.$$p_d).find(
(key) =>
this.$$props_definition[key].attribute === attribute_name ||
(!this.$$props_definition[key].attribute && key.toLowerCase() === attribute_name)
this.$$p_d[key].attribute === attribute_name ||
(!this.$$p_d[key].attribute && key.toLowerCase() === attribute_name)
) || attribute_name
);
}
Expand Down Expand Up @@ -371,7 +369,7 @@ export function create_custom_element(
const Class = class extends SvelteElement {
constructor() {
super(Component, slots, use_shadow_dom);
this.$$props_definition = props_definition;
this.$$p_d = props_definition;
}
static get observedAttributes() {
return Object.keys(props_definition).map((key) =>
Expand All @@ -382,16 +380,14 @@ export function create_custom_element(
Object.keys(props_definition).forEach((prop) => {
Object.defineProperty(Class.prototype, prop, {
get() {
return this.$$component && prop in this.$$component
? this.$$component[prop]
: this.$$data[prop];
return this.$$c && prop in this.$$c ? this.$$c[prop] : this.$$d[prop];
},
set(value) {
value = get_custom_element_value(prop, value, props_definition);
this.$$data[prop] = value;
this.$$component?.$set({ [prop]: value });
this.$$d[prop] = value;
this.$$c?.$set({ [prop]: value });
if (props_definition[prop].reflect) {
this.$$reflecting = true;
this.$$r = true;
const attribute_value = get_custom_element_value(
prop,
value,
Expand All @@ -403,15 +399,15 @@ export function create_custom_element(
} else {
this.setAttribute(props_definition[prop].attribute || prop, attribute_value);
}
this.$$reflecting = false;
this.$$r = false;
}
}
});
});
accessors.forEach((accessor) => {
Object.defineProperty(Class.prototype, accessor, {
get() {
return this.$$component?.[accessor];
return this.$$c?.[accessor];
}
});
});
Expand Down

0 comments on commit 4b3eb72

Please sign in to comment.