tw-framework 0.2.3__tar.gz
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.
- tw_framework-0.2.3/PKG-INFO +8 -0
- tw_framework-0.2.3/README.md +91 -0
- tw_framework-0.2.3/pyproject.toml +25 -0
- tw_framework-0.2.3/setup.cfg +4 -0
- tw_framework-0.2.3/tests/test_deployment.py +106 -0
- tw_framework-0.2.3/tw_framework/__init__.py +13 -0
- tw_framework-0.2.3/tw_framework/advanced_diagnostics.py +25 -0
- tw_framework-0.2.3/tw_framework/asset_optimizer.py +30 -0
- tw_framework-0.2.3/tw_framework/ast_nodes.py +283 -0
- tw_framework-0.2.3/tw_framework/build.py +7 -0
- tw_framework-0.2.3/tw_framework/build_performance.py +26 -0
- tw_framework-0.2.3/tw_framework/build_report.py +84 -0
- tw_framework-0.2.3/tw_framework/cli.py +1258 -0
- tw_framework-0.2.3/tw_framework/code_splitting.py +76 -0
- tw_framework-0.2.3/tw_framework/common.py +22 -0
- tw_framework-0.2.3/tw_framework/compiler.py +4817 -0
- tw_framework-0.2.3/tw_framework/compiler_stats.py +51 -0
- tw_framework-0.2.3/tw_framework/dead_code.py +44 -0
- tw_framework-0.2.3/tw_framework/dependency_graph.py +91 -0
- tw_framework-0.2.3/tw_framework/deploy.py +7 -0
- tw_framework-0.2.3/tw_framework/dev.py +9 -0
- tw_framework-0.2.3/tw_framework/diagnostics.py +98 -0
- tw_framework-0.2.3/tw_framework/dynamic_imports.py +51 -0
- tw_framework-0.2.3/tw_framework/error_formatter.py +53 -0
- tw_framework-0.2.3/tw_framework/framework.py +3216 -0
- tw_framework-0.2.3/tw_framework/hydration.py +129 -0
- tw_framework-0.2.3/tw_framework/incremental_cache.py +53 -0
- tw_framework-0.2.3/tw_framework/interpreter.py +42 -0
- tw_framework-0.2.3/tw_framework/ir.py +171 -0
- tw_framework-0.2.3/tw_framework/lexer.py +52 -0
- tw_framework-0.2.3/tw_framework/lowering.py +68 -0
- tw_framework-0.2.3/tw_framework/parser.py +167 -0
- tw_framework-0.2.3/tw_framework/partial_rebuild.py +24 -0
- tw_framework-0.2.3/tw_framework/performance_analyzer.py +37 -0
- tw_framework-0.2.3/tw_framework/plugin_runtime.py +196 -0
- tw_framework-0.2.3/tw_framework/production_optimizer.py +82 -0
- tw_framework-0.2.3/tw_framework/reactivity.py +349 -0
- tw_framework-0.2.3/tw_framework/render_css.py +22 -0
- tw_framework-0.2.3/tw_framework/render_html.py +259 -0
- tw_framework-0.2.3/tw_framework/route_optimizer.py +40 -0
- tw_framework-0.2.3/tw_framework/router.py +7 -0
- tw_framework-0.2.3/tw_framework/runtime_model.py +23 -0
- tw_framework-0.2.3/tw_framework/runtime_values.py +31 -0
- tw_framework-0.2.3/tw_framework/semantic.py +162 -0
- tw_framework-0.2.3/tw_framework/server.py +532 -0
- tw_framework-0.2.3/tw_framework/signature.py +51 -0
- tw_framework-0.2.3/tw_framework/static_dynamic_auto.py +32 -0
- tw_framework-0.2.3/tw_framework/streaming.py +135 -0
- tw_framework-0.2.3/tw_framework/test_build_cli_pytest.py +1078 -0
- tw_framework-0.2.3/tw_framework/tree_shaking.py +48 -0
- tw_framework-0.2.3/tw_framework/twm_parser.py +274 -0
- tw_framework-0.2.3/tw_framework.egg-info/PKG-INFO +8 -0
- tw_framework-0.2.3/tw_framework.egg-info/SOURCES.txt +55 -0
- tw_framework-0.2.3/tw_framework.egg-info/dependency_links.txt +1 -0
- tw_framework-0.2.3/tw_framework.egg-info/entry_points.txt +2 -0
- tw_framework-0.2.3/tw_framework.egg-info/requires.txt +3 -0
- tw_framework-0.2.3/tw_framework.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# TW Framework
|
|
2
|
+
|
|
3
|
+
TW is a custom language + framework for building websites with a Next.js‑like developer experience.
|
|
4
|
+
|
|
5
|
+
## Zero‑Config Deployment
|
|
6
|
+
|
|
7
|
+
1. Create a GitHub repository with your TW project (containing `tw.config`, `pages/`, `components/`, `public/`).
|
|
8
|
+
2. Connect the repository to Vercel or Netlify.
|
|
9
|
+
3. The platform automatically detects `tw.config` and runs `tw build`.
|
|
10
|
+
4. Your site goes live – no terminal commands required.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Create a new project
|
|
16
|
+
tw init my-site
|
|
17
|
+
cd my-site
|
|
18
|
+
|
|
19
|
+
# Development server
|
|
20
|
+
tw dev
|
|
21
|
+
|
|
22
|
+
# Production build
|
|
23
|
+
tw build
|
|
24
|
+
|
|
25
|
+
# Preview the build
|
|
26
|
+
tw preview
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## CLI Commands
|
|
30
|
+
|
|
31
|
+
| Command | Description |
|
|
32
|
+
|---------|-------------|
|
|
33
|
+
| `tw init [name]` | Create a new TW project in the current directory |
|
|
34
|
+
| `tw dev` | Start the development server with live reload |
|
|
35
|
+
| `tw build` | Generate a production build |
|
|
36
|
+
| `tw preview` | Preview the built output |
|
|
37
|
+
| `tw export` | Generate a static export |
|
|
38
|
+
| `tw deploy` | Build and deploy to a hosting provider |
|
|
39
|
+
| `tw doctor` | Run project health checks and deployment compatibility |
|
|
40
|
+
| `tw ast file.tw` | Print the AST JSON for a TW source file |
|
|
41
|
+
| `tw ir file.tw` | Print the IR JSON for a TW source file |
|
|
42
|
+
| `tw run file.tw` | Interpret a TW file and output HTML |
|
|
43
|
+
| `tw check file.tw` | Print diagnostics for a TW file |
|
|
44
|
+
| `tw tokens file.tw` | Print the token stream JSON for a TW source file |
|
|
45
|
+
| `tw clean` | Clean dist and hidden cache folders |
|
|
46
|
+
| `tw info` | Show a project summary |
|
|
47
|
+
| `tw login` | Save deploy provider configuration |
|
|
48
|
+
|
|
49
|
+
## Deployment Metadata
|
|
50
|
+
|
|
51
|
+
After a build, the file `dist/tw.deploy.json` is automatically generated. It contains:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"framework": "tw",
|
|
56
|
+
"version": "1.0.0",
|
|
57
|
+
"build": "tw build",
|
|
58
|
+
"output": "dist",
|
|
59
|
+
"runtime": "ssr"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Deployment platforms can read this file to detect the framework and configure the build.
|
|
64
|
+
|
|
65
|
+
## Adapters
|
|
66
|
+
|
|
67
|
+
TW Framework includes built‑in adapters for Vercel and Netlify. They provide:
|
|
68
|
+
|
|
69
|
+
- Framework detection (`detect()` function)
|
|
70
|
+
- Automatic generation of `vercel.json` / `netlify.toml` when missing
|
|
71
|
+
- Build command, output directory, install command, and SSR handling
|
|
72
|
+
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
- [Getting Started](docs/getting-started.md)
|
|
76
|
+
- [Deployment to Vercel](docs/deployment/vercel.md)
|
|
77
|
+
- [Deployment to Netlify](docs/deployment/netlify.md)
|
|
78
|
+
|
|
79
|
+
## Testing
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python3 -m unittest discover -s tests -q
|
|
83
|
+
python3 run_tests.py
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Debug Flags
|
|
87
|
+
|
|
88
|
+
- `TW_WARN_LITERAL_PARSE=1` – warn when literal parsing falls back to string
|
|
89
|
+
- `TW_STRICT_EVAL=1` – re‑raise runtime expression errors instead of warning
|
|
90
|
+
|
|
91
|
+
These flags are mainly useful while debugging parser/runtime edge cases or tightening CI checks around expression handling.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tw-framework"
|
|
7
|
+
version = "0.2.3"
|
|
8
|
+
description = "TW framework CLI with create, dev, build, and deploy commands"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "TW Framework" }
|
|
12
|
+
]
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
# Dev / contributor tooling (not required to use the framework)
|
|
17
|
+
dev = [
|
|
18
|
+
"pytest>=7",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
tw = "tw_framework.cli:main"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools]
|
|
25
|
+
packages = ["tw_framework"]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Tests for deployment metadata, adapters, and zero‑config detection."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from tw_framework import compiler
|
|
10
|
+
from tw_framework.adapters import vercel, netlify
|
|
11
|
+
from tw_framework.framework import generate_deploy_metadata
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestDeploymentMetadata(unittest.TestCase):
|
|
15
|
+
def setUp(self):
|
|
16
|
+
self.tmpdir = tempfile.mkdtemp()
|
|
17
|
+
self.output_dir = os.path.join(self.tmpdir, "dist")
|
|
18
|
+
os.makedirs(self.output_dir, exist_ok=True)
|
|
19
|
+
|
|
20
|
+
def tearDown(self):
|
|
21
|
+
import shutil
|
|
22
|
+
shutil.rmtree(self.tmpdir)
|
|
23
|
+
|
|
24
|
+
def test_generate_deploy_metadata(self):
|
|
25
|
+
config = {"name": "Test"}
|
|
26
|
+
generate_deploy_metadata(self.output_dir, config)
|
|
27
|
+
deploy_path = os.path.join(self.output_dir, "tw.deploy.json")
|
|
28
|
+
self.assertTrue(os.path.isfile(deploy_path))
|
|
29
|
+
with open(deploy_path, "r") as f:
|
|
30
|
+
data = json.load(f)
|
|
31
|
+
self.assertEqual(data["framework"], "tw")
|
|
32
|
+
self.assertEqual(data["build"], "tw build")
|
|
33
|
+
self.assertEqual(data["output"], "dist")
|
|
34
|
+
self.assertEqual(data["runtime"], "ssr")
|
|
35
|
+
self.assertIn("version", data)
|
|
36
|
+
|
|
37
|
+
def test_detect_tw_project(self):
|
|
38
|
+
# Create a fake tw.config
|
|
39
|
+
config_path = os.path.join(self.tmpdir, "tw.config")
|
|
40
|
+
with open(config_path, "w") as f:
|
|
41
|
+
f.write("name: Test\n")
|
|
42
|
+
self.assertTrue(compiler.detect_tw_project(self.tmpdir))
|
|
43
|
+
# Without tw.config
|
|
44
|
+
empty_dir = os.path.join(self.tmpdir, "empty")
|
|
45
|
+
os.makedirs(empty_dir, exist_ok=True)
|
|
46
|
+
self.assertFalse(compiler.detect_tw_project(empty_dir))
|
|
47
|
+
|
|
48
|
+
def test_vercel_adapter_detect(self):
|
|
49
|
+
meta = vercel.detect()
|
|
50
|
+
self.assertEqual(meta["framework"], "tw")
|
|
51
|
+
self.assertEqual(meta["buildCommand"], "tw build")
|
|
52
|
+
self.assertEqual(meta["outputDirectory"], "dist")
|
|
53
|
+
|
|
54
|
+
def test_netlify_adapter_detect(self):
|
|
55
|
+
meta = netlify.detect()
|
|
56
|
+
self.assertEqual(meta["framework"], "tw")
|
|
57
|
+
self.assertEqual(meta["buildCommand"], "tw build")
|
|
58
|
+
self.assertEqual(meta["publish"], "dist")
|
|
59
|
+
|
|
60
|
+
def test_vercel_auto_generate_json(self):
|
|
61
|
+
# Simulate generate_vercel_output with missing vercel.json
|
|
62
|
+
from tw_framework.adapters.vercel import generate_vercel_output
|
|
63
|
+
config = {"name": "Test"}
|
|
64
|
+
# Create a minimal dist directory
|
|
65
|
+
dist_dir = os.path.join(self.tmpdir, "dist")
|
|
66
|
+
os.makedirs(dist_dir, exist_ok=True)
|
|
67
|
+
# The function expects dist_dir to exist
|
|
68
|
+
generate_vercel_output(dist_dir, config, self.tmpdir)
|
|
69
|
+
vercel_json = os.path.join(self.tmpdir, "vercel.json")
|
|
70
|
+
self.assertTrue(os.path.isfile(vercel_json))
|
|
71
|
+
with open(vercel_json, "r") as f:
|
|
72
|
+
data = json.load(f)
|
|
73
|
+
self.assertEqual(data["framework"], "tw")
|
|
74
|
+
|
|
75
|
+
def test_netlify_auto_generate_toml(self):
|
|
76
|
+
from tw_framework.adapters.netlify import generate_netlify_output
|
|
77
|
+
config = {"name": "Test"}
|
|
78
|
+
dist_dir = os.path.join(self.tmpdir, "dist")
|
|
79
|
+
os.makedirs(dist_dir, exist_ok=True)
|
|
80
|
+
generate_netlify_output(dist_dir, config, self.tmpdir)
|
|
81
|
+
netlify_toml = os.path.join(self.tmpdir, "netlify.toml")
|
|
82
|
+
self.assertTrue(os.path.isfile(netlify_toml))
|
|
83
|
+
with open(netlify_toml, "r") as f:
|
|
84
|
+
content = f.read()
|
|
85
|
+
self.assertIn("tw build", content)
|
|
86
|
+
self.assertIn('publish = "dist"', content)
|
|
87
|
+
|
|
88
|
+
def test_streaming_render(self):
|
|
89
|
+
"""Test that streaming render produces valid HTML."""
|
|
90
|
+
from tw_framework.streaming import render_program_streaming
|
|
91
|
+
from tw_framework.ir import IRProgram, IRText
|
|
92
|
+
program = IRProgram(
|
|
93
|
+
meta={"title": "Test"},
|
|
94
|
+
head={},
|
|
95
|
+
lets={},
|
|
96
|
+
state={},
|
|
97
|
+
body=[IRText("Hello World")],
|
|
98
|
+
)
|
|
99
|
+
chunks = list(render_program_streaming(program))
|
|
100
|
+
html = "".join(chunks)
|
|
101
|
+
self.assertIn("Hello World", html)
|
|
102
|
+
self.assertIn("<!DOCTYPE html>", html)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
unittest.main()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""TW Framework package."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.4.0"
|
|
4
|
+
|
|
5
|
+
from .server import run_production_server, SSRCache # noqa: F401
|
|
6
|
+
from .reactivity import ( # noqa: F401
|
|
7
|
+
has_reactivity,
|
|
8
|
+
parse_state_block,
|
|
9
|
+
get_reactivity_runtime_js,
|
|
10
|
+
transform_reactive_attrs,
|
|
11
|
+
)
|
|
12
|
+
from .compiler import compile_file_pipeline, compile_text_pipeline # noqa: F401
|
|
13
|
+
from .interpreter import Interpreter # noqa: F401
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Advanced compiler diagnostics for TW Framework.
|
|
3
|
+
|
|
4
|
+
Detects duplicate attributes, unused variables, circular imports,
|
|
5
|
+
recursive components, invalid nesting, duplicate IDs, duplicate routes,
|
|
6
|
+
and missing exports.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Dict, List
|
|
11
|
+
|
|
12
|
+
from . import compiler
|
|
13
|
+
from .diagnostics import Diagnostic, DiagnosticBag
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_advanced_diagnostics(project_root: str) -> DiagnosticBag:
|
|
19
|
+
"""Run all advanced diagnostics and return the results."""
|
|
20
|
+
bag = DiagnosticBag()
|
|
21
|
+
# TODO: Implement actual detection logic
|
|
22
|
+
return bag
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["run_advanced_diagnostics"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Smart asset optimization for TW Framework.
|
|
3
|
+
|
|
4
|
+
Automatically compresses images, converts to WebP, lazy loads images,
|
|
5
|
+
preloads critical assets, and removes duplicate CSS/JS.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def optimize_assets(project_root: str, output_dir: str) -> Dict[str, List[str]]:
|
|
16
|
+
"""Optimize assets in the output directory."""
|
|
17
|
+
optimized = {
|
|
18
|
+
"images_compressed": [],
|
|
19
|
+
"images_converted_to_webp": [],
|
|
20
|
+
"images_lazy_loaded": [],
|
|
21
|
+
"critical_assets_preloaded": [],
|
|
22
|
+
"duplicate_css_removed": [],
|
|
23
|
+
"duplicate_js_removed": [],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# TODO: Implement actual optimization logic
|
|
27
|
+
return optimized
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
__all__ = ["optimize_assets"]
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass, field, is_dataclass
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Attribute:
|
|
9
|
+
name: str
|
|
10
|
+
value: Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def attribute_to_dict(attribute: Any) -> Dict[str, Any]:
|
|
14
|
+
if isinstance(attribute, Attribute):
|
|
15
|
+
return {
|
|
16
|
+
"name": attribute.name,
|
|
17
|
+
"value": serialize_value(attribute.value),
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
"name": getattr(attribute, "name", ""),
|
|
21
|
+
"value": serialize_value(getattr(attribute, "value", None)),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def serialize_value(value: Any) -> Any:
|
|
26
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
27
|
+
return value
|
|
28
|
+
if isinstance(value, Attribute):
|
|
29
|
+
return attribute_to_dict(value)
|
|
30
|
+
if isinstance(value, dict):
|
|
31
|
+
return {str(key): serialize_value(item) for key, item in value.items()}
|
|
32
|
+
if isinstance(value, (list, tuple, set)):
|
|
33
|
+
return [serialize_value(item) for item in value]
|
|
34
|
+
if is_dataclass(value):
|
|
35
|
+
return serialize_value(asdict(value))
|
|
36
|
+
if hasattr(value, "to_dict") and callable(value.to_dict):
|
|
37
|
+
try:
|
|
38
|
+
return serialize_value(value.to_dict())
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
if hasattr(value, "rules") and isinstance(getattr(value, "rules"), list):
|
|
42
|
+
return {
|
|
43
|
+
"kind": value.__class__.__name__,
|
|
44
|
+
"rules": [serialize_value(rule) for rule in getattr(value, "rules", [])],
|
|
45
|
+
}
|
|
46
|
+
if hasattr(value, "selector") and hasattr(value, "declarations"):
|
|
47
|
+
return {
|
|
48
|
+
"kind": value.__class__.__name__,
|
|
49
|
+
"selector": getattr(value, "selector", ""),
|
|
50
|
+
"declarations": [serialize_value(item) for item in getattr(value, "declarations", [])],
|
|
51
|
+
"children": [serialize_value(item) for item in getattr(value, "children", [])],
|
|
52
|
+
}
|
|
53
|
+
if hasattr(value, "__dict__"):
|
|
54
|
+
raw = {}
|
|
55
|
+
for key, item in vars(value).items():
|
|
56
|
+
if key.startswith("_"):
|
|
57
|
+
continue
|
|
58
|
+
raw[key] = serialize_value(item)
|
|
59
|
+
if raw:
|
|
60
|
+
raw.setdefault("kind", value.__class__.__name__)
|
|
61
|
+
return raw
|
|
62
|
+
return repr(value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class HeadModel:
|
|
67
|
+
metas: List[Dict[str, Any]] = field(default_factory=list)
|
|
68
|
+
icon: Optional[str] = None
|
|
69
|
+
seo: Dict[str, Any] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class PageMeta:
|
|
74
|
+
title: str = ""
|
|
75
|
+
layout: Optional[str] = None
|
|
76
|
+
layouts: List[str] = field(default_factory=list)
|
|
77
|
+
render_mode: str = "static"
|
|
78
|
+
revalidate: Optional[int] = None
|
|
79
|
+
redirect_to: Optional[str] = None
|
|
80
|
+
rewrite_to: Optional[str] = None
|
|
81
|
+
responsive: bool = False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class BaseNode:
|
|
86
|
+
kind: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class TextNode(BaseNode):
|
|
91
|
+
value: str
|
|
92
|
+
|
|
93
|
+
def __init__(self, value: str):
|
|
94
|
+
super().__init__("text")
|
|
95
|
+
self.value = value
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class LetNode(BaseNode):
|
|
100
|
+
name: str
|
|
101
|
+
value: Any
|
|
102
|
+
|
|
103
|
+
def __init__(self, name: str, value: Any):
|
|
104
|
+
super().__init__("let")
|
|
105
|
+
self.name = name
|
|
106
|
+
self.value = value
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class IfNode(BaseNode):
|
|
111
|
+
condition: str
|
|
112
|
+
children: List[BaseNode] = field(default_factory=list)
|
|
113
|
+
else_children: List[BaseNode] = field(default_factory=list)
|
|
114
|
+
|
|
115
|
+
def __init__(self, condition: str, children: Optional[List[BaseNode]] = None, else_children: Optional[List[BaseNode]] = None):
|
|
116
|
+
super().__init__("if")
|
|
117
|
+
self.condition = condition
|
|
118
|
+
self.children = children or []
|
|
119
|
+
self.else_children = else_children or []
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class ForNode(BaseNode):
|
|
124
|
+
var_name: str
|
|
125
|
+
iterable: str
|
|
126
|
+
children: List[BaseNode] = field(default_factory=list)
|
|
127
|
+
|
|
128
|
+
def __init__(self, var_name: str, iterable: str, children: Optional[List[BaseNode]] = None):
|
|
129
|
+
super().__init__("for")
|
|
130
|
+
self.var_name = var_name
|
|
131
|
+
self.iterable = iterable
|
|
132
|
+
self.children = children or []
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class ScriptNode(BaseNode):
|
|
137
|
+
raw_js: str
|
|
138
|
+
|
|
139
|
+
def __init__(self, raw_js: str):
|
|
140
|
+
super().__init__("script")
|
|
141
|
+
self.raw_js = raw_js
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class ElementNode(BaseNode):
|
|
146
|
+
tag: str
|
|
147
|
+
text: Optional[str] = None
|
|
148
|
+
attrs: List[Attribute] = field(default_factory=list)
|
|
149
|
+
styles: List[Attribute] = field(default_factory=list)
|
|
150
|
+
events: List[Attribute] = field(default_factory=list)
|
|
151
|
+
router: Dict[str, Any] = field(default_factory=dict)
|
|
152
|
+
children: List[BaseNode] = field(default_factory=list)
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
tag: str,
|
|
157
|
+
text: Optional[str] = None,
|
|
158
|
+
attrs: Optional[List[Attribute]] = None,
|
|
159
|
+
styles: Optional[List[Attribute]] = None,
|
|
160
|
+
events: Optional[List[Attribute]] = None,
|
|
161
|
+
router: Optional[Dict[str, Any]] = None,
|
|
162
|
+
children: Optional[List[BaseNode]] = None,
|
|
163
|
+
):
|
|
164
|
+
super().__init__("element")
|
|
165
|
+
self.tag = tag
|
|
166
|
+
self.text = text
|
|
167
|
+
self.attrs = attrs or []
|
|
168
|
+
self.styles = styles or []
|
|
169
|
+
self.events = events or []
|
|
170
|
+
self.router = router or {}
|
|
171
|
+
self.children = children or []
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class ComponentNode(BaseNode):
|
|
176
|
+
name: str
|
|
177
|
+
props: List[Attribute] = field(default_factory=list)
|
|
178
|
+
children: List[BaseNode] = field(default_factory=list)
|
|
179
|
+
|
|
180
|
+
def __init__(self, name: str, props: Optional[List[Attribute]] = None, children: Optional[List[BaseNode]] = None):
|
|
181
|
+
super().__init__("component")
|
|
182
|
+
self.name = name
|
|
183
|
+
self.props = props or []
|
|
184
|
+
self.children = children or []
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass
|
|
188
|
+
class Program:
|
|
189
|
+
meta: PageMeta = field(default_factory=PageMeta)
|
|
190
|
+
head: HeadModel = field(default_factory=HeadModel)
|
|
191
|
+
lets: Dict[str, Any] = field(default_factory=dict)
|
|
192
|
+
state: Dict[str, Any] = field(default_factory=dict)
|
|
193
|
+
body: List[BaseNode] = field(default_factory=list)
|
|
194
|
+
loaded_sheets: List[Any] = field(default_factory=list)
|
|
195
|
+
loaded_json: List[Dict[str, Any]] = field(default_factory=list)
|
|
196
|
+
source_path: str = ""
|
|
197
|
+
legacy_page: Any = None
|
|
198
|
+
|
|
199
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
200
|
+
return {
|
|
201
|
+
"meta": {
|
|
202
|
+
"title": self.meta.title,
|
|
203
|
+
"layout": self.meta.layout,
|
|
204
|
+
"layouts": list(self.meta.layouts),
|
|
205
|
+
"render_mode": self.meta.render_mode,
|
|
206
|
+
"revalidate": self.meta.revalidate,
|
|
207
|
+
"redirect_to": self.meta.redirect_to,
|
|
208
|
+
"rewrite_to": self.meta.rewrite_to,
|
|
209
|
+
"responsive": self.meta.responsive,
|
|
210
|
+
},
|
|
211
|
+
"head": {
|
|
212
|
+
"metas": list(self.head.metas),
|
|
213
|
+
"icon": self.head.icon,
|
|
214
|
+
"seo": dict(self.head.seo),
|
|
215
|
+
},
|
|
216
|
+
"lets": serialize_value(self.lets),
|
|
217
|
+
"state": serialize_value(self.state),
|
|
218
|
+
"body": [node_to_dict(node) for node in self.body],
|
|
219
|
+
"loaded_sheets": serialize_value(self.loaded_sheets),
|
|
220
|
+
"loaded_json": serialize_value(self.loaded_json),
|
|
221
|
+
"source_path": self.source_path,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def node_to_dict(node: BaseNode) -> Dict[str, Any]:
|
|
226
|
+
if isinstance(node, TextNode):
|
|
227
|
+
return {"kind": node.kind, "value": node.value}
|
|
228
|
+
if isinstance(node, LetNode):
|
|
229
|
+
return {"kind": node.kind, "name": node.name, "value": serialize_value(node.value)}
|
|
230
|
+
if isinstance(node, IfNode):
|
|
231
|
+
return {
|
|
232
|
+
"kind": node.kind,
|
|
233
|
+
"condition": node.condition,
|
|
234
|
+
"children": [node_to_dict(child) for child in node.children],
|
|
235
|
+
"else_children": [node_to_dict(child) for child in node.else_children],
|
|
236
|
+
}
|
|
237
|
+
if isinstance(node, ForNode):
|
|
238
|
+
return {
|
|
239
|
+
"kind": node.kind,
|
|
240
|
+
"var_name": node.var_name,
|
|
241
|
+
"iterable": node.iterable,
|
|
242
|
+
"children": [node_to_dict(child) for child in node.children],
|
|
243
|
+
}
|
|
244
|
+
if isinstance(node, ScriptNode):
|
|
245
|
+
return {"kind": node.kind, "raw_js": node.raw_js}
|
|
246
|
+
if isinstance(node, ElementNode):
|
|
247
|
+
return {
|
|
248
|
+
"kind": node.kind,
|
|
249
|
+
"tag": node.tag,
|
|
250
|
+
"text": node.text,
|
|
251
|
+
"attrs": [attribute_to_dict(attr) for attr in node.attrs],
|
|
252
|
+
"styles": [attribute_to_dict(attr) for attr in node.styles],
|
|
253
|
+
"events": [attribute_to_dict(attr) for attr in node.events],
|
|
254
|
+
"router": serialize_value(node.router),
|
|
255
|
+
"children": [node_to_dict(child) for child in node.children],
|
|
256
|
+
}
|
|
257
|
+
if isinstance(node, ComponentNode):
|
|
258
|
+
return {
|
|
259
|
+
"kind": node.kind,
|
|
260
|
+
"name": node.name,
|
|
261
|
+
"props": [attribute_to_dict(prop) for prop in node.props],
|
|
262
|
+
"children": [node_to_dict(child) for child in node.children],
|
|
263
|
+
}
|
|
264
|
+
return {"kind": getattr(node, "kind", "unknown"), "repr": repr(node)}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
__all__ = [
|
|
268
|
+
"Attribute",
|
|
269
|
+
"BaseNode",
|
|
270
|
+
"ComponentNode",
|
|
271
|
+
"ElementNode",
|
|
272
|
+
"ForNode",
|
|
273
|
+
"HeadModel",
|
|
274
|
+
"IfNode",
|
|
275
|
+
"LetNode",
|
|
276
|
+
"PageMeta",
|
|
277
|
+
"Program",
|
|
278
|
+
"ScriptNode",
|
|
279
|
+
"TextNode",
|
|
280
|
+
"attribute_to_dict",
|
|
281
|
+
"node_to_dict",
|
|
282
|
+
"serialize_value",
|
|
283
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build performance optimization for TW Framework.
|
|
3
|
+
|
|
4
|
+
Parallelizes compilation, uses multiple CPU cores, and avoids recompiling unchanged files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from . import compiler
|
|
12
|
+
from .compiler_stats import CompilerStats
|
|
13
|
+
from .incremental_cache import IncrementalCache
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def optimize_build(project_root: str, output_dir: str, force: bool = False, workers: Optional[int] = None) -> CompilerStats:
|
|
19
|
+
"""Run an optimized build with parallelism and caching."""
|
|
20
|
+
stats = CompilerStats()
|
|
21
|
+
cache = IncrementalCache(project_root)
|
|
22
|
+
# TODO: Implement actual parallel build logic
|
|
23
|
+
return stats
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = ["optimize_build"]
|