@hyperfixi/vite-plugin 2.7.2 → 2.8.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/dist/index.d.cts CHANGED
@@ -209,6 +209,11 @@ interface HtmxUsage {
209
209
  urlManagement: Set<string>;
210
210
  /** Whether hx-confirm is used */
211
211
  usesConfirm: boolean;
212
+ /**
213
+ * `hx-swap` carries a `swap:`/`settle:` timing modifier, which the translator
214
+ * emits as `wait <n>ms`. Optional so older fixture literals stay valid.
215
+ */
216
+ needsSwapTiming?: boolean;
212
217
  /**
213
218
  * htmx v4 reactive/streaming surface detected. Each flag pulls in the
214
219
  * corresponding bundle module; collectively they're what makes
@@ -625,6 +630,16 @@ declare function getOptimalRegion(languages: Set<SupportedLanguage>): 'western'
625
630
  * ```
626
631
  */
627
632
 
633
+ /**
634
+ * Compute a hash of the current usage for dev-server cache invalidation.
635
+ *
636
+ * Every input that influences bundle SELECTION must be part of this hash.
637
+ * The htmx/reactivity flags route to the hybrid-hx / hx-v4 tiers; before they
638
+ * were hashed, adding or removing an hx-live/sse-connect/ws-send attribute —
639
+ * without touching any command/block/language — left the cached dev bundle at
640
+ * the wrong tier. Exported for tests.
641
+ */
642
+ declare function computeUsageHash(usage: AggregatedUsage): string;
628
643
  /**
629
644
  * Create the HyperFixi Vite plugin
630
645
  *
@@ -633,4 +648,4 @@ declare function getOptimalRegion(languages: Set<SupportedLanguage>): 'western'
633
648
  */
634
649
  declare function hyperfixi(options?: HyperfixiPluginOptions): Plugin;
635
650
 
636
- export { type AggregatedUsage, type CompileOptions, type CompiledHandler, type CustomLanguageKeywords, type DomainAggregatedUsage, DomainAggregator, type DomainFileUsage, type DomainScanRule, DomainScanner, type FileUsage, type HyperfixiPluginOptions, REGIONS, SUPPORTED_LANGUAGES, type ServerBridgeOptions, type SupportedLanguage, clearCustomKeywords, clearSemanticParser, hyperfixi as default, descriptorToScanRule, detectLanguages, getAllLanguageCodes, getKeywordsForLanguage, getOptimalRegion, hasSemanticParser, hyperfixi, isNonLatinLanguage, registerCustomKeywords, setSemanticParser };
651
+ export { type AggregatedUsage, type CompileOptions, type CompiledHandler, type CustomLanguageKeywords, type DomainAggregatedUsage, DomainAggregator, type DomainFileUsage, type DomainScanRule, DomainScanner, type FileUsage, type HyperfixiPluginOptions, REGIONS, SUPPORTED_LANGUAGES, type ServerBridgeOptions, type SupportedLanguage, clearCustomKeywords, clearSemanticParser, computeUsageHash, hyperfixi as default, descriptorToScanRule, detectLanguages, getAllLanguageCodes, getKeywordsForLanguage, getOptimalRegion, hasSemanticParser, hyperfixi, isNonLatinLanguage, registerCustomKeywords, setSemanticParser };
package/dist/index.d.ts CHANGED
@@ -209,6 +209,11 @@ interface HtmxUsage {
209
209
  urlManagement: Set<string>;
210
210
  /** Whether hx-confirm is used */
211
211
  usesConfirm: boolean;
212
+ /**
213
+ * `hx-swap` carries a `swap:`/`settle:` timing modifier, which the translator
214
+ * emits as `wait <n>ms`. Optional so older fixture literals stay valid.
215
+ */
216
+ needsSwapTiming?: boolean;
212
217
  /**
213
218
  * htmx v4 reactive/streaming surface detected. Each flag pulls in the
214
219
  * corresponding bundle module; collectively they're what makes
@@ -625,6 +630,16 @@ declare function getOptimalRegion(languages: Set<SupportedLanguage>): 'western'
625
630
  * ```
626
631
  */
627
632
 
633
+ /**
634
+ * Compute a hash of the current usage for dev-server cache invalidation.
635
+ *
636
+ * Every input that influences bundle SELECTION must be part of this hash.
637
+ * The htmx/reactivity flags route to the hybrid-hx / hx-v4 tiers; before they
638
+ * were hashed, adding or removing an hx-live/sse-connect/ws-send attribute —
639
+ * without touching any command/block/language — left the cached dev bundle at
640
+ * the wrong tier. Exported for tests.
641
+ */
642
+ declare function computeUsageHash(usage: AggregatedUsage): string;
628
643
  /**
629
644
  * Create the HyperFixi Vite plugin
630
645
  *
@@ -633,4 +648,4 @@ declare function getOptimalRegion(languages: Set<SupportedLanguage>): 'western'
633
648
  */
634
649
  declare function hyperfixi(options?: HyperfixiPluginOptions): Plugin;
635
650
 
636
- export { type AggregatedUsage, type CompileOptions, type CompiledHandler, type CustomLanguageKeywords, type DomainAggregatedUsage, DomainAggregator, type DomainFileUsage, type DomainScanRule, DomainScanner, type FileUsage, type HyperfixiPluginOptions, REGIONS, SUPPORTED_LANGUAGES, type ServerBridgeOptions, type SupportedLanguage, clearCustomKeywords, clearSemanticParser, hyperfixi as default, descriptorToScanRule, detectLanguages, getAllLanguageCodes, getKeywordsForLanguage, getOptimalRegion, hasSemanticParser, hyperfixi, isNonLatinLanguage, registerCustomKeywords, setSemanticParser };
651
+ export { type AggregatedUsage, type CompileOptions, type CompiledHandler, type CustomLanguageKeywords, type DomainAggregatedUsage, DomainAggregator, type DomainFileUsage, type DomainScanRule, DomainScanner, type FileUsage, type HyperfixiPluginOptions, REGIONS, SUPPORTED_LANGUAGES, type ServerBridgeOptions, type SupportedLanguage, clearCustomKeywords, clearSemanticParser, computeUsageHash, hyperfixi as default, descriptorToScanRule, detectLanguages, getAllLanguageCodes, getKeywordsForLanguage, getOptimalRegion, hasSemanticParser, hyperfixi, isNonLatinLanguage, registerCustomKeywords, setSemanticParser };
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/scanner.ts
2
+ import { AVAILABLE_COMMANDS, FULL_RUNTIME_ONLY_COMMANDS } from "@hyperfixi/core/bundle-generator";
3
+
1
4
  // src/language-keywords.ts
2
5
  var SUPPORTED_LANGUAGES = [
3
6
  "en",
@@ -369,6 +372,7 @@ var INDONESIAN_KEYWORDS = /* @__PURE__ */ new Set([
369
372
  "kalau",
370
373
  "bila",
371
374
  "selainnya",
375
+ "lainnya",
372
376
  "ulangi",
373
377
  "tunggu",
374
378
  "selama"
@@ -505,6 +509,7 @@ var VIETNAMESE_KEYWORDS = /* @__PURE__ */ new Set([
505
509
  "l\u1EB7p l\u1EA1i",
506
510
  "ch\u1EDD",
507
511
  "\u0111\u1EE3i",
512
+ "ch\u1EDD \u0111\u1EE3i",
508
513
  "trong khi"
509
514
  ]);
510
515
  var POLISH_KEYWORDS = /* @__PURE__ */ new Set([
@@ -911,6 +916,17 @@ var SSE_NS_PATTERN = /\bsse-[\w\-\p{L}]+\s*=\s*["']/u;
911
916
  var WS_NS_PATTERN = /\bws-[\w\-\p{L}]+\s*=\s*["']/u;
912
917
 
913
918
  // src/scanner.ts
919
+ var SCANNABLE_COMMANDS = [
920
+ .../* @__PURE__ */ new Set([...AVAILABLE_COMMANDS, ...FULL_RUNTIME_ONLY_COMMANDS])
921
+ ].filter(
922
+ (cmd) => /^[A-Za-z]+$/.test(cmd) && // `unless` is handled by the block detection below (it compiles to the
923
+ // lite-capable `if` block — flagging it as a full-runtime-only COMMAND
924
+ // would needlessly bump every unless-user to a full-runtime bundle).
925
+ // `bind` is handled by the dedicated reactivity detection below, which
926
+ // routes to the hx-v4 bundle (the tier that actually ships reactivity).
927
+ cmd !== "unless" && cmd !== "bind"
928
+ );
929
+ var COMMAND_PATTERN_SOURCE = `\\b(${SCANNABLE_COMMANDS.join("|")})\\b`;
914
930
  var HTMX_REQUEST_PATTERN = /\b(hx-get|hx-post|hx-put|hx-patch|hx-delete)\s*=\s*["']([^"']+)["']/gi;
915
931
  var FIXI_ACTION_PATTERN = /\bfx-action\s*=\s*["']([^"']+)["']/gi;
916
932
  var FIXI_METHOD_PATTERN = /\bfx-method\s*=\s*["'](GET|POST|PUT|PATCH|DELETE)["']/gi;
@@ -1019,7 +1035,7 @@ var Scanner = class {
1019
1035
  * Analyze a hyperscript snippet for commands, blocks, and expressions
1020
1036
  */
1021
1037
  analyzeScript(script, usage) {
1022
- const commandPattern = /\b(toggle|add|remove|removeClass|show|hide|set|get|put|append|take|increment|decrement|log|send|trigger|wait|transition|go|call|focus|blur|return)\b/g;
1038
+ const commandPattern = new RegExp(COMMAND_PATTERN_SOURCE, "g");
1023
1039
  let match;
1024
1040
  while (match = commandPattern.exec(script)) {
1025
1041
  usage.commands.add(match[1]);
@@ -1058,6 +1074,7 @@ var Scanner = class {
1058
1074
  triggerModifiers: /* @__PURE__ */ new Set(),
1059
1075
  urlManagement: /* @__PURE__ */ new Set(),
1060
1076
  usesConfirm: false,
1077
+ needsSwapTiming: false,
1061
1078
  needsHxLive: false,
1062
1079
  needsSSE: false,
1063
1080
  needsWS: false,
@@ -1083,7 +1100,14 @@ var Scanner = class {
1083
1100
  const swapPattern = new RegExp(HTMX_SWAP_PATTERN.source, "gi");
1084
1101
  while (match = swapPattern.exec(code)) {
1085
1102
  usage.hasHtmxAttributes = true;
1086
- usage.swapStrategies.add(match[2].split(/\s+/)[0]);
1103
+ const spec = match[2].trim();
1104
+ const head = spec.split(/\s+/)[0];
1105
+ if (head.startsWith("morph") || !/^\S*:/.test(spec)) {
1106
+ usage.swapStrategies.add(head);
1107
+ }
1108
+ if (/\b(swap|settle):/.test(spec)) {
1109
+ usage.needsSwapTiming = true;
1110
+ }
1087
1111
  }
1088
1112
  const triggerPattern = new RegExp(HTMX_TRIGGER_PATTERN.source, "gi");
1089
1113
  while (match = triggerPattern.exec(code)) {
@@ -1151,17 +1175,19 @@ var Scanner = class {
1151
1175
  usage.commands.add("put");
1152
1176
  }
1153
1177
  for (const swap of htmx.swapStrategies) {
1154
- switch (swap.toLowerCase()) {
1155
- case "morph":
1156
- usage.commands.add("morph");
1157
- break;
1158
- case "delete":
1159
- usage.commands.add("remove");
1160
- break;
1161
- default:
1162
- usage.commands.add("put");
1178
+ const strategy = swap.toLowerCase();
1179
+ if (strategy.startsWith("morph")) {
1180
+ usage.commands.add("morph");
1181
+ if (strategy === "morph:innerhtml") usage.commands.add("put");
1182
+ } else if (strategy === "delete") {
1183
+ usage.commands.add("remove");
1184
+ } else {
1185
+ usage.commands.add("put");
1163
1186
  }
1164
1187
  }
1188
+ if (htmx.needsSwapTiming) {
1189
+ usage.commands.add("wait");
1190
+ }
1165
1191
  if (htmx.usesConfirm) {
1166
1192
  usage.blocks.add("if");
1167
1193
  }
@@ -1256,6 +1282,7 @@ var Aggregator = class {
1256
1282
  triggerModifiers: /* @__PURE__ */ new Set(),
1257
1283
  urlManagement: /* @__PURE__ */ new Set(),
1258
1284
  usesConfirm: false,
1285
+ needsSwapTiming: false,
1259
1286
  needsHxLive: false,
1260
1287
  needsSSE: false,
1261
1288
  needsWS: false,
@@ -1280,6 +1307,7 @@ var Aggregator = class {
1280
1307
  for (const modifier of usage.htmx.triggerModifiers) htmx.triggerModifiers.add(modifier);
1281
1308
  for (const url of usage.htmx.urlManagement) htmx.urlManagement.add(url);
1282
1309
  if (usage.htmx.usesConfirm) htmx.usesConfirm = true;
1310
+ if (usage.htmx.needsSwapTiming) htmx.needsSwapTiming = true;
1283
1311
  if (usage.htmx.needsHxLive) htmx.needsHxLive = true;
1284
1312
  if (usage.htmx.needsSSE) htmx.needsSSE = true;
1285
1313
  if (usage.htmx.needsWS) htmx.needsWS = true;
@@ -1352,7 +1380,15 @@ var Aggregator = class {
1352
1380
  htmxUsageEqual(a, b) {
1353
1381
  if (!a && !b) return true;
1354
1382
  if (!a || !b) return false;
1355
- return a.hasHtmxAttributes === b.hasHtmxAttributes && a.hasFixiAttributes === b.hasFixiAttributes && this.setsEqual(a.httpMethods, b.httpMethods) && this.setsEqual(a.swapStrategies, b.swapStrategies) && this.setsEqual(a.triggerModifiers, b.triggerModifiers) && this.setsEqual(a.urlManagement, b.urlManagement) && a.usesConfirm === b.usesConfirm;
1383
+ return a.hasHtmxAttributes === b.hasHtmxAttributes && a.hasFixiAttributes === b.hasFixiAttributes && this.setsEqual(a.httpMethods, b.httpMethods) && this.setsEqual(a.swapStrategies, b.swapStrategies) && this.setsEqual(a.triggerModifiers, b.triggerModifiers) && this.setsEqual(a.urlManagement, b.urlManagement) && a.usesConfirm === b.usesConfirm && !!a.needsSwapTiming === !!b.needsSwapTiming && // htmx v4 / reactivity flags: these drive selection of the hx-v4 tier,
1384
+ // so a change must count as "usage changed" or HMR serves a stale
1385
+ // bundle. They were omitted when the v4 surface landed (#623).
1386
+ !!a.needsHxLive === !!b.needsHxLive && !!a.needsSSE === !!b.needsSSE && !!a.needsWS === !!b.needsWS && !!a.needsBindToProperty === !!b.needsBindToProperty && !!a.needsReactivity === !!b.needsReactivity && this.arraysEqual(a.onHandlers, b.onHandlers);
1387
+ }
1388
+ arraysEqual(a, b) {
1389
+ const aa = a ?? [];
1390
+ const bb = b ?? [];
1391
+ return aa.length === bb.length && aa.every((v, i) => v === bb[i]);
1356
1392
  }
1357
1393
  };
1358
1394
 
@@ -1366,7 +1402,7 @@ import {
1366
1402
  LITE_PARSER_TEMPLATE,
1367
1403
  HYBRID_PARSER_TEMPLATE,
1368
1404
  canUseLiteParser,
1369
- FULL_RUNTIME_ONLY_COMMANDS,
1405
+ FULL_RUNTIME_ONLY_COMMANDS as FULL_RUNTIME_ONLY_COMMANDS2,
1370
1406
  isAvailableCommand
1371
1407
  } from "@hyperfixi/core/bundle-generator";
1372
1408
 
@@ -2460,7 +2496,7 @@ ${additionalApiProps}`
2460
2496
  getUnsupportedCommands(commands) {
2461
2497
  const unsupported = [];
2462
2498
  for (const cmd of commands) {
2463
- if (FULL_RUNTIME_ONLY_COMMANDS.includes(cmd)) {
2499
+ if (FULL_RUNTIME_ONLY_COMMANDS2.includes(cmd)) {
2464
2500
  unsupported.push(cmd);
2465
2501
  } else if (!isAvailableCommand(cmd) && !COMMAND_IMPLEMENTATIONS[cmd]) {
2466
2502
  unsupported.push(cmd);
@@ -3751,6 +3787,22 @@ var DomainAggregator = class {
3751
3787
  var VIRTUAL_MODULE_ID = "virtual:hyperfixi";
3752
3788
  var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
3753
3789
  var IMPORT_ALIASES = ["hyperfixi", "@hyperfixi/core", "virtual:hyperfixi"];
3790
+ function computeUsageHash(usage) {
3791
+ const commands = [...usage.commands].sort().join(",");
3792
+ const blocks = [...usage.blocks].sort().join(",");
3793
+ const languages = [...usage.detectedLanguages].sort().join(",");
3794
+ const htmx = usage.htmx ? [
3795
+ usage.htmx.hasHtmxAttributes,
3796
+ usage.htmx.hasFixiAttributes,
3797
+ usage.htmx.needsHxLive,
3798
+ usage.htmx.needsSSE,
3799
+ usage.htmx.needsWS,
3800
+ usage.htmx.needsBindToProperty,
3801
+ usage.htmx.needsReactivity
3802
+ ].map((f) => f ? "1" : "0").join("") : "none";
3803
+ const reactivity = `${usage.needsReactivity ? 1 : 0}${usage.needsBindToProperty ? 1 : 0}`;
3804
+ return `${commands}|${blocks}|${usage.positional}|${languages}|${htmx}|${reactivity}`;
3805
+ }
3754
3806
  function hyperfixi(options = {}) {
3755
3807
  const mode = options.mode ?? "interpret";
3756
3808
  const scanner = new Scanner(options);
@@ -3767,12 +3819,6 @@ function hyperfixi(options = {}) {
3767
3819
  const fallbackScripts = /* @__PURE__ */ new Set();
3768
3820
  let needsLocals = false;
3769
3821
  let needsGlobals = false;
3770
- function computeUsageHash(usage) {
3771
- const commands = [...usage.commands].sort().join(",");
3772
- const blocks = [...usage.blocks].sort().join(",");
3773
- const languages = [...usage.detectedLanguages].sort().join(",");
3774
- return `${commands}|${blocks}|${usage.positional}|${languages}`;
3775
- }
3776
3822
  function invalidateVirtualModule() {
3777
3823
  if (!server) return;
3778
3824
  const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
@@ -4072,6 +4118,7 @@ export {
4072
4118
  SUPPORTED_LANGUAGES,
4073
4119
  clearCustomKeywords,
4074
4120
  clearSemanticParser,
4121
+ computeUsageHash,
4075
4122
  index_default as default,
4076
4123
  descriptorToScanRule,
4077
4124
  detectLanguages,