@lovrozagar/flare 0.1.0
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/LICENSE +21 -0
- package/README.md +11 -0
- package/package.json +146 -0
- package/scripts/download-fonts.ts +136 -0
- package/scripts/populate-fonts.ts +721 -0
- package/src/await.ts +2 -0
- package/src/boundaries/index.ts +99 -0
- package/src/broadcast/channel.ts +65 -0
- package/src/broadcast/context.ts +12 -0
- package/src/broadcast/index.ts +75 -0
- package/src/broadcast/provider.tsx +7 -0
- package/src/build-location.ts +2 -0
- package/src/caches/index.ts +384 -0
- package/src/client/index.ts +75 -0
- package/src/client.tsx +2 -0
- package/src/codegen/index.ts +41 -0
- package/src/components/await.tsx +140 -0
- package/src/components/dev-error-overlay.tsx +154 -0
- package/src/components/direction-script.tsx +22 -0
- package/src/components/index.ts +150 -0
- package/src/components/locale-script.tsx +26 -0
- package/src/components/reset-css.tsx +14 -0
- package/src/components/ssr-context.tsx +37 -0
- package/src/components/theme-script.tsx +22 -0
- package/src/components/view-transition-css.tsx +21 -0
- package/src/config/index.ts +84 -0
- package/src/create-client-only-fn.ts +1 -0
- package/src/create-isomorphic-fn.ts +2 -0
- package/src/create-layout.ts +1 -0
- package/src/create-page.ts +1 -0
- package/src/create-path-segment.ts +1 -0
- package/src/create-query-client-getter.ts +2 -0
- package/src/create-root-layout.ts +1 -0
- package/src/create-router.ts +8 -0
- package/src/create-server-only-fn.ts +1 -0
- package/src/dedupe/index.ts +151 -0
- package/src/defer/index.ts +82 -0
- package/src/direction/index.tsx +163 -0
- package/src/direction.ts +3 -0
- package/src/duration/index.ts +18 -0
- package/src/env-fn/index.ts +101 -0
- package/src/env-fn.ts +2 -0
- package/src/errors/index.ts +166 -0
- package/src/fonts/abeezee.ts +50 -0
- package/src/fonts/abel.ts +26 -0
- package/src/fonts/abril-fatface.ts +34 -0
- package/src/fonts/acme.ts +26 -0
- package/src/fonts/advent-pro.ts +92 -0
- package/src/fonts/alegreya-sans.ts +228 -0
- package/src/fonts/alegreya.ts +228 -0
- package/src/fonts/alex-brush.ts +42 -0
- package/src/fonts/alfa-slab-one.ts +42 -0
- package/src/fonts/alice.ts +48 -0
- package/src/fonts/amatic-sc.ts +94 -0
- package/src/fonts/amiri.ts +114 -0
- package/src/fonts/antic-slab.ts +26 -0
- package/src/fonts/anton.ts +42 -0
- package/src/fonts/archivo-black.ts +34 -0
- package/src/fonts/archivo-narrow.ts +114 -0
- package/src/fonts/archivo.ts +66 -0
- package/src/fonts/arimo.ts +256 -0
- package/src/fonts/arvo.ts +50 -0
- package/src/fonts/asap.ts +66 -0
- package/src/fonts/assistant.ts +64 -0
- package/src/fonts/baloo-2.ts +82 -0
- package/src/fonts/barlow-condensed.ts +114 -0
- package/src/fonts/barlow-semi-condensed.ts +114 -0
- package/src/fonts/barlow.ts +114 -0
- package/src/fonts/be-vietnam-pro.ts +114 -0
- package/src/fonts/bebas-neue.ts +34 -0
- package/src/fonts/besley.ts +82 -0
- package/src/fonts/biorhyme.ts +50 -0
- package/src/fonts/bitter.ts +94 -0
- package/src/fonts/black-ops-one.ts +49 -0
- package/src/fonts/bree-serif.ts +34 -0
- package/src/fonts/bricolage-grotesque.ts +66 -0
- package/src/fonts/cabin.ts +114 -0
- package/src/fonts/cairo.ts +66 -0
- package/src/fonts/cardo.ts +193 -0
- package/src/fonts/catamaran.ts +41 -0
- package/src/fonts/caveat.ts +78 -0
- package/src/fonts/chakra-petch.ts +142 -0
- package/src/fonts/chivo.ts +66 -0
- package/src/fonts/cinzel.ts +50 -0
- package/src/fonts/comfortaa.ts +109 -0
- package/src/fonts/commissioner.ts +64 -0
- package/src/fonts/component.tsx +27 -0
- package/src/fonts/cormorant-garamond.ts +171 -0
- package/src/fonts/cormorant.ts +170 -0
- package/src/fonts/courgette.ts +34 -0
- package/src/fonts/courier-prime.ts +82 -0
- package/src/fonts/create-font.ts +47 -0
- package/src/fonts/create-registry-font.ts +40 -0
- package/src/fonts/crete-round.ts +50 -0
- package/src/fonts/crimson-text.ts +114 -0
- package/src/fonts/css.ts +86 -0
- package/src/fonts/dancing-script.ts +66 -0
- package/src/fonts/dm-mono.ts +50 -0
- package/src/fonts/dm-sans.ts +50 -0
- package/src/fonts/dm-serif-display.ts +50 -0
- package/src/fonts/domine.ts +50 -0
- package/src/fonts/dosis.ts +66 -0
- package/src/fonts/eb-garamond.ts +228 -0
- package/src/fonts/eczar.ts +94 -0
- package/src/fonts/encode-sans.ts +42 -0
- package/src/fonts/epilogue.ts +66 -0
- package/src/fonts/exo-2.ts +94 -0
- package/src/fonts/exo.ts +66 -0
- package/src/fonts/figtree.ts +82 -0
- package/src/fonts/fira-code.ts +121 -0
- package/src/fonts/fira-sans-condensed.ts +228 -0
- package/src/fonts/fira-sans.ts +228 -0
- package/src/fonts/fjalla-one.ts +49 -0
- package/src/fonts/font-urls.gen.json +1962 -0
- package/src/fonts/fraunces.ts +66 -0
- package/src/fonts/fredoka.ts +64 -0
- package/src/fonts/gelasio.ts +114 -0
- package/src/fonts/gloria-hallelujah.ts +34 -0
- package/src/fonts/gothic-a1.ts +124 -0
- package/src/fonts/great-vibes.ts +64 -0
- package/src/fonts/gudea.ts +66 -0
- package/src/fonts/heebo.ts +57 -0
- package/src/fonts/hind-siliguri.ts +66 -0
- package/src/fonts/hind.ts +66 -0
- package/src/fonts/ibm-plex-mono.ts +172 -0
- package/src/fonts/ibm-plex-sans-arabic.ts +80 -0
- package/src/fonts/ibm-plex-sans.ts +199 -0
- package/src/fonts/ibm-plex-serif.ts +171 -0
- package/src/fonts/inconsolata.ts +66 -0
- package/src/fonts/index.ts +4 -0
- package/src/fonts/indie-flower.ts +34 -0
- package/src/fonts/instrument-sans.ts +82 -0
- package/src/fonts/instrument-serif.ts +50 -0
- package/src/fonts/inter-display.ts +52 -0
- package/src/fonts/inter-tight.ts +124 -0
- package/src/fonts/inter.ts +123 -0
- package/src/fonts/jetbrains-mono.ts +199 -0
- package/src/fonts/josefin-sans.ts +114 -0
- package/src/fonts/josefin-slab.ts +50 -0
- package/src/fonts/jost.ts +64 -0
- package/src/fonts/kalam.ts +66 -0
- package/src/fonts/kanit.ts +142 -0
- package/src/fonts/karla.ts +82 -0
- package/src/fonts/khand.ts +66 -0
- package/src/fonts/lato.ts +82 -0
- package/src/fonts/league-spartan.ts +42 -0
- package/src/fonts/lexend-deca.ts +42 -0
- package/src/fonts/lexend.ts +42 -0
- package/src/fonts/libre-baskerville.ts +82 -0
- package/src/fonts/libre-franklin.ts +95 -0
- package/src/fonts/lilita-one.ts +34 -0
- package/src/fonts/lobster-two.ts +50 -0
- package/src/fonts/lobster.ts +56 -0
- package/src/fonts/lora.ts +235 -0
- package/src/fonts/m-plus-rounded-1c.ts +138 -0
- package/src/fonts/manrope.ts +109 -0
- package/src/fonts/maven-pro.ts +66 -0
- package/src/fonts/merriweather-sans.ts +142 -0
- package/src/fonts/merriweather.ts +171 -0
- package/src/fonts/montserrat-alternates.ts +171 -0
- package/src/fonts/montserrat.ts +94 -0
- package/src/fonts/mukta.ts +66 -0
- package/src/fonts/mulish.ts +170 -0
- package/src/fonts/nanum-gothic.ts +34 -0
- package/src/fonts/nanum-myeongjo.ts +34 -0
- package/src/fonts/neuton.ts +66 -0
- package/src/fonts/newsreader.ts +114 -0
- package/src/fonts/noticia-text.ts +114 -0
- package/src/fonts/noto-sans-arabic.ts +58 -0
- package/src/fonts/noto-sans-hk.ts +49 -0
- package/src/fonts/noto-sans-jp.ts +49 -0
- package/src/fonts/noto-sans-kr.ts +49 -0
- package/src/fonts/noto-sans-sc.ts +49 -0
- package/src/fonts/noto-sans-tc.ts +49 -0
- package/src/fonts/noto-sans.ts +140 -0
- package/src/fonts/noto-serif-jp.ts +80 -0
- package/src/fonts/noto-serif.ts +140 -0
- package/src/fonts/nunito-sans.ts +170 -0
- package/src/fonts/nunito.ts +170 -0
- package/src/fonts/old-standard-tt.ts +133 -0
- package/src/fonts/onest.ts +48 -0
- package/src/fonts/open-sans.ts +340 -0
- package/src/fonts/orbitron.ts +34 -0
- package/src/fonts/oswald.ts +94 -0
- package/src/fonts/outfit.ts +34 -0
- package/src/fonts/overpass.ts +94 -0
- package/src/fonts/oxygen.ts +50 -0
- package/src/fonts/pacifico.ts +56 -0
- package/src/fonts/passion-one.ts +50 -0
- package/src/fonts/pathway-gothic-one.ts +34 -0
- package/src/fonts/permanent-marker.ts +26 -0
- package/src/fonts/philosopher.ts +172 -0
- package/src/fonts/play.ts +109 -0
- package/src/fonts/playfair-display.ts +142 -0
- package/src/fonts/plus-jakarta-sans.ts +142 -0
- package/src/fonts/poppins.ts +114 -0
- package/src/fonts/prompt.ts +142 -0
- package/src/fonts/pt-sans-narrow.ts +78 -0
- package/src/fonts/pt-sans.ts +138 -0
- package/src/fonts/pt-serif.ts +138 -0
- package/src/fonts/public-sans.ts +66 -0
- package/src/fonts/questrial.ts +42 -0
- package/src/fonts/quicksand.ts +66 -0
- package/src/fonts/rajdhani.ts +66 -0
- package/src/fonts/raleway.ts +94 -0
- package/src/fonts/recursive.ts +80 -0
- package/src/fonts/red-hat-display.ts +82 -0
- package/src/fonts/red-rose.ts +66 -0
- package/src/fonts/registry.gen.ts +173 -0
- package/src/fonts/righteous.ts +34 -0
- package/src/fonts/roboto-condensed.ts +124 -0
- package/src/fonts/roboto-flex.ts +64 -0
- package/src/fonts/roboto-mono.ts +199 -0
- package/src/fonts/roboto-slab.ts +72 -0
- package/src/fonts/roboto.ts +156 -0
- package/src/fonts/rokkitt.ts +66 -0
- package/src/fonts/rubik.ts +199 -0
- package/src/fonts/sacramento.ts +34 -0
- package/src/fonts/saira.ts +66 -0
- package/src/fonts/sarabun.ts +142 -0
- package/src/fonts/satisfy.ts +26 -0
- package/src/fonts/sawarabi-mincho.ts +41 -0
- package/src/fonts/shadows-into-light.ts +34 -0
- package/src/fonts/signika-negative.ts +66 -0
- package/src/fonts/signika.ts +66 -0
- package/src/fonts/slabo-27px.ts +34 -0
- package/src/fonts/sora.ts +50 -0
- package/src/fonts/source-code-pro.ts +228 -0
- package/src/fonts/source-sans-3.ts +228 -0
- package/src/fonts/source-serif-4.ts +199 -0
- package/src/fonts/space-grotesk.ts +66 -0
- package/src/fonts/space-mono.ts +114 -0
- package/src/fonts/spectral.ts +170 -0
- package/src/fonts/tajawal.ts +50 -0
- package/src/fonts/teko.ts +66 -0
- package/src/fonts/tenor-sans.ts +41 -0
- package/src/fonts/tinos.ts +256 -0
- package/src/fonts/titillium-web.ts +82 -0
- package/src/fonts/types.ts +43 -0
- package/src/fonts/ubuntu-condensed.ts +63 -0
- package/src/fonts/ubuntu-mono.ts +195 -0
- package/src/fonts/ubuntu.ts +195 -0
- package/src/fonts/unbounded.ts +94 -0
- package/src/fonts/unicode-ranges.gen.ts +28 -0
- package/src/fonts/urbanist.ts +50 -0
- package/src/fonts/varela-round.ts +49 -0
- package/src/fonts/vollkorn.ts +199 -0
- package/src/fonts/work-sans.ts +66 -0
- package/src/fonts/yanone-kaffeesatz.ts +128 -0
- package/src/fonts/yantramanav.ts +66 -0
- package/src/fonts/yellowtail.ts +34 -0
- package/src/fonts/young-serif.ts +34 -0
- package/src/fonts/zen-kaku-gothic-new.ts +64 -0
- package/src/fonts/zilla-slab.ts +82 -0
- package/src/form/index.tsx +205 -0
- package/src/generators/index.ts +1589 -0
- package/src/head-client/index.ts +649 -0
- package/src/history/index.ts +210 -0
- package/src/hooks.ts +61 -0
- package/src/hydrate/index.tsx +381 -0
- package/src/hydration/index.ts +187 -0
- package/src/i18n/index.ts +250 -0
- package/src/i18n/jsx.tsx +42 -0
- package/src/i18n/message-format.ts +174 -0
- package/src/image/index.tsx +473 -0
- package/src/image.ts +12 -0
- package/src/intercept-outlet/index.tsx +18 -0
- package/src/intercept-outlet.ts +2 -0
- package/src/internal/index.ts +143 -0
- package/src/internal/once-idle.ts +24 -0
- package/src/internal.ts +12 -0
- package/src/lazy/index.tsx +205 -0
- package/src/link/index.tsx +367 -0
- package/src/loader-pipeline/index.ts +639 -0
- package/src/locale/index.tsx +137 -0
- package/src/locale.ts +3 -0
- package/src/logger.ts +22 -0
- package/src/middleware/builtins/api-proxy.ts +64 -0
- package/src/middleware/builtins/cdn-proxy.ts +58 -0
- package/src/middleware/builtins/i18n.ts +256 -0
- package/src/middleware/builtins/keepalive.ts +24 -0
- package/src/middleware/builtins/markdown-negotiation.ts +141 -0
- package/src/middleware/builtins/static-assets.ts +32 -0
- package/src/middleware/index.ts +223 -0
- package/src/module-graph/index.ts +181 -0
- package/src/mount/index.ts +146 -0
- package/src/navigate.ts +1 -0
- package/src/navigation/index.ts +1278 -0
- package/src/navigation/types.ts +59 -0
- package/src/navigation-progress/index.tsx +247 -0
- package/src/navigation-progress.ts +1 -0
- package/src/ndjson-client/index.ts +376 -0
- package/src/ndjson-server/index.ts +284 -0
- package/src/outlet/index.tsx +633 -0
- package/src/outlet/types.ts +243 -0
- package/src/plugins/css-transform.ts +70 -0
- package/src/plugins/dev-cdn-cache.ts +454 -0
- package/src/plugins/dev-dashboard/client.tsx +2603 -0
- package/src/plugins/dev-dashboard/match-route-tree.ts +159 -0
- package/src/plugins/dev-dashboard/plugin.ts +404 -0
- package/src/plugins/dev-prerender.ts +168 -0
- package/src/plugins/dev-server.ts +327 -0
- package/src/plugins/generate-watch.ts +32 -0
- package/src/plugins/image-plugin.ts +171 -0
- package/src/plugins/index.ts +445 -0
- package/src/plugins/options.ts +37 -0
- package/src/plugins/parse-utils.ts +59 -0
- package/src/plugins/prerender-plugin.ts +144 -0
- package/src/plugins/purge.ts +242 -0
- package/src/plugins/server-fn.ts +641 -0
- package/src/plugins/service-worker.ts +225 -0
- package/src/plugins/solid-jsx.d.ts +12 -0
- package/src/plugins/sx-ast/atomic.ts +166 -0
- package/src/plugins/sx-ast/evaluator.ts +235 -0
- package/src/plugins/sx-ast/index.ts +372 -0
- package/src/plugins/sx-ast/parser.ts +22 -0
- package/src/plugins/sx-ast/rewrite.ts +763 -0
- package/src/plugins/sx.d.ts +14 -0
- package/src/plugins/tw-compile.ts +240 -0
- package/src/plugins/types.ts +41 -0
- package/src/plugins/virtual.d.ts +110 -0
- package/src/plugins/virtual.ts +184 -0
- package/src/preload/index.ts +77 -0
- package/src/prerender/index.ts +431 -0
- package/src/query-client/index.tsx +168 -0
- package/src/registry/index.ts +88 -0
- package/src/reset-css.ts +1 -0
- package/src/revalidation/index.ts +84 -0
- package/src/rewrite/index.ts +83 -0
- package/src/route-builder/create-layout.ts +655 -0
- package/src/route-builder/create-page.ts +806 -0
- package/src/route-builder/create-path-segment.ts +34 -0
- package/src/route-builder/create-root-layout.ts +608 -0
- package/src/route-builder/index.ts +49 -0
- package/src/route-builder/register.ts +242 -0
- package/src/route-builder/types.ts +294 -0
- package/src/router-config/index.ts +91 -0
- package/src/router-primitives/index.ts +25 -0
- package/src/router-primitives/location.ts +29 -0
- package/src/router-primitives/match-id.ts +111 -0
- package/src/router-primitives/paths.ts +109 -0
- package/src/router-primitives/tree.ts +243 -0
- package/src/router-primitives/types.ts +74 -0
- package/src/router.ts +31 -0
- package/src/search-engine/bing.ts +32 -0
- package/src/search-engine/google-jwt.ts +95 -0
- package/src/search-engine/google.ts +139 -0
- package/src/search-engine/index-now.ts +64 -0
- package/src/search-engine/index.ts +12 -0
- package/src/security/index.ts +230 -0
- package/src/server/index.ts +286 -0
- package/src/server-context/browser.ts +113 -0
- package/src/server-context/index.ts +217 -0
- package/src/server-fn/index.ts +710 -0
- package/src/server-fn-query.ts +169 -0
- package/src/server-handler/etag.ts +51 -0
- package/src/server-handler/headers.ts +5 -0
- package/src/server-handler/index.ts +1664 -0
- package/src/server-handler/types.ts +24 -0
- package/src/server-handler/validate-static-params.ts +130 -0
- package/src/server.ts +2 -0
- package/src/service-worker/template.ts +153 -0
- package/src/service-worker-hook/index.ts +70 -0
- package/src/sitemap/index.ts +231 -0
- package/src/ssr/critical-css.ts +150 -0
- package/src/ssr/head-prefix.ts +65 -0
- package/src/ssr/head.ts +259 -0
- package/src/ssr/index.tsx +879 -0
- package/src/state-parser/index.ts +266 -0
- package/src/store/filesystem.ts +131 -0
- package/src/store/index.ts +29 -0
- package/src/styles/index.ts +1200 -0
- package/src/styles/sx-types.ts +11 -0
- package/src/testing/index.ts +251 -0
- package/src/testing.ts +2 -0
- package/src/theme/index.tsx +232 -0
- package/src/theme.ts +3 -0
- package/src/tracing/index.ts +4 -0
- package/src/tracing/noop.ts +13 -0
- package/src/tracing/otel.ts +37 -0
- package/src/tracing/timing.ts +34 -0
- package/src/tracing/types.ts +9 -0
- package/src/url/index.ts +140 -0
- package/src/use-router.ts +11 -0
- package/src/use-suspense-query.ts +2 -0
- package/src/validation/index.ts +57 -0
- package/src/view-transition-css.ts +1 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { ViteManifest } from "../module-graph/index.ts";
|
|
5
|
+
import { generateSwSource } from "../service-worker/template.ts";
|
|
6
|
+
import type { VitePlugin } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
/* ── Config ──────────────────────────────────────────────────────── */
|
|
9
|
+
|
|
10
|
+
export interface ServiceWorkerConfig {
|
|
11
|
+
offlineFallback?: string;
|
|
12
|
+
runtimeCacheMax?: number;
|
|
13
|
+
scope?: string;
|
|
14
|
+
skipWaiting?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ResolvedServiceWorkerConfig {
|
|
18
|
+
offlineFallback?: string;
|
|
19
|
+
runtimeCacheMax: number;
|
|
20
|
+
scope: string;
|
|
21
|
+
skipWaiting: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SW_DEFAULTS: ResolvedServiceWorkerConfig = {
|
|
25
|
+
runtimeCacheMax: 32,
|
|
26
|
+
scope: "/",
|
|
27
|
+
skipWaiting: true,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function normalizeSwConfig(
|
|
31
|
+
input: ServiceWorkerConfig | boolean | undefined,
|
|
32
|
+
): ResolvedServiceWorkerConfig | undefined {
|
|
33
|
+
if (input === false || input === undefined) return undefined;
|
|
34
|
+
if (input === true) return { ...SW_DEFAULTS };
|
|
35
|
+
return { ...SW_DEFAULTS, ...input };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ── Manifest utilities ──────────────────────────────────────────── */
|
|
39
|
+
|
|
40
|
+
export function extractPrecacheUrls(manifest: ViteManifest): string[] {
|
|
41
|
+
const urls = new Set<string>();
|
|
42
|
+
|
|
43
|
+
for (const entry of Object.values(manifest)) {
|
|
44
|
+
if (entry.file) {
|
|
45
|
+
urls.add(`/${entry.file}`);
|
|
46
|
+
}
|
|
47
|
+
if (entry.css) {
|
|
48
|
+
for (const css of entry.css) {
|
|
49
|
+
urls.add(`/${css}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return [...urls];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function computeBuildId(urls: string[]): string {
|
|
58
|
+
const sorted = [...urls].sort();
|
|
59
|
+
const hash = createHash("sha256").update(sorted.join("\n")).digest("hex");
|
|
60
|
+
return hash.slice(0, 12);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ── Dev SW ──────────────────────────────────────────────────────── */
|
|
64
|
+
|
|
65
|
+
function generateDevSw(offlineFallback?: string): string {
|
|
66
|
+
if (!offlineFallback) {
|
|
67
|
+
return `self.addEventListener("install", function () { self.skipWaiting() })
|
|
68
|
+
self.addEventListener("activate", function (event) { event.waitUntil(self.clients.claim()) })
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return `var OFFLINE_PAGE = ${JSON.stringify(offlineFallback)}
|
|
73
|
+
var CACHE = "flare-dev-offline"
|
|
74
|
+
|
|
75
|
+
self.addEventListener("install", function (event) {
|
|
76
|
+
event.waitUntil(
|
|
77
|
+
caches.open(CACHE).then(function (cache) {
|
|
78
|
+
return fetch(OFFLINE_PAGE).then(function (res) {
|
|
79
|
+
if (res.ok) return cache.put(OFFLINE_PAGE, res)
|
|
80
|
+
}).catch(function () {})
|
|
81
|
+
}).then(function () { self.skipWaiting() })
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
self.addEventListener("activate", function (event) {
|
|
86
|
+
event.waitUntil(self.clients.claim())
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
self.addEventListener("fetch", function (event) {
|
|
90
|
+
if (event.request.mode !== "navigate") return
|
|
91
|
+
event.respondWith(
|
|
92
|
+
fetch(event.request).catch(function () {
|
|
93
|
+
return caches.match(OFFLINE_PAGE)
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface NodeReq {
|
|
101
|
+
url?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface NodeRes {
|
|
105
|
+
end: (data?: unknown) => void;
|
|
106
|
+
writeHead: (status: number, headers: Record<string, string>) => void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface ViteDevServer {
|
|
110
|
+
middlewares: {
|
|
111
|
+
use: (fn: (req: NodeReq, res: NodeRes, next: () => void) => void) => void;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createServiceWorkerPlugin(
|
|
116
|
+
swConfig: ResolvedServiceWorkerConfig,
|
|
117
|
+
assetsBase: string = "/assets",
|
|
118
|
+
): VitePlugin {
|
|
119
|
+
return {
|
|
120
|
+
closeBundle(this: { environment?: { config?: { root?: string }; name?: string } }): void {
|
|
121
|
+
const envName = this.environment?.name;
|
|
122
|
+
/* Multi-env: SSR closes last, manifest already written. Single-env: no "ssr" env, fire on "client". */
|
|
123
|
+
if (envName !== "ssr" && envName !== "client") return;
|
|
124
|
+
|
|
125
|
+
const root = this.environment?.config?.root ?? process.cwd();
|
|
126
|
+
const manifestPath = join(root, "dist/client/.vite/manifest.json");
|
|
127
|
+
|
|
128
|
+
if (!existsSync(manifestPath)) return;
|
|
129
|
+
|
|
130
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
131
|
+
const manifest = JSON.parse(raw) as ViteManifest;
|
|
132
|
+
const urls = extractPrecacheUrls(manifest);
|
|
133
|
+
const buildId = computeBuildId(urls);
|
|
134
|
+
|
|
135
|
+
const swSource = generateSwSource(urls, buildId, {
|
|
136
|
+
assetsBase,
|
|
137
|
+
offlineFallback: swConfig.offlineFallback ?? null,
|
|
138
|
+
runtimeCacheMax: swConfig.runtimeCacheMax,
|
|
139
|
+
skipWaiting: swConfig.skipWaiting,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
writeFileSync(join(root, "dist/client/sw.js"), swSource, "utf-8");
|
|
143
|
+
process.stderr.write(`[flare:service-worker] Generated sw.js (${urls.length} assets, build ${buildId})\n`);
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
configurePreviewServer(server: unknown) {
|
|
147
|
+
const preview = server as {
|
|
148
|
+
config?: { root?: string };
|
|
149
|
+
middlewares: ViteDevServer["middlewares"];
|
|
150
|
+
};
|
|
151
|
+
const root = preview.config?.root ?? process.cwd();
|
|
152
|
+
const swPath = join(root, "dist/client/sw.js");
|
|
153
|
+
|
|
154
|
+
preview.middlewares.use((req, res, next) => {
|
|
155
|
+
if (req.url === "/sw.js" && existsSync(swPath)) {
|
|
156
|
+
const content = readFileSync(swPath, "utf-8");
|
|
157
|
+
res.writeHead(200, {
|
|
158
|
+
"cache-control": "no-cache",
|
|
159
|
+
"content-type": "application/javascript",
|
|
160
|
+
});
|
|
161
|
+
res.end(content);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
next();
|
|
165
|
+
});
|
|
166
|
+
return undefined;
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
configureServer(server: unknown) {
|
|
170
|
+
const vite = server as ViteDevServer;
|
|
171
|
+
const devSwSource = generateDevSw(swConfig.offlineFallback);
|
|
172
|
+
vite.middlewares.use((req, res, next) => {
|
|
173
|
+
if (req.url === "/sw.js") {
|
|
174
|
+
res.writeHead(200, {
|
|
175
|
+
"cache-control": "no-cache",
|
|
176
|
+
"content-type": "application/javascript",
|
|
177
|
+
});
|
|
178
|
+
res.end(devSwSource);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
next();
|
|
182
|
+
});
|
|
183
|
+
return undefined;
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
load(id: string): { code: string; moduleType: string } | null {
|
|
187
|
+
if (id === "\0virtual:flare-sw-config") {
|
|
188
|
+
return {
|
|
189
|
+
code: `export default ${JSON.stringify({
|
|
190
|
+
enabled: true,
|
|
191
|
+
path: "/sw.js",
|
|
192
|
+
scope: swConfig.scope,
|
|
193
|
+
})}`,
|
|
194
|
+
moduleType: "js",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
name: "flare:service-worker",
|
|
201
|
+
|
|
202
|
+
resolveId(id: string): string | null {
|
|
203
|
+
if (id === "virtual:flare-sw-config") return "\0virtual:flare-sw-config";
|
|
204
|
+
return null;
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function createServiceWorkerDisabledPlugin(): VitePlugin {
|
|
210
|
+
return {
|
|
211
|
+
load(id: string): { code: string; moduleType: string } | null {
|
|
212
|
+
if (id === "\0virtual:flare-sw-config") {
|
|
213
|
+
return { code: "export default { enabled: false }", moduleType: "js" };
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
name: "flare:service-worker",
|
|
219
|
+
|
|
220
|
+
resolveId(id: string): string | null {
|
|
221
|
+
if (id === "virtual:flare-sw-config") return "\0virtual:flare-sw-config";
|
|
222
|
+
return null;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { Expression } from "oxc-parser";
|
|
2
|
+
import type { SxIR } from "./evaluator.ts";
|
|
3
|
+
|
|
4
|
+
export interface AtomicResult {
|
|
5
|
+
classes: string[];
|
|
6
|
+
cssRules: Map<string, string>;
|
|
7
|
+
vars: Array<{ exprNode: Expression; index: number }>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** FNV-1a 32-bit hash over the input string. */
|
|
11
|
+
function fnv1a32(s: string): number {
|
|
12
|
+
let h = 0x811c9dc5;
|
|
13
|
+
for (let i = 0; i < s.length; i++) {
|
|
14
|
+
h = Math.imul(h ^ s.charCodeAt(i), 0x01000193);
|
|
15
|
+
}
|
|
16
|
+
return h >>> 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function hash8(s: string): string {
|
|
20
|
+
return fnv1a32(s).toString(36).padStart(8, "0").slice(-8);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hash4(s: string): string {
|
|
24
|
+
return hash8(s).slice(0, 4);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* Class name triple: selector\0property\0value.
|
|
29
|
+
* Null-byte separators prevent `color:red` ↔ `col:or:red` collisions.
|
|
30
|
+
*/
|
|
31
|
+
function tripleKey(selector: string, prop: string, value: string): string {
|
|
32
|
+
return `${selector}\0${prop}\0${value}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function className(selector: string, prop: string, value: string, mode: "dev" | "prod"): string {
|
|
36
|
+
const key = tripleKey(selector, prop, value);
|
|
37
|
+
if (mode === "prod") {
|
|
38
|
+
return `a1-${hash8(key)}`;
|
|
39
|
+
}
|
|
40
|
+
/* dev: sx-<prop>-<value>-<hash4> — sanitize value for use in class name */
|
|
41
|
+
const safeProp = prop.replace(/[^a-zA-Z0-9-]/g, "-");
|
|
42
|
+
const safeVal = value.replace(/[^a-zA-Z0-9-]/g, "-").slice(0, 20);
|
|
43
|
+
return `sx-${safeProp}-${safeVal}-${hash4(key)}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toKebab(prop: string): string {
|
|
47
|
+
/* Vendor prefixes: WebkitFoo → -webkit-foo, MozFoo → -moz-foo */
|
|
48
|
+
return prop.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function expandSelector(parentSelector: string, sel: string): string {
|
|
52
|
+
/* Replace & with parent, or prepend if no & */
|
|
53
|
+
if (sel.includes("&")) return sel.replace(/&/g, parentSelector);
|
|
54
|
+
return `${parentSelector}${sel}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
* Wraps any pseudo-class/pseudo-element and attribute modifier portions of a
|
|
59
|
+
* selector in `:where()` so that specificity stays at (0,1,0) — matching
|
|
60
|
+
* the base-rule class selector. Without this, `.cls:hover` (0,2,0) would
|
|
61
|
+
* permanently beat `.cls2` (0,1,0) on the same element, even when not hovered.
|
|
62
|
+
* Within an @layer the last rule with equal specificity wins, so source order
|
|
63
|
+
* (base first, modifiers last) correctly governs override priority.
|
|
64
|
+
*/
|
|
65
|
+
function wrapModifiersInWhere(sel: string): string {
|
|
66
|
+
/* sel is already fully expanded, e.g. `.a1-xxx:hover:focus-visible` or
|
|
67
|
+
* `.a1-xxx[data-size="lg"]`. Split off the leading class selector, then
|
|
68
|
+
* wrap the remainder. If there's no remainder (bare class), return as-is. */
|
|
69
|
+
/* Walk from char 1 to find the first pseudo/attr modifier after the class */
|
|
70
|
+
let splitAt = sel.length;
|
|
71
|
+
for (let i = 1; i < sel.length; i++) {
|
|
72
|
+
const c = sel[i];
|
|
73
|
+
if (c === ":" || c === "[") {
|
|
74
|
+
splitAt = i;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (splitAt === sel.length) return sel;
|
|
79
|
+
const base = sel.slice(0, splitAt);
|
|
80
|
+
const modifier = sel.slice(splitAt);
|
|
81
|
+
return `${base}:where(${modifier})`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface EmitCtx {
|
|
85
|
+
classes: string[];
|
|
86
|
+
cssRules: Map<string, string>;
|
|
87
|
+
mode: "dev" | "prod";
|
|
88
|
+
varCounter: { n: number };
|
|
89
|
+
vars: Array<{ exprNode: Expression; index: number }>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function emitDeclarations(ctx: EmitCtx, cssSelector: string, base: SxIR["base"], atWrap?: string): void {
|
|
93
|
+
for (const [prop, cv] of Object.entries(base)) {
|
|
94
|
+
let value: string;
|
|
95
|
+
if (cv.kind === "dynamic") {
|
|
96
|
+
const idx = ctx.varCounter.n++;
|
|
97
|
+
ctx.vars.push({ exprNode: cv.exprNode, index: idx });
|
|
98
|
+
value = `var(--_${idx})`;
|
|
99
|
+
} else {
|
|
100
|
+
value = cv.text;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const cssProp = toKebab(prop);
|
|
104
|
+
const cls = className(cssSelector, cssProp, value, ctx.mode);
|
|
105
|
+
ctx.classes.push(cls);
|
|
106
|
+
|
|
107
|
+
if (ctx.cssRules.has(cls)) continue;
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* Base: `.cls { prop: value }`.
|
|
111
|
+
* Modified (pseudo/attr): wrap modifiers in `:where()` so all rules share
|
|
112
|
+
* (0,1,0) specificity — source order within @layer governs overrides.
|
|
113
|
+
*/
|
|
114
|
+
let rule: string;
|
|
115
|
+
if (cssSelector === "") {
|
|
116
|
+
rule = `.${cls} { ${cssProp}: ${value} }`;
|
|
117
|
+
} else {
|
|
118
|
+
const resolved = wrapModifiersInWhere(expandSelector(`.${cls}`, cssSelector));
|
|
119
|
+
rule = `${resolved} { ${cssProp}: ${value} }`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (atWrap) {
|
|
123
|
+
rule = `${atWrap} { ${rule} }`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ctx.cssRules.set(cls, rule);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function emitIR(ctx: EmitCtx, ir: SxIR, parentSelector = "", atWrap?: string): void {
|
|
131
|
+
/* Base declarations */
|
|
132
|
+
emitDeclarations(ctx, parentSelector, ir.base, atWrap);
|
|
133
|
+
|
|
134
|
+
/* Nested selectors */
|
|
135
|
+
for (const { body, sel } of ir.selectors) {
|
|
136
|
+
const resolved = parentSelector === "" ? sel : expandSelector(parentSelector, sel);
|
|
137
|
+
emitIR(ctx, body, resolved, atWrap);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* At-rules */
|
|
141
|
+
for (const { at, body } of ir.atRules) {
|
|
142
|
+
emitIR(ctx, body, parentSelector, at);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Variants: data-attr selector */
|
|
146
|
+
for (const [varName, varValues] of Object.entries(ir.variants)) {
|
|
147
|
+
for (const [varValue, bodyIR] of Object.entries(varValues)) {
|
|
148
|
+
const varSel = `[data-${varName}="${varValue}"]`;
|
|
149
|
+
const combined = parentSelector === "" ? `&${varSel}` : `${parentSelector}${varSel}`;
|
|
150
|
+
emitIR(ctx, bodyIR, combined, atWrap);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Convert an SxIR to atomic class names + CSS rules. */
|
|
156
|
+
export function emitAtomic(ir: SxIR, mode: "dev" | "prod"): AtomicResult {
|
|
157
|
+
const ctx: EmitCtx = {
|
|
158
|
+
classes: [],
|
|
159
|
+
cssRules: new Map(),
|
|
160
|
+
mode,
|
|
161
|
+
varCounter: { n: 0 },
|
|
162
|
+
vars: [],
|
|
163
|
+
};
|
|
164
|
+
emitIR(ctx, ir);
|
|
165
|
+
return { classes: ctx.classes, cssRules: ctx.cssRules, vars: ctx.vars };
|
|
166
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { Expression, Node, ObjectExpression } from "oxc-parser";
|
|
2
|
+
|
|
3
|
+
export type CssValue = { kind: "static"; text: string } | { kind: "dynamic"; exprNode: Expression };
|
|
4
|
+
|
|
5
|
+
export interface SxIR {
|
|
6
|
+
base: Record<string, CssValue>;
|
|
7
|
+
selectors: Array<{ sel: string; body: SxIR }>;
|
|
8
|
+
atRules: Array<{ at: string; body: SxIR }>;
|
|
9
|
+
variants: Record<string, Record<string, SxIR>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface EvalDiagnostic {
|
|
13
|
+
kind: "warning" | "error";
|
|
14
|
+
message: string;
|
|
15
|
+
node: Node;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface EvalResult {
|
|
19
|
+
ir: SxIR | null;
|
|
20
|
+
diagnostics: EvalDiagnostic[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* React DOMProperty unitless list + csstype cross-reference.
|
|
25
|
+
* Properties here get no `px` suffix when their value is a plain number.
|
|
26
|
+
*/
|
|
27
|
+
const UNITLESS = new Set([
|
|
28
|
+
"animationIterationCount",
|
|
29
|
+
"aspectRatio",
|
|
30
|
+
"borderImageOutset",
|
|
31
|
+
"borderImageSlice",
|
|
32
|
+
"borderImageWidth",
|
|
33
|
+
"columnCount",
|
|
34
|
+
"columns",
|
|
35
|
+
"flex",
|
|
36
|
+
"flexGrow",
|
|
37
|
+
"flexShrink",
|
|
38
|
+
"fontWeight",
|
|
39
|
+
"gridColumn",
|
|
40
|
+
"gridColumnEnd",
|
|
41
|
+
"gridColumnStart",
|
|
42
|
+
"gridRow",
|
|
43
|
+
"gridRowEnd",
|
|
44
|
+
"gridRowStart",
|
|
45
|
+
"lineClamp",
|
|
46
|
+
"lineHeight",
|
|
47
|
+
"opacity",
|
|
48
|
+
"order",
|
|
49
|
+
"orphans",
|
|
50
|
+
"tabSize",
|
|
51
|
+
"widows",
|
|
52
|
+
"zIndex",
|
|
53
|
+
"zoom",
|
|
54
|
+
"fillOpacity",
|
|
55
|
+
"floodOpacity",
|
|
56
|
+
"stopOpacity",
|
|
57
|
+
"strokeDasharray",
|
|
58
|
+
"strokeDashoffset",
|
|
59
|
+
"strokeMiterlimit",
|
|
60
|
+
"strokeOpacity",
|
|
61
|
+
"strokeWidth",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
function makeEmptyIR(): SxIR {
|
|
65
|
+
return { atRules: [], base: {}, selectors: [], variants: {} };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normNumeric(prop: string, raw: number): string {
|
|
69
|
+
if (UNITLESS.has(prop)) return String(raw);
|
|
70
|
+
return `${raw}px`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Evaluate the value expression of a single CSS property. */
|
|
74
|
+
function evalValue(prop: string, expr: Expression): CssValue {
|
|
75
|
+
if (expr.type === "Literal") {
|
|
76
|
+
const lit = expr as { type: "Literal"; value: unknown };
|
|
77
|
+
if (typeof lit.value === "string") return { kind: "static", text: lit.value };
|
|
78
|
+
if (typeof lit.value === "number") return { kind: "static", text: normNumeric(prop, lit.value) };
|
|
79
|
+
if (typeof lit.value === "boolean") return { kind: "static", text: String(lit.value) };
|
|
80
|
+
}
|
|
81
|
+
if (expr.type === "UnaryExpression") {
|
|
82
|
+
const unary = expr as { type: "UnaryExpression"; operator: string; argument: Expression };
|
|
83
|
+
if (unary.operator === "-" && unary.argument.type === "Literal") {
|
|
84
|
+
const argLit = unary.argument as { value: unknown };
|
|
85
|
+
if (typeof argLit.value === "number") {
|
|
86
|
+
return { kind: "static", text: normNumeric(prop, -argLit.value) };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (expr.type === "TemplateLiteral") {
|
|
91
|
+
const tl = expr as {
|
|
92
|
+
type: "TemplateLiteral";
|
|
93
|
+
expressions: Expression[];
|
|
94
|
+
quasis: Array<{ value: { cooked: string } }>;
|
|
95
|
+
};
|
|
96
|
+
if (tl.expressions.length === 0) {
|
|
97
|
+
return { kind: "static", text: tl.quasis[0].value.cooked };
|
|
98
|
+
}
|
|
99
|
+
return { exprNode: expr, kind: "dynamic" };
|
|
100
|
+
}
|
|
101
|
+
/* Identifier, MemberExpression, CallExpression, etc. → dynamic */
|
|
102
|
+
return { exprNode: expr, kind: "dynamic" };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Resolve a key node to a string, or null if unresolvable (computed/dynamic). */
|
|
106
|
+
function resolveKey(prop: { computed: boolean; key: Node }): string | null {
|
|
107
|
+
if (prop.computed) return null;
|
|
108
|
+
const key = prop.key as { type: string; name?: string; value?: unknown };
|
|
109
|
+
if (key.type === "Identifier" && typeof key.name === "string") return key.name;
|
|
110
|
+
if (key.type === "Literal" && typeof key.value === "string") return key.value;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Merge properties from a literal ObjectExpression into an existing IR (for spread inlining). */
|
|
115
|
+
function mergeObjectIntoIR(objExpr: ObjectExpression, ir: SxIR, diagnostics: EvalDiagnostic[]): boolean {
|
|
116
|
+
for (const propOrSpread of objExpr.properties) {
|
|
117
|
+
if (propOrSpread.type === "SpreadElement") {
|
|
118
|
+
const spread = propOrSpread as { type: "SpreadElement"; argument: Expression };
|
|
119
|
+
/* Only inline if argument is itself an ObjectExpression */
|
|
120
|
+
if (spread.argument.type === "ObjectExpression") {
|
|
121
|
+
const ok = mergeObjectIntoIR(spread.argument as ObjectExpression, ir, diagnostics);
|
|
122
|
+
if (!ok) return false;
|
|
123
|
+
} else {
|
|
124
|
+
diagnostics.push({
|
|
125
|
+
kind: "error",
|
|
126
|
+
message: `partially dynamic sx object: ${(spread.argument as { name?: string }).name ?? "expression"} cannot be resolved at build time`,
|
|
127
|
+
node: propOrSpread as unknown as Node,
|
|
128
|
+
});
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const prop = propOrSpread as {
|
|
135
|
+
type: "Property";
|
|
136
|
+
computed: boolean;
|
|
137
|
+
key: Node;
|
|
138
|
+
value: Expression;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (prop.computed) {
|
|
142
|
+
diagnostics.push({
|
|
143
|
+
kind: "warning",
|
|
144
|
+
message: "computed key in sx object cannot be statically resolved — skipped",
|
|
145
|
+
node: prop.key,
|
|
146
|
+
});
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const keyStr = resolveKey(prop);
|
|
151
|
+
if (keyStr === null) {
|
|
152
|
+
diagnostics.push({
|
|
153
|
+
kind: "warning",
|
|
154
|
+
message: "unresolvable key in sx object — skipped",
|
|
155
|
+
node: prop.key,
|
|
156
|
+
});
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (keyStr === "variants") {
|
|
161
|
+
/* variants: { propName: { value: SxIR } } */
|
|
162
|
+
if (prop.value.type !== "ObjectExpression") continue;
|
|
163
|
+
const variantsObj = prop.value as ObjectExpression;
|
|
164
|
+
for (const varProp of variantsObj.properties) {
|
|
165
|
+
if (varProp.type !== "Property") continue;
|
|
166
|
+
const vp = varProp as { type: "Property"; computed: boolean; key: Node; value: Expression };
|
|
167
|
+
const varName = resolveKey(vp);
|
|
168
|
+
if (!varName) continue;
|
|
169
|
+
if (vp.value.type !== "ObjectExpression") continue;
|
|
170
|
+
const valuesObj = vp.value as ObjectExpression;
|
|
171
|
+
ir.variants[varName] = ir.variants[varName] ?? {};
|
|
172
|
+
for (const valProp of valuesObj.properties) {
|
|
173
|
+
if (valProp.type !== "Property") continue;
|
|
174
|
+
const vvp = valProp as { type: "Property"; computed: boolean; key: Node; value: Expression };
|
|
175
|
+
const valName = resolveKey(vvp);
|
|
176
|
+
if (!valName) continue;
|
|
177
|
+
if (vvp.value.type !== "ObjectExpression") continue;
|
|
178
|
+
const bodyIR = makeEmptyIR();
|
|
179
|
+
mergeObjectIntoIR(vvp.value as ObjectExpression, bodyIR, diagnostics);
|
|
180
|
+
ir.variants[varName][valName] = bodyIR;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (keyStr.startsWith("&")) {
|
|
187
|
+
/* nested selector */
|
|
188
|
+
if (prop.value.type !== "ObjectExpression") continue;
|
|
189
|
+
const bodyIR = makeEmptyIR();
|
|
190
|
+
mergeObjectIntoIR(prop.value as ObjectExpression, bodyIR, diagnostics);
|
|
191
|
+
ir.selectors.push({ body: bodyIR, sel: keyStr });
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (keyStr.startsWith("@")) {
|
|
196
|
+
/* at-rule */
|
|
197
|
+
if (prop.value.type !== "ObjectExpression") continue;
|
|
198
|
+
const bodyIR = makeEmptyIR();
|
|
199
|
+
mergeObjectIntoIR(prop.value as ObjectExpression, bodyIR, diagnostics);
|
|
200
|
+
ir.atRules.push({ at: keyStr, body: bodyIR });
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* plain CSS property */
|
|
205
|
+
ir.base[keyStr] = evalValue(keyStr, prop.value);
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Evaluate a JSX `sx={{...}}` ObjectExpression to an intermediate representation. */
|
|
211
|
+
export function evaluateSxObject(node: ObjectExpression): EvalResult {
|
|
212
|
+
const diagnostics: EvalDiagnostic[] = [];
|
|
213
|
+
const ir = makeEmptyIR();
|
|
214
|
+
|
|
215
|
+
/* First pass: check for top-level spreads that are non-literal */
|
|
216
|
+
for (const propOrSpread of node.properties) {
|
|
217
|
+
if (propOrSpread.type === "SpreadElement") {
|
|
218
|
+
const spread = propOrSpread as { type: "SpreadElement"; argument: Expression };
|
|
219
|
+
if (spread.argument.type !== "ObjectExpression") {
|
|
220
|
+
const name = (spread.argument as { name?: string }).name ?? "expression";
|
|
221
|
+
diagnostics.push({
|
|
222
|
+
kind: "error",
|
|
223
|
+
message: `partially dynamic sx object: ${name} cannot be resolved at build time`,
|
|
224
|
+
node: propOrSpread as unknown as Node,
|
|
225
|
+
});
|
|
226
|
+
return { diagnostics, ir: null };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const ok = mergeObjectIntoIR(node, ir, diagnostics);
|
|
232
|
+
if (!ok) return { diagnostics, ir: null };
|
|
233
|
+
|
|
234
|
+
return { diagnostics, ir };
|
|
235
|
+
}
|