jac-client 0.1.0__py3-none-any.whl → 0.2.1__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 (141) hide show
  1. jac_client/docs/README.md +232 -172
  2. jac_client/docs/advanced-state.md +1012 -452
  3. jac_client/docs/asset-serving/intro.md +209 -0
  4. jac_client/docs/assets/pipe_line-v2.svg +32 -0
  5. jac_client/docs/assets/pipe_line.png +0 -0
  6. jac_client/docs/file-system/intro.md +90 -0
  7. jac_client/docs/guide-example/intro.md +117 -0
  8. jac_client/docs/guide-example/step-01-setup.md +260 -0
  9. jac_client/docs/guide-example/step-02-components.md +416 -0
  10. jac_client/docs/guide-example/step-03-styling.md +478 -0
  11. jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
  12. jac_client/docs/guide-example/step-05-local-state.md +530 -0
  13. jac_client/docs/guide-example/step-06-events.md +750 -0
  14. jac_client/docs/guide-example/step-07-effects.md +469 -0
  15. jac_client/docs/guide-example/step-08-walkers.md +534 -0
  16. jac_client/docs/guide-example/step-09-authentication.md +586 -0
  17. jac_client/docs/guide-example/step-10-routing.md +540 -0
  18. jac_client/docs/guide-example/step-11-final.md +964 -0
  19. jac_client/docs/imports.md +538 -46
  20. jac_client/docs/lifecycle-hooks.md +517 -297
  21. jac_client/docs/routing.md +487 -357
  22. jac_client/docs/styling/intro.md +250 -0
  23. jac_client/docs/styling/js-styling.md +373 -0
  24. jac_client/docs/styling/material-ui.md +346 -0
  25. jac_client/docs/styling/pure-css.md +305 -0
  26. jac_client/docs/styling/sass.md +409 -0
  27. jac_client/docs/styling/styled-components.md +401 -0
  28. jac_client/docs/styling/tailwind.md +303 -0
  29. jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
  30. jac_client/examples/asset-serving/css-with-image/README.md +91 -0
  31. jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
  32. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  33. jac_client/examples/asset-serving/css-with-image/package.json +28 -0
  34. jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
  35. jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
  36. jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
  37. jac_client/examples/asset-serving/image-asset/README.md +119 -0
  38. jac_client/examples/asset-serving/image-asset/app.jac +43 -0
  39. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  40. jac_client/examples/asset-serving/image-asset/package.json +28 -0
  41. jac_client/examples/asset-serving/image-asset/styles.css +27 -0
  42. jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
  43. jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
  44. jac_client/examples/asset-serving/import-alias/README.md +83 -0
  45. jac_client/examples/asset-serving/import-alias/app.jac +57 -0
  46. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  47. jac_client/examples/asset-serving/import-alias/package.json +28 -0
  48. jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
  49. jac_client/examples/basic/.babelrc +9 -0
  50. jac_client/examples/basic/README.md +16 -0
  51. jac_client/examples/basic/app.jac +16 -0
  52. jac_client/examples/basic/package.json +27 -0
  53. jac_client/examples/basic/vite.config.js +28 -0
  54. jac_client/examples/basic-auth/.babelrc +9 -0
  55. jac_client/examples/basic-auth/README.md +16 -0
  56. jac_client/examples/basic-auth/app.jac +308 -0
  57. jac_client/examples/basic-auth/package.json +27 -0
  58. jac_client/examples/basic-auth/vite.config.js +28 -0
  59. jac_client/examples/basic-auth-with-router/.babelrc +9 -0
  60. jac_client/examples/basic-auth-with-router/README.md +60 -0
  61. jac_client/examples/basic-auth-with-router/app.jac +464 -0
  62. jac_client/examples/basic-auth-with-router/package.json +28 -0
  63. jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
  64. jac_client/examples/basic-full-stack/.babelrc +9 -0
  65. jac_client/examples/basic-full-stack/README.md +18 -0
  66. jac_client/examples/basic-full-stack/app.jac +320 -0
  67. jac_client/examples/basic-full-stack/package.json +28 -0
  68. jac_client/examples/basic-full-stack/vite.config.js +28 -0
  69. jac_client/examples/css-styling/js-styling/.babelrc +9 -0
  70. jac_client/examples/css-styling/js-styling/README.md +183 -0
  71. jac_client/examples/css-styling/js-styling/app.jac +63 -0
  72. jac_client/examples/css-styling/js-styling/package.json +28 -0
  73. jac_client/examples/css-styling/js-styling/styles.js +100 -0
  74. jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
  75. jac_client/examples/css-styling/material-ui/.babelrc +9 -0
  76. jac_client/examples/css-styling/material-ui/README.md +16 -0
  77. jac_client/examples/css-styling/material-ui/app.jac +82 -0
  78. jac_client/examples/css-styling/material-ui/package.json +32 -0
  79. jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
  80. jac_client/examples/css-styling/pure-css/.babelrc +9 -0
  81. jac_client/examples/css-styling/pure-css/README.md +16 -0
  82. jac_client/examples/css-styling/pure-css/app.jac +63 -0
  83. jac_client/examples/css-styling/pure-css/package.json +28 -0
  84. jac_client/examples/css-styling/pure-css/styles.css +112 -0
  85. jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
  86. jac_client/examples/css-styling/sass-example/.babelrc +9 -0
  87. jac_client/examples/css-styling/sass-example/README.md +16 -0
  88. jac_client/examples/css-styling/sass-example/app.jac +63 -0
  89. jac_client/examples/css-styling/sass-example/package.json +29 -0
  90. jac_client/examples/css-styling/sass-example/styles.scss +158 -0
  91. jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
  92. jac_client/examples/css-styling/styled-components/.babelrc +9 -0
  93. jac_client/examples/css-styling/styled-components/README.md +16 -0
  94. jac_client/examples/css-styling/styled-components/app.jac +66 -0
  95. jac_client/examples/css-styling/styled-components/package.json +29 -0
  96. jac_client/examples/css-styling/styled-components/styled.js +91 -0
  97. jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
  98. jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
  99. jac_client/examples/css-styling/tailwind-example/README.md +16 -0
  100. jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
  101. jac_client/examples/css-styling/tailwind-example/global.css +1 -0
  102. jac_client/examples/css-styling/tailwind-example/package.json +30 -0
  103. jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
  104. jac_client/examples/full-stack-with-auth/.babelrc +9 -0
  105. jac_client/examples/full-stack-with-auth/README.md +16 -0
  106. jac_client/examples/full-stack-with-auth/app.jac +735 -0
  107. jac_client/examples/full-stack-with-auth/package.json +28 -0
  108. jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
  109. jac_client/examples/with-router/.babelrc +9 -0
  110. jac_client/examples/with-router/README.md +17 -0
  111. jac_client/examples/with-router/app.jac +323 -0
  112. jac_client/examples/with-router/package.json +28 -0
  113. jac_client/examples/with-router/vite.config.js +28 -0
  114. jac_client/plugin/cli.py +95 -179
  115. jac_client/plugin/client.py +111 -2
  116. jac_client/plugin/client_runtime.jac +183 -890
  117. jac_client/plugin/vite_client_bundle.py +185 -205
  118. jac_client/tests/__init__.py +0 -1
  119. jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
  120. jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
  121. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  122. jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
  123. jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
  124. jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
  125. jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
  126. jac_client/tests/fixtures/spawn_test/app.jac +133 -0
  127. jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
  128. jac_client/tests/test_asset_examples.py +339 -0
  129. jac_client/tests/test_cl.py +345 -151
  130. jac_client/tests/test_create_jac_app.py +41 -45
  131. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
  132. jac_client-0.2.1.dist-info/RECORD +140 -0
  133. jac_client/examples/little-x/package-lock.json +0 -2840
  134. jac_client/examples/todo-app/README.md +0 -82
  135. jac_client/examples/todo-app/app.jac +0 -683
  136. jac_client/examples/todo-app/package-lock.json +0 -999
  137. jac_client/examples/todo-app/package.json +0 -22
  138. jac_client-0.1.0.dist-info/RECORD +0 -33
  139. /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
  140. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
  141. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -4,12 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  import contextlib
