@chr33s/solarflare 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +52 -0
- package/readme.md +183 -0
- package/src/ast.ts +316 -0
- package/src/build.bundle-client.ts +404 -0
- package/src/build.bundle-server.ts +131 -0
- package/src/build.bundle.ts +48 -0
- package/src/build.emit-manifests.ts +25 -0
- package/src/build.hmr-entry.ts +88 -0
- package/src/build.scan.ts +182 -0
- package/src/build.ts +227 -0
- package/src/build.validate.ts +63 -0
- package/src/client.hmr.ts +78 -0
- package/src/client.styles.ts +68 -0
- package/src/client.ts +190 -0
- package/src/codemod.ts +688 -0
- package/src/console-forward.ts +254 -0
- package/src/critical-css.ts +103 -0
- package/src/devtools-json.ts +52 -0
- package/src/diff-dom-streaming.ts +406 -0
- package/src/early-flush.ts +125 -0
- package/src/early-hints.ts +83 -0
- package/src/fetch.ts +44 -0
- package/src/fs.ts +11 -0
- package/src/head.ts +876 -0
- package/src/hmr.ts +647 -0
- package/src/hydration.ts +238 -0
- package/src/manifest.runtime.ts +25 -0
- package/src/manifest.ts +23 -0
- package/src/paths.ts +96 -0
- package/src/render-priority.ts +69 -0
- package/src/route-cache.ts +163 -0
- package/src/router-deferred.ts +85 -0
- package/src/router-stream.ts +65 -0
- package/src/router.ts +535 -0
- package/src/runtime.ts +32 -0
- package/src/serialize.ts +38 -0
- package/src/server.hmr.ts +67 -0
- package/src/server.styles.ts +42 -0
- package/src/server.ts +480 -0
- package/src/solarflare.d.ts +101 -0
- package/src/speculation-rules.ts +171 -0
- package/src/store.ts +78 -0
- package/src/stream-assets.ts +135 -0
- package/src/stylesheets.ts +222 -0
- package/src/worker.config.ts +243 -0
- package/src/worker.ts +542 -0
- package/tsconfig.json +21 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { type FunctionComponent } from "preact";
|
|
2
|
+
import register from "preact-custom-element";
|
|
3
|
+
import { parsePath } from "./paths.ts";
|
|
4
|
+
import { hydrateStore, initHydrationCoordinator } from "./hydration.ts";
|
|
5
|
+
import { installHeadHoisting, createHeadContext, setHeadContext } from "./head.ts";
|
|
6
|
+
import { getRuntime } from "./runtime.ts";
|
|
7
|
+
import { stylesheets, supportsConstructableStylesheets } from "./stylesheets.ts";
|
|
8
|
+
import { getPreloadedStylesheet } from "./server.styles.ts";
|
|
9
|
+
|
|
10
|
+
export { initHmrEntry, reloadAllStylesheets } from "./hmr.ts";
|
|
11
|
+
export { hmr } from "./client.hmr.ts";
|
|
12
|
+
export { Deferred } from "./render-priority.ts";
|
|
13
|
+
export { navigate } from "./router.ts";
|
|
14
|
+
|
|
15
|
+
/** Initializes client-side store from SSR hydration data. */
|
|
16
|
+
export async function initClient() {
|
|
17
|
+
const runtime = getRuntime();
|
|
18
|
+
runtime.headContext ??= createHeadContext();
|
|
19
|
+
setHeadContext(runtime.headContext);
|
|
20
|
+
installHeadHoisting();
|
|
21
|
+
|
|
22
|
+
await hydrateStore();
|
|
23
|
+
initHydrationCoordinator();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Inline stylesheet entry for dev HMR registration. */
|
|
27
|
+
export interface InlineStyleEntry {
|
|
28
|
+
id: string;
|
|
29
|
+
css: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Registers inline stylesheets for a component (dev HMR). */
|
|
33
|
+
export function registerInlineStyles(tag: string, styles: InlineStyleEntry[]) {
|
|
34
|
+
if (!styles.length) return;
|
|
35
|
+
if (!supportsConstructableStylesheets() || typeof document === "undefined") return;
|
|
36
|
+
|
|
37
|
+
for (const style of styles) {
|
|
38
|
+
const preloaded = getPreloadedStylesheet(style.id);
|
|
39
|
+
if (!preloaded) {
|
|
40
|
+
stylesheets.register(style.id, style.css, { consumer: tag });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sheets = stylesheets.getForConsumer(tag);
|
|
45
|
+
document.adoptedStyleSheets = [
|
|
46
|
+
...document.adoptedStyleSheets.filter((s) => !sheets.includes(s)),
|
|
47
|
+
...sheets,
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Tag metadata from file path. */
|
|
52
|
+
export interface TagMeta {
|
|
53
|
+
tag: string;
|
|
54
|
+
filePath: string;
|
|
55
|
+
segments: string[];
|
|
56
|
+
/** Dynamic param names (from $param). */
|
|
57
|
+
paramNames: string[];
|
|
58
|
+
isRoot: boolean;
|
|
59
|
+
type: "client" | "server" | "unknown";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Validation result for tag generation. */
|
|
63
|
+
export interface TagValidation {
|
|
64
|
+
valid: boolean;
|
|
65
|
+
errors: string[];
|
|
66
|
+
warnings: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Parses file path into structured tag metadata. */
|
|
70
|
+
export function parseTagMeta(path: string) {
|
|
71
|
+
const parsed = parsePath(path);
|
|
72
|
+
|
|
73
|
+
const type: TagMeta["type"] =
|
|
74
|
+
parsed.kind === "client" || parsed.kind === "server" ? parsed.kind : "unknown";
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
tag: parsed.tag,
|
|
78
|
+
filePath: parsed.original,
|
|
79
|
+
segments: parsed.segments,
|
|
80
|
+
paramNames: parsed.params,
|
|
81
|
+
isRoot: parsed.isIndex,
|
|
82
|
+
type,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Validates a tag against web component naming rules. */
|
|
87
|
+
export function validateTag(meta: TagMeta) {
|
|
88
|
+
const errors: string[] = [];
|
|
89
|
+
const warnings: string[] = [];
|
|
90
|
+
|
|
91
|
+
if (!meta.tag.includes("-")) {
|
|
92
|
+
errors.push(`Tag "${meta.tag}" must contain a hyphen for custom elements`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!/^[a-z]/.test(meta.tag)) {
|
|
96
|
+
errors.push(`Tag "${meta.tag}" must start with a lowercase letter`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const reservedPrefixes = ["xml", "xlink", "xmlns"];
|
|
100
|
+
for (const prefix of reservedPrefixes) {
|
|
101
|
+
if (meta.tag.toLowerCase().startsWith(prefix)) {
|
|
102
|
+
errors.push(`Tag "${meta.tag}" must not start with reserved prefix "${prefix}"`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!/^[a-z][a-z0-9-]*$/.test(meta.tag)) {
|
|
107
|
+
errors.push(
|
|
108
|
+
`Tag "${meta.tag}" contains invalid characters (only lowercase letters, numbers, and hyphens allowed)`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (meta.tag.length > 50) {
|
|
113
|
+
warnings.push(
|
|
114
|
+
`Tag "${meta.tag}" is very long (${meta.tag.length} chars), consider shorter path`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (meta.type === "server") {
|
|
119
|
+
warnings.push(
|
|
120
|
+
`Server component "${meta.filePath}" should not be registered as a custom element`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (meta.type === "unknown") {
|
|
125
|
+
warnings.push(
|
|
126
|
+
`Component "${meta.filePath}" has unknown type (missing .client. or .server. suffix)`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
valid: errors.length === 0,
|
|
132
|
+
errors,
|
|
133
|
+
warnings,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface DefineOptions {
|
|
138
|
+
/** Custom element tag name. @default generated from file path */
|
|
139
|
+
tag?: string;
|
|
140
|
+
/** Use Shadow DOM. @default false */
|
|
141
|
+
shadow?: boolean;
|
|
142
|
+
/** Observed attributes. @default auto-extracted */
|
|
143
|
+
observedAttributes?: string[];
|
|
144
|
+
/** Validate tag in dev mode. @default true */
|
|
145
|
+
validate?: boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Registers a Preact component as a web component (build-time macro). */
|
|
149
|
+
export function define<P extends Record<string, any>>(
|
|
150
|
+
Component: FunctionComponent<P>,
|
|
151
|
+
options?: DefineOptions,
|
|
152
|
+
) {
|
|
153
|
+
// Only register custom elements in the browser
|
|
154
|
+
if (typeof window !== "undefined" && typeof HTMLElement !== "undefined") {
|
|
155
|
+
const propNames = options?.observedAttributes ?? [];
|
|
156
|
+
const filePath = import.meta.path ?? import.meta.url ?? "";
|
|
157
|
+
const meta = parseTagMeta(filePath);
|
|
158
|
+
const tag = options?.tag ?? meta.tag;
|
|
159
|
+
const shadow = options?.shadow ?? false;
|
|
160
|
+
const shouldValidate = options?.validate ?? import.meta.env?.DEV ?? false;
|
|
161
|
+
|
|
162
|
+
if (customElements.get(tag)) {
|
|
163
|
+
console.warn(`[solarflare] Custom element "${tag}" is already registered, skipping`);
|
|
164
|
+
return Component;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (shouldValidate) {
|
|
168
|
+
const validation = validateTag({ ...meta, tag });
|
|
169
|
+
|
|
170
|
+
for (const warning of validation.warnings) {
|
|
171
|
+
console.warn(`[solarflare] ${warning}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const error of validation.errors) {
|
|
175
|
+
console.error(`[solarflare] ${error}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!validation.valid) {
|
|
179
|
+
console.error(
|
|
180
|
+
`[solarflare] Tag validation failed for "${filePath}", component may not work correctly`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Register the component as a custom element
|
|
186
|
+
register(Component, tag, propNames, { shadow });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return Component;
|
|
190
|
+
}
|