jac-client 0.2.6__py3-none-any.whl → 0.2.8__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/examples/all-in-one/app.jac +573 -0
- jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
- jac_client/examples/all-in-one/components/Header.jac +13 -0
- jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
- jac_client/examples/all-in-one/components/Summary.jac +53 -0
- jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
- jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
- jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
- jac_client/examples/all-in-one/components/navigation.jac +132 -0
- jac_client/examples/all-in-one/constants/categories.jac +37 -0
- jac_client/examples/all-in-one/constants/clients.jac +13 -0
- jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
- jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
- jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +70 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.jac +126 -0
- jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +552 -0
- jac_client/examples/all-in-one/pages/FeaturesTest.jac +126 -0
- jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
- jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
- jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
- jac_client/examples/all-in-one/pages/notFound.jac +24 -0
- jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
- jac_client/examples/all-in-one/utils/formatters.jac +52 -0
- jac_client/examples/asset-serving/css-with-image/src/app.jac +3 -3
- jac_client/examples/asset-serving/image-asset/src/app.jac +3 -3
- jac_client/examples/asset-serving/import-alias/src/app.jac +3 -3
- jac_client/examples/basic/src/app.jac +3 -3
- jac_client/examples/basic-auth/src/app.jac +31 -37
- jac_client/examples/basic-auth-with-router/src/app.jac +16 -16
- jac_client/examples/basic-full-stack/src/app.jac +24 -30
- jac_client/examples/css-styling/js-styling/src/app.jac +5 -5
- jac_client/examples/css-styling/material-ui/src/app.jac +5 -5
- jac_client/examples/css-styling/pure-css/src/app.jac +5 -5
- jac_client/examples/css-styling/sass-example/src/app.jac +5 -5
- jac_client/examples/css-styling/styled-components/src/app.jac +5 -5
- jac_client/examples/css-styling/tailwind-example/src/app.jac +5 -5
- jac_client/examples/full-stack-with-auth/src/app.jac +16 -16
- jac_client/examples/ts-support/src/app.jac +4 -4
- jac_client/examples/with-router/src/app.jac +4 -4
- jac_client/plugin/cli.jac +160 -203
- jac_client/plugin/client.jac +8 -15
- jac_client/plugin/client_runtime.cl.jac +18 -14
- jac_client/plugin/impl/client.impl.jac +85 -26
- jac_client/plugin/impl/client_runtime.impl.jac +27 -9
- jac_client/plugin/plugin_config.jac +11 -11
- jac_client/plugin/src/compiler.jac +2 -1
- jac_client/plugin/src/impl/babel_processor.impl.jac +22 -17
- jac_client/plugin/src/impl/compiler.impl.jac +55 -18
- jac_client/plugin/src/impl/vite_bundler.impl.jac +215 -102
- jac_client/plugin/src/package_installer.jac +1 -1
- jac_client/plugin/src/vite_bundler.jac +9 -1
- jac_client/tests/conftest.py +10 -8
- jac_client/tests/fixtures/spawn_test/app.jac +15 -18
- jac_client/tests/fixtures/with-ts/app.jac +4 -4
- jac_client/tests/test_cli.py +105 -49
- jac_client/tests/test_it.py +297 -82
- {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/METADATA +16 -7
- jac_client-0.2.8.dist-info/RECORD +97 -0
- jac_client/examples/all-in-one/src/app.jac +0 -841
- jac_client-0.2.6.dist-info/RECORD +0 -74
- /jac_client/examples/all-in-one/{src/button.jac → button.jac} +0 -0
- /jac_client/examples/all-in-one/{src/components → components}/button.jac +0 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/WHEEL +0 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/entry_points.txt +0 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.8.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Get the client build directory from project config."""
|
|
2
|
+
impl ViteBundler._get_client_dir(self: ViteBundler) -> Path {
|
|
3
|
+
# Try to get from project config
|
|
4
|
+
try {
|
|
5
|
+
import from jaclang.project.config { get_config }
|
|
6
|
+
config = get_config();
|
|
7
|
+
if config is not None {
|
|
8
|
+
return config.get_client_dir();
|
|
9
|
+
}
|
|
10
|
+
} except ImportError { }
|
|
11
|
+
# Fallback to default
|
|
12
|
+
return self.project_dir / '.jac' / 'client';
|
|
13
|
+
}
|
|
2
14
|
|
|
15
|
+
"""Create package.json from config.json during bundling."""
|
|
3
16
|
impl ViteBundler.create_package_json(
|
|
4
17
|
self: ViteBundler, project_name: Optional[str] = None
|
|
5
18
|
) -> Path {
|
|
6
|
-
build_dir = self.
|
|
7
|
-
build_dir.mkdir(exist_ok=True);
|
|
8
|
-
configs_dir = build_dir / '
|
|
19
|
+
build_dir = self._get_client_dir();
|
|
20
|
+
build_dir.mkdir(parents=True, exist_ok=True);
|
|
21
|
+
configs_dir = build_dir / 'configs';
|
|
9
22
|
configs_dir.mkdir(exist_ok=True);
|
|
10
23
|
package_config = self.config_loader.get_package_config();
|
|
11
24
|
package_json_path = configs_dir / 'package.json';
|
|
@@ -29,32 +42,12 @@ impl ViteBundler.create_package_json(
|
|
|
29
42
|
if not name {
|
|
30
43
|
name = self.project_dir.name or 'jac-app';
|
|
31
44
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
'react': '^18.2.0',
|
|
35
|
-
'react-dom': '^18.2.0',
|
|
36
|
-
'react-router-dom': '^6.22.0'
|
|
37
|
-
};
|
|
38
|
-
default_dev_dependencies = {
|
|
39
|
-
'vite': '^6.4.1',
|
|
40
|
-
'@babel/cli': '^7.28.3',
|
|
41
|
-
'@babel/core': '^7.28.5',
|
|
42
|
-
'@babel/preset-env': '^7.28.5',
|
|
43
|
-
'@babel/preset-react': '^7.28.5',
|
|
44
|
-
'@vitejs/plugin-react': '^4.2.1',
|
|
45
|
-
'typescript': '^5.3.3',
|
|
46
|
-
'@types/react': '^18.2.0',
|
|
47
|
-
'@types/react-dom': '^18.2.0'
|
|
48
|
-
};
|
|
49
|
-
# Merge user dependencies from TOML with defaults (user can override)
|
|
50
|
-
user_dependencies = package_config.get('dependencies', {});
|
|
51
|
-
user_dev_dependencies = package_config.get('devDependencies', {});
|
|
52
|
-
dependencies = {** default_dependencies, ** user_dependencies};
|
|
53
|
-
dev_dependencies = {** default_dev_dependencies, ** user_dev_dependencies};
|
|
45
|
+
dependencies = package_config.get('dependencies', {});
|
|
46
|
+
dev_dependencies = package_config.get('devDependencies', {});
|
|
54
47
|
scripts = {
|
|
55
|
-
'build': 'npm run compile && vite build --config .
|
|
56
|
-
'dev': 'vite dev --config .
|
|
57
|
-
'preview': 'vite preview --config .
|
|
48
|
+
'build': 'npm run compile && vite build --config .jac/client/configs/vite.config.js',
|
|
49
|
+
'dev': 'vite dev --config .jac/client/configs/vite.config.js',
|
|
50
|
+
'preview': 'vite preview --config .jac/client/configs/vite.config.js',
|
|
58
51
|
'compile': 'babel compiled --out-dir build --extensions ".jsx,.js" --out-file-extension .js'
|
|
59
52
|
};
|
|
60
53
|
user_scripts = package_config.get('scripts', {});
|
|
@@ -90,9 +83,9 @@ impl ViteBundler.create_package_json(
|
|
|
90
83
|
|
|
91
84
|
"""Create tsconfig.json during build time, merging user config from jac.toml."""
|
|
92
85
|
impl ViteBundler.create_tsconfig(self: ViteBundler) -> Path {
|
|
93
|
-
build_dir = self.
|
|
94
|
-
build_dir.mkdir(exist_ok=True);
|
|
95
|
-
configs_dir = build_dir / '
|
|
86
|
+
build_dir = self._get_client_dir();
|
|
87
|
+
build_dir.mkdir(parents=True, exist_ok=True);
|
|
88
|
+
configs_dir = build_dir / 'configs';
|
|
96
89
|
configs_dir.mkdir(exist_ok=True);
|
|
97
90
|
tsconfig_path = configs_dir / 'tsconfig.json';
|
|
98
91
|
# Default tsconfig settings
|
|
@@ -114,7 +107,7 @@ impl ViteBundler.create_tsconfig(self: ViteBundler) -> Path {
|
|
|
114
107
|
'noFallthroughCasesInSwitch': True
|
|
115
108
|
};
|
|
116
109
|
default_include = ['components/**/*'];
|
|
117
|
-
default_exclude = ['.
|
|
110
|
+
default_exclude = ['.jac'];
|
|
118
111
|
# Get user config from [plugins.client.ts] in jac.toml
|
|
119
112
|
user_ts_config = self.config_loader.get_ts_config();
|
|
120
113
|
user_compiler_options = user_ts_config.get('compilerOptions', {});
|
|
@@ -130,21 +123,21 @@ impl ViteBundler.create_tsconfig(self: ViteBundler) -> Path {
|
|
|
130
123
|
'include': merged_include,
|
|
131
124
|
'exclude': merged_exclude
|
|
132
125
|
};
|
|
133
|
-
# Write the config to .
|
|
126
|
+
# Write the config to .jac/client/configs/tsconfig.json (no root config file)
|
|
134
127
|
tsconfig_path.write_text(json.dumps(tsconfig_data, indent=2), encoding='utf-8');
|
|
135
128
|
return tsconfig_path;
|
|
136
129
|
}
|
|
137
130
|
|
|
138
131
|
"""
|
|
139
|
-
Clean up root package.json and move package-lock.json to
|
|
132
|
+
Clean up root package.json and move package-lock.json to configs/.
|
|
140
133
|
|
|
141
|
-
Remove root package.json and move package-lock.json to
|
|
134
|
+
Remove root package.json and move package-lock.json to configs/.
|
|
142
135
|
"""
|
|
143
136
|
impl ViteBundler._cleanup_root_package_files(self: ViteBundler) -> None {
|
|
144
137
|
root_package_json = self.project_dir / 'package.json';
|
|
145
138
|
root_package_lock = self.project_dir / 'package-lock.json';
|
|
146
|
-
build_dir = self.
|
|
147
|
-
configs_dir = build_dir / '
|
|
139
|
+
build_dir = self._get_client_dir();
|
|
140
|
+
configs_dir = build_dir / 'configs';
|
|
148
141
|
configs_package_lock = configs_dir / 'package-lock.json';
|
|
149
142
|
if root_package_lock.exists() {
|
|
150
143
|
configs_dir.mkdir(exist_ok=True);
|
|
@@ -164,7 +157,7 @@ Ensure root package.json exists temporarily for npm commands.
|
|
|
164
157
|
Create root package.json temporarily if it doesn't exist.
|
|
165
158
|
"""
|
|
166
159
|
impl ViteBundler._ensure_root_package_json(self: ViteBundler) -> None {
|
|
167
|
-
generated_package_json = self.
|
|
160
|
+
generated_package_json = self._get_client_dir() / 'configs' / 'package.json';
|
|
168
161
|
root_package_json = self.project_dir / 'package.json';
|
|
169
162
|
if not generated_package_json.exists() {
|
|
170
163
|
self.create_package_json();
|
|
@@ -235,51 +228,28 @@ impl ViteBundler._get_plugin_var_name(self: ViteBundler, plugin_name: str) -> st
|
|
|
235
228
|
|
|
236
229
|
"""Create vite.config.js from config.json during bundling."""
|
|
237
230
|
impl ViteBundler.create_vite_config(self: ViteBundler, entry_file: Path) -> Path {
|
|
238
|
-
build_dir = self.
|
|
239
|
-
build_dir.mkdir(exist_ok=True);
|
|
240
|
-
configs_dir = build_dir / '
|
|
231
|
+
build_dir = self._get_client_dir();
|
|
232
|
+
build_dir.mkdir(parents=True, exist_ok=True);
|
|
233
|
+
configs_dir = build_dir / 'configs';
|
|
241
234
|
configs_dir.mkdir(exist_ok=True);
|
|
242
235
|
vite_config_data = self.config_loader.get_vite_config();
|
|
243
236
|
config_path = configs_dir / 'vite.config.js';
|
|
244
237
|
# TypeScript is always enabled by default
|
|
245
|
-
build_dir = self.project_dir / '.client-build';
|
|
246
238
|
try {
|
|
247
|
-
# Entry file path relative to
|
|
239
|
+
# Entry file path relative to client build dir (not project root)
|
|
248
240
|
entry_relative = entry_file.relative_to(build_dir).as_posix();
|
|
249
241
|
} except ValueError {
|
|
250
|
-
# Fallback:
|
|
251
|
-
|
|
252
|
-
entry_relative_full = entry_file.relative_to(self.project_dir).as_posix();
|
|
253
|
-
prefix = '.client-build/';
|
|
254
|
-
if entry_relative_full.startswith(prefix) {
|
|
255
|
-
# Remove '.client-build/' prefix (15 characters)
|
|
256
|
-
entry_relative = entry_relative_full[15:];
|
|
257
|
-
} else {
|
|
258
|
-
entry_relative = entry_relative_full;
|
|
259
|
-
}
|
|
260
|
-
} except ValueError {
|
|
261
|
-
entry_relative = entry_file.as_posix();
|
|
262
|
-
}
|
|
242
|
+
# Fallback: use absolute path
|
|
243
|
+
entry_relative = entry_file.as_posix();
|
|
263
244
|
}
|
|
264
245
|
try {
|
|
265
|
-
# Output dir path relative to
|
|
246
|
+
# Output dir path relative to client build dir (not project root)
|
|
266
247
|
output_relative = self.output_dir.relative_to(build_dir).as_posix();
|
|
267
248
|
} except ValueError {
|
|
268
|
-
# Fallback:
|
|
269
|
-
|
|
270
|
-
output_relative_full = self.output_dir.relative_to(self.project_dir).as_posix();
|
|
271
|
-
prefix = '.client-build/';
|
|
272
|
-
if output_relative_full.startswith(prefix) {
|
|
273
|
-
# Remove '.client-build/' prefix (15 characters)
|
|
274
|
-
output_relative = output_relative_full[15:];
|
|
275
|
-
} else {
|
|
276
|
-
output_relative = output_relative_full;
|
|
277
|
-
}
|
|
278
|
-
} except ValueError {
|
|
279
|
-
output_relative = self.output_dir.as_posix();
|
|
280
|
-
}
|
|
249
|
+
# Fallback: use absolute path
|
|
250
|
+
output_relative = self.output_dir.as_posix();
|
|
281
251
|
}
|
|
282
|
-
# Calculate compiled directory path for aliases (relative to
|
|
252
|
+
# Calculate compiled directory path for aliases (relative to client build dir)
|
|
283
253
|
if entry_relative.endswith('/build/main.js') {
|
|
284
254
|
compiled_utils_relative = entry_relative[:-13] + '/compiled/client_runtime.js';
|
|
285
255
|
compiled_assets_relative = entry_relative[:-13] + '/compiled/assets';
|
|
@@ -333,9 +303,9 @@ impl ViteBundler.create_vite_config(self: ViteBundler, entry_file: Path) -> Path
|
|
|
333
303
|
import path from "path";
|
|
334
304
|
import {{ fileURLToPath }} from "url";
|
|
335
305
|
{imports_section}const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
336
|
-
// Config is in
|
|
306
|
+
// Config is in configs/ inside .jac/client/, so go up one level to .jac/client/, then up two more to project root
|
|
337
307
|
const buildDir = path.resolve(__dirname, "..");
|
|
338
|
-
const projectRoot = path.resolve(__dirname, "
|
|
308
|
+
const projectRoot = path.resolve(__dirname, "../../..");
|
|
339
309
|
|
|
340
310
|
/**
|
|
341
311
|
* Vite configuration generated from config.json (in project root)
|
|
@@ -344,13 +314,13 @@ const projectRoot = path.resolve(__dirname, "../..");
|
|
|
344
314
|
|
|
345
315
|
export default defineConfig({{
|
|
346
316
|
plugins: [{(newline + plugins_str + newline + ' ') if plugins_str else ''}],
|
|
347
|
-
root: buildDir, // base folder (.client
|
|
317
|
+
root: buildDir, // base folder (.jac/client/) so vite can find node_modules
|
|
348
318
|
build: {{
|
|
349
319
|
rollupOptions: {{
|
|
350
320
|
input: path.resolve(buildDir, "{entry_relative}"), // your compiled entry file
|
|
351
321
|
output: {{
|
|
352
322
|
entryFileNames: "client.[hash].js", // name of the final js file
|
|
353
|
-
assetFileNames:
|
|
323
|
+
assetFileNames: (assetInfo) => assetInfo.name?.endsWith('.css') ? 'styles.css' : '[name].[ext]',
|
|
354
324
|
}},
|
|
355
325
|
}},
|
|
356
326
|
outDir: path.resolve(buildDir, "{output_relative}"), // final bundled output
|
|
@@ -391,14 +361,8 @@ impl ViteBundler.read_bundle(self: ViteBundler) -> tuple[str, str] {
|
|
|
391
361
|
|
|
392
362
|
"""Find the generated Vite CSS file."""
|
|
393
363
|
impl ViteBundler.find_css(self: ViteBundler) -> Optional[Path] {
|
|
394
|
-
css_file = self.output_dir / '
|
|
395
|
-
if css_file.exists()
|
|
396
|
-
return css_file;
|
|
397
|
-
}
|
|
398
|
-
for file in self.output_dir.glob('*.css') {
|
|
399
|
-
return file;
|
|
400
|
-
}
|
|
401
|
-
return None;
|
|
364
|
+
css_file = self.output_dir / 'styles.css';
|
|
365
|
+
return css_file if css_file.exists() else None;
|
|
402
366
|
}
|
|
403
367
|
|
|
404
368
|
"""Find the generated Vite bundle file."""
|
|
@@ -409,10 +373,10 @@ impl ViteBundler.find_bundle(self: ViteBundler) -> Optional[Path] {
|
|
|
409
373
|
return None;
|
|
410
374
|
}
|
|
411
375
|
|
|
412
|
-
"""Run Vite build with generated config in .
|
|
376
|
+
"""Run Vite build with generated config in .jac/client/configs/."""
|
|
413
377
|
impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) -> None {
|
|
414
378
|
self.output_dir.mkdir(parents=True, exist_ok=True);
|
|
415
|
-
generated_package_json = self.
|
|
379
|
+
generated_package_json = self._get_client_dir() / 'configs' / 'package.json';
|
|
416
380
|
if not generated_package_json.exists() {
|
|
417
381
|
self.create_package_json();
|
|
418
382
|
} else {
|
|
@@ -420,18 +384,18 @@ impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) ->
|
|
|
420
384
|
self.create_tsconfig();
|
|
421
385
|
}
|
|
422
386
|
try {
|
|
423
|
-
build_dir = self.
|
|
387
|
+
build_dir = self._get_client_dir();
|
|
424
388
|
node_modules = build_dir / 'node_modules';
|
|
425
389
|
if not node_modules.exists() {
|
|
426
|
-
# Temporarily copy package.json to
|
|
390
|
+
# Temporarily copy package.json to client build dir for npm install
|
|
427
391
|
build_package_json = build_dir / 'package.json';
|
|
428
|
-
configs_package_json = build_dir / '
|
|
392
|
+
configs_package_json = build_dir / 'configs' / 'package.json';
|
|
429
393
|
if configs_package_json.exists() and not build_package_json.exists() {
|
|
430
394
|
import shutil;
|
|
431
395
|
shutil.copy2(configs_package_json, build_package_json);
|
|
432
396
|
}
|
|
433
397
|
try {
|
|
434
|
-
# Install to .client
|
|
398
|
+
# Install to .jac/client/node_modules
|
|
435
399
|
subprocess.run(
|
|
436
400
|
['npm', 'install'],
|
|
437
401
|
cwd=build_dir,
|
|
@@ -449,27 +413,24 @@ impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) ->
|
|
|
449
413
|
) ;
|
|
450
414
|
}
|
|
451
415
|
}
|
|
452
|
-
build_dir = self.project_dir / '.client-build';
|
|
453
416
|
if self.config_path {
|
|
454
417
|
# Make config path relative to build_dir (where vite runs from)
|
|
455
|
-
|
|
456
|
-
if '.client-build' in config_str {
|
|
457
|
-
# Config is in .client-build/, make it relative to build_dir
|
|
418
|
+
try {
|
|
458
419
|
config_rel = self.config_path.relative_to(build_dir);
|
|
459
420
|
command = ['npx', 'vite', 'build', '--config', str(config_rel)];
|
|
460
|
-
}
|
|
461
|
-
# Config is outside
|
|
421
|
+
} except ValueError {
|
|
422
|
+
# Config is outside client build dir, use absolute path
|
|
462
423
|
command = ['npx', 'vite', 'build', '--config', str(self.config_path)];
|
|
463
424
|
}
|
|
464
425
|
} elif entry_file {
|
|
465
426
|
generated_config = self.create_vite_config(entry_file);
|
|
466
|
-
# Config is in
|
|
427
|
+
# Config is in configs/, make it relative to build_dir
|
|
467
428
|
config_rel = generated_config.relative_to(build_dir);
|
|
468
429
|
command = ['npx', 'vite', 'build', '--config', str(config_rel)];
|
|
469
430
|
} else {
|
|
470
431
|
command = ['npm', 'run', 'build'];
|
|
471
432
|
}
|
|
472
|
-
# Run vite from
|
|
433
|
+
# Run vite from client build directory so it can find node_modules
|
|
473
434
|
result = subprocess.run(
|
|
474
435
|
command, cwd=build_dir, check=False, capture_output=True, text=True
|
|
475
436
|
);
|
|
@@ -480,15 +441,15 @@ impl ViteBundler.build(self: ViteBundler, entry_file: Optional[Path] = None) ->
|
|
|
480
441
|
) from None ;
|
|
481
442
|
}
|
|
482
443
|
} finally {
|
|
483
|
-
# Clean up temporary package.json in
|
|
444
|
+
# Clean up temporary package.json in client build dir
|
|
484
445
|
build_package_json = build_dir / 'package.json';
|
|
485
446
|
if build_package_json.exists() {
|
|
486
447
|
build_package_json.unlink();
|
|
487
448
|
}
|
|
488
|
-
# Move package-lock.json to
|
|
449
|
+
# Move package-lock.json to configs/ if it exists
|
|
489
450
|
build_package_lock = build_dir / 'package-lock.json';
|
|
490
451
|
if build_package_lock.exists() {
|
|
491
|
-
configs_package_lock = build_dir / '
|
|
452
|
+
configs_package_lock = build_dir / 'configs' / 'package-lock.json';
|
|
492
453
|
if configs_package_lock.exists() {
|
|
493
454
|
configs_package_lock.unlink();
|
|
494
455
|
}
|
|
@@ -506,8 +467,160 @@ impl ViteBundler.init(
|
|
|
506
467
|
config_path: Optional[Path] = None
|
|
507
468
|
) {
|
|
508
469
|
self.project_dir = project_dir;
|
|
509
|
-
self.output_dir = output_dir or (project_dir / '.client-build' / 'dist');
|
|
510
470
|
self.minify = minify;
|
|
511
471
|
self.config_path = config_path;
|
|
512
472
|
self.config_loader = JacClientConfig(project_dir);
|
|
473
|
+
# Set output_dir after config_loader is initialized so _get_client_dir works
|
|
474
|
+
self.output_dir = output_dir or (self._get_client_dir() / 'dist');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
"""Create a dev-mode vite config with API proxy for HMR."""
|
|
478
|
+
impl ViteBundler.create_dev_vite_config(
|
|
479
|
+
self: ViteBundler, entry_file: Path, api_port: int = 8000
|
|
480
|
+
) -> Path {
|
|
481
|
+
build_dir = self._get_client_dir();
|
|
482
|
+
build_dir.mkdir(parents=True, exist_ok=True);
|
|
483
|
+
configs_dir = build_dir / 'configs';
|
|
484
|
+
configs_dir.mkdir(exist_ok=True);
|
|
485
|
+
config_path = configs_dir / 'vite.dev.config.js';
|
|
486
|
+
# Get entry file relative path
|
|
487
|
+
try {
|
|
488
|
+
entry_relative = entry_file.relative_to(build_dir).as_posix();
|
|
489
|
+
} except ValueError {
|
|
490
|
+
entry_relative = entry_file.as_posix();
|
|
491
|
+
}
|
|
492
|
+
# Calculate paths for aliases
|
|
493
|
+
if entry_relative.endswith('/build/main.js') {
|
|
494
|
+
compiled_utils_relative = entry_relative[:-13] + '/compiled/client_runtime.js';
|
|
495
|
+
compiled_assets_relative = entry_relative[:-13] + '/compiled/assets';
|
|
496
|
+
} elif entry_relative.endswith('build/main.js') {
|
|
497
|
+
compiled_utils_relative = 'compiled/client_runtime.js';
|
|
498
|
+
compiled_assets_relative = 'compiled/assets';
|
|
499
|
+
} else {
|
|
500
|
+
compiled_utils_relative = 'compiled/client_runtime.js';
|
|
501
|
+
compiled_assets_relative = 'compiled/assets';
|
|
502
|
+
}
|
|
503
|
+
# Extensions for TypeScript
|
|
504
|
+
extensions = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'];
|
|
505
|
+
extensions_str = ', '.join(f'"{ext}"' for ext in extensions);
|
|
506
|
+
# Generate dev config with proxy for API routes
|
|
507
|
+
config_content = f'''import {{ defineConfig }} from "vite";
|
|
508
|
+
import path from "path";
|
|
509
|
+
import {{ fileURLToPath }} from "url";
|
|
510
|
+
import react from "@vitejs/plugin-react";
|
|
511
|
+
|
|
512
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
513
|
+
const buildDir = path.resolve(__dirname, "..");
|
|
514
|
+
const projectRoot = path.resolve(__dirname, "../../..");
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Vite DEV configuration for HMR mode
|
|
518
|
+
* Proxies API routes to Python server at localhost:{api_port}
|
|
519
|
+
*/
|
|
520
|
+
export default defineConfig({{
|
|
521
|
+
plugins: [react()],
|
|
522
|
+
root: buildDir,
|
|
523
|
+
publicDir: false,
|
|
524
|
+
server: {{
|
|
525
|
+
watch: {{
|
|
526
|
+
usePolling: true,
|
|
527
|
+
interval: 100,
|
|
528
|
+
}},
|
|
529
|
+
proxy: {{
|
|
530
|
+
"/walker": {{
|
|
531
|
+
target: "http://localhost:{api_port}",
|
|
532
|
+
changeOrigin: true,
|
|
533
|
+
}},
|
|
534
|
+
"/function": {{
|
|
535
|
+
target: "http://localhost:{api_port}",
|
|
536
|
+
changeOrigin: true,
|
|
537
|
+
}},
|
|
538
|
+
"/user": {{
|
|
539
|
+
target: "http://localhost:{api_port}",
|
|
540
|
+
changeOrigin: true,
|
|
541
|
+
}},
|
|
542
|
+
"/introspect": {{
|
|
543
|
+
target: "http://localhost:{api_port}",
|
|
544
|
+
changeOrigin: true,
|
|
545
|
+
}},
|
|
546
|
+
}},
|
|
547
|
+
}},
|
|
548
|
+
resolve: {{
|
|
549
|
+
alias: {{
|
|
550
|
+
"@jac-client/utils": path.resolve(buildDir, "{compiled_utils_relative}"),
|
|
551
|
+
"@jac-client/assets": path.resolve(buildDir, "{compiled_assets_relative}"),
|
|
552
|
+
}},
|
|
553
|
+
extensions: [{extensions_str}],
|
|
554
|
+
}},
|
|
555
|
+
}});
|
|
556
|
+
''';
|
|
557
|
+
config_path.write_text(config_content, encoding='utf-8');
|
|
558
|
+
return config_path;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
"""Start Vite dev server as a subprocess."""
|
|
562
|
+
impl ViteBundler.start_dev_server(self: ViteBundler, port: int = 3000) -> Any {
|
|
563
|
+
build_dir = self._get_client_dir();
|
|
564
|
+
node_modules = build_dir / 'node_modules';
|
|
565
|
+
# Create/update index.html for dev server (load from compiled/ for HMR)
|
|
566
|
+
index_html = build_dir / 'index.html';
|
|
567
|
+
index_content = '''<!DOCTYPE html>
|
|
568
|
+
<html lang="en">
|
|
569
|
+
<head>
|
|
570
|
+
<meta charset="UTF-8" />
|
|
571
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
572
|
+
<title>Jac App (Dev)</title>
|
|
573
|
+
</head>
|
|
574
|
+
<body>
|
|
575
|
+
<div id="root"></div>
|
|
576
|
+
<script type="module" src="/compiled/_entry.js"></script>
|
|
577
|
+
</body>
|
|
578
|
+
</html>
|
|
579
|
+
''';
|
|
580
|
+
index_html.write_text(index_content, encoding='utf-8');
|
|
581
|
+
# Ensure dependencies are installed
|
|
582
|
+
if not node_modules.exists() {
|
|
583
|
+
generated_package_json = build_dir / 'configs' / 'package.json';
|
|
584
|
+
if not generated_package_json.exists() {
|
|
585
|
+
self.create_package_json();
|
|
586
|
+
}
|
|
587
|
+
# Temporarily copy package.json for npm install
|
|
588
|
+
build_package_json = build_dir / 'package.json';
|
|
589
|
+
if not build_package_json.exists() {
|
|
590
|
+
shutil.copy2(generated_package_json, build_package_json);
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
print("[Vite] Installing dependencies...");
|
|
594
|
+
subprocess.run(
|
|
595
|
+
['npm', 'install'],
|
|
596
|
+
cwd=build_dir,
|
|
597
|
+
check=True,
|
|
598
|
+
capture_output=True,
|
|
599
|
+
text=True
|
|
600
|
+
);
|
|
601
|
+
} except subprocess.CalledProcessError as e {
|
|
602
|
+
print(f"[Vite] Error installing dependencies: {e.stderr}");
|
|
603
|
+
raise ;
|
|
604
|
+
} finally {
|
|
605
|
+
# Clean up temp package.json
|
|
606
|
+
if build_package_json.exists() {
|
|
607
|
+
build_package_json.unlink();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
# Find the dev config
|
|
612
|
+
dev_config = build_dir / 'configs' / 'vite.dev.config.js';
|
|
613
|
+
if not dev_config.exists() {
|
|
614
|
+
raise ClientBundleError(
|
|
615
|
+
"Dev config not found. Call create_dev_vite_config first."
|
|
616
|
+
) ;
|
|
617
|
+
}
|
|
618
|
+
config_rel = dev_config.relative_to(build_dir);
|
|
619
|
+
print(f"[Vite] Starting dev server on port {port}...");
|
|
620
|
+
# Start Vite in dev mode (let output go to terminal for HMR visibility)
|
|
621
|
+
process = subprocess.Popen(
|
|
622
|
+
['npx', 'vite', '--config', str(config_rel), '--port', str(port)],
|
|
623
|
+
cwd=build_dir
|
|
624
|
+
);
|
|
625
|
+
return process;
|
|
513
626
|
}
|
|
@@ -4,7 +4,7 @@ import json;
|
|
|
4
4
|
import shutil;
|
|
5
5
|
import subprocess;
|
|
6
6
|
import from pathlib { Path }
|
|
7
|
-
import from typing { Optional }
|
|
7
|
+
import from typing { Any, Optional }
|
|
8
8
|
import from jaclang.runtimelib.client_bundle { ClientBundleError }
|
|
9
9
|
import from .config_loader { JacClientConfig }
|
|
10
10
|
"""Handles Vite bundling operations."""
|
|
@@ -17,6 +17,7 @@ class ViteBundler {
|
|
|
17
17
|
config_path: Optional[Path] = None
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
+
def _get_client_dir(self: ViteBundler) -> Path;
|
|
20
21
|
def build(self: ViteBundler, entry_file: Optional[Path] = None) -> None;
|
|
21
22
|
def find_bundle(self: ViteBundler) -> Optional[Path];
|
|
22
23
|
def find_css(self: ViteBundler) -> Optional[Path];
|
|
@@ -33,4 +34,11 @@ class ViteBundler {
|
|
|
33
34
|
) -> Path;
|
|
34
35
|
|
|
35
36
|
def create_tsconfig(self: ViteBundler) -> Path;
|
|
37
|
+
"""Create a dev-mode vite config with API proxy for HMR."""
|
|
38
|
+
def create_dev_vite_config(
|
|
39
|
+
self: ViteBundler, entry_file: Path, api_port: int = 8000
|
|
40
|
+
) -> Path;
|
|
41
|
+
|
|
42
|
+
"""Start Vite dev server as a subprocess."""
|
|
43
|
+
def start_dev_server(self: ViteBundler, port: int = 3000) -> Any;
|
|
36
44
|
}
|
jac_client/tests/conftest.py
CHANGED
|
@@ -108,7 +108,7 @@ def npm_cache_dir() -> Generator[Path, None, None]:
|
|
|
108
108
|
"""Session-scoped fixture that provides a directory with npm packages installed.
|
|
109
109
|
|
|
110
110
|
This runs npm install once per test session and provides the path to the
|
|
111
|
-
.jac
|
|
111
|
+
.jac/client/configs directory containing node_modules.
|
|
112
112
|
"""
|
|
113
113
|
global _npm_cache_dir
|
|
114
114
|
|
|
@@ -156,13 +156,14 @@ def vite_project_dir(npm_cache_dir: Path, tmp_path: Path) -> Path:
|
|
|
156
156
|
jac_toml = tmp_path / "jac.toml"
|
|
157
157
|
jac_toml.write_text(_get_minimal_jac_toml())
|
|
158
158
|
|
|
159
|
-
# Copy .jac
|
|
160
|
-
source_configs = npm_cache_dir / ".jac
|
|
161
|
-
dest_configs = tmp_path / ".jac
|
|
159
|
+
# Copy .jac/client/configs directory (contains package.json)
|
|
160
|
+
source_configs = npm_cache_dir / ".jac" / "client" / "configs"
|
|
161
|
+
dest_configs = tmp_path / ".jac" / "client" / "configs"
|
|
162
162
|
if source_configs.exists():
|
|
163
|
+
dest_configs.parent.mkdir(parents=True, exist_ok=True)
|
|
163
164
|
shutil.copytree(source_configs, dest_configs, symlinks=True)
|
|
164
165
|
|
|
165
|
-
# Copy node_modules from project root (npm installs there, not in .jac
|
|
166
|
+
# Copy node_modules from project root (npm installs there, not in .jac/client/configs)
|
|
166
167
|
source_node_modules = npm_cache_dir / "node_modules"
|
|
167
168
|
dest_node_modules = tmp_path / "node_modules"
|
|
168
169
|
if source_node_modules.exists():
|
|
@@ -195,10 +196,11 @@ antd = "^6.0.0"
|
|
|
195
196
|
jac_toml = tmp_path / "jac.toml"
|
|
196
197
|
jac_toml.write_text(jac_toml_content)
|
|
197
198
|
|
|
198
|
-
# Copy base .jac
|
|
199
|
-
source_configs = npm_cache_dir / ".jac
|
|
200
|
-
dest_configs = tmp_path / ".jac
|
|
199
|
+
# Copy base .jac/client/configs first for faster install
|
|
200
|
+
source_configs = npm_cache_dir / ".jac" / "client" / "configs"
|
|
201
|
+
dest_configs = tmp_path / ".jac" / "client" / "configs"
|
|
201
202
|
if source_configs.exists():
|
|
203
|
+
dest_configs.parent.mkdir(parents=True, exist_ok=True)
|
|
202
204
|
shutil.copytree(source_configs, dest_configs, symlinks=True)
|
|
203
205
|
|
|
204
206
|
# Copy base node_modules for faster install (npm will add antd on top)
|
|
@@ -32,51 +32,48 @@ walker positional_walker {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
# Client-side code testing both spawn orderings
|
|
35
|
-
cl import from react {
|
|
36
|
-
useState,
|
|
37
|
-
useEffect
|
|
38
|
-
}
|
|
35
|
+
cl import from react { useEffect }
|
|
39
36
|
|
|
40
37
|
cl {
|
|
41
38
|
def app() -> any {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
has standardResult: any = None;
|
|
40
|
+
has standardComputed: any = None;
|
|
41
|
+
has reverseResult: any = None;
|
|
42
|
+
has uuidResult: any = None;
|
|
43
|
+
has reverseUuidResult: any = None;
|
|
44
|
+
has positionalResult: any = None;
|
|
45
|
+
has spreadResult: any = None;
|
|
49
46
|
|
|
50
47
|
async def loadData() -> None {
|
|
51
48
|
# Test standard spawn order: node spawn walker()
|
|
52
49
|
data1 = root spawn test_walker();
|
|
53
|
-
|
|
50
|
+
standardResult = data1;
|
|
54
51
|
|
|
55
52
|
data2 = root spawn parameterized_walker(value=42);
|
|
56
|
-
|
|
53
|
+
standardComputed = data2;
|
|
57
54
|
|
|
58
55
|
# Test reverse spawn order: walker() spawn node
|
|
59
56
|
data3 = test_walker(message="Reverse spawn!") spawn root;
|
|
60
|
-
|
|
57
|
+
reverseResult = data3;
|
|
61
58
|
|
|
62
59
|
# Test spawn with UUID string: uuid_string spawn walker()
|
|
63
60
|
node_id = "550e8400-e29b-41d4-a716-446655440000";
|
|
64
61
|
data4 = node_id spawn test_walker();
|
|
65
|
-
|
|
62
|
+
uuidResult = data4;
|
|
66
63
|
|
|
67
64
|
# Test reverse spawn with UUID string: walker() spawn uuid_string
|
|
68
65
|
another_node_id = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
|
|
69
66
|
data5 = parameterized_walker(value=100) spawn another_node_id;
|
|
70
|
-
|
|
67
|
+
reverseUuidResult = data5;
|
|
71
68
|
|
|
72
69
|
# Test positional walker arguments inferred from has fields
|
|
73
70
|
data6 = node_id spawn positional_walker("Node positional", 2);
|
|
74
|
-
|
|
71
|
+
positionalResult = data6;
|
|
75
72
|
|
|
76
73
|
# Test **kwargs via spread when walker is on left-hand side
|
|
77
74
|
extra_fields = {"metadata": {"source": "client-side"}};
|
|
78
75
|
data7 = positional_walker("Spread order", 5, **extra_fields) spawn root;
|
|
79
|
-
|
|
76
|
+
spreadResult = data7;
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
useEffect(lambda -> None{ loadData();} , []);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
# Pages
|
|
3
|
-
cl import from react {
|
|
3
|
+
cl import from react { useEffect }
|
|
4
4
|
cl import from ".components/Button.tsx" { Button }
|
|
5
5
|
|
|
6
6
|
cl {
|
|
7
7
|
def app() -> any {
|
|
8
|
-
|
|
8
|
+
has count: int = 0;
|
|
9
9
|
useEffect(lambda -> None{ console.log("Count: ", count);} , [count]);
|
|
10
10
|
return <div
|
|
11
11
|
style={{padding: "2rem", fontFamily: "Arial, sans-serif"}}
|
|
@@ -21,12 +21,12 @@ cl {
|
|
|
21
21
|
>
|
|
22
22
|
<Button
|
|
23
23
|
label="Increment"
|
|
24
|
-
onClick={lambda -> None{
|
|
24
|
+
onClick={lambda -> None{ count = count + 1;} }
|
|
25
25
|
variant="primary"
|
|
26
26
|
/>
|
|
27
27
|
<Button
|
|
28
28
|
label="Reset"
|
|
29
|
-
onClick={lambda -> None{
|
|
29
|
+
onClick={lambda -> None{ count = 0;} }
|
|
30
30
|
variant="secondary"
|
|
31
31
|
/>
|
|
32
32
|
</div>
|