6
6
  import hashlib
7
- import json
8
7
  import shutil
9
8
  import subprocess
10
9
  from pathlib import Path
11
10
  from types import ModuleType
12
- from typing import Any, Sequence, TYPE_CHECKING
11
+ from typing import Any, TYPE_CHECKING
13
12
 
14
13
  from jaclang.runtimelib.client_bundle import (
15
14
  ClientBundle,
@@ -53,7 +52,6 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
53
52
  and local .js files we embed). Bare package specifiers (e.g., "antd") are left as real
54
53
  ES imports so Vite can resolve and bundle them.
55
54
  """
56
- # TODO: return pure js files separately
57
55
  imported_js_modules: list[Path | None] = []
58
56
 
59
57
  if manifest and manifest.imports:
@@ -86,10 +84,8 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
86
84
  self,
87
85
  module_path: Path,
88
86
  visited: set[Path] | None = None,
89
- is_root: bool = False,
90
87
  collected_exports: set[str] | None = None,
91
88
  collected_globals: dict[str, Any] | None = None,
92
- runtime_js: str | None = None,
93
89
  ) -> None:
94
90
  """Recursively compile/copy .jac/.js imports to temp, skipping bundling.
95
91
 
@@ -97,8 +93,6 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
97
93
  or copying local JS (.js) into the temp directory. Bare specifiers are left
