@hyperfixi/vite-plugin 3.3.0 → 4.0.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.js CHANGED
@@ -1,5 +1,67 @@
1
- // src/scanner.ts
2
- import { AVAILABLE_COMMANDS, FULL_RUNTIME_ONLY_COMMANDS } from "@hyperfixi/core/bundle-generator";
1
+ // src/engine-modules.ts
2
+ import * as engine from "@hyperfixi/engine";
3
+ import { createGrammar, everything } from "@hyperfixi/engine";
4
+ function exportNameOf(mod) {
5
+ for (const [name, value] of Object.entries(engine)) {
6
+ if (value === mod) return name;
7
+ }
8
+ throw new Error("[hyperfixi] an engine module in `everything` is not a named export");
9
+ }
10
+ var ENGINE_MODULES = everything.map((mod) => {
11
+ const grammar = createGrammar();
12
+ mod(grammar);
13
+ return {
14
+ name: exportNameOf(mod),
15
+ // The template text line registers under a keyword no identifier can be
16
+ // spelled as (`#text`); it is not something a scan can find.
17
+ commands: Object.keys(grammar.commands).filter((k) => /^[a-z]+$/.test(k)),
18
+ features: Object.keys(grammar.features)
19
+ };
20
+ });
21
+ var byName = new Map(ENGINE_MODULES.map((m) => [m.name, m]));
22
+ var COMMAND_KEYWORDS = new Map(
23
+ ENGINE_MODULES.flatMap((m) => m.commands.map((k) => [k, m.name]))
24
+ );
25
+ var FEATURE_KEYWORDS = new Map(
26
+ ENGINE_MODULES.flatMap((m) => m.features.map((k) => [k, m.name]))
27
+ );
28
+ var NOT_SCANNED = /* @__PURE__ */ new Set(["on", "if", "live", "when", "bind"]);
29
+ var SCANNABLE_KEYWORDS = [
30
+ .../* @__PURE__ */ new Set([...COMMAND_KEYWORDS.keys(), ...FEATURE_KEYWORDS.keys()])
31
+ ].filter((k) => !NOT_SCANNED.has(k)).sort();
32
+ var BLOCK_MODULES = {
33
+ if: ["ifCommand"],
34
+ repeat: ["repeat", "loopControl"],
35
+ for: ["repeat", "loopControl"],
36
+ while: ["repeat", "loopControl"],
37
+ fetch: ["fetchCommand"]
38
+ };
39
+ var ALWAYS = ["on", "conversions"];
40
+ function resolveModules(usage) {
41
+ const wanted = new Set(ALWAYS);
42
+ const unknown = [];
43
+ for (const name of usage.commands) {
44
+ const mod = COMMAND_KEYWORDS.get(name) ?? FEATURE_KEYWORDS.get(name);
45
+ if (mod) wanted.add(mod);
46
+ else if (!(name in BLOCK_MODULES)) unknown.push(name);
47
+ }
48
+ for (const block of usage.blocks) {
49
+ for (const mod of BLOCK_MODULES[block] ?? []) wanted.add(mod);
50
+ }
51
+ if (usage.positional) wanted.add("expressionsExtra");
52
+ if (usage.reactivity) {
53
+ wanted.add("reactivity");
54
+ wanted.add("liveTemplates");
55
+ }
56
+ if (usage.construct) wanted.add("construct");
57
+ if (usage.cookies) wanted.add("cookies");
58
+ if (wanted.has("toggle")) wanted.add("toggleElement");
59
+ const modules = ENGINE_MODULES.filter((m) => wanted.has(m.name)).map((m) => m.name);
60
+ for (const name of wanted) {
61
+ if (!byName.has(name)) throw new Error(`[hyperfixi] no engine module named ${name}`);
62
+ }
63
+ return { modules, unknown: [...new Set(unknown)].sort() };
64
+ }
3
65
 
4
66
  // src/language-keywords.ts
