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.
- jac_client/docs/README.md +232 -172
- jac_client/docs/advanced-state.md +1012 -452
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/file-system/intro.md +90 -0
- jac_client/docs/guide-example/intro.md +117 -0
- jac_client/docs/guide-example/step-01-setup.md +260 -0
- jac_client/docs/guide-example/step-02-components.md +416 -0
- jac_client/docs/guide-example/step-03-styling.md +478 -0
- jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
- jac_client/docs/guide-example/step-05-local-state.md +530 -0
- jac_client/docs/guide-example/step-06-events.md +750 -0
- jac_client/docs/guide-example/step-07-effects.md +469 -0
- jac_client/docs/guide-example/step-08-walkers.md +534 -0
- jac_client/docs/guide-example/step-09-authentication.md +586 -0
- jac_client/docs/guide-example/step-10-routing.md +540 -0
- jac_client/docs/guide-example/step-11-final.md +964 -0
- jac_client/docs/imports.md +538 -46
- jac_client/docs/lifecycle-hooks.md +517 -297
- jac_client/docs/routing.md +487 -357
- jac_client/docs/styling/intro.md +250 -0
- jac_client/docs/styling/js-styling.md +373 -0
- jac_client/docs/styling/material-ui.md +346 -0
- jac_client/docs/styling/pure-css.md +305 -0
- jac_client/docs/styling/sass.md +409 -0
- jac_client/docs/styling/styled-components.md +401 -0
- jac_client/docs/styling/tailwind.md +303 -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 +67 -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 +27 -0
- jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -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 +43 -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 +27 -0
- jac_client/examples/asset-serving/image-asset/vite.config.js +29 -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 +57 -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 +29 -0
- jac_client/examples/basic/.babelrc +9 -0
- jac_client/examples/basic/README.md +16 -0
- jac_client/examples/basic/app.jac +16 -0
- jac_client/examples/basic/package.json +27 -0
- jac_client/examples/basic/vite.config.js +28 -0
- jac_client/examples/basic-auth/.babelrc +9 -0
- jac_client/examples/basic-auth/README.md +16 -0
- jac_client/examples/basic-auth/app.jac +308 -0
- jac_client/examples/basic-auth/package.json +27 -0
- jac_client/examples/basic-auth/vite.config.js +28 -0
- jac_client/examples/basic-auth-with-router/.babelrc +9 -0
- jac_client/examples/basic-auth-with-router/README.md +60 -0
- jac_client/examples/basic-auth-with-router/app.jac +464 -0
- jac_client/examples/basic-auth-with-router/package.json +28 -0
- jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
- jac_client/examples/basic-full-stack/.babelrc +9 -0
- jac_client/examples/basic-full-stack/README.md +18 -0
- jac_client/examples/basic-full-stack/app.jac +320 -0
- jac_client/examples/basic-full-stack/package.json +28 -0
- jac_client/examples/basic-full-stack/vite.config.js +28 -0
- 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 +63 -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 +28 -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 +82 -0
- jac_client/examples/css-styling/material-ui/package.json +32 -0
- jac_client/examples/css-styling/material-ui/vite.config.js +28 -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 +63 -0
- jac_client/examples/css-styling/pure-css/package.json +28 -0
- jac_client/examples/css-styling/pure-css/styles.css +112 -0
- jac_client/examples/css-styling/pure-css/vite.config.js +28 -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 +63 -0
- jac_client/examples/css-styling/sass-example/package.json +29 -0
- jac_client/examples/css-styling/sass-example/styles.scss +158 -0
- jac_client/examples/css-styling/sass-example/vite.config.js +28 -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 +66 -0
- jac_client/examples/css-styling/styled-components/package.json +29 -0
- jac_client/examples/css-styling/styled-components/styled.js +91 -0
- jac_client/examples/css-styling/styled-components/vite.config.js +28 -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 +64 -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 +30 -0
- jac_client/examples/full-stack-with-auth/.babelrc +9 -0
- jac_client/examples/full-stack-with-auth/README.md +16 -0
- jac_client/examples/full-stack-with-auth/app.jac +735 -0
- jac_client/examples/full-stack-with-auth/package.json +28 -0
- jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
- jac_client/examples/with-router/.babelrc +9 -0
- jac_client/examples/with-router/README.md +17 -0
- jac_client/examples/with-router/app.jac +323 -0
- jac_client/examples/with-router/package.json +28 -0
- jac_client/examples/with-router/vite.config.js +28 -0
- jac_client/plugin/cli.py +95 -179
- jac_client/plugin/client.py +111 -2
- jac_client/plugin/client_runtime.jac +183 -890
- jac_client/plugin/vite_client_bundle.py +185 -205
- jac_client/tests/__init__.py +0 -1
- jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
- jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
- jac_client/tests/fixtures/cl_file/app.jac +15 -0
- jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
- jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
- jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
- jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
- jac_client/tests/fixtures/spawn_test/app.jac +133 -0
- jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
- jac_client/tests/test_asset_examples.py +339 -0
- jac_client/tests/test_cl.py +345 -151
- jac_client/tests/test_create_jac_app.py +41 -45
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
- jac_client-0.2.1.dist-info/RECORD +140 -0
- jac_client/examples/little-x/package-lock.json +0 -2840
- jac_client/examples/todo-app/README.md +0 -82
- jac_client/examples/todo-app/app.jac +0 -683
- jac_client/examples/todo-app/package-lock.json +0 -999
- jac_client/examples/todo-app/package.json +0 -22
- jac_client-0.1.0.dist-info/RECORD +0 -33
- /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
- {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,
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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 / "
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
276
|
-
|
|
283
|
+
src_dir = project_dir / "src"
|
|
284
|
+
src_dir.mkdir(exist_ok=True)
|
|
277
285
|
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
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 / "
|
|
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});"
|
jac_client/tests/__init__.py
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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
|
|
27
|
+
cl def app() -> any {
|
|
28
28
|
return <JsImportTest />;
|
|
29
29
|
}
|
|
30
30
|
|