98
94
  untouched for Vite to resolve.
99
95
  """
100
- from jaclang.runtimelib.machine import JacMachine as Jac
101
-
102
96
  if visited is None:
103
97
  visited = set()
104
98
  if collected_exports is None:
@@ -111,40 +105,33 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
111
105
  return
112
106
  visited.add(module_path)
113
107
  manifest = None
114
- if not is_root:
115
- # Compile current module to JS and append registration
116
- module_js, mod = self._compile_to_js(module_path)
117
- manifest = mod.gen.client_manifest if mod else None
118
-
119
- # Extract exports from manifest
120
- exports_list = self._extract_client_exports(manifest)
121
- collected_exports.update(exports_list)
122
-
123
- # Build globals map using manifest.globals_values only for non-root
124
- non_root_globals: dict[str, Any] = {}
125
- if manifest:
126
- for name in manifest.globals:
127
- non_root_globals[name] = manifest.globals_values.get(name)
128
- collected_globals.update(non_root_globals)
129
-
130
- exposure_js = self._generate_global_exposure_code(exports_list)
131
- registration_js = self._generate_registration_js(
132
- module_path.stem,
133
- exports_list,
134
- non_root_globals,
135
- )
136
- export_block = (
137
- f"export {{ {', '.join(exports_list)} }};\n" if exports_list else ""
138
- )
139
108
 
140
- combined_js = f"{module_js}\n{exposure_js}\n{registration_js}\n{runtime_js}\n{export_block}"
141
- if self.vite_package_json is not None:
142
- (
143
- self.vite_package_json.parent / "temp" / f"{module_path.stem}.js"
144
- ).write_text(combined_js, encoding="utf-8")
145
- else:
146
- mod = Jac.program.mod.hub.get(str(module_path))
147
- manifest = mod.gen.client_manifest if mod else None
109
+ # Compile current module to JS and append registration
110
+ module_js, mod = self._compile_to_js(module_path)
111
+ manifest = mod.gen.client_manifest if mod else None
112
+
113
+ # Extract exports from manifest
114
+ exports_list = self._extract_client_exports(manifest)
115
+ collected_exports.update(exports_list)
116
+
117
+ # Build globals map using manifest.globals_values only for non-root
118
+ non_root_globals: dict[str, Any] = {}
119
+ if manifest:
120
+ for name in manifest.globals:
121
+ non_root_globals[name] = manifest.globals_values.get(name)
122
+ collected_globals.update(non_root_globals)
123
+ export_block = (
124
+ f"export {{ {', '.join(exports_list)} }};\n" if exports_list else ""
125
+ )
126
+
127
+ # inport jacJsx from client_runtime_utils.jac
128
+ jac_jsx_path = 'import {__jacJsx, __jacSpawn} from "@jac-client/utils";'
129
+
130
+ combined_js = f"{jac_jsx_path}\n{module_js}\n{export_block}"
131
+ if self.vite_package_json is not None:
132
+ (
133
+ self.vite_package_json.parent / "src" / f"{module_path.stem}.js"
134
+ ).write_text(combined_js, encoding="utf-8")
148
135
 
149
136
  if not manifest or not manifest.imports:
150
137
  return
@@ -159,22 +146,24 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
159
146
  self._compile_dependencies_recursively(
160
147
  path_obj,
161
148
  visited,
162
- is_root=False,
163
149
  collected_exports=collected_exports,
164
150
  collected_globals=collected_globals,
165
- runtime_js=runtime_js,
166
151
  )
167
152
  elif path_obj.suffix == ".js":
168
153
  try:
169
154
  js_code = path_obj.read_text(encoding="utf-8")
170
155
  if self.vite_package_json is not None:
171
156
  (
172
- self.vite_package_json.parent / "temp" / path_obj.name
157
+ self.vite_package_json.parent / "src" / path_obj.name
173
158
  ).write_text(js_code, encoding="utf-8")
174
159
  except FileNotFoundError:
175
160
  pass
176
161
  else:
177
162
  # Bare specifiers or other assets handled by Vite
163
+ if self.vite_package_json is not None and path_obj.is_file():
164
+ (self.vite_package_json.parent / "src" / path_obj.name).write_text(
165
+ path_obj.read_text(encoding="utf-8"), encoding="utf-8"
166
+ )
178
167
  continue
179
168
 
180
169
  def _compile_bundle(
@@ -183,62 +172,82 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
183
172
  module_path: Path,
184
173
  ) -> ClientBundle:
185
174
  """Override to use Vite bundling instead of simple concatenation."""
186
- # Get manifest from JacProgram first to check for imports
187
- from jaclang.runtimelib.machine import JacMachine as Jac
188
175
 
189
- mod = Jac.program.mod.hub.get(str(module_path))
190
- manifest = mod.gen.client_manifest if mod else None
176
+ # Check if package.json exists before proceeding
177
+ if not self.vite_package_json or not self.vite_package_json.exists():
178
+ raise ClientBundleError(
179
+ "Vite package.json not found. Set vite_package_json when using ViteClientBundleBuilder"
180
+ )
191
181
 
192
- module_js, _ = self._compile_to_js(module_path)
182
+ # client_runtime for jac client utils
183
+ runtime_utils_path = self.runtime_path.parent / "client_runtime.jac"
184
+ runtimeutils_js, mod = self._compile_to_js(runtime_utils_path)
185
+ runtimeutils_manifest = mod.gen.client_manifest if mod else None
186
+ runtimeutils_exports_list = self._extract_client_exports(runtimeutils_manifest)
187
+
188
+ # Add React Router exports that are variable declarations (not functions)
189
+ # These need to be manually added since they're 'let' declarations, not 'def' functions
190
+ router_exports = [
191
+ "Router",
192
+ "Routes",
193
+ "Route",
194
+ "Link",
195
+ "Navigate",
196
+ "useNavigate",
197
+ "useLocation",
198
+ "useParams",
199
+ ]
200
+
201
+ # Combine manifest exports with router exports
202
+ all_exports = sorted(set(runtimeutils_exports_list + router_exports))
203
+
204
+ export_block = (
205
+ f"export {{ {', '.join(all_exports)} }};\n" if all_exports else ""
206
+ )
193
207
 
194
- # Compile runtime to JS and add to temp for Vite to consume
195
- runtime_js, mod = self._compile_to_js(self.runtime_path)
208
+ combined_runtime_utils_js = f"{runtimeutils_js}\n{export_block}"
209
+ (self.vite_package_json.parent / "src" / "client_runtime.js").write_text(
210
+ combined_runtime_utils_js, encoding="utf-8"
211
+ )
196
212
 
213
+ # Get manifest from JacProgram first to check for imports
197
214
  # Collect exports/globals across root and recursive deps
198
- collected_exports: set[str] = set(self._extract_client_exports(manifest))
199
- client_globals_map = self._extract_client_globals(manifest, module)
215
+ module_js, mod = self._compile_to_js(module_path)
216
+ module_manifest = mod.gen.client_manifest if mod else None
217
+ collected_exports: set[str] = set(self._extract_client_exports(module_manifest))
218
+ client_globals_map = self._extract_client_globals(module_manifest, module)
200
219
  collected_globals: dict[str, Any] = dict(client_globals_map)
201
220
 
202
221
  # Recursively prepare dependencies and accumulate symbols
203
222
  self._compile_dependencies_recursively(
204
223
  module_path,
205
- is_root=True,
206
224
  collected_exports=collected_exports,
207
225
  collected_globals=collected_globals,
208
- runtime_js=runtime_js,
209
226
  )
210
227
 
228
+ # Copy assets from root assets/ folder to src/assets/ for @jac-client/assets alias
229
+ project_dir = self.vite_package_json.parent
230
+ root_assets_dir = project_dir / "assets"
231
+ src_assets_dir = project_dir / "src" / "assets"
232
+ if root_assets_dir.exists() and root_assets_dir.is_dir():
233
+ self._copy_asset_files(root_assets_dir, src_assets_dir)
234
+
211
235
  client_exports = sorted(collected_exports)
212
236
  client_globals_map = collected_globals
213
237
 
214
- bundle_pieces = []
215
-
216
- # Add main module (without registration_js - we'll handle that in Jac init script)
217
- bundle_pieces.extend(
218
- [
219
- "// Runtime module:",
220
- runtime_js,
221
- f"// Client module: {module.__name__}",
222
- module_js,
223
- "",
224
- ]
225
- )
226
-
227
- # Add global exposure code first (before Jac initialization)
228
- global_exposure_code = self._generate_global_exposure_code(client_exports)
229
- bundle_pieces.append(global_exposure_code)
238
+ entry_file = self.vite_package_json.parent / "src" / "main.js"
230
239
 
231
- # Add Jac runtime initialization script (includes globals)
232
- jac_init_script = self._generate_jac_init_script(
233
- module_path.stem, client_exports, client_globals_map
234
- )
235
- bundle_pieces.append(jac_init_script)
240
+ entry_content = """import React from "react";
241
+ import { createRoot } from "react-dom/client";
242
+ import { app as App } from "./app.js";
236
243
 
