@geajs/vite-plugin 1.2.0 → 1.2.2

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.
Files changed (2) hide show
  1. package/dist/index.mjs +110 -37
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import _generate from "@babel/generator";
3
3
  import * as t from "@babel/types";
4
4
  import { parse } from "@babel/parser";
5
5
  import { appendToBody, id, js, jsAll, jsBlockBody, jsClassProp, jsExpr, jsImport, jsMethod, jsObjectExpr, jsPrivateProp, num, str, tpl } from "eszter";
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
7
7
  import { dirname, relative, resolve } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  //#region src/utils/babel-interop.ts
@@ -1126,6 +1126,7 @@ function buildComponentPropsExpression(jsxElement, imports, componentInstances,
1126
1126
  else if (t.isJSXExpressionContainer(attr.value) && !t.isJSXEmptyExpression(attr.value.expression)) {
1127
1127
  const expr = attr.value.expression;
1128
1128
  propValue = transformExpression(expr);
1129
+ if (propValue && t.isTemplateLiteral(propValue) && propValue.quasis.length === 2 && propValue.quasis[0].value.raw === "" && propValue.quasis[1].value.raw === "" && propValue.expressions.length === 1) propValue = propValue.expressions[0];
1129
1130
  if (propValue && (/^on[A-Z]/.test(propName) || DOM_EVENT_RE.test(propName)) && t.isMemberExpression(propValue)) {
1130
1131
  const argsId = t.identifier("args");
1131
1132
  propValue = t.arrowFunctionExpression([t.restElement(argsId)], t.callExpression(t.cloneNode(propValue, true), [t.spreadElement(argsId)]));
@@ -1734,6 +1735,12 @@ function collectComponentTags(node, imports, instanceTags, inMapCallback = false
1734
1735
  if (tag && isComponentTag(tag) && imports.has(tag)) {
1735
1736
  if (!inMapCallback) instanceTags.push(tag);
1736
1737
  }
1738
+ for (const attr of node.openingElement.attributes) {
1739
+ if (!t.isJSXAttribute(attr) || !attr.value || !t.isJSXExpressionContainer(attr.value)) continue;
1740
+ if (t.isJSXEmptyExpression(attr.value.expression)) continue;
1741
+ const attrExpr = attr.value.expression;
1742
+ visitExpr(attrExpr, inMapCallback);
1743
+ }
1737
1744
  node.children.forEach((c) => {
1738
1745
  if (t.isJSXElement(c)) collectComponentTags(c, imports, instanceTags, inMapCallback);
1739
1746
  else if (t.isJSXFragment(c)) collectComponentTags(c, imports, instanceTags, inMapCallback);
@@ -2276,7 +2283,7 @@ function processChildren(children, parts, ctx, elementPath, dcCursor, directChil
2276
2283
  pushString(parts, "");
2277
2284
  }
2278
2285
  if (expressionMayBeFalsy(rawExpr)) expr = t.logicalExpression("||", expr, t.stringLiteral(""));
2279
- const safeExpr = childCallInfo || isChildrenPropAccess(rawExpr) || expressionContainsJSX(rawExpr) || ctx.inMapCallback || callsJSXReturningProperty(rawExpr, ctx.classBody) ? expr : jsExpr`geaEscapeHtml(String(${expr}))`;
2286
+ const safeExpr = childCallInfo || isChildrenPropAccess(rawExpr) || expressionContainsJSX(rawExpr) || ctx.inMapCallback || callsJSXReturningProperty(rawExpr, ctx.classBody) ? expr : jsExpr`geaEscapeHtml(${expr})`;
2280
2287
  parts.push({
2281
2288
  type: "expression",
2282
2289
  value: safeExpr
@@ -9687,7 +9694,9 @@ function transformRemainingJSX(ast, imports) {
9687
9694
  const classMethod = path.findParent((p) => t.isClassMethod(p.node));
9688
9695
  if (classMethod && t.isClassMethod(classMethod.node) && t.isIdentifier(classMethod.node.key) && classMethod.node.key.name === "template") return;
9689
9696
  try {
9690
- path.replaceWith(transformJSXToTemplate(path.node, { imports }));
9697
+ let result = transformJSXToTemplate(path.node, { imports });
9698
+ if (t.isTemplateLiteral(result) && result.quasis.length === 2 && result.quasis[0].value.raw === "" && result.quasis[1].value.raw === "" && result.expressions.length === 1 && path.parent && t.isArrowFunctionExpression(path.parent) && path.parent.body === path.node) result = result.expressions[0];
9699
+ path.replaceWith(result);
9691
9700
  } catch (err) {
9692
9701
  console.warn("[gea] Failed to transform JSX element:", err instanceof Error ? err.message : err);
9693
9702
  }
@@ -9717,39 +9726,41 @@ const legacyShouldProxyDep = (p) => /\.(js|ts)$/.test(p) && !p.match(/(store|sta
9717
9726
  * Returns `true` when HMR code was added, `false` otherwise (no component
9718
9727
  * class, or the file contains a `gea-auto-register plugin` comment).
9719
9728
  */
9720
- function injectHMR(ast, componentClassName, componentImports, componentImportsUsedAsTags, isDefaultExport, hmrImportSource = "virtual:gea-hmr", shouldProxyDep) {
9729
+ function injectHMR(ast, componentClassNames, defaultExportClassName, componentImports, componentImportsUsedAsTags, hmrImportSource = "virtual:gea-hmr", shouldProxyDep, shouldSkipDepAccept) {
9721
9730
  if (hasAutoRegisterComment(ast)) return false;
9731
+ if (componentClassNames.length === 0) return false;
9722
9732
  const hmrStmts = [];
9723
- if (componentClassName) {
9724
- ensureImport(ast, hmrImportSource, "handleComponentUpdate");
9725
- ensureImport(ast, hmrImportSource, "registerHotModule");
9726
- ensureImport(ast, hmrImportSource, "registerComponentInstance");
9727
- ensureImport(ast, hmrImportSource, "unregisterComponentInstance");
9728
- const proxyDep = shouldProxyDep ?? legacyShouldProxyDep;
9729
- if (rewriteComponentDeps(ast, componentImports, proxyDep).length > 0) ensureImport(ast, hmrImportSource, "createHotComponentProxy");
9730
- const modExports = isDefaultExport ? t.objectExpression([t.objectProperty(t.identifier("default"), t.identifier(componentClassName))]) : t.objectExpression([t.objectProperty(t.identifier(componentClassName), t.identifier(componentClassName), false, true)]);
9731
- hmrStmts.push(js`const __moduleExports = ${modExports};`);
9732
- hmrStmts.push(js`registerHotModule(${jsExpr`${importMeta()}.url`}, __moduleExports);`);
9733
- hmrStmts.push(js`
9734
- ${hot()}.accept((newModule) => {
9735
- const __updatedModule = newModule || __moduleExports;
9736
- registerHotModule(${jsExpr`${importMeta()}.url`}, __updatedModule);
9737
- handleComponentUpdate(${jsExpr`${importMeta()}.url`}, __updatedModule);
9738
- });
9739
- `);
9740
- hmrStmts.push(...createAccepts(componentImports, proxyDep));
9741
- hmrStmts.push(js`const __origCreated = ${id(componentClassName)}.prototype.created;`);
9742
- hmrStmts.push(js`${id(componentClassName)}.prototype.created = function(__geaProps) {
9733
+ ensureImport(ast, hmrImportSource, "handleComponentUpdate");
9734
+ ensureImport(ast, hmrImportSource, "registerHotModule");
9735
+ ensureImport(ast, hmrImportSource, "registerComponentInstance");
9736
+ ensureImport(ast, hmrImportSource, "unregisterComponentInstance");
9737
+ const proxyDep = shouldProxyDep ?? legacyShouldProxyDep;
9738
+ if (rewriteComponentDeps(ast, componentImports, proxyDep).length > 0) ensureImport(ast, hmrImportSource, "createHotComponentProxy");
9739
+ const exportProperties = [];
9740
+ for (const cn of componentClassNames) if (cn === defaultExportClassName) exportProperties.push(t.objectProperty(t.identifier("default"), t.identifier(cn)));
9741
+ else exportProperties.push(t.objectProperty(t.identifier(cn), t.identifier(cn), false, true));
9742
+ hmrStmts.push(js`const __moduleExports = ${t.objectExpression(exportProperties)};`);
9743
+ hmrStmts.push(js`registerHotModule(${jsExpr`${importMeta()}.url`}, __moduleExports);`);
9744
+ const acceptBody = [t.variableDeclaration("const", [t.variableDeclarator(t.identifier("__updatedModule"), t.logicalExpression("||", t.identifier("newModule"), t.identifier("__moduleExports")))]), t.expressionStatement(t.callExpression(t.identifier("registerHotModule"), [t.memberExpression(importMeta(), t.identifier("url")), t.identifier("__updatedModule")]))];
9745
+ for (const cn of componentClassNames) {
9746
+ const key = cn === defaultExportClassName ? "default" : cn;
9747
+ acceptBody.push(t.expressionStatement(t.callExpression(t.identifier("handleComponentUpdate"), [t.memberExpression(importMeta(), t.identifier("url")), t.objectExpression([t.objectProperty(t.identifier("default"), t.memberExpression(t.identifier("__updatedModule"), t.identifier(key)))])])));
9748
+ }
9749
+ hmrStmts.push(t.expressionStatement(t.callExpression(t.memberExpression(hot(), t.identifier("accept")), [t.arrowFunctionExpression([t.identifier("newModule")], t.blockStatement(acceptBody))])));
9750
+ hmrStmts.push(...createAccepts(componentImports, proxyDep, shouldSkipDepAccept));
9751
+ for (const cn of componentClassNames) {
9752
+ const suffix = componentClassNames.length > 1 ? `_${cn}` : "";
9753
+ hmrStmts.push(js`const ${id("__origCreated" + suffix)} = ${id(cn)}.prototype.created;`);
9754
+ hmrStmts.push(js`${id(cn)}.prototype.created = function(__geaProps) {
9743
9755
  registerComponentInstance(this.constructor.name, this);
9744
- return __origCreated.call(this, __geaProps);
9756
+ return ${id("__origCreated" + suffix)}.call(this, __geaProps);
9745
9757
  };`);
9746
- hmrStmts.push(js`const __origDispose = ${id(componentClassName)}.prototype.dispose;`);
9747
- hmrStmts.push(js`${id(componentClassName)}.prototype.dispose = function() {
9758
+ hmrStmts.push(js`const ${id("__origDispose" + suffix)} = ${id(cn)}.prototype.dispose;`);
9759
+ hmrStmts.push(js`${id(cn)}.prototype.dispose = function() {
9748
9760
  unregisterComponentInstance(this.constructor.name, this);
9749
- return __origDispose.call(this);
9761
+ return ${id("__origDispose" + suffix)}.call(this);
9750
9762
  };`);
9751
9763
  }
9752
- if (hmrStmts.length === 0) return false;
9753
9764
  ast.program.body.push(t.ifStatement(hot(), t.blockStatement(hmrStmts)));
9754
9765
  return true;
9755
9766
  }
@@ -9791,10 +9802,11 @@ function rewriteComponentDeps(ast, imports, proxyDep) {
9791
9802
  * add `hot.accept(dep, () => hot.invalidate())` so the page reloads when
9792
9803
  * stores/utils change.
9793
9804
  */
9794
- function createAccepts(imports, proxyDep) {
9805
+ function createAccepts(imports, proxyDep, shouldSkip) {
9795
9806
  const stmts = [];
9796
9807
  for (const p of imports) {
9797
9808
  if (!isRelative(p)) continue;
9809
+ if (shouldSkip?.(p)) continue;
9798
9810
  if (!proxyDep(p)) stmts.push(js`${hot()}.accept(${p}, ${invalidateCb()});`);
9799
9811
  }
9800
9812
  return stmts;
@@ -9855,6 +9867,14 @@ function isComponentImportSource(source) {
9855
9867
  if (source.startsWith("node:")) return false;
9856
9868
  return true;
9857
9869
  }
9870
+ /**
9871
+ * In dev mode, convert relative store imports (e.g. `import { router } from '../router'`)
9872
+ * to `const { router } = await import('../router')` placed after all class declarations.
9873
+ *
9874
+ * This breaks circular-dependency TDZ during HMR: classes are fully initialized
9875
+ * before the store module is loaded, so the store can safely import the component
9876
+ * classes back without hitting the temporal dead zone.
9877
+ */
9858
9878
  function transform(ctx) {
9859
9879
  const { sourceFile, code, isServe, isSSR, hmrImportSource } = ctx;
9860
9880
  if (!(code.includes("<") && code.includes(">"))) return null;
@@ -9880,7 +9900,6 @@ function transform(ctx) {
9880
9900
  let transformed = false;
9881
9901
  const componentImportSet = /* @__PURE__ */ new Set();
9882
9902
  const componentImportsUsedAsTags = /* @__PURE__ */ new Set();
9883
- let isDefaultExport = false;
9884
9903
  imports.forEach((source) => {
9885
9904
  if (!isComponentImportSource(source)) return;
9886
9905
  componentImportSet.add(source);
@@ -9890,9 +9909,6 @@ function transform(ctx) {
9890
9909
  const knownComponentImports = /* @__PURE__ */ new Set();
9891
9910
  const namedImportSources = /* @__PURE__ */ new Map();
9892
9911
  traverse(ast, {
9893
- ExportDefaultDeclaration() {
9894
- isDefaultExport = true;
9895
- },
9896
9912
  ImportDeclaration(path) {
9897
9913
  const source = path.node.source.value;
9898
9914
  if (!isComponentImportSource(source)) return;
@@ -9941,7 +9957,9 @@ function transform(ctx) {
9941
9957
  }
9942
9958
  } else transformed = transformNonComponentJSX(ast, imports);
9943
9959
  }
9944
- if (isServe && componentClassName) {
9960
+ if (isServe && componentClassNames.length > 0) {
9961
+ let defaultExportClassName = null;
9962
+ for (const node of ast.program.body) if (t.isExportDefaultDeclaration(node) && t.isClassDeclaration(node.declaration) && node.declaration.id) defaultExportClassName = node.declaration.id.name;
9945
9963
  const shouldProxyDep = (source) => {
9946
9964
  if (!source.startsWith(".")) return false;
9947
9965
  const resolved = ctx.resolveImportPath(sourceFile, source);
@@ -9950,7 +9968,13 @@ function transform(ctx) {
9950
9968
  if (ctx.isComponentModule(resolved)) return true;
9951
9969
  return false;
9952
9970
  };
9953
- if (injectHMR(ast, componentClassName, componentImports, componentImportsUsedAsTags, isDefaultExport, hmrImportSource, shouldProxyDep)) transformed = true;
9971
+ const shouldSkipDepAccept = (source) => {
9972
+ if (!source.startsWith(".")) return false;
9973
+ const resolved = ctx.resolveImportPath(sourceFile, source);
9974
+ if (!resolved) return false;
9975
+ return ctx.isStoreModule(resolved);
9976
+ };
9977
+ if (injectHMR(ast, componentClassNames, defaultExportClassName, componentImports, componentImportsUsedAsTags, hmrImportSource, shouldProxyDep, shouldSkipDepAccept)) transformed = true;
9954
9978
  }
9955
9979
  if (!transformed) return null;
9956
9980
  ensureGeaCompilerSymbolImports(ast);
@@ -10029,6 +10053,13 @@ export function reconcile(oldC, newC) {
10029
10053
  }
10030
10054
  `;
10031
10055
  const HMR_RUNTIME_SOURCE = `
10056
+ var GEA_ELEMENT = Symbol.for('gea.element');
10057
+ var GEA_RENDERED = Symbol.for('gea.rendered');
10058
+ var GEA_CLEANUP_BINDINGS = Symbol.for('gea.component.cleanupBindings');
10059
+ var GEA_TEARDOWN_SELF_LISTENERS = Symbol.for('gea.component.teardownSelfListeners');
10060
+ var GEA_CHILD_COMPONENTS = Symbol.for('gea.childComponents');
10061
+ var GEA_BINDINGS = Symbol.for('gea.bindings');
10062
+ var GEA_SELF_LISTENERS = Symbol.for('gea.selfListeners');
10032
10063
  var hot = import.meta.hot;
10033
10064
  var componentInstances = hot && hot.data && hot.data.componentInstances || new Map();
10034
10065
  if (hot) hot.data.componentInstances = componentInstances;
@@ -10340,7 +10371,7 @@ function geaPlugin() {
10340
10371
  }
10341
10372
  }
10342
10373
  if (/\bclass\s+Component\s+extends\s+Store\b/.test(code)) return null;
10343
- return transform({
10374
+ const result = transform({
10344
10375
  sourceFile: cleanId,
10345
10376
  code,
10346
10377
  isServe: isServeCommand,
@@ -10352,8 +10383,50 @@ function geaPlugin() {
10352
10383
  registerStoreModule: (fp) => storeModules.add(fp),
10353
10384
  registerComponentModule: (fp) => componentModules.add(fp)
10354
10385
  });
10386
+ if (result) return result;
10387
+ if (isServeCommand && !isSSR) {
10388
+ const componentDeps = findComponentDeps(code, cleanId);
10389
+ if (componentDeps.length > 0) return {
10390
+ code: code + `\nif (import.meta.hot) {\n${componentDeps.map((dep) => ` import.meta.hot.accept('${dep}', () => {});`).join("\n")}\n}\n`,
10391
+ map: null
10392
+ };
10393
+ }
10394
+ return null;
10355
10395
  }
10356
10396
  };
10357
10397
  }
10398
+ function resolveToFile(base) {
10399
+ const exts = [
10400
+ ".ts",
10401
+ ".tsx",
10402
+ ".js",
10403
+ ".jsx"
10404
+ ];
10405
+ const indexFiles = exts.map((ext) => resolve(base, "index" + ext));
10406
+ const candidates = [
10407
+ base,
10408
+ ...exts.map((ext) => base + ext),
10409
+ ...indexFiles
10410
+ ];
10411
+ for (const c of candidates) try {
10412
+ if (existsSync(c) && statSync(c).isFile()) return c;
10413
+ } catch {}
10414
+ return null;
10415
+ }
10416
+ function findComponentDeps(code, filePath) {
10417
+ const deps = [];
10418
+ const importRegex = /import\s+(?:[\w{},\s*]+)\s+from\s+['"](\.[^'"]+)['"]/g;
10419
+ let match;
10420
+ while ((match = importRegex.exec(code)) !== null) {
10421
+ const source = match[1];
10422
+ const resolved = resolveToFile(resolve(dirname(filePath), source));
10423
+ if (!resolved) continue;
10424
+ try {
10425
+ const depCode = readFileSync(resolved, "utf8");
10426
+ if (/class\s+\w+\s+extends\s+Component\b/.test(depCode) && depCode.includes("<") && depCode.includes(">")) deps.push(source);
10427
+ } catch {}
10428
+ }
10429
+ return deps;
10430
+ }
10358
10431
  //#endregion
10359
10432
  export { geaPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geajs/vite-plugin",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Vite plugin for Gea framework - JSX/TSX transform, reactivity, HMR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",