jac-client 0.2.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 +31 -1
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/file-system/intro.md +90 -0
- 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/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/with-router/app.jac +1 -1
- jac_client/plugin/cli.py +5 -0
- jac_client/plugin/client.py +64 -3
- jac_client/plugin/vite_client_bundle.py +96 -1
- jac_client/tests/__init__.py +0 -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/js_import/app.jac +1 -1
- jac_client/tests/fixtures/relative_import/button.jac +2 -2
- jac_client/tests/fixtures/test_fragments_spread/app.jac +2 -2
- jac_client/tests/test_asset_examples.py +339 -0
- jac_client/tests/test_cl.py +165 -87
- jac_client/tests/test_create_jac_app.py +40 -44
- {jac_client-0.2.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +2 -2
- jac_client-0.2.1.dist-info/RECORD +140 -0
- jac_client-0.2.0.dist-info/RECORD +0 -72
- {jac_client-0.2.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
- {jac_client-0.2.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
|
|
3
|
+
export const Container = styled.div`
|
|
4
|
+
min-height: 100vh;
|
|
5
|
+
background: linear-gradient(to bottom right, #dbeafe, #e0e7ff);
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
padding: 1rem;
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
export const Card = styled.div`
|
|
13
|
+
background-color: #ffffff;
|
|
14
|
+
border-radius: 1rem;
|
|
15
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
16
|
+
padding: 2rem;
|
|
17
|
+
max-width: 28rem;
|
|
18
|
+
width: 100%;
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
export const Title = styled.h1`
|
|
22
|
+
font-size: 1.875rem;
|
|
23
|
+
font-weight: bold;
|
|
24
|
+
color: #1f2937;
|
|
25
|
+
text-align: center;
|
|
26
|
+
margin-bottom: 1.5rem;
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
export const Divider = styled.div`
|
|
30
|
+
height: 1px;
|
|
31
|
+
background: linear-gradient(to right, transparent, #d1d5db, transparent);
|
|
32
|
+
margin-bottom: 1.5rem;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
export const CounterSection = styled.div`
|
|
36
|
+
text-align: center;
|
|
37
|
+
margin-bottom: 2rem;
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export const Label = styled.div`
|
|
41
|
+
font-size: 0.875rem;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
color: #4b5563;
|
|
44
|
+
margin-bottom: 0.5rem;
|
|
45
|
+
text-transform: uppercase;
|
|
46
|
+
letter-spacing: 0.05em;
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
export const CountDisplay = styled.div`
|
|
50
|
+
font-size: 3.75rem;
|
|
51
|
+
font-weight: bold;
|
|
52
|
+
transition: color 0.3s ease;
|
|
53
|
+
color: ${props => props.count === 0 ? "#1f2937" : (props.count > 0 ? "#16a34a" : "#dc2626")};
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
export const ButtonGroup = styled.div`
|
|
57
|
+
display: flex;
|
|
58
|
+
justify-content: center;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 1rem;
|
|
61
|
+
margin-bottom: 1.5rem;
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
export const Button = styled.button`
|
|
65
|
+
color: #ffffff;
|
|
66
|
+
font-weight: bold;
|
|
67
|
+
padding: 0.75rem 1.5rem;
|
|
68
|
+
border-radius: 0.5rem;
|
|
69
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
70
|
+
transition: all 0.2s ease;
|
|
71
|
+
font-size: 1.25rem;
|
|
72
|
+
border: none;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
background-color: ${props => props.bgColor};
|
|
75
|
+
|
|
76
|
+
&:hover {
|
|
77
|
+
transform: scale(1.05);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&:active {
|
|
81
|
+
transform: scale(0.95);
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
export const Hint = styled.div`
|
|
86
|
+
text-align: center;
|
|
87
|
+
font-size: 0.875rem;
|
|
88
|
+
color: #6b7280;
|
|
89
|
+
font-style: italic;
|
|
90
|
+
`;
|
|
91
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
root: ".", // base folder
|
|
10
|
+
build: {
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
input: "build/main.js", // your compiled entry file
|
|
13
|
+
output: {
|
|
14
|
+
entryFileNames: "client.[hash].js", // name of the final js file
|
|
15
|
+
assetFileNames: "[name].[ext]",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
outDir: "dist", // final bundled output
|
|
19
|
+
emptyOutDir: true,
|
|
20
|
+
},
|
|
21
|
+
publicDir: false,
|
|
22
|
+
resolve: {
|
|
23
|
+
alias: {
|
|
24
|
+
"@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Pages
|
|
2
|
+
cl import from react {useState, useEffect}
|
|
3
|
+
cl import ".global.css";
|
|
4
|
+
cl {
|
|
5
|
+
def app() -> any {
|
|
6
|
+
let [count, setCount] = useState(0);
|
|
7
|
+
|
|
8
|
+
useEffect(lambda -> None {
|
|
9
|
+
console.log("Count changed: ", count);
|
|
10
|
+
}, [count]);
|
|
11
|
+
|
|
12
|
+
let handleIncrement = lambda e: any -> None {
|
|
13
|
+
setCount(count + 1);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let handleDecrement = lambda e: any -> None {
|
|
17
|
+
setCount(count - 1);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let handleReset = lambda e: any -> None {
|
|
21
|
+
setCount(0);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let countColorClass = "text-gray-800" if count == 0 else ("text-green-600" if count > 0 else "text-red-600");
|
|
25
|
+
|
|
26
|
+
return <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
|
27
|
+
<div className="bg-white rounded-2xl shadow-2xl p-8 max-w-md w-full">
|
|
28
|
+
<h1 className="text-3xl font-bold text-gray-800 text-center mb-6">Counter Application</h1>
|
|
29
|
+
<div className="h-px bg-gradient-to-r from-transparent via-gray-300 to-transparent mb-6"></div>
|
|
30
|
+
|
|
31
|
+
<div className="text-center mb-8">
|
|
32
|
+
<div className="text-sm font-semibold text-gray-600 mb-2 uppercase tracking-wide">Current Count</div>
|
|
33
|
+
<div className={"text-6xl font-bold " + countColorClass + " transition-colors duration-300"}>
|
|
34
|
+
{count}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div className="h-px bg-gradient-to-r from-transparent via-gray-300 to-transparent mb-6"></div>
|
|
39
|
+
|
|
40
|
+
<div className="flex justify-center items-center gap-4 mb-6">
|
|
41
|
+
<button
|
|
42
|
+
className="bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition-all duration-200 transform hover:scale-105 active:scale-95 text-xl"
|
|
43
|
+
onClick={handleDecrement}>
|
|
44
|
+
-
|
|
45
|
+
</button>
|
|
46
|
+
<button
|
|
47
|
+
className="bg-gray-500 hover:bg-gray-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition-all duration-200 transform hover:scale-105 active:scale-95 text-xl"
|
|
48
|
+
onClick={handleReset}>
|
|
49
|
+
↻
|
|
50
|
+
</button>
|
|
51
|
+
<button
|
|
52
|
+
className="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition-all duration-200 transform hover:scale-105 active:scale-95 text-xl"
|
|
53
|
+
onClick={handleIncrement}>
|
|
54
|
+
+
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div className="text-center text-sm text-gray-500 italic">
|
|
59
|
+
Click the buttons to increment, decrement, or reset the counter
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tailwind-example",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "npm run compile && vite build",
|
|
7
|
+
"dev": "vite dev",
|
|
8
|
+
"preview": "vite preview",
|
|
9
|
+
"compile": "babel src --out-dir build --extensions \".jsx,.js\" --out-file-extension .js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"description": "Jac application: tailwind-example",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@babel/cli": "^7.28.3",
|
|
18
|
+
"@babel/core": "^7.28.5",
|
|
19
|
+
"@babel/preset-env": "^7.28.5",
|
|
20
|
+
"@babel/preset-react": "^7.28.5",
|
|
21
|
+
"vite": "^6.4.1"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@tailwindcss/vite": "^4.1.17",
|
|
25
|
+
"react": "^19.2.0",
|
|
26
|
+
"react-dom": "^19.2.0",
|
|
27
|
+
"react-router-dom": "^6.30.1",
|
|
28
|
+
"tailwindcss": "^4.1.17"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
root: ".", // base folder
|
|
11
|
+
build: {
|
|
12
|
+
rollupOptions: {
|
|
13
|
+
input: "build/main.js", // your compiled entry file
|
|
14
|
+
output: {
|
|
15
|
+
entryFileNames: "client.[hash].js", // name of the final js file
|
|
16
|
+
assetFileNames: "[name].[ext]",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
outDir: "dist", // final bundled output
|
|
20
|
+
emptyOutDir: true,
|
|
21
|
+
},
|
|
22
|
+
plugins: [ tailwindcss(), ],
|
|
23
|
+
publicDir: false,
|
|
24
|
+
resolve: {
|
|
25
|
+
alias: {
|
|
26
|
+
"@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
jac_client/plugin/cli.py
CHANGED
|
@@ -84,6 +84,10 @@ class JacCmd:
|
|
|
84
84
|
build_folder = os.path.join(project_path, "build")
|
|
85
85
|
os.makedirs(build_folder, exist_ok=True)
|
|
86
86
|
|
|
87
|
+
# create assets folder for static assets (images, fonts, etc.)
|
|
88
|
+
assets_folder = os.path.join(project_path, "assets")
|
|
89
|
+
os.makedirs(assets_folder, exist_ok=True)
|
|
90
|
+
|
|
87
91
|
# Update package.json with Jac-specific configuration
|
|
88
92
|
package_data.update(
|
|
89
93
|
{
|
|
@@ -186,6 +190,7 @@ export default defineConfig({
|
|
|
186
190
|
resolve: {
|
|
187
191
|
alias: {
|
|
188
192
|
"@jac-client/utils": path.resolve(__dirname, "src/client_runtime.js"),
|
|
193
|
+
"@jac-client/assets": path.resolve(__dirname, "src/assets"),
|
|
189
194
|
},
|
|
190
195
|
},
|
|
191
196
|
});
|
jac_client/plugin/client.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
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
10
|
from jaclang.runtimelib.machine import (
|
|
@@ -12,6 +15,11 @@ from jaclang.runtimelib.server import ModuleIntrospector
|
|
|
12
15
|
|
|
13
16
|
from .vite_client_bundle import ViteClientBundleBuilder
|
|
14
17
|
|
|
18
|
+
JsonValue: TypeAlias = (
|
|
19
|
+
None | str | int | float | bool | list["JsonValue"] | dict[str, "JsonValue"]
|
|
20
|
+
)
|
|
21
|
+
StatusCode: TypeAlias = Literal[200, 201, 400, 401, 404, 503]
|
|
22
|
+
|
|
15
23
|
|
|
16
24
|
class JacClientModuleIntrospector(ModuleIntrospector):
|
|
17
25
|
"""Jac Client Module Introspector."""
|
|
@@ -30,12 +38,28 @@ class JacClientModuleIntrospector(ModuleIntrospector):
|
|
|
30
38
|
|
|
31
39
|
bundle_hash = self.ensure_bundle()
|
|
32
40
|
|
|
41
|
+
# Find CSS file in dist directory
|
|
42
|
+
base_path = Path(Jac.base_path_dir)
|
|
43
|
+
dist_dir = base_path / "dist"
|
|
44
|
+
css_link = ""
|
|
45
|
+
|
|
46
|
+
# Try to find CSS file (main.css is the default Vite output)
|
|
47
|
+
css_file = dist_dir / "main.css"
|
|
48
|
+
if css_file.exists():
|
|
49
|
+
css_hash = hashlib.sha256(css_file.read_bytes()).hexdigest()[:8]
|
|
50
|
+
css_link = (
|
|
51
|
+
f'<link rel="stylesheet" href="/static/main.css?hash={css_hash}"/>'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
head_content = f'<meta charset="utf-8"/>\n <title>{html.escape(function_name)}</title>'
|
|
55
|
+
if css_link:
|
|
56
|
+
head_content += f"\n {css_link}"
|
|
57
|
+
|
|
33
58
|
page = (
|
|
34
59
|
"<!DOCTYPE html>"
|
|
35
60
|
'<html lang="en">'
|
|
36
61
|
"<head>"
|
|
37
|
-
|
|
38
|
-
f"<title>{html.escape(function_name)}</title>"
|
|
62
|
+
f"{head_content}"
|
|
39
63
|
"</head>"
|
|
40
64
|
"<body>"
|
|
41
65
|
'<div id="root"></div>'
|
|
@@ -87,3 +111,40 @@ class JacClient:
|
|
|
87
111
|
) -> ModuleIntrospector:
|
|
88
112
|
"""Get a module introspector for the supplied module."""
|
|
89
113
|
return JacClientModuleIntrospector(module_name, base_path)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
@hookimpl
|
|
117
|
+
def send_static_file(
|
|
118
|
+
handler: BaseHTTPRequestHandler,
|
|
119
|
+
file_path: Path,
|
|
120
|
+
content_type: str | None = None,
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Send static file response (images, fonts, etc.).
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
handler: HTTP request handler
|
|
126
|
+
file_path: Path to the file to serve
|
|
127
|
+
content_type: MIME type (auto-detected if None)
|
|
128
|
+
"""
|
|
129
|
+
from jaclang.runtimelib.server import ResponseBuilder
|
|
130
|
+
|
|
131
|
+
if not file_path.exists() or not file_path.is_file():
|
|
132
|
+
ResponseBuilder.send_json(handler, 404, {"error": "File not found"})
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
file_content = file_path.read_bytes()
|
|
137
|
+
if content_type is None:
|
|
138
|
+
content_type, _ = mimetypes.guess_type(str(file_path))
|
|
139
|
+
if content_type is None:
|
|
140
|
+
content_type = "application/octet-stream"
|
|
141
|
+
|
|
142
|
+
handler.send_response(200)
|
|
143
|
+
handler.send_header("Content-Type", content_type)
|
|
144
|
+
handler.send_header("Content-Length", str(len(file_content)))
|
|
145
|
+
handler.send_header("Cache-Control", "public, max-age=3600")
|
|
146
|
+
ResponseBuilder._add_cors_headers(handler)
|
|
147
|
+
handler.end_headers()
|
|
148
|
+
handler.wfile.write(file_content)
|
|
149
|
+
except Exception as exc:
|
|
150
|
+
ResponseBuilder.send_json(handler, 500, {"error": str(exc)})
|
|
@@ -160,6 +160,10 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
160
160
|
pass
|
|
161
161
|
else:
|
|
162
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
|
+
)
|
|
163
167
|
continue
|
|
164
168
|
|
|
165
169
|
def _compile_bundle(
|
|
@@ -221,6 +225,13 @@ class ViteClientBundleBuilder(ClientBundleBuilder):
|
|
|
221
225
|
collected_globals=collected_globals,
|
|
222
226
|
)
|
|
223
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
|
+
|
|
224
235
|
client_exports = sorted(collected_exports)
|
|
225
236
|
client_globals_map = collected_globals
|
|
226
237
|
|
|
@@ -283,6 +294,9 @@ root.render(<App />);
|
|
|
283
294
|
subprocess.run(
|
|
284
295
|
command, cwd=project_dir, check=True, capture_output=True, text=True
|
|
285
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")
|
|
286
300
|
# then build the code
|
|
287
301
|
command = ["npm", "run", "build"]
|
|
288
302
|
subprocess.run(
|
|
@@ -336,12 +350,93 @@ root.render(<App />);
|
|
|
336
350
|
}});
|
|
337
351
|
"""
|
|
338
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
|
+
|
|
339
422
|
def _find_vite_bundle(self, output_dir: Path) -> Path | None:
|
|
340
423
|
"""Find the generated Vite bundle file."""
|
|
341
424
|
for file in output_dir.glob("client.*.js"):
|
|
342
425
|
return file
|
|
343
426
|
return None
|
|
344
427
|
|
|
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
|
|
439
|
+
|
|
345
440
|
def cleanup_temp_dir(self) -> None:
|
|
346
441
|
"""Clean up the src directory and its contents."""
|
|
347
442
|
if not self.vite_package_json or not self.vite_package_json.exists():
|
|
@@ -351,5 +446,5 @@ root.render(<App />);
|
|
|
351
446
|
temp_dir = project_dir / "src"
|
|
352
447
|
|
|
353
448
|
if temp_dir.exists():
|
|
354
|
-
with contextlib.suppress(OSError):
|
|
449
|
+
with contextlib.suppress(OSError, shutil.Error):
|
|
355
450
|
shutil.rmtree(temp_dir)
|
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>
|
|
@@ -11,7 +11,7 @@ cl def FragmentTest() {
|
|
|
11
11
|
cl def SpreadPropsTest() {
|
|
12
12
|
# Test spread props
|
|
13
13
|
unwrapped = {"id": "my-div", "class": "container", "data-role": "main"};
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
return <div {...unwrapped}>
|
|
16
16
|
<span>{"Spread props work!"}</span>
|
|
17
17
|
</div>;
|
|
@@ -20,7 +20,7 @@ cl def SpreadPropsTest() {
|
|
|
20
20
|
cl def MixedTest() {
|
|
21
21
|
# Test mixing spread props with regular props
|
|
22
22
|
baseStyle = {"id": "base-id", "color": "blue"};
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
return <>
|
|
25
25
|
<div {...baseStyle} class="override">
|
|
26
26
|
{"Mixed test"}
|