jac-client 0.2.10__py3-none-any.whl → 0.2.12__py3-none-any.whl

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 (85) hide show
  1. jac_client/examples/all-in-one/button.jac +4 -3
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +36 -24
  3. jac_client/examples/all-in-one/components/Header.jac +12 -8
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +49 -35
  5. jac_client/examples/all-in-one/components/Summary.jac +59 -36
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +142 -112
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +37 -30
  8. jac_client/examples/all-in-one/components/TransactionList.jac +33 -26
  9. jac_client/examples/all-in-one/components/button.jac +4 -3
  10. jac_client/examples/all-in-one/components/navigation.jac +111 -117
  11. jac_client/examples/all-in-one/constants/categories.jac +23 -24
  12. jac_client/examples/all-in-one/constants/clients.jac +7 -8
  13. jac_client/examples/all-in-one/context/BudgetContext.jac +9 -6
  14. jac_client/examples/all-in-one/hooks/useBudget.jac +18 -12
  15. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +14 -13
  16. jac_client/examples/all-in-one/main.jac +340 -371
  17. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +19 -12
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +31 -15
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +113 -90
  20. jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +34 -39
  21. jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +464 -352
  22. jac_client/examples/all-in-one/pages/loginPage.jac +114 -119
  23. jac_client/examples/all-in-one/pages/nestedDemo.jac +43 -50
  24. jac_client/examples/all-in-one/pages/notFound.jac +14 -15
  25. jac_client/examples/all-in-one/pages/signupPage.jac +113 -119
  26. jac_client/examples/all-in-one/utils/formatters.jac +5 -8
  27. jac_client/examples/asset-serving/css-with-image/main.jac +77 -73
  28. jac_client/examples/asset-serving/image-asset/main.jac +47 -46
  29. jac_client/examples/asset-serving/import-alias/main.jac +93 -95
  30. jac_client/examples/basic/main.jac +17 -15
  31. jac_client/examples/basic-auth/main.jac +246 -254
  32. jac_client/examples/basic-auth-with-router/main.jac +272 -285
  33. jac_client/examples/basic-full-stack/main.jac +245 -242
  34. jac_client/examples/css-styling/js-styling/main.jac +41 -62
  35. jac_client/examples/css-styling/material-ui/main.jac +90 -90
  36. jac_client/examples/css-styling/pure-css/main.jac +35 -44
  37. jac_client/examples/css-styling/sass-example/main.jac +35 -44
  38. jac_client/examples/css-styling/styled-components/main.jac +38 -47
  39. jac_client/examples/css-styling/tailwind-example/main.jac +54 -43
  40. jac_client/examples/full-stack-with-auth/main.jac +407 -433
  41. jac_client/examples/little-x/main.jac +306 -344
  42. jac_client/examples/little-x/src/submit-button.jac +15 -14
  43. jac_client/examples/nested-folders/nested-advance/main.jac +18 -27
  44. jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +4 -6
  45. jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +9 -13
  46. jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +29 -32
  47. jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +12 -18
  48. jac_client/examples/nested-folders/nested-basic/main.jac +7 -5
  49. jac_client/examples/nested-folders/nested-basic/src/button.jac +4 -3
  50. jac_client/examples/nested-folders/nested-basic/src/components/button.jac +4 -3
  51. jac_client/examples/ts-support/main.jac +26 -26
  52. jac_client/examples/with-router/main.jac +186 -223
  53. jac_client/plugin/client_runtime.cl.jac +5 -3
  54. jac_client/plugin/impl/client_runtime.impl.jac +1 -1
  55. jac_client/plugin/plugin_config.jac +53 -99
  56. jac_client/plugin/src/__init__.jac +0 -2
  57. jac_client/plugin/src/compiler.jac +0 -1
  58. jac_client/plugin/src/impl/compiler.impl.jac +49 -17
  59. jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
  60. jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
  61. jac_client/plugin/src/impl/vite_bundler.impl.jac +146 -84
  62. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +54 -41
  63. jac_client/plugin/utils/__init__.jac +3 -0
  64. jac_client/plugin/utils/bun_installer.jac +16 -0
  65. jac_client/plugin/utils/client_deps.jac +14 -0
  66. jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
  67. jac_client/plugin/utils/impl/client_deps.impl.jac +73 -0
  68. jac_client/templates/client.jacpack +0 -4
  69. jac_client/templates/fullstack.jacpack +1 -5
  70. jac_client/tests/conftest.py +56 -41
  71. jac_client/tests/fixtures/spawn_test/app.jac +49 -52
  72. jac_client/tests/fixtures/with-ts/app.jac +27 -27
  73. jac_client/tests/test_cli.py +71 -6
  74. jac_client/tests/test_helpers.py +11 -18
  75. jac_client/tests/test_it.py +1 -1
  76. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/METADATA +5 -5
  77. jac_client-0.2.12.dist-info/RECORD +115 -0
  78. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/WHEEL +1 -1
  79. jac_client/plugin/src/babel_processor.jac +0 -18
  80. jac_client/plugin/src/impl/babel_processor.impl.jac +0 -89
  81. jac_client/plugin/utils/impl/node_installer.impl.jac +0 -249
  82. jac_client/plugin/utils/node_installer.jac +0 -41
  83. jac_client-0.2.10.dist-info/RECORD +0 -115
  84. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/entry_points.txt +0 -0
  85. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/top_level.txt +0 -0