5
67
  var SUPPORTED_LANGUAGES = [
@@ -1036,17 +1098,7 @@ var SSE_NS_PATTERN = /\bsse-[\w\-\p{L}]+\s*=\s*["']/u;
1036
1098
  var WS_NS_PATTERN = /\bws-[\w\-\p{L}]+\s*=\s*["']/u;
1037
1099
 
1038
1100
  // src/scanner.ts
1039
- var SCANNABLE_COMMANDS = [
1040
- .../* @__PURE__ */ new Set([...AVAILABLE_COMMANDS, ...FULL_RUNTIME_ONLY_COMMANDS])
1041
- ].filter(
1042
- (cmd) => /^[A-Za-z]+$/.test(cmd) && // `unless` is handled by the block detection below (it compiles to the
1043
- // lite-capable `if` block — flagging it as a full-runtime-only COMMAND
1044
- // would needlessly bump every unless-user to a full-runtime bundle).
1045
- // `bind` is handled by the dedicated reactivity detection below, which
1046
- // routes to the hx-v4 bundle (the tier that actually ships reactivity).
1047
- cmd !== "unless" && cmd !== "bind"
1048
- );
1049
- var COMMAND_PATTERN_SOURCE = `\\b(${SCANNABLE_COMMANDS.join("|")})\\b`;
1101
+ var COMMAND_PATTERN_SOURCE = `\\b(${SCANNABLE_KEYWORDS.join("|")})\\b`;
1050
1102
  var HTMX_REQUEST_PATTERN = /\b(hx-get|hx-post|hx-put|hx-patch|hx-delete)\s*=\s*["']([^"']+)["']/gi;
1051
1103
  var FIXI_ACTION_PATTERN = /\bfx-action\s*=\s*["']([^"']+)["']/gi;
1052
1104
  var FIXI_METHOD_PATTERN = /\bfx-method\s*=\s*["'](GET|POST|PUT|PATCH|DELETE)["']/gi;
@@ -1098,7 +1150,9 @@ var Scanner = class {
1098
1150
  positional: false,
1099
1151
  detectedLanguages: /* @__PURE__ */ new Set(),
1100
1152
  needsReactivity: false,
1101
- needsBindToProperty: false
1153
+ needsBindToProperty: false,
1154
+ needsConstruct: false,
1155
+ needsCookies: false
1102
1156
  };
1103
1157
  const attrPatterns = [
1104
1158
  /_\s*=\s*"([^"]+)"/g,
@@ -1166,9 +1220,11 @@ var Scanner = class {
1166
1220
  if (/\bfor\s+(each|every)\b/i.test(script)) usage.blocks.add("for");
1167
1221
  if (/\bwhile\b/.test(script)) usage.blocks.add("while");
1168
1222
  if (/\bfetch\b/.test(script)) usage.blocks.add("fetch");
1169
- if (/\b(first|last|next|previous|closest|parent)\b/.test(script)) {
1223
+ if (/\b(first|last|next|previous|closest|parent|random)\b/.test(script) || /\b(where|sorted\s+by|mapped\s+to|split\s+by|joined\s+by|some|beep!)/.test(script)) {
1170
1224
  usage.positional = true;
1171
1225
  }
1226
+ if (/\bnew\s+[A-Z$_][\w$.]*\s*\(/.test(script)) usage.needsConstruct = true;
1227
+ if (/\bcookies\b/.test(script)) usage.needsCookies = true;
1172
1228
  const languages = detectLanguages(script);
1173
1229
  for (const lang of languages) {
1174
1230
  usage.detectedLanguages.add(lang);
@@ -1363,7 +1419,8 @@ var Aggregator = class {
1363
1419
  const positionalEqual = existing.positional === usage.positional;
1364
1420
  const languagesEqual = this.setsEqual(existing.detectedLanguages, usage.detectedLanguages);
1365
1421
  const htmxEqual = this.htmxUsageEqual(existing.htmx, usage.htmx);
1366
- if (commandsEqual && blocksEqual && positionalEqual && languagesEqual && htmxEqual) {
1422
+ const flagsEqual = !!existing.needsReactivity === !!usage.needsReactivity && !!existing.needsBindToProperty === !!usage.needsBindToProperty && !!existing.needsConstruct === !!usage.needsConstruct && !!existing.needsCookies === !!usage.needsCookies;
1423
+ if (commandsEqual && blocksEqual && positionalEqual && languagesEqual && htmxEqual && flagsEqual) {
1367
1424
  return false;
1368
1425
  }
1369
1426
  }
@@ -1411,6 +1468,8 @@ var Aggregator = class {
1411
1468
  };
1412
1469
  let needsReactivity = false;
1413
1470
  let needsBindToProperty = false;
1471
+ let needsConstruct = false;
1472
+ let needsCookies = false;
1414
1473
  for (const usage of this.fileUsage.values()) {
1415
1474
  for (const cmd of usage.commands) commands.add(cmd);
1416
1475
  for (const block of usage.blocks) blocks.add(block);
@@ -1418,6 +1477,8 @@ var Aggregator = class {
1418
1477
  if (usage.positional) positional = true;
1419
1478
  if (usage.needsReactivity) needsReactivity = true;
1420
1479
  if (usage.needsBindToProperty) needsBindToProperty = true;
1480
+ if (usage.needsConstruct) needsConstruct = true;
1481
+ if (usage.needsCookies) needsCookies = true;
1421
1482
  if (usage.htmx) {
1422
1483
  if (usage.htmx.hasHtmxAttributes) htmx.hasHtmxAttributes = true;
1423
1484
  if (usage.htmx.hasFixiAttributes) htmx.hasFixiAttributes = true;
@@ -1446,6 +1507,8 @@ var Aggregator = class {
1446
1507
  htmx,
1447
1508
  needsReactivity,
1448
1509
  needsBindToProperty,
1510
+ needsConstruct,
1511
+ needsCookies,
1449
1512
  fileUsage: new Map(this.fileUsage)
1450
1513
  };
1451
1514
  return this.cachedUsage;
@@ -1500,9 +1563,9 @@ var Aggregator = class {
1500
1563
  htmxUsageEqual(a, b) {
1501
1564
  if (!a && !b) return true;
1502
1565
  if (!a || !b) return false;
1503
- 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,
1504
- // so a change must count as "usage changed" or HMR serves a stale
1505
- // bundle. They were omitted when the v4 surface landed (#623).
1566
+ 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 / reactivity flags: these decide which engine modules the bundle
1567
+ // registers, so a change must count as "usage changed" or HMR serves a
1568
+ // stale bundle. They were omitted when the v4 surface landed (#623).
1506
1569
  !!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);
1507
1570
  }
1508
1571
  arraysEqual(a, b) {
@@ -1512,236 +1575,7 @@ var Aggregator = class {
1512
1575
  }
1513
1576
  };
1514
1577
 
1515
- // src/generator.ts
1516
- import {
1517
- COMMAND_IMPLEMENTATIONS as COMMAND_IMPL_TS,
1518
- BLOCK_IMPLEMENTATIONS as BLOCK_IMPL_TS,
1519
- STYLE_COMMANDS,
1520
- ELEMENT_ARRAY_COMMANDS,
1521
- MORPH_COMMANDS,
1522
- LITE_PARSER_TEMPLATE,
1523
- HYBRID_PARSER_TEMPLATE,
1524
- canUseLiteParser,
1525
- FULL_RUNTIME_ONLY_COMMANDS as FULL_RUNTIME_ONLY_COMMANDS2,
1526
- isAvailableCommand
1527
- } from "@hyperfixi/core/bundle-generator";
1528
-
1529
1578
  // src/semantic-integration.ts
1530
- var MULTILINGUAL_COMMAND_ALIASES = {
1531
- ja: {
1532
- \u30C8\u30B0\u30EB: "toggle",
1533
- \u5207\u308A\u66FF\u3048: "toggle",
1534
- \u8FFD\u52A0: "add",
1535
- \u524A\u9664: "remove",
1536
- \u8868\u793A: "show",
1537
- \u96A0\u3059: "hide",
1538
- \u975E\u8868\u793A: "hide",
1539
- \u8A2D\u5B9A: "set",
1540
- \u30BB\u30C3\u30C8: "set",
1541
- \u5897\u52A0: "increment",
1542
- \u6E1B\u5C11: "decrement",
1543
- \u30ED\u30B0: "log",
1544
- \u51FA\u529B: "log"
1545
- },
1546
- ko: {
1547
- \uD1A0\uAE00: "toggle",
1548
- \uC804\uD658: "toggle",
1549
- \uCD94\uAC00: "add",
1550
- \uC81C\uAC70: "remove",
1551
- \uC0AD\uC81C: "remove",
1552
- \uD45C\uC2DC: "show",
1553
- \uC228\uAE30\uB2E4: "hide",
1554
- \uC124\uC815: "set",
1555
- \uC99D\uAC00: "increment",
1556
- \uAC10\uC18C: "decrement",
1557
- \uB85C\uADF8: "log"
1558
- },
1559
- zh: {
1560
- \u5207\u6362: "toggle",
1561
- \u6DFB\u52A0: "add",
1562
- \u79FB\u9664: "remove",
1563
- \u5220\u9664: "remove",
1564
- \u663E\u793A: "show",
1565
- \u9690\u85CF: "hide",
1566
- \u8BBE\u7F6E: "set",
1567
- \u8BBE\u5B9A: "set",
1568
- \u589E\u52A0: "increment",
1569
- \u51CF\u5C11: "decrement",
1570
- \u65E5\u5FD7: "log",
1571
- \u8BB0\u5F55: "log"
1572
- },
1573
- ar: {
1574
- \u0628\u062F\u0651\u0644: "toggle",
1575
- \u0628\u062F\u0644: "toggle",
1576
- \u0623\u0636\u0641: "add",
1577
- \u0627\u0636\u0641: "add",
1578
- \u0623\u0632\u0644: "remove",
1579
- \u0627\u0632\u0644: "remove",
1580
- \u0627\u062D\u0630\u0641: "remove",
1581
- \u0623\u0638\u0647\u0631: "show",
1582
- \u0627\u0638\u0647\u0631: "show",
1583
- \u0623\u062E\u0641\u0650: "hide",
1584
- \u0627\u062E\u0641: "hide",
1585
- \u0636\u0639: "set",
1586
- \u0627\u0636\u0639: "set",
1587
- \u0632\u0650\u062F: "increment",
1588
- \u0623\u0646\u0642\u0635: "decrement"
1589
- },
1590
- es: {
1591
- alternar: "toggle",
1592
- a\u00F1adir: "add",
1593
- agregar: "add",
1594
- quitar: "remove",
1595
- eliminar: "remove",
1596
- mostrar: "show",
1597
- ocultar: "hide",
1598
- esconder: "hide",
1599
- establecer: "set",
1600
- fijar: "set",
1601
- incrementar: "increment",
1602
- decrementar: "decrement"
1603
- },
1604
- pt: {
1605
- alternar: "toggle",
1606
- adicionar: "add",
1607
- remover: "remove",
1608
- mostrar: "show",
1609
- esconder: "hide",
1610
- ocultar: "hide",
1611
- definir: "set",
1612
- incrementar: "increment",
1613
- decrementar: "decrement"
1614
- },
1615
- fr: {
1616
- basculer: "toggle",
1617
- ajouter: "add",
1618
- supprimer: "remove",
1619
- retirer: "remove",
1620
- afficher: "show",
1621
- montrer: "show",
1622
- cacher: "hide",
1623
- masquer: "hide",
1624
- d\u00E9finir: "set",
1625
- incr\u00E9menter: "increment",
1626
- d\u00E9cr\u00E9menter: "decrement"
1627
- },
1628
- de: {
1629
- umschalten: "toggle",
1630
- hinzuf\u00FCgen: "add",
1631
- entfernen: "remove",
1632
- l\u00F6schen: "remove",
1633
- anzeigen: "show",
1634
- zeigen: "show",
1635
- verbergen: "hide",
1636
- verstecken: "hide",
1637
- setzen: "set",
1638
- festlegen: "set",
1639
- erh\u00F6hen: "increment",
1640
- verringern: "decrement"
1641
- },
1642
- tr: {
1643
- de\u011Fi\u015Ftir: "toggle",
1644
- de\u011Fistir: "toggle",
1645
- ekle: "add",
1646
- kald\u0131r: "remove",
1647
- kaldir: "remove",
1648
- sil: "remove",
1649
- g\u00F6ster: "show",
1650
- gizle: "hide",
1651
- sakla: "hide",
1652
- ayarla: "set",
1653
- belirle: "set",
1654
- artt\u0131r: "increment",
1655
- azalt: "decrement"
1656
- },
1657
- id: {
1658
- alih: "toggle",
1659
- beralih: "toggle",
1660
- tambah: "add",
1661
- hapus: "remove",
1662
- buang: "remove",
1663
- tampilkan: "show",
1664
- sembunyikan: "hide",
1665
- atur: "set",
1666
- tetapkan: "set",
1667
- tambahkan: "increment",
1668
- kurangi: "decrement"
1669
- },
1670
- sw: {
1671
- badilisha: "toggle",
1672
- ongeza: "add",
1673
- ondoa: "remove",
1674
- futa: "remove",
1675
- onyesha: "show",
1676
- ficha: "hide",
1677
- weka: "set",
1678
- sanidi: "set",
1679
- ongezea: "increment",
1680
- punguza: "decrement"
1681
- },
1682
- qu: {
1683
- tikray: "toggle",
1684
- yapay: "add",
1685
- qichuy: "remove",
1686
- pichay: "remove",
1687
- rikuchiy: "show",
1688
- pakay: "hide",
1689
- churay: "set",
1690
- pisiyachiy: "decrement"
1691
- }
1692
- };
1693
- function getMultilingualCommandAliases(languages) {
1694
- const aliases = {};
1695
- for (const lang of languages) {
1696
- const langAliases = MULTILINGUAL_COMMAND_ALIASES[lang];
1697
- if (langAliases) {
1698
- Object.assign(aliases, langAliases);
1699
- }
1700
- }
1701
- return aliases;
1702
- }
1703
- function generateMultilingualAliases(languages) {
1704
- const aliases = {};
1705
- for (const lang of languages) {
1706
- const langAliases = MULTILINGUAL_COMMAND_ALIASES[lang];
1707
- if (langAliases) {
1708
- Object.assign(aliases, langAliases);
1709
- }
1710
- }
1711
- if (Object.keys(aliases).length === 0) {
1712
- return `
1713
- // No multilingual aliases needed - just pass through
1714
- function preprocessMultilingual(code) { return code; }
1715
- `;
1716
- }
1717
- const aliasEntries = Object.entries(aliases).map(([key, val]) => ` '${key}': '${val}'`).join(",\n");
1718
- const escapedKeys = Object.keys(aliases).map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
1719
- return `
1720
- // Multilingual command aliases for HybridParser fallback
1721
- const MULTILINGUAL_ALIASES = {
1722
- ${aliasEntries}
1723
- };
1724
-
1725
- // Register multilingual aliases with the parser
1726
- addCommandAliases(MULTILINGUAL_ALIASES);
1727
-
1728
- // Preprocessing regex to translate multilingual keywords to English
1729
- // The HybridParser tokenizer only accepts ASCII identifiers, so we need
1730
- // to translate non-ASCII keywords before parsing.
1731
- const MULTILINGUAL_KEYWORD_REGEX = new RegExp('(^|\\\\s)(${escapedKeys})(?=\\\\s|\\\\.|$)', 'g');
1732
-
1733
- /**
1734
- * Preprocess multilingual code to replace non-ASCII keywords with English equivalents.
1735
- * This is needed because HybridParser's tokenizer only accepts ASCII identifiers.
1736
- */
1737
- function preprocessMultilingual(code) {
1738
- return code.replace(MULTILINGUAL_KEYWORD_REGEX, (match, prefix, keyword) => {
1739
- const english = MULTILINGUAL_ALIASES[keyword];
1740
- return english ? prefix + english : match;
1741
- });
1742
- }
1743
- `;
1744
- }
1745
1579
  function resolveSemanticConfig(options, detectedLanguages) {
1746
1580
  const grammarEnabled = options.grammar ?? false;
1747
1581
  let semanticEnabled = grammarEnabled;
@@ -1957,15 +1791,11 @@ function generateSemanticIntegrationCode(config) {
1957
1791
  const languages = [...config.languages].join("', '");
1958
1792
  let code = `
1959
1793
  // =============================================================================
1960
- // SEMANTIC PARSER INTEGRATION
1794
+ // MULTILINGUAL (semantic) \u2014 translated as read; the attribute stays as written
1961
1795
  // =============================================================================
1962
1796
 
1963
1797
  // Core infrastructure (no language data bundled)
1964
- import {
1965
- parseWithConfidence,
1966
- buildAST,
1967
- isLanguageSupported,
1968
- } from '@lokascript/semantic/core';
1798
+ import { parseWithConfidence, render, isLanguageRegistered } from '@lokascript/semantic/core';
1969
1799
 
1970
1800
  // Register required languages (self-registering side-effect imports)
1971
1801
  `;
@@ -1979,58 +1809,36 @@ import {
1979
1809
  import { translate } from '@lokascript/semantic/core';
1980
1810
  `;
1981
1811
  }
1982
- const aliasesCode = generateMultilingualAliases(config.languages);
1983
1812
  code += `
1984
1813
  const SUPPORTED_SEMANTIC_LANGUAGES = ['${languages}', 'en'];
1985
1814
  const SEMANTIC_CONFIDENCE_THRESHOLD = 0.7;
1986
1815
 
1987
- ${aliasesCode}
1816
+ /** The element's language: data-lang, data-hyperscript-lang, the lang cascade, the document's. */
1817
+ function resolveLanguage(elt) {
1818
+ const code =
1819
+ elt.getAttribute('data-lang') ||
1820
+ elt.closest?.('[data-hyperscript-lang]')?.getAttribute('data-hyperscript-lang') ||
1821
+ elt.closest?.('[lang]')?.getAttribute('lang') ||
1822
+ (typeof document !== 'undefined' ? document.documentElement?.lang : null);
1823
+ return code ? code.toLowerCase().split(/[-_]/)[0] : null;
1824
+ }
1988
1825
 
1989
1826
  /**
1990
- * Parse hyperscript using semantic parser with auto-language detection.
1991
- * Tries each supported language until one returns a high-confidence result.
1992
- * Falls back to HybridParser if none match.
1993
- *
1994
- * @param code - The hyperscript code to parse
1995
- * @param lang - Optional language hint. If provided, tries this language first.
1827
+ * The engine's source transform: the English for a non-English script, or null
1828
+ * to leave the script as written (English, an unregistered language, a parse
1829
+ * below the confidence threshold, or a render that changed nothing).
1996
1830
  */
1997
- function parseWithSemantic(code, lang = null) {
1998
- // Determine languages to try: specified lang first, then others
1999
- let languagesToTry = [...SUPPORTED_SEMANTIC_LANGUAGES];
2000
- if (lang && !languagesToTry.includes(lang)) {
2001
- languagesToTry.unshift(lang);
2002
- } else if (lang) {
2003
- // Move specified lang to front
2004
- languagesToTry = [lang, ...languagesToTry.filter(l => l !== lang)];
2005
- }
2006
-
2007
- // Try each language until one succeeds
2008
- for (const tryLang of languagesToTry) {
2009
- if (!isLanguageSupported(tryLang)) continue;
2010
- try {
2011
- const result = parseWithConfidence(code, tryLang);
2012
- if (result && result.confidence >= SEMANTIC_CONFIDENCE_THRESHOLD) {
2013
- // buildAST returns {ast, warnings} - extract the ast
2014
- const buildResult = buildAST(result.node);
2015
- const ast = buildResult.ast;
2016
-
2017
- // Semantic parser doesn't fully handle event handlers - fallback to HybridParser
2018
- // Event handlers like "on click toggle .active" produce incomplete AST
2019
- if (ast && ast.type === 'command' && ast.name === 'on') {
2020
- break; // Fallback to HybridParser
2021
- }
2022
-
2023
- return ast;
2024
- }
2025
- } catch (e) {
2026
- // Continue trying other languages
2027
- }
1831
+ function translateSource(src, elt) {
1832
+ const lang = elt ? resolveLanguage(elt) : null;
1833
+ if (!lang || lang === 'en' || !isLanguageRegistered(lang)) return null;
1834
+ try {
1835
+ const result = parseWithConfidence(src, lang);
1836
+ if (!result || !result.node || result.confidence < SEMANTIC_CONFIDENCE_THRESHOLD) return null;
1837
+ const english = render(result.node, 'en');
1838
+ return english && english !== src ? english : null;
1839
+ } catch {
1840
+ return null;
2028
1841
  }
2029
-
2030
- // Fallback to HybridParser for event handlers and unrecognized patterns
2031
- // Preprocess to translate non-ASCII keywords to English (tokenizer only accepts ASCII)
2032
- const preprocessedCode = preprocessMultilingual(code);
2033
- return new HybridParser(preprocessedCode).parse();
2034
1842
  }
2035
1843
  `;
2036
1844
  if (config.grammarEnabled) {
@@ -2040,9 +1848,7 @@ function parseWithSemantic(code, lang = null) {
2040
1848
  *
2041
1849
  * Returns the input UNCHANGED when it cannot be parsed in \`fromLang\`, or when
2042
1850
  * \`toLang\` is not one of the languages this bundle registered. The semantic
2043
- * translator throws in both cases; the i18n one this replaced never did \u2014 it
2044
- * degraded to word substitution, which turned unparseable input into plausible
2045
- * nonsense. Returning the source is the honest degradation.
1851
+ * translator throws in both cases; returning the source is the honest degradation.
2046
1852
  */
2047
1853
  function translateHyperscript(code, fromLang, toLang) {
2048
1854
  try {
@@ -2058,7 +1864,7 @@ function translateHyperscript(code, fromLang, toLang) {
2058
1864
  function getSemanticExports(config) {
2059
1865
  const exports = [];
2060
1866
  if (config.enabled) {
2061
- exports.push("parseWithSemantic");
1867
+ exports.push("translateSource");
2062
1868
  exports.push("SUPPORTED_SEMANTIC_LANGUAGES");
2063
1869
  }
2064
1870
  if (config.grammarEnabled) {
@@ -2068,421 +1874,77 @@ function getSemanticExports(config) {
2068
1874
  }
2069
1875
 
2070
1876
  // src/generator.ts
2071
- function stripTypes(code) {
2072
- return code.replace(/\s+as\s+\w+<[^>]*(?:<[^>]*>[^>]*)*>/g, "").replace(/\s+as\s+\w+(?:\[\])?/g, "").replace(/:\s*\w+<[^>]*(?:<[^>]*>[^>]*)*>(?:\[\])?(?=\s*[=;,)\]])/g, "").replace(
2073
- /:\s*(?:string|number|boolean|any|unknown|void|never|null|undefined|object|symbol|bigint)(?=\s*[=;,)\]])/g,
2074
- ""
2075
- ).replace(/:\s*[A-Z]\w*(?:\[\])?(?=\s*[=;,)\]])/g, "").replace(/\((\w+):\s*(?:\w+|unknown|any)\)/g, "($1)").replace(/Promise<[^>]+>/g, "Promise").replace(/Array<[^>]+>/g, "Array").replace(/\):\s*\w+(?:<[^>]*>)?(?:\[\])?\s*(?=[{=])/g, ") ").replace(/\):\s*\w+(?:<[^>]*>)?\s*=>/g, ") =>").replace(
2076
- /:\s*(?:TransitionEvent|MouseEvent|KeyboardEvent|Event|Element|HTMLElement|Node)(?=\s*[=;,)\]])/g,
2077
- ""
2078
- ).replace(/(\w)!(?=[,;)\].\s])/g, "$1");
2079
- }
2080
- var COMMAND_IMPLEMENTATIONS = {};
2081
- var BLOCK_IMPLEMENTATIONS = {};
2082
- for (const [name, code] of Object.entries(COMMAND_IMPL_TS)) {
2083
- COMMAND_IMPLEMENTATIONS[name] = stripTypes(code);
2084
- }
2085
- for (const [name, code] of Object.entries(BLOCK_IMPL_TS)) {
2086
- BLOCK_IMPLEMENTATIONS[name] = stripTypes(code);
2087
- }
2088
- function generateBundleCode(config) {
1877
+ var ENGINE_API_NAMES = ["api", "boot", "register"];
1878
+ function generateBundleCode(spec) {
2089
1879
  const {
2090
1880
  name,
1881
+ modules,
2091
1882
  commands,
2092
- blocks = [],
2093
- htmxIntegration = false,
2094
- globalName = "hyperfixi",
2095
- positionalExpressions = false,
2096
- autoInit = true,
2097
- esModule = true,
2098
- parserType = "hybrid"
2099
- } = config;
2100
- const needsStyleHelpers = commands.some((cmd) => STYLE_COMMANDS.includes(cmd));
2101
- const needsElementArrayHelper = commands.some((cmd) => ELEMENT_ARRAY_COMMANDS.includes(cmd));
2102
- const needsMorphlex = commands.some((cmd) => MORPH_COMMANDS.includes(cmd));
2103
- const hasBlocks = blocks.length > 0;
2104
- const hasReturn = commands.includes("return");
2105
- const commandCases = commands.filter((cmd) => COMMAND_IMPLEMENTATIONS[cmd]).map((cmd) => COMMAND_IMPLEMENTATIONS[cmd]).join("\n");
2106
- const blockCases = blocks.filter((block) => BLOCK_IMPLEMENTATIONS[block]).map((block) => BLOCK_IMPLEMENTATIONS[block]).join("\n");
2107
- const parserTemplate = parserType === "lite" ? LITE_PARSER_TEMPLATE : HYBRID_PARSER_TEMPLATE;
2108
- const morphlexImport = needsMorphlex ? `import { morph as morphlexMorph, morphInner as morphlexMorphInner } from 'morphlex';
2109
-
1883
+ blocks,
1884
+ positional,
1885
+ reactivity,
1886
+ htmxIntegration,
1887
+ globalName,
1888
+ autoInit,
1889
+ esModule,
1890
+ semantic
1891
+ } = spec;
1892
+ const semanticCode = generateSemanticIntegrationCode(semantic);
1893
+ const semanticExports = getSemanticExports(semantic);
1894
+ const languages = semantic.enabled ? [...semantic.languages].sort() : [];
1895
+ const headerLines = [
1896
+ `Modules: ${modules.join(", ")}`,
1897
+ commands.length > 0 ? `Commands: ${commands.join(", ")}` : "",
1898
+ blocks.length > 0 ? `Blocks: ${blocks.join(", ")}` : "",
1899
+ positional ? "Expressions: positional / collection (expressionsExtra)" : "",
1900
+ reactivity ? "Reactivity: live / when / bind" : "",
1901
+ semantic.enabled ? `Multilingual: ${languages.join(", ") || "en"} (translated as read, through the engine's source transform)` : "",
1902
+ htmxIntegration ? "htmx: content swapped in is processed (htmx:load / htmx:afterSettle)" : ""
1903
+ ].filter(Boolean);
1904
+ const bundleInfo = JSON.stringify({
1905
+ generator: "@hyperfixi/vite-plugin",
1906
+ modules,
1907
+ commands,
1908
+ blocks,
1909
+ languages
1910
+ });
1911
+ const apiProps = [`bundle: ${bundleInfo}`, ...semanticExports].map((p) => ` ${p},`).join("\n");
1912
+ const installGlobal = globalName === "_hyperscript" ? "" : ` window.${globalName} = api;
1913
+ `;
1914
+ const htmxHooks = htmxIntegration ? `
1915
+ // htmx (any major) announces swapped-in content on these; hand it to the engine.
1916
+ for (const type of ['htmx:load', 'htmx:afterSettle']) {
1917
+ document.addEventListener(type, event => {
1918
+ const detail = event.detail;
1919
+ const target = (detail && (detail.elt || detail.target)) || event.target;
1920
+ if (target instanceof Element) api.processNode(target);
1921
+ });
1922
+ }
2110
1923
  ` : "";
2111
1924
  return `/**
2112
- * LokaScript ${name} Bundle (Auto-Generated)
1925
+ * hyperfixi ${name} bundle (auto-generated)
2113
1926
  *
2114
1927
  * Generated by: @hyperfixi/vite-plugin
2115
- * Parser: ${parserType}
2116
- * Commands: ${commands.join(", ")}${blocks.length > 0 ? `
2117
- * Blocks: ${blocks.join(", ")}` : ""}${positionalExpressions ? "\n * Positional expressions: enabled" : ""}${needsMorphlex ? "\n * Morphing: morphlex (~2.5KB)" : ""}
1928
+ * Engine: @hyperfixi/engine \u2014 the grammar is exactly the modules registered below.
1929
+ ${headerLines.map((l) => ` * ${l}`).join("\n")}
2118
1930
  *
2119
1931
  * DO NOT EDIT - This file is automatically regenerated.
2120
1932
  */
2121
1933
 
2122
- ${morphlexImport}// Embedded ${parserType} parser (no external dependencies)
2123
- ${parserTemplate}
2124
-
2125
- // Runtime state
2126
- const globalVars = new Map();
2127
- ${hasBlocks ? "const MAX_LOOP_ITERATIONS = 1000;" : ""}
2128
-
2129
- async function evaluate(node, ctx) {
2130
- switch (node.type) {
2131
- case 'literal': return node.value;
2132
-
2133
- case 'identifier':
2134
- if (node.value === 'me' || node.value === 'my') return ctx.me;
2135
- if (node.value === 'it') return ctx.it;
2136
- if (node.value === 'you') return ctx.you;
2137
- if (node.value === 'event') return ctx.event;
2138
- if (node.value === 'body') return document.body;
2139
- if (node.value === 'document') return document;
2140
- if (node.value === 'window') return window;
2141
- if (ctx.locals.has(node.value)) return ctx.locals.get(node.value);
2142
- if (node.value in ctx.me) return ctx.me[node.value];
2143
- return node.value;
2144
-
2145
- case 'contextReference':
2146
- // Semantic AST format for context references (me, you, it, etc.)
2147
- switch (node.contextType || node.name) {
2148
- case 'me': return ctx.me;
2149
- case 'it': return ctx.it;
2150
- case 'you': return ctx.you;
2151
- case 'event': return ctx.event;
2152
- case 'body': return document.body;
2153
- case 'document': return document;
2154
- case 'window': return window;
2155
- default: return ctx.me;
2156
- }
2157
-
2158
- case 'variable':
2159
- if (node.scope === 'local') return ctx.locals.get(node.name.slice(1));
2160
- const gName = node.name.slice(1);
2161
- if (globalVars.has(gName)) return globalVars.get(gName);
2162
- return window[node.name];
2163
-
2164
- case 'selector':
2165
- const elements = document.querySelectorAll(node.value);
2166
- return elements.length === 1 ? elements[0] : Array.from(elements);
2167
-
2168
- case 'binary':
2169
- return evaluateBinary(node, ctx);
2170
-
2171
- case 'possessive':
2172
- case 'member':
2173
- const obj = await evaluate(node.object, ctx);
2174
- if (obj == null) return undefined;
2175
- const prop = node.computed ? await evaluate(node.property, ctx) : node.property;
2176
- if (prop === 'values' && obj instanceof Element) {
2177
- if (obj instanceof HTMLFormElement) return new FormData(obj);
2178
- const fd = new FormData();
2179
- obj.querySelectorAll('input, select, textarea').forEach(input => {
2180
- const name = input.getAttribute('name');
2181
- if (name && 'value' in input) fd.append(name, input.value);
2182
- });
2183
- return fd;
2184
- }
2185
- return obj[prop];
2186
-
2187
- case 'call': {
2188
- let callContext = null;
2189
- let callee;
2190
-
2191
- if (node.callee.type === 'member' || node.callee.type === 'possessive') {
2192
- callContext = await evaluate(node.callee.object, ctx);
2193
- const p = node.callee.computed
2194
- ? await evaluate(node.callee.property, ctx)
2195
- : node.callee.property;
2196
- callee = callContext?.[p];
2197
- } else {
2198
- callee = await evaluate(node.callee, ctx);
2199
- }
2200
-
2201
- const args = await Promise.all(node.args.map(a => evaluate(a, ctx)));
2202
- if (typeof callee === 'function') return callee.apply(callContext, args);
2203
- return undefined;
2204
- }
2205
- ${positionalExpressions ? `
2206
- case 'positional':
2207
- return evaluatePositional(node, ctx);
2208
- ` : ""}
2209
- default: return undefined;
2210
- }
2211
- }
2212
-
2213
- async function evaluateBinary(node, ctx) {
2214
- if (node.operator === 'has') {
2215
- const left = await evaluate(node.left, ctx);
2216
- if (left instanceof Element && node.right.type === 'selector' && node.right.value.startsWith('.')) {
2217
- return left.classList.contains(node.right.value.slice(1));
2218
- }
2219
- return false;
2220
- }
2221
-
2222
- const left = await evaluate(node.left, ctx);
2223
- const right = await evaluate(node.right, ctx);
2224
-
2225
- switch (node.operator) {
2226
- case '+': return left + right;
2227
- case '-': return left - right;
2228
- case '*': return left * right;
2229
- case '/': return left / right;
2230
- case '%': return left % right;
2231
- case '==': case 'is': return left == right;
2232
- case '!=': case 'is not': return left != right;
2233
- case '<': return left < right;
2234
- case '>': return left > right;
2235
- case '<=': return left <= right;
2236
- case '>=': return left >= right;
2237
- case 'and': case '&&': return left && right;
2238
- case 'or': case '||': return left || right;
2239
- case 'contains': case 'includes':
2240
- if (typeof left === 'string') return left.includes(right);
2241
- if (Array.isArray(left)) return left.includes(right);
2242
- if (left instanceof Element) return left.contains(right);
2243
- return false;
2244
- case 'matches':
2245
- if (left instanceof Element) return left.matches(right);
2246
- if (typeof left === 'string') return new RegExp(right).test(left);
2247
- return false;
2248
- default: return undefined;
2249
- }
2250
- }
2251
- ${positionalExpressions ? `
2252
- function evaluatePositional(node, ctx) {
2253
- const target = node.target;
2254
- let elements = [];
2255
-
2256
- let selector = null;
2257
- if (target.type === 'selector') {
2258
- selector = target.value;
2259
- } else if (target.type === 'identifier') {
2260
- selector = target.value;
2261
- } else if (target.type === 'htmlSelector') {
2262
- selector = target.value;
2263
- }
2264
- if (selector) {
2265
- elements = Array.from(document.querySelectorAll(selector));
2266
- }
2267
-
2268
- switch (node.position) {
2269
- case 'first': return elements[0] || null;
2270
- case 'last': return elements[elements.length - 1] || null;
2271
- case 'next': return ctx.me.nextElementSibling;
2272
- case 'previous': return ctx.me.previousElementSibling;
2273
- case 'closest': return target.value ? ctx.me.closest(target.value) : null;
2274
- case 'parent': return ctx.me.parentElement;
2275
- default: return elements[0] || null;
2276
- }
2277
- }
2278
- ` : ""}
2279
- ${needsStyleHelpers ? `
2280
- const isStyleProp = (prop) => prop?.startsWith('*');
2281
- const getStyleName = (prop) => prop.substring(1);
2282
- const setStyleProp = (el, prop, value) => {
2283
- if (!isStyleProp(prop)) return false;
2284
- el.style.setProperty(getStyleName(prop), String(value));
2285
- return true;
2286
- };
2287
- ` : ""}
2288
-
2289
- ${needsElementArrayHelper ? `
2290
- const toElementArray = (val) => {
2291
- if (Array.isArray(val)) return val.filter(e => e instanceof Element);
2292
- if (val instanceof Element) return [val];
2293
- if (typeof val === 'string') return Array.from(document.querySelectorAll(val));
2294
- return [];
2295
- };
2296
- ` : ""}
2297
-
2298
- async function executeCommand(cmd, ctx) {
2299
- const getTarget = async () => {
2300
- // Support both HybridParser (cmd.target) and semantic AST (cmd.modifiers.on) formats
2301
- const target = cmd.target || cmd.modifiers?.on;
2302
- if (!target) return [ctx.me];
2303
- const t = await evaluate(target, ctx);
2304
- if (Array.isArray(t)) return t;
2305
- if (t instanceof Element) return [t];
2306
- if (typeof t === 'string') return Array.from(document.querySelectorAll(t));
2307
- return [ctx.me];
2308
- };
2309
-
2310
- const getClassName = (node) => {
2311
- if (!node) return '';
2312
- if (node.type === 'selector') return node.value.slice(1);
2313
- if (node.type === 'string' || node.type === 'literal') {
2314
- const val = node.value;
2315
- return typeof val === 'string' && val.startsWith('.') ? val.slice(1) : String(val);
2316
- }
2317
- if (node.type === 'identifier') return node.value;
2318
- return '';
2319
- };
2320
-
2321
- switch (cmd.name) {
2322
- ${commandCases}
2323
-
2324
- default:
2325
- console.warn(\`${name} bundle: Unknown command '\${cmd.name}'\`);
2326
- return null;
2327
- }
2328
- }
2329
- ${hasBlocks ? `
2330
- async function executeBlock(block, ctx) {
2331
- switch (block.type) {
2332
- ${blockCases}
2333
-
2334
- default:
2335
- console.warn(\`${name} bundle: Unknown block '\${block.type}'\`);
2336
- return null;
2337
- }
2338
- }
2339
- ` : ""}
2340
- async function executeSequence(nodes, ctx) {
2341
- let result;
2342
- for (const node of nodes) {
2343
- if (node.type === 'command') {
2344
- result = await executeCommand(node, ctx);
2345
- }${hasBlocks ? ` else if (['if', 'repeat', 'for', 'while', 'fetch'].includes(node.type)) {
2346
- result = await executeBlock(node, ctx);
2347
- }` : ""}
2348
- }
2349
- return result;
2350
- }
2351
- ${hasBlocks ? `
2352
- async function executeSequenceWithBlocks(nodes, ctx) {
2353
- try {
2354
- return await executeSequence(nodes, ctx);
2355
- } catch (e) {
2356
- if (e?.type === 'return') throw e;
2357
- throw e;
2358
- }
2359
- }
2360
- ` : ""}
2361
- async function executeAST(ast, me, event) {
2362
- const ctx = { me, event, locals: new Map() };
2363
-
2364
- if (ast.type === 'sequence') {
2365
- ${hasReturn || hasBlocks ? "try { return await executeSequence(ast.commands, ctx); } catch (e) { if (e?.type === 'return') return e.value; throw e; }" : "return executeSequence(ast.commands, ctx);"}
2366
- }
2367
-
2368
- if (ast.type === 'event') {
2369
- const eventNode = ast;
2370
- const eventName = eventNode.event;
2371
-
2372
- if (eventName === 'init') {
2373
- ${hasReturn || hasBlocks ? "try { await executeSequence(eventNode.body, ctx); } catch (e) { if (e?.type !== 'return') throw e; }" : "await executeSequence(eventNode.body, ctx);"}
2374
- return;
2375
- }
2376
-
2377
- const target = eventNode.filter ? await evaluate(eventNode.filter, ctx) : me;
2378
- const targetEl = target instanceof Element ? target : me;
2379
- const mods = eventNode.modifiers;
2380
-
2381
- let handler = async (e) => {
2382
- if (mods.prevent) e.preventDefault();
2383
- if (mods.stop) e.stopPropagation();
2384
-
2385
- const handlerCtx = {
2386
- me, event: e,
2387
- you: e.target instanceof Element ? e.target : undefined,
2388
- locals: new Map(),
2389
- };
2390
- ${hasReturn || hasBlocks ? "try { await executeSequence(eventNode.body, handlerCtx); } catch (err) { if (err?.type !== 'return') throw err; }" : "await executeSequence(eventNode.body, handlerCtx);"}
2391
- };
2392
-
2393
- if (mods.debounce) {
2394
- let timeout;
2395
- const orig = handler;
2396
- handler = async (e) => {
2397
- clearTimeout(timeout);
2398
- timeout = setTimeout(() => orig(e), mods.debounce);
2399
- };
2400
- }
2401
-
2402
- if (mods.throttle) {
2403
- let lastCall = 0;
2404
- const orig = handler;
2405
- handler = async (e) => {
2406
- const now = Date.now();
2407
- if (now - lastCall >= mods.throttle) {
2408
- lastCall = now;
2409
- await orig(e);
2410
- }
2411
- };
2412
- }
2413
-
2414
- targetEl.addEventListener(eventName, handler, { once: !!mods.once });
2415
- return;
2416
- }
2417
-
2418
- // Handle single command (not wrapped in event or sequence)
2419
- if (ast.type === 'command') {
2420
- return executeCommand(ast, ctx);
2421
- }
2422
-
2423
- return null;
2424
- }
2425
-
2426
- function processElement(el) {
2427
- const code = el.getAttribute('_');
2428
- if (!code) return;
2429
-
2430
- try {
2431
- const ast = ${parserType === "lite" ? "parseLite(code)" : "new HybridParser(code).parse()"};
2432
- executeAST(ast, el);
2433
- } catch (err) {
2434
- console.error('LokaScript ${name} error:', err, 'Code:', code);
2435
- }
2436
- }
2437
-
2438
- function processElements(root = document) {
2439
- const elements = root.querySelectorAll('[_]');
2440
- elements.forEach(processElement);
2441
- }
2442
-
2443
- const api = {
2444
- version: '1.0.0-${name.toLowerCase().replace(/\s+/g, "-")}',
2445
-
2446
- parse(code) {
2447
- return ${parserType === "lite" ? "parseLite(code)" : "new HybridParser(code).parse()"};
2448
- },
2449
-
2450
- async execute(code, element) {
2451
- const me = element || document.body;
2452
- const ast = ${parserType === "lite" ? "parseLite(code)" : "new HybridParser(code).parse()"};
2453
- return executeAST(ast, me);
2454
- },
2455
-
2456
- run: async (code, element) => api.execute(code, element),
2457
- eval: async (code, element) => api.execute(code, element),
2458
-
2459
- init: processElements,
2460
- process: processElements,
2461
-
2462
- commands: ${JSON.stringify(commands)},
2463
- ${blocks.length > 0 ? `blocks: ${JSON.stringify(blocks)},` : ""}
2464
- parserName: '${parserType}',
2465
- };
1934
+ import { ${[...ENGINE_API_NAMES, ...modules].join(", ")} } from '@hyperfixi/engine';
1935
+ ${semanticCode}
1936
+ register(${modules.join(", ")});
1937
+ ${semantic.enabled ? "api.addSourceTransform(translateSource);\n" : ""}
1938
+ Object.assign(api, {
1939
+ ${apiProps}
1940
+ });
2466
1941
  ${autoInit ? `
2467
1942
  if (typeof window !== 'undefined') {
2468
- window.${globalName} = api;
2469
-
2470
- if (document.readyState === 'loading') {
2471
- document.addEventListener('DOMContentLoaded', () => processElements());
2472
- } else {
2473
- processElements();
2474
- }
2475
- ${htmxIntegration ? `
2476
- document.addEventListener('htmx:afterSettle', (e) => {
2477
- const target = e.detail?.target;
2478
- if (target) processElements(target);
2479
- });
2480
- ` : ""}
2481
- }
2482
- ` : ""}
2483
- ${esModule ? `
1943
+ ${installGlobal} boot();
1944
+ ${htmxHooks}}
1945
+ ` : ""}${esModule ? `
2484
1946
  export default api;
2485
- export { api, processElements };
1947
+ export { api };
2486
1948
  ` : ""}`;
2487
1949
  }
2488
1950
  var Generator = class {
@@ -2493,148 +1955,70 @@ var Generator = class {
2493
1955
  * Generate bundle code from aggregated usage
2494
1956
  */
2495
1957
  generate(usage, options) {
2496
- const commands = [...usage.commands, ...options.extraCommands ?? []];
2497
- const blocks = [...usage.blocks, ...options.extraBlocks ?? []];
1958
+ const commands = [.../* @__PURE__ */ new Set([...usage.commands, ...options.extraCommands ?? []])];
1959
+ const blocks = [.../* @__PURE__ */ new Set([...usage.blocks, ...options.extraBlocks ?? []])];
2498
1960
  const positional = usage.positional || options.positional || false;
2499
- const semanticConfig = resolveSemanticConfig(options, usage.detectedLanguages);
2500
- if (commands.length === 0 && blocks.length === 0 && !positional && !semanticConfig.enabled) {
1961
+ const reactivity = Boolean(
1962
+ usage.needsReactivity || usage.needsBindToProperty || usage.htmx?.needsReactivity
1963
+ );
1964
+ const semantic = resolveSemanticConfig(options, usage.detectedLanguages);
1965
+ if (commands.length === 0 && blocks.length === 0 && !positional && !reactivity && !semantic.enabled) {
2501
1966
  return this.generateEmptyBundle(options);
2502
1967
  }
2503
- const needsV4Bundle = usage.needsReactivity || usage.htmx?.needsHxLive || usage.htmx?.needsSSE || usage.htmx?.needsWS;
2504
- if (needsV4Bundle) {
2505
- if (this.debug) {
2506
- const triggers = [];
2507
- if (usage.htmx?.needsHxLive) triggers.push("hx-live");
2508
- if (usage.htmx?.needsSSE) triggers.push("sse-connect/sse-swap");
2509
- if (usage.htmx?.needsWS) triggers.push("ws-connect/ws-send");
2510
- if (usage.needsBindToProperty) triggers.push("bind-to-property");
2511
- else if (usage.needsReactivity) triggers.push("live/when/bind in _=");
1968
+ const moduleUsage = {
1969
+ commands,
1970
+ blocks,
1971
+ positional,
1972
+ reactivity,
1973
+ construct: usage.needsConstruct,
1974
+ cookies: usage.needsCookies
1975
+ };
1976
+ const { modules, unknown } = resolveModules(moduleUsage);
1977
+ if (this.debug) {
1978
+ if (unknown.length > 0) {
2512
1979
  console.log(
2513
- `[hyperfixi] Detected htmx v4 features: ${triggers.join(", ")}
2514
- Using the hx-v4 premade bundle (auto-installs reactivity + SSE/WS).`
1980
+ `[hyperfixi] Scanned names with no engine module (a form the engine dropped, or a false positive): ${unknown.join(", ")}`
2515
1981
  );
2516
- const onlyReactivity = usage.needsReactivity && !usage.htmx?.needsHxLive && !usage.htmx?.needsSSE && !usage.htmx?.needsWS && !usage.htmx?.hasHtmxAttributes;
2517
- if (onlyReactivity) {
2518
- console.log(
2519
- ` Note: this project uses reactivity only (no hx-*/sse-*/ws-* attributes). A reactivity-only fallback is parked pending size feedback.`
2520
- );
2521
- }
2522
1982
  }
2523
- return this.generateDevFallback("hx-v4");
2524
- }
2525
- const unsupportedCommands = this.getUnsupportedCommands(commands);
2526
- if (unsupportedCommands.length > 0) {
2527
- if (this.debug) {
1983
+ if (usage.htmx?.needsHxLive || usage.htmx?.needsSSE || usage.htmx?.needsWS) {
2528
1984
  console.log(
2529
- `[hyperfixi] Commands detected that require full runtime: ${unsupportedCommands.join(", ")}
2530
- Falling back to full browser bundle for complete functionality.`
1985
+ "[hyperfixi] hx-live / sse-* / ws-* attributes seen: these are htmx 4 features on the engine (load htmx 4 beside this bundle; a hyperscript-bodied hx-live is a `live \u2026 end` block)."
2531
1986
  );
2532
1987
  }
2533
- return this.generateDevFallback("full");
2534
- }
2535
- const parserType = canUseLiteParser(commands, blocks, positional) ? "lite" : "hybrid";
2536
- const config = {
2537
- name: options.bundleName ?? "ViteAutoGenerated",
2538
- commands,
2539
- blocks,
2540
- positionalExpressions: positional,
2541
- htmxIntegration: options.htmx ?? usage.htmx?.hasHtmxAttributes ?? false,
2542
- globalName: options.globalName ?? "hyperfixi",
2543
- parserType,
2544
- autoInit: true,
2545
- esModule: true
2546
- };
2547
- if (this.debug) {
2548
1988
  const bundleInfo = {
1989
+ modules,
2549
1990
  commands,
2550
1991
  blocks,
2551
1992
  positional,
2552
- htmx: config.htmxIntegration,
2553
- semantic: semanticConfig.enabled,
2554
- parserType
1993
+ reactivity
2555
1994
  };
2556
- if (semanticConfig.enabled && semanticConfig.bundleType) {
2557
- const sizeInfo = getSemanticBundleSize(semanticConfig.bundleType);
2558
- bundleInfo.semanticBundle = semanticConfig.bundleType;
1995
+ if (semantic.enabled && semantic.bundleType) {
1996
+ const sizeInfo = getSemanticBundleSize(semantic.bundleType);
1997
+ bundleInfo.semanticBundle = semantic.bundleType;
2559
1998
  bundleInfo.semanticSize = sizeInfo.gzip;
2560
- bundleInfo.languages = [...semanticConfig.languages];
2561
- bundleInfo.grammar = semanticConfig.grammarEnabled;
1999
+ bundleInfo.languages = [...semantic.languages];
2000
+ bundleInfo.grammar = semantic.grammarEnabled;
2562
2001
  }
2563
2002
  console.log("[hyperfixi] Generating bundle:", bundleInfo);
2564
- if (semanticConfig.bundleType === "all" || semanticConfig.bundleType === "priority") {
2003
+ if (semantic.bundleType === "all" || semantic.bundleType === "priority") {
2565
2004
  console.log(
2566
- `[hyperfixi] Note: Using '${semanticConfig.bundleType}' semantic bundle (${getSemanticBundleSize(semanticConfig.bundleType).gzip}). Consider using 'region' option to select a smaller regional bundle if all languages aren't needed.`
2005
+ `[hyperfixi] Note: Using '${semantic.bundleType}' semantic bundle (${getSemanticBundleSize(semantic.bundleType).gzip}). Consider using 'region' option to select a smaller regional bundle if all languages aren't needed.`
2567
2006
  );
2568
2007
  }
2569
2008
  }
2570
- let bundleCode = generateBundleCode(config);
2571
- if (semanticConfig.enabled) {
2572
- bundleCode = this.addSemanticIntegration(bundleCode, semanticConfig);
2573
- }
2574
- return bundleCode;
2575
- }
2576
- /**
2577
- * Add semantic integration code to the bundle
2578
- */
2579
- addSemanticIntegration(bundleCode, config) {
2580
- const semanticCode = generateSemanticIntegrationCode(config);
2581
- const semanticExports = getSemanticExports(config);
2582
- const parserImportEnd = bundleCode.indexOf("// Runtime state");
2583
- if (parserImportEnd === -1) {
2584
- bundleCode = semanticCode + "\n" + bundleCode;
2585
- } else {
2586
- bundleCode = bundleCode.slice(0, parserImportEnd) + semanticCode + "\n\n" + bundleCode.slice(parserImportEnd);
2587
- }
2588
- bundleCode = bundleCode.replace(
2589
- /function processElement\(el\) \{\s*const code = el\.getAttribute\('_'\);\s*if \(!code\) return;\s*try \{\s*const ast = (?:new HybridParser\(code\)\.parse\(\)|parseLite\(code\));/g,
2590
- `function processElement(el) {
2591
- const code = el.getAttribute('_');
2592
- if (!code) return;
2593
-
2594
- try {
2595
- const ast = parseWithSemantic(code);`
2596
- );
2597
- bundleCode = bundleCode.replace(
2598
- /parse\(code\) \{\s*return (?:new HybridParser\(code\)\.parse\(\)|parseLite\(code\));/g,
2599
- `parse(code, lang = null) {
2600
- return parseWithSemantic(code, lang);`
2601
- );
2602
- bundleCode = bundleCode.replace(
2603
- /async execute\(code, element\) \{\s*const me = element \|\| document\.body;\s*const ast = (?:new HybridParser\(code\)\.parse\(\)|parseLite\(code\));/g,
2604
- `async execute(code, element, lang = null) {
2605
- const me = element || document.body;
2606
- const ast = parseWithSemantic(code, lang);`
2607
- );
2608
- if (semanticExports.length > 0) {
2609
- const apiExportsMarkerRegex = /parserName: '(?:lite|hybrid)',/;
2610
- const additionalApiProps = semanticExports.map((exp) => ` ${exp},`).join("\n");
2611
- bundleCode = bundleCode.replace(
2612
- apiExportsMarkerRegex,
2613
- `parserName: 'semantic',
2614
- ${additionalApiProps}`
2615
- );
2616
- }
2617
- return bundleCode;
2618
- }
2619
- /**
2620
- * Get commands that require the full runtime and are not available in generated bundles
2621
- */
2622
- getUnsupportedCommands(commands) {
2623
- const unsupported = [];
2624
- for (const cmd of commands) {
2625
- if (FULL_RUNTIME_ONLY_COMMANDS2.includes(cmd)) {
2626
- unsupported.push(cmd);
2627
- } else if (!isAvailableCommand(cmd) && !COMMAND_IMPLEMENTATIONS[cmd]) {
2628
- unsupported.push(cmd);
2629
- }
2630
- }
2631
- return unsupported;
2632
- }
2633
- /**
2634
- * Check if any commands require full runtime and should trigger fallback
2635
- */
2636
- requiresFullRuntime(commands) {
2637
- return this.getUnsupportedCommands(commands).length > 0;
2009
+ return generateBundleCode({
2010
+ name: options.bundleName ?? "ViteAutoGenerated",
2011
+ modules,
2012
+ commands,
2013
+ blocks,
2014
+ positional,
2015
+ reactivity,
2016
+ htmxIntegration: options.htmx ?? usage.htmx?.hasHtmxAttributes ?? false,
2017
+ globalName: options.globalName ?? "hyperfixi",
2018
+ autoInit: true,
2019
+ esModule: true,
2020
+ semantic
2021
+ });
2638
2022
  }
2639
2023
  /**
2640
2024
  * Generate an empty bundle when no hyperscript is detected
@@ -2642,7 +2026,7 @@ ${additionalApiProps}`
2642
2026
  generateEmptyBundle(options) {
2643
2027
  const globalName = options.globalName ?? "hyperfixi";
2644
2028
  return `/**
2645
- * LokaScript Empty Bundle (Auto-Generated)
2029
+ * hyperfixi Empty Bundle (Auto-Generated)
2646
2030
  *
2647
2031
  * No hyperscript usage detected. This bundle provides a minimal API.
2648
2032
  * Add hyperscript attributes (_="...") to your HTML to enable features.
@@ -2652,28 +2036,24 @@ ${additionalApiProps}`
2652
2036
 
2653
2037
  const api = {
2654
2038
  version: '1.0.0-empty',
2655
- commands: [],
2656
- parserName: 'none',
2039
+ bundle: { generator: '@hyperfixi/vite-plugin', modules: [], commands: [], blocks: [], languages: [] },
2657
2040
 
2658
2041
  parse() {
2659
- console.warn('LokaScript: No parser loaded. Add hyperscript to your HTML to enable parsing.');
2660
- return { type: 'empty' };
2042
+ console.warn('hyperfixi: No engine loaded. Add hyperscript to your HTML to enable parsing.');
2043
+ return { errors: [] };
2661
2044
  },
2662
2045
 
2663
- async execute(code, element) {
2664
- console.warn('LokaScript: No commands loaded. Detected commands will be automatically included.');
2046
+ async evaluate(code, element) {
2047
+ console.warn('hyperfixi: No modules loaded. Detected commands will be automatically included.');
2665
2048
  return null;
2666
2049
  },
2667
2050
 
2668
- run: async (code, element) => api.execute(code, element),
2669
- eval: async (code, element) => api.execute(code, element),
2670
-
2671
- init() {},
2051
+ processNode() {},
2672
2052
  process() {},
2673
2053
  };
2674
2054
 
2675
2055
  if (typeof window !== 'undefined') {
2676
- (window).${globalName} = api;
2056
+ window.${globalName} = api;
2677
2057
  }
2678
2058
 
2679
2059
  export default api;
@@ -2681,947 +2061,40 @@ export { api };
2681
2061
  `;
2682
2062
  }
2683
2063
  /**
2684
- * Generate a development fallback bundle.
2064
+ * Generate a development fallback bundle: the engine with every module.
2685
2065
  *
2686
- * `hx-v4` re-exports the htmx v4 surface bundle: full runtime +
2687
- * htmx-compat + reactivity (hx-live, bind, when) + SSE + WS, all
2688
- * auto-installed. The vite plugin selects this when scanner detects
2689
- * any of `hx-live`, `sse-connect`/`sse-swap`, `ws-connect`/`ws-send`,
2690
- * or `bind ... to <expr>.<prop>` in `_=` bodies — the minimal
2691
- * generator can't satisfy those features (the slim runtime lacks the
2692
- * reactive notify chain and the SSE/WS modules). Larger artifact but
2693
- * correct end-to-end.
2066
+ * One shape serves every former fallback (`hybrid-complete`, `full`, `hx-v4`):
2067
+ * the engine's `everything` is 34 KB gzipped and carries the reactive features
2068
+ * (`live`, `when`, `bind`) the hx-v4 fallback existed for. The argument is kept
2069
+ * for the option's type; its value no longer changes the output.
2694
2070
  */
2695
- generateDevFallback(fallback) {
2696
- const bundle = fallback === "full" ? "@hyperfixi/core/browser" : fallback === "hx-v4" ? "@hyperfixi/core/browser/hybrid-hx-v4" : "@hyperfixi/core/browser/hybrid-complete";
2071
+ generateDevFallback(_fallback = "everything") {
2697
2072
  return `/**
2698
- * LokaScript Dev Fallback Bundle
2073
+ * hyperfixi Dev Fallback Bundle
2699
2074
  *
2700
- * Using pre-built ${fallback} bundle for faster development.
2701
- * Production builds will generate minimal bundles.
2075
+ * The engine with every module, for faster development rebuilds.
2076
+ * Production builds register only the modules the project uses.
2702
2077
  *
2703
2078
  * Generated by: @hyperfixi/vite-plugin
2704
2079
  */
2705
2080
 
2706
- export * from '${bundle}';
2707
- export { default } from '${bundle}';
2708
- `;
2709
- }
2710
- };
2711
-
2712
- // src/compiler.ts
2713
- import { HybridParser } from "@hyperfixi/core/parser/hybrid/parser-core";
2714
- var semanticParse = null;
2715
- var supportsLanguage = null;
2716
- var buildASTFn = null;
2717
- function setSemanticParser(parse, supports, buildAST) {
2718
- semanticParse = parse;
2719
- supportsLanguage = supports;
2720
- buildASTFn = buildAST;
2721
- }
2722
- function clearSemanticParser() {
2723
- semanticParse = null;
2724
- supportsLanguage = null;
2725
- buildASTFn = null;
2726
- }
2727
- function hasSemanticParser() {
2728
- return semanticParse !== null && supportsLanguage !== null && buildASTFn !== null;
2729
- }
2730
- var SEMANTIC_CONFIDENCE_THRESHOLD = 0.7;
2731
- var multilingualAliases = {};
2732
- var multilingualRegex = null;
2733
- function setMultilingualAliases(aliases) {
2734
- multilingualAliases = { ...aliases };
2735
- if (Object.keys(aliases).length > 0) {
2736
- const escapedKeys = Object.keys(aliases).map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2737
- multilingualRegex = new RegExp(`(^|\\s)(${escapedKeys})(?=\\s|\\.|$)`, "g");
2738
- } else {
2739
- multilingualRegex = null;
2740
- }
2741
- }
2742
- function preprocessMultilingual(code) {
2743
- if (!multilingualRegex) return code;
2744
- return code.replace(multilingualRegex, (match, prefix, keyword) => {
2745
- const english = multilingualAliases[keyword];
2746
- return english ? prefix + english : match;
2747
- });
2748
- }
2749
- function sanitizeClassName(name) {
2750
- if (!/^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name)) {
2751
- return null;
2752
- }
2753
- return name;
2754
- }
2755
- function sanitizeSelector(selector) {
2756
- return selector.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\0/g, "");
2757
- }
2758
- function hashScript(script) {
2759
- let hash = 5381;
2760
- for (let i = 0; i < script.length; i++) {
2761
- hash = (hash << 5) + hash ^ script.charCodeAt(i);
2762
- }
2763
- return Math.abs(hash).toString(36).slice(0, 4);
2764
- }
2765
- var usedIds = /* @__PURE__ */ new Set();
2766
- function resetCompiler() {
2767
- usedIds.clear();
2768
- }
2769
- function generateHandlerId(event, command, script) {
2770
- const hash = hashScript(script);
2771
- let id = `${event}_${command}_${hash}`;
2772
- let suffix = 0;
2773
- while (usedIds.has(id)) {
2774
- id = `${event}_${command}_${hash}${suffix++}`;
2775
- }
2776
- usedIds.add(id);
2777
- return id;
2778
- }
2779
- function compile(script, options = {}) {
2780
- const { language = "en", debug = false } = options;
2781
- try {
2782
- let ast;
2783
- if (language !== "en" && semanticParse && supportsLanguage && buildASTFn) {
2784
- if (supportsLanguage(language)) {
2785
- const result = semanticParse(script, language);
2786
- if (result.node && result.confidence >= SEMANTIC_CONFIDENCE_THRESHOLD) {
2787
- const { ast: builtAST, warnings } = buildASTFn(result.node);
2788
- if (debug && warnings.length > 0) {
2789
- console.log(`[hyperfixi] Semantic AST warnings for "${script}":`, warnings);
2790
- }
2791
- ast = builtAST;
2792
- if (debug) {
2793
- console.log(
2794
- `[hyperfixi] Compiled ${language}: "${script}" (confidence: ${result.confidence.toFixed(2)})`
2795
- );
2796
- }
2797
- } else {
2798
- if (debug) {
2799
- const reason = result.error || `low confidence (${result.confidence.toFixed(2)})`;
2800
- console.log(`[hyperfixi] Semantic parse failed for "${script}": ${reason}`);
2801
- }
2802
- return null;
2803
- }
2804
- } else {
2805
- if (debug) {
2806
- console.log(`[hyperfixi] Language "${language}" not supported by semantic parser`);
2807
- }
2808
- return null;
2809
- }
2810
- } else if (language !== "en") {
2811
- if (debug) {
2812
- console.log(
2813
- `[hyperfixi] Semantic parser not available for "${language}". Install @lokascript/semantic and call setSemanticParser().`
2814
- );
2815
- }
2816
- return null;
2817
- } else {
2818
- const preprocessedScript = preprocessMultilingual(script);
2819
- const parser = new HybridParser(preprocessedScript);
2820
- ast = parser.parse();
2821
- }
2822
- return compileAST(ast, script);
2823
- } catch (error) {
2824
- if (debug) {
2825
- console.warn(`[hyperfixi] Failed to compile: ${script}`, error);
2826
- }
2827
- return null;
2828
- }
2829
- }
2830
- function extractPrimaryCommand(body) {
2831
- if (body.length === 0) return "handler";
2832
- const first = body[0];
2833
- if (first.type === "command") {
2834
- return first.name || "cmd";
2835
- }
2836
- return first.type || "handler";
2837
- }
2838
- function compileAST(ast, original) {
2839
- if (ast.type !== "event") {
2840
- return null;
2841
- }
2842
- const eventNode = ast;
2843
- const event = eventNode.event;
2844
- const modifiers = eventNode.modifiers || {};
2845
- const body = eventNode.body || [];
2846
- const { code, needsEvaluator, needsLocals, needsGlobals } = compileBody(body);
2847
- if (code === null || code === "") {
2848
- return null;
2849
- }
2850
- const command = extractPrimaryCommand(body);
2851
- const id = generateHandlerId(event, command, original);
2852
- return {
2853
- id,
2854
- event,
2855
- modifiers: {
2856
- prevent: modifiers.prevent,
2857
- stop: modifiers.stop,
2858
- once: modifiers.once,
2859
- debounce: modifiers.debounce,
2860
- throttle: modifiers.throttle
2861
- },
2862
- code,
2863
- needsEvaluator: needsEvaluator || needsLocals || needsGlobals,
2864
- original
2865
- };
2866
- }
2867
- function compileBody(nodes) {
2868
- const statements = [];
2869
- let needsEvaluator = false;
2870
- let needsLocals = false;
2871
- let needsGlobals = false;
2872
- for (const node of nodes) {
2873
- const result = compileNode(node);
2874
- if (result === null) {
2875
- return { code: null, needsEvaluator: false, needsLocals: false, needsGlobals: false };
2876
- }
2877
- statements.push(result.code);
2878
- needsEvaluator = needsEvaluator || result.needsEvaluator;
2879
- needsLocals = needsLocals || result.needsLocals;
2880
- needsGlobals = needsGlobals || result.needsGlobals;
2881
- }
2882
- return {
2883
- code: statements.join("\n"),
2884
- needsEvaluator,
2885
- needsLocals,
2886
- needsGlobals
2887
- };
2888
- }
2889
- function compileNode(node) {
2890
- if (node.type === "command") {
2891
- return compileCommand(node);
2892
- }
2893
- return null;
2894
- }
2895
- function compileCommand(cmd) {
2896
- const target = cmd.target ? compileTarget(cmd.target) : "m";
2897
- switch (cmd.name) {
2898
- case "toggle":
2899
- return compileToggle(cmd.args, target);
2900
- case "add":
2901
- return compileAdd(cmd.args, target);
2902
- case "remove":
2903
- return compileRemove(cmd.args, target);
2904
- case "removeClass":
2905
- return compileRemoveClass(cmd.args, target);
2906
- case "show":
2907
- return {
2908
- code: `${target}.style.display = ''`,
2909
- needsEvaluator: false,
2910
- needsLocals: false,
2911
- needsGlobals: false
2912
- };
2913
- case "hide":
2914
- return {
2915
- code: `${target}.style.display = 'none'`,
2916
- needsEvaluator: false,
2917
- needsLocals: false,
2918
- needsGlobals: false
2919
- };
2920
- case "focus":
2921
- return {
2922
- code: `${target}.focus()`,
2923
- needsEvaluator: false,
2924
- needsLocals: false,
2925
- needsGlobals: false
2926
- };
2927
- case "blur":
2928
- return {
2929
- code: `${target}.blur()`,
2930
- needsEvaluator: false,
2931
- needsLocals: false,
2932
- needsGlobals: false
2933
- };
2934
- case "log":
2935
- return compileLog(cmd.args);
2936
- case "set":
2937
- return compileSet(cmd.args);
2938
- case "increment":
2939
- return compileIncrement(cmd.args);
2940
- case "decrement":
2941
- return compileDecrement(cmd.args);
2942
- case "put":
2943
- return compilePut(cmd.args, target, cmd.modifier);
2944
- case "send":
2945
- case "trigger":
2946
- return compileTrigger(cmd.args, target);
2947
- case "wait":
2948
- return compileWait(cmd.args);
2949
- default:
2950
- return null;
2951
- }
2952
- }
2953
- function compileToggle(args, target) {
2954
- const className = extractClassName(args[0]);
2955
- if (!className) return null;
2956
- if (target === "m") {
2957
- return {
2958
- code: `m.classList.toggle('${className}')`,
2959
- needsEvaluator: false,
2960
- needsLocals: false,
2961
- needsGlobals: false
2962
- };
2963
- }
2964
- return {
2965
- code: `${target}.forEach(el => el.classList.toggle('${className}'))`,
2966
- needsEvaluator: false,
2967
- needsLocals: false,
2968
- needsGlobals: false
2969
- };
2970
- }
2971
- function compileAdd(args, target) {
2972
- const className = extractClassName(args[0]);
2973
- if (!className) return null;
2974
- if (target === "m") {
2975
- return {
2976
- code: `m.classList.add('${className}')`,
2977
- needsEvaluator: false,
2978
- needsLocals: false,
2979
- needsGlobals: false
2980
- };
2981
- }
2982
- return {
2983
- code: `${target}.forEach(el => el.classList.add('${className}'))`,
2984
- needsEvaluator: false,
2985
- needsLocals: false,
2986
- needsGlobals: false
2987
- };
2988
- }
2989
- function compileRemove(args, target) {
2990
- if (!args || args.length === 0) {
2991
- return {
2992
- code: `${target}.remove()`,
2993
- needsEvaluator: false,
2994
- needsLocals: false,
2995
- needsGlobals: false
2996
- };
2997
- }
2998
- const className = extractClassName(args[0]);
2999
- if (!className) return null;
3000
- if (target === "m") {
3001
- return {
3002
- code: `m.classList.remove('${className}')`,
3003
- needsEvaluator: false,
3004
- needsLocals: false,
3005
- needsGlobals: false
3006
- };
3007
- }
3008
- return {
3009
- code: `${target}.forEach(el => el.classList.remove('${className}'))`,
3010
- needsEvaluator: false,
3011
- needsLocals: false,
3012
- needsGlobals: false
3013
- };
3014
- }
3015
- function compileRemoveClass(args, target) {
3016
- const className = extractClassName(args[0]);
3017
- if (!className) return null;
3018
- if (target === "m") {
3019
- return {
3020
- code: `m.classList.remove('${className}')`,
3021
- needsEvaluator: false,
3022
- needsLocals: false,
3023
- needsGlobals: false
3024
- };
3025
- }
3026
- return {
3027
- code: `${target}.forEach(el => el.classList.remove('${className}'))`,
3028
- needsEvaluator: false,
3029
- needsLocals: false,
3030
- needsGlobals: false
3031
- };
3032
- }
3033
- function compileLog(args) {
3034
- const compiled = args.map(compileExpression).filter(Boolean);
3035
- if (compiled.length !== args.length) {
3036
- return {
3037
- code: `console.log(${compiled.join(", ")})`,
3038
- needsEvaluator: true,
3039
- needsLocals: false,
3040
- needsGlobals: false
3041
- };
3042
- }
3043
- return {
3044
- code: `console.log(${compiled.join(", ")})`,
3045
- needsEvaluator: false,
3046
- needsLocals: false,
3047
- needsGlobals: false
3048
- };
3049
- }
3050
- function compileSet(args) {
3051
- if (args.length < 2) return null;
3052
- const [target, value] = args;
3053
- const compiledValue = compileExpression(value);
3054
- if (!compiledValue) return null;
3055
- if (target.type === "variable") {
3056
- const varNode = target;
3057
- if (varNode.scope === "local") {
3058
- const varName = varNode.name.slice(1);
3059
- return {
3060
- code: `L.${varName} = ${compiledValue}`,
3061
- needsEvaluator: false,
3062
- needsLocals: true,
3063
- needsGlobals: false
3064
- };
3065
- }
3066
- if (varNode.scope === "global") {
3067
- const varName = varNode.name.slice(1);
3068
- return {
3069
- code: `G.${varName} = ${compiledValue}`,
3070
- needsEvaluator: false,
3071
- needsLocals: false,
3072
- needsGlobals: true
3073
- };
3074
- }
3075
- }
3076
- if (target.type === "possessive") {
3077
- const possNode = target;
3078
- const obj = compileExpression(possNode.object);
3079
- const prop = possNode.property;
3080
- if (!obj) return null;
3081
- if (prop.startsWith("*")) {
3082
- const styleProp = prop.slice(1);
3083
- return {
3084
- code: `${obj}.style.${styleProp} = ${compiledValue}`,
3085
- needsEvaluator: false,
3086
- needsLocals: false,
3087
- needsGlobals: false
3088
- };
3089
- }
3090
- return {
3091
- code: `${obj}.${prop} = ${compiledValue}`,
3092
- needsEvaluator: false,
3093
- needsLocals: false,
3094
- needsGlobals: false
3095
- };
3096
- }
3097
- if (target.type === "member") {
3098
- const memNode = target;
3099
- const obj = compileExpression(memNode.object);
3100
- const prop = typeof memNode.property === "string" ? memNode.property : null;
3101
- if (!obj || !prop) return null;
3102
- if (prop.startsWith("*")) {
3103
- const styleProp = prop.slice(1);
3104
- return {
3105
- code: `${obj}.style.${styleProp} = ${compiledValue}`,
3106
- needsEvaluator: false,
3107
- needsLocals: false,
3108
- needsGlobals: false
3109
- };
3110
- }
3111
- return {
3112
- code: `${obj}.${prop} = ${compiledValue}`,
3113
- needsEvaluator: false,
3114
- needsLocals: false,
3115
- needsGlobals: false
3116
- };
3117
- }
3118
- return null;
3119
- }
3120
- function compileIncrement(args) {
3121
- if (args.length < 1) return null;
3122
- const target = args[0];
3123
- const amount = args.length > 1 ? compileExpression(args[1]) : "1";
3124
- if (target.type === "variable") {
3125
- const varNode = target;
3126
- if (varNode.scope === "local") {
3127
- const varName = varNode.name.slice(1);
3128
- return {
3129
- code: `L.${varName} = (L.${varName} || 0) + ${amount}`,
3130
- needsEvaluator: false,
3131
- needsLocals: true,
3132
- needsGlobals: false
3133
- };
3134
- }
3135
- if (varNode.scope === "global") {
3136
- const varName = varNode.name.slice(1);
3137
- return {
3138
- code: `G.${varName} = (G.${varName} || 0) + ${amount}`,
3139
- needsEvaluator: false,
3140
- needsLocals: false,
3141
- needsGlobals: true
3142
- };
3143
- }
3144
- }
3145
- const compiled = compileExpression(target);
3146
- if (compiled) {
3147
- return {
3148
- code: `${compiled}.textContent = (parseFloat(${compiled}.textContent) || 0) + ${amount}`,
3149
- needsEvaluator: false,
3150
- needsLocals: false,
3151
- needsGlobals: false
3152
- };
3153
- }
3154
- return null;
3155
- }
3156
- function compileDecrement(args) {
3157
- if (args.length < 1) return null;
3158
- const target = args[0];
3159
- const amount = args.length > 1 ? compileExpression(args[1]) : "1";
3160
- if (target.type === "variable") {
3161
- const varNode = target;
3162
- if (varNode.scope === "local") {
3163
- const varName = varNode.name.slice(1);
3164
- return {
3165
- code: `L.${varName} = (L.${varName} || 0) - ${amount}`,
3166
- needsEvaluator: false,
3167
- needsLocals: true,
3168
- needsGlobals: false
3169
- };
3170
- }
3171
- if (varNode.scope === "global") {
3172
- const varName = varNode.name.slice(1);
3173
- return {
3174
- code: `G.${varName} = (G.${varName} || 0) - ${amount}`,
3175
- needsEvaluator: false,
3176
- needsLocals: false,
3177
- needsGlobals: true
3178
- };
3179
- }
3180
- }
3181
- const compiled = compileExpression(target);
3182
- if (compiled) {
3183
- return {
3184
- code: `${compiled}.textContent = (parseFloat(${compiled}.textContent) || 0) - ${amount}`,
3185
- needsEvaluator: false,
3186
- needsLocals: false,
3187
- needsGlobals: false
3188
- };
3189
- }
3190
- return null;
3191
- }
3192
- function compilePut(args, target, modifier) {
3193
- if (args.length < 1) return null;
3194
- const content = compileExpression(args[0]);
3195
- if (!content) return null;
3196
- const mod = modifier || "into";
3197
- switch (mod) {
3198
- case "into":
3199
- return {
3200
- code: `${target}.innerHTML = ${content}`,
3201
- needsEvaluator: false,
3202
- needsLocals: false,
3203
- needsGlobals: false
3204
- };
3205
- case "before":
3206
- return {
3207
- code: `${target}.insertAdjacentHTML('beforebegin', ${content})`,
3208
- needsEvaluator: false,
3209
- needsLocals: false,
3210
- needsGlobals: false
3211
- };
3212
- case "after":
3213
- return {
3214
- code: `${target}.insertAdjacentHTML('afterend', ${content})`,
3215
- needsEvaluator: false,
3216
- needsLocals: false,
3217
- needsGlobals: false
3218
- };
3219
- case "at start of":
3220
- return {
3221
- code: `${target}.insertAdjacentHTML('afterbegin', ${content})`,
3222
- needsEvaluator: false,
3223
- needsLocals: false,
3224
- needsGlobals: false
3225
- };
3226
- case "at end of":
3227
- return {
3228
- code: `${target}.insertAdjacentHTML('beforeend', ${content})`,
3229
- needsEvaluator: false,
3230
- needsLocals: false,
3231
- needsGlobals: false
3232
- };
3233
- default:
3234
- return {
3235
- code: `${target}.innerHTML = ${content}`,
3236
- needsEvaluator: false,
3237
- needsLocals: false,
3238
- needsGlobals: false
3239
- };
3240
- }
3241
- }
3242
- function compileTrigger(args, target) {
3243
- if (args.length < 1) return null;
3244
- const eventName = compileExpression(args[0]);
3245
- if (!eventName) return null;
3246
- return {
3247
- code: `${target}.dispatchEvent(new CustomEvent(${eventName}, { bubbles: true }))`,
3248
- needsEvaluator: false,
3249
- needsLocals: false,
3250
- needsGlobals: false
3251
- };
3252
- }
3253
- function compileWait(args) {
3254
- if (args.length < 1) return null;
3255
- const duration = compileExpression(args[0]);
3256
- if (!duration) return null;
3257
- return {
3258
- code: `await new Promise(r => setTimeout(r, ${duration}))`,
3259
- needsEvaluator: false,
3260
- needsLocals: false,
3261
- needsGlobals: false
3262
- };
3263
- }
3264
- function compileExpression(node) {
3265
- if (!node) return null;
3266
- switch (node.type) {
3267
- case "literal": {
3268
- const litNode = node;
3269
- return JSON.stringify(litNode.value);
3270
- }
3271
- case "identifier": {
3272
- const idNode = node;
3273
- const val = idNode.value;
3274
- if (val === "me" || val === "my") return "m";
3275
- if (val === "you") return "y";
3276
- if (val === "it") return "it";
3277
- if (val === "event") return "e";
3278
- if (val === "body") return "document.body";
3279
- if (val === "document") return "document";
3280
- if (val === "window") return "window";
3281
- return val;
3282
- }
3283
- case "variable": {
3284
- const varNode = node;
3285
- const name = varNode.name.slice(1);
3286
- return varNode.scope === "local" ? `L.${name}` : `G.${name}`;
3287
- }
3288
- case "selector": {
3289
- const selNode = node;
3290
- const sanitized = sanitizeSelector(selNode.value);
3291
- return `document.querySelector('${sanitized}')`;
3292
- }
3293
- case "possessive": {
3294
- const possNode = node;
3295
- const obj = compileExpression(possNode.object);
3296
- const prop = possNode.property;
3297
- if (!obj) return null;
3298
- if (prop.startsWith("*")) {
3299
- return `${obj}.style.${prop.slice(1)}`;
3300
- }
3301
- if (prop === "values") {
3302
- return `(function(el){if(el instanceof HTMLFormElement)return new FormData(el);var fd=new FormData();el.querySelectorAll('input,select,textarea').forEach(function(i){var n=i.getAttribute('name');if(n&&'value'in i)fd.append(n,i.value)});return fd})(${obj})`;
3303
- }
3304
- return `${obj}.${prop}`;
3305
- }
3306
- case "member": {
3307
- const memNode = node;
3308
- const obj = compileExpression(memNode.object);
3309
- const prop = typeof memNode.property === "string" ? memNode.property : null;
3310
- if (!obj || !prop) return null;
3311
- if (prop.startsWith("*")) {
3312
- return `${obj}.style.${prop.slice(1)}`;
3313
- }
3314
- return `${obj}.${prop}`;
3315
- }
3316
- case "binary": {
3317
- const binNode = node;
3318
- const left = compileExpression(binNode.left);
3319
- const right = compileExpression(binNode.right);
3320
- if (!left || !right) return null;
3321
- const op = binNode.operator;
3322
- switch (op) {
3323
- case "+":
3324
- case "-":
3325
- case "*":
3326
- case "/":
3327
- case "%":
3328
- return `(${left} ${op} ${right})`;
3329
- case "==":
3330
- case "is":
3331
- return `(${left} == ${right})`;
3332
- case "!=":
3333
- case "is not":
3334
- return `(${left} != ${right})`;
3335
- case "<":
3336
- case ">":
3337
- case "<=":
3338
- case ">=":
3339
- return `(${left} ${op} ${right})`;
3340
- case "and":
3341
- case "&&":
3342
- return `(${left} && ${right})`;
3343
- case "or":
3344
- case "||":
3345
- return `(${left} || ${right})`;
3346
- case "has": {
3347
- const rightSel = binNode.right;
3348
- const className = rightSel.value?.slice(1) || "";
3349
- const sanitizedClass = sanitizeClassName(className);
3350
- if (!sanitizedClass) return null;
3351
- return `${left}.classList.contains('${sanitizedClass}')`;
3352
- }
3353
- default:
3354
- return null;
3355
- }
3356
- }
3357
- case "call": {
3358
- const callNode = node;
3359
- const callee = callNode.callee;
3360
- const args = (callNode.args || []).map(compileExpression);
3361
- if (args.some((a) => a === null)) return null;
3362
- if (callee.type === "member" || callee.type === "possessive") {
3363
- const memCallee = callee;
3364
- const obj = compileExpression(memCallee.object);
3365
- const prop = typeof memCallee.property === "string" ? memCallee.property : null;
3366
- if (!obj || !prop) return null;
3367
- return `${obj}.${prop}(${args.join(", ")})`;
3368
- }
3369
- const fn = compileExpression(callee);
3370
- if (!fn) return null;
3371
- return `${fn}(${args.join(", ")})`;
3372
- }
3373
- case "positional": {
3374
- const posNode = node;
3375
- const position = posNode.position;
3376
- const target = posNode.target;
3377
- switch (position) {
3378
- case "first":
3379
- if (target?.type === "selector") {
3380
- const selNode = target;
3381
- const sanitized = sanitizeSelector(selNode.value);
3382
- return `document.querySelector('${sanitized}')`;
3383
- }
3384
- return null;
3385
- // Arrays need runtime
3386
- case "last":
3387
- if (target?.type === "selector") {
3388
- const selNode = target;
3389
- const sanitized = sanitizeSelector(selNode.value);
3390
- return `(()=>{const e=document.querySelectorAll('${sanitized}');return e[e.length-1]})()`;
3391
- }
3392
- return null;
3393
- // Arrays need runtime
3394
- case "next":
3395
- return "m.nextElementSibling";
3396
- case "previous":
3397
- return "m.previousElementSibling";
3398
- case "closest":
3399
- if (target?.type === "selector") {
3400
- const selNode = target;
3401
- const sanitized = sanitizeSelector(selNode.value);
3402
- return `m.closest('${sanitized}')`;
3403
- }
3404
- return null;
3405
- case "parent":
3406
- return "m.parentElement";
3407
- default:
3408
- return null;
3409
- }
3410
- }
3411
- default:
3412
- return null;
3413
- }
3414
- }
3415
- function compileTarget(target) {
3416
- if (target.type === "identifier") {
3417
- const idNode = target;
3418
- if (idNode.value === "me" || idNode.value === "my") return "m";
3419
- if (idNode.value === "you") return "y";
3420
- }
3421
- if (target.type === "selector") {
3422
- const selNode = target;
3423
- const sanitized = sanitizeSelector(selNode.value);
3424
- return `document.querySelectorAll('${sanitized}')`;
3425
- }
3426
- const compiled = compileExpression(target);
3427
- return compiled || "m";
3428
- }
3429
- function extractClassName(node) {
3430
- if (!node) return null;
3431
- if (node.type === "selector") {
3432
- const selNode = node;
3433
- const val = selNode.value;
3434
- const rawClass = val.startsWith(".") ? val.slice(1) : val;
3435
- return sanitizeClassName(rawClass);
3436
- }
3437
- if (node.type === "literal" || node.type === "string") {
3438
- const litNode = node;
3439
- const val = litNode.value;
3440
- if (typeof val === "string") {
3441
- const rawClass = val.startsWith(".") ? val.slice(1) : val;
3442
- return sanitizeClassName(rawClass);
3443
- }
3444
- }
3445
- if (node.type === "identifier") {
3446
- const idNode = node;
3447
- return sanitizeClassName(idNode.value);
3448
- }
3449
- return null;
3450
- }
3451
-
3452
- // src/compiled-generator.ts
3453
- function generateSourceMapTable(handlers) {
3454
- if (handlers.length === 0) return "";
3455
- const lines = [
3456
- " *",
3457
- " * Handler Source Map:",
3458
- " * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"
3459
- ];
3460
- for (const h of handlers) {
3461
- const original = h.original.replace(/\n/g, " ").slice(0, 40);
3462
- const truncated = original.length < h.original.length ? original + "..." : original;
3463
- lines.push(` * \u2502 ${h.id.padEnd(20)} \u2190 ${truncated.padEnd(40)} \u2502`);
3464
- }
3465
- lines.push(" * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
3466
- lines.push(" *");
3467
- return lines.join("\n");
3468
- }
3469
- function generateCompiledBundle(options) {
3470
- const {
3471
- handlers,
3472
- needsLocals,
3473
- needsGlobals,
3474
- globalName = "hyperfixi",
3475
- htmx = false,
3476
- debug = false
3477
- } = options;
3478
- const handlerEntries = handlers.map((h) => {
3479
- const isAsync = h.code.includes("await");
3480
- const fn = isAsync ? `async(m,e,y)=>{${h.code}}` : `(m,e,y)=>{${h.code}}`;
3481
- if (debug) {
3482
- const escapedOriginal = h.original.replace(/\*/g, "\\*").replace(/\n/g, " ");
3483
- return `
3484
- /* ${escapedOriginal} */
3485
- ${h.id}:${fn}`;
3486
- }
3487
- return `${h.id}:${fn}`;
3488
- });
3489
- const registrations = handlers.map((h) => {
3490
- const modsCode = generateModifiersCode(h);
3491
- return ` case '${h.id}': r(el,'${h.event}',H.${h.id}${modsCode}); break;`;
3492
- });
3493
- const localsCode = needsLocals ? "const L={};" : "";
3494
- const globalsCode = needsGlobals ? "const G={};" : "";
3495
- const sourceMapTable = debug ? generateSourceMapTable(handlers) : "";
3496
- return `/**
3497
- * HyperFixi Compiled Bundle (Auto-Generated)
3498
- *
3499
- * Pre-compiled hyperscript - no runtime parser needed!
3500
- * Handlers: ${handlers.length}
3501
- *
3502
- * DO NOT EDIT - Regenerate with build.
3503
- ${sourceMapTable} */
3504
-
3505
- ${localsCode}${globalsCode}
3506
- const H={${handlerEntries.join(",")}};
2081
+ import { api, boot, register, everything } from '@hyperfixi/engine';
3507
2082
 
3508
- function r(el,ev,fn${hasModifiers(handlers) ? ",m" : ""}){
3509
- let h=e=>fn(el,e,e.target);
3510
- ${generateModifierWrappers(handlers)}
3511
- el.addEventListener(ev,h${hasOnce(handlers) ? ",{once:m?.o}" : ""});
3512
- }
2083
+ register(...everything);
2084
+ Object.assign(api, {
2085
+ bundle: { generator: '@hyperfixi/vite-plugin', modules: ['everything'], commands: [], blocks: [], languages: [] },
2086
+ });
3513
2087
 
3514
- function init(root=document){
3515
- root.querySelectorAll('[data-h]').forEach(el=>{
3516
- switch(el.dataset.h){
3517
- ${registrations.join("\n")}
3518
- }
3519
- });
3520
- }
3521
-
3522
- ${htmx ? `document.addEventListener('htmx:afterSettle',e=>init(e.detail?.target));` : ""}
3523
-
3524
- const api={
3525
- version:'1.0.0-compiled',
3526
- commands:${JSON.stringify(handlers.map((h) => h.id))},
3527
- init,
3528
- process:init,
3529
- };
3530
-
3531
- if(typeof window!=='undefined'){
3532
- window.${globalName}=api;
3533
- document.readyState==='loading'
3534
- ?document.addEventListener('DOMContentLoaded',()=>init())
3535
- :init();
2088
+ if (typeof window !== 'undefined') {
2089
+ window.hyperfixi = api;
2090
+ boot();
3536
2091
  }
3537
2092
 
3538
2093
  export default api;
3539
- export{api,init};
2094
+ export { api };
3540
2095
  `;
3541
- }
3542
- function generateModifiersCode(h) {
3543
- const mods = [];
3544
- if (h.modifiers.prevent) mods.push("p:1");
3545
- if (h.modifiers.stop) mods.push("s:1");
3546
- if (h.modifiers.once) mods.push("o:1");
3547
- if (h.modifiers.debounce) mods.push(`d:${h.modifiers.debounce}`);
3548
- if (h.modifiers.throttle) mods.push(`t:${h.modifiers.throttle}`);
3549
- if (mods.length === 0) return "";
3550
- return `,{${mods.join(",")}}`;
3551
- }
3552
- function hasModifiers(handlers) {
3553
- return handlers.some(
3554
- (h) => h.modifiers.prevent || h.modifiers.stop || h.modifiers.once || h.modifiers.debounce || h.modifiers.throttle
3555
- );
3556
- }
3557
- function hasOnce(handlers) {
3558
- return handlers.some((h) => h.modifiers.once);
3559
- }
3560
- function generateModifierWrappers(handlers) {
3561
- const lines = [];
3562
- if (handlers.some((h) => h.modifiers.prevent || h.modifiers.stop)) {
3563
- lines.push(
3564
- ` if(m?.p||m?.s){const _h=h;h=e=>{m.p&&e.preventDefault();m.s&&e.stopPropagation();_h(e.currentTarget,e,e.target)};}`
3565
- );
3566
- }
3567
- if (handlers.some((h) => h.modifiers.debounce)) {
3568
- lines.push(
3569
- ` if(m?.d){let t;const _h=h;h=e=>{clearTimeout(t);t=setTimeout(()=>_h(e.currentTarget,e,e.target),m.d)};}`
3570
- );
3571
- }
3572
- if (handlers.some((h) => h.modifiers.throttle)) {
3573
- lines.push(
3574
- ` if(m?.t){let l=0;const _h=h;h=e=>{const n=Date.now();if(n-l>=m.t){l=n;_h(e.currentTarget,e,e.target)}};}`
3575
- );
3576
- }
3577
- return lines.join("\n");
3578
- }
3579
-
3580
- // src/html-transformer.ts
3581
- function transformHTML(html, handlerMap, fallbackScripts) {
3582
- let modified = false;
3583
- const fallbacks = [];
3584
- const patterns = [
3585
- /(\s)_\s*=\s*"([^"]+)"/g,
3586
- // _="..."
3587
- /(\s)_\s*=\s*'([^']+)'/g,
3588
- // _='...'
3589
- /(\s)_\s*=\s*`([^`]+)`/g
3590
- // _=`...`
3591
- ];
3592
- let result = html;
3593
- for (const pattern of patterns) {
3594
- result = result.replace(pattern, (match, whitespace, script) => {
3595
- const handlerId = handlerMap.get(script);
3596
- if (handlerId) {
3597
- modified = true;
3598
- return `${whitespace}data-h="${handlerId}"`;
3599
- }
3600
- if (fallbackScripts.has(script)) {
3601
- fallbacks.push(script);
3602
- return match;
3603
- }
3604
- fallbacks.push(script);
3605
- return match;
3606
- });
3607
- }
3608
- return {
3609
- code: result,
3610
- modified,
3611
- fallbacks
3612
- };
3613
- }
3614
- function extractScripts(html) {
3615
- const scripts = [];
3616
- const patterns = [/_\s*=\s*"([^"]+)"/g, /_\s*=\s*'([^']+)'/g, /_\s*=\s*`([^`]+)`/g];
3617
- for (const pattern of patterns) {
3618
- let match;
3619
- while (match = pattern.exec(html)) {
3620
- scripts.push(match[1]);
3621
- }
3622
2096
  }
3623
- return scripts;
3624
- }
2097
+ };
3625
2098
 
3626
2099
  // src/server-bridge-integration.ts
3627
2100
  import { resolve, join } from "path";
@@ -3925,10 +2398,15 @@ function computeUsageHash(usage) {
3925
2398
  usage.htmx.needsReactivity
3926
2399
  ].map((f) => f ? "1" : "0").join("") : "none";
3927
2400
  const reactivity = `${usage.needsReactivity ? 1 : 0}${usage.needsBindToProperty ? 1 : 0}`;
3928
- return `${commands}|${blocks}|${usage.positional}|${languages}|${htmx}|${reactivity}`;
2401
+ const extras = `${usage.needsConstruct ? 1 : 0}${usage.needsCookies ? 1 : 0}`;
2402
+ return `${commands}|${blocks}|${usage.positional}|${languages}|${htmx}|${reactivity}|${extras}`;
3929
2403
  }
3930
2404
  function hyperfixi(options = {}) {
3931
- const mode = options.mode ?? "interpret";
2405
+ if (options.mode === "compile") {
2406
+ console.warn(
2407
+ `[hyperfixi] mode: "compile" was removed in 4.0 (it compiled with @hyperfixi/core's parser); building the bundle on @hyperfixi/engine instead.`
2408
+ );
2409
+ }
3932
2410
  const scanner = new Scanner(options);
3933
2411
  const aggregator = new Aggregator();
3934
2412
  const generator = new Generator(options);
@@ -3938,11 +2416,6 @@ function hyperfixi(options = {}) {
3938
2416
  let cachedBundle = null;
3939
2417
  let lastUsageHash = "";
3940
2418
  let isDev = false;
3941
- const compiledHandlers = [];
3942
- const handlerMap = /* @__PURE__ */ new Map();
3943
- const fallbackScripts = /* @__PURE__ */ new Set();
3944
- let needsLocals = false;
3945
- let needsGlobals = false;
3946
2419
  function invalidateVirtualModule() {
3947
2420
  if (!server) return;
3948
2421
  const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
@@ -3954,32 +2427,6 @@ function hyperfixi(options = {}) {
3954
2427
  }
3955
2428
  }
3956
2429
  }
3957
- function compileScript(script) {
3958
- if (handlerMap.has(script)) {
3959
- return handlerMap.get(script);
3960
- }
3961
- if (fallbackScripts.has(script)) {
3962
- return null;
3963
- }
3964
- const handler = compile(script);
3965
- if (handler) {
3966
- compiledHandlers.push(handler);
3967
- handlerMap.set(script, handler.id);
3968
- if (handler.needsEvaluator) {
3969
- if (handler.code.includes("L.")) needsLocals = true;
3970
- if (handler.code.includes("G.")) needsGlobals = true;
3971
- }
3972
- if (options.debug) {
3973
- console.log(`[hyperfixi] Compiled: "${script}" -> ${handler.id}`);
3974
- }
3975
- return handler.id;
3976
- }
3977
- fallbackScripts.add(script);
3978
- if (options.debug) {
3979
- console.log(`[hyperfixi] Fallback (not compilable): "${script}"`);
3980
- }
3981
- return null;
3982
- }
3983
2430
  function generateInterpretBundle() {
3984
2431
  const usage = aggregator.getUsage();
3985
2432
  const usageHash = computeUsageHash(usage);
@@ -3998,30 +2445,7 @@ function hyperfixi(options = {}) {
3998
2445
  }
3999
2446
  return cachedBundle;
4000
2447
  }
4001
- function generateCompiledBundleCode() {
4002
- if (fallbackScripts.size > 0) {
4003
- if (options.debug) {
4004
- console.log(`[hyperfixi] ${fallbackScripts.size} scripts need interpreter fallback`);
4005
- }
4006
- return generateInterpretBundle();
4007
- }
4008
- const bundle = generateCompiledBundle({
4009
- handlers: compiledHandlers,
4010
- needsLocals,
4011
- needsGlobals,
4012
- globalName: options.globalName,
4013
- htmx: options.htmx,
4014
- debug: options.debug
4015
- });
4016
- if (options.debug) {
4017
- console.log(`[hyperfixi] Compiled bundle: ${compiledHandlers.length} handlers`);
4018
- }
4019
- return bundle;
4020
- }
4021
2448
  function generateBundle() {
4022
- if (mode === "compile") {
4023
- return generateCompiledBundleCode();
4024
- }
4025
2449
  return generateInterpretBundle();
4026
2450
  }
4027
2451
  return {
@@ -4099,20 +2523,6 @@ function hyperfixi(options = {}) {
4099
2523
  if (!scanner.shouldScan(id)) {
4100
2524
  return null;
4101
2525
  }
4102
- if (mode === "compile" && !isDev) {
4103
- const scripts = extractScripts(code);
4104
- for (const script of scripts) {
4105
- compileScript(script);
4106
- }
4107
- const result = transformHTML(code, handlerMap, fallbackScripts);
4108
- if (result.modified) {
4109
- if (options.debug) {
4110
- console.log(`[hyperfixi] Transformed: ${id.split("/").pop()}`);
4111
- }
4112
- return { code: result.code, map: null };
4113
- }
4114
- return null;
4115
- }
4116
2526
  const usage = scanner.scan(code, id);
4117
2527
  const changed = aggregator.add(id, usage);
4118
2528
  if (domainScanner && domainAggregator && domainScanner.hasAnyDomainUsage(code)) {
@@ -4166,17 +2576,6 @@ function hyperfixi(options = {}) {
4166
2576
  */
4167
2577
  async buildStart() {
4168
2578
  if (!isDev) {
4169
- if (mode === "compile") {
4170
- resetCompiler();
4171
- compiledHandlers.length = 0;
4172
- handlerMap.clear();
4173
- fallbackScripts.clear();
4174
- needsLocals = false;
4175
- needsGlobals = false;
4176
- if (options.debug) {
4177
- console.log("[hyperfixi] Compile mode: ready for build");
4178
- }
4179
- }
4180
2579
  const cwd = process.cwd();
4181
2580
  if (options.debug) {
4182
2581
  console.log("[hyperfixi] Scanning project for hyperscript usage...");
@@ -4202,22 +2601,6 @@ function hyperfixi(options = {}) {
4202
2601
  console.log("[hyperfixi] Domain build scan:", domainSummary);
4203
2602
  }
4204
2603
  }
4205
- if (mode === "compile") {
4206
- const usage = aggregator.getUsage();
4207
- if (usage.detectedLanguages.size > 0) {
4208
- const aliases = getMultilingualCommandAliases(
4209
- usage.detectedLanguages
4210
- );
4211
- if (Object.keys(aliases).length > 0) {
4212
- setMultilingualAliases(aliases);
4213
- if (options.debug) {
4214
- console.log(
4215
- `[hyperfixi] Compile mode: configured ${Object.keys(aliases).length} multilingual aliases`
4216
- );
4217
- }
4218
- }
4219
- }
4220
- }
4221
2604
  if (options.debug) {
4222
2605
  const summary = aggregator.getSummary();
4223
2606
  console.log(`[hyperfixi] Found ${summary.fileCount} files with hyperscript:`, {
@@ -4241,7 +2624,6 @@ export {
4241
2624
  REGIONS,
4242
2625
  SUPPORTED_LANGUAGES,
4243
2626
  clearCustomKeywords,
4244
- clearSemanticParser,
4245
2627
  computeUsageHash,
4246
2628
  index_default as default,
4247
2629
  descriptorToScanRule,
@@ -4249,10 +2631,8 @@ export {
4249
2631
  getAllLanguageCodes,
4250
2632
  getKeywordsForLanguage,
4251
2633
  getOptimalRegion,
4252
- hasSemanticParser,
4253
2634
  hyperfixi,
4254
2635
  isNonLatinLanguage,
4255
- registerCustomKeywords,
4256
- setSemanticParser
2636
+ registerCustomKeywords
4257
2637
  };
4258
2638
  //# sourceMappingURL=index.js.map