jac-client 0.2.4__py3-none-any.whl → 0.2.5__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.
Files changed (70) hide show
  1. jac_client/examples/all-in-one/src/app.jac +841 -0
  2. jac_client/examples/all-in-one/src/button.jac +7 -0
  3. jac_client/examples/all-in-one/src/components/button.jac +7 -0
  4. jac_client/examples/asset-serving/css-with-image/src/app.jac +88 -0
  5. jac_client/examples/asset-serving/image-asset/src/app.jac +55 -0
  6. jac_client/examples/asset-serving/import-alias/src/app.jac +111 -0
  7. jac_client/examples/basic/src/app.jac +21 -0
  8. jac_client/examples/basic-auth/src/app.jac +377 -0
  9. jac_client/examples/basic-auth-with-router/src/app.jac +464 -0
  10. jac_client/examples/basic-full-stack/src/app.jac +365 -0
  11. jac_client/examples/css-styling/js-styling/src/app.jac +84 -0
  12. jac_client/examples/css-styling/material-ui/src/app.jac +122 -0
  13. jac_client/examples/css-styling/pure-css/src/app.jac +64 -0
  14. jac_client/examples/css-styling/sass-example/src/app.jac +64 -0
  15. jac_client/examples/css-styling/styled-components/src/app.jac +71 -0
  16. jac_client/examples/css-styling/tailwind-example/src/app.jac +63 -0
  17. jac_client/examples/full-stack-with-auth/src/app.jac +722 -0
  18. jac_client/examples/little-x/src/app.jac +719 -0
  19. jac_client/examples/little-x/src/submit-button.jac +16 -0
  20. jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +11 -0
  21. jac_client/examples/nested-folders/nested-advance/src/app.jac +35 -0
  22. jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +19 -0
  23. jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +43 -0
  24. jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +25 -0
  25. jac_client/examples/nested-folders/nested-basic/src/app.jac +13 -0
  26. jac_client/examples/nested-folders/nested-basic/src/button.jac +7 -0
  27. jac_client/examples/nested-folders/nested-basic/src/components/button.jac +7 -0
  28. jac_client/examples/ts-support/src/app.jac +35 -0
  29. jac_client/examples/with-router/src/app.jac +323 -0
  30. jac_client/plugin/cli.jac +547 -0
  31. jac_client/plugin/client.jac +52 -0
  32. jac_client/plugin/client_runtime.cl.jac +38 -0
  33. jac_client/plugin/impl/client.impl.jac +134 -0
  34. jac_client/plugin/impl/client_runtime.impl.jac +177 -0
  35. jac_client/plugin/impl/vite_client_bundle.impl.jac +72 -0
  36. jac_client/plugin/plugin_config.jac +195 -0
  37. jac_client/plugin/src/__init__.jac +20 -0
  38. jac_client/plugin/src/asset_processor.jac +33 -0
  39. jac_client/plugin/src/babel_processor.jac +18 -0
  40. jac_client/plugin/src/compiler.jac +66 -0
  41. jac_client/plugin/src/config_loader.jac +32 -0
  42. jac_client/plugin/src/impl/asset_processor.impl.jac +127 -0
  43. jac_client/plugin/src/impl/babel_processor.impl.jac +84 -0
  44. jac_client/plugin/src/impl/compiler.impl.jac +251 -0
  45. jac_client/plugin/src/impl/config_loader.impl.jac +119 -0
  46. jac_client/plugin/src/impl/import_processor.impl.jac +33 -0
  47. jac_client/plugin/src/impl/jac_to_js.impl.jac +41 -0
  48. jac_client/plugin/src/impl/package_installer.impl.jac +105 -0
  49. jac_client/plugin/src/impl/vite_bundler.impl.jac +513 -0
  50. jac_client/plugin/src/import_processor.jac +19 -0
  51. jac_client/plugin/src/jac_to_js.jac +35 -0
  52. jac_client/plugin/src/package_installer.jac +26 -0
  53. jac_client/plugin/src/vite_bundler.jac +36 -0
  54. jac_client/plugin/vite_client_bundle.jac +31 -0
  55. jac_client/tests/fixtures/basic-app/app.jac +23 -0
  56. jac_client/tests/fixtures/cl_file/app.cl.jac +48 -0
  57. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  58. jac_client/tests/fixtures/client_app_with_antd/app.jac +34 -0
  59. jac_client/tests/fixtures/js_import/app.jac +34 -0
  60. jac_client/tests/fixtures/relative_import/app.jac +11 -0
  61. jac_client/tests/fixtures/relative_import/button.jac +7 -0
  62. jac_client/tests/fixtures/spawn_test/app.jac +129 -0
  63. jac_client/tests/fixtures/test_fragments_spread/app.jac +67 -0
  64. jac_client/tests/fixtures/with-ts/app.jac +35 -0
  65. {jac_client-0.2.4.dist-info → jac_client-0.2.5.dist-info}/METADATA +2 -2
  66. jac_client-0.2.5.dist-info/RECORD +74 -0
  67. jac_client-0.2.4.dist-info/RECORD +0 -10
  68. {jac_client-0.2.4.dist-info → jac_client-0.2.5.dist-info}/WHEEL +0 -0
  69. {jac_client-0.2.4.dist-info → jac_client-0.2.5.dist-info}/entry_points.txt +0 -0
  70. {jac_client-0.2.4.dist-info → jac_client-0.2.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,66 @@
1
+ """Main compilation orchestration module."""
2
+ import contextlib;
3
+ import from collections.abc { Callable }
4
+ import from pathlib { Path }
5
+ import from types { ModuleType }
6
+ import from typing { TYPE_CHECKING, Any }
7
+ import from jaclang.runtimelib.client_bundle { ClientBundleError }
8
+ import from .asset_processor { AssetProcessor }
9
+ import from .babel_processor { BabelProcessor }
10
+ import from .import_processor { ImportProcessor }
11
+ import from .jac_to_js { JacToJSCompiler }
12
+ import from .vite_bundler { ViteBundler }
13
+
14
+ with entry {
15
+ if TYPE_CHECKING { }
16
+ }
17
+
18
+ """Orchestrates the compilation process for Vite client bundles."""
19
+ class ViteCompiler {
20
+ with entry {
21
+ ROUTER_EXPORTS = [
22
+ 'Router',
23
+ 'Routes',
24
+ 'Route',
25
+ 'Link',
26
+ 'Navigate',
27
+ 'useNavigate',
28
+ 'useLocation',
29
+ 'useParams'
30
+ ];
31
+ }
32
+
33
+ def init(
34
+ self: ViteCompiler,
35
+ vite_package_json: Path,
36
+ vite_output_dir: (Path | None) = None,
37
+ vite_minify: bool = False,
38
+ runtime_path: (Path | None) = None,
39
+ compile_to_js_func:
40
+ (Callable[([Path], tuple[(str, (ModuleType | None))])] | None) = None,
41
+ extract_exports_func: (Callable[([Any], list[str])] | None) = None,
42
+ extract_globals_func: (Callable[([Any, ModuleType], dict[(str, Any)])] | None) = None
43
+ );
44
+
45
+ def compile_runtime_utils(self: ViteCompiler) -> tuple[str, list[str]];
46
+ def compile_dependencies_recursively(
47
+ self: ViteCompiler,
48
+ module_path: Path,
49
+ visited: (set[Path] | None) = None,
50
+ collected_exports: (set[str] | None) = None,
51
+ collected_globals: (dict[(str, Any)] | None) = None,
52
+ source_root: (Path | None) = None
53
+ ) -> None;
54
+
55
+ def _copy_js_file(self: ViteCompiler, js_path: Path, source_root: Path) -> None;
56
+ def _copy_ts_file(self: ViteCompiler, ts_path: Path, source_root: Path) -> None;
57
+ def _copy_asset_file(
58
+ self: ViteCompiler, asset_path: Path, source_root: Path
59
+ ) -> None;
60
+
61
+ def copy_root_assets(self: ViteCompiler) -> None;
62
+ def create_entry_file(self: ViteCompiler) -> None;
63
+ def compile_and_bundle(
64
+ self: ViteCompiler, module: ModuleType, module_path: Path
65
+ ) -> tuple[str, str, list[str], list[str]];
66
+ }
@@ -0,0 +1,32 @@
1
+ """Configuration loader for Jac Client build system.
2
+
3
+ This module provides configuration access for the client plugin.
4
+ Configuration is loaded from jac.toml under [plugins.client] section.
5
+
6
+ Inherits from PluginConfigBase to eliminate boilerplate code.
7
+ """
8
+ import from pathlib { Path }
9
+ import from typing { Any }
10
+ import from jaclang.project.plugin_config { PluginConfigBase }
11
+
12
+ """Client-specific configuration loader.
13
+
14
+ Provides access to vite, ts, and package configuration
15
+ from the [plugins.client] section of jac.toml.
16
+ """
17
+ class JacClientConfig(PluginConfigBase) {
18
+ override def get_plugin_name(self: JacClientConfig) -> str;
19
+ override def get_default_config(self: JacClientConfig) -> dict[str, Any];
20
+ override def load(self: JacClientConfig) -> dict[str, Any];
21
+ def save(self: JacClientConfig) -> None;
22
+ def get_vite_config(self: JacClientConfig) -> dict[str, Any];
23
+ def get_ts_config(self: JacClientConfig) -> dict[str, Any];
24
+ def get_package_config(self: JacClientConfig) -> dict[str, Any];
25
+ def add_dependency(
26
+ self: JacClientConfig, name: str, version: str, is_dev: bool = False
27
+ ) -> None;
28
+
29
+ def remove_dependency(
30
+ self: JacClientConfig, name: str, is_dev: bool = False
31
+ ) -> bool;
32
+ }
@@ -0,0 +1,127 @@
1
+ """Copy custom asset files (based on jac.toml configuration) from source to destination recursively."""
2
+
3
+ impl AssetProcessor.copy_custom_asset_types(
4
+ self: AssetProcessor,
5
+ src_dir: Path,
6
+ dest_dir: Path,
7
+ preserve_structure: bool = True
8
+ ) -> None {
9
+ if not src_dir.exists() {
10
+ return;
11
+ }
12
+ dest_dir.mkdir(parents=True, exist_ok=True);
13
+ def copy_recursive(
14
+ source: Path, destination: Path, base: (Path | None) = None
15
+ ) -> None {
16
+ if not source.exists() {
17
+ return;
18
+ }
19
+ if (base is None) {
20
+ base = source;
21
+ }
22
+ for item in source.iterdir() {
23
+ if (item.is_file() and (item.suffix.lower() in custom_asset_extensions)) {
24
+ if preserve_structure {
25
+ relative_path = item.relative_to(base);
26
+ dest_file = destination / relative_path;
27
+ } else {
28
+ dest_file = destination / item.name;
29
+ }
30
+ dest_file.parent.mkdir(parents=True, exist_ok=True);
31
+ with contextlib.suppress(OSError, shutil.Error) {
32
+ shutil.copy2(item, dest_file);
33
+ }
34
+ } elif item.is_dir() {
35
+ copy_recursive(item, destination, base);
36
+ }
37
+ }
38
+ }
39
+ config = get_config();
40
+ assets_config = config.get_plugin_config("client").get("assets", {});
41
+ custom_asset_extensions = set(assets_config.get("custom_extensions", []));
42
+ if custom_asset_extensions {
43
+ copy_recursive(src_dir, dest_dir);
44
+ }
45
+ return;
46
+ }
47
+
48
+ """Copy TypeScript files (.ts, .tsx) from source to destination recursively."""
49
+ impl AssetProcessor.copy_typescript_files(
50
+ self: AssetProcessor,
51
+ src_dir: Path,
52
+ dest_dir: Path,
53
+ preserve_structure: bool = True
54
+ ) -> None {
55
+ if not src_dir.exists() {
56
+ return;
57
+ }
58
+ dest_dir.mkdir(parents=True, exist_ok=True);
59
+ ts_extensions = {'.ts','.tsx'};
60
+ def copy_recursive(
61
+ source: Path, destination: Path, base: (Path | None) = None
62
+ ) -> None {
63
+ if not source.exists() {
64
+ return;
65
+ }
66
+ if (base is None) {
67
+ base = source;
68
+ }
69
+ for item in source.iterdir() {
70
+ if (item.is_file() and (item.suffix.lower() in ts_extensions)) {
71
+ if preserve_structure {
72
+ relative_path = item.relative_to(base);
73
+ dest_file = destination / relative_path;
74
+ } else {
75
+ dest_file = destination / item.name;
76
+ }
77
+ dest_file.parent.mkdir(parents=True, exist_ok=True);
78
+ with contextlib.suppress(OSError, shutil.Error) {
79
+ shutil.copy2(item, dest_file);
80
+ }
81
+ } elif item.is_dir() {
82
+ copy_recursive(item, destination, base);
83
+ }
84
+ }
85
+ }
86
+ copy_recursive(src_dir, dest_dir);
87
+ }
88
+
89
+ """Copy CSS and other asset files from source to destination recursively."""
90
+ impl AssetProcessor.copy_assets(
91
+ self: AssetProcessor,
92
+ src_dir: Path,
93
+ dest_dir: Path,
94
+ preserve_structure: bool = True
95
+ ) -> None {
96
+ if not src_dir.exists() {
97
+ return;
98
+ }
99
+ dest_dir.mkdir(parents=True, exist_ok=True);
100
+ def copy_recursive(
101
+ source: Path, destination: Path, base: (Path | None) = None
102
+ ) -> None {
103
+ if not source.exists() {
104
+ return;
105
+ }
106
+ if (base is None) {
107
+ base = source;
108
+ }
109
+ for item in source.iterdir() {
110
+ if (item.is_file() and (item.suffix.lower() in self.ASSET_EXTENSIONS)) {
111
+ if preserve_structure {
112
+ relative_path = item.relative_to(base);
113
+ dest_file = destination / relative_path;
114
+ } else {
115
+ dest_file = destination / item.name;
116
+ }
117
+ dest_file.parent.mkdir(parents=True, exist_ok=True);
118
+ with contextlib.suppress(OSError, shutil.Error) {
119
+ shutil.copy2(item, dest_file);
120
+ }
121
+ } elif item.is_dir() {
122
+ copy_recursive(item, destination, base);
123
+ }
124
+ }
125
+ }
126
+ copy_recursive(src_dir, dest_dir);
127
+ }
@@ -0,0 +1,84 @@
1
+ """Copy CSS, assets, and TypeScript files from compiled/ to build/ after Babel compilation."""
2
+
3
+ impl BabelProcessor.copy_assets_after_compile(
4
+ self: BabelProcessor,
5
+ compiled_dir: Path,
6
+ build_dir: Path,
7
+ asset_processor: AssetProcessor
8
+ ) -> None {
9
+ asset_processor.copy_assets(compiled_dir, build_dir);
10
+ asset_processor.copy_typescript_files(compiled_dir, build_dir);
11
+ }
12
+
13
+ """Run Babel compilation (npm run compile)."""
14
+ impl BabelProcessor.compile(self: BabelProcessor) -> None {
15
+ bundler = ViteBundler(self.project_dir);
16
+ # Ensure root package.json exists temporarily for npm commands
17
+ bundler._ensure_root_package_json();
18
+ try {
19
+ # Ensure dependencies are installed (check if node_modules exists)
20
+ build_dir = self.project_dir / '.client-build';
21
+ node_modules = build_dir / 'node_modules';
22
+ if not node_modules.exists() {
23
+ # Temporarily copy package.json to .client-build/ for npm install
24
+ build_package_json = build_dir / 'package.json';
25
+ configs_package_json = build_dir / '.jac-client.configs' / 'package.json';
26
+ if configs_package_json.exists() and not build_package_json.exists() {
27
+ import shutil;
28
+ shutil.copy2(configs_package_json, build_package_json);
29
+ }
30
+ try {
31
+ # Install to .client-build/node_modules
32
+ subprocess.run(
33
+ ['npm', 'install'],
34
+ cwd=build_dir,
35
+ check=True,
36
+ capture_output=True,
37
+ text=True
38
+ );
39
+ } except subprocess.CalledProcessError as e {
40
+ raise ClientBundleError(
41
+ f'Failed to install npm dependencies: {e.stderr}'
42
+ ) from e ;
43
+ } except FileNotFoundError {
44
+ raise ClientBundleError(
45
+ 'npm command not found. Ensure Node.js and npm are installed.'
46
+ ) from None ;
47
+ }
48
+ }
49
+
50
+ # Temporarily copy package.json to .client-build/ for npm run compile
51
+ build_package_json = build_dir / 'package.json';
52
+ configs_package_json = build_dir / '.jac-client.configs' / 'package.json';
53
+ if configs_package_json.exists() and not build_package_json.exists() {
54
+ import shutil;
55
+ shutil.copy2(configs_package_json, build_package_json);
56
+ }
57
+ command = ['npm', 'run', 'compile'];
58
+ subprocess.run(
59
+ command, cwd=build_dir, check=True, capture_output=True, text=True
60
+ );
61
+ } finally {
62
+ # Clean up temporary package.json in .client-build/
63
+ build_package_json = build_dir / 'package.json';
64
+ if build_package_json.exists() {
65
+ build_package_json.unlink();
66
+ }
67
+ # Move package-lock.json to .jac-client.configs/ if it exists
68
+ build_package_lock = build_dir / 'package-lock.json';
69
+ if build_package_lock.exists() {
70
+ configs_package_lock = build_dir / '.jac-client.configs' / 'package-lock.json';
71
+ if configs_package_lock.exists() {
72
+ configs_package_lock.unlink();
73
+ }
74
+ build_package_lock.rename(configs_package_lock);
75
+ }
76
+ # Always clean up root package.json and move package-lock.json
77
+ bundler._cleanup_root_package_files();
78
+ }
79
+ }
80
+
81
+ """Initialize the Babel processor."""
82
+ impl BabelProcessor.init(self: BabelProcessor, project_dir: Path) {
83
+ self.project_dir = project_dir;
84
+ }
@@ -0,0 +1,251 @@
1
+ """Compile module and dependencies, then bundle with Vite."""
2
+
3
+ impl ViteCompiler.compile_and_bundle(
4
+ self: ViteCompiler, module: ModuleType, module_path: Path
5
+ ) -> tuple[str, str, list[str], list[str]] {
6
+ self.compile_runtime_utils();
7
+ (module_js, mod, module_manifest) = self.jac_compiler.compile_module(module_path);
8
+ collected_exports: set[str] = set(
9
+ self.jac_compiler.extract_exports(module_manifest)
10
+ );
11
+ client_globals_map = self.jac_compiler.extract_globals(module_manifest, module);
12
+ collected_globals: dict[(str, Any)] = dict(client_globals_map);
13
+ self.compile_dependencies_recursively(
14
+ module_path,
15
+ collected_exports=collected_exports,
16
+ collected_globals=collected_globals
17
+ );
18
+ self.copy_root_assets();
19
+ self.create_entry_file();
20
+ self.babel_processor.compile();
21
+ self.babel_processor.copy_assets_after_compile(
22
+ self.compiled_dir,
23
+ (self.project_dir / '.client-build' / 'build'),
24
+ self.asset_processor
25
+ );
26
+ entry_file = self.project_dir / '.client-build' / 'build' / 'main.js';
27
+ self.vite_bundler.build(entry_file=entry_file);
28
+ (bundle_code, bundle_hash) = self.vite_bundler.read_bundle();
29
+ client_exports = sorted(collected_exports);
30
+ client_globals = list(collected_globals.keys());
31
+ return (bundle_code, bundle_hash, client_exports, client_globals);
32
+ }
33
+
34
+ """Create the main entry file for Vite bundling."""
35
+ impl ViteCompiler.create_entry_file(self: ViteCompiler) -> None {
36
+ entry_file = self.compiled_dir / 'main.js';
37
+ entry_content = 'import React from "react";\nimport { createRoot } from "react-dom/client";\nimport { app as App } from "./app.js";\n\nconst root = createRoot(document.getElementById("root"));\nroot.render(<App />);\n';
38
+ entry_file.write_text(entry_content, encoding='utf-8');
39
+ }
40
+
41
+ """Copy assets from root assets/ folder to compiled/assets/ for @jac-client/assets alias."""
42
+ impl ViteCompiler.copy_root_assets(self: ViteCompiler) -> None {
43
+ root_assets_dir = self.project_dir / 'assets';
44
+ compiled_assets_dir = self.compiled_dir / 'assets';
45
+ if (root_assets_dir.exists() and root_assets_dir.is_dir()) {
46
+ self.asset_processor.copy_assets(root_assets_dir, compiled_assets_dir);
47
+ }
48
+ # Copy configured_asset files from root assets/ to build assets/ (bypassing compiled)
49
+ build_assets_dir = self.project_dir / '.client-build' / 'build' / 'assets';
50
+ if (root_assets_dir.exists() and root_assets_dir.is_dir()) {
51
+ self.asset_processor.copy_custom_asset_types(root_assets_dir, build_assets_dir);
52
+ }
53
+ }
54
+
55
+ """Copy an asset file to the compiled directory."""
56
+ impl ViteCompiler._copy_asset_file(
57
+ self: ViteCompiler, asset_path: Path, source_root: Path
58
+ ) -> None {
59
+ if not asset_path.exists() {
60
+ return;
61
+ }
62
+ try {
63
+ relative_path = asset_path.relative_to(source_root);
64
+ output_path = self.compiled_dir / relative_path;
65
+ } except ValueError {
66
+ output_path = self.compiled_dir / asset_path.name;
67
+ }
68
+ output_path.parent.mkdir(parents=True, exist_ok=True);
69
+ with contextlib.suppress(FileNotFoundError, OSError) {
70
+ output_path.write_text(
71
+ asset_path.read_text(encoding='utf-8'), encoding='utf-8'
72
+ );
73
+ }
74
+ }
75
+
76
+ """Copy a TypeScript file to the compiled directory."""
77
+ impl ViteCompiler._copy_ts_file(
78
+ self: ViteCompiler, ts_path: Path, source_root: Path
79
+ ) -> None {
80
+ if not ts_path.exists() {
81
+ return;
82
+ }
83
+ try {
84
+ ts_code = ts_path.read_text(encoding='utf-8');
85
+ try {
86
+ relative_path = ts_path.relative_to(source_root);
87
+ output_path = self.compiled_dir / relative_path;
88
+ } except ValueError {
89
+ output_path = self.compiled_dir / ts_path.name;
90
+ }
91
+ output_path.parent.mkdir(parents=True, exist_ok=True);
92
+ output_path.write_text(ts_code, encoding='utf-8');
93
+ } except (FileNotFoundError, OSError) { }
94
+ }
95
+
96
+ """Copy a JavaScript file to the compiled directory."""
97
+ impl ViteCompiler._copy_js_file(
98
+ self: ViteCompiler, js_path: Path, source_root: Path
99
+ ) -> None {
100
+ try {
101
+ js_code = js_path.read_text(encoding='utf-8');
102
+ try {
103
+ relative_path = js_path.relative_to(source_root);
104
+ output_path = self.compiled_dir / relative_path;
105
+ } except ValueError {
106
+ output_path = self.compiled_dir / js_path.name;
107
+ }
108
+ output_path.parent.mkdir(parents=True, exist_ok=True);
109
+ output_path.write_text(js_code, encoding='utf-8');
110
+ } except FileNotFoundError { }
111
+ }
112
+
113
+ """Recursively compile/copy .jac/.js imports to temp, skipping bundling."""
114
+ impl ViteCompiler.compile_dependencies_recursively(
115
+ self: ViteCompiler,
116
+ module_path: Path,
117
+ visited: (set[Path] | None) = None,
118
+ collected_exports: (set[str] | None) = None,
119
+ collected_globals: (dict[(str, Any)] | None) = None,
120
+ source_root: (Path | None) = None
121
+ ) -> None {
122
+ if (visited is None) {
123
+ visited = set();
124
+ }
125
+ if (collected_exports is None) {
126
+ collected_exports = set();
127
+ }
128
+ if (collected_globals is None) {
129
+ collected_globals = {};
130
+ }
131
+ module_path = module_path.resolve();
132
+ if (module_path in visited) {
133
+ return;
134
+ }
135
+ visited.add(module_path);
136
+ if (source_root is None) {
137
+ source_root = module_path.parent.resolve();
138
+ }
139
+ (module_js, mod, manifest) = self.jac_compiler.compile_module(module_path);
140
+ exports_list = self.jac_compiler.extract_exports(manifest);
141
+ collected_exports.update(exports_list);
142
+ non_root_globals: dict[(str, Any)] = {};
143
+ if manifest {
144
+ for name in manifest.globals {
145
+ non_root_globals[name] = manifest.globals_values.get(name);
146
+ }
147
+ }
148
+ collected_globals.update(non_root_globals);
149
+ combined_js = self.jac_compiler.add_runtime_imports(module_js);
150
+ try {
151
+ relative_path = module_path.relative_to(source_root);
152
+ output_path = self.compiled_dir / relative_path.with_suffix('.js');
153
+ } except ValueError {
154
+ output_path = self.compiled_dir / f"{module_path.stem}.js";
155
+ }
156
+ output_path.parent.mkdir(parents=True, exist_ok=True);
157
+ output_path.write_text(combined_js, encoding='utf-8');
158
+ if (not manifest or not manifest.imports) {
159
+ return;
160
+ }
161
+ for (_name, import_path) in manifest.imports.items() {
162
+ path_obj = Path(import_path).resolve();
163
+ if (path_obj in visited) {
164
+ continue;
165
+ }
166
+ if (path_obj.suffix == '.jac') {
167
+ self.compile_dependencies_recursively(
168
+ path_obj,
169
+ visited,
170
+ collected_exports=collected_exports,
171
+ collected_globals=collected_globals,
172
+ source_root=source_root
173
+ );
174
+ } elif (path_obj.suffix == '.js') {
175
+ self._copy_js_file(path_obj, source_root);
176
+ } elif (path_obj.suffix in {'.ts','.tsx'}) {
177
+ self._copy_ts_file(path_obj, source_root);
178
+ } elif path_obj.is_file() {
179
+ self._copy_asset_file(path_obj, source_root);
180
+ }
181
+ }
182
+ }
183
+
184
+ """Compile client runtime utilities."""
185
+ impl ViteCompiler.compile_runtime_utils(self: ViteCompiler) -> tuple[str, list[str]] {
186
+ if not self.runtime_path {
187
+ raise ClientBundleError('Runtime path not set') ;
188
+ }
189
+ runtime_utils_path = self.runtime_path.parent / 'client_runtime.cl.jac';
190
+ (runtimeutils_js, mod, runtimeutils_manifest) = self.jac_compiler.compile_module(
191
+ runtime_utils_path
192
+ );
193
+ runtimeutils_exports_list = self.jac_compiler.extract_exports(
194
+ runtimeutils_manifest
195
+ );
196
+ all_exports = sorted(set((runtimeutils_exports_list + self.ROUTER_EXPORTS)));
197
+ combined_runtime_utils_js = runtimeutils_js;
198
+ self.compiled_dir.mkdir(parents=True, exist_ok=True);
199
+ (self.compiled_dir / 'client_runtime.js').write_text(
200
+ combined_runtime_utils_js, encoding='utf-8'
201
+ );
202
+ return (combined_runtime_utils_js, all_exports);
203
+ }
204
+
205
+ """Initialize the Vite compiler."""
206
+ impl ViteCompiler.init(
207
+ self: ViteCompiler,
208
+ vite_package_json: Path,
209
+ vite_output_dir: (Path | None) = None,
210
+ vite_minify: bool = False,
211
+ runtime_path: (Path | None) = None,
212
+ compile_to_js_func: (Callable[([Path], tuple[(str, (ModuleType | None))])] | None) = None,
213
+ extract_exports_func: (Callable[([Any], list[str])] | None) = None,
214
+ extract_globals_func: (Callable[([Any, ModuleType], dict[(str, Any)])] | None) = None
215
+ ) {
216
+ if (not vite_package_json or not vite_package_json.exists()) {
217
+ raise ClientBundleError(
218
+ 'Vite package.json not found. Set vite_package_json when using ViteCompiler'
219
+ ) ;
220
+ }
221
+ if (
222
+ (compile_to_js_func is None)
223
+ or (extract_exports_func is None)
224
+ or (extract_globals_func is None)
225
+ ) {
226
+ raise ClientBundleError(
227
+ 'compile_to_js_func, extract_exports_func, and extract_globals_func are required'
228
+ ) ;
229
+ }
230
+ self.vite_package_json = vite_package_json;
231
+ # If package.json is in .client-build/.jac-client.configs/, go up two levels to get project root
232
+ if (
233
+ vite_package_json.parent.name == '.jac-client.configs'
234
+ and vite_package_json.parent.parent.name == '.client-build'
235
+ ) {
236
+ self.project_dir = vite_package_json.parent.parent.parent;
237
+ } elif (vite_package_json.parent.name == '.jac-client.configs') {
238
+ self.project_dir = vite_package_json.parent.parent;
239
+ } else {
240
+ self.project_dir = vite_package_json.parent;
241
+ }
242
+ self.runtime_path = runtime_path;
243
+ self.compiled_dir = self.project_dir / '.client-build' / 'compiled';
244
+ self.jac_compiler = JacToJSCompiler(
245
+ compile_to_js_func, extract_exports_func, extract_globals_func
246
+ );
247
+ self.import_processor = ImportProcessor();
248
+ self.asset_processor = AssetProcessor();
249
+ self.babel_processor = BabelProcessor(self.project_dir);
250
+ self.vite_bundler = ViteBundler(self.project_dir, vite_output_dir, vite_minify);
251
+ }
@@ -0,0 +1,119 @@
1
+ """Implementation of Jac Client configuration loader.
2
+
3
+ This module provides the implementation for JacClientConfig, which inherits
4
+ from PluginConfigBase for common configuration loading functionality.
5
+
6
+ The base class handles: init, get_jac_config, get_section.
7
+ This file implements plugin-specific methods including a custom load()
8
+ that combines plugin config with npm dependencies.
9
+ """
10
+ import from pathlib { Path }
11
+ import from typing { Any }
12
+ import from jaclang.project.plugin_config { deep_merge }
13
+
14
+ """Get the plugin section name for client."""
15
+ impl JacClientConfig.get_plugin_name(self: JacClientConfig) -> str {
16
+ return "client";
17
+ }
18
+
19
+ """Get default configuration structure for client."""
20
+ impl JacClientConfig.get_default_config(self: JacClientConfig) -> dict[str, Any] {
21
+ return {
22
+ 'vite': {
23
+ 'plugins': [],
24
+ 'lib_imports': [],
25
+ 'build': {},
26
+ 'server': {},
27
+ 'resolve': {}
28
+ },
29
+ 'ts': {'compilerOptions': {}, 'include': [], 'exclude': []},
30
+ 'package': {
31
+ 'name': '',
32
+ 'version': '1.0.0',
33
+ 'description': '',
34
+ 'dependencies': {},
35
+ 'devDependencies': {}
36
+ }
37
+ };
38
+ }
39
+
40
+ """Load configuration from jac.toml, merging with defaults.
41
+
42
+ Overrides base class to also include npm dependencies from [dependencies.npm].
43
+ """
44
+ impl JacClientConfig.load(self: JacClientConfig) -> dict[str, Any] {
45
+ if self._config is not None {
46
+ return self._config;
47
+ }
48
+ default_config = self.get_default_config();
49
+ jac_config = self.get_jac_config();
50
+ # Get client plugin config from [plugins.client]
51
+ client_config = jac_config.get_plugin_config('client');
52
+ # Get npm dependencies from [dependencies.npm]
53
+ npm_deps = jac_config.get_plugin_deps('npm');
54
+ # Get npm dev dependencies from [dev-dependencies.npm]
55
+ # These are stored in plugin_dependencies["npm"]["dev"] after parsing
56
+ npm_plugin_deps = jac_config.plugin_dependencies.get('npm', {});
57
+ npm_dev_deps = npm_plugin_deps.get('dev', {})
58
+ if isinstance(npm_plugin_deps.get('dev'), dict)
59
+ else {};
60
+ # Build user config in the expected internal format
61
+ user_config: dict[str, Any] = {
62
+ 'vite': client_config.get('vite', {}),
63
+ 'ts': client_config.get('ts', {}),
64
+ 'package': {
65
+ 'name': jac_config.project.name,
66
+ 'version': jac_config.project.version,
67
+ 'description': jac_config.project.description,
68
+ 'dependencies': npm_deps,
69
+ 'devDependencies': npm_dev_deps
70
+ }
71
+ };
72
+ self._config = deep_merge(default_config, user_config);
73
+ return self._config;
74
+ }
75
+
76
+ """Get package configuration (npm dependencies)."""
77
+ impl JacClientConfig.get_package_config(self: JacClientConfig) -> dict[str, Any] {
78
+ config = self.load();
79
+ return config.get('package', {});
80
+ }
81
+
82
+ """Get Vite-specific configuration."""
83
+ impl JacClientConfig.get_vite_config(self: JacClientConfig) -> dict[str, Any] {
84
+ config = self.load();
85
+ return config.get('vite', {});
86
+ }
87
+
88
+ """Get TypeScript-specific configuration for tsconfig.json customization."""
89
+ impl JacClientConfig.get_ts_config(self: JacClientConfig) -> dict[str, Any] {
90
+ config = self.load();
91
+ return config.get('ts', {});
92
+ }
93
+
94
+ """Save configuration back to jac.toml using core JacConfig."""
95
+ impl JacClientConfig.save(self: JacClientConfig) -> None {
96
+ jac_config = self.get_jac_config();
97
+ jac_config.save();
98
+ }
99
+
100
+ """Add an npm dependency."""
101
+ impl JacClientConfig.add_dependency(
102
+ self: JacClientConfig, name: str, version: str, is_dev: bool = False
103
+ ) -> None {
104
+ jac_config = self.get_jac_config();
105
+ jac_config.add_dependency(name, version, dev=is_dev, dep_type="npm");
106
+ # Invalidate cache
107
+ self.invalidate();
108
+ }
109
+
110
+ """Remove an npm dependency."""
111
+ impl JacClientConfig.remove_dependency(
112
+ self: JacClientConfig, name: str, is_dev: bool = False
113
+ ) -> bool {
114
+ jac_config = self.get_jac_config();
115
+ result = jac_config.remove_dependency(name, dev=is_dev, dep_type="npm");
116
+ # Invalidate cache
117
+ self.invalidate();
118
+ return result;
119
+ }