jac-client 0.1.0__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.
- jac_client/docs/README.md +629 -0
- jac_client/docs/advanced-state.md +706 -0
- jac_client/docs/imports.md +650 -0
- jac_client/docs/lifecycle-hooks.md +554 -0
- jac_client/docs/routing.md +530 -0
- jac_client/examples/little-x/app.jac +615 -0
- jac_client/examples/little-x/package-lock.json +2840 -0
- jac_client/examples/little-x/package.json +23 -0
- jac_client/examples/little-x/submit-button.jac +8 -0
- jac_client/examples/todo-app/README.md +82 -0
- jac_client/examples/todo-app/app.jac +683 -0
- jac_client/examples/todo-app/package-lock.json +999 -0
- jac_client/examples/todo-app/package.json +22 -0
- jac_client/plugin/cli.py +328 -0
- jac_client/plugin/client.py +41 -0
- jac_client/plugin/client_runtime.jac +941 -0
- jac_client/plugin/vite_client_bundle.py +470 -0
- jac_client/tests/__init__.py +2 -0
- jac_client/tests/fixtures/button.jac +6 -0
- jac_client/tests/fixtures/client_app.jac +18 -0
- jac_client/tests/fixtures/client_app_with_antd.jac +21 -0
- jac_client/tests/fixtures/js_import.jac +30 -0
- jac_client/tests/fixtures/package-lock.json +329 -0
- jac_client/tests/fixtures/package.json +11 -0
- jac_client/tests/fixtures/relative_import.jac +13 -0
- jac_client/tests/fixtures/test_fragments_spread.jac +44 -0
- jac_client/tests/fixtures/utils.js +22 -0
- jac_client/tests/test_cl.py +360 -0
- jac_client/tests/test_create_jac_app.py +139 -0
- jac_client-0.1.0.dist-info/METADATA +126 -0
- jac_client-0.1.0.dist-info/RECORD +33 -0
- jac_client-0.1.0.dist-info/WHEEL +4 -0
- jac_client-0.1.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
"""Vite-enhanced client bundle generation for Jac web front-ends."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
from typing import Any, Sequence, TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from jaclang.runtimelib.client_bundle import (
|
|
15
|
+
ClientBundle,
|
|
16
|
+
ClientBundleBuilder,
|
|
17
|
+
ClientBundleError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from jaclang.compiler.codeinfo import ClientManifest
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
25
|
+
"""Enhanced ClientBundleBuilder that uses Vite for optimized bundling."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
runtime_path: Path | None = None,
|
|
30
|
+
vite_output_dir: Path | None = None,
|
|
31
|
+
vite_package_json: Path | None = None,
|
|
32
|
+
vite_minify: bool = False,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Initialize the Vite-enhanced bundle builder.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
runtime_path: Path to client runtime file
|
|
38
|
+
vite_output_dir: Output directory for Vite builds (defaults to temp/dist)
|
|
39
|
+
vite_package_json: Path to package.json for Vite (required)
|
|
40
|
+
vite_minify: Whether to enable minification in Vite build
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(runtime_path)
|
|
43
|
+
self.vite_output_dir = vite_output_dir
|
|
44
|
+
self.vite_package_json = vite_package_json
|
|
45
|
+
self.vite_minify = vite_minify
|
|
46
|
+
|
|
47
|
+
def _process_imports(
|
|
48
|
+
self, manifest: ClientManifest | None, module_path: Path
|
|
49
|
+
) -> list[Path | None]: # type: ignore[override]
|
|
50
|
+
"""Process client imports for Vite bundling.
|
|
51
|
+
|
|
52
|
+
Only mark modules as bundled when we actually inline their code (.jac files we compile
|
|
53
|
+
and local .js files we embed). Bare package specifiers (e.g., "antd") are left as real
|
|
54
|
+
ES imports so Vite can resolve and bundle them.
|
|
55
|
+
"""
|
|
56
|
+
# TODO: return pure js files separately
|
|
57
|
+
imported_js_modules: list[Path | None] = []
|
|
58
|
+
|
|
59
|
+
if manifest and manifest.imports:
|
|
60
|
+
for _, import_path in manifest.imports.items():
|
|
61
|
+
import_path_obj = Path(import_path)
|
|
62
|
+
|
|
63
|
+
if import_path_obj.suffix == ".js":
|
|
64
|
+
# Inline local JS files and mark as bundled
|
|
65
|
+
try:
|
|
66
|
+
|
|
67
|
+
imported_js_modules.append(import_path_obj)
|
|
68
|
+
except FileNotFoundError:
|
|
69
|
+
imported_js_modules.append(None)
|
|
70
|
+
|
|
71
|
+
elif import_path_obj.suffix == ".jac":
|
|
72
|
+
# Compile .jac imports and include transitive .jac imports
|
|
73
|
+
try:
|
|
74
|
+
imported_js_modules.append(import_path_obj)
|
|
75
|
+
except ClientBundleError:
|
|
76
|
+
imported_js_modules.append(None)
|
|
77
|
+
|
|
78
|
+
else:
|
|
79
|
+
# Non .jac/.js entries (likely bare specifiers) should be handled by Vite.
|
|
80
|
+
# Do not inline or mark as bundled so their import lines are preserved.
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
return imported_js_modules
|
|
84
|
+
|
|
85
|
+
def _compile_dependencies_recursively(
|
|
86
|
+
self,
|
|
87
|
+
module_path: Path,
|
|
88
|
+
visited: set[Path] | None = None,
|
|
89
|
+
is_root: bool = False,
|
|
90
|
+
collected_exports: set[str] | None = None,
|
|
91
|
+
collected_globals: dict[str, Any] | None = None,
|
|
92
|
+
runtime_js: str | None = None,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Recursively compile/copy .jac/.js imports to temp, skipping bundling.
|
|
95
|
+
|
|
96
|
+
Only prepares dependency JS artifacts for Vite by writing compiled JS (.jac)
|
|
97
|
+
or copying local JS (.js) into the temp directory. Bare specifiers are left
|
|
98
|
+
untouched for Vite to resolve.
|
|
99
|
+
"""
|
|
100
|
+
from jaclang.runtimelib.machine import JacMachine as Jac
|
|
101
|
+
|
|
102
|
+
if visited is None:
|
|
103
|
+
visited = set()
|
|
104
|
+
if collected_exports is None:
|
|
105
|
+
collected_exports = set()
|
|
106
|
+
if collected_globals is None:
|
|
107
|
+
collected_globals = {}
|
|
108
|
+
|
|
109
|
+
module_path = module_path.resolve()
|
|
110
|
+
if module_path in visited:
|
|
111
|
+
return
|
|
112
|
+
visited.add(module_path)
|
|
113
|
+
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
|
+
|
|
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
|
|
148
|
+
|
|
149
|
+
if not manifest or not manifest.imports:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
for _name, import_path in manifest.imports.items():
|
|
153
|
+
path_obj = Path(import_path).resolve()
|
|
154
|
+
# Avoid re-processing
|
|
155
|
+
if path_obj in visited:
|
|
156
|
+
continue
|
|
157
|
+
if path_obj.suffix == ".jac":
|
|
158
|
+
# Recurse into transitive deps
|
|
159
|
+
self._compile_dependencies_recursively(
|
|
160
|
+
path_obj,
|
|
161
|
+
visited,
|
|
162
|
+
is_root=False,
|
|
163
|
+
collected_exports=collected_exports,
|
|
164
|
+
collected_globals=collected_globals,
|
|
165
|
+
runtime_js=runtime_js,
|
|
166
|
+
)
|
|
167
|
+
elif path_obj.suffix == ".js":
|
|
168
|
+
try:
|
|
169
|
+
js_code = path_obj.read_text(encoding="utf-8")
|
|
170
|
+
if self.vite_package_json is not None:
|
|
171
|
+
(
|
|
172
|
+
self.vite_package_json.parent / "temp" / path_obj.name
|
|
173
|
+
).write_text(js_code, encoding="utf-8")
|
|
174
|
+
except FileNotFoundError:
|
|
175
|
+
pass
|
|
176
|
+
else:
|
|
177
|
+
# Bare specifiers or other assets handled by Vite
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
def _compile_bundle(
|
|
181
|
+
self,
|
|
182
|
+
module: ModuleType,
|
|
183
|
+
module_path: Path,
|
|
184
|
+
) -> ClientBundle:
|
|
185
|
+
"""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
|
+
|
|
189
|
+
mod = Jac.program.mod.hub.get(str(module_path))
|
|
190
|
+
manifest = mod.gen.client_manifest if mod else None
|
|
191
|
+
|
|
192
|
+
module_js, _ = self._compile_to_js(module_path)
|
|
193
|
+
|
|
194
|
+
# Compile runtime to JS and add to temp for Vite to consume
|
|
195
|
+
runtime_js, mod = self._compile_to_js(self.runtime_path)
|
|
196
|
+
|
|
197
|
+
# 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)
|
|
200
|
+
collected_globals: dict[str, Any] = dict(client_globals_map)
|
|
201
|
+
|
|
202
|
+
# Recursively prepare dependencies and accumulate symbols
|
|
203
|
+
self._compile_dependencies_recursively(
|
|
204
|
+
module_path,
|
|
205
|
+
is_root=True,
|
|
206
|
+
collected_exports=collected_exports,
|
|
207
|
+
collected_globals=collected_globals,
|
|
208
|
+
runtime_js=runtime_js,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
client_exports = sorted(collected_exports)
|
|
212
|
+
client_globals_map = collected_globals
|
|
213
|
+
|
|
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)
|
|
230
|
+
|
|
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)
|
|
236
|
+
|
|
237
|
+
# Do not add export block for root since output is iife
|
|
238
|
+
|
|
239
|
+
# Use Vite bundling instead of simple concatenation
|
|
240
|
+
bundle_code, bundle_hash = self._bundle_with_vite(
|
|
241
|
+
bundle_pieces, module.__name__, client_exports
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return ClientBundle(
|
|
245
|
+
module_name=module.__name__,
|
|
246
|
+
code=bundle_code,
|
|
247
|
+
client_functions=client_exports,
|
|
248
|
+
client_globals=list(client_globals_map.keys()),
|
|
249
|
+
hash=bundle_hash,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def _bundle_with_vite(
|
|
253
|
+
self, bundle_pieces: list[str], module_name: str, client_functions: list[str]
|
|
254
|
+
) -> tuple[str, str]:
|
|
255
|
+
"""Bundle JavaScript code using Vite for optimization.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
bundle_pieces: List of JavaScript code pieces to bundle
|
|
259
|
+
module_name: Name of the module being bundled
|
|
260
|
+
client_functions: List of client function names
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Tuple of (bundle_code, bundle_hash)
|
|
264
|
+
|
|
265
|
+
Raises:
|
|
266
|
+
ClientBundleError: If Vite bundling fails
|
|
267
|
+
"""
|
|
268
|
+
if not self.vite_package_json or not self.vite_package_json.exists():
|
|
269
|
+
raise ClientBundleError(
|
|
270
|
+
"Vite package.json not found. Set vite_package_json when using ViteClientBundleBuilder"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Create temp directory for Vite build
|
|
274
|
+
project_dir = self.vite_package_json.parent
|
|
275
|
+
temp_dir = project_dir / "temp"
|
|
276
|
+
temp_dir.mkdir(exist_ok=True)
|
|
277
|
+
|
|
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")
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
# Run Vite build from project directory
|
|
294
|
+
# need to install packages you told in package.json inside here
|
|
295
|
+
command = ["npx", "vite", "build", "--config", str(vite_config)]
|
|
296
|
+
subprocess.run(
|
|
297
|
+
command, cwd=project_dir, check=True, capture_output=True, text=True
|
|
298
|
+
)
|
|
299
|
+
except subprocess.CalledProcessError as e:
|
|
300
|
+
raise ClientBundleError(f"Vite build failed: {e.stderr}") from e
|
|
301
|
+
except FileNotFoundError:
|
|
302
|
+
raise ClientBundleError(
|
|
303
|
+
"npx or vite command not found. Ensure Node.js and npm are installed."
|
|
304
|
+
)
|
|
305
|
+
finally:
|
|
306
|
+
# Clean up temp config file
|
|
307
|
+
if vite_config.exists():
|
|
308
|
+
vite_config.unlink()
|
|
309
|
+
|
|
310
|
+
# Find the generated bundle file
|
|
311
|
+
bundle_file = self._find_vite_bundle(output_dir)
|
|
312
|
+
if not bundle_file:
|
|
313
|
+
raise ClientBundleError("Vite build completed but no bundle file found")
|
|
314
|
+
|
|
315
|
+
# Read the bundled code
|
|
316
|
+
bundle_code = bundle_file.read_text(encoding="utf-8")
|
|
317
|
+
bundle_hash = hashlib.sha256(bundle_code.encode("utf-8")).hexdigest()
|
|
318
|
+
|
|
319
|
+
return bundle_code, bundle_hash
|
|
320
|
+
|
|
321
|
+
def _generate_vite_config(self, entry_file: Path, output_dir: Path) -> str:
|
|
322
|
+
"""Generate Vite configuration for bundling."""
|
|
323
|
+
entry_name = entry_file.as_posix()
|
|
324
|
+
output_dir_name = output_dir.as_posix()
|
|
325
|
+
minify_setting = "true" if self.vite_minify else "false"
|
|
326
|
+
|
|
327
|
+
return f"""
|
|
328
|
+
import {{ defineConfig }} from 'vite';
|
|
329
|
+
import {{ resolve }} from 'path';
|
|
330
|
+
|
|
331
|
+
export default defineConfig({{
|
|
332
|
+
build: {{
|
|
333
|
+
outDir: '{output_dir_name}',
|
|
334
|
+
emptyOutDir: true,
|
|
335
|
+
rollupOptions: {{
|
|
336
|
+
input: {{
|
|
337
|
+
main: resolve(__dirname, '{entry_name}'),
|
|
338
|
+
}},
|
|
339
|
+
output: {{
|
|
340
|
+
entryFileNames: 'client.[hash].js',
|
|
341
|
+
format: 'iife',
|
|
342
|
+
name: 'JacClient',
|
|
343
|
+
}},
|
|
344
|
+
}},
|
|
345
|
+
minify: {minify_setting}, // Configurable minification
|
|
346
|
+
}},
|
|
347
|
+
resolve: {{
|
|
348
|
+
}}
|
|
349
|
+
}});
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
def _find_vite_bundle(self, output_dir: Path) -> Path | None:
|
|
353
|
+
"""Find the generated Vite bundle file."""
|
|
354
|
+
for file in output_dir.glob("client.*.js"):
|
|
355
|
+
return file
|
|
356
|
+
return None
|
|
357
|
+
|
|
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
|
+
"""
|
|
434
|
+
|
|
435
|
+
def cleanup_temp_dir(self) -> None:
|
|
436
|
+
"""Clean up the temp directory and its contents."""
|
|
437
|
+
if not self.vite_package_json or not self.vite_package_json.exists():
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
project_dir = self.vite_package_json.parent
|
|
441
|
+
temp_dir = project_dir / "temp"
|
|
442
|
+
|
|
443
|
+
if temp_dir.exists():
|
|
444
|
+
with contextlib.suppress(OSError):
|
|
445
|
+
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});"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Sample Jac module containing client-side declarations."""
|
|
2
|
+
|
|
3
|
+
cl let API_LABEL: str = "Runtime Test";
|
|
4
|
+
|
|
5
|
+
cl obj ButtonProps {
|
|
6
|
+
has label: str = "Tap Me";
|
|
7
|
+
has color: str = "primary";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
cl def client_page() {
|
|
11
|
+
let props = ButtonProps(label="Tap Me", color="primary");
|
|
12
|
+
return <div class="app">
|
|
13
|
+
<h1>{API_LABEL}</h1>
|
|
14
|
+
<button class={props.color} data-id="button">
|
|
15
|
+
{props.label}
|
|
16
|
+
</button>
|
|
17
|
+
</div>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Sample Jac module using Ant Design components."""
|
|
2
|
+
|
|
3
|
+
cl import from antd {
|
|
4
|
+
Button
|
|
5
|
+
}
|
|
6
|
+
cl let APP_NAME: str = "Ant Design Test";
|
|
7
|
+
|
|
8
|
+
cl def ButtonTest() {
|
|
9
|
+
return <div>
|
|
10
|
+
<h1>{APP_NAME}</h1>
|
|
11
|
+
<p>Testing Ant Design integration</p>
|
|
12
|
+
<Button>Click Me</Button>
|
|
13
|
+
</div>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
cl def CardTest() {
|
|
17
|
+
return <div class="card-wrapper">
|
|
18
|
+
<h2>Card Component</h2>
|
|
19
|
+
</div>;
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Test module that imports JavaScript functions."""
|
|
2
|
+
|
|
3
|
+
cl import from .utils {
|
|
4
|
+
formatMessage,
|
|
5
|
+
calculateSum,
|
|
6
|
+
JS_CONSTANT,
|
|
7
|
+
MessageFormatter
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
cl let JS_IMPORT_LABEL: str = "JavaScript Import Test";
|
|
11
|
+
|
|
12
|
+
cl def JsImportTest() -> any {
|
|
13
|
+
let greeting = formatMessage("Jac");
|
|
14
|
+
let sum = calculateSum(5, 3);
|
|
15
|
+
let formatter = MessageFormatter("JS");
|
|
16
|
+
let formatted = formatter.format("Hello from JS class");
|
|
17
|
+
|
|
18
|
+
return <div class="js-import-test">
|
|
19
|
+
<h1>{JS_IMPORT_LABEL}</h1>
|
|
20
|
+
<p>Greeting: {greeting}</p>
|
|
21
|
+
<p>Sum (5 + 3): {sum}</p>
|
|
22
|
+
<p>Constant: {JS_CONSTANT}</p>
|
|
23
|
+
<p>Formatted: {formatted}</p>
|
|
24
|
+
</div>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
cl def Main() -> any {
|
|
28
|
+
return <JsImportTest />;
|
|
29
|
+
}
|
|
30
|
+
|