@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.
Files changed (47) hide show
  1. package/package.json +52 -0
  2. package/readme.md +183 -0
  3. package/src/ast.ts +316 -0
  4. package/src/build.bundle-client.ts +404 -0
  5. package/src/build.bundle-server.ts +131 -0
  6. package/src/build.bundle.ts +48 -0
  7. package/src/build.emit-manifests.ts +25 -0
  8. package/src/build.hmr-entry.ts +88 -0
  9. package/src/build.scan.ts +182 -0
  10. package/src/build.ts +227 -0
  11. package/src/build.validate.ts +63 -0
  12. package/src/client.hmr.ts +78 -0
  13. package/src/client.styles.ts +68 -0
  14. package/src/client.ts +190 -0
  15. package/src/codemod.ts +688 -0
  16. package/src/console-forward.ts +254 -0
  17. package/src/critical-css.ts +103 -0
  18. package/src/devtools-json.ts +52 -0
  19. package/src/diff-dom-streaming.ts +406 -0
  20. package/src/early-flush.ts +125 -0
  21. package/src/early-hints.ts +83 -0
  22. package/src/fetch.ts +44 -0
  23. package/src/fs.ts +11 -0
  24. package/src/head.ts +876 -0
  25. package/src/hmr.ts +647 -0
  26. package/src/hydration.ts +238 -0
  27. package/src/manifest.runtime.ts +25 -0
  28. package/src/manifest.ts +23 -0
  29. package/src/paths.ts +96 -0
  30. package/src/render-priority.ts +69 -0
  31. package/src/route-cache.ts +163 -0
  32. package/src/router-deferred.ts +85 -0
  33. package/src/router-stream.ts +65 -0
  34. package/src/router.ts +535 -0
  35. package/src/runtime.ts +32 -0
  36. package/src/serialize.ts +38 -0
  37. package/src/server.hmr.ts +67 -0
  38. package/src/server.styles.ts +42 -0
  39. package/src/server.ts +480 -0
  40. package/src/solarflare.d.ts +101 -0
  41. package/src/speculation-rules.ts +171 -0
  42. package/src/store.ts +78 -0
  43. package/src/stream-assets.ts +135 -0
  44. package/src/stylesheets.ts +222 -0
  45. package/src/worker.config.ts +243 -0
  46. package/src/worker.ts +542 -0
  47. 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
+ }