onelaraveljs 1.0.0 → 1.1.3
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.
- package/README.md +1 -1
- package/bin/onejs-build.js +32 -0
- package/index.js +3 -1
- package/package.json +11 -3
- package/scripts/README-template-compiler.md +133 -0
- package/scripts/README.md +61 -0
- package/scripts/__pycache__/build.cpython-314.pyc +0 -0
- package/scripts/__pycache__/compile.cpython-313.pyc +0 -0
- package/scripts/__pycache__/compile.cpython-314.pyc +0 -0
- package/scripts/build.py +574 -0
- package/scripts/check-system-errors.php +214 -0
- package/scripts/compile.py +101 -0
- package/scripts/compiler/README_CONFIG.md +196 -0
- package/scripts/compiler/__init__.py +18 -0
- package/scripts/compiler/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/binding_directive_service.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/class_binding_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/compiler_utils.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/compiler_utils.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/conditional_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/conditional_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/config.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/config.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/declaration_tracker.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/directive_processors.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/directive_processors.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/echo_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/event_directive_processor.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/event_directive_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/function_generators.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/function_generators.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/loop_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/loop_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/main_compiler.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/main_compiler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/parsers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/parsers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/php_converter.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/php_converter.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/php_js_converter.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/php_js_converter.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/register_parser.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/register_parser.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/section_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/section_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/show_directive_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/style_directive_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_analyzer.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_analyzer.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processor.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processors.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processors.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/utils.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/utils.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/wrapper_parser.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/wrapper_parser.cpython-314.pyc +0 -0
- package/scripts/compiler/binding_directive_service.py +103 -0
- package/scripts/compiler/class_binding_handler.py +347 -0
- package/scripts/compiler/cli.py +34 -0
- package/scripts/compiler/code_generator.py +141 -0
- package/scripts/compiler/compiler.config.json +36 -0
- package/scripts/compiler/compiler_utils.py +55 -0
- package/scripts/compiler/conditional_handlers.py +252 -0
- package/scripts/compiler/config.py +107 -0
- package/scripts/compiler/declaration_tracker.py +420 -0
- package/scripts/compiler/directive_processors.py +603 -0
- package/scripts/compiler/echo_processor.py +667 -0
- package/scripts/compiler/event_directive_processor.py +1099 -0
- package/scripts/compiler/fetch_parser.py +49 -0
- package/scripts/compiler/function_generators.py +310 -0
- package/scripts/compiler/loop_handlers.py +224 -0
- package/scripts/compiler/main_compiler.py +1763 -0
- package/scripts/compiler/parsers.py +1418 -0
- package/scripts/compiler/php_converter.py +470 -0
- package/scripts/compiler/php_js_converter.py +603 -0
- package/scripts/compiler/register_parser.py +480 -0
- package/scripts/compiler/section_handlers.py +122 -0
- package/scripts/compiler/show_directive_handler.py +85 -0
- package/scripts/compiler/style_directive_handler.py +169 -0
- package/scripts/compiler/template_analyzer.py +162 -0
- package/scripts/compiler/template_processor.py +1167 -0
- package/scripts/compiler/template_processors.py +1557 -0
- package/scripts/compiler/test_compiler.py +69 -0
- package/scripts/compiler/utils.py +54 -0
- package/scripts/compiler/variables_analyzer.py +135 -0
- package/scripts/compiler/view_identifier_generator.py +278 -0
- package/scripts/compiler/wrapper_parser.py +78 -0
- package/scripts/dev-context.js +311 -0
- package/scripts/dev.js +109 -0
- package/scripts/generate-assets-order.js +208 -0
- package/scripts/migrate-namespace.php +146 -0
- package/scripts/node/MIGRATION.md +190 -0
- package/scripts/node/README.md +269 -0
- package/scripts/node/build.js +208 -0
- package/scripts/node/compiler/compiler-utils.js +38 -0
- package/scripts/node/compiler/conditional-handlers.js +45 -0
- package/scripts/node/compiler/config.js +178 -0
- package/scripts/node/compiler/directive-processors.js +51 -0
- package/scripts/node/compiler/event-directive-processor.js +182 -0
- package/scripts/node/compiler/function-generators.js +239 -0
- package/scripts/node/compiler/loop-handlers.js +45 -0
- package/scripts/node/compiler/main-compiler.js +236 -0
- package/scripts/node/compiler/parsers.js +358 -0
- package/scripts/node/compiler/php-converter.js +227 -0
- package/scripts/node/compiler/register-parser.js +32 -0
- package/scripts/node/compiler/section-handlers.js +46 -0
- package/scripts/node/compiler/template-analyzer.js +50 -0
- package/scripts/node/compiler/template-processor.js +371 -0
- package/scripts/node/compiler/template-processors.js +219 -0
- package/scripts/node/compiler/utils.js +203 -0
- package/scripts/node/compiler/wrapper-parser.js +25 -0
- package/scripts/node/package.json +24 -0
- package/scripts/node/test-compiler.js +52 -0
- package/scripts/node-run.cjs +28 -0
- package/scripts/standardize-directories.php +92 -0
- package/src/core/ViewManager.js +4 -4
- package/templates/view.module.js +2 -0
- package/templates/view.tpl-raw.js +13 -0
- package/templates/wraper.js +71 -0
|
@@ -0,0 +1,1763 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main compiler class tổng hợp tất cả các module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import json
|
|
7
|
+
from config import JS_FUNCTION_PREFIX, HTML_ATTR_PREFIX
|
|
8
|
+
from parsers import DirectiveParsers
|
|
9
|
+
from template_processor import TemplateProcessor
|
|
10
|
+
from template_analyzer import TemplateAnalyzer
|
|
11
|
+
from function_generators import FunctionGenerators
|
|
12
|
+
from compiler_utils import CompilerUtils
|
|
13
|
+
from wrapper_parser import WrapperParser
|
|
14
|
+
from register_parser import RegisterParser
|
|
15
|
+
from config import ViewConfig
|
|
16
|
+
from declaration_tracker import DeclarationTracker
|
|
17
|
+
from binding_directive_service import BindingDirectiveService
|
|
18
|
+
from style_directive_handler import StyleDirectiveHandler
|
|
19
|
+
from show_directive_handler import ShowDirectiveHandler
|
|
20
|
+
|
|
21
|
+
class BladeCompiler:
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.parsers = DirectiveParsers()
|
|
24
|
+
self.template_processor = TemplateProcessor()
|
|
25
|
+
self.template_analyzer = TemplateAnalyzer()
|
|
26
|
+
self.function_generators = FunctionGenerators()
|
|
27
|
+
self.compiler_utils = CompilerUtils()
|
|
28
|
+
self.wrapper_parser = WrapperParser()
|
|
29
|
+
self.register_parser = RegisterParser()
|
|
30
|
+
self.declaration_tracker = DeclarationTracker()
|
|
31
|
+
self.binding_directive_service = BindingDirectiveService()
|
|
32
|
+
self.style_directive_handler = StyleDirectiveHandler()
|
|
33
|
+
self.show_directive_handler = ShowDirectiveHandler()
|
|
34
|
+
|
|
35
|
+
def convert_view_path_to_function_name(self, view_path):
|
|
36
|
+
"""Convert view path to function name (e.g., web.demo-if -> WebDemoIf)"""
|
|
37
|
+
# Split by dots and hyphens
|
|
38
|
+
parts = re.split(r'[.-]', view_path)
|
|
39
|
+
# Capitalize each part and join
|
|
40
|
+
function_name = ''.join(part.capitalize() for part in parts)
|
|
41
|
+
return function_name
|
|
42
|
+
|
|
43
|
+
def compile_blade_to_js(self, blade_code, view_name):
|
|
44
|
+
"""Main compiler function"""
|
|
45
|
+
blade_code = blade_code.strip()
|
|
46
|
+
|
|
47
|
+
# Reset watch counter for this view (each view starts from watch-1)
|
|
48
|
+
self.template_processor.watch_counter = 0
|
|
49
|
+
|
|
50
|
+
# ========================================================================
|
|
51
|
+
# PRIORITY 1 (HIGHEST): Process @verbatim...@endverbatim blocks FIRST
|
|
52
|
+
# ========================================================================
|
|
53
|
+
# @verbatim blocks have ABSOLUTE PRIORITY - all content inside is preserved
|
|
54
|
+
# as-is, NO processing, NO compilation, NO escaping, NO directive parsing
|
|
55
|
+
# This must be done before ANY other processing to ensure complete protection
|
|
56
|
+
verbatim_blocks = {}
|
|
57
|
+
verbatim_counter = 0
|
|
58
|
+
|
|
59
|
+
def protect_verbatim_block(match):
|
|
60
|
+
nonlocal verbatim_counter
|
|
61
|
+
# Store the ENTIRE content between @verbatim and @endverbatim
|
|
62
|
+
# This content will be restored EXACTLY as-is at the end
|
|
63
|
+
content = match.group(1)
|
|
64
|
+
placeholder = f"__VERBATIM_BLOCK_{verbatim_counter}__"
|
|
65
|
+
verbatim_blocks[placeholder] = content
|
|
66
|
+
verbatim_counter += 1
|
|
67
|
+
return placeholder
|
|
68
|
+
|
|
69
|
+
# Match @verbatim...@endverbatim (case insensitive, multiline)
|
|
70
|
+
verbatim_pattern = r'@verbatim\s*(.*?)\s*@endverbatim'
|
|
71
|
+
blade_code = re.sub(verbatim_pattern, protect_verbatim_block, blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
72
|
+
|
|
73
|
+
# ========================================================================
|
|
74
|
+
# PRIORITY 2: Process @register/@endregister blocks BEFORE escaping backticks
|
|
75
|
+
# ========================================================================
|
|
76
|
+
# @register blocks are processed AFTER @verbatim to ensure @register inside
|
|
77
|
+
# @verbatim is NOT processed. This protects content inside @register blocks
|
|
78
|
+
# from being escaped (they contain raw JavaScript code)
|
|
79
|
+
register_blocks = {}
|
|
80
|
+
register_counter = 0
|
|
81
|
+
|
|
82
|
+
def protect_register_block(match):
|
|
83
|
+
nonlocal register_counter
|
|
84
|
+
# Get full match including @register and @endregister
|
|
85
|
+
# Note: This will NOT match @register inside @verbatim (already replaced)
|
|
86
|
+
full_content = match.group(0)
|
|
87
|
+
placeholder = f"__REGISTER_BLOCK_{register_counter}__"
|
|
88
|
+
register_blocks[placeholder] = full_content
|
|
89
|
+
register_counter += 1
|
|
90
|
+
return placeholder
|
|
91
|
+
|
|
92
|
+
# Match @register with optional parameters and @endregister (case insensitive)
|
|
93
|
+
# Also match aliases: @setup/@endsetup, @script/@endscript
|
|
94
|
+
# IMPORTANT: This will NOT match inside @verbatim blocks (already protected)
|
|
95
|
+
register_pattern = r'@(?:register|setup|script)\s*(?:\([^)]*\))?\s*(.*?)\s*@end(?:register|setup|script)'
|
|
96
|
+
blade_code = re.sub(register_pattern, protect_register_block, blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
97
|
+
|
|
98
|
+
# Escape backticks in blade code to prevent JavaScript syntax errors
|
|
99
|
+
# This needs to be done AFTER protecting @register blocks to avoid escaping backticks
|
|
100
|
+
# that are part of JavaScript template strings in @register blocks
|
|
101
|
+
escape_str = '@@@@@@@@@@@@@@@@@@@@@@@--------------------------------$$$$$$$$$$$$$$$$$$$$$$$$$$'
|
|
102
|
+
blade_code = blade_code.replace('\\`', escape_str) # Protect already escaped backticks
|
|
103
|
+
blade_code = blade_code.replace('`', '\\`') # Escape all backticks
|
|
104
|
+
blade_code = blade_code.replace(escape_str, '\\`') # Restore protected backticks
|
|
105
|
+
|
|
106
|
+
# Reset parser states to avoid data leakage between views
|
|
107
|
+
if hasattr(self.register_parser, 'reset'):
|
|
108
|
+
self.register_parser.reset()
|
|
109
|
+
# Note: wrapper_parser không reset vì dữ liệu từ wraper.js không thay đổi
|
|
110
|
+
|
|
111
|
+
# Parse wrapper content
|
|
112
|
+
wrapper_function_content, wrapper_config_content = self.wrapper_parser.parse_wrapper_file()
|
|
113
|
+
|
|
114
|
+
# Convert view path to function name
|
|
115
|
+
function_name = self.convert_view_path_to_function_name(view_name)
|
|
116
|
+
|
|
117
|
+
# Initialize update_functions list for storing update$stateKey functions
|
|
118
|
+
self.update_functions = []
|
|
119
|
+
|
|
120
|
+
# Remove Blade comments
|
|
121
|
+
blade_code = re.sub(r'{{--.*?--}}', '', blade_code, flags=re.DOTALL)
|
|
122
|
+
|
|
123
|
+
# Check for directives
|
|
124
|
+
has_await = '@await(' in blade_code
|
|
125
|
+
has_fetch = '@fetch(' in blade_code
|
|
126
|
+
has_subscribe = ('@subscribe(' in blade_code) or re.search(r'@dontsubscribe\b', blade_code, flags=re.IGNORECASE)
|
|
127
|
+
|
|
128
|
+
# NEW: Use DeclarationTracker to parse all declarations in order
|
|
129
|
+
all_declarations = self.declaration_tracker.parse_all_declarations(blade_code)
|
|
130
|
+
|
|
131
|
+
# Generate wrapper declarations from tracked declarations
|
|
132
|
+
wrapper_declarations_code, variable_list, state_declarations = self._generate_wrapper_declarations(all_declarations)
|
|
133
|
+
|
|
134
|
+
# Parse main components (keep for compatibility, but we'll use DeclarationTracker results)
|
|
135
|
+
extended_view, extends_expression, extends_data = self.parsers.parse_extends(blade_code)
|
|
136
|
+
vars_declaration = self.parsers.parse_vars(blade_code)
|
|
137
|
+
let_declarations = self.parsers.parse_let_directives(blade_code)
|
|
138
|
+
const_declarations = self.parsers.parse_const_directives(blade_code)
|
|
139
|
+
usestate_declarations = self.parsers.parse_usestate_directives(blade_code)
|
|
140
|
+
|
|
141
|
+
# Extract usestate_variables for event processor
|
|
142
|
+
usestate_variables = self._extract_usestate_variables(usestate_declarations, all_declarations)
|
|
143
|
+
|
|
144
|
+
# Update template_processor with usestate_variables (don't reinitialize to preserve watch_counter)
|
|
145
|
+
self.template_processor.state_variables = usestate_variables
|
|
146
|
+
self.template_processor.conditional_handlers.state_variables = usestate_variables
|
|
147
|
+
self.template_processor.loop_handlers.state_variables = usestate_variables
|
|
148
|
+
self.template_processor.event_processor.state_variables = usestate_variables
|
|
149
|
+
self.template_processor.echo_processor.state_variables = usestate_variables
|
|
150
|
+
self.template_processor.class_binding_handler.state_variables = usestate_variables
|
|
151
|
+
|
|
152
|
+
# Parse block directives
|
|
153
|
+
blade_code = self.parsers.parse_block_directives(blade_code)
|
|
154
|
+
blade_code = self.parsers.parse_endblock_directives(blade_code)
|
|
155
|
+
blade_code = self.parsers.parse_useblock_directives(blade_code)
|
|
156
|
+
blade_code = self.parsers.parse_onblock_directives(blade_code)
|
|
157
|
+
|
|
158
|
+
fetch_config = self.parsers.parse_fetch(blade_code) if has_fetch else None
|
|
159
|
+
subscribe_config = None # Deprecated: @subscribe directive removed, use @view/:subscribe instead
|
|
160
|
+
init_functions, css_content = self.parsers.parse_init(blade_code)
|
|
161
|
+
view_type_data = self.parsers.parse_view_type(blade_code)
|
|
162
|
+
|
|
163
|
+
# Extract unescaped content from @register blocks BEFORE restoring to blade_code
|
|
164
|
+
# This ensures register_parser gets original content without escaped backticks
|
|
165
|
+
register_content_unescaped = None
|
|
166
|
+
for placeholder, original_content in register_blocks.items():
|
|
167
|
+
# Extract just the content inside @register...@endregister (without the directives)
|
|
168
|
+
content_match = re.search(r'@(?:register|setup|script)\s*(?:\([^)]*\))?\s*(.*?)\s*@end(?:register|setup|script)', original_content, re.DOTALL | re.IGNORECASE)
|
|
169
|
+
if content_match:
|
|
170
|
+
if register_content_unescaped is None:
|
|
171
|
+
register_content_unescaped = content_match.group(1)
|
|
172
|
+
else:
|
|
173
|
+
# If multiple @register blocks, concatenate them
|
|
174
|
+
register_content_unescaped += "\n" + content_match.group(1)
|
|
175
|
+
|
|
176
|
+
# Restore @register blocks to blade_code for removal from template
|
|
177
|
+
# (This is needed so parse_register can find and remove them)
|
|
178
|
+
for placeholder, original_content in register_blocks.items():
|
|
179
|
+
blade_code = blade_code.replace(placeholder, original_content)
|
|
180
|
+
|
|
181
|
+
# Parse @register directive from blade_code (for removal from template)
|
|
182
|
+
register_content = self.parsers.parse_register(blade_code)
|
|
183
|
+
|
|
184
|
+
# Use unescaped content for register_parser to avoid escaped backticks
|
|
185
|
+
if register_content_unescaped:
|
|
186
|
+
register_data = self.register_parser.parse_register_content(register_content_unescaped, view_name)
|
|
187
|
+
elif register_content:
|
|
188
|
+
# Fallback: use escaped content if extraction failed
|
|
189
|
+
register_data = self.register_parser.parse_register_content(register_content, view_name)
|
|
190
|
+
else:
|
|
191
|
+
register_data = None
|
|
192
|
+
|
|
193
|
+
# Remove script setup/import/imports/scope content from blade_code before processing
|
|
194
|
+
# These should only be used for import statements, not for render function content
|
|
195
|
+
script_types = ['setup', 'import', 'imports', 'scope', 'scoped']
|
|
196
|
+
for script_type in script_types:
|
|
197
|
+
pattern = rf'<script\s+{script_type}[^>]*>.*?</script>'
|
|
198
|
+
blade_code = re.sub(pattern, '', blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
199
|
+
|
|
200
|
+
# Remove @viewType directive after parsing
|
|
201
|
+
blade_code = re.sub(r'@viewtype\s*\([^)]*\)', '', blade_code, flags=re.IGNORECASE)
|
|
202
|
+
|
|
203
|
+
# Then remove script setup/import/imports/scope content from register_content for template processing
|
|
204
|
+
if register_content:
|
|
205
|
+
for script_type in script_types:
|
|
206
|
+
pattern = rf'<script\s+{script_type}[^>]*>.*?</script>'
|
|
207
|
+
register_content = re.sub(pattern, '', register_content, flags=re.DOTALL | re.IGNORECASE)
|
|
208
|
+
|
|
209
|
+
# Process template content
|
|
210
|
+
# NOTE: verbatim blocks are already protected as placeholders, so they won't be processed
|
|
211
|
+
template_content, sections = self.template_processor.process_template(blade_code)
|
|
212
|
+
|
|
213
|
+
# ========================================================================
|
|
214
|
+
# Restore @verbatim blocks - escape backticks for template string safety
|
|
215
|
+
# ========================================================================
|
|
216
|
+
# Verbatim content is restored but backticks must be escaped because
|
|
217
|
+
# template_content will be inserted into a JavaScript template string (backticks)
|
|
218
|
+
# Example: __outputRenderedContent__ = `{template_content}`
|
|
219
|
+
# If verbatim content has backticks, they will break the template string syntax
|
|
220
|
+
# So we escape: ` → \` and \` → \\`
|
|
221
|
+
for placeholder, content in verbatim_blocks.items():
|
|
222
|
+
# Escape backticks and ${} in verbatim content for safe insertion into template string
|
|
223
|
+
# Verbatim content will be inserted into: __outputRenderedContent__ = `{content}`
|
|
224
|
+
# So we need to escape:
|
|
225
|
+
# 1. Backticks: ` → \` (to prevent breaking template string)
|
|
226
|
+
# 2. ${...}: ${ → \${ (to prevent JavaScript interpolation)
|
|
227
|
+
# 3. Already escaped: \` → \\` and \${ → \\${
|
|
228
|
+
|
|
229
|
+
# Step 1: Protect already escaped sequences
|
|
230
|
+
protected_content = content.replace('\\`', '__ESCAPED_BACKTICK__')
|
|
231
|
+
protected_content = protected_content.replace('\\${', '__ESCAPED_DOLLAR_BRACE__')
|
|
232
|
+
|
|
233
|
+
# Step 2: Escape unescaped backticks and ${ sequences
|
|
234
|
+
protected_content = protected_content.replace('`', '\\`')
|
|
235
|
+
protected_content = protected_content.replace('${', '\\${')
|
|
236
|
+
|
|
237
|
+
# Step 3: Restore protected sequences (now double-escaped)
|
|
238
|
+
protected_content = protected_content.replace('__ESCAPED_BACKTICK__', '\\\\`')
|
|
239
|
+
protected_content = protected_content.replace('__ESCAPED_DOLLAR_BRACE__', '\\\\${')
|
|
240
|
+
|
|
241
|
+
# Replace placeholder with escaped content
|
|
242
|
+
template_content = template_content.replace(placeholder, protected_content)
|
|
243
|
+
|
|
244
|
+
# Extract wrapper config from template content
|
|
245
|
+
wrapper_config = self._extract_wrapper_config(template_content)
|
|
246
|
+
|
|
247
|
+
# If wrapper exists, extract inner and outer content (separate before/after)
|
|
248
|
+
outer_before = ''
|
|
249
|
+
outer_after = ''
|
|
250
|
+
if wrapper_config:
|
|
251
|
+
inner_content, outer_before_raw, outer_after_raw = self._extract_wrapper_inner_content(template_content)
|
|
252
|
+
template_content = inner_content
|
|
253
|
+
|
|
254
|
+
# Filter outer content to only include directives (remove pure HTML)
|
|
255
|
+
outer_before = self._filter_directives_only(outer_before_raw) if outer_before_raw else ''
|
|
256
|
+
outer_after = self._filter_directives_only(outer_after_raw) if outer_after_raw else ''
|
|
257
|
+
|
|
258
|
+
# Remove __WRAPPER_CONFIG__ and __WRAPPER_END__ from template content
|
|
259
|
+
if wrapper_config:
|
|
260
|
+
# Remove __WRAPPER_END__ marker first
|
|
261
|
+
template_content = re.sub(r'__WRAPPER_END__\s*', '', template_content)
|
|
262
|
+
|
|
263
|
+
# Use same logic as extraction to handle nested braces for config
|
|
264
|
+
match = re.search(r'__WRAPPER_CONFIG__\s*=\s*', template_content)
|
|
265
|
+
if match:
|
|
266
|
+
start_pos = match.start()
|
|
267
|
+
end_pos = match.end()
|
|
268
|
+
|
|
269
|
+
# Find the end of the config object
|
|
270
|
+
if end_pos < len(template_content) and template_content[end_pos] == '{':
|
|
271
|
+
brace_count = 0
|
|
272
|
+
in_string = False
|
|
273
|
+
string_char = None
|
|
274
|
+
|
|
275
|
+
for i in range(end_pos, len(template_content)):
|
|
276
|
+
char = template_content[i]
|
|
277
|
+
|
|
278
|
+
if char in ['"', "'"] and (i == 0 or template_content[i-1] != '\\'):
|
|
279
|
+
if not in_string:
|
|
280
|
+
in_string = True
|
|
281
|
+
string_char = char
|
|
282
|
+
elif char == string_char:
|
|
283
|
+
in_string = False
|
|
284
|
+
string_char = None
|
|
285
|
+
|
|
286
|
+
if not in_string:
|
|
287
|
+
if char == '{':
|
|
288
|
+
brace_count += 1
|
|
289
|
+
elif char == '}':
|
|
290
|
+
brace_count -= 1
|
|
291
|
+
if brace_count == 0:
|
|
292
|
+
config_end = i + 1
|
|
293
|
+
# Include semicolon if present
|
|
294
|
+
if config_end < len(template_content) and template_content[config_end] == ';':
|
|
295
|
+
config_end += 1
|
|
296
|
+
# Remove including trailing whitespace/newline
|
|
297
|
+
while config_end < len(template_content) and template_content[config_end] in [' ', '\n', '\r', '\t']:
|
|
298
|
+
config_end += 1
|
|
299
|
+
template_content = template_content[:start_pos] + template_content[config_end:]
|
|
300
|
+
break
|
|
301
|
+
|
|
302
|
+
# Generate sections info
|
|
303
|
+
sections_info = self.template_analyzer.analyze_sections_info(sections, vars_declaration, has_await, has_fetch)
|
|
304
|
+
|
|
305
|
+
# Integrate register_data vào sections_info
|
|
306
|
+
if register_data and register_data.get('sections'):
|
|
307
|
+
for section_name, script_obj in register_data['sections'].items():
|
|
308
|
+
# Tìm section trong sections_info list
|
|
309
|
+
section_found = False
|
|
310
|
+
for section_info in sections_info:
|
|
311
|
+
if section_info.get('name') == section_name:
|
|
312
|
+
section_info['script'] = script_obj
|
|
313
|
+
section_found = True
|
|
314
|
+
break
|
|
315
|
+
|
|
316
|
+
# Nếu không tìm thấy, thêm section mới
|
|
317
|
+
if not section_found:
|
|
318
|
+
sections_info.append({
|
|
319
|
+
'name': section_name,
|
|
320
|
+
'type': 'short',
|
|
321
|
+
'preloader': False,
|
|
322
|
+
'useVars': False,
|
|
323
|
+
'script': script_obj
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
# Analyze conditional structures outside sections
|
|
327
|
+
conditional_content = self.template_analyzer.analyze_conditional_structures(template_content, vars_declaration, has_await, has_fetch)
|
|
328
|
+
|
|
329
|
+
# Generate components
|
|
330
|
+
vars_line = " " + vars_declaration + "\n" if vars_declaration else ""
|
|
331
|
+
|
|
332
|
+
# Combine all directive declarations
|
|
333
|
+
# Collect declarations for render function - chỉ @let và @const, không bao gồm @useState
|
|
334
|
+
all_declarations = []
|
|
335
|
+
if let_declarations:
|
|
336
|
+
all_declarations.append(let_declarations)
|
|
337
|
+
if const_declarations:
|
|
338
|
+
all_declarations.append(const_declarations)
|
|
339
|
+
# Thêm usestate_declarations vào render function để xử lý updateStateByKey
|
|
340
|
+
if usestate_declarations:
|
|
341
|
+
all_declarations.append(usestate_declarations)
|
|
342
|
+
|
|
343
|
+
directives_line = ""
|
|
344
|
+
if all_declarations:
|
|
345
|
+
# Replace useState declarations with updateRealState calls
|
|
346
|
+
processed_declarations = []
|
|
347
|
+
has_usestate_declarations = False
|
|
348
|
+
for declaration in all_declarations:
|
|
349
|
+
if declaration.strip():
|
|
350
|
+
# Process each line separately to handle multi-line declarations
|
|
351
|
+
for line in declaration.split('\n'):
|
|
352
|
+
if line.strip():
|
|
353
|
+
# Check if this line contains @useState directive
|
|
354
|
+
if line.strip().startswith('@useState'):
|
|
355
|
+
# Handle @useState($value, $stateKey, $setStateKey) format
|
|
356
|
+
match = re.search(r'@useState\s*\(\s*\$?(\w+)\s*,\s*\$?(\w+)\s*,\s*\$?(\w+)\s*\)', line)
|
|
357
|
+
if match:
|
|
358
|
+
value = match.group(1).strip()
|
|
359
|
+
state_key = match.group(2).strip()
|
|
360
|
+
set_state_key = match.group(3).strip()
|
|
361
|
+
# Remove $ prefix if present
|
|
362
|
+
if state_key.startswith('$'):
|
|
363
|
+
state_key = state_key[1:]
|
|
364
|
+
if set_state_key.startswith('$'):
|
|
365
|
+
set_state_key = set_state_key[1:]
|
|
366
|
+
if value.startswith('$'):
|
|
367
|
+
value = value[1:]
|
|
368
|
+
|
|
369
|
+
# Only process valid state keys
|
|
370
|
+
if state_key and state_key.isalnum():
|
|
371
|
+
# Store update function for later (outside render)
|
|
372
|
+
self.update_functions.append(f" const update${state_key} = (value) => {{")
|
|
373
|
+
self.update_functions.append(f" if(__STATE__.__.canUpdateStateByKey){{")
|
|
374
|
+
self.update_functions.append(f" updateStateByKey('{state_key}', value);")
|
|
375
|
+
self.update_functions.append(f" {state_key} = value;")
|
|
376
|
+
self.update_functions.append(f" }}")
|
|
377
|
+
self.update_functions.append(f" }};")
|
|
378
|
+
# Add state initialization (inside render)
|
|
379
|
+
processed_declarations.append(f" update${state_key}({value});")
|
|
380
|
+
has_usestate_declarations = True
|
|
381
|
+
# Don't add the original line since we already have updateStateByKey
|
|
382
|
+
else:
|
|
383
|
+
processed_declarations.append(line)
|
|
384
|
+
else:
|
|
385
|
+
# Handle @useState($value) format (simple)
|
|
386
|
+
match_simple = re.search(r'@useState\s*\(\s*\$?(\w+)\s*\)', line)
|
|
387
|
+
if match_simple:
|
|
388
|
+
value = match_simple.group(1).strip()
|
|
389
|
+
# Remove $ prefix if present
|
|
390
|
+
if value.startswith('$'):
|
|
391
|
+
value = value[1:]
|
|
392
|
+
# For simple format, we'll skip it as it's just a value without state key
|
|
393
|
+
# Don't add the original line since we don't process it
|
|
394
|
+
else:
|
|
395
|
+
processed_declarations.append(line)
|
|
396
|
+
else:
|
|
397
|
+
# Check if this line contains useState destructuring
|
|
398
|
+
match = re.search(r'\[([^,]+),\s*[^]]+\]\s*=\s*useState\([^)]+\)', line)
|
|
399
|
+
if match:
|
|
400
|
+
state_key = match.group(1).strip()
|
|
401
|
+
# Remove $ prefix if present
|
|
402
|
+
if state_key.startswith('$'):
|
|
403
|
+
state_key = state_key[1:]
|
|
404
|
+
# Only process valid state keys
|
|
405
|
+
if state_key and state_key.isalnum():
|
|
406
|
+
# Extract value from useState(value)
|
|
407
|
+
value_match = re.search(r'useState\(([^)]+)\)', line)
|
|
408
|
+
value = value_match.group(1).strip() if value_match else 'null'
|
|
409
|
+
# Store update function for later (outside render)
|
|
410
|
+
self.update_functions.append(f" const update${state_key} = (value) => {{")
|
|
411
|
+
self.update_functions.append(f" if(__STATE__.__.canUpdateStateByKey){{")
|
|
412
|
+
self.update_functions.append(f" updateStateByKey('{state_key}', value);")
|
|
413
|
+
self.update_functions.append(f" {state_key} = value;")
|
|
414
|
+
self.update_functions.append(f" }}")
|
|
415
|
+
self.update_functions.append(f" }};")
|
|
416
|
+
# Add state initialization (inside render)
|
|
417
|
+
processed_declarations.append(f" update${state_key}({value});")
|
|
418
|
+
has_usestate_declarations = True
|
|
419
|
+
# Don't add the original line since we already have updateStateByKey
|
|
420
|
+
else:
|
|
421
|
+
processed_declarations.append(line)
|
|
422
|
+
|
|
423
|
+
directives_line = " " + "\n ".join(processed_declarations) + "\n"
|
|
424
|
+
|
|
425
|
+
# Add lockUpdateRealState if there are useState declarations
|
|
426
|
+
if has_usestate_declarations:
|
|
427
|
+
directives_line += " lockUpdateRealState();\n"
|
|
428
|
+
|
|
429
|
+
# Process init_functions để tạo updateStateByKey calls từ @onInit
|
|
430
|
+
init_update_functions = []
|
|
431
|
+
init_state_initializations = []
|
|
432
|
+
|
|
433
|
+
if init_functions:
|
|
434
|
+
for init_code in init_functions:
|
|
435
|
+
for line in init_code.split('\n'):
|
|
436
|
+
if line.strip():
|
|
437
|
+
# Check if this line contains @useState directive
|
|
438
|
+
if line.strip().startswith('@useState'):
|
|
439
|
+
# Handle @useState($value, $stateKey, $setStateKey) format
|
|
440
|
+
match = re.search(r'@useState\s*\(\s*\$?(\w+)\s*,\s*\$?(\w+)\s*,\s*\$?(\w+)\s*\)', line)
|
|
441
|
+
if match:
|
|
442
|
+
value = match.group(1).strip()
|
|
443
|
+
state_key = match.group(2).strip()
|
|
444
|
+
set_state_key = match.group(3).strip()
|
|
445
|
+
# Remove $ prefix if present
|
|
446
|
+
if state_key.startswith('$'):
|
|
447
|
+
state_key = state_key[1:]
|
|
448
|
+
if set_state_key.startswith('$'):
|
|
449
|
+
set_state_key = set_state_key[1:]
|
|
450
|
+
if value.startswith('$'):
|
|
451
|
+
value = value[1:]
|
|
452
|
+
|
|
453
|
+
# Only process valid state keys
|
|
454
|
+
if state_key and state_key.isalnum():
|
|
455
|
+
# Store update function for later (outside render)
|
|
456
|
+
self.update_functions.append(f" const update${state_key} = (value) => {{")
|
|
457
|
+
self.update_functions.append(f" if(__STATE__.__.canUpdateStateByKey){{")
|
|
458
|
+
self.update_functions.append(f" updateStateByKey('{state_key}', value);")
|
|
459
|
+
self.update_functions.append(f" {state_key} = value;")
|
|
460
|
+
self.update_functions.append(f" }}")
|
|
461
|
+
self.update_functions.append(f" }};")
|
|
462
|
+
# Add state initialization (inside render)
|
|
463
|
+
init_state_initializations.append(f" update${state_key}({value});")
|
|
464
|
+
else:
|
|
465
|
+
# Handle @useState($value) format (simple)
|
|
466
|
+
match_simple = re.search(r'@useState\s*\(\s*\$?(\w+)\s*\)', line)
|
|
467
|
+
if match_simple:
|
|
468
|
+
value = match_simple.group(1).strip()
|
|
469
|
+
# Remove $ prefix if present
|
|
470
|
+
if value.startswith('$'):
|
|
471
|
+
value = value[1:]
|
|
472
|
+
# For simple format, we'll skip it as it's just a value without state key
|
|
473
|
+
# Don't add the original line since we don't process it
|
|
474
|
+
else:
|
|
475
|
+
# Pattern 1: [stateKey, setState] = useState(value) - destructuring
|
|
476
|
+
match = re.search(r'\[([^,]+),\s*[^]]+\]\s*=\s*useState\([^)]+\)', line)
|
|
477
|
+
if match:
|
|
478
|
+
state_key = match.group(1).strip()
|
|
479
|
+
# Remove $ prefix if present
|
|
480
|
+
if state_key.startswith('$'):
|
|
481
|
+
state_key = state_key[1:]
|
|
482
|
+
# Only process valid state keys
|
|
483
|
+
if state_key and state_key.isalnum():
|
|
484
|
+
# Extract value from useState(value)
|
|
485
|
+
value_match = re.search(r'useState\(([^)]+)\)', line)
|
|
486
|
+
value = value_match.group(1).strip() if value_match else 'null'
|
|
487
|
+
# Store update function for later (outside render)
|
|
488
|
+
self.update_functions.append(f" const update${state_key} = (value) => {{")
|
|
489
|
+
self.update_functions.append(f" if(__STATE__.__.canUpdateStateByKey){{")
|
|
490
|
+
self.update_functions.append(f" updateStateByKey('{state_key}', value);")
|
|
491
|
+
self.update_functions.append(f" {state_key} = value;")
|
|
492
|
+
self.update_functions.append(f" }}")
|
|
493
|
+
self.update_functions.append(f" }};")
|
|
494
|
+
# Add state initialization (inside render)
|
|
495
|
+
init_state_initializations.append(f" update${state_key}({value});")
|
|
496
|
+
else:
|
|
497
|
+
# Pattern 2: const [stateKey] = useState(value) - simple destructuring
|
|
498
|
+
match_simple = re.search(r'const\s+\[([^\]]+)\]\s*=\s*useState\(([^)]+)\)', line)
|
|
499
|
+
if match_simple:
|
|
500
|
+
state_key = match_simple.group(1).strip()
|
|
501
|
+
value = match_simple.group(2).strip()
|
|
502
|
+
if state_key.startswith('$'):
|
|
503
|
+
state_key = state_key[1:]
|
|
504
|
+
if state_key and state_key.isalnum():
|
|
505
|
+
# Store update function for later (outside render)
|
|
506
|
+
self.update_functions.append(f" const update${state_key} = (value) => {{")
|
|
507
|
+
self.update_functions.append(f" if(__STATE__.__.canUpdateStateByKey){{")
|
|
508
|
+
self.update_functions.append(f" {state_key} = value;")
|
|
509
|
+
self.update_functions.append(f" }}")
|
|
510
|
+
self.update_functions.append(f" return updateStateByKey('{state_key}', value);")
|
|
511
|
+
self.update_functions.append(f" }};")
|
|
512
|
+
# Add state initialization (inside render)
|
|
513
|
+
init_state_initializations.append(f" update${state_key}({value});")
|
|
514
|
+
|
|
515
|
+
# Add init state initializations to directives_line (inside render)
|
|
516
|
+
if init_state_initializations:
|
|
517
|
+
if directives_line.strip():
|
|
518
|
+
directives_line += " " + "\n ".join(init_state_initializations) + "\n"
|
|
519
|
+
else:
|
|
520
|
+
directives_line = " " + "\n ".join(init_state_initializations) + "\n"
|
|
521
|
+
|
|
522
|
+
# Add lockUpdateRealState if there are useState declarations from init
|
|
523
|
+
if init_state_initializations:
|
|
524
|
+
directives_line += " lockUpdateRealState();\n"
|
|
525
|
+
|
|
526
|
+
view_id_line = " \n"
|
|
527
|
+
|
|
528
|
+
# Calculate has_prerender - complex logic
|
|
529
|
+
has_prerender = self._calculate_prerender_need(
|
|
530
|
+
has_await, has_fetch, vars_declaration,
|
|
531
|
+
sections_info, template_content,
|
|
532
|
+
usestate_declarations, let_declarations, const_declarations
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
# Process binding directives (@val and @bind)
|
|
536
|
+
template_content = self.binding_directive_service.process_all_binding_directives(template_content)
|
|
537
|
+
|
|
538
|
+
# Update style and show handlers with state variables
|
|
539
|
+
self.style_directive_handler.state_variables = usestate_variables
|
|
540
|
+
self.show_directive_handler.state_variables = usestate_variables
|
|
541
|
+
|
|
542
|
+
# Process @style directive
|
|
543
|
+
template_content = self.style_directive_handler.process_style_directive(template_content)
|
|
544
|
+
|
|
545
|
+
# Process @show directive
|
|
546
|
+
template_content = self.show_directive_handler.process_show_directive(template_content)
|
|
547
|
+
|
|
548
|
+
# Generate render function (setup script will be added to view function instead)
|
|
549
|
+
render_function = self.function_generators.generate_render_function(template_content, vars_declaration, extended_view, extends_expression, extends_data, sections_info, has_prerender, "", directives_line, outer_before, outer_after)
|
|
550
|
+
|
|
551
|
+
# Generate init function
|
|
552
|
+
init_code = '\n '.join(init_functions) if init_functions else ''
|
|
553
|
+
init_function = "function() { " + init_code + " }"
|
|
554
|
+
|
|
555
|
+
# Generate prerender function (chỉ sử dụng vars_declaration thuần túy, không có directives)
|
|
556
|
+
# Fix double braces issue by using string formatting instead of f-string
|
|
557
|
+
if vars_declaration:
|
|
558
|
+
prerender_vars_line = " " + vars_declaration + "\n"
|
|
559
|
+
else:
|
|
560
|
+
prerender_vars_line = ""
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
prerender_func = self.function_generators.generate_prerender_function(has_await, has_fetch, prerender_vars_line, view_id_line, template_content, extended_view, extends_expression, extends_data, sections_info, conditional_content, has_prerender)
|
|
564
|
+
|
|
565
|
+
# Generate loadServerData function - empty function (logic removed)
|
|
566
|
+
load_server_data_func = self.function_generators.generate_load_server_data_function()
|
|
567
|
+
|
|
568
|
+
# CSS functions - combine CSS từ @onInit và @register
|
|
569
|
+
combined_css_content = css_content.copy() if css_content else []
|
|
570
|
+
|
|
571
|
+
# Thêm CSS từ @register
|
|
572
|
+
if register_data and register_data.get('css'):
|
|
573
|
+
css_data = register_data['css']
|
|
574
|
+
|
|
575
|
+
# Thêm inline CSS
|
|
576
|
+
if css_data.get('inline'):
|
|
577
|
+
combined_css_content.append(css_data['inline'])
|
|
578
|
+
|
|
579
|
+
# Thêm external CSS links
|
|
580
|
+
if css_data.get('external'):
|
|
581
|
+
for css_url in css_data['external']:
|
|
582
|
+
combined_css_content.append(f'/* External CSS: {css_url} */')
|
|
583
|
+
|
|
584
|
+
# CSS functions removed - using scripts/styles arrays instead
|
|
585
|
+
|
|
586
|
+
# Process sections_info to handle script objects properly
|
|
587
|
+
sections_js_object = {}
|
|
588
|
+
for section in sections_info:
|
|
589
|
+
section_name = section.get('name', '')
|
|
590
|
+
section_config = {
|
|
591
|
+
'type': section.get('type', 'short'),
|
|
592
|
+
'preloader': section.get('preloader', False),
|
|
593
|
+
'useVars': section.get('useVars', False)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
# Handle script object - keep as JavaScript object, not JSON string
|
|
597
|
+
if 'script' in section and section['script']:
|
|
598
|
+
section_config['script'] = section['script'] # Keep as JavaScript object string
|
|
599
|
+
else:
|
|
600
|
+
section_config['script'] = '{}'
|
|
601
|
+
|
|
602
|
+
sections_js_object[section_name] = section_config
|
|
603
|
+
|
|
604
|
+
# Create JavaScript object string instead of array with proper formatting
|
|
605
|
+
sections_parts = []
|
|
606
|
+
for name, config in sections_js_object.items():
|
|
607
|
+
section_parts = [
|
|
608
|
+
f'"type":"{config["type"]}"',
|
|
609
|
+
f'"preloader":{str(config["preloader"]).lower()}',
|
|
610
|
+
f'"useVars":{str(config["useVars"]).lower()}',
|
|
611
|
+
f'"script":{config["script"]}'
|
|
612
|
+
]
|
|
613
|
+
section_content = ",\n ".join(section_parts)
|
|
614
|
+
sections_parts.append(f' "{name}":{{\n {section_content}\n }}')
|
|
615
|
+
|
|
616
|
+
if sections_parts:
|
|
617
|
+
sections_json = '{\n' + ',\n'.join(sections_parts) + '\n }'
|
|
618
|
+
else:
|
|
619
|
+
sections_json = '{}'
|
|
620
|
+
|
|
621
|
+
# Collect long section names for renderLongSections
|
|
622
|
+
render_long_sections = []
|
|
623
|
+
for section in sections_info:
|
|
624
|
+
section_type = section.get('type', 'short')
|
|
625
|
+
if section_type == 'long':
|
|
626
|
+
section_name = section.get('name', '')
|
|
627
|
+
if section_name:
|
|
628
|
+
render_long_sections.append(f'"{section_name}"')
|
|
629
|
+
|
|
630
|
+
render_long_sections_json = '[' + ','.join(render_long_sections) + ']'
|
|
631
|
+
|
|
632
|
+
# Analyze sections in render and prerender functions
|
|
633
|
+
render_sections = self._extract_sections_from_template(template_content, sections_info)
|
|
634
|
+
prerender_sections = self._extract_sections_from_prerender(has_prerender, has_await, has_fetch, sections_info)
|
|
635
|
+
|
|
636
|
+
render_sections_json = '[' + ','.join([f'"{section}"' for section in render_sections]) + ']'
|
|
637
|
+
prerender_sections_json = '[' + ','.join([f'"{section}"' for section in prerender_sections]) + ']'
|
|
638
|
+
|
|
639
|
+
if extended_view:
|
|
640
|
+
# Static view name
|
|
641
|
+
super_view_config = "'" + extended_view + "'"
|
|
642
|
+
has_super_view = "true"
|
|
643
|
+
elif extends_expression:
|
|
644
|
+
# Dynamic expression - add to config
|
|
645
|
+
super_view_config = extends_expression
|
|
646
|
+
has_super_view = "true"
|
|
647
|
+
else:
|
|
648
|
+
super_view_config = "null"
|
|
649
|
+
has_super_view = "false"
|
|
650
|
+
|
|
651
|
+
# Determine view type
|
|
652
|
+
view_type = "view" # default
|
|
653
|
+
if view_type_data and view_type_data.get('viewType'):
|
|
654
|
+
view_type = view_type_data['viewType']
|
|
655
|
+
|
|
656
|
+
# Prepare wrapperConfig value for view config (from @wrap directive)
|
|
657
|
+
wrapper_config_value = ""
|
|
658
|
+
if wrapper_config:
|
|
659
|
+
# From @wrap directive - use directly
|
|
660
|
+
wrapper_config_value = wrapper_config
|
|
661
|
+
else:
|
|
662
|
+
# Default config
|
|
663
|
+
wrapper_config_value = "{ enable: false, tag: null, follow: true, attributes: {} }"
|
|
664
|
+
|
|
665
|
+
# Normalize legacy `follow` -> `subscribe` in wrapper_config_value.
|
|
666
|
+
# If `subscribe` already present, remove `follow`. Otherwise replace `follow` key with `subscribe`.
|
|
667
|
+
try:
|
|
668
|
+
import re as _re_norm
|
|
669
|
+
if wrapper_config_value and _re_norm.search(r'\bfollow\s*:', wrapper_config_value):
|
|
670
|
+
# If subscribe exists, remove follow occurrence
|
|
671
|
+
if _re_norm.search(r'\bsubscribe\s*:', wrapper_config_value):
|
|
672
|
+
# Remove follow: ... (handle commas and spacing)
|
|
673
|
+
wrapper_config_value = _re_norm.sub(r',?\s*follow\s*:\s*(true|false|\[[^\]]*\]|"[^"]*"|\'[^\']*\'|[^,\}]+)\s*,?', wrapper_config_value)
|
|
674
|
+
# Normalize commas
|
|
675
|
+
wrapper_config_value = _re_norm.sub(r',\s*,', ',', wrapper_config_value)
|
|
676
|
+
wrapper_config_value = _re_norm.sub(r',\s*}', '}', wrapper_config_value)
|
|
677
|
+
else:
|
|
678
|
+
# Replace follow: VALUE with subscribe: VALUE
|
|
679
|
+
def _replace_follow(m):
|
|
680
|
+
val = m.group(1)
|
|
681
|
+
return f'subscribe: {val}'
|
|
682
|
+
wrapper_config_value = _re_norm.sub(r'\bfollow\s*:\s*(true|false|\[[^\]]*\]|"[^"]*"|\'[^\']*\'|[^,\}]+)', _replace_follow, wrapper_config_value)
|
|
683
|
+
except Exception:
|
|
684
|
+
pass
|
|
685
|
+
|
|
686
|
+
# Prepare WRAPPER_CONFIG properties to add to view config (from wraper.js)
|
|
687
|
+
# These are separate properties, NOT nested in wrapperConfig
|
|
688
|
+
wrapper_props_line = ""
|
|
689
|
+
if wrapper_config_content:
|
|
690
|
+
config_content = wrapper_config_content.strip()
|
|
691
|
+
# Remove trailing comma if exists
|
|
692
|
+
if config_content.endswith(','):
|
|
693
|
+
config_content = config_content[:-1]
|
|
694
|
+
# Add comma at the end
|
|
695
|
+
wrapper_props_line = "\n " + config_content.replace('\n', '\n ') + ","
|
|
696
|
+
# Detect state keys for registration - chỉ từ @let và @const, không từ @useState
|
|
697
|
+
state_keys = self._detect_state_keys(blade_code, let_declarations, const_declarations, "")
|
|
698
|
+
|
|
699
|
+
# Add wrapper function content to view function
|
|
700
|
+
wrapper_function_line = self._add_wrapper_content(wrapper_function_content)
|
|
701
|
+
|
|
702
|
+
# NEW: Add wrapper declarations (vars, let, const, useState) at the beginning
|
|
703
|
+
if wrapper_declarations_code:
|
|
704
|
+
wrapper_function_line = wrapper_function_line + wrapper_declarations_code + "\n"
|
|
705
|
+
|
|
706
|
+
# Add setup script to top of file (before export function)
|
|
707
|
+
# Collect setup content ONLY from setup scripts (with setup/view/component attributes)
|
|
708
|
+
# Regular scripts should NOT be here - they are in scripts array and will be inserted into DOM when mounted
|
|
709
|
+
setup_script_line = ""
|
|
710
|
+
setup_scripts = []
|
|
711
|
+
|
|
712
|
+
# Add setup content from setup scripts (imports + remaining code)
|
|
713
|
+
# NOTE: Only scripts with setup/view/component attributes are in setupContent
|
|
714
|
+
# Regular scripts (without these attributes) are in scripts array and should NOT be here
|
|
715
|
+
if register_data and register_data.get('setupContent'):
|
|
716
|
+
setup_content = register_data['setupContent']
|
|
717
|
+
if setup_content and setup_content.strip():
|
|
718
|
+
setup_scripts.append(setup_content)
|
|
719
|
+
|
|
720
|
+
# ❌ REMOVED: Regular scripts should NOT be added to setup_scripts
|
|
721
|
+
# They are already in scripts array and will be inserted into DOM when mounted
|
|
722
|
+
# Adding them here causes duplicate execution (file-level + DOM insertion)
|
|
723
|
+
|
|
724
|
+
# Generate View.registerScript() calls for inline scripts
|
|
725
|
+
# This ensures scripts are registered and can be retrieved by view name and key
|
|
726
|
+
script_registrations_line = ""
|
|
727
|
+
script_registrations = []
|
|
728
|
+
script_function_map = {} # Map script index to function name
|
|
729
|
+
|
|
730
|
+
if register_data and register_data.get('scripts'):
|
|
731
|
+
scripts_data = register_data['scripts']
|
|
732
|
+
for index, script in enumerate(scripts_data):
|
|
733
|
+
if script['type'] == 'code' and script.get('content', '').strip():
|
|
734
|
+
# Generate unique function name/key for script wrapper
|
|
735
|
+
script_function_name = f"__script_{view_name.replace('.', '_')}_{index}"
|
|
736
|
+
script_function_map[index] = script_function_name
|
|
737
|
+
|
|
738
|
+
# Wrap script content in View.registerScript() call
|
|
739
|
+
script_content = script['content']
|
|
740
|
+
# Indent script content for better readability
|
|
741
|
+
indented_content = '\n'.join(' ' + line if line.strip() else line
|
|
742
|
+
for line in script_content.split('\n'))
|
|
743
|
+
|
|
744
|
+
script_registration = f"""View.registerScript('{view_name}', '{script_function_name}', function () {{
|
|
745
|
+
{indented_content}
|
|
746
|
+
}});"""
|
|
747
|
+
script_registrations.append(script_registration)
|
|
748
|
+
|
|
749
|
+
if script_registrations:
|
|
750
|
+
script_registrations_line = '\n\n'.join(script_registrations) + "\n\n"
|
|
751
|
+
|
|
752
|
+
if setup_scripts:
|
|
753
|
+
setup_script_line = '\n\n'.join(setup_scripts) + "\n\n"
|
|
754
|
+
|
|
755
|
+
# Add lifecycle script to userDefined section (inside function)
|
|
756
|
+
lifecycle_script_line = ""
|
|
757
|
+
if register_data and register_data.get('lifecycle'):
|
|
758
|
+
lifecycle_script = register_data['lifecycle']
|
|
759
|
+
if lifecycle_script and lifecycle_script.strip():
|
|
760
|
+
# Lifecycle script is raw object content - use directly
|
|
761
|
+
lifecycle_script_line = lifecycle_script
|
|
762
|
+
|
|
763
|
+
# NOTE: State registration is now handled by DeclarationTracker in wrapper_declarations_code
|
|
764
|
+
# No need to add state registration here anymore
|
|
765
|
+
# if state_keys:
|
|
766
|
+
# # Add useState declarations with registration
|
|
767
|
+
# usestate_declarations_js = self._generate_usestate_declarations(state_keys)
|
|
768
|
+
# wrapper_function_line = wrapper_function_line + usestate_declarations_js
|
|
769
|
+
|
|
770
|
+
# NOTE: Update functions are now handled by DeclarationTracker
|
|
771
|
+
# No need to add update functions here anymore
|
|
772
|
+
# if self.update_functions:
|
|
773
|
+
# update_functions_js = "\n".join(self.update_functions) + "\n"
|
|
774
|
+
# wrapper_function_line = wrapper_function_line + update_functions_js
|
|
775
|
+
|
|
776
|
+
# Prepare userDefined object từ register_data (separate from config)
|
|
777
|
+
user_defined_value = "{}"
|
|
778
|
+
if lifecycle_script_line:
|
|
779
|
+
user_defined_value = lifecycle_script_line
|
|
780
|
+
else:
|
|
781
|
+
user_defined_value = "{}"
|
|
782
|
+
|
|
783
|
+
# Setup imports will be handled in build.py, not here
|
|
784
|
+
|
|
785
|
+
# Add resources array từ register_data
|
|
786
|
+
resources_line = ""
|
|
787
|
+
if register_data and register_data.get('resources'):
|
|
788
|
+
resources_data = register_data['resources']
|
|
789
|
+
resources_json_parts = []
|
|
790
|
+
|
|
791
|
+
for resource in resources_data:
|
|
792
|
+
# Process Blade syntax in attrs
|
|
793
|
+
processed_attrs = {}
|
|
794
|
+
has_template_strings = False
|
|
795
|
+
|
|
796
|
+
for key, value in resource["attrs"].items():
|
|
797
|
+
if isinstance(value, str) and '{{' in value and '}}' in value:
|
|
798
|
+
# Convert Blade syntax to template string
|
|
799
|
+
processed_value = self._convert_blade_to_template_string(value)
|
|
800
|
+
processed_attrs[key] = f'`{processed_value}`'
|
|
801
|
+
has_template_strings = True
|
|
802
|
+
else:
|
|
803
|
+
processed_attrs[key] = value
|
|
804
|
+
|
|
805
|
+
# Format resource object
|
|
806
|
+
if has_template_strings:
|
|
807
|
+
# Build manually to handle template strings
|
|
808
|
+
attrs_parts = []
|
|
809
|
+
for key, value in processed_attrs.items():
|
|
810
|
+
if isinstance(value, str) and value.startswith('`') and value.endswith('`'):
|
|
811
|
+
# Template string - don't quote
|
|
812
|
+
attrs_parts.append(f'"{key}":{value}')
|
|
813
|
+
else:
|
|
814
|
+
# Regular value - use JSON format
|
|
815
|
+
attrs_parts.append(f'"{key}":"{value}"')
|
|
816
|
+
|
|
817
|
+
attrs_str = '{' + ','.join(attrs_parts) + '}'
|
|
818
|
+
resource_parts = [
|
|
819
|
+
f'"tag":"{resource["tag"]}"',
|
|
820
|
+
f'"uuid":"{resource["uuid"]}"',
|
|
821
|
+
f'"attrs":{attrs_str}'
|
|
822
|
+
]
|
|
823
|
+
resources_json_parts.append('{' + ','.join(resource_parts) + '}')
|
|
824
|
+
else:
|
|
825
|
+
# No template strings - use regular JSON format
|
|
826
|
+
resource_parts = [
|
|
827
|
+
f'"tag":"{resource["tag"]}"',
|
|
828
|
+
f'"uuid":"{resource["uuid"]}"',
|
|
829
|
+
f'"attrs":{self.compiler_utils.format_attrs(resource["attrs"])}'
|
|
830
|
+
]
|
|
831
|
+
resources_json_parts.append('{' + ','.join(resource_parts) + '}')
|
|
832
|
+
|
|
833
|
+
# Handle template strings in final output
|
|
834
|
+
if any('`' in part for part in resources_json_parts):
|
|
835
|
+
resources_line = "\n resources: [" + ','.join(resources_json_parts) + "]"
|
|
836
|
+
else:
|
|
837
|
+
resources_json = '[' + ','.join(resources_json_parts) + ']'
|
|
838
|
+
resources_line = "\n resources: " + resources_json
|
|
839
|
+
else:
|
|
840
|
+
resources_line = "\n resources: []"
|
|
841
|
+
|
|
842
|
+
# Add scripts array từ register_data
|
|
843
|
+
# Use function wrapper for inline scripts to avoid JSON escaping issues
|
|
844
|
+
scripts_line = ""
|
|
845
|
+
if register_data and register_data.get('scripts'):
|
|
846
|
+
scripts_data = register_data['scripts']
|
|
847
|
+
scripts_json_parts = []
|
|
848
|
+
|
|
849
|
+
for index, script in enumerate(scripts_data):
|
|
850
|
+
script_parts = [f'"type":"{script["type"]}"']
|
|
851
|
+
|
|
852
|
+
if script['type'] == 'code':
|
|
853
|
+
# Use function wrapper instead of content string
|
|
854
|
+
if index in script_function_map:
|
|
855
|
+
script_function_name = script_function_map[index]
|
|
856
|
+
script_parts.append(f'"function":"{script_function_name}"')
|
|
857
|
+
else:
|
|
858
|
+
# Fallback: use content if function not generated
|
|
859
|
+
content_escaped = script["content"].replace('"', '\\"').replace('\n', '\\n')
|
|
860
|
+
script_parts.append(f'"content":"{content_escaped}"')
|
|
861
|
+
elif script['type'] == 'src':
|
|
862
|
+
# Process Blade syntax in src
|
|
863
|
+
src_value = script["src"]
|
|
864
|
+
if '{{' in src_value and '}}' in src_value:
|
|
865
|
+
# Convert Blade syntax to template string
|
|
866
|
+
processed_src = self._convert_blade_to_template_string(src_value)
|
|
867
|
+
script_parts.append(f'"src":`{processed_src}`')
|
|
868
|
+
else:
|
|
869
|
+
# Regular string
|
|
870
|
+
script_parts.append(f'"src":"{src_value}"')
|
|
871
|
+
|
|
872
|
+
# Add id, className, attributes
|
|
873
|
+
if script.get('id'):
|
|
874
|
+
script_parts.append(f'"id":"{script["id"]}"')
|
|
875
|
+
if script.get('className'):
|
|
876
|
+
script_parts.append(f'"className":"{script["className"]}"')
|
|
877
|
+
if script.get('attributes'):
|
|
878
|
+
attrs_json = self.compiler_utils.format_attributes_to_json(script['attributes'])
|
|
879
|
+
script_parts.append(f'"attributes":{attrs_json}')
|
|
880
|
+
|
|
881
|
+
scripts_json_parts.append('{' + ','.join(script_parts) + '}')
|
|
882
|
+
|
|
883
|
+
# Use special handling for template strings - don't put in JSON string
|
|
884
|
+
if any('`' in part for part in scripts_json_parts):
|
|
885
|
+
# Build JavaScript array manually to handle template strings
|
|
886
|
+
scripts_line = "\n scripts: [" + ','.join(scripts_json_parts) + "]"
|
|
887
|
+
else:
|
|
888
|
+
scripts_json = '[' + ','.join(scripts_json_parts) + ']'
|
|
889
|
+
scripts_line = "\n scripts: " + scripts_json
|
|
890
|
+
else:
|
|
891
|
+
scripts_line = "\n scripts: []"
|
|
892
|
+
|
|
893
|
+
# Add styles array từ register_data
|
|
894
|
+
styles_line = ""
|
|
895
|
+
if register_data and register_data.get('styles'):
|
|
896
|
+
styles_data = register_data['styles']
|
|
897
|
+
styles_json_parts = []
|
|
898
|
+
|
|
899
|
+
for style in styles_data:
|
|
900
|
+
style_parts = [f'"type":"{style["type"]}"']
|
|
901
|
+
|
|
902
|
+
if style['type'] == 'code':
|
|
903
|
+
content_escaped = style["content"].replace('"', '\\"').replace('\n', '\\n')
|
|
904
|
+
style_parts.append(f'"content":"{content_escaped}"')
|
|
905
|
+
elif style['type'] == 'href':
|
|
906
|
+
# Process Blade syntax in href
|
|
907
|
+
href_value = style["href"]
|
|
908
|
+
if '{{' in href_value and '}}' in href_value:
|
|
909
|
+
# Convert Blade syntax to template string
|
|
910
|
+
processed_href = self._convert_blade_to_template_string(href_value)
|
|
911
|
+
style_parts.append(f'"href":`{processed_href}`')
|
|
912
|
+
else:
|
|
913
|
+
# Regular string
|
|
914
|
+
style_parts.append(f'"href":"{href_value}"')
|
|
915
|
+
|
|
916
|
+
# Add id, className, attributes
|
|
917
|
+
if style.get('id'):
|
|
918
|
+
style_parts.append(f'"id":"{style["id"]}"')
|
|
919
|
+
if style.get('className'):
|
|
920
|
+
style_parts.append(f'"className":"{style["className"]}"')
|
|
921
|
+
if style.get('attributes'):
|
|
922
|
+
attrs_json = self.compiler_utils.format_attributes_to_json(style['attributes'])
|
|
923
|
+
style_parts.append(f'"attributes":{attrs_json}')
|
|
924
|
+
|
|
925
|
+
styles_json_parts.append('{' + ','.join(style_parts) + '}')
|
|
926
|
+
|
|
927
|
+
# Use special handling for template strings - don't put in JSON string
|
|
928
|
+
if any('`' in part for part in styles_json_parts):
|
|
929
|
+
# Build JavaScript array manually to handle template strings
|
|
930
|
+
styles_line = "\n styles: [" + ','.join(styles_json_parts) + "]"
|
|
931
|
+
else:
|
|
932
|
+
styles_json = '[' + ','.join(styles_json_parts) + ']'
|
|
933
|
+
styles_line = "\n styles: " + styles_json
|
|
934
|
+
else:
|
|
935
|
+
styles_line = "\n styles: []"
|
|
936
|
+
|
|
937
|
+
# If wrapperConfig contains a `subscribe` property and no explicit subscribe_config
|
|
938
|
+
# was provided via other directives, read it but keep the property inside
|
|
939
|
+
# `wrapper_config_value` (do not remove it). This preserves subscribe inside
|
|
940
|
+
# wrapperConfig while still allowing top-level `subscribe` detection when needed.
|
|
941
|
+
if subscribe_config is None and wrapper_config_value:
|
|
942
|
+
try:
|
|
943
|
+
import re as _re_sub
|
|
944
|
+
m = _re_sub.search(r'\bsubscribe\s*:\s*(true|false|\[[^\]]*\])', wrapper_config_value)
|
|
945
|
+
if m:
|
|
946
|
+
wrapper_subscribe_val = m.group(1)
|
|
947
|
+
# Set subscribe_config from wrapper but do NOT remove it from wrapper_config_value
|
|
948
|
+
if wrapper_subscribe_val in ('true', 'false'):
|
|
949
|
+
subscribe_config = (wrapper_subscribe_val == 'true')
|
|
950
|
+
else:
|
|
951
|
+
inner = wrapper_subscribe_val.strip()[1:-1].strip()
|
|
952
|
+
if inner:
|
|
953
|
+
parts = [p.strip().strip('"\'') for p in inner.split(',') if p.strip()]
|
|
954
|
+
subscribe_config = parts
|
|
955
|
+
else:
|
|
956
|
+
subscribe_config = []
|
|
957
|
+
except Exception:
|
|
958
|
+
pass
|
|
959
|
+
|
|
960
|
+
# If wrapper exists and no explicit subscribe/dontsubscribe directive was provided,
|
|
961
|
+
# default to enabling subscribe and ensure wrapper_config_value contains it.
|
|
962
|
+
if wrapper_config and subscribe_config is None and wrapper_config_value:
|
|
963
|
+
try:
|
|
964
|
+
import re as _re_ins
|
|
965
|
+
if not _re_ins.search(r'\bsubscribe\s*:', wrapper_config_value):
|
|
966
|
+
# Insert subscribe: true right after opening brace
|
|
967
|
+
wrapper_config_value = _re_ins.sub(r'^\s*\{', '{ subscribe: true,', wrapper_config_value, count=1)
|
|
968
|
+
# Also set top-level subscribe_config to True for consistency
|
|
969
|
+
subscribe_config = True
|
|
970
|
+
except Exception:
|
|
971
|
+
pass
|
|
972
|
+
|
|
973
|
+
# Build the return string carefully to avoid syntax errors
|
|
974
|
+
# Build subscribe config JS value
|
|
975
|
+
# Optimization: If no vars/useState and no explicit subscribe config, default to false
|
|
976
|
+
if subscribe_config is None:
|
|
977
|
+
# Check if view uses vars or state
|
|
978
|
+
has_vars = bool(vars_declaration and vars_declaration.strip())
|
|
979
|
+
has_state = bool(state_keys) # state_keys detected from @let/@const/@useState
|
|
980
|
+
|
|
981
|
+
# If no state/vars used and no explicit subscribe directive, default to false
|
|
982
|
+
# Reasoning: without state/vars, re-rendering won't change anything
|
|
983
|
+
if not has_vars and not has_state:
|
|
984
|
+
subscribe_js = 'false'
|
|
985
|
+
else:
|
|
986
|
+
subscribe_js = 'true'
|
|
987
|
+
elif isinstance(subscribe_config, bool):
|
|
988
|
+
subscribe_js = 'true' if subscribe_config else 'false'
|
|
989
|
+
else:
|
|
990
|
+
subscribe_js = json.dumps(subscribe_config, ensure_ascii=False)
|
|
991
|
+
|
|
992
|
+
# If an explicit subscribe/dontsubscribe directive was provided,
|
|
993
|
+
# ensure wrapperConfig.subscribe mirrors that value so runtime
|
|
994
|
+
# logic (which reads wrapperConfig) remains consistent.
|
|
995
|
+
if wrapper_config_value and subscribe_config is not None:
|
|
996
|
+
try:
|
|
997
|
+
import re as _re_fix
|
|
998
|
+
if _re_fix.search(r'\bsubscribe\s*:', wrapper_config_value):
|
|
999
|
+
wrapper_config_value = _re_fix.sub(r'subscribe\s*:\s*[^,}]+', f'subscribe: {subscribe_js}', wrapper_config_value, count=1)
|
|
1000
|
+
else:
|
|
1001
|
+
wrapper_config_value = _re_fix.sub(r'^\s*\{', '{ subscribe: ' + subscribe_js + ',', wrapper_config_value, count=1)
|
|
1002
|
+
except Exception:
|
|
1003
|
+
pass
|
|
1004
|
+
|
|
1005
|
+
# Add View import if scripts are registered
|
|
1006
|
+
view_import = ""
|
|
1007
|
+
if script_registrations:
|
|
1008
|
+
view_import = "import { View } from 'onelaraveljs';\n\n"
|
|
1009
|
+
|
|
1010
|
+
return_template = setup_script_line + view_import + script_registrations_line + """export function """ + function_name + """($$$DATA$$$ = {}, systemData = {}) {
|
|
1011
|
+
const {App, View, __base__, __layout__, __page__, __component__, __template__, __context__, __partial__, __system__, __env = {}, __helper = {}} = systemData;
|
|
1012
|
+
const __VIEW_PATH__ = '""" + view_name + """';
|
|
1013
|
+
const __VIEW_ID__ = $$$DATA$$$.__SSR_VIEW_ID__ || """ + JS_FUNCTION_PREFIX + """.generateViewId();
|
|
1014
|
+
const __VIEW_TYPE__ = '""" + view_type + """';
|
|
1015
|
+
const cpparts = __VIEW_PATH__.split('.');
|
|
1016
|
+
cpparts.pop(); // Remove view name
|
|
1017
|
+
const __VIEW_NAMESPACE__ = cpparts.length > 0 ? cpparts.join('.') + '.' : '';
|
|
1018
|
+
""" + wrapper_function_line + """
|
|
1019
|
+
self.setup('""" + view_name + """', """ + user_defined_value + """, {
|
|
1020
|
+
superView: """ + super_view_config + """,
|
|
1021
|
+
hasSuperView: """ + has_super_view + """,
|
|
1022
|
+
viewType: '""" + view_type + """',
|
|
1023
|
+
sections: """ + sections_json + """,
|
|
1024
|
+
wrapperConfig: """ + wrapper_config_value + """,""" + wrapper_props_line + """
|
|
1025
|
+
hasAwaitData: """ + str(has_await).lower() + """,
|
|
1026
|
+
hasFetchData: """ + str(has_fetch).lower() + """,
|
|
1027
|
+
subscribe: """ + subscribe_js + """,
|
|
1028
|
+
fetch: """ + (self.compiler_utils.format_fetch_config(fetch_config) if fetch_config else 'null') + """,
|
|
1029
|
+
data: $$$DATA$$$,
|
|
1030
|
+
viewId: __VIEW_ID__,
|
|
1031
|
+
path: __VIEW_PATH__,
|
|
1032
|
+
usesVars: """ + str(bool(vars_declaration)).lower() + """,
|
|
1033
|
+
hasSections: """ + str(bool(sections)).lower() + """,
|
|
1034
|
+
hasSectionPreload: """ + str(any(section.get('preloader', False) for section in sections_info)).lower() + """,
|
|
1035
|
+
hasPrerender: """ + str(has_prerender).lower() + """,
|
|
1036
|
+
renderLongSections: """ + render_long_sections_json + """,
|
|
1037
|
+
renderSections: """ + render_sections_json + """,
|
|
1038
|
+
prerenderSections: """ + prerender_sections_json + """,""" + scripts_line + """,""" + styles_line + """,""" + resources_line + """,
|
|
1039
|
+
commitConstructorData: function() {
|
|
1040
|
+
// Then update states from data
|
|
1041
|
+
""" + self._generate_state_updates(state_declarations) + """
|
|
1042
|
+
// Finally lock state updates
|
|
1043
|
+
""" + ("lockUpdateRealState();" if state_declarations else "") + """
|
|
1044
|
+
},
|
|
1045
|
+
updateVariableData: function(data) {
|
|
1046
|
+
// Update all variables first
|
|
1047
|
+
for (const key in data) {
|
|
1048
|
+
if (data.hasOwnProperty(key)) {
|
|
1049
|
+
// Call updateVariableItemData directly from config
|
|
1050
|
+
if (typeof this.config.updateVariableItemData === 'function') {
|
|
1051
|
+
this.config.updateVariableItemData.call(this, key, data[key]);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
// Then update states from data
|
|
1056
|
+
""" + self._generate_state_updates(state_declarations) + """
|
|
1057
|
+
// Finally lock state updates
|
|
1058
|
+
""" + ("lockUpdateRealState();" if state_declarations else "") + """
|
|
1059
|
+
},
|
|
1060
|
+
updateVariableItemData: function(key, value) {
|
|
1061
|
+
this.data[key] = value;
|
|
1062
|
+
if (typeof __UPDATE_DATA_TRAIT__[key] === "function") {
|
|
1063
|
+
__UPDATE_DATA_TRAIT__[key](value);
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
prerender: """ + prerender_func + """,
|
|
1067
|
+
render: """ + render_function + """
|
|
1068
|
+
});
|
|
1069
|
+
return self;
|
|
1070
|
+
}"""
|
|
1071
|
+
|
|
1072
|
+
# Restore verbatim blocks in final return_template (in case any were in scripts/styles)
|
|
1073
|
+
# For scripts/styles, we also need to escape backticks and ${} if they're in template strings
|
|
1074
|
+
# Escape to prevent breaking template string syntax
|
|
1075
|
+
for placeholder, content in verbatim_blocks.items():
|
|
1076
|
+
# Escape backticks and ${} in verbatim content for safe insertion into template strings
|
|
1077
|
+
# Step 1: Protect already escaped sequences
|
|
1078
|
+
protected_content = content.replace('\\`', '__ESCAPED_BACKTICK__')
|
|
1079
|
+
protected_content = protected_content.replace('\\${', '__ESCAPED_DOLLAR_BRACE__')
|
|
1080
|
+
|
|
1081
|
+
# Step 2: Escape unescaped backticks and ${ sequences
|
|
1082
|
+
protected_content = protected_content.replace('`', '\\`')
|
|
1083
|
+
protected_content = protected_content.replace('${', '\\${')
|
|
1084
|
+
|
|
1085
|
+
# Step 3: Restore protected sequences (now double-escaped)
|
|
1086
|
+
protected_content = protected_content.replace('__ESCAPED_BACKTICK__', '\\\\`')
|
|
1087
|
+
protected_content = protected_content.replace('__ESCAPED_DOLLAR_BRACE__', '\\\\${')
|
|
1088
|
+
|
|
1089
|
+
return_template = return_template.replace(placeholder, protected_content)
|
|
1090
|
+
|
|
1091
|
+
return return_template
|
|
1092
|
+
|
|
1093
|
+
def _add_wrapper_content(self, wrapper_function_content):
|
|
1094
|
+
"""Add wrapper content to view function"""
|
|
1095
|
+
if wrapper_function_content:
|
|
1096
|
+
return wrapper_function_content + "\n"
|
|
1097
|
+
return ""
|
|
1098
|
+
|
|
1099
|
+
def _extract_sections_from_template(self, template_content, sections_info):
|
|
1100
|
+
"""Extract section names that are used in the template content"""
|
|
1101
|
+
sections_used = []
|
|
1102
|
+
if not template_content or not sections_info:
|
|
1103
|
+
return sections_used
|
|
1104
|
+
|
|
1105
|
+
for section in sections_info:
|
|
1106
|
+
section_name = section.get('name', '')
|
|
1107
|
+
if section_name:
|
|
1108
|
+
# Check if section is used in template content
|
|
1109
|
+
if f"App.View.section('{section_name}'" in template_content:
|
|
1110
|
+
sections_used.append(section_name)
|
|
1111
|
+
|
|
1112
|
+
return sections_used
|
|
1113
|
+
|
|
1114
|
+
def _extract_sections_from_prerender(self, has_prerender, has_await, has_fetch, sections_info):
|
|
1115
|
+
"""Extract section names that are used in prerender function"""
|
|
1116
|
+
sections_used = []
|
|
1117
|
+
|
|
1118
|
+
if not has_prerender:
|
|
1119
|
+
return sections_used
|
|
1120
|
+
|
|
1121
|
+
# If has prerender, check which sections are used
|
|
1122
|
+
# For now, we'll include sections that have preloader or are used with await/fetch
|
|
1123
|
+
for section in sections_info:
|
|
1124
|
+
section_name = section.get('name', '')
|
|
1125
|
+
if section_name:
|
|
1126
|
+
# Include sections that have preloader
|
|
1127
|
+
if section.get('preloader', False):
|
|
1128
|
+
sections_used.append(section_name)
|
|
1129
|
+
# Include sections used with await/fetch (these typically need prerender)
|
|
1130
|
+
elif (has_await or has_fetch) and section.get('useVars', False):
|
|
1131
|
+
sections_used.append(section_name)
|
|
1132
|
+
|
|
1133
|
+
return sections_used
|
|
1134
|
+
|
|
1135
|
+
def _extract_usestate_variables(self, usestate_declarations, all_declarations):
|
|
1136
|
+
"""
|
|
1137
|
+
Extract usestate variable names from declarations
|
|
1138
|
+
Returns a set of variable names that have useState declarations
|
|
1139
|
+
Includes both state variable names and setter names
|
|
1140
|
+
"""
|
|
1141
|
+
usestate_variables = set()
|
|
1142
|
+
|
|
1143
|
+
# Extract from usestate_declarations string
|
|
1144
|
+
if usestate_declarations:
|
|
1145
|
+
# Pattern: const [stateKey, setStateKey] = useState(...)
|
|
1146
|
+
matches = re.findall(r'const\s+\[([^,]+),\s*([^\]]+)\]', usestate_declarations)
|
|
1147
|
+
for match in matches:
|
|
1148
|
+
state_var = match[0].strip()
|
|
1149
|
+
setter_var = match[1].strip()
|
|
1150
|
+
if state_var and state_var.isalnum():
|
|
1151
|
+
usestate_variables.add(state_var)
|
|
1152
|
+
if setter_var and setter_var.isalnum():
|
|
1153
|
+
usestate_variables.add(setter_var)
|
|
1154
|
+
|
|
1155
|
+
# Extract from all_declarations (from DeclarationTracker)
|
|
1156
|
+
for decl in all_declarations:
|
|
1157
|
+
if decl.get('type') in ['useState', 'const', 'let']:
|
|
1158
|
+
variables = decl.get('variables', [])
|
|
1159
|
+
for var in variables:
|
|
1160
|
+
if var.get('isUseState'):
|
|
1161
|
+
names = var.get('names', [])
|
|
1162
|
+
if names and len(names) > 0:
|
|
1163
|
+
# First name is the state variable
|
|
1164
|
+
state_var = names[0]
|
|
1165
|
+
if state_var and state_var.isalnum():
|
|
1166
|
+
usestate_variables.add(state_var)
|
|
1167
|
+
# Second name is the setter
|
|
1168
|
+
if len(names) > 1:
|
|
1169
|
+
setter_var = names[1]
|
|
1170
|
+
if setter_var and setter_var.isalnum():
|
|
1171
|
+
usestate_variables.add(setter_var)
|
|
1172
|
+
|
|
1173
|
+
return usestate_variables
|
|
1174
|
+
|
|
1175
|
+
def _detect_state_keys(self, blade_code, let_declarations, const_declarations, usestate_declarations):
|
|
1176
|
+
"""Detect state keys from directives that use useState or destructuring"""
|
|
1177
|
+
state_keys = set()
|
|
1178
|
+
|
|
1179
|
+
# Check @useState directives
|
|
1180
|
+
if usestate_declarations:
|
|
1181
|
+
for declaration in usestate_declarations.split('\n'):
|
|
1182
|
+
if declaration.strip():
|
|
1183
|
+
# Extract state key from @useState(value, stateKey)
|
|
1184
|
+
# Pattern: const [stateKey] = useState(value);
|
|
1185
|
+
match = re.search(r'const\s+\[([^,]+),\s*[^]]+\]\s*=\s*useState\([^)]+\);', declaration)
|
|
1186
|
+
if match:
|
|
1187
|
+
state_key = match.group(1).strip()
|
|
1188
|
+
state_keys.add(state_key)
|
|
1189
|
+
|
|
1190
|
+
# Check @let and @const directives for destructuring patterns
|
|
1191
|
+
all_declarations = []
|
|
1192
|
+
if let_declarations:
|
|
1193
|
+
all_declarations.extend(let_declarations.split('\n'))
|
|
1194
|
+
if const_declarations:
|
|
1195
|
+
all_declarations.extend(const_declarations.split('\n'))
|
|
1196
|
+
|
|
1197
|
+
for declaration in all_declarations:
|
|
1198
|
+
if declaration.strip():
|
|
1199
|
+
# Check for [stateKey] = useState(...) pattern
|
|
1200
|
+
match = re.search(r'\[([^,]+),\s*[^]]+\]\s*=\s*useState\(', declaration)
|
|
1201
|
+
if match:
|
|
1202
|
+
state_key = match.group(1).strip()
|
|
1203
|
+
# Remove $ prefix if present
|
|
1204
|
+
if state_key.startswith('$'):
|
|
1205
|
+
state_key = state_key[1:]
|
|
1206
|
+
# Only add valid state keys (not empty or special characters)
|
|
1207
|
+
if state_key and state_key.isalnum():
|
|
1208
|
+
state_keys.add(state_key)
|
|
1209
|
+
|
|
1210
|
+
return list(state_keys)
|
|
1211
|
+
|
|
1212
|
+
def _generate_state_registration(self, state_keys):
|
|
1213
|
+
"""Generate state registration code"""
|
|
1214
|
+
if not state_keys:
|
|
1215
|
+
return ""
|
|
1216
|
+
|
|
1217
|
+
registration_lines = []
|
|
1218
|
+
for state_key in state_keys:
|
|
1219
|
+
registration_lines.append(f" __STATE__.__.register('{state_key}');")
|
|
1220
|
+
|
|
1221
|
+
registration_lines.append(" __STATE__.__.lockRegister();")
|
|
1222
|
+
|
|
1223
|
+
return '\n'.join(registration_lines) + '\n'
|
|
1224
|
+
|
|
1225
|
+
def _generate_usestate_declarations(self, state_keys):
|
|
1226
|
+
"""Generate useState declarations with custom setters"""
|
|
1227
|
+
if not state_keys:
|
|
1228
|
+
return ""
|
|
1229
|
+
|
|
1230
|
+
declaration_lines = []
|
|
1231
|
+
for state_key in state_keys:
|
|
1232
|
+
# Convert stateKey to set$stateKey format (e.g., todos -> set$todos)
|
|
1233
|
+
set_state_key = f'set${state_key}'
|
|
1234
|
+
declaration_lines.append(f" const {set_state_key} = __STATE__.__.register('{state_key}');")
|
|
1235
|
+
declaration_lines.append(f" let {state_key} = null;")
|
|
1236
|
+
declaration_lines.append(f" const {set_state_key} = (state) => {{")
|
|
1237
|
+
declaration_lines.append(f" {state_key} = state;")
|
|
1238
|
+
declaration_lines.append(f" {set_state_key}(state);")
|
|
1239
|
+
declaration_lines.append(f" }};")
|
|
1240
|
+
|
|
1241
|
+
# Don't add lockRegister - not needed anymore
|
|
1242
|
+
|
|
1243
|
+
return '\n'.join(declaration_lines) + '\n'
|
|
1244
|
+
|
|
1245
|
+
def _calculate_prerender_need(self, has_await, has_fetch, vars_declaration, sections_info, template_content, usestate_declarations, let_declarations, const_declarations):
|
|
1246
|
+
"""
|
|
1247
|
+
Calculate if prerender is needed based on complex logic:
|
|
1248
|
+
- Need (has_await OR has_fetch) AND vars_declaration AND (sections use vars OR useState/let/const with vars)
|
|
1249
|
+
"""
|
|
1250
|
+
# Must have await/fetch AND vars declaration
|
|
1251
|
+
if not (has_await or has_fetch) or not vars_declaration:
|
|
1252
|
+
return False
|
|
1253
|
+
|
|
1254
|
+
# Extract variable names from @vars declaration
|
|
1255
|
+
vars_names = self._extract_vars_names(vars_declaration)
|
|
1256
|
+
if not vars_names:
|
|
1257
|
+
return False
|
|
1258
|
+
|
|
1259
|
+
# Check if any section uses vars
|
|
1260
|
+
has_sections_with_vars = any(section.get('useVars', False) for section in sections_info)
|
|
1261
|
+
|
|
1262
|
+
# Check if template content uses vars
|
|
1263
|
+
has_template_with_vars = self._template_uses_vars(template_content, vars_names)
|
|
1264
|
+
|
|
1265
|
+
# Check if useState/let/const declarations use vars
|
|
1266
|
+
has_declarations_with_vars = self._declarations_use_vars(
|
|
1267
|
+
usestate_declarations, let_declarations, const_declarations, vars_names
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
# Need prerender if any of these conditions are true
|
|
1271
|
+
return has_sections_with_vars or has_template_with_vars or has_declarations_with_vars
|
|
1272
|
+
|
|
1273
|
+
def _extract_vars_names(self, vars_declaration):
|
|
1274
|
+
"""Extract variable names from @vars declaration"""
|
|
1275
|
+
if not vars_declaration:
|
|
1276
|
+
return []
|
|
1277
|
+
|
|
1278
|
+
# Extract variable names from {var1, var2} pattern (without let)
|
|
1279
|
+
import re
|
|
1280
|
+
pattern = r'\{([^}]+)\}'
|
|
1281
|
+
match = re.search(pattern, vars_declaration)
|
|
1282
|
+
if match:
|
|
1283
|
+
vars_str = match.group(1)
|
|
1284
|
+
# Split by comma and clean up
|
|
1285
|
+
return [var.strip().replace('$', '') for var in vars_str.split(',') if var.strip()]
|
|
1286
|
+
|
|
1287
|
+
return []
|
|
1288
|
+
|
|
1289
|
+
def _template_uses_vars(self, template_content, vars_names):
|
|
1290
|
+
"""Check if template content uses any of the vars"""
|
|
1291
|
+
if not template_content or not vars_names:
|
|
1292
|
+
return False
|
|
1293
|
+
|
|
1294
|
+
# Check if any var name appears in template content
|
|
1295
|
+
for var_name in vars_names:
|
|
1296
|
+
if f'${{{var_name}}}' in template_content or f'${{' + var_name + '}}' in template_content:
|
|
1297
|
+
return True
|
|
1298
|
+
|
|
1299
|
+
return False
|
|
1300
|
+
|
|
1301
|
+
def _declarations_use_vars(self, usestate_declarations, let_declarations, const_declarations, vars_names):
|
|
1302
|
+
"""Check if useState/let/const declarations use any of the vars"""
|
|
1303
|
+
if not vars_names:
|
|
1304
|
+
return False
|
|
1305
|
+
|
|
1306
|
+
all_declarations = []
|
|
1307
|
+
if usestate_declarations:
|
|
1308
|
+
all_declarations.append(usestate_declarations)
|
|
1309
|
+
if let_declarations:
|
|
1310
|
+
all_declarations.append(let_declarations)
|
|
1311
|
+
if const_declarations:
|
|
1312
|
+
all_declarations.append(const_declarations)
|
|
1313
|
+
|
|
1314
|
+
# Check if any declaration uses vars
|
|
1315
|
+
for declaration in all_declarations:
|
|
1316
|
+
for var_name in vars_names:
|
|
1317
|
+
if var_name in declaration:
|
|
1318
|
+
return True
|
|
1319
|
+
|
|
1320
|
+
return False
|
|
1321
|
+
|
|
1322
|
+
def _filter_directives_only(self, content):
|
|
1323
|
+
"""Filter content to only include directives (@if/@section/@block), remove pure HTML
|
|
1324
|
+
Returns filtered content or empty string if no directives found
|
|
1325
|
+
"""
|
|
1326
|
+
import re
|
|
1327
|
+
|
|
1328
|
+
if not content or not content.strip():
|
|
1329
|
+
return ''
|
|
1330
|
+
|
|
1331
|
+
# Extract only directive expressions (${...})
|
|
1332
|
+
# This regex finds ${...} patterns with proper nesting
|
|
1333
|
+
directive_parts = []
|
|
1334
|
+
|
|
1335
|
+
i = 0
|
|
1336
|
+
while i < len(content):
|
|
1337
|
+
# Find next ${
|
|
1338
|
+
match = re.search(r'\$\{(?:App\.View\.execute|App\.View\.section|this\.__section|this\.__useBlock|App\.View\.useBlock|this\.__subscribeBlock)\(', content[i:])
|
|
1339
|
+
|
|
1340
|
+
if not match:
|
|
1341
|
+
break
|
|
1342
|
+
|
|
1343
|
+
# Found a directive start
|
|
1344
|
+
start_pos = i + match.start()
|
|
1345
|
+
|
|
1346
|
+
# Find matching closing }
|
|
1347
|
+
depth = 0
|
|
1348
|
+
pos = start_pos + 2 # Skip ${
|
|
1349
|
+
in_string = False
|
|
1350
|
+
string_char = None
|
|
1351
|
+
|
|
1352
|
+
while pos < len(content):
|
|
1353
|
+
char = content[pos]
|
|
1354
|
+
|
|
1355
|
+
# Handle string escaping
|
|
1356
|
+
if char == '\\' and in_string:
|
|
1357
|
+
pos += 2
|
|
1358
|
+
continue
|
|
1359
|
+
|
|
1360
|
+
# Handle string delimiters
|
|
1361
|
+
if char in ['"', "'", '`'] and (pos == 0 or content[pos-1] != '\\'):
|
|
1362
|
+
if not in_string:
|
|
1363
|
+
in_string = True
|
|
1364
|
+
string_char = char
|
|
1365
|
+
elif char == string_char:
|
|
1366
|
+
in_string = False
|
|
1367
|
+
string_char = None
|
|
1368
|
+
|
|
1369
|
+
if not in_string:
|
|
1370
|
+
if char == '{':
|
|
1371
|
+
depth += 1
|
|
1372
|
+
elif char == '}':
|
|
1373
|
+
if depth == 0:
|
|
1374
|
+
# Found the closing }
|
|
1375
|
+
directive_parts.append(content[start_pos:pos+1])
|
|
1376
|
+
i = pos + 1
|
|
1377
|
+
break
|
|
1378
|
+
else:
|
|
1379
|
+
depth -= 1
|
|
1380
|
+
|
|
1381
|
+
pos += 1
|
|
1382
|
+
else:
|
|
1383
|
+
# Didn't find closing brace, skip this one
|
|
1384
|
+
i = start_pos + 1
|
|
1385
|
+
continue
|
|
1386
|
+
|
|
1387
|
+
# If no directives found, return empty
|
|
1388
|
+
if not directive_parts:
|
|
1389
|
+
return ''
|
|
1390
|
+
|
|
1391
|
+
# Join all directive parts with newlines
|
|
1392
|
+
result = '\n'.join(directive_parts)
|
|
1393
|
+
|
|
1394
|
+
return result
|
|
1395
|
+
|
|
1396
|
+
def _extract_wrapper_inner_content(self, template_content):
|
|
1397
|
+
"""Extract inner and outer content from wrapper directive
|
|
1398
|
+
Returns: (inner_content, outer_before, outer_after)
|
|
1399
|
+
"""
|
|
1400
|
+
import re
|
|
1401
|
+
|
|
1402
|
+
# Find __WRAPPER_CONFIG__ position (marks the start of wrapper)
|
|
1403
|
+
config_match = re.search(r'__WRAPPER_CONFIG__\s*=\s*', template_content)
|
|
1404
|
+
|
|
1405
|
+
if not config_match:
|
|
1406
|
+
return template_content, '', ''
|
|
1407
|
+
|
|
1408
|
+
# Content before wrapper
|
|
1409
|
+
outer_before = template_content[:config_match.start()].strip()
|
|
1410
|
+
|
|
1411
|
+
# Find where config ends (after the semicolon)
|
|
1412
|
+
config_end = config_match.end()
|
|
1413
|
+
|
|
1414
|
+
# Parse to find end of config object
|
|
1415
|
+
if config_end < len(template_content) and template_content[config_end] == '{':
|
|
1416
|
+
brace_count = 0
|
|
1417
|
+
in_string = False
|
|
1418
|
+
string_char = None
|
|
1419
|
+
|
|
1420
|
+
for i in range(config_end, len(template_content)):
|
|
1421
|
+
char = template_content[i]
|
|
1422
|
+
|
|
1423
|
+
if char in ['"', "'"] and (i == 0 or template_content[i-1] != '\\'):
|
|
1424
|
+
if not in_string:
|
|
1425
|
+
in_string = True
|
|
1426
|
+
string_char = char
|
|
1427
|
+
elif char == string_char:
|
|
1428
|
+
in_string = False
|
|
1429
|
+
string_char = None
|
|
1430
|
+
|
|
1431
|
+
if not in_string:
|
|
1432
|
+
if char == '{':
|
|
1433
|
+
brace_count += 1
|
|
1434
|
+
elif char == '}':
|
|
1435
|
+
brace_count -= 1
|
|
1436
|
+
if brace_count == 0:
|
|
1437
|
+
config_end = i + 1
|
|
1438
|
+
if config_end < len(template_content) and template_content[config_end] == ';':
|
|
1439
|
+
config_end += 1
|
|
1440
|
+
break
|
|
1441
|
+
|
|
1442
|
+
# Skip whitespace/newlines after config
|
|
1443
|
+
while config_end < len(template_content) and template_content[config_end] in [' ', '\n', '\r', '\t']:
|
|
1444
|
+
config_end += 1
|
|
1445
|
+
|
|
1446
|
+
# Find __WRAPPER_END__ marker
|
|
1447
|
+
end_match = re.search(r'__WRAPPER_END__', template_content[config_end:])
|
|
1448
|
+
|
|
1449
|
+
if end_match:
|
|
1450
|
+
# Extract content between config and end marker
|
|
1451
|
+
inner_end = config_end + end_match.start()
|
|
1452
|
+
inner_content = template_content[config_end:inner_end]
|
|
1453
|
+
|
|
1454
|
+
# Content after wrapper
|
|
1455
|
+
outer_after_start = config_end + end_match.end()
|
|
1456
|
+
outer_after = template_content[outer_after_start:].strip()
|
|
1457
|
+
else:
|
|
1458
|
+
# No end marker found, take all content after config (fallback)
|
|
1459
|
+
inner_content = template_content[config_end:]
|
|
1460
|
+
outer_after = ''
|
|
1461
|
+
|
|
1462
|
+
# Return separate before and after
|
|
1463
|
+
return inner_content, outer_before, outer_after
|
|
1464
|
+
|
|
1465
|
+
def _extract_wrapper_config(self, template_content):
|
|
1466
|
+
"""Extract wrapper config from template content and return both config and cleaned template"""
|
|
1467
|
+
import re
|
|
1468
|
+
|
|
1469
|
+
# Find __WRAPPER_CONFIG__ = position
|
|
1470
|
+
match = re.search(r'__WRAPPER_CONFIG__\s*=\s*', template_content)
|
|
1471
|
+
|
|
1472
|
+
if not match:
|
|
1473
|
+
return None
|
|
1474
|
+
|
|
1475
|
+
# Start parsing from the opening brace
|
|
1476
|
+
start_pos = match.end()
|
|
1477
|
+
if start_pos >= len(template_content) or template_content[start_pos] != '{':
|
|
1478
|
+
return None
|
|
1479
|
+
|
|
1480
|
+
# Parse balanced braces
|
|
1481
|
+
brace_count = 0
|
|
1482
|
+
in_string = False
|
|
1483
|
+
string_char = None
|
|
1484
|
+
end_pos = start_pos
|
|
1485
|
+
|
|
1486
|
+
for i in range(start_pos, len(template_content)):
|
|
1487
|
+
char = template_content[i]
|
|
1488
|
+
|
|
1489
|
+
# Handle strings
|
|
1490
|
+
if char in ['"', "'"] and (i == 0 or template_content[i-1] != '\\'):
|
|
1491
|
+
if not in_string:
|
|
1492
|
+
in_string = True
|
|
1493
|
+
string_char = char
|
|
1494
|
+
elif char == string_char:
|
|
1495
|
+
in_string = False
|
|
1496
|
+
string_char = None
|
|
1497
|
+
|
|
1498
|
+
# Handle braces (only when not in string)
|
|
1499
|
+
if not in_string:
|
|
1500
|
+
if char == '{':
|
|
1501
|
+
brace_count += 1
|
|
1502
|
+
elif char == '}':
|
|
1503
|
+
brace_count -= 1
|
|
1504
|
+
if brace_count == 0:
|
|
1505
|
+
# Found matching closing brace
|
|
1506
|
+
end_pos = i + 1
|
|
1507
|
+
# Look for semicolon
|
|
1508
|
+
if end_pos < len(template_content) and template_content[end_pos] == ';':
|
|
1509
|
+
end_pos += 1
|
|
1510
|
+
config_str = template_content[start_pos:end_pos-1] # Exclude semicolon
|
|
1511
|
+
return config_str
|
|
1512
|
+
|
|
1513
|
+
return None
|
|
1514
|
+
|
|
1515
|
+
def _generate_wrapper_declarations(self, declarations):
|
|
1516
|
+
"""
|
|
1517
|
+
Generate wrapper function declarations from DeclarationTracker
|
|
1518
|
+
Returns: (wrapper_code, variable_list, state_declarations)
|
|
1519
|
+
"""
|
|
1520
|
+
|
|
1521
|
+
wrapper_lines = []
|
|
1522
|
+
variable_list = []
|
|
1523
|
+
update_trait_items = []
|
|
1524
|
+
state_declarations = [] # Store useState declarations separately
|
|
1525
|
+
|
|
1526
|
+
# First, generate __UPDATE_DATA_TRAIT__
|
|
1527
|
+
wrapper_lines.append(" const __UPDATE_DATA_TRAIT__ = {};")
|
|
1528
|
+
|
|
1529
|
+
for decl in declarations:
|
|
1530
|
+
decl_type = decl['type']
|
|
1531
|
+
variables = decl['variables']
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
if decl_type == 'vars':
|
|
1535
|
+
# Process @vars variables -> destructure from $$$DATA$$$
|
|
1536
|
+
# Build destructuring parts with defaults when provided
|
|
1537
|
+
destructure_parts = []
|
|
1538
|
+
for var in variables:
|
|
1539
|
+
var_name = var['name']
|
|
1540
|
+
if var['hasDefault']:
|
|
1541
|
+
destructure_parts.append(f"{var_name} = {var['value']}")
|
|
1542
|
+
else:
|
|
1543
|
+
destructure_parts.append(f"{var_name}")
|
|
1544
|
+
# Add to __UPDATE_DATA_TRAIT__ and variable list
|
|
1545
|
+
update_trait_items.append(f" __UPDATE_DATA_TRAIT__.{var_name} = value => {var_name} = value;")
|
|
1546
|
+
variable_list.append(var_name)
|
|
1547
|
+
# Emit single destructuring statement
|
|
1548
|
+
if destructure_parts:
|
|
1549
|
+
wrapper_lines.append(f" let {{{', '.join(destructure_parts)}}} = $$$DATA$$$;")
|
|
1550
|
+
|
|
1551
|
+
elif decl_type == 'let':
|
|
1552
|
+
# Process @let variables
|
|
1553
|
+
for var in variables:
|
|
1554
|
+
|
|
1555
|
+
if var.get('isDestructuring'):
|
|
1556
|
+
# Handle destructuring
|
|
1557
|
+
if var.get('isUseState'):
|
|
1558
|
+
# [$stateKey, $setter] = useState($value)
|
|
1559
|
+
# Keep OLD state registration style, not React style!
|
|
1560
|
+
names = var['names']
|
|
1561
|
+
value = var['value']
|
|
1562
|
+
|
|
1563
|
+
# Extract state key and setter name
|
|
1564
|
+
if len(names) >= 2:
|
|
1565
|
+
state_key = names[0]
|
|
1566
|
+
setter_name_raw = names[1]
|
|
1567
|
+
|
|
1568
|
+
# Keep user-declared setter name as is
|
|
1569
|
+
setter_name = setter_name_raw
|
|
1570
|
+
|
|
1571
|
+
# Extract initial value from useState(value)
|
|
1572
|
+
import re
|
|
1573
|
+
match = re.search(r'useState\s*\(\s*(.+)\s*\)', value)
|
|
1574
|
+
initial_value = match.group(1) if match else 'null'
|
|
1575
|
+
|
|
1576
|
+
# Generate OLD style state registration
|
|
1577
|
+
state_declarations.append({
|
|
1578
|
+
'stateKey': state_key,
|
|
1579
|
+
'setterName': setter_name,
|
|
1580
|
+
'initialValue': initial_value
|
|
1581
|
+
})
|
|
1582
|
+
# Don't add to variable_list or update_trait (useState variables)
|
|
1583
|
+
else:
|
|
1584
|
+
# Regular destructuring: [$a, $b] = [1, 2]
|
|
1585
|
+
names = var['names']
|
|
1586
|
+
value = var['value']
|
|
1587
|
+
bracket_type = '[' if var['destructuringType'] == 'array' else '{'
|
|
1588
|
+
close_bracket = ']' if var['destructuringType'] == 'array' else '}'
|
|
1589
|
+
wrapper_lines.append(f" let {bracket_type}{', '.join(names)}{close_bracket} = {value};")
|
|
1590
|
+
|
|
1591
|
+
# Add each destructured variable to update trait and variable list
|
|
1592
|
+
for name in names:
|
|
1593
|
+
update_trait_items.append(f" __UPDATE_DATA_TRAIT__.{name} = value => {name} = value;")
|
|
1594
|
+
variable_list.append(name)
|
|
1595
|
+
else:
|
|
1596
|
+
# Regular variable
|
|
1597
|
+
var_name = var['name']
|
|
1598
|
+
if var['hasDefault']:
|
|
1599
|
+
wrapper_lines.append(f" let {var_name} = {var['value']};")
|
|
1600
|
+
else:
|
|
1601
|
+
wrapper_lines.append(f" let {var_name};")
|
|
1602
|
+
|
|
1603
|
+
# Add to update trait and variable list
|
|
1604
|
+
update_trait_items.append(f" __UPDATE_DATA_TRAIT__.{var_name} = value => {var_name} = value;")
|
|
1605
|
+
variable_list.append(var_name)
|
|
1606
|
+
|
|
1607
|
+
elif decl_type == 'const':
|
|
1608
|
+
# Process @const variables
|
|
1609
|
+
for var in variables:
|
|
1610
|
+
if var.get('isDestructuring'):
|
|
1611
|
+
# Handle destructuring
|
|
1612
|
+
if var.get('isUseState'):
|
|
1613
|
+
# [$stateKey, $setter] = useState($value)
|
|
1614
|
+
# Keep OLD state registration style!
|
|
1615
|
+
names = var['names']
|
|
1616
|
+
value = var['value']
|
|
1617
|
+
|
|
1618
|
+
# Extract state key and setter name
|
|
1619
|
+
if len(names) >= 2:
|
|
1620
|
+
state_key = names[0]
|
|
1621
|
+
setter_name_raw = names[1]
|
|
1622
|
+
|
|
1623
|
+
# Keep user-declared setter name as is
|
|
1624
|
+
setter_name = setter_name_raw
|
|
1625
|
+
|
|
1626
|
+
# Extract initial value from useState(value)
|
|
1627
|
+
import re
|
|
1628
|
+
match = re.search(r'useState\s*\(\s*(.+)\s*\)', value)
|
|
1629
|
+
initial_value = match.group(1) if match else 'null'
|
|
1630
|
+
|
|
1631
|
+
# Generate OLD style state registration
|
|
1632
|
+
state_declarations.append({
|
|
1633
|
+
'stateKey': state_key,
|
|
1634
|
+
'setterName': setter_name,
|
|
1635
|
+
'initialValue': initial_value
|
|
1636
|
+
})
|
|
1637
|
+
# Don't add to variable_list or update_trait (useState variables)
|
|
1638
|
+
else:
|
|
1639
|
+
# Regular const destructuring
|
|
1640
|
+
names = var['names']
|
|
1641
|
+
value = var['value']
|
|
1642
|
+
bracket_type = '[' if var['destructuringType'] == 'array' else '{'
|
|
1643
|
+
close_bracket = ']' if var['destructuringType'] == 'array' else '}'
|
|
1644
|
+
wrapper_lines.append(f" const {bracket_type}{', '.join(names)}{close_bracket} = {value};")
|
|
1645
|
+
# Const variables are not mutable, don't add to update_trait or variable_list
|
|
1646
|
+
else:
|
|
1647
|
+
# Regular const
|
|
1648
|
+
var_name = var['name']
|
|
1649
|
+
if var['hasDefault']:
|
|
1650
|
+
wrapper_lines.append(f" const {var_name} = {var['value']};")
|
|
1651
|
+
# Const variables are not mutable, don't add to update_trait or variable_list
|
|
1652
|
+
|
|
1653
|
+
elif decl_type == 'useState':
|
|
1654
|
+
# Process @useState directives - same as @let/@const with useState
|
|
1655
|
+
for var in variables:
|
|
1656
|
+
if var.get('isDestructuring') and var.get('isUseState'):
|
|
1657
|
+
# [@useState($value, $varName, $setVarName)] becomes [$varName, $setVarName] = useState($value)
|
|
1658
|
+
names = var['names']
|
|
1659
|
+
value = var['value']
|
|
1660
|
+
|
|
1661
|
+
# Extract state key and setter name
|
|
1662
|
+
if len(names) >= 2:
|
|
1663
|
+
state_key = names[0]
|
|
1664
|
+
setter_name_raw = names[1]
|
|
1665
|
+
|
|
1666
|
+
# Keep user-declared setter name as is
|
|
1667
|
+
setter_name = setter_name_raw
|
|
1668
|
+
|
|
1669
|
+
# Extract initial value from useState(value)
|
|
1670
|
+
import re
|
|
1671
|
+
match = re.search(r'useState\s*\(\s*(.+)\s*\)', value)
|
|
1672
|
+
initial_value = match.group(1) if match else 'null'
|
|
1673
|
+
|
|
1674
|
+
# Generate OLD style state registration
|
|
1675
|
+
state_declarations.append({
|
|
1676
|
+
'stateKey': state_key,
|
|
1677
|
+
'setterName': setter_name,
|
|
1678
|
+
'initialValue': initial_value
|
|
1679
|
+
})
|
|
1680
|
+
|
|
1681
|
+
# Add update trait assignments after all variable declarations
|
|
1682
|
+
wrapper_lines.extend(update_trait_items)
|
|
1683
|
+
|
|
1684
|
+
# Generate __VARIABLE_LIST__
|
|
1685
|
+
variable_list_str = ', '.join([f'"{v}"' for v in variable_list])
|
|
1686
|
+
wrapper_lines.append(f" const __VARIABLE_LIST__ = [{variable_list_str}];")
|
|
1687
|
+
|
|
1688
|
+
# Generate OLD style state registrations
|
|
1689
|
+
for state in state_declarations:
|
|
1690
|
+
state_key = state['stateKey']
|
|
1691
|
+
setter_name = state['setterName'] # Keep user-declared name as is
|
|
1692
|
+
initial_value = state['initialValue']
|
|
1693
|
+
|
|
1694
|
+
# Convert setStateKey to set$stateKey format for register name only
|
|
1695
|
+
register_setter_name = setter_name
|
|
1696
|
+
if setter_name.startswith('set') and len(setter_name) > 3:
|
|
1697
|
+
# setTodos -> set$todos (convert first char after 'set' to lowercase)
|
|
1698
|
+
state_part = setter_name[3:] # Remove 'set'
|
|
1699
|
+
if state_part:
|
|
1700
|
+
state_part_lower = state_part[0].lower() + state_part[1:] if len(state_part) > 1 else state_part.lower()
|
|
1701
|
+
register_setter_name = f'set${state_part_lower}'
|
|
1702
|
+
|
|
1703
|
+
internal_register = f"set${state_key}" # __set$todosRegister
|
|
1704
|
+
wrapper_lines.append(f" const {internal_register} = __STATE__.__.register('{state_key}');")
|
|
1705
|
+
wrapper_lines.append(f" let {state_key} = null;")
|
|
1706
|
+
wrapper_lines.append(f" const {setter_name} = (state) => {{")
|
|
1707
|
+
wrapper_lines.append(f" {state_key} = state;")
|
|
1708
|
+
wrapper_lines.append(f" {internal_register}(state);")
|
|
1709
|
+
wrapper_lines.append(f" }};")
|
|
1710
|
+
wrapper_lines.append(f" __STATE__.__.setters.{setter_name} = {setter_name};")
|
|
1711
|
+
wrapper_lines.append(f" __STATE__.__.setters.{state_key} = {setter_name};")
|
|
1712
|
+
# Add update$stateKey function - use state_key as is
|
|
1713
|
+
update_func_name = f"update${state_key}"
|
|
1714
|
+
wrapper_lines.append(f" const {update_func_name} = (value) => {{")
|
|
1715
|
+
wrapper_lines.append(f" if(__STATE__.__.canUpdateStateByKey){{")
|
|
1716
|
+
wrapper_lines.append(f" updateStateByKey('{state_key}', value);")
|
|
1717
|
+
wrapper_lines.append(f" {state_key} = value;")
|
|
1718
|
+
wrapper_lines.append(f" }}")
|
|
1719
|
+
wrapper_lines.append(f" }};")
|
|
1720
|
+
|
|
1721
|
+
wrapper_code = '\n'.join(wrapper_lines)
|
|
1722
|
+
|
|
1723
|
+
return wrapper_code, variable_list, state_declarations
|
|
1724
|
+
|
|
1725
|
+
def _convert_blade_to_template_string(self, blade_expression):
|
|
1726
|
+
"""Convert Blade expressions like {{ asset('css/file.css') }} to template string format"""
|
|
1727
|
+
# Convert {{ expression }} to ${App.View.escString(App.Helper.expression)}
|
|
1728
|
+
def replace_blade_expression(match):
|
|
1729
|
+
expression = match.group(1).strip()
|
|
1730
|
+
|
|
1731
|
+
# Handle common Laravel helpers
|
|
1732
|
+
if expression.startswith('asset('):
|
|
1733
|
+
return "${App.View.escString(App.Helper." + expression + ")}"
|
|
1734
|
+
elif expression.startswith('route('):
|
|
1735
|
+
return "${App.View.escString(App.View." + expression + ")}"
|
|
1736
|
+
elif expression.startswith('config('):
|
|
1737
|
+
return "${App.View.escString(App.Helper." + expression + ")}"
|
|
1738
|
+
elif expression.startswith('date('):
|
|
1739
|
+
return "${App.View.escString(App.Helper." + expression + ")}"
|
|
1740
|
+
else:
|
|
1741
|
+
# Generic expression
|
|
1742
|
+
return "${App.View.escString(" + expression + ")}"
|
|
1743
|
+
|
|
1744
|
+
# Replace all {{ expression }} with template string format
|
|
1745
|
+
result = re.sub(r'\{\{\s*([^}]+)\s*\}\}', replace_blade_expression, blade_expression)
|
|
1746
|
+
return result
|
|
1747
|
+
|
|
1748
|
+
def _generate_state_updates(self, state_declarations):
|
|
1749
|
+
"""Generate state update calls for updateVariableData function"""
|
|
1750
|
+
if not state_declarations:
|
|
1751
|
+
return ""
|
|
1752
|
+
|
|
1753
|
+
update_lines = []
|
|
1754
|
+
for state in state_declarations:
|
|
1755
|
+
state_key = state['stateKey']
|
|
1756
|
+
setter_name = state['setterName']
|
|
1757
|
+
initial_value = state['initialValue']
|
|
1758
|
+
# Generate: update$userState(user) - use update function instead of setter
|
|
1759
|
+
update_func_name = f"update${state_key}"
|
|
1760
|
+
update_lines.append(f"{update_func_name}({initial_value});")
|
|
1761
|
+
|
|
1762
|
+
return "\n ".join(update_lines)
|
|
1763
|
+
|