237
- # Do not add export block for root since output is iife
244
+ const root = createRoot(document.getElementById("root"));
245
+ root.render(<App />);
246
+ """
247
+ entry_file.write_text(entry_content, encoding="utf-8")
238
248
 
239
- # Use Vite bundling instead of simple concatenation
240
249
  bundle_code, bundle_hash = self._bundle_with_vite(
241
- bundle_pieces, module.__name__, client_exports
250
+ module.__name__, client_exports
242
251
  )
243
252
 
244
253
  return ClientBundle(
@@ -250,12 +259,11 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
250
259
  )
251
260
 
252
261
  def _bundle_with_vite(
253
- self, bundle_pieces: list[str], module_name: str, client_functions: list[str]
262
+ self, module_name: str, client_functions: list[str]
254
263
  ) -> tuple[str, str]:
255
264
  """Bundle JavaScript code using Vite for optimization.
256
265
 
257
266
  Args:
258
- bundle_pieces: List of JavaScript code pieces to bundle
259
267
  module_name: Name of the module being bundled
260
268
  client_functions: List of client function names
261
269
 
@@ -272,27 +280,25 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
272
280
 
273
281
  # Create temp directory for Vite build
274
282
  project_dir = self.vite_package_json.parent
275
- temp_dir = project_dir / "temp"
276
- temp_dir.mkdir(exist_ok=True)
283
+ src_dir = project_dir / "src"
284
+ src_dir.mkdir(exist_ok=True)
277
285
 