@@ -157,41 +157,26 @@ def _load_template(template_name: str) -> dict[str, Any] | None {
157
157
  # ===============================================================================
158
158
  # Post-create Hook for Client Template
159
159
  # ===============================================================================
160
- """Post-create hook to ensure Node.js is installed and optionally install npm packages."""
160
+ """Post-create hook to ensure Bun is installed and optionally install packages."""
161
161
  def _post_create_client(project_path: Path, project_name: str) -> None {
162
162
  import from jac_client.plugin.src.vite_bundler { ViteBundler }
163
- import from jac_client.plugin.utils.node_installer { NodeInstaller }
164
163
  import from jaclang.cli.console { console }
165
- import shutil;
164
+ import from jac_client.plugin.utils { ensure_bun_available }
166
165
 
167
- # First, ensure Node.js is installed
166
+ # First, ensure Bun is installed (prompt to install if not)
168
167
  console.print();
169
- with console.status(
170
- "[cyan]Checking Node.js installation...[/cyan]", spinner="dots"
171
- ) as status {
172
- node_result = NodeInstaller.ensure_node_installed(interactive=True);
173
- node_installed = node_result[0];
174
- message = node_result[1];
175
- was_just_installed = node_result[2];
176
- }
177
-
178
- if not node_installed {
179
- console.warning(f"Node.js is required for client development");
180
- console.print(f"\n {message}", file=sys.stderr);
181
- console.print(
182
- " After installing Node.js, run: jac add --cl\n",
183
- style="muted",
184
- file=sys.stderr
185
- );
168
+ if not ensure_bun_available() {
169
+ console.warning("Bun is required for client development");
170
+ console.print(" After installing Bun, run: jac add --cl\n", style="muted");
186
171
  return;
187
172
  }
188
173
 
189
- console.print(f" ✔ {message}", style="success");
174
+ console.print(" ✔ Bun is installed", style="success");
190
175
 
191
- # Check if npm package installation should be skipped
176
+ # Check if package installation should be skipped
192
177
  if os.environ.get("JAC_CLIENT_SKIP_NPM_INSTALL") {
193
178
  console.print(
194
- "\n ⏭ Skipping npm package installation (JAC_CLIENT_SKIP_NPM_INSTALL is set)",
179
+ "\n ⏭ Skipping package installation (JAC_CLIENT_SKIP_NPM_INSTALL is set)",
195
180
  style="muted"
196
181
  );
197
182
  console.print(
@@ -223,7 +208,7 @@ def _post_create_client(project_path: Path, project_name: str) -> None {
223
208
  client_dir = bundler._get_client_dir();
224
209
  client_dir.mkdir(parents=True, exist_ok=True);
225
210
 
226
- # Copy package.json to .jac/client/ for npm install
211
+ # Copy package.json to .jac/client/ for bun install
227
212
  configs_package_json = client_dir / 'configs' / 'package.json';
228
213
  build_package_json = client_dir / 'package.json';
229
214
 
@@ -237,38 +222,38 @@ def _post_create_client(project_path: Path, project_name: str) -> None {
237
222
 
238
223
  shutil.copy2(configs_package_json, build_package_json);
239
224
 
240
- # Run npm install using NodeInstaller to handle NVM environment
241
- try {
242
- console.print(" Installing npm packages...", style="cyan");
243
- with console.status(
244
- "[cyan]Installing npm packages...[/cyan]", spinner="dots"
245
- ) as status {
246
- NodeInstaller.run_npm_with_nvm(
247
- ['install'], cwd=client_dir, timeout=300
248
- );
225
+ # Run bun install (ensure bun is available first)
226
+ import from jac_client.plugin.utils { ensure_bun_available }
227
+ if not ensure_bun_available() {
228
+ console.warning("Bun is required. Install manually: https://bun.sh");
229
+ console.print(
230
+ " 💡 You can install packages later with: jac add --cl\n",
231
+ style="muted"
232
+ );
233
+ # Clean up temporary package.json
234
+ if build_package_json.exists() {
235
+ build_package_json.unlink();
249
236
  }
237
+ return;
238
+ }
250
239
 
251
- # Move package-lock.json to configs/ if it was created
252
- build_package_lock = client_dir / 'package-lock.json';
253
- configs_package_lock = client_dir / 'configs' / 'package-lock.json';
254
- if build_package_lock.exists() {
255
- if configs_package_lock.exists() {
256
- configs_package_lock.unlink();
240
+ try {
241
+ console.print("\n ⏳ Installing packages...\n", style="cyan");
242
+ subprocess.run(['bun', 'install'], cwd=client_dir, check=True, timeout=300);
243
+
244
+ # Move bun.lockb to configs/ if it was created
245
+ build_bun_lockb = client_dir / 'bun.lockb';
246
+ configs_bun_lockb = client_dir / 'configs' / 'bun.lockb';
247
+ if build_bun_lockb.exists() {
248
+ if configs_bun_lockb.exists() {
249
+ configs_bun_lockb.unlink();
257
250
  }
258
- shutil.move(str(build_package_lock), str(configs_package_lock));
251
+ shutil.move(str(build_bun_lockb), str(configs_bun_lockb));
259
252
  }
260
253
 
261
- console.print(" ✔ npm packages installed", style="success");
254
+ console.print(" ✔ Packages installed", style="success");
262
255
  } except subprocess.CalledProcessError as e {
263
- console.warning(f"Failed to install packages: {e.stderr}");
264
- console.print(
265
- " 💡 You can install packages later with: jac add --cl\n",
266
- style="muted"
267
- );
268
- } except FileNotFoundError {
269
- console.warning(
270
- "npm command not found. This may require reloading your shell."
271
- );
256
+ console.warning(f"Failed to install packages: {e}");
272
257
  console.print(
273
258
  " 💡 You can install packages later with: jac add --cl\n",
274
259
  style="muted"
@@ -285,19 +270,6 @@ def _post_create_client(project_path: Path, project_name: str) -> None {
285
270
  " 💡 You can install packages later with: jac add --cl\n", style="muted"
286
271
  );
287
272
  }
288
-
289
- # If Node.js was just installed, offer to restart terminal for full integration
290
- if was_just_installed {
291
- console.print();
292
- console.info("Node.js was just installed via NVM");
293
- console.print(
294
- "\n For Node.js to be available in your current shell:", style="muted"
295
- );
296
- console.print(" 1. Run: source ~/.nvm/nvm.sh", style="muted");
297
- console.print(" 2. Or restart your terminal", style="muted");
298
- console.print("\n 💡 npm commands from jac work immediately", style="info");
299
- console.print();
300
- }
301
273
  }
302
274
 
303
275
  # ===============================================================================
@@ -357,39 +329,25 @@ def _npm_remove_handler(
357
329
  _regenerate_and_install(project_dir);
358
330
  }
359
331
 
360
- """Regenerate package.json from jac.toml and run npm install."""
332
+ """Regenerate package.json from jac.toml and run bun install."""
361
333
  def _regenerate_and_install(project_dir: Path) -> None {
362
334
  import from jac_client.plugin.src.vite_bundler { ViteBundler }
363
- import from jac_client.plugin.utils.node_installer { NodeInstaller }
335
+ import from jac_client.plugin.utils { ensure_bun_available }
364
336
  import shutil;
365
337
 
366
- # Ensure Node.js is installed before proceeding
367
- node_result = NodeInstaller.ensure_node_installed(interactive=True);
368
- node_installed = node_result[0];
369
- message = node_result[1];
370
- was_just_installed = node_result[2];
371
-
372
- if not node_installed {
373
- raise RuntimeError(
374
- f"Node.js installation required:\n{message}\n" + "Please install Node.js and try again."
375
- ) ;
376
- }
377
-
378
- # Inform user if Node.js was just installed
379
- if was_just_installed {
380
- print(
381
- "\nNote: Node.js was just installed. npm commands will work via NVM sourcing."
382
- );
338
+ # Ensure Bun is installed before proceeding (prompt to install if not)
339
+ if not ensure_bun_available() {
340
+ raise RuntimeError("Bun is required. Install manually: https://bun.sh") from None ;
383
341
  }
384
342
 
385
343
  bundler = ViteBundler(project_dir);
386
344
  bundler.create_package_json();
387
345
 
388
- # Install to .jac/client/ directory where babel processor expects it
346
+ # Install to .jac/client/ directory
389
347
  client_dir = bundler._get_client_dir();
390
348
  client_dir.mkdir(parents=True, exist_ok=True);
391
349
 
392
- # Copy package.json to .jac/client/ for npm install
350
+ # Copy package.json to .jac/client/ for bun install
393
351
  configs_package_json = client_dir / 'configs' / 'package.json';
394
352
  build_package_json = client_dir / 'package.json';
395
353
  if configs_package_json.exists() {
@@ -397,27 +355,23 @@ def _regenerate_and_install(project_dir: Path) -> None {
397
355
  }
398
356
 
399
357
  try {
400
- # Use NodeInstaller.run_npm_with_nvm to handle NVM environment
401
- NodeInstaller.run_npm_with_nvm(["install"], cwd=client_dir, timeout=300);
358
+ # Run bun install
359
+ subprocess.run(['bun', 'install'], cwd=client_dir, check=True, timeout=300);
402
360
  } except subprocess.CalledProcessError as e {
403
- raise RuntimeError(f"Failed to install npm packages: {e.stderr}") from e ;
404
- } except FileNotFoundError {
405
- raise RuntimeError(
406
- "npm command not found. This may require reloading your shell after Node.js installation."
407
- ) from None ;
361
+ raise RuntimeError(f"Failed to install packages: {e}") from e ;
408
362
  } finally {
409
363
  # Clean up temporary package.json
410
364
  if build_package_json.exists() {
411
365
  build_package_json.unlink();
412
366
  }
413
- # Move package-lock.json to configs/ if it exists
414
- build_package_lock = client_dir / 'package-lock.json';
415
- if build_package_lock.exists() {
416
- configs_package_lock = client_dir / 'configs' / 'package-lock.json';
417
- if configs_package_lock.exists() {
418
- configs_package_lock.unlink();
367
+ # Move bun.lockb to configs/ if it exists
368
+ build_bun_lockb = client_dir / 'bun.lockb';
369
+ if build_bun_lockb.exists() {
370
+ configs_bun_lockb = client_dir / 'configs' / 'bun.lockb';
371
+ if configs_bun_lockb.exists() {
372
+ configs_bun_lockb.unlink();
419
373
  }
420
- build_package_lock.rename(configs_package_lock);
374
+ build_bun_lockb.rename(configs_bun_lockb);
421
375
  }
422
376
  }
423
377
  }
@@ -1,6 +1,5 @@
1
1
  """Vite client bundle processing modules."""
2
2
  import from jac_client.plugin.src.asset_processor { AssetProcessor }
3
- import from jac_client.plugin.src.babel_processor { BabelProcessor }
4
3
  import from jac_client.plugin.src.compiler { ViteCompiler }
5
4
  import from jac_client.plugin.src.config_loader { JacClientConfig }
6
5
  import from jac_client.plugin.src.import_processor { ImportProcessor }
@@ -10,7 +9,6 @@ import from jac_client.plugin.src.vite_bundler { ViteBundler }
10
9
 
11
10
  glob __all__ = [
12
11
  'AssetProcessor',
13
- 'BabelProcessor',
14
12
  'ViteCompiler',
15
13
  'JacClientConfig',
16
14
  'ImportProcessor',
@@ -6,7 +6,6 @@ import from types { ModuleType }
6
6
  import from typing { TYPE_CHECKING, Any }
7
7
  import from jaclang.runtimelib.client_bundle { ClientBundleError }
8
8
  import from .asset_processor { AssetProcessor }
9
- import from .babel_processor { BabelProcessor }
10
9
  import from .import_processor { ImportProcessor }
11
10
  import from .jac_to_js { JacToJSCompiler }
12
11
  import from .vite_bundler { ViteBundler }
@@ -16,7 +16,6 @@ impl ViteCompiler._get_client_dir(self: ViteCompiler) -> Path {
16
16
  impl ViteCompiler.compile_and_bundle(
17
17
  self: ViteCompiler, module: ModuleType, module_path: Path
18
18
  ) -> tuple[str, str, list[str], list[str]] {
19
- self.compile_runtime_utils();
20
19
  (module_js, mod, module_manifest) = self.jac_compiler.compile_module(module_path);
21
20
  collected_exports: set[str] = set(
22
21
  self.jac_compiler.extract_exports(module_manifest)
@@ -28,14 +27,14 @@ impl ViteCompiler.compile_and_bundle(
28
27
  collected_exports=collected_exports,
29
28
  collected_globals=collected_globals
30
29
  );
30
+ # Compile runtime AFTER dependencies so it overwrites any dependency-compiled
31
+ # version of client_runtime.js with the proper ES module exports
32
+ self.compile_runtime_utils();
31
33
  self.copy_root_assets();
32
34
  self.create_entry_file(module_path);
33
- self.babel_processor.compile();
34
- client_dir = self._get_client_dir();
35
- self.babel_processor.copy_assets_after_compile(
36
- self.compiled_dir, (client_dir / 'build'), self.asset_processor
37
- );
38
- entry_file = client_dir / 'build' / '_entry.js';
35
+ # Vite handles JSX/TSX transpilation natively with Bun - no Babel needed
36
+ # Vite builds directly from compiled/ directory
37
+ entry_file = self.compiled_dir / '_entry.js';
39
38
  self.vite_bundler.build(entry_file=entry_file);
40
39
  (bundle_code, bundle_hash) = self.vite_bundler.read_bundle();
41
40
  client_exports = sorted(collected_exports);
@@ -49,7 +48,7 @@ impl ViteCompiler.create_entry_file(self: ViteCompiler, module_path: Path) -> No
49
48
  entry_file = self.compiled_dir / '_entry.js';
50
49
  # Derive the app module filename from the entry point (e.g., main.jac -> main.js, app.jac -> app.js)
51
50
  app_module_name = module_path.stem;
52
- entry_content = f'import React from "react";\nimport {{ createRoot }} from "react-dom/client";\nimport {{ app as App }} from "./{app_module_name}.js";\nimport {{ JacClientErrorBoundary, ErrorFallback }} from "@jac-client/utils";\n\nconst root = createRoot(document.getElementById("root"));\nroot.render(\n\tReact.createElement(\n\t\tJacClientErrorBoundary,{{ FallbackComponent: ErrorFallback }},\n\t\tReact.createElement(App, null)\n\t)\n);\n';
51
+ entry_content = f'import React from "react";\nimport {{ createRoot }} from "react-dom/client";\nimport {{ app as App }} from "./{app_module_name}.js";\nimport {{ JacClientErrorBoundary, ErrorFallback }} from "@jac/runtime";\n\nconst root = createRoot(document.getElementById("root"));\nroot.render(\n\tReact.createElement(\n\t\tJacClientErrorBoundary,{{ FallbackComponent: ErrorFallback }},\n\t\tReact.createElement(App, null)\n\t)\n);\n';
53
52
  entry_file.write_text(entry_content, encoding='utf-8');
54
53
  }
55
54
 
@@ -59,11 +58,10 @@ impl ViteCompiler.copy_root_assets(self: ViteCompiler) -> None {
59
58
  compiled_assets_dir = self.compiled_dir / 'assets';
60
59
  if (root_assets_dir.exists() and root_assets_dir.is_dir()) {
61
60
  self.asset_processor.copy_assets(root_assets_dir, compiled_assets_dir);
62
- }
63
- # Copy configured_asset files from root assets/ to build assets/ (bypassing compiled)
64
- build_assets_dir = self._get_client_dir() / 'build' / 'assets';
65
- if (root_assets_dir.exists() and root_assets_dir.is_dir()) {
66
- self.asset_processor.copy_custom_asset_types(root_assets_dir, build_assets_dir);
61
+ # Copy configured custom asset types to compiled/assets/
62
+ self.asset_processor.copy_custom_asset_types(
63
+ root_assets_dir, compiled_assets_dir
64
+ );
67
65
  }
68
66
  }
69
67
 
@@ -189,7 +187,21 @@ impl ViteCompiler.compile_dependencies_recursively(
189
187
  output_path = self.compiled_dir / name;
190
188
  }
191
189
  output_path.parent.mkdir(parents=True, exist_ok=True);
192
- output_path.write_text(combined_js, encoding='utf-8');
190
+ # Add source file header comment for better error messages
191
+ source_header = f"/* Source: {module_path} */\n";
192
+ # Add ES module exports so Vite can resolve named imports between modules
193
+ module_export_names = sorted(set(exports_list + list(non_root_globals.keys())));
194
+ if module_export_names {
195
+ import re;
196
+ # Strip any existing export declarations to avoid duplicates
197
+ clean_combined_js = re.sub(r'\nexport\s*\{[^}]*\}\s*;?\s*', '\n', combined_js);
198
+ clean_combined_js = re.sub(
199
+ r'\bexport\s+(let|const|var|function|class)\b', r'\1', clean_combined_js
200
+ );
201
+ export_stmt = f"\nexport {{ {', '.join(module_export_names)} }};\n";
202
+ combined_js = clean_combined_js + export_stmt;
203
+ }
204
+ output_path.write_text(source_header + combined_js, encoding='utf-8');
193
205
  if (not manifest or not manifest.imports) {
194
206
  return;
195
207
  }
@@ -228,8 +240,29 @@ impl ViteCompiler.compile_runtime_utils(self: ViteCompiler) -> tuple[str, list[s
228
240
  runtimeutils_exports_list = self.jac_compiler.extract_exports(
229
241
  runtimeutils_manifest
230
242
  );
231
- all_exports = sorted(set((runtimeutils_exports_list + self.ROUTER_EXPORTS)));
232
- combined_runtime_utils_js = runtimeutils_js;
243
+ # Include both function exports and glob exports (e.g., useState, useEffect)
244
+ glob_names = list(runtimeutils_manifest.globals) if runtimeutils_manifest else [];
245
+ all_exports = sorted(
246
+ set(runtimeutils_exports_list + self.ROUTER_EXPORTS + glob_names)
247
+ );
248
+ # Strip all existing export statements from compiled JS to avoid duplicates.
249
+ # The codegen produces `export let`, `export function`, `export class`, and
250
+ # `export { ... }` forms. We remove the export keyword from declarations and
251
+ # remove export blocks entirely, then add one comprehensive export at the end.
252
+ import re;
253
+ clean_js = re.sub(r'\nexport\s*\{[^}]*\}\s*;?\s*', '\n', runtimeutils_js);
254
+ clean_js = re.sub(r'\bexport\s+(let|const|var|function|class)\b', r'\1', clean_js);
255
+ # Append single comprehensive ES module export for Vite to resolve @jac/runtime
256
+ export_names = [
257
+ e
258
+ for e in all_exports
259
+ if not e.startswith('_')
260
+ ];
261
+ # Also include internal names that are imported by compiled modules
262
+ internal_exports = ['__jacJsx', '__jacSpawn', '__jacCallFunction'];
263
+ all_export_names = sorted(set(export_names + internal_exports));
264
+ export_stmt = f"\nexport {{ {', '.join(all_export_names)} }};\n";
265
+ combined_runtime_utils_js = clean_js + export_stmt;
233
266
  self.compiled_dir.mkdir(parents=True, exist_ok=True);
234
267
  (self.compiled_dir / 'client_runtime.js').write_text(
235
268
  combined_runtime_utils_js, encoding='utf-8'
@@ -283,6 +316,5 @@ impl ViteCompiler.init(
283
316
  );
284
317
  self.import_processor = ImportProcessor();
285
318
  self.asset_processor = AssetProcessor();
286
- self.babel_processor = BabelProcessor(self.project_dir);
287
319
  self.vite_bundler = ViteBundler(self.project_dir, vite_output_dir, vite_minify);
288
320
  }
@@ -1,7 +1,11 @@
1
1
  """Add runtime imports to compiled JS code."""
2
2
 
3
3
  impl JacToJSCompiler.add_runtime_imports(self: JacToJSCompiler, js_code: str) -> str {
4
- jac_jsx_import = 'import {__jacJsx, __jacSpawn} from "@jac-client/utils";';
4
+ # Skip adding import if this file IS the runtime (defines __jacJsx itself)
5
+ if "function __jacJsx(" in js_code {
6
+ return js_code;
7
+ }
8
+ jac_jsx_import = 'import {__jacJsx, __jacSpawn} from "@jac/runtime";';
5
9
  return f"{jac_jsx_import}\n{js_code}";
6
10
  }
7
11
 
@@ -33,39 +33,39 @@ impl PackageInstaller.uninstall_package(
33
33
  }
34
34
  # Save the updated config
35
35
  self.config_loader.save();
36
- # Regenerate package.json and run npm install to actually remove the package
36
+ # Regenerate package.json and run bun install to actually remove the package
37
37
  self._regenerate_and_install();
38
38
  }
39
39
 
40
- """Regenerate package.json from jac.toml and run npm install."""
40
+ """Regenerate package.json from jac.toml and run bun install."""
41
41
  impl PackageInstaller._regenerate_and_install(self: PackageInstaller) -> None {
42
+ import from jac_client.plugin.utils { ensure_bun_available }
43
+ # Ensure bun is available before proceeding
44
+ if not ensure_bun_available() {
45
+ raise ClientBundleError('Bun is required. Install manually: https://bun.sh') from None ;
46
+ }
42
47
  # Regenerate package.json from updated jac.toml
43
48
  bundler = ViteBundler(self.project_dir);
44
49
  bundler.create_package_json();
45
- # Ensure root package.json exists temporarily for npm commands
50
+ # Ensure root package.json exists temporarily for bun commands
46
51
  bundler._ensure_root_package_json();
47
52
  try {
48
- # Run npm install to actually install the packages
49
- subprocess.run(
50
- ['npm', 'install'],
51
- cwd=self.project_dir,
52
- check=True,
53
- capture_output=True,
54
- text=True
53
+ # Run bun install to actually install the packages
54
+ print("\n ⏳ Installing packages...\n", flush=True);
55
+ result = subprocess.run(
56
+ ['bun', 'install'], cwd=self.project_dir, check=False, text=True
55
57
  );
56
- } except subprocess.CalledProcessError as e {
57
- raise ClientBundleError(f'Failed to install npm packages: {e.stderr}') from e ;
58
- } except FileNotFoundError {
59
- raise ClientBundleError(
60
- 'npm command not found. Ensure Node.js and npm are installed.'
61
- ) from None ;
58
+ if result.returncode != 0 {
59
+ raise ClientBundleError('Failed to install packages (see output above)') ;
60
+ }
61
+ print("\n ✔ Packages installed", flush=True);
62
62
  } finally {
63
- # Always clean up root package.json and move package-lock.json
63
+ # Always clean up root package.json and move bun.lockb
64
64
  bundler._cleanup_root_package_files();
65
65
  }
66
66
  }
67
67
 
68
- """Install all packages from jac.toml (regenerate package.json and run npm install)."""
68
+ """Install all packages from jac.toml (regenerate package.json and run bun install)."""
69
69
  impl PackageInstaller.install_all(self: PackageInstaller) -> None {
70
70
  if not self.config_file.exists() {
71
71
  raise ClientBundleError(
@@ -93,11 +93,11 @@ impl PackageInstaller.install_package(
93
93
  self.config_loader.add_dependency(package_name, package_version, is_dev);
94
94
  # Save the updated config
95
95
  self.config_loader.save();
96
- # Regenerate package.json and install the package via npm
96
+ # Regenerate package.json and install the package via bun
97
97
  self._regenerate_and_install();
98
98
  }
99
99
 
100
- """Handles installing npm packages by updating jac.toml."""
100
+ """Handles installing packages by updating jac.toml."""
101
101
  impl PackageInstaller.init(self: PackageInstaller, project_dir: Path) {
102
102
  self.project_dir = project_dir;
103
103
  self.config_loader = JacClientConfig(project_dir);