@stacksjs/stx 0.2.160 → 0.2.162

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.
@@ -106,6 +106,24 @@ export declare function usesSignalsInScript(template: string): boolean;
106
106
  * attributes and surrounding HTML are left alone.
107
107
  */
108
108
  export declare function interpolateScriptExpressions(scriptBody: string, context: Record<string, any>): string;
109
+ /**
110
+ * Interpolate expressions in a `<script>` tag's ATTRIBUTES.
111
+ *
112
+ * Attributes are HTML, not JavaScript, so they get HTML rules rather than the
113
+ * JSON-stringifying rules {@link interpolateScriptExpressions} applies to a
114
+ * body: `data-id="{{ id }}"` must become `data-id="7"`, never `data-id=""7""`.
115
+ *
116
+ * This is the only channel a module island has for server data. A module
117
+ * script is emitted verbatim — no IIFE, no server-data bridge — so
118
+ * `<script type="module" data-session="{{ id }}">` was the documented way to
119
+ * pass a value in, and it shipped the literal text `{{ id }}` to the browser.
120
+ * The failure is silent: the markup looks right, and only the code reading
121
+ * `dataset.session` sees a mustache.
122
+ *
123
+ * Unresolvable expressions are left untouched, matching the "preserve for the
124
+ * client" semantics used everywhere else.
125
+ */
126
+ export declare function interpolateScriptAttributes(attrs: string, context: Record<string, any>): string;
109
127
  /**
110
128
  * Options for {@link interpolateScriptsInTemplate}.
111
129
  */
@@ -123,8 +141,9 @@ export interface InterpolateScriptsOptions {
123
141
  * skipping any script that opts out via `data-raw`, `type="application/json"`,
124
142
  * or a `src=` attribute (external scripts don't have inline content to template).
125
143
  *
126
- * Each matched script has its body passed through {@link interpolateScriptExpressions}.
127
- * Attributes and tag structure are never modified.
144
+ * Each matched script has its body passed through {@link interpolateScriptExpressions}
145
+ * and its attributes through {@link interpolateScriptAttributes}. Tag structure
146
+ * is otherwise untouched.
128
147
  */
129
148
  export declare function interpolateScriptsInTemplate(template: string, context: Record<string, any>, options?: InterpolateScriptsOptions): string;
130
149
  /**
@@ -325,22 +325,51 @@ export function interpolateScriptExpressions(scriptBody, context) {
325
325
  });
326
326
  return output;
327
327
  }
328
+ export function interpolateScriptAttributes(attrs, context) {
329
+ if (!attrs.includes("{"))
330
+ return attrs;
331
+ let output = attrs;
332
+ output = output.replace(/\{!!([\s\S]*?)!!\}/g, (match, expr) => {
333
+ try {
334
+ const value = evaluateExpression(expr, context);
335
+ if (value === void 0)
336
+ return match;
337
+ return value === null ? "" : String(value);
338
+ } catch {
339
+ return match;
340
+ }
341
+ });
342
+ output = output.replace(/\{\{([\s\S]*?)\}\}/g, (match, expr) => {
343
+ const trimmed = expr.trim();
344
+ if (/^__[A-Z_]+__$/.test(trimmed))
345
+ return match;
346
+ try {
347
+ const value = evaluateExpression(expr, context);
348
+ if (value === void 0)
349
+ return match;
350
+ if (value === null)
351
+ return "";
352
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
353
+ } catch {
354
+ return match;
355
+ }
356
+ });
357
+ return output;
358
+ }
328
359
  export function interpolateScriptsInTemplate(template, context, options = {}) {
329
360
  const { output, scripts } = stashScriptElements(template), interpolatedScripts = scripts.map((full) => {
330
361
  const match = full.match(/^<script\b([^>]*)>([\s\S]*?)<\/script\s*>$/i), opening = full.match(/^<script\b[^>]*>/i), closingStart = full.search(/<\/script\s*>$/i);
331
362
  if (!match || !opening || closingStart === -1)
332
363
  return full;
333
364
  const attrs = match[1], body = match[2];
334
- if (/\bsrc\s*=/.test(attrs))
335
- return full;
336
365
  if (/\bdata-raw\b/.test(attrs))
337
366
  return full;
338
- if (/\btype\s*=\s*["']application\/json["']/.test(attrs))
339
- return full;
340
367
  if (options.skipServer && /\bserver\b/.test(attrs))
341
368
  return full;
342
- const interpolated = interpolateScriptExpressions(body, context);
343
- return opening[0] + interpolated + full.slice(closingStart);
369
+ const openTag = `<script${interpolateScriptAttributes(attrs, context)}>`;
370
+ if (/\bsrc\s*=/.test(attrs) || /\btype\s*=\s*["']application\/json["']/.test(attrs))
371
+ return openTag + body + full.slice(closingStart);
372
+ return openTag + interpolateScriptExpressions(body, context) + full.slice(closingStart);
344
373
  });
345
374
  return restoreStashedScripts(output, interpolatedScripts);
346
375
  }