@stacksjs/stx 0.2.100 → 0.2.101

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.
@@ -132,6 +132,33 @@ export declare function extractExports(setupContent: string): string;
132
132
  * injects the signals runtime and browser runtime.
133
133
  */
134
134
  export declare function processSignals(template: string, options: StxOptions, filePath?: string, serverData?: Record<string, unknown>): Promise<string>;
135
+ /**
136
+ * Index of the first `pattern` match in `html` that is not inside a `<script>`
137
+ * or `<style>` block, or -1 when every match is embedded.
138
+ *
139
+ * Markup landmarks cannot be located with a plain `indexOf` / `String.replace`
140
+ * once a runtime has been inlined: those scripts are hundreds of kilobytes of
141
+ * JavaScript that legitimately mention `<body>` and `</head>` in comments,
142
+ * strings and regex literals. Anchoring on the first textual hit splices
143
+ * generated markup into the middle of a script tag, which both corrupts the
144
+ * script and — when the inserted markup contains `</script>` — silently ends
145
+ * the tag early.
146
+ */
147
+ export declare function findMarkupIndexOutsideScripts(html: string, pattern: RegExp): number;
148
+ /**
149
+ * Turn the specifier list of an `import { … } from 'stx'` into local aliases.
150
+ *
151
+ * The setup function already destructures the stx API from `window.stx`, so a
152
+ * plain `{ state }` specifier needs nothing. A RENAMED one does: when the
153
+ * bundler inlines a composable that imports a name the outer script already
154
+ * binds, it emits `import { state as state2 } from '@stacksjs/stx'`. Dropping
155
+ * that line loses the mapping and every `state2(…)` call in the inlined module
156
+ * throws `state2 is not defined` — which aborts the whole setup, so no
157
+ * directive on the page ever binds.
158
+ *
159
+ * Type-only specifiers are erased: they have no runtime binding to alias.
160
+ */
161
+ export declare function rewriteStxImportSpecifiers(specifiers: string): string;
135
162
  /**
136
163
  * Parsed form of a loop expression (`@for` / `@foreach` argument).
137
164
  *
@@ -667,7 +667,7 @@ export async function processScriptSetup(template, filePath, serverData) {
667
667
  projectRoot: process.cwd()
668
668
  });
669
669
  }
670
- scriptContent = scriptContent.replace(/^\s*import\s+(?:type\s+)?\{[^}]*\}\s+from\s+['"](?:stx|@stacksjs\/stx|@stacksjs\/browser)['"]\s*;?\s*$/gm, "// [stx import stripped \u2014 resolved via window.stx in __stx_setup]");
670
+ scriptContent = scriptContent.replace(/^\s*import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+['"](?:stx|@stacksjs\/stx|@stacksjs\/browser)['"]\s*;?\s*$/gm, (_match, specifiers) => rewriteStxImportSpecifiers(specifiers));
671
671
  const resolved = transformStoreImports(scriptContent);
672
672
  resolvedParts.push(`// \u2500\u2500 merged signal script #${i + 1} \u2500\u2500
673
673
  ${resolved}`);
@@ -908,35 +908,22 @@ export async function processSignals(template, options, filePath, serverData) {
908
908
  output = processedOutput;
909
909
  output = await injectSignalsRuntime(output, options);
910
910
  output = injectBrowserRuntime(output);
911
- if (setupCode)
912
- if (output.includes("</head>"))
913
- output = output.replace("</head>", `${setupCode}
914
- </head>`);
915
- else if (output.includes("<body"))
916
- output = output.replace(/<body([^>]*)>/, `<body$1>
917
- ${setupCode}`);
918
- else
919
- output = output + `
920
- ` + setupCode;
911
+ if (setupCode) {
912
+ const headIdx = findMarkupIndexOutsideScripts(output, /<\/head>/i), bodyIdx = headIdx === -1 ? findMarkupIndexOutsideScripts(output, /<body[^>]*>/i) : -1;
913
+ if (headIdx !== -1)
914
+ output = `${output.slice(0, headIdx)}${setupCode}
915
+ ${output.slice(headIdx)}`;
916
+ else if (bodyIdx !== -1) {
917
+ const bodyTag = /<body[^>]*>/i.exec(output.slice(bodyIdx))[0], insertAt = bodyIdx + bodyTag.length;
918
+ output = `${output.slice(0, insertAt)}
919
+ ${setupCode}${output.slice(insertAt)}`;
920
+ } else
921
+ output = `${output}
922
+ ${setupCode}`;
923
+ }
921
924
  const bodyMatch = output.replace(/<script\b[\s\S]*?<\/script>/gi, "").replace(/<style\b[\s\S]*?<\/style>/gi, "").match(/<body([^>]*)>/i), bodyHasDataStx = bodyMatch && /data-stx/.test(bodyMatch[1]);
922
925
  if (!setupCode && !bodyHasDataStx && bodyMatch && !/data-stx-auto/.test(bodyMatch[1])) {
923
- const bodyTagRe = /<body[^>]*>/i, scriptOrStyleRe = /<(script|style)\b[\s\S]*?<\/\1>/gi, skipRanges = [];
924
- let m;
925
- while ((m = scriptOrStyleRe.exec(output)) !== null)
926
- skipRanges.push([m.index, m.index + m[0].length]);
927
- let searchFrom = 0, bodyIdx = -1;
928
- while (searchFrom < output.length) {
929
- bodyTagRe.lastIndex = searchFrom;
930
- const hit = bodyTagRe.exec(output.slice(searchFrom));
931
- if (!hit)
932
- break;
933
- const absIdx = searchFrom + hit.index;
934
- if (!skipRanges.some(([s, e]) => absIdx >= s && absIdx < e)) {
935
- bodyIdx = absIdx;
936
- break;
937
- }
938
- searchFrom = absIdx + hit[0].length;
939
- }
926
+ const bodyTagRe = /<body[^>]*>/i, bodyIdx = findMarkupIndexOutsideScripts(output, bodyTagRe);
940
927
  if (bodyIdx !== -1) {
941
928
  const before = output.slice(0, bodyIdx), after = output.slice(bodyIdx).replace(bodyTagRe, (m) => m.replace(/^<body([^>]*)>$/i, "<body$1 data-stx-auto>"));
942
929
  output = before + after;
@@ -944,3 +931,36 @@ ${setupCode}`);
944
931
  }
945
932
  return output;
946
933
  }
934
+ export function findMarkupIndexOutsideScripts(html, pattern) {
935
+ const scriptOrStyleRe = /<(script|style)\b[\s\S]*?<\/\1>/gi, skipRanges = [];
936
+ let block;
937
+ while ((block = scriptOrStyleRe.exec(html)) !== null)
938
+ skipRanges.push([block.index, block.index + block[0].length]);
939
+ const probe = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
940
+ let searchFrom = 0;
941
+ while (searchFrom < html.length) {
942
+ const hit = probe.exec(html.slice(searchFrom));
943
+ if (!hit)
944
+ return -1;
945
+ const absoluteIndex = searchFrom + hit.index;
946
+ if (!skipRanges.some(([start, end]) => absoluteIndex >= start && absoluteIndex < end))
947
+ return absoluteIndex;
948
+ searchFrom = absoluteIndex + Math.max(hit[0].length, 1);
949
+ }
950
+ return -1;
951
+ }
952
+ const STX_IMPORT_STRIPPED = "// [stx import stripped \u2014 resolved via window.stx in __stx_setup]";
953
+ export function rewriteStxImportSpecifiers(specifiers) {
954
+ const aliases = [];
955
+ for (const raw of specifiers.split(",")) {
956
+ const specifier = raw.trim();
957
+ if (!specifier || specifier.startsWith("type "))
958
+ continue;
959
+ const renamed = specifier.match(/^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/);
960
+ if (renamed && renamed[1] !== renamed[2])
961
+ aliases.push(`${renamed[2]} = ${renamed[1]}`);
962
+ }
963
+ if (aliases.length === 0)
964
+ return STX_IMPORT_STRIPPED;
965
+ return `const ${aliases.join(", ")}; ${STX_IMPORT_STRIPPED}`;
966
+ }