278
- # Create entry file with stitched content
279
- entry_file = temp_dir / "app.js"
280
-
281
- entry_content = "\n".join(piece for piece in bundle_pieces if piece is not None)
282
- entry_file.write_text(entry_content, encoding="utf-8")
283
-
284
- # Create Vite config in the project directory (where node_modules exists)
285
- vite_config = project_dir / "temp_vite.config.js"
286
- output_dir = self.vite_output_dir or temp_dir / "dist"
287
- output_dir.mkdir(exist_ok=True)
288
-
289
- config_content = self._generate_vite_config(entry_file, output_dir)
290
- vite_config.write_text(config_content, encoding="utf-8")
286
+ output_dir = self.vite_output_dir or src_dir / "dist" / "assets"
287
+ output_dir.mkdir(parents=True, exist_ok=True)
291
288
 
292
289
  try:
293
290
  # Run Vite build from project directory
294
291
  # need to install packages you told in package.json inside here
295
- command = ["npx", "vite", "build", "--config", str(vite_config)]
292
+ # first compile the code
293
+ command = ["npm", "run", "compile"]
294
+ subprocess.run(
295
+ command, cwd=project_dir, check=True, capture_output=True, text=True
296
+ )
297
+ # Copy CSS and other asset files from src/ to build/ after Babel compilation
298
+ # Babel only transpiles JS, so we need to manually copy assets
299
+ self._copy_asset_files(project_dir / "src", project_dir / "build")
300
+ # then build the code
301
+ command = ["npm", "run", "build"]
296
302
  subprocess.run(
297
303
  command, cwd=project_dir, check=True, capture_output=True, text=True
298
304
  )
@@ -302,11 +308,6 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
302
308
  raise ClientBundleError(
303
309
  "npx or vite command not found. Ensure Node.js and npm are installed."
304
310
  )
305
- finally:
306
- # Clean up temp config file
307
- if vite_config.exists():
308
- vite_config.unlink()
309
-
310
311
  # Find the generated bundle file
311
312
  bundle_file = self._find_vite_bundle(output_dir)
312
313
  if not bundle_file:
@@ -349,122 +350,101 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
349
350
  }});
350
351
  """
351
352
 
353
+ def _copy_asset_files(self, src_dir: Path, build_dir: Path) -> None:
354
+ """Copy CSS and other asset files from src/ to build/ directory recursively.
355
+
356
+ Babel only transpiles JavaScript files, so CSS and other assets need to be
357
+ manually copied to the build directory for Vite to resolve them.
358
+ This method recursively copies assets from subdirectories (e.g., src/assets/)
359
+ while preserving the directory structure.
360
+ """
361
+ if not src_dir.exists():
362
+ return
363
+
364
+ # Ensure build directory exists
365
+ build_dir.mkdir(parents=True, exist_ok=True)
366
+
367
+ # Asset file extensions to copy
368
+ asset_extensions = {
369
+ ".css",
370
+ ".scss",
371
+ ".sass",
372
+ ".less",
373
+ ".svg",
374
+ ".png",
375
+ ".jpg",
376
+ ".jpeg",
377
+ ".gif",
378
+ ".webp",
379
+ ".ico",
380
+ ".woff",
381
+ ".woff2",
382
+ ".ttf",
383
+ ".eot",
384
+ ".otf",
385
+ ".mp4",
386
+ ".webm",
387
+ ".mp3",
388
+ ".wav",
389
+ }
390
+
391
+ def copy_recursive(
392
+ source: Path, destination: Path, base: Path | None = None
393
+ ) -> None:
394
+ """Recursively copy asset files from source to destination.
395
+
396
+ Args:
397
+ source: Source directory to copy from
398
+ destination: Destination directory to copy to
399
+ base: Base directory for calculating relative paths (defaults to source)
400
+ """
401
+ if not source.exists():
402
+ return
403
+
404
+ if base is None:
405
+ base = source
406
+
407
+ for item in source.iterdir():
408
+ if item.is_file() and item.suffix.lower() in asset_extensions:
409
+ # Preserve relative path structure from base
410
+ relative_path = item.relative_to(base)
411
+ dest_file = destination / relative_path
412
+ dest_file.parent.mkdir(parents=True, exist_ok=True)
413
+ with contextlib.suppress(OSError, shutil.Error):
414
+ shutil.copy2(item, dest_file)
415
+ elif item.is_dir():
416
+ # Recursively process subdirectories
417
+ copy_recursive(item, destination, base)
418
+
419
+ # Copy files from src_dir root and recursively from subdirectories
420
+ copy_recursive(src_dir, build_dir)
421
+
352
422
  def _find_vite_bundle(self, output_dir: Path) -> Path | None:
353
423
  """Find the generated Vite bundle file."""
354
424
  for file in output_dir.glob("client.*.js"):
355
425
  return file
356
426
  return None
357
427
 
358
- def _generate_jac_init_script(
359
- self,
360
- module_name: str,
361
- client_functions: list[str],
362
- client_globals: dict[str, Any],
363
- ) -> str:
364
- """Generate Jac runtime initialization script."""
365
- if not client_functions:
366
- return ""
367
-
368
- # Generate function map dynamically
369
- map_entries = []
370
- for func_name in client_functions:
371
- map_entries.append(f' "{func_name}": {func_name}')
372
- function_map_str = "{\n" + ",\n".join(map_entries) + "\n}"
373
-
374
- # Generate globals map
375
- globals_entries = []
376
- for name, value in client_globals.items():
377
- identifier = json.dumps(name)
378
- try:
379
- value_literal = json.dumps(value)
380
- except TypeError:
381
- value_literal = "null"
382
- globals_entries.append(f"{identifier}: {value_literal}")
383
- globals_literal = (
384
- "{ " + ", ".join(globals_entries) + " }" if globals_entries else "{}"
385
- )
386
-
387
- # Find the main app function (usually the last function or one ending with '_app')
388
- main_app_func = (
389
- "jac_app" # this need to be always same and defined by our run time
390
- )
391
- # for func_name in reversed(client_functions):
392
- # if func_name.endswith('_app') or func_name == 'App':
393
- # main_app_func = func_name
394
- # break
395
-
396
- return f"""
397
- // --- JAC CLIENT INITIALIZATION SCRIPT ---
398
- // Expose functions globally for Jac runtime registration
399
- const clientFunctions = {client_functions};
400
- const functionMap = {function_map_str};
401
- for (const funcName of clientFunctions) {{
402
- globalThis[funcName] = functionMap[funcName];
403
- }}
404
- __jacRegisterClientModule("{module_name}", clientFunctions, {globals_literal});
405
- globalThis.start_app = {main_app_func};
406
- // Call the start function immediately if we're not hydrating from the server
407
- if (!document.getElementById('__jac_init__')) {{
408
- globalThis.start_app();
409
- }}
410
- // --- END JAC CLIENT INITIALIZATION SCRIPT ---
411
- """
412
-
413
- def _generate_global_exposure_code(self, client_functions: list[str]) -> str:
414
- """Generate code to expose functions globally for Vite IIFE."""
415
- if not client_functions:
416
- return ""
417
-
418
- # Generate function map dynamically
419
- map_entries = []
420
- for func_name in client_functions:
421
- map_entries.append(f' "{func_name}": {func_name}')
422
- function_map_str = "{\n" + ",\n".join(map_entries) + "\n}"
423
-
424
- return f"""
425
- // --- GLOBAL EXPOSURE FOR VITE IIFE ---
426
- // Expose functions globally so they're available on globalThis
427
- const globalClientFunctions = {client_functions};
428
- const globalFunctionMap = {function_map_str};
429
- for (const funcName of globalClientFunctions) {{
430
- globalThis[funcName] = globalFunctionMap[funcName];
431
- }}
432
- // --- END GLOBAL EXPOSURE ---
433
- """
428
+ def _find_vite_css(self, output_dir: Path) -> Path | None:
429
+ """Find the generated Vite CSS file."""
430
+ # Vite typically outputs CSS as main.css or with a hash
431
+ # Try main.css first (most common), then any .css file
432
+ css_file = output_dir / "main.css"
433
+ if css_file.exists():
434
+ return css_file
435
+ # Fallback: find any CSS file
436
+ for file in output_dir.glob("*.css"):
437
+ return file
438
+ return None
434
439
 
435
440
  def cleanup_temp_dir(self) -> None:
436
- """Clean up the temp directory and its contents."""
441
+ """Clean up the src directory and its contents."""
437
442
  if not self.vite_package_json or not self.vite_package_json.exists():
438
443
  return
439
444
 
440
445
  project_dir = self.vite_package_json.parent
441
- temp_dir = project_dir / "temp"
446
+ temp_dir = project_dir / "src"
442
447
 
443
448
  if temp_dir.exists():
444
- with contextlib.suppress(OSError):
449
+ with contextlib.suppress(OSError, shutil.Error):
445
450
  shutil.rmtree(temp_dir)
446
-
447
- @staticmethod
448
- def _generate_registration_js(
449
- module_name: str,
450
- client_functions: Sequence[str],
451
- client_globals: dict[str, Any],
452
- ) -> str:
453
- """Generate registration code that exposes client symbols globally."""
454
- globals_entries: list[str] = []
455
- for name, value in client_globals.items():
456
- identifier = json.dumps(name)
457
- try:
458
- value_literal = json.dumps(value)
459
- except TypeError:
460
- value_literal = "null"
461
- globals_entries.append(f"{identifier}: {value_literal}")
462
-
463
- globals_literal = (
464
- "{ " + ", ".join(globals_entries) + " }" if globals_entries else "{}"
465
- )
466
- functions_literal = json.dumps(list(client_functions))
467
- module_literal = json.dumps(module_name)
468
-
469
- # Use the registration function from client_runtime.jac
470
- return f"__jacRegisterClientModule({module_literal}, {functions_literal}, {globals_literal});"
@@ -1,2 +1 @@
1
1
  """Tests for jac-client package."""
2
-
@@ -7,7 +7,7 @@ cl obj ButtonProps {
7
7
  has color: str = "primary";
8
8
  }
9
9
 
10
- cl def client_page() {
10
+ cl def app() {
11
11
  let props = ButtonProps(label="Tap Me", color="primary");
12
12
  return <div class="app">
13
13
  <h1>{API_LABEL}</h1>
@@ -0,0 +1,38 @@
1
+ import from react {useState}
2
+
3
+
4
+ def app() -> any {
5
+ let [todos, setTodos] = useState([]);
6
+ let [input, setInput] = useState("");
7
+
8
+ # Event Handler
9
+ async def addTodo() -> None {
10
+ if not input.trim() { return; }
11
+ response = root spawn create_todo(text=input.trim());
12
+ new_todo = response.reports[0][0];
13
+ setTodos(todos.concat([new_todo]));
14
+ setInput("");
15
+
16
+ def foo{}
17
+ }
18
+
19
+ return <div>
20
+ <h2>My Todos</h2>
21
+ <input
22
+ value={input}
23
+ onChange={lambda e: any -> None { setInput(e.target.value); }}
24
+ onKeyPress={lambda e: any -> None {
25
+ if e.key == "Enter" { addTodo(); }
26
+ }}
27
+ />
28
+ <button onClick={addTodo}>Add Todo</button>
29
+
30
+ <div>
31
+ {todos.map(lambda todo: any -> any {
32
+ return <div key={todo._jac_id}>
33
+ <span>{todo.text}</span>
34
+ </div>;
35
+ })}
36
+ </div>
37
+ </div>;
38
+ }
@@ -0,0 +1,15 @@
1
+
2
+ '''Test file for .cl file serves the client module.'''
3
+
4
+ node Todo {
5
+ has text: str;
6
+ has done: bool = False;
7
+ }
8
+
9
+ walker create_todo {
10
+ has text: str;
11
+ can create with `root entry {
12
+ new_todo = here ++> Todo(text=self.text);
13
+ report new_todo;
14
+ }
15
+ }
@@ -19,3 +19,10 @@ cl def CardTest() {
19
19
  </div>;
20
20
  }
21
21
 
22
+ cl def app() {
23
+ return <div>
24
+ <ButtonTest />
25
+ <CardTest />
26
+ </div>;
27
+ }
28
+
@@ -14,7 +14,7 @@ cl def JsImportTest() -> any {
14
14
  let sum = calculateSum(5, 3);
15
15
  let formatter = MessageFormatter("JS");
16
16
  let formatted = formatter.format("Hello from JS class");
17
-
17
+
18
18
  return <div class="js-import-test">
19
19
  <h1>{JS_IMPORT_LABEL}</h1>
20
20
  <p>Greeting: {greeting}</p>
@@ -24,7 +24,7 @@ cl def JsImportTest() -> any {
24
24
  </div>;
25
25
  }
26
26
 
27
- cl def Main() -> any {
27
+ cl def app() -> any {
28
28
  return <JsImportTest />;
29
29
  }
30
30
 
@@ -8,6 +8,6 @@ cl def RelativeImport() -> any {
8
8
  </div>;
9
9
  }
10
10
 
11
- cl def main() -> any {
11
+ cl def app() -> any {
12
12
  return <RelativeImport />;
13
13
  }
@@ -1,6 +1,6 @@
1
1
  cl import from antd {
2
2
  Button
3
- }
4
- cl def CustomButton() -> any {
3
+ }
4
+ cl def CustomButton() -> any {
5
5
  return <Button>Click Me</Button>;
6
6
  }