@sigil-dev/grimoire 0.8.0 → 0.8.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
5
  "private": false,
6
- "version": "0.8.0",
6
+ "version": "0.8.2",
7
7
  "exports": {
8
8
  ".": "./index.ts",
9
9
  "./server": "./server.ts",
@@ -14,7 +14,8 @@
14
14
  "./vite": "./src/integrations/vite.ts",
15
15
  "./bun": "./src/rendering/ssrPlugin.ts",
16
16
  "./env/public": "./src/env/public.ts",
17
- "./env/private": "./src/env/private.ts"
17
+ "./env/private": "./src/env/private.ts",
18
+ "./logger": "./src/logger/index.ts"
18
19
  },
19
20
  "bin": {
20
21
  "grimoire": "src/sync.ts"
@@ -32,13 +33,13 @@
32
33
  "vite": "^8.0.16"
33
34
  },
34
35
  "peerDependencies": {
35
- "@sigil-dev/compiler": "0.8.0",
36
- "@sigil-dev/runtime": "0.8.0",
36
+ "@sigil-dev/compiler": "0.8.2",
37
+ "@sigil-dev/runtime": "0.8.2",
37
38
  "typescript": "^5"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@types/bun": "latest",
41
- "@sigil-dev/compiler": "0.8.0",
42
- "@sigil-dev/runtime": "0.8.0"
42
+ "@sigil-dev/compiler": "0.8.2",
43
+ "@sigil-dev/runtime": "0.8.2"
43
44
  }
44
45
  }
package/server.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { log } from "./src/logger/instance.ts";
1
2
  export { scanRoutes } from "./src/routing/scanner";
2
3
  export { createServer } from "./src/server";
3
4
  export { buildProject } from "./src/server/build";
@@ -1,291 +1,291 @@
1
- import { popHydrationNodes, pushHydrationNodes } from "@sigil-dev/runtime";
2
- import { withEffectScope } from "./scope";
3
-
4
- let routeMap: Record<
5
- string,
6
- ((props: any) => any) & { load?: (ctx: any) => Promise<any> }
7
- > = {};
8
- let layoutList: { path: string; component: (props: any) => any }[] = [];
9
- let disposeCurrentPage: (() => void) | null = null;
10
- let currentPath = location.pathname;
11
-
12
- // C1: Navigation hooks
13
- let beforeNavigateCb: ((url: URL) => boolean | void) | null = null;
14
- let onNavigateCb: ((url: URL) => void) | null = null;
15
- let afterNavigateCb: ((url: URL) => void) | null = null;
16
-
17
- // // HMR
18
- // (globalThis as any).__grimoire_routes__ = routeMap;
19
- // (globalThis as any).__grimoire_current_pattern__ = currentPath;
20
- // (globalThis as any).__grimoire_navigate__ = navigate;
21
-
22
- export async function hmrRerender(): Promise<void> {
23
- if (!lastState) {
24
- location.reload();
25
- return;
26
- }
27
- disposeCurrentPage?.();
28
- pushHydrationNodes([]);
29
- let rootNode: any;
30
- disposeCurrentPage = withEffectScope(() => {
31
- rootNode = buildRouteTree(lastState!);
32
- });
33
- popHydrationNodes();
34
- document.getElementById("grimoire-root")?.replaceChildren(rootNode);
35
- }
36
-
37
- // (globalThis as any).__grimoire_rerender__ = hmrRerender;
38
-
39
- export function beforeNavigate(cb: (url: URL) => boolean | void): void {
40
- beforeNavigateCb = cb;
41
- }
42
-
43
- export function onNavigate(cb: (url: URL) => void): void {
44
- onNavigateCb = cb;
45
- }
46
-
47
- export function afterNavigate(cb: (url: URL) => void): void {
48
- afterNavigateCb = cb;
49
- }
50
-
51
- // C2: Scroll restoration
52
- const scrollPositions = new Map<string, number>();
53
-
54
- function saveScrollPosition(): void {
55
- scrollPositions.set(currentPath, window.scrollY);
56
- }
57
-
58
- function restoreScrollPosition(path: string): void {
59
- const saved = scrollPositions.get(path);
60
- if (saved !== undefined) {
61
- requestAnimationFrame(() => window.scrollTo(0, saved));
62
- } else {
63
- requestAnimationFrame(() => window.scrollTo(0, 0));
64
- }
65
- }
66
-
67
- // C3: Preloading
68
- let preloadTimer: Timer | null = null;
69
-
70
- function setupPreload(): void {
71
- document.addEventListener("mouseover", (e) => {
72
- const a = (e.target as Element).closest("a");
73
- if (!a) return;
74
- const href = a.getAttribute("href");
75
- if (!href) return;
76
- if (/^(https?:\/\/|\/\/|#|mailto:|tel:)/.test(href)) return;
77
-
78
- const preloadData = a.hasAttribute("data-sigil-preload-data");
79
- const preloadCode = a.hasAttribute("data-sigil-preload-code");
80
- if (!preloadData && !preloadCode) return;
81
-
82
- if (preloadTimer) clearTimeout(preloadTimer);
83
- preloadTimer = setTimeout(() => {
84
- if (preloadCode) {
85
- const link = document.createElement("link");
86
- link.rel = "prefetch";
87
- link.href = href;
88
- document.head.appendChild(link);
89
- }
90
- if (preloadData) {
91
- fetch(href, {
92
- headers: { "x-grimoire-navigate": "1" },
93
- priority: "low",
94
- }).catch(() => {});
95
- }
96
- }, 80);
97
- });
98
-
99
- document.addEventListener("mouseout", (e) => {
100
- const a = (e.target as Element).closest("a");
101
- if (!a) return;
102
- if (preloadTimer) clearTimeout(preloadTimer);
103
- });
104
- }
105
-
106
- let lastState: {
107
- data: any;
108
- layoutData: any[];
109
- params: any;
110
- pattern: string;
111
- } | null = null;
112
-
113
- function buildRouteTree(state: typeof lastState): any {
114
- const { data, layoutData, params, pattern } = state!;
115
- const Page = routeMap[pattern];
116
- if (!Page) throw new Error(`no component for pattern ${pattern}`);
117
-
118
- const matchedLayouts = layoutList
119
- .filter(
120
- (l) =>
121
- l.path === "/" ||
122
- pattern === l.path ||
123
- pattern.startsWith(l.path + "/"),
124
- )
125
- .sort((a, b) => a.path.length - b.path.length);
126
-
127
- let renderFn = () => {
128
- const pageNode = Page({ data, params });
129
- const pageDiv = document.createElement("div");
130
- pageDiv.id = "grimoire-page";
131
- pageDiv.appendChild(pageNode);
132
- return pageDiv;
133
- };
134
-
135
- for (let i = matchedLayouts.length - 1; i >= 0; i--) {
136
- const LayoutComponent = matchedLayouts[i].component;
137
- const innerRender = renderFn;
138
- const currentLayoutData = layoutData?.[i];
139
- renderFn = () => {
140
- const childNode = innerRender();
141
- return LayoutComponent({
142
- data: currentLayoutData,
143
- params,
144
- children: childNode,
145
- });
146
- };
147
- }
148
-
149
- return renderFn();
150
- }
151
-
152
- export async function navigate(path: string) {
153
- const url = new URL(path, location.origin);
154
-
155
- if (beforeNavigateCb) {
156
- const shouldProceed = beforeNavigateCb(url);
157
- if (shouldProceed === false) return;
158
- }
159
- onNavigateCb?.(url);
160
- saveScrollPosition();
161
-
162
- const res = await fetch(path, { headers: { "x-grimoire-navigate": "1" } });
163
- const contentType = res.headers.get("content-type") ?? "";
164
- if (!contentType.includes("application/json")) {
165
- window.location.href = path;
166
- return;
167
- }
168
- const json = await res.json();
169
- const { data, layoutData, params, pattern, head } = json;
170
- const Page = routeMap[pattern];
171
- if (!Page) {
172
- window.location.href = path;
173
- return;
174
- }
175
-
176
- let mergedData = data;
177
- if (Page.load) {
178
- try {
179
- const clientData = await Page.load({
180
- params,
181
- url,
182
- request: new Request(url),
183
- });
184
- mergedData = { ...clientData, ...data };
185
- } catch (e: any) {
186
- if (e?.location) {
187
- window.location.href = e.location;
188
- return;
189
- }
190
- console.error("[grimoire] universal load error:", e);
191
- }
192
- }
193
-
194
- // track state for HMR rerender
195
- lastState = { data: mergedData, layoutData, params, pattern };
196
- (globalThis as any).__grimoire_current_pattern__ = pattern;
197
-
198
- disposeCurrentPage?.();
199
- disposeCurrentPage = null;
200
- pushHydrationNodes([]);
201
- let rootNode: any;
202
- disposeCurrentPage = withEffectScope(() => {
203
- try {
204
- rootNode = buildRouteTree(lastState!);
205
- } catch (e) {
206
- console.error(e);
207
- }
208
- });
209
- popHydrationNodes();
210
-
211
- updateHead(head);
212
- document.title = head?.title ?? document.title;
213
- document.getElementById("grimoire-root")?.replaceChildren(rootNode);
214
- currentPath = path;
215
- history.pushState(null, "", path);
216
- restoreScrollPosition(path);
217
- afterNavigateCb?.(url);
218
- }
219
-
220
- export function initRouter(
221
- routes: Record<
222
- string,
223
- ((props: any) => any) & { load?: (ctx: any) => Promise<any> }
224
- >,
225
- layouts: any[],
226
- initialDispose?: () => void,
227
- ) {
228
- routeMap = routes;
229
- layoutList = layouts;
230
- disposeCurrentPage = initialDispose ?? null;
231
- currentPath = location.pathname;
232
-
233
- // set initial HMR state from SSR
234
- const stateEl = document.getElementById("__grimoire_state__");
235
- if (stateEl) {
236
- const state = JSON.parse(stateEl.textContent!);
237
- lastState = {
238
- data: state.data,
239
- layoutData: state.layoutData,
240
- params: state.params,
241
- pattern: state.pattern,
242
- };
243
- (globalThis as any).__grimoire_current_pattern__ = state.pattern;
244
- (globalThis as any).__grimoire_routes__ = routeMap;
245
- (globalThis as any).__grimoire_rerender__ = hmrRerender;
246
- (globalThis as any).__grimoire_navigate__ = (path: string) =>
247
- navigate(path);
248
- }
249
-
250
- saveScrollPosition();
251
- document.addEventListener("click", handleClick);
252
- window.addEventListener("popstate", () => {
253
- if (location.pathname !== currentPath) {
254
- saveScrollPosition();
255
- currentPath = location.pathname;
256
- navigate(location.pathname);
257
- }
258
- });
259
- setupPreload();
260
- }
261
-
262
- function updateHead(headHtml: string) {
263
- document
264
- .querySelectorAll("[data-grimoire-head]")
265
- //biome-ignore lint/suspicious/useIterableCallbackReturn: shut up
266
- .forEach((el) => el.remove());
267
- const parsed = headHtml
268
- ? new DOMParser().parseFromString(`<head>${headHtml}</head>`, "text/html")
269
- : null;
270
- const incomingTitle = parsed?.querySelector("title");
271
- //biome-ignore lint/suspicious/useIterableCallbackReturn: shut up
272
- document.querySelectorAll("title").forEach((el) => el.remove());
273
- document.title = incomingTitle?.textContent ?? document.title;
274
- if (parsed) {
275
- for (const el of Array.from(parsed.head.children)) {
276
- if (el.tagName === "TITLE") continue;
277
- (el as HTMLElement).dataset.grimoireHead = "1";
278
- document.head.appendChild(el);
279
- }
280
- }
281
- }
282
-
283
- function handleClick(e: MouseEvent) {
284
- const a = (e.target as Element).closest("a");
285
- if (!a) return;
286
- const href = a.getAttribute("href");
287
- if (!href) return;
288
- if (/^(https?:\/\/|\/\/|#|mailto:|tel:)/.test(href)) return;
289
- e.preventDefault();
290
- navigate(href);
291
- }
1
+ import { popHydrationNodes, pushHydrationNodes } from "@sigil-dev/runtime";
2
+ import { withEffectScope } from "./scope";
3
+
4
+ let routeMap: Record<
5
+ string,
6
+ ((props: any) => any) & { load?: (ctx: any) => Promise<any> }
7
+ > = {};
8
+ let layoutList: { path: string; component: (props: any) => any }[] = [];
9
+ let disposeCurrentPage: (() => void) | null = null;
10
+ let currentPath = location.pathname;
11
+
12
+ // C1: Navigation hooks
13
+ let beforeNavigateCb: ((url: URL) => boolean | undefined) | null = null;
14
+ let onNavigateCb: ((url: URL) => void) | null = null;
15
+ let afterNavigateCb: ((url: URL) => void) | null = null;
16
+
17
+ // // HMR
18
+ // (globalThis as any).__grimoire_routes__ = routeMap;
19
+ // (globalThis as any).__grimoire_current_pattern__ = currentPath;
20
+ // (globalThis as any).__grimoire_navigate__ = navigate;
21
+
22
+ export async function hmrRerender(): Promise<void> {
23
+ if (!lastState) {
24
+ location.reload();
25
+ return;
26
+ }
27
+ disposeCurrentPage?.();
28
+ pushHydrationNodes([]);
29
+ let rootNode: any;
30
+ disposeCurrentPage = withEffectScope(() => {
31
+ rootNode = buildRouteTree(lastState!);
32
+ });
33
+ popHydrationNodes();
34
+ document.getElementById("grimoire-root")?.replaceChildren(rootNode);
35
+ }
36
+
37
+ // (globalThis as any).__grimoire_rerender__ = hmrRerender;
38
+
39
+ export function beforeNavigate(cb: (url: URL) => boolean | undefined): void {
40
+ beforeNavigateCb = cb;
41
+ }
42
+
43
+ export function onNavigate(cb: (url: URL) => void): void {
44
+ onNavigateCb = cb;
45
+ }
46
+
47
+ export function afterNavigate(cb: (url: URL) => void): void {
48
+ afterNavigateCb = cb;
49
+ }
50
+
51
+ // C2: Scroll restoration
52
+ const scrollPositions = new Map<string, number>();
53
+
54
+ function saveScrollPosition(): void {
55
+ scrollPositions.set(currentPath, window.scrollY);
56
+ }
57
+
58
+ function restoreScrollPosition(path: string): void {
59
+ const saved = scrollPositions.get(path);
60
+ if (saved !== undefined) {
61
+ requestAnimationFrame(() => window.scrollTo(0, saved));
62
+ } else {
63
+ requestAnimationFrame(() => window.scrollTo(0, 0));
64
+ }
65
+ }
66
+
67
+ // C3: Preloading
68
+ let preloadTimer: Timer | null = null;
69
+
70
+ function setupPreload(): void {
71
+ document.addEventListener("mouseover", (e) => {
72
+ const a = (e.target as Element).closest("a");
73
+ if (!a) return;
74
+ const href = a.getAttribute("href");
75
+ if (!href) return;
76
+ if (/^(https?:\/\/|\/\/|#|mailto:|tel:)/.test(href)) return;
77
+
78
+ const preloadData = a.hasAttribute("data-sigil-preload-data");
79
+ const preloadCode = a.hasAttribute("data-sigil-preload-code");
80
+ if (!preloadData && !preloadCode) return;
81
+
82
+ if (preloadTimer) clearTimeout(preloadTimer);
83
+ preloadTimer = setTimeout(() => {
84
+ if (preloadCode) {
85
+ const link = document.createElement("link");
86
+ link.rel = "prefetch";
87
+ link.href = href;
88
+ document.head.appendChild(link);
89
+ }
90
+ if (preloadData) {
91
+ fetch(href, {
92
+ headers: { "x-grimoire-navigate": "1" },
93
+ priority: "low",
94
+ }).catch(() => {});
95
+ }
96
+ }, 80);
97
+ });
98
+
99
+ document.addEventListener("mouseout", (e) => {
100
+ const a = (e.target as Element).closest("a");
101
+ if (!a) return;
102
+ if (preloadTimer) clearTimeout(preloadTimer);
103
+ });
104
+ }
105
+
106
+ let lastState: {
107
+ data: any;
108
+ layoutData: any[];
109
+ params: any;
110
+ pattern: string;
111
+ } | null = null;
112
+
113
+ function buildRouteTree(state: typeof lastState): any {
114
+ const { data, layoutData, params, pattern } = state!;
115
+ const Page = routeMap[pattern];
116
+ if (!Page) throw new Error(`no component for pattern ${pattern}`);
117
+
118
+ const matchedLayouts = layoutList
119
+ .filter(
120
+ (l) =>
121
+ l.path === "/" ||
122
+ pattern === l.path ||
123
+ pattern.startsWith(`${l.path}/`),
124
+ )
125
+ .sort((a, b) => a.path.length - b.path.length);
126
+
127
+ let renderFn = () => {
128
+ const pageNode = Page({ data, params });
129
+ const pageDiv = document.createElement("div");
130
+ pageDiv.id = "grimoire-page";
131
+ pageDiv.appendChild(pageNode);
132
+ return pageDiv;
133
+ };
134
+
135
+ for (let i = matchedLayouts.length - 1; i >= 0; i--) {
136
+ const LayoutComponent = matchedLayouts[i].component;
137
+ const innerRender = renderFn;
138
+ const currentLayoutData = layoutData?.[i];
139
+ renderFn = () => {
140
+ const childNode = innerRender();
141
+ return LayoutComponent({
142
+ data: currentLayoutData,
143
+ params,
144
+ children: childNode,
145
+ });
146
+ };
147
+ }
148
+
149
+ return renderFn();
150
+ }
151
+
152
+ export async function navigate(path: string) {
153
+ const url = new URL(path, location.origin);
154
+
155
+ if (beforeNavigateCb) {
156
+ const shouldProceed = beforeNavigateCb(url);
157
+ if (shouldProceed === false) return;
158
+ }
159
+ onNavigateCb?.(url);
160
+ saveScrollPosition();
161
+
162
+ const res = await fetch(path, { headers: { "x-grimoire-navigate": "1" } });
163
+ const contentType = res.headers.get("content-type") ?? "";
164
+ if (!contentType.includes("application/json")) {
165
+ window.location.href = path;
166
+ return;
167
+ }
168
+ const json = await res.json();
169
+ const { data, layoutData, params, pattern, head } = json;
170
+ const Page = routeMap[pattern];
171
+ if (!Page) {
172
+ window.location.href = path;
173
+ return;
174
+ }
175
+
176
+ let mergedData = data;
177
+ if (Page.load) {
178
+ try {
179
+ const clientData = await Page.load({
180
+ params,
181
+ url,
182
+ request: new Request(url),
183
+ });
184
+ mergedData = { ...clientData, ...data };
185
+ } catch (e: any) {
186
+ if (e?.location) {
187
+ window.location.href = e.location;
188
+ return;
189
+ }
190
+ console.error("[grimoire] universal load error:", e);
191
+ }
192
+ }
193
+
194
+ // track state for HMR rerender
195
+ lastState = { data: mergedData, layoutData, params, pattern };
196
+ (globalThis as any).__grimoire_current_pattern__ = pattern;
197
+
198
+ disposeCurrentPage?.();
199
+ disposeCurrentPage = null;
200
+ pushHydrationNodes([]);
201
+ let rootNode: any;
202
+ disposeCurrentPage = withEffectScope(() => {
203
+ try {
204
+ rootNode = buildRouteTree(lastState!);
205
+ } catch (e) {
206
+ console.error(e);
207
+ }
208
+ });
209
+ popHydrationNodes();
210
+
211
+ updateHead(head);
212
+ document.title = head?.title ?? document.title;
213
+ document.getElementById("grimoire-root")?.replaceChildren(rootNode);
214
+ currentPath = path;
215
+ history.pushState(null, "", path);
216
+ restoreScrollPosition(path);
217
+ afterNavigateCb?.(url);
218
+ }
219
+
220
+ export function initRouter(
221
+ routes: Record<
222
+ string,
223
+ ((props: any) => any) & { load?: (ctx: any) => Promise<any> }
224
+ >,
225
+ layouts: any[],
226
+ initialDispose?: () => void,
227
+ ) {
228
+ routeMap = routes;
229
+ layoutList = layouts;
230
+ disposeCurrentPage = initialDispose ?? null;
231
+ currentPath = location.pathname;
232
+
233
+ // set initial HMR state from SSR
234
+ const stateEl = document.getElementById("__grimoire_state__");
235
+ if (stateEl) {
236
+ const state = JSON.parse(stateEl.textContent!);
237
+ lastState = {
238
+ data: state.data,
239
+ layoutData: state.layoutData,
240
+ params: state.params,
241
+ pattern: state.pattern,
242
+ };
243
+ (globalThis as any).__grimoire_current_pattern__ = state.pattern;
244
+ (globalThis as any).__grimoire_routes__ = routeMap;
245
+ (globalThis as any).__grimoire_rerender__ = hmrRerender;
246
+ (globalThis as any).__grimoire_navigate__ = (path: string) =>
247
+ navigate(path);
248
+ }
249
+
250
+ saveScrollPosition();
251
+ document.addEventListener("click", handleClick);
252
+ window.addEventListener("popstate", () => {
253
+ if (location.pathname !== currentPath) {
254
+ saveScrollPosition();
255
+ currentPath = location.pathname;
256
+ navigate(location.pathname);
257
+ }
258
+ });
259
+ setupPreload();
260
+ }
261
+
262
+ function updateHead(headHtml: string) {
263
+ document
264
+ .querySelectorAll("[data-grimoire-head]")
265
+ //biome-ignore lint/suspicious/useIterableCallbackReturn: shut up
266
+ .forEach((el) => el.remove());
267
+ const parsed = headHtml
268
+ ? new DOMParser().parseFromString(`<head>${headHtml}</head>`, "text/html")
269
+ : null;
270
+ const incomingTitle = parsed?.querySelector("title");
271
+ //biome-ignore lint/suspicious/useIterableCallbackReturn: shut up
272
+ document.querySelectorAll("title").forEach((el) => el.remove());
273
+ document.title = incomingTitle?.textContent ?? document.title;
274
+ if (parsed) {
275
+ for (const el of Array.from(parsed.head.children)) {
276
+ if (el.tagName === "TITLE") continue;
277
+ (el as HTMLElement).dataset.grimoireHead = "1";
278
+ document.head.appendChild(el);
279
+ }
280
+ }
281
+ }
282
+
283
+ function handleClick(e: MouseEvent) {
284
+ const a = (e.target as Element).closest("a");
285
+ if (!a) return;
286
+ const href = a.getAttribute("href");
287
+ if (!href) return;
288
+ if (/^(https?:\/\/|\/\/|#|mailto:|tel:)/.test(href)) return;
289
+ e.preventDefault();
290
+ navigate(href);
291
+ }
@@ -1,13 +1,10 @@
1
1
  import { existsSync } from "node:fs";
2
- import { dirname, extname, join, relative } from "node:path";
2
+ import { dirname, join, relative } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
- import type { DevGraph } from "./graph";
5
- import { normalizePath } from "./paths";
6
4
 
7
5
  const RUNTIME_URL = "/__grimoire__/runtime.js";
8
6
  const MODULES_BASE = "/__grimoire__/m/";
9
7
  const DEPS_BASE = "/__grimoire__/dep/";
10
-
11
8
  /**
12
9
  * Rewrite a single import specifier to a browser-servable URL.
13
10
  * project-relative → /__grimoire__/m/...
@@ -72,7 +69,7 @@ function rewriteImports(
72
69
  ): string {
73
70
  return code.replace(
74
71
  /(from\s+|import\s+)(["'])([^"']+)\2/g,
75
- (match, keyword, quote, specifier) => {
72
+ (_match, keyword, quote, specifier) => {
76
73
  const rewritten = rewriteSpecifier(specifier, filePath, projectRoot);
77
74
  return `${keyword}${quote}${rewritten}${quote}`;
78
75
  },
@@ -119,11 +116,6 @@ export async function compileForBrowser(
119
116
  plugins: [[sigilPlugin, { mode: "dom", hash }]],
120
117
  filename: filePath,
121
118
  });
122
-
123
- console.log(
124
- "[compile] babel output first 500 chars:",
125
- babelResult?.code?.slice(0, 500),
126
- );
127
119
  // strip TypeScript types
128
120
  const transpiler = new Bun.Transpiler({ loader: "ts", target: "browser" });
129
121
  let js = transpiler.transformSync(babelResult?.code ?? "");
@@ -157,13 +149,12 @@ export async function compileForBrowser(
157
149
  // inject scoped CSS at runtime
158
150
  if (css) {
159
151
  //biome-ignore lint: bro shut up already
160
- js =
161
- `if (typeof document !== 'undefined' && !document.getElementById('sigil-${hash}')) {
152
+ js = `if (typeof document !== 'undefined' && !document.getElementById('sigil-${hash}')) {
162
153
  const __s = document.createElement('style');
163
154
  __s.id = 'sigil-${hash}';
164
155
  __s.textContent = ${JSON.stringify(css)};
165
156
  document.head.appendChild(__s);
166
- }\n` + js;
157
+ }\n${js}`;
167
158
  }
168
159
 
169
160
  // rewrite imports to browser-servable URLs