aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/svelte/client.js
blob: 056fd19c77fb72310ec305b38140ef960db9e7e4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
const noop = () => {};

let originalConsoleWarning;
let consoleFilterRefs = 0;

export default (element) => {
	return (Component, props, slotted, { client }) => {
		if (!element.hasAttribute('ssr')) return;
		const slots = {};
		for (const [key, value] of Object.entries(slotted)) {
			slots[key] = createSlotDefinition(key, value);
		}

		try {
			if (import.meta.env.DEV) useConsoleFilter();

			const component = new Component({
				target: element,
				props: {
					...props,
					$$slots: slots,
					$$scope: { ctx: [] },
				},
				hydrate: client !== 'only',
				$$inline: true,
			});

			element.addEventListener('astro:unmount', () => component.$destroy(), { once: true });
		} finally {
			if (import.meta.env.DEV) finishUsingConsoleFilter();
		}
	};
};

function createSlotDefinition(key, children) {
	let parent;
	return [
		() => ({
			// mount
			m(target) {
				parent = target;
				target.insertAdjacentHTML(
					'beforeend',
					`<astro-slot${key === 'default' ? '' : ` name="${key}"`}>${children}</astro-slot>`
				);
			},
			// create
			c: noop,
			// hydrate
			l: noop,
			// destroy
			d() {
				if (!parent) return;
				const slot = parent.querySelector(
					`astro-slot${key === 'default' ? ':not([name])' : `[name="${key}"]`}`
				);
				if (slot) slot.remove();
			},
		}),
		noop,
		noop,
	];
}

/**
 * Reduces console noise by filtering known non-problematic warnings.
 *
 * Performs reference counting to allow parallel usage from async code.
 *
 * To stop filtering, please ensure that there always is a matching call
 * to `finishUsingConsoleFilter` afterwards.
 */
function useConsoleFilter() {
	consoleFilterRefs++;

	if (!originalConsoleWarning) {
		originalConsoleWarning = console.warn;
		try {
			console.warn = filteredConsoleWarning;
		} catch {
			// If we're unable to hook `console.warn`, just accept it
		}
	}
}

/**
 * Indicates that the filter installed by `useConsoleFilter`
 * is no longer needed by the calling code.
 */
function finishUsingConsoleFilter() {
	consoleFilterRefs--;

	// Note: Instead of reverting `console.warning` back to the original
	// when the reference counter reaches 0, we leave our hook installed
	// to prevent potential race conditions once `check` is made async
}

/**
 * Hook/wrapper function for the global `console.warning` function.
 *
 * Ignores known non-problematic errors while any code is using the console filter.
 * Otherwise, simply forwards all arguments to the original function.
 */
function filteredConsoleWarning(msg, ...rest) {
	if (consoleFilterRefs > 0 && typeof msg === 'string') {
		// Astro passes `class` and `data-astro-cid` props to the Svelte component, which
		// outputs the following warning, which we can safely filter out.

		// NOTE: In practice data-astro-cid props have a hash suffix. Hence the use of a
		// quoted prop name string without a closing quote.

		const isKnownSvelteError =
			msg.endsWith("was created with unknown prop 'class'") ||
			msg.includes("was created with unknown prop 'data-astro-cid");
		if (isKnownSvelteError) return;
	}
	originalConsoleWarning(msg, ...rest);
}