@stacksjs/stx 0.2.113 → 0.2.114

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.
@@ -0,0 +1,103 @@
1
+ import { createPreferences, createWindow, setAutoLaunch } from "@stacksjs/desktop";
2
+ import { renderTemplate } from "./render.js";
3
+ const MENU_BAR_WINDOW = {
4
+ width: 320,
5
+ height: 640,
6
+ systemTray: !0,
7
+ hideDockIcon: !0,
8
+ titlebarHidden: !0,
9
+ resizable: !1,
10
+ alwaysOnTop: !0,
11
+ darkMode: !0
12
+ }, JSON_HEADERS = { "Content-Type": "application/json; charset=utf-8" };
13
+ export function normalizeRouteKey(key) {
14
+ const [first, ...rest] = key.trim().split(/\s+/);
15
+ return rest.length ? `${first.toUpperCase()} ${rest.join(" ")}` : `GET ${first}`;
16
+ }
17
+ export function toResponse(result) {
18
+ if (result instanceof Response)
19
+ return result;
20
+ if (result === void 0)
21
+ return new Response(null, { status: 204 });
22
+ return new Response(JSON.stringify(result), { headers: JSON_HEADERS });
23
+ }
24
+ export function applyPreferencePatch(preferences, patch) {
25
+ const known = preferences.getAll();
26
+ for (const [key, value] of Object.entries(patch))
27
+ if (key in known)
28
+ preferences.set(key, value);
29
+ return preferences.getAll();
30
+ }
31
+ export function createMenuBarApp(options) {
32
+ const preferences = createPreferences({
33
+ name: options.name.toLowerCase(),
34
+ defaults: options.preferences ?? {}
35
+ }), routes = new Map(Object.entries(options.routes ?? {}).map(([key, handler]) => [normalizeRouteKey(key), handler])), builtins = {
36
+ "GET /api/preferences": () => preferences.getAll(),
37
+ "POST /api/preferences": async (request) => applyPreferencePatch(preferences, await request.json()),
38
+ "GET /api/menu": () => options.menu?.(preferences) ?? []
39
+ };
40
+ for (const [key, handler] of Object.entries(builtins))
41
+ if (!routes.has(key))
42
+ routes.set(key, handler);
43
+ let server = null, window = null, url = null;
44
+ async function renderPopup() {
45
+ const context = await options.context?.(preferences) ?? {}, html = await renderTemplate(options.template, {
46
+ context,
47
+ wrapInDocument: !0,
48
+ title: options.name
49
+ });
50
+ return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" } });
51
+ }
52
+ function serve() {
53
+ server = Bun.serve({
54
+ port: options.port ?? 0,
55
+ hostname: "127.0.0.1",
56
+ async fetch(request) {
57
+ const { pathname } = new URL(request.url);
58
+ if (request.method === "GET" && (pathname === "/" || pathname === "/index.html"))
59
+ return renderPopup();
60
+ const handler = routes.get(`${request.method} ${pathname}`);
61
+ if (!handler)
62
+ return new Response("Not Found", { status: 404 });
63
+ return toResponse(await handler(request, preferences));
64
+ }
65
+ });
66
+ url = `http://127.0.0.1:${server.port}`;
67
+ }
68
+ function bindLaunchAtLogin() {
69
+ const key = options.launchAtLogin;
70
+ if (!key)
71
+ return;
72
+ const sync = (enabled) => {
73
+ setAutoLaunch(Boolean(enabled), { appName: options.name, isHidden: !0 }).catch(() => {});
74
+ };
75
+ sync(preferences.get(key));
76
+ preferences.onChange(key, sync);
77
+ }
78
+ return {
79
+ preferences,
80
+ get url() {
81
+ return url;
82
+ },
83
+ async start() {
84
+ if (!server)
85
+ serve();
86
+ bindLaunchAtLogin();
87
+ window = await createWindow(url, {
88
+ ...MENU_BAR_WINDOW,
89
+ title: options.name,
90
+ ...options.window
91
+ });
92
+ return this;
93
+ },
94
+ stop() {
95
+ window?.close();
96
+ window = null;
97
+ server?.stop();
98
+ server = null;
99
+ url = null;
100
+ preferences.close();
101
+ }
102
+ };
103
+ }
package/dist/plugin.js CHANGED
@@ -5,6 +5,7 @@ import { isDevelopment } from "./env.js";
5
5
  import { escapeHtml } from "./expressions.js";
6
6
  import { performanceMonitor } from "./performance-utils.js";
7
7
  import { injectRouterScript } from "./process.js";
8
+ import { findSfcTemplateBlock } from "./sfc-template.js";
8
9
  export const plugin = {
9
10
  name: "bun-plugin-stx",
10
11
  async setup(build) {
@@ -42,9 +43,9 @@ export const plugin = {
42
43
  throw new StxFileError(`stx file is empty: ${filePath}`, filePath);
43
44
  const { scriptContent, templateContent, allScripts } = performanceMonitor.time("script-extraction", () => {
44
45
  let workingContent = content;
45
- const templateTagMatch = content.match(/<template\b(?![^>]*(?:\b(?:id|x-for|x-if|@for|@if|:for|:if)\s*=|\s#[\w-]|\bv-slot|\bslot\s*=))[^>]*>([\s\S]*?)<\/template>/i);
46
- if (templateTagMatch)
47
- workingContent = templateTagMatch[1].trim();
46
+ const templateBlock = findSfcTemplateBlock(content);
47
+ if (templateBlock)
48
+ workingContent = templateBlock.content.trim();
48
49
  const allScriptMatches = content.match(/<script\b[^>]*>[\s\S]*?<\/script>/gi) || [];
49
50
  let serverScriptContent = "";
50
51
  const clientScripts = [];
package/dist/process.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { injectCrosswindCSS } from "./dev-server/crosswind.js";
3
- import { matchHtmlComment, maskAtElementPosition } from "./html-masking.js";
3
+ import { matchHtmlComment, matchStyleElement, maskAtElementPosition, stashScriptElements } from "./html-masking.js";
4
4
  import { processA11yDirectives } from "./a11y.js";
5
5
  import { generateLifecycleRuntime } from "./composables.js";
6
6
  import { processTemplateBindings } from "./reactive-bindings.js";
@@ -186,6 +186,58 @@ function findScriptBodyEnd(src, from) {
186
186
  }
187
187
  return -1;
188
188
  }
189
+ function findScriptBlockByAttribute(html, attribute) {
190
+ const escapedAttribute = attribute.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), openingTag = new RegExp(`<script\\b(?=[^>]*\\b${escapedAttribute}(?:\\s|=|/?>))[^>]*>`, "i").exec(html);
191
+ if (!openingTag)
192
+ return null;
193
+ const bodyStart = openingTag.index + openingTag[0].length, closeStart = html.indexOf("</script>", bodyStart);
194
+ if (closeStart === -1)
195
+ return null;
196
+ const closeEnd = html.indexOf(">", closeStart);
197
+ if (closeEnd === -1)
198
+ return null;
199
+ return {
200
+ start: openingTag.index,
201
+ end: closeEnd + 1
202
+ };
203
+ }
204
+ function findFirstScriptTag(html) {
205
+ let searchFrom = 0;
206
+ while (searchFrom < html.length) {
207
+ const tagStart = html.indexOf("<", searchFrom);
208
+ if (tagStart === -1)
209
+ return -1;
210
+ if (html.startsWith("<!--", tagStart)) {
211
+ const commentEnd = html.indexOf("-->", tagStart + 4);
212
+ searchFrom = commentEnd === -1 ? html.length : commentEnd + 3;
213
+ continue;
214
+ }
215
+ const remainder = html.slice(tagStart), styleTag = /^<style\b[^>]*>/i.exec(remainder);
216
+ if (styleTag) {
217
+ const styleEnd = html.toLowerCase().indexOf("</style>", tagStart + styleTag[0].length);
218
+ searchFrom = styleEnd === -1 ? html.length : styleEnd + 8;
219
+ continue;
220
+ }
221
+ if (/^<script\b[^>]*>/i.test(remainder))
222
+ return tagStart;
223
+ searchFrom = tagStart + 1;
224
+ }
225
+ return -1;
226
+ }
227
+ function placeSignalsRuntimeBeforeScripts(html) {
228
+ const runtime = findScriptBlockByAttribute(html, "data-stx-runtime");
229
+ if (!runtime)
230
+ return html;
231
+ const firstScript = findFirstScriptTag(html);
232
+ if (firstScript === -1 || firstScript === runtime.start)
233
+ return html;
234
+ const runtimeTag = html.slice(runtime.start, runtime.end), withoutRuntime = html.slice(0, runtime.start) + html.slice(runtime.end), insertionPoint = findFirstScriptTag(withoutRuntime);
235
+ if (insertionPoint === -1)
236
+ return runtimeTag + `
237
+ ` + withoutRuntime;
238
+ return withoutRuntime.slice(0, insertionPoint) + runtimeTag + `
239
+ ` + withoutRuntime.slice(insertionPoint);
240
+ }
189
241
  export async function processDirectives(template, context, filePath, options, dependencies) {
190
242
  if (options.ssr === !1 && !options.autoShell && options.buildMode === "spa")
191
243
  return template;
@@ -238,21 +290,28 @@ export async function processDirectives(template, context, filePath, options, de
238
290
  result = result.replace(wrongAttrRegex, "$1");
239
291
  }
240
292
  }
241
- if (isTopLevel)
293
+ if (isTopLevel) {
242
294
  result = injectCloakStyle(result);
295
+ result = placeSignalsRuntimeBeforeScripts(result);
296
+ }
243
297
  if (isTopLevel)
244
298
  try {
245
299
  const { getStoreScript } = await import("./store-loader.js"), resolvedStoresDir = options.storesDir, storeCode = await getStoreScript(resolvedStoresDir);
246
300
  if (storeCode) {
247
- const storeTag = `<script data-stx-stores>${storeCode}</script>`, runtimeStart = result.indexOf("<script data-stx-scoped>");
248
- if (runtimeStart !== -1) {
249
- const runtimeClose = result.indexOf("</script>", runtimeStart + 24);
250
- if (runtimeClose !== -1) {
251
- const insertPos = runtimeClose + 9;
252
- result = result.slice(0, insertPos) + `
253
- ` + storeTag + result.slice(insertPos);
254
- }
255
- }
301
+ const storeTag = `<script data-stx-stores>${storeCode}</script>`, runtime = findScriptBlockByAttribute(result, "data-stx-runtime");
302
+ if (runtime)
303
+ result = result.slice(0, runtime.end) + `
304
+ ` + storeTag + result.slice(runtime.end);
305
+ }
306
+ } catch {}
307
+ if (isTopLevel)
308
+ try {
309
+ const { getComposableScript } = await import("./composable-loader.js"), resolvedComposablesDir = options.composablesDir, composableCode = await getComposableScript(resolvedComposablesDir);
310
+ if (composableCode) {
311
+ const composableTag = `<script data-stx-composables>${composableCode}</script>`, anchor = findScriptBlockByAttribute(result, "data-stx-stores") || findScriptBlockByAttribute(result, "data-stx-runtime");
312
+ if (anchor)
313
+ result = result.slice(0, anchor.end) + `
314
+ ` + composableTag + result.slice(anchor.end);
256
315
  }
257
316
  } catch {}
258
317
  if (isTopLevel && context.__stx_inject_css !== !1)
@@ -370,9 +429,11 @@ ${output.trim()}
370
429
  });
371
430
  output = processStackReplacements(output, stacks);
372
431
  if (layoutPath) {
373
- const orphans = [];
374
- output = output.replace(/<(script|style)\b[\s\S]*?<\/\1>/gi, (m) => {
375
- orphans.push(m);
432
+ const orphans = [], scripts = stashScriptElements(output), styles = maskAtElementPosition(scripts.output, matchStyleElement, (_token, index) => `\x00STX_ORPHAN_STYLE_${index}\x00`);
433
+ output = styles.output.replace(/\x00STX_(SCRIPT|ORPHAN_STYLE)_(\d+)\x00/g, (_match, kind, index) => {
434
+ const token = kind === "SCRIPT" ? scripts.scripts[Number(index)] : styles.tokens[Number(index)];
435
+ if (token)
436
+ orphans.push(token);
376
437
  return "";
377
438
  });
378
439
  if (orphans.length > 0) {
@@ -670,18 +731,17 @@ ${runtimeScript}`);
670
731
  ` + output;
671
732
  }
672
733
  {
673
- const serverScriptRe = /<script\s+server\b[^>]*>/gi;
674
- let serverMatch;
675
- const serverRemoveRanges = [];
676
- while ((serverMatch = serverScriptRe.exec(output)) !== null) {
677
- const tagEnd = serverMatch.index + serverMatch[0].length, closeIdx = findScriptBodyEnd(output, tagEnd);
678
- if (closeIdx === -1)
734
+ const { scanScriptTags } = await import("./signal-processing.js"), serverRemoveRanges = [];
735
+ for (const script of scanScriptTags(output)) {
736
+ if (!/\bserver\b/i.test(script.attrs))
737
+ continue;
738
+ const bodyStart = script.start + script.fullMatch.indexOf(">") + 1, closeStart = findScriptBodyEnd(output, bodyStart);
739
+ if (closeStart === -1)
679
740
  continue;
680
- let rangeEnd = closeIdx + 9;
741
+ let rangeEnd = closeStart + 9;
681
742
  while (rangeEnd < output.length && /\s/.test(output[rangeEnd]))
682
743
  rangeEnd++;
683
- serverRemoveRanges.push({ start: serverMatch.index, end: rangeEnd });
684
- serverScriptRe.lastIndex = rangeEnd;
744
+ serverRemoveRanges.push({ start: script.start, end: rangeEnd });
685
745
  }
686
746
  for (let ri = serverRemoveRanges.length - 1;ri >= 0; ri--) {
687
747
  const { start, end } = serverRemoveRanges[ri];