jac-client 0.2.0__py3-none-any.whl → 0.2.2__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 +50 -20
- jac_client/docs/advanced-state.md +13 -14
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/file-system/app.jac.md +121 -0
- jac_client/docs/file-system/backend-frontend.md +217 -0
- jac_client/docs/file-system/intro.md +72 -0
- jac_client/docs/file-system/nested-imports.md +348 -0
- jac_client/docs/guide-example/intro.md +11 -13
- jac_client/docs/guide-example/step-01-setup.md +30 -20
- jac_client/docs/guide-example/step-02-components.md +24 -24
- jac_client/docs/guide-example/step-03-styling.md +24 -24
- jac_client/docs/guide-example/step-04-todo-ui.md +17 -17
- jac_client/docs/guide-example/step-05-local-state.md +23 -23
- jac_client/docs/guide-example/step-06-events.md +23 -24
- jac_client/docs/guide-example/step-07-effects.md +27 -28
- jac_client/docs/guide-example/step-08-walkers.md +23 -23
- jac_client/docs/guide-example/step-09-authentication.md +18 -18
- jac_client/docs/guide-example/step-10-routing.md +20 -21
- jac_client/docs/guide-example/step-11-final.md +34 -35
- jac_client/docs/imports.md +4 -5
- jac_client/docs/lifecycle-hooks.md +12 -13
- jac_client/docs/routing.md +21 -22
- jac_client/docs/styling/intro.md +249 -0
- jac_client/docs/styling/js-styling.md +367 -0
- jac_client/docs/styling/material-ui.md +341 -0
- jac_client/docs/styling/pure-css.md +299 -0
- jac_client/docs/styling/sass.md +403 -0
- jac_client/docs/styling/styled-components.md +395 -0
- jac_client/docs/styling/tailwind.md +298 -0
- jac_client/examples/all-in-one/.babelrc +9 -0
- jac_client/examples/all-in-one/README.md +16 -0
- jac_client/examples/all-in-one/app.jac +426 -0
- jac_client/examples/all-in-one/assets/burger.png +0 -0
- jac_client/examples/all-in-one/button.jac +7 -0
- jac_client/examples/all-in-one/components/button.jac +7 -0
- jac_client/examples/all-in-one/package.json +29 -0
- jac_client/examples/all-in-one/styles.css +26 -0
- jac_client/examples/all-in-one/vite.config.js +28 -0
- jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
- jac_client/examples/asset-serving/css-with-image/README.md +91 -0
- jac_client/examples/asset-serving/css-with-image/app.jac +88 -0
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +28 -0
- jac_client/examples/asset-serving/css-with-image/styles.css +26 -0
- jac_client/examples/asset-serving/css-with-image/vite.config.js +28 -0
- jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
- jac_client/examples/asset-serving/image-asset/README.md +119 -0
- jac_client/examples/asset-serving/image-asset/app.jac +55 -0
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +28 -0
- jac_client/examples/asset-serving/image-asset/styles.css +26 -0
- jac_client/examples/asset-serving/image-asset/vite.config.js +28 -0
- jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
- jac_client/examples/asset-serving/import-alias/README.md +83 -0
- jac_client/examples/asset-serving/import-alias/app.jac +111 -0
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +28 -0
- jac_client/examples/asset-serving/import-alias/vite.config.js +28 -0
- jac_client/examples/basic/app.jac +14 -9
- jac_client/examples/basic/package.json +1 -1
- jac_client/examples/basic/vite.config.js +0 -1
- jac_client/examples/basic-auth/package.json +1 -1
- jac_client/examples/basic-auth/vite.config.js +0 -1
- jac_client/examples/basic-auth-with-router/package.json +1 -1
- jac_client/examples/basic-auth-with-router/vite.config.js +0 -1
- jac_client/examples/basic-full-stack/package.json +1 -1
- jac_client/examples/basic-full-stack/vite.config.js +0 -1
- jac_client/examples/css-styling/js-styling/.babelrc +9 -0
- jac_client/examples/css-styling/js-styling/README.md +183 -0
- jac_client/examples/css-styling/js-styling/app.jac +84 -0
- jac_client/examples/css-styling/js-styling/package.json +28 -0
- jac_client/examples/css-styling/js-styling/styles.js +100 -0
- jac_client/examples/css-styling/js-styling/vite.config.js +27 -0
- jac_client/examples/css-styling/material-ui/.babelrc +9 -0
- jac_client/examples/css-styling/material-ui/README.md +16 -0
- jac_client/examples/css-styling/material-ui/app.jac +122 -0
- jac_client/examples/css-styling/material-ui/package.json +32 -0
- jac_client/examples/css-styling/material-ui/vite.config.js +27 -0
- jac_client/examples/css-styling/pure-css/.babelrc +9 -0
- jac_client/examples/css-styling/pure-css/README.md +16 -0
- jac_client/examples/css-styling/pure-css/app.jac +64 -0
- jac_client/examples/css-styling/pure-css/package.json +28 -0
- jac_client/examples/css-styling/pure-css/styles.css +111 -0
- jac_client/examples/css-styling/pure-css/vite.config.js +27 -0
- jac_client/examples/css-styling/sass-example/.babelrc +9 -0
- jac_client/examples/css-styling/sass-example/README.md +16 -0
- jac_client/examples/css-styling/sass-example/app.jac +64 -0
- jac_client/examples/css-styling/sass-example/package.json +29 -0
- jac_client/examples/css-styling/sass-example/styles.scss +153 -0
- jac_client/examples/css-styling/sass-example/vite.config.js +27 -0
- jac_client/examples/css-styling/styled-components/.babelrc +9 -0
- jac_client/examples/css-styling/styled-components/README.md +16 -0
- jac_client/examples/css-styling/styled-components/app.jac +71 -0
- jac_client/examples/css-styling/styled-components/package.json +29 -0
- jac_client/examples/css-styling/styled-components/styled.js +90 -0
- jac_client/examples/css-styling/styled-components/vite.config.js +27 -0
- jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
- jac_client/examples/css-styling/tailwind-example/README.md +16 -0
- jac_client/examples/css-styling/tailwind-example/app.jac +63 -0
- jac_client/examples/css-styling/tailwind-example/global.css +1 -0
- jac_client/examples/css-styling/tailwind-example/package.json +30 -0
- jac_client/examples/css-styling/tailwind-example/vite.config.js +29 -0
- jac_client/examples/full-stack-with-auth/app.jac +20 -33
- jac_client/examples/full-stack-with-auth/package.json +1 -1
- jac_client/examples/full-stack-with-auth/vite.config.js +0 -1
- jac_client/examples/little-x/app.jac +327 -218
- jac_client/examples/little-x/submit-button.jac +1 -1
- jac_client/examples/nested-folders/nested-advance/.babelrc +9 -0
- jac_client/examples/nested-folders/nested-advance/ButtonRoot.jac +11 -0
- jac_client/examples/nested-folders/nested-advance/README.md +77 -0
- jac_client/examples/nested-folders/nested-advance/app.jac +35 -0
- jac_client/examples/nested-folders/nested-advance/level1/ButtonSecondL.jac +19 -0
- jac_client/examples/nested-folders/nested-advance/level1/Card.jac +43 -0
- jac_client/examples/nested-folders/nested-advance/level1/level2/ButtonThirdL.jac +25 -0
- jac_client/examples/nested-folders/nested-advance/package.json +29 -0
- jac_client/examples/nested-folders/nested-advance/vite.config.js +28 -0
- jac_client/examples/nested-folders/nested-basic/.babelrc +9 -0
- jac_client/examples/nested-folders/nested-basic/README.md +183 -0
- jac_client/examples/nested-folders/nested-basic/app.jac +13 -0
- jac_client/examples/nested-folders/nested-basic/app.js +7 -0
- jac_client/examples/nested-folders/nested-basic/button.jac +7 -0
- jac_client/examples/nested-folders/nested-basic/components/button.jac +7 -0
- jac_client/examples/nested-folders/nested-basic/package.json +28 -0
- jac_client/examples/nested-folders/nested-basic/vite.config.js +27 -0
- jac_client/examples/with-router/app.jac +1 -1
- jac_client/examples/with-router/package.json +1 -1
- jac_client/examples/with-router/vite.config.js +0 -1
- jac_client/plugin/cli.py +7 -2
- jac_client/plugin/client.py +68 -5
- jac_client/plugin/client_runtime.jac +1 -1
- jac_client/plugin/vite_client_bundle.py +162 -14
- jac_client/tests/__init__.py +0 -1
- jac_client/tests/fixtures/basic-app/app.jac +7 -2
- jac_client/tests/fixtures/cl_file/app.cl.jac +48 -0
- jac_client/tests/fixtures/cl_file/app.jac +15 -0
- jac_client/tests/fixtures/client_app_with_antd/app.jac +14 -8
- jac_client/tests/fixtures/js_import/app.jac +19 -15
- jac_client/tests/fixtures/js_import/utils.js +0 -1
- jac_client/tests/fixtures/package.json +1 -1
- jac_client/tests/fixtures/relative_import/app.jac +4 -6
- jac_client/tests/fixtures/relative_import/button.jac +7 -6
- jac_client/tests/fixtures/spawn_test/app.jac +1 -5
- jac_client/tests/fixtures/test_fragments_spread/app.jac +24 -10
- jac_client/tests/test_asset_examples.py +322 -0
- jac_client/tests/test_cl.py +480 -426
- jac_client/tests/test_create_jac_app.py +125 -133
- jac_client/tests/test_it.py +329 -0
- jac_client/tests/test_nested_file.py +374 -0
- {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/METADATA +2 -2
- jac_client-0.2.2.dist-info/RECORD +171 -0
- jac_client-0.2.0.dist-info/RECORD +0 -72
- {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/WHEEL +0 -0
- {jac_client-0.2.0.dist-info → jac_client-0.2.2.dist-info}/entry_points.txt +0 -0
jac_client/plugin/client.py
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
|
+
import hashlib
|
|
1
2
|
import html
|
|
3
|
+
import mimetypes
|
|
2
4
|
import types
|
|
5
|
+
from http.server import BaseHTTPRequestHandler
|
|
3
6
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Literal, TypeAlias
|
|
5
8
|
|
|
6
9
|
from jaclang.runtimelib.client_bundle import ClientBundle
|
|
7
|
-
from jaclang.runtimelib.
|
|
8
|
-
|
|
10
|
+
from jaclang.runtimelib.runtime import (
|
|
11
|
+
JacRuntime as Jac,
|
|
12
|
+
)
|
|
13
|
+
from jaclang.runtimelib.runtime import (
|
|
9
14
|
hookimpl,
|
|
10
15
|
)
|
|
11
16
|
from jaclang.runtimelib.server import ModuleIntrospector
|
|
12
17
|
|
|
13
18
|
from .vite_client_bundle import ViteClientBundleBuilder
|
|
14
19
|
|
|
20
|
+
JsonValue: TypeAlias = (
|
|
21
|
+
None | str | int | float | bool | list["JsonValue"] | dict[str, "JsonValue"]
|
|
22
|
+
)
|
|
23
|
+
StatusCode: TypeAlias = Literal[200, 201, 400, 401, 404, 503]
|
|
24
|
+
|
|
15
25
|
|
|
16
26
|
class JacClientModuleIntrospector(ModuleIntrospector):
|
|
17
27
|
"""Jac Client Module Introspector."""
|
|
@@ -30,12 +40,28 @@ class JacClientModuleIntrospector(ModuleIntrospector):
|
|
|
30
40
|
|
|
31
41
|
bundle_hash = self.ensure_bundle()
|
|
32
42
|
|
|
43
|
+
# Find CSS file in dist directory
|
|
44
|
+
base_path = Path(Jac.base_path_dir)
|
|
45
|
+
dist_dir = base_path / "dist"
|
|
46
|
+
css_link = ""
|
|
47
|
+
|
|
48
|
+
# Try to find CSS file (main.css is the default Vite output)
|
|
49
|
+
css_file = dist_dir / "main.css"
|
|
50
|
+
if css_file.exists():
|
|
51
|
+
css_hash = hashlib.sha256(css_file.read_bytes()).hexdigest()[:8]
|
|
52
|
+
css_link = (
|
|
53
|
+
f'<link rel="stylesheet" href="/static/main.css?hash={css_hash}"/>'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
head_content = f'<meta charset="utf-8"/>\n <title>{html.escape(function_name)}</title>'
|
|
57
|
+
if css_link:
|
|
58
|
+
head_content += f"\n {css_link}"
|
|
59
|
+
|
|
33
60
|
page = (
|
|
34
61
|
"<!DOCTYPE html>"
|
|
35
62
|
'<html lang="en">'
|
|
36
63
|
"<head>"
|
|
37
|
-
|
|
38
|
-
f"<title>{html.escape(function_name)}</title>"
|
|
64
|
+
f"{head_content}"
|
|
39
65
|
"</head>"
|
|
40
66
|
"<body>"
|
|
41
67
|
'<div id="root"></div>'
|
|
@@ -87,3 +113,40 @@ class JacClient:
|
|
|
87
113
|
) -> ModuleIntrospector:
|
|
88
114
|
"""Get a module introspector for the supplied module."""
|
|
89
115
|
return JacClientModuleIntrospector(module_name, base_path)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
@hookimpl
|
|
119
|
+
def send_static_file(
|
|
120
|
+
handler: BaseHTTPRequestHandler,
|
|
121
|
+
file_path: Path,
|
|
122
|
+
content_type: str | None = None,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Send static file response (images, fonts, etc.).
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
handler: HTTP request handler
|
|
128
|
+
file_path: Path to the file to serve
|
|
129
|
+
content_type: MIME type (auto-detected if None)
|
|
130
|
+
"""
|
|
131
|
+
from jaclang.runtimelib.server import ResponseBuilder
|
|
132
|
+
|
|
133
|
+
if not file_path.exists() or not file_path.is_file():
|
|
134
|
+
ResponseBuilder.send_json(handler, 404, {"error": "File not found"})
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
file_content = file_path.read_bytes()
|
|
139
|
+
if content_type is None:
|
|
140
|
+
content_type, _ = mimetypes.guess_type(str(file_path))
|
|
141
|
+
if content_type is None:
|
|
142
|
+
content_type = "application/octet-stream"
|
|
143
|
+
|
|
144
|
+
handler.send_response(200)
|
|
145
|
+
handler.send_header("Content-Type", content_type)
|
|
146
|
+
handler.send_header("Content-Length", str(len(file_content)))
|
|
147
|
+
handler.send_header("Cache-Control", "public, max-age=3600")
|
|
148
|
+
ResponseBuilder._add_cors_headers(handler)
|
|
149
|
+
handler.end_headers()
|
|
150
|
+
handler.wfile.write(file_content)
|
|
151
|
+
except Exception as exc:
|
|
152
|
+
ResponseBuilder.send_json(handler, 500, {"error": str(exc)})
|
|
@@ -8,7 +8,7 @@ import shutil
|
|
|
8
8
|
import subprocess
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from types import ModuleType
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
12
|
|
|
13
13
|
from jaclang.runtimelib.client_bundle import (
|
|
14
14
|
ClientBundle,
|
|
@@ -43,9 +43,9 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
43
43
|
self.vite_package_json = vite_package_json
|
|
44
44
|
self.vite_minify = vite_minify
|
|
45
45
|
|
|
46
|
-
def
|
|
46
|
+
def _process_vite_imports(
|
|
47
47
|
self, manifest: ClientManifest | None, module_path: Path
|
|
48
|
-
) -> list[Path | None]:
|
|
48
|
+
) -> list[Path | None]:
|
|
49
49
|
"""Process client imports for Vite bundling.
|
|
50
50
|
|
|
51
51
|
Only mark modules as bundled when we actually inline their code (.jac files we compile
|
|
@@ -53,7 +53,6 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
53
53
|
ES imports so Vite can resolve and bundle them.
|
|
54
54
|
"""
|
|
55
55
|
imported_js_modules: list[Path | None] = []
|
|
56
|
-
|
|
57
56
|
if manifest and manifest.imports:
|
|
58
57
|
for _, import_path in manifest.imports.items():
|
|
59
58
|
import_path_obj = Path(import_path)
|
|
@@ -61,7 +60,6 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
61
60
|
if import_path_obj.suffix == ".js":
|
|
62
61
|
# Inline local JS files and mark as bundled
|
|
63
62
|
try:
|
|
64
|
-
|
|
65
63
|
imported_js_modules.append(import_path_obj)
|
|
66
64
|
except FileNotFoundError:
|
|
67
65
|
imported_js_modules.append(None)
|
|
@@ -86,12 +84,20 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
86
84
|
visited: set[Path] | None = None,
|
|
87
85
|
collected_exports: set[str] | None = None,
|
|
88
86
|
collected_globals: dict[str, Any] | None = None,
|
|
87
|
+
source_root: Path | None = None,
|
|
89
88
|
) -> None:
|
|
90
89
|
"""Recursively compile/copy .jac/.js imports to temp, skipping bundling.
|
|
91
90
|
|
|
92
91
|
Only prepares dependency JS artifacts for Vite by writing compiled JS (.jac)
|
|
93
92
|
or copying local JS (.js) into the temp directory. Bare specifiers are left
|
|
94
93
|
untouched for Vite to resolve.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
module_path: Path to the module being compiled
|
|
97
|
+
visited: Set of already visited paths to avoid cycles
|
|
98
|
+
collected_exports: Set to accumulate exported symbols
|
|
99
|
+
collected_globals: Dict to accumulate global values
|
|
100
|
+
source_root: Root directory of the source files (for preserving folder structure)
|
|
95
101
|
"""
|
|
96
102
|
if visited is None:
|
|
97
103
|
visited = set()
|
|
@@ -104,6 +110,11 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
104
110
|
if module_path in visited:
|
|
105
111
|
return
|
|
106
112
|
visited.add(module_path)
|
|
113
|
+
|
|
114
|
+
# Set source_root on first call (root module's parent directory)
|
|
115
|
+
if source_root is None:
|
|
116
|
+
source_root = module_path.parent.resolve()
|
|
117
|
+
|
|
107
118
|
manifest = None
|
|
108
119
|
|
|
109
120
|
# Compile current module to JS and append registration
|
|
@@ -129,9 +140,24 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
129
140
|
|
|
130
141
|
combined_js = f"{jac_jsx_path}\n{module_js}\n{export_block}"
|
|
131
142
|
if self.vite_package_json is not None:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
# Preserve folder structure: calculate relative path from source_root
|
|
144
|
+
try:
|
|
145
|
+
relative_path = module_path.relative_to(source_root)
|
|
146
|
+
# Change extension from .jac to .js
|
|
147
|
+
output_path = (
|
|
148
|
+
self.vite_package_json.parent
|
|
149
|
+
/ "src"
|
|
150
|
+
/ relative_path.with_suffix(".js")
|
|
151
|
+
)
|
|
152
|
+
except ValueError:
|
|
153
|
+
# If file is outside source_root, fall back to just filename
|
|
154
|
+
output_path = (
|
|
155
|
+
self.vite_package_json.parent / "src" / f"{module_path.stem}.js"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Ensure parent directories exist
|
|
159
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
160
|
+
output_path.write_text(combined_js, encoding="utf-8")
|
|
135
161
|
|
|
136
162
|
if not manifest or not manifest.imports:
|
|
137
163
|
return
|
|
@@ -148,18 +174,49 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
148
174
|
visited,
|
|
149
175
|
collected_exports=collected_exports,
|
|
150
176
|
collected_globals=collected_globals,
|
|
177
|
+
source_root=source_root,
|
|
151
178
|
)
|
|
152
179
|
elif path_obj.suffix == ".js":
|
|
153
180
|
try:
|
|
154
181
|
js_code = path_obj.read_text(encoding="utf-8")
|
|
155
182
|
if self.vite_package_json is not None:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
183
|
+
# Preserve folder structure for .js files too
|
|
184
|
+
try:
|
|
185
|
+
relative_path = path_obj.relative_to(source_root)
|
|
186
|
+
output_path = (
|
|
187
|
+
self.vite_package_json.parent / "src" / relative_path
|
|
188
|
+
)
|
|
189
|
+
except ValueError:
|
|
190
|
+
# If file is outside source_root, fall back to just filename
|
|
191
|
+
output_path = (
|
|
192
|
+
self.vite_package_json.parent / "src" / path_obj.name
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Ensure parent directories exist
|
|
196
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
197
|
+
output_path.write_text(js_code, encoding="utf-8")
|
|
159
198
|
except FileNotFoundError:
|
|
160
199
|
pass
|
|
161
200
|
else:
|
|
162
201
|
# Bare specifiers or other assets handled by Vite
|
|
202
|
+
if self.vite_package_json is not None and path_obj.is_file():
|
|
203
|
+
# Preserve folder structure for other assets too
|
|
204
|
+
try:
|
|
205
|
+
relative_path = path_obj.relative_to(source_root)
|
|
206
|
+
output_path = (
|
|
207
|
+
self.vite_package_json.parent / "src" / relative_path
|
|
208
|
+
)
|
|
209
|
+
except ValueError:
|
|
210
|
+
# If file is outside source_root, fall back to just filename
|
|
211
|
+
output_path = (
|
|
212
|
+
self.vite_package_json.parent / "src" / path_obj.name
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Ensure parent directories exist
|
|
216
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
output_path.write_text(
|
|
218
|
+
path_obj.read_text(encoding="utf-8"), encoding="utf-8"
|
|
219
|
+
)
|
|
163
220
|
continue
|
|
164
221
|
|
|
165
222
|
def _compile_bundle(
|
|
@@ -211,9 +268,9 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
211
268
|
module_js, mod = self._compile_to_js(module_path)
|
|
212
269
|
module_manifest = mod.gen.client_manifest if mod else None
|
|
213
270
|
collected_exports: set[str] = set(self._extract_client_exports(module_manifest))
|
|
271
|
+
|
|
214
272
|
client_globals_map = self._extract_client_globals(module_manifest, module)
|
|
215
273
|
collected_globals: dict[str, Any] = dict(client_globals_map)
|
|
216
|
-
|
|
217
274
|
# Recursively prepare dependencies and accumulate symbols
|
|
218
275
|
self._compile_dependencies_recursively(
|
|
219
276
|
module_path,
|
|
@@ -221,6 +278,13 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
221
278
|
collected_globals=collected_globals,
|
|
222
279
|
)
|
|
223
280
|
|
|
281
|
+
# Copy assets from root assets/ folder to src/assets/ for @jac-client/assets alias
|
|
282
|
+
project_dir = self.vite_package_json.parent
|
|
283
|
+
root_assets_dir = project_dir / "assets"
|
|
284
|
+
src_assets_dir = project_dir / "src" / "assets"
|
|
285
|
+
if root_assets_dir.exists() and root_assets_dir.is_dir():
|
|
286
|
+
self._copy_asset_files(root_assets_dir, src_assets_dir)
|
|
287
|
+
|
|
224
288
|
client_exports = sorted(collected_exports)
|
|
225
289
|
client_globals_map = collected_globals
|
|
226
290
|
|
|
@@ -283,6 +347,9 @@ root.render(<App />);
|
|
|
283
347
|
subprocess.run(
|
|
284
348
|
command, cwd=project_dir, check=True, capture_output=True, text=True
|
|
285
349
|
)
|
|
350
|
+
# Copy CSS and other asset files from src/ to build/ after Babel compilation
|
|
351
|
+
# Babel only transpiles JS, so we need to manually copy assets
|
|
352
|
+
self._copy_asset_files(project_dir / "src", project_dir / "build")
|
|
286
353
|
# then build the code
|
|
287
354
|
command = ["npm", "run", "build"]
|
|
288
355
|
subprocess.run(
|
|
@@ -293,7 +360,7 @@ root.render(<App />);
|
|
|
293
360
|
except FileNotFoundError:
|
|
294
361
|
raise ClientBundleError(
|
|
295
362
|
"npx or vite command not found. Ensure Node.js and npm are installed."
|
|
296
|
-
)
|
|
363
|
+
) from None
|
|
297
364
|
# Find the generated bundle file
|
|
298
365
|
bundle_file = self._find_vite_bundle(output_dir)
|
|
299
366
|
if not bundle_file:
|
|
@@ -336,12 +403,93 @@ root.render(<App />);
|
|
|
336
403
|
}});
|
|
337
404
|
"""
|
|
338
405
|
|
|
406
|
+
def _copy_asset_files(self, src_dir: Path, build_dir: Path) -> None:
|
|
407
|
+
"""Copy CSS and other asset files from src/ to build/ directory recursively.
|
|
408
|
+
|
|
409
|
+
Babel only transpiles JavaScript files, so CSS and other assets need to be
|
|
410
|
+
manually copied to the build directory for Vite to resolve them.
|
|
411
|
+
This method recursively copies assets from subdirectories (e.g., src/assets/)
|
|
412
|
+
while preserving the directory structure.
|
|
413
|
+
"""
|
|
414
|
+
if not src_dir.exists():
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
# Ensure build directory exists
|
|
418
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
419
|
+
|
|
420
|
+
# Asset file extensions to copy
|
|
421
|
+
asset_extensions = {
|
|
422
|
+
".css",
|
|
423
|
+
".scss",
|
|
424
|
+
".sass",
|
|
425
|
+
".less",
|
|
426
|
+
".svg",
|
|
427
|
+
".png",
|
|
428
|
+
".jpg",
|
|
429
|
+
".jpeg",
|
|
430
|
+
".gif",
|
|
431
|
+
".webp",
|
|
432
|
+
".ico",
|
|
433
|
+
".woff",
|
|
434
|
+
".woff2",
|
|
435
|
+
".ttf",
|
|
436
|
+
".eot",
|
|
437
|
+
".otf",
|
|
438
|
+
".mp4",
|
|
439
|
+
".webm",
|
|
440
|
+
".mp3",
|
|
441
|
+
".wav",
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
def copy_recursive(
|
|
445
|
+
source: Path, destination: Path, base: Path | None = None
|
|
446
|
+
) -> None:
|
|
447
|
+
"""Recursively copy asset files from source to destination.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
source: Source directory to copy from
|
|
451
|
+
destination: Destination directory to copy to
|
|
452
|
+
base: Base directory for calculating relative paths (defaults to source)
|
|
453
|
+
"""
|
|
454
|
+
if not source.exists():
|
|
455
|
+
return
|
|
456
|
+
|
|
457
|
+
if base is None:
|
|
458
|
+
base = source
|
|
459
|
+
|
|
460
|
+
for item in source.iterdir():
|
|
461
|
+
if item.is_file() and item.suffix.lower() in asset_extensions:
|
|
462
|
+
# Preserve relative path structure from base
|
|
463
|
+
relative_path = item.relative_to(base)
|
|
464
|
+
dest_file = destination / relative_path
|
|
465
|
+
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
466
|
+
with contextlib.suppress(OSError, shutil.Error):
|
|
467
|
+
shutil.copy2(item, dest_file)
|
|
468
|
+
elif item.is_dir():
|
|
469
|
+
# Recursively process subdirectories
|
|
470
|
+
copy_recursive(item, destination, base)
|
|
471
|
+
|
|
472
|
+
# Copy files from src_dir root and recursively from subdirectories
|
|
473
|
+
copy_recursive(src_dir, build_dir)
|
|
474
|
+
|
|
339
475
|
def _find_vite_bundle(self, output_dir: Path) -> Path | None:
|
|
340
476
|
"""Find the generated Vite bundle file."""
|
|
341
477
|
for file in output_dir.glob("client.*.js"):
|
|
342
478
|
return file
|
|
343
479
|
return None
|
|
344
480
|
|
|
481
|
+
def _find_vite_css(self, output_dir: Path) -> Path | None:
|
|
482
|
+
"""Find the generated Vite CSS file."""
|
|
483
|
+
# Vite typically outputs CSS as main.css or with a hash
|
|
484
|
+
# Try main.css first (most common), then any .css file
|
|
485
|
+
css_file = output_dir / "main.css"
|
|
486
|
+
if css_file.exists():
|
|
487
|
+
return css_file
|
|
488
|
+
# Fallback: find any CSS file
|
|
489
|
+
for file in output_dir.glob("*.css"):
|
|
490
|
+
return file
|
|
491
|
+
return None
|
|
492
|
+
|
|
345
493
|
def cleanup_temp_dir(self) -> None:
|
|
346
494
|
"""Clean up the src directory and its contents."""
|
|
347
495
|
if not self.vite_package_json or not self.vite_package_json.exists():
|
|
@@ -351,5 +499,5 @@ root.render(<App />);
|
|
|
351
499
|
temp_dir = project_dir / "src"
|
|
352
500
|
|
|
353
501
|
if temp_dir.exists():
|
|
354
|
-
with contextlib.suppress(OSError):
|
|
502
|
+
with contextlib.suppress(OSError, shutil.Error):
|
|
355
503
|
shutil.rmtree(temp_dir)
|
jac_client/tests/__init__.py
CHANGED
|
@@ -10,8 +10,13 @@ cl obj ButtonProps {
|
|
|
10
10
|
cl def app() {
|
|
11
11
|
let props = ButtonProps(label="Tap Me", color="primary");
|
|
12
12
|
return <div class="app">
|
|
13
|
-
<h1>
|
|
14
|
-
|
|
13
|
+
<h1>
|
|
14
|
+
{API_LABEL}
|
|
15
|
+
</h1>
|
|
16
|
+
<button
|
|
17
|
+
class={props.color}
|
|
18
|
+
data-id="button"
|
|
19
|
+
>
|
|
15
20
|
{props.label}
|
|
16
21
|
</button>
|
|
17
22
|
</div>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import from react { useState }
|
|
2
|
+
|
|
3
|
+
def app() -> any {
|
|
4
|
+
let [todos, setTodos] = useState([]);
|
|
5
|
+
let [input, setInput] = useState("");
|
|
6
|
+
|
|
7
|
+
# Event Handler
|
|
8
|
+
async def addTodo() -> None {
|
|
9
|
+
if not input.trim() {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
response = root spawn create_todo(text=input.trim());
|
|
13
|
+
new_todo = response.reports[0][0];
|
|
14
|
+
setTodos(todos.concat([new_todo]));
|
|
15
|
+
setInput("");
|
|
16
|
+
|
|
17
|
+
def foo { }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return <div>
|
|
21
|
+
<h2>
|
|
22
|
+
My Todos
|
|
23
|
+
</h2>
|
|
24
|
+
<input
|
|
25
|
+
value={input}
|
|
26
|
+
onChange={lambda e: any -> None{ setInput(e.target.value);} }
|
|
27
|
+
onKeyPress={lambda e: any -> None{ if e.key == "Enter" {
|
|
28
|
+
addTodo();
|
|
29
|
+
}} }
|
|
30
|
+
/>
|
|
31
|
+
<button
|
|
32
|
+
onClick={addTodo}
|
|
33
|
+
>
|
|
34
|
+
Add Todo
|
|
35
|
+
</button>
|
|
36
|
+
<div>
|
|
37
|
+
{todos.map(
|
|
38
|
+
lambda todo: any -> any{ return <div
|
|
39
|
+
key={todo._jac_id}
|
|
40
|
+
>
|
|
41
|
+
<span>
|
|
42
|
+
{todo.text}
|
|
43
|
+
</span>
|
|
44
|
+
</div>; }
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'''Test file for .cl file serves the client module.'''
|
|
2
|
+
|
|
3
|
+
node Todo {
|
|
4
|
+
has text: str;
|
|
5
|
+
has done: bool = False;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
walker create_todo {
|
|
9
|
+
has text: str;
|
|
10
|
+
|
|
11
|
+
can create with `root entry {
|
|
12
|
+
new_todo = here ++> Todo(text=self.text);
|
|
13
|
+
report new_todo ;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
"""Sample Jac module using Ant Design components."""
|
|
2
2
|
|
|
3
|
-
cl import from antd {
|
|
4
|
-
|
|
5
|
-
}
|
|
3
|
+
cl import from antd { Button }
|
|
4
|
+
|
|
6
5
|
cl let APP_NAME: str = "Ant Design Test";
|
|
7
6
|
|
|
8
7
|
cl def ButtonTest() {
|
|
9
8
|
return <div>
|
|
10
|
-
<h1>
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
<h1>
|
|
10
|
+
{APP_NAME}
|
|
11
|
+
</h1>
|
|
12
|
+
<p>
|
|
13
|
+
Testing Ant Design integration
|
|
14
|
+
</p>
|
|
15
|
+
<Button>
|
|
16
|
+
Click Me
|
|
17
|
+
</Button>
|
|
13
18
|
</div>;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
cl def CardTest() {
|
|
17
22
|
return <div class="card-wrapper">
|
|
18
|
-
<h2>
|
|
23
|
+
<h2>
|
|
24
|
+
Card Component
|
|
25
|
+
</h2>
|
|
19
26
|
</div>;
|
|
20
27
|
}
|
|
21
28
|
|
|
@@ -25,4 +32,3 @@ cl def app() {
|
|
|
25
32
|
<CardTest />
|
|
26
33
|
</div>;
|
|
27
34
|
}
|
|
28
|
-
|
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
"""Test module that imports JavaScript functions."""
|
|
2
2
|
|
|
3
|
-
cl import from .utils {
|
|
4
|
-
formatMessage,
|
|
5
|
-
calculateSum,
|
|
6
|
-
JS_CONSTANT,
|
|
7
|
-
MessageFormatter
|
|
8
|
-
}
|
|
3
|
+
cl import from .utils { formatMessage, calculateSum, JS_CONSTANT, MessageFormatter }
|
|
9
4
|
|
|
10
5
|
cl let JS_IMPORT_LABEL: str = "JavaScript Import Test";
|
|
11
6
|
|
|
12
|
-
cl def JsImportTest()
|
|
7
|
+
cl def JsImportTest() -> any {
|
|
13
8
|
let greeting = formatMessage("Jac");
|
|
14
9
|
let sum = calculateSum(5, 3);
|
|
15
10
|
let formatter = MessageFormatter("JS");
|
|
16
11
|
let formatted = formatter.format("Hello from JS class");
|
|
17
|
-
|
|
12
|
+
|
|
18
13
|
return <div class="js-import-test">
|
|
19
|
-
<h1>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<p>
|
|
23
|
-
|
|
14
|
+
<h1>
|
|
15
|
+
{JS_IMPORT_LABEL}
|
|
16
|
+
</h1>
|
|
17
|
+
<p>
|
|
18
|
+
Greeting: {greeting}
|
|
19
|
+
</p>
|
|
20
|
+
<p>
|
|
21
|
+
Sum (5 + 3): {sum}
|
|
22
|
+
</p>
|
|
23
|
+
<p>
|
|
24
|
+
Constant: {JS_CONSTANT}
|
|
25
|
+
</p>
|
|
26
|
+
<p>
|
|
27
|
+
Formatted: {formatted}
|
|
28
|
+
</p>
|
|
24
29
|
</div>;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
cl def app()
|
|
32
|
+
cl def app() -> any {
|
|
28
33
|
return <JsImportTest />;
|
|
29
34
|
}
|
|
30
|
-
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
cl import from .button {
|
|
2
|
-
CustomButton
|
|
3
|
-
}
|
|
1
|
+
cl import from .button { CustomButton }
|
|
4
2
|
|
|
5
|
-
cl def RelativeImport()
|
|
3
|
+
cl def RelativeImport() -> any {
|
|
6
4
|
return <div>
|
|
7
5
|
<CustomButton />
|
|
8
6
|
</div>;
|
|
9
7
|
}
|
|
10
8
|
|
|
11
|
-
cl def app()
|
|
9
|
+
cl def app() -> any {
|
|
12
10
|
return <RelativeImport />;
|
|
13
|
-
}
|
|
11
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
cl import from antd {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
cl import from antd { Button }
|
|
2
|
+
|
|
3
|
+
cl def CustomButton() -> any {
|
|
4
|
+
return <Button>
|
|
5
|
+
Click Me
|
|
6
|
+
</Button>;
|
|
7
|
+
}
|
|
@@ -27,11 +27,7 @@ walker positional_walker {
|
|
|
27
27
|
has metadata: dict = {};
|
|
28
28
|
|
|
29
29
|
can execute with `root entry {
|
|
30
|
-
report {
|
|
31
|
-
"label": self.label,
|
|
32
|
-
"count": self.count,
|
|
33
|
-
"meta": self.metadata
|
|
34
|
-
} ;
|
|
30
|
+
report {"label": self.label, "count": self.count, "meta": self.metadata} ;
|
|
35
31
|
}
|
|
36
32
|
}
|
|
37
33
|
|