-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
Copy pathserver.ts
154 lines (132 loc) · 4.41 KB
/
server.ts
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import type { AstroComponentMetadata } from 'astro';
import { Component as BaseComponent, h, type VNode } from 'preact';
import prepass from 'preact-ssr-prepass';
import { render } from 'preact-render-to-string';
import { getContext } from './context.js';
import { restoreSignalsOnProps, serializeSignals } from './signals.js';
import StaticHtml from './static-html.js';
import type { AstroPreactAttrs, RendererContext } from './types.js';
const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
let originalConsoleError: typeof console.error;
let consoleFilterRefs = 0;
async function check(
this: RendererContext,
Component: any,
props: Record<string, any>,
children: any
) {
if (typeof Component !== 'function') return false;
if (Component.name === 'QwikComponent') return false;
if (Component.prototype != null && typeof Component.prototype.render === 'function') {
return BaseComponent.isPrototypeOf(Component);
}
useConsoleFilter();
try {
try {
const { html } = await renderToStaticMarkup.call(this, Component, props, children, undefined);
if (typeof html !== 'string') {
return false;
}
// There are edge cases (SolidJS) where Preact *might* render a string,
// but components would be <undefined></undefined>
// It also might render an empty sting.
return html == '' ? false : !/\<undefined\>/.test(html);
} catch (err) {
return false;
}
} finally {
finishUsingConsoleFilter();
}
}
function shouldHydrate(metadata: AstroComponentMetadata | undefined) {
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
return metadata?.astroStaticSlot ? !!metadata.hydrate : true;
}
async function renderToStaticMarkup(
this: RendererContext,
Component: any,
props: Record<string, any>,
{ default: children, ...slotted }: Record<string, any>,
metadata: AstroComponentMetadata | undefined
) {
const ctx = getContext(this.result);
const slots: Record<string, ReturnType<typeof h>> = {};
for (const [key, value] of Object.entries(slotted)) {
const name = slotName(key);
slots[name] = h(StaticHtml, {
hydrate: shouldHydrate(metadata),
value,
name,
}) as VNode<any>;
}
// Restore signals back onto props so that they will be passed as-is to components
let propsMap = restoreSignalsOnProps(ctx, props);
const newProps = { ...props, ...slots };
const attrs: AstroPreactAttrs = {};
serializeSignals(ctx, props, attrs, propsMap);
const vNode: VNode<any> = h(
Component,
newProps,
children != null
? h(StaticHtml, {
hydrate: shouldHydrate(metadata),
value: children,
})
: children
);
await prepass(vNode);
const html = render(vNode);
return { attrs, html };
}
/**
* Reduces console noise by filtering known non-problematic errors.
*
* 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 (!originalConsoleError) {
originalConsoleError = console.error;
try {
console.error = filteredConsoleError;
} catch (error) {
// If we're unable to hook `console.error`, 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.error` 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.error` function.
*
* Ignores known non-problematic errors while any code is using the console filter.
* Otherwise, simply forwards all arguments to the original function.
*/
function filteredConsoleError(msg: string, ...rest: any[]) {
if (consoleFilterRefs > 0 && typeof msg === 'string') {
// In `check`, we attempt to render JSX components through Preact.
// When attempting this on a React component, React may output
// the following error, which we can safely filter out:
const isKnownReactHookError =
msg.includes('Warning: Invalid hook call.') &&
msg.includes('https://reactjs.org/link/invalid-hook-call');
if (isKnownReactHookError) return;
}
originalConsoleError(msg, ...rest);
}
export default {
check,
renderToStaticMarkup,
supportsAstroStaticSlot: true,
};