@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
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { type RouteCacheConfig } from "./route-cache.ts";
|
|
2
|
+
import {
|
|
3
|
+
type SpeculationEagerness,
|
|
4
|
+
type SpeculationRules,
|
|
5
|
+
createPrefetchListRule,
|
|
6
|
+
createPrerenderListRule,
|
|
7
|
+
createDocumentRule,
|
|
8
|
+
createSelectorRule,
|
|
9
|
+
} from "./speculation-rules.ts";
|
|
10
|
+
|
|
11
|
+
/** Configuration extracted from meta tags. */
|
|
12
|
+
export interface WorkerMetaConfig {
|
|
13
|
+
/** HTML lang attribute */
|
|
14
|
+
lang: string;
|
|
15
|
+
/** Origins to preconnect to */
|
|
16
|
+
preconnectOrigins: string[];
|
|
17
|
+
/** Cache configuration for this route */
|
|
18
|
+
cacheConfig?: RouteCacheConfig;
|
|
19
|
+
/** Enable early flush */
|
|
20
|
+
earlyFlush: boolean;
|
|
21
|
+
/** Enable critical CSS inlining */
|
|
22
|
+
criticalCss: boolean;
|
|
23
|
+
/** URLs/patterns to prefetch */
|
|
24
|
+
prefetch: string[];
|
|
25
|
+
/** URLs/patterns to prerender */
|
|
26
|
+
prerender: string[];
|
|
27
|
+
/** CSS selector for document-based prefetch rules */
|
|
28
|
+
prefetchSelector?: string;
|
|
29
|
+
/** Eagerness level for speculation rules */
|
|
30
|
+
speculationEagerness: SpeculationEagerness;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Default configuration values. */
|
|
34
|
+
const DEFAULTS: WorkerMetaConfig = {
|
|
35
|
+
lang: "en",
|
|
36
|
+
preconnectOrigins: ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
|
|
37
|
+
earlyFlush: false,
|
|
38
|
+
criticalCss: false,
|
|
39
|
+
prefetch: [],
|
|
40
|
+
prerender: [],
|
|
41
|
+
speculationEagerness: "moderate",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Parses worker configuration from HTML meta tags. */
|
|
45
|
+
export function parseMetaConfig(html: string) {
|
|
46
|
+
const config: WorkerMetaConfig = { ...DEFAULTS };
|
|
47
|
+
|
|
48
|
+
const langMatch = html.match(/<html[^>]*\slang=["']([^"']+)["']/i);
|
|
49
|
+
if (langMatch) {
|
|
50
|
+
config.lang = langMatch[1];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const matchMeta = (name: string) => {
|
|
54
|
+
const pattern1 = new RegExp(`<meta[^>]*name=["']${name}["'][^>]*content=["']([^"']+)["']`, "i");
|
|
55
|
+
const pattern2 = new RegExp(`<meta[^>]*content=["']([^"']+)["'][^>]*name=["']${name}["']`, "i");
|
|
56
|
+
const match = html.match(pattern1) ?? html.match(pattern2);
|
|
57
|
+
return match ? match[1] : null;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const preconnect = matchMeta("sf:preconnect");
|
|
61
|
+
if (preconnect) {
|
|
62
|
+
config.preconnectOrigins = preconnect
|
|
63
|
+
.split(",")
|
|
64
|
+
.map((s) => s.trim())
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const maxAge = matchMeta("sf:cache-max-age");
|
|
69
|
+
const swr = matchMeta("sf:cache-swr");
|
|
70
|
+
|
|
71
|
+
if (maxAge) {
|
|
72
|
+
config.cacheConfig = {
|
|
73
|
+
maxAge: parseInt(maxAge, 10),
|
|
74
|
+
staleWhileRevalidate: swr ? parseInt(swr, 10) : undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const earlyFlush = matchMeta("sf:early-flush");
|
|
79
|
+
if (earlyFlush) {
|
|
80
|
+
config.earlyFlush = earlyFlush === "true";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const criticalCss = matchMeta("sf:critical-css");
|
|
84
|
+
if (criticalCss) {
|
|
85
|
+
config.criticalCss = criticalCss === "true";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const prefetch = matchMeta("sf:prefetch");
|
|
89
|
+
if (prefetch) {
|
|
90
|
+
config.prefetch = prefetch
|
|
91
|
+
.split(",")
|
|
92
|
+
.map((s) => s.trim())
|
|
93
|
+
.filter(Boolean);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const prerender = matchMeta("sf:prerender");
|
|
97
|
+
if (prerender) {
|
|
98
|
+
config.prerender = prerender
|
|
99
|
+
.split(",")
|
|
100
|
+
.map((s) => s.trim())
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const prefetchSelector = matchMeta("sf:prefetch-selector");
|
|
105
|
+
if (prefetchSelector) {
|
|
106
|
+
config.prefetchSelector = prefetchSelector;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const eagerness = matchMeta("sf:speculation-eagerness");
|
|
110
|
+
if (eagerness) {
|
|
111
|
+
config.speculationEagerness = eagerness as SpeculationEagerness;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return config;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generates meta tags for worker configuration. Use with useHead() in _layout.tsx.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```tsx
|
|
122
|
+
* import { useHead } from "solarflare";
|
|
123
|
+
* import { workerConfigMeta } from "solarflare/client";
|
|
124
|
+
*
|
|
125
|
+
* export default function BlogLayout({ children }) {
|
|
126
|
+
* useHead({
|
|
127
|
+
* meta: workerConfigMeta({
|
|
128
|
+
* cacheMaxAge: 300,
|
|
129
|
+
* cacheSwr: 3600,
|
|
130
|
+
* }),
|
|
131
|
+
* });
|
|
132
|
+
* return <>{children}</>;
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function workerConfigMeta(config: {
|
|
137
|
+
preconnect?: string[];
|
|
138
|
+
cacheMaxAge?: number;
|
|
139
|
+
cacheSwr?: number;
|
|
140
|
+
earlyFlush?: boolean;
|
|
141
|
+
criticalCss?: boolean;
|
|
142
|
+
prefetch?: string[];
|
|
143
|
+
prerender?: string[];
|
|
144
|
+
prefetchSelector?: string;
|
|
145
|
+
speculationEagerness?: SpeculationEagerness;
|
|
146
|
+
}) {
|
|
147
|
+
const meta: Array<{ name: string; content: string }> = [];
|
|
148
|
+
|
|
149
|
+
if (config.preconnect?.length) {
|
|
150
|
+
meta.push({ name: "sf:preconnect", content: config.preconnect.join(",") });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (config.cacheMaxAge !== undefined) {
|
|
154
|
+
meta.push({
|
|
155
|
+
name: "sf:cache-max-age",
|
|
156
|
+
content: String(config.cacheMaxAge),
|
|
157
|
+
});
|
|
158
|
+
if (config.cacheSwr !== undefined) {
|
|
159
|
+
meta.push({ name: "sf:cache-swr", content: String(config.cacheSwr) });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (config.earlyFlush !== undefined) {
|
|
164
|
+
meta.push({ name: "sf:early-flush", content: String(config.earlyFlush) });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (config.criticalCss !== undefined) {
|
|
168
|
+
meta.push({ name: "sf:critical-css", content: String(config.criticalCss) });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (config.prefetch?.length) {
|
|
172
|
+
meta.push({ name: "sf:prefetch", content: config.prefetch.join(",") });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (config.prerender?.length) {
|
|
176
|
+
meta.push({ name: "sf:prerender", content: config.prerender.join(",") });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (config.prefetchSelector) {
|
|
180
|
+
meta.push({
|
|
181
|
+
name: "sf:prefetch-selector",
|
|
182
|
+
content: config.prefetchSelector,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (config.speculationEagerness && config.speculationEagerness !== "moderate") {
|
|
187
|
+
meta.push({
|
|
188
|
+
name: "sf:speculation-eagerness",
|
|
189
|
+
content: config.speculationEagerness,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return meta;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Builds SpeculationRules from parsed meta config. Returns null if no rules configured. */
|
|
197
|
+
export function buildSpeculationRulesFromConfig(config: WorkerMetaConfig) {
|
|
198
|
+
const hasPrefetch = config.prefetch.length > 0 || config.prefetchSelector;
|
|
199
|
+
const hasPrerender = config.prerender.length > 0;
|
|
200
|
+
|
|
201
|
+
if (!hasPrefetch && !hasPrerender) return null;
|
|
202
|
+
|
|
203
|
+
const rules: SpeculationRules = {};
|
|
204
|
+
const eagerness = config.speculationEagerness;
|
|
205
|
+
|
|
206
|
+
if (config.prefetch.length > 0 || config.prefetchSelector) {
|
|
207
|
+
rules.prefetch = [];
|
|
208
|
+
|
|
209
|
+
if (config.prefetch.length > 0) {
|
|
210
|
+
// Separate URL patterns (contain *) from exact URLs
|
|
211
|
+
const patterns = config.prefetch.filter((u) => u.includes("*"));
|
|
212
|
+
const urls = config.prefetch.filter((u) => !u.includes("*"));
|
|
213
|
+
|
|
214
|
+
if (urls.length > 0) {
|
|
215
|
+
rules.prefetch.push(createPrefetchListRule(urls, { eagerness }));
|
|
216
|
+
}
|
|
217
|
+
if (patterns.length > 0) {
|
|
218
|
+
rules.prefetch.push(createDocumentRule(patterns, { eagerness }));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (config.prefetchSelector) {
|
|
223
|
+
rules.prefetch.push(createSelectorRule(config.prefetchSelector, { eagerness }));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (config.prerender.length > 0) {
|
|
228
|
+
// Separate URL patterns (contain *) from exact URLs
|
|
229
|
+
const patterns = config.prerender.filter((u) => u.includes("*"));
|
|
230
|
+
const urls = config.prerender.filter((u) => !u.includes("*"));
|
|
231
|
+
|
|
232
|
+
rules.prerender = [];
|
|
233
|
+
|
|
234
|
+
if (urls.length > 0) {
|
|
235
|
+
rules.prerender.push(createPrerenderListRule(urls, { eagerness }));
|
|
236
|
+
}
|
|
237
|
+
if (patterns.length > 0) {
|
|
238
|
+
rules.prerender.push(createDocumentRule(patterns, { eagerness }));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return rules;
|
|
243
|
+
}
|