@mulanjs/mulanjs 1.0.1-dev.20260227135307 → 1.0.1-dev.20260227173253

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/compiler/ast-parser.d.ts +48 -0
  2. package/dist/compiler/ast-parser.js +54 -9
  3. package/dist/compiler/compiler.d.ts +9 -0
  4. package/dist/compiler/compiler.js +45 -2
  5. package/dist/compiler/dom-compiler.d.ts +7 -0
  6. package/dist/compiler/dom-compiler.js +90 -33
  7. package/dist/compiler/script-compiler.d.ts +11 -0
  8. package/dist/compiler/script-compiler.js +108 -116
  9. package/dist/compiler/sfc-parser.d.ts +21 -0
  10. package/dist/compiler/ssr-compiler.d.ts +7 -0
  11. package/dist/compiler/ssr-compiler.js +142 -0
  12. package/dist/compiler/style-compiler.d.ts +8 -0
  13. package/dist/compiler/template-compiler.d.ts +8 -0
  14. package/dist/components/bloch-sphere.js +9 -3
  15. package/dist/components/infinity-list.js +7 -1
  16. package/dist/core/component.js +66 -15
  17. package/dist/core/hooks.js +53 -29
  18. package/dist/core/quantum.js +30 -17
  19. package/dist/core/query.js +11 -6
  20. package/dist/core/reactive.js +88 -7
  21. package/dist/core/renderer.js +9 -8
  22. package/dist/core/ssr.js +50 -0
  23. package/dist/core/surge.js +7 -2
  24. package/dist/core/vault.js +9 -5
  25. package/dist/index.js +63 -27
  26. package/dist/mulan.esm.js +187 -19
  27. package/dist/mulan.esm.js.map +1 -1
  28. package/dist/mulan.js +1890 -1590
  29. package/dist/mulan.js.map +1 -1
  30. package/dist/router/index.js +17 -10
  31. package/dist/security/sanitizer.js +5 -1
  32. package/dist/store/index.js +9 -5
  33. package/dist/types/ast-parser.d.ts +2 -0
  34. package/dist/types/compiler/ast-parser.d.ts +2 -0
  35. package/dist/types/compiler/compiler.d.ts +1 -0
  36. package/dist/types/compiler/ssr-compiler.d.ts +7 -0
  37. package/dist/types/compiler.d.ts +1 -0
  38. package/dist/types/components/bloch-sphere.d.ts +3 -1
  39. package/dist/types/components/infinity-list.d.ts +3 -1
  40. package/dist/types/core/component.d.ts +14 -0
  41. package/dist/types/core/reactive.d.ts +5 -1
  42. package/dist/types/core/renderer.d.ts +0 -1
  43. package/dist/types/core/ssr.d.ts +9 -0
  44. package/dist/types/index.d.ts +1 -0
  45. package/dist/types/ssr-compiler.d.ts +7 -0
  46. package/package.json +1 -1
  47. package/src/compiler/ast-parser.ts +62 -10
  48. package/src/compiler/compiler.ts +46 -1
  49. package/src/compiler/dom-compiler.ts +100 -34
  50. package/src/compiler/script-compiler.ts +117 -126
  51. package/src/compiler/ssr-compiler.ts +157 -0
  52. package/src/loader/index.js +12 -19
@@ -0,0 +1,48 @@
1
+ export type NodeType = 'Element' | 'Text' | 'Interpolation';
2
+ export type Node = ElementNode | TextNode | InterpolationNode;
3
+ export interface BaseNode {
4
+ type: NodeType;
5
+ isStatic?: boolean;
6
+ }
7
+ export interface ElementNode extends BaseNode {
8
+ type: 'Element';
9
+ tag: string;
10
+ props: Record<string, string>;
11
+ children: Node[];
12
+ directives: {
13
+ vFor?: {
14
+ item: string;
15
+ list: string;
16
+ };
17
+ vIf?: string;
18
+ };
19
+ }
20
+ export interface TextNode extends BaseNode {
21
+ type: 'Text';
22
+ content: string;
23
+ }
24
+ export interface InterpolationNode extends BaseNode {
25
+ type: 'Interpolation';
26
+ content: string;
27
+ }
28
+ /**
29
+ * Decodes standard HTML entities.
30
+ */
31
+ export declare function decodeEntities(str: string): string;
32
+ /**
33
+ * Unified MulanJS Template Parser
34
+ * Handles HTML tags, {{ }} interpolation, and is robust against raw < symbols in text.
35
+ */
36
+ export declare function parse(template: string, errors: string[]): ElementNode;
37
+ export declare function markStatic(node: Node): boolean;
38
+ export declare function parseTag(content: string): {
39
+ tag: string;
40
+ props: Record<string, string>;
41
+ directives: {
42
+ vFor?: {
43
+ item: string;
44
+ list: string;
45
+ };
46
+ vIf?: string;
47
+ };
48
+ };
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // --- AST Definitions ---
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.parseTag = exports.parse = exports.decodeEntities = void 0;
4
+ exports.parseTag = exports.markStatic = exports.parse = exports.decodeEntities = void 0;
5
5
  /**
6
6
  * Decodes standard HTML entities.
7
7
  */
@@ -66,12 +66,9 @@ function parse(template, errors) {
66
66
  errors.push('Unclosed opening tag.');
67
67
  break;
68
68
  }
69
- let tagContent = template.slice(cursor + 1, end);
70
- const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(tagContent.split(' ')[0].toLowerCase());
71
- if (tagContent.endsWith('/')) {
72
- tagContent = tagContent.slice(0, -1).trim();
73
- }
69
+ const tagContent = template.slice(cursor + 1, end);
74
70
  const { tag, props, directives } = parseTag(tagContent);
71
+ const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(tag.toLowerCase());
75
72
  const element = { type: 'Element', tag, props, children: [], directives };
76
73
  stack[stack.length - 1].children.push(element);
77
74
  if (!isSelfClosing) {
@@ -147,15 +144,63 @@ function parse(template, errors) {
147
144
  cursor = end;
148
145
  }
149
146
  }
147
+ markStatic(root);
150
148
  return root;
151
149
  }
152
150
  exports.parse = parse;
151
+ function markStatic(node) {
152
+ if (node.type === 'Interpolation') {
153
+ node.isStatic = false;
154
+ return false;
155
+ }
156
+ if (node.type === 'Text') {
157
+ const text = node;
158
+ node.isStatic = !text.content.includes('${');
159
+ return node.isStatic;
160
+ }
161
+ if (node.type === 'Element') {
162
+ const element = node;
163
+ let isStatic = true;
164
+ // 1. Directives make it dynamic
165
+ if (element.directives.vIf || element.directives.vFor) {
166
+ isStatic = false;
167
+ }
168
+ // 2. Dynamic properties or event listeners make it dynamic
169
+ if (isStatic) {
170
+ for (const key in element.props) {
171
+ const val = element.props[key];
172
+ if (key.startsWith('@') || key.startsWith('v-on:') || key.startsWith('on') || key.startsWith(':') || key.startsWith('.')) {
173
+ isStatic = false;
174
+ break;
175
+ }
176
+ if (val.includes('${')) {
177
+ isStatic = false;
178
+ break;
179
+ }
180
+ }
181
+ }
182
+ // 3. Check all children. Every child must be static for the parent to be static.
183
+ // We still need to call markStatic on ALL children, so don't short-circuit the loop early.
184
+ for (const child of element.children) {
185
+ const childStatic = markStatic(child);
186
+ if (!childStatic) {
187
+ isStatic = false;
188
+ }
189
+ }
190
+ element.isStatic = isStatic;
191
+ return isStatic;
192
+ }
193
+ return false;
194
+ }
195
+ exports.markStatic = markStatic;
153
196
  function parseTag(content) {
154
- const parts = content.split(' ');
155
- const tag = parts[0];
197
+ // 1. Extract tag name (first word, ignoring leading whitespace)
198
+ const tagMatch = content.trim().match(/^([a-zA-Z0-9:-]+)/);
199
+ const tag = tagMatch ? tagMatch[1] : '';
156
200
  const props = {};
157
201
  const directives = {};
158
- const attrStr = content.slice(tag.length).trim();
202
+ // 2. Remove tag name from content to parse attributes
203
+ const attrStr = content.trim().slice(tag.length).trim();
159
204
  let i = 0;
160
205
  while (i < attrStr.length) {
161
206
  if (/\s/.test(attrStr[i])) {
@@ -0,0 +1,9 @@
1
+ import { CompilerOptions } from './script-compiler';
2
+ export interface CompileResult {
3
+ code: string;
4
+ css: string;
5
+ errors: string[];
6
+ map?: string;
7
+ }
8
+ export declare function compileSFC(source: string, filename: string, options?: CompilerOptions): Promise<CompileResult>;
9
+ export declare function compileSFCForSSR(source: string, filename: string, options?: CompilerOptions): Promise<CompileResult>;
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compileSFC = void 0;
3
+ exports.compileSFCForSSR = exports.compileSFC = void 0;
4
4
  const sfc_parser_1 = require("./sfc-parser");
5
5
  const script_compiler_1 = require("./script-compiler");
6
6
  const dom_compiler_1 = require("./dom-compiler");
7
7
  const style_compiler_1 = require("./style-compiler");
8
+ const ssr_compiler_1 = require("./ssr-compiler");
8
9
  const source_map_1 = require("source-map");
9
10
  async function compileSFC(source, filename, options) {
10
11
  // 1. Parse
@@ -17,6 +18,9 @@ async function compileSFC(source, filename, options) {
17
18
  if (!scriptCode.includes('const __component__ =')) {
18
19
  scriptCode = scriptCode.replace(/export\s+default/, 'const __component__ =');
19
20
  }
21
+ if (!scriptCode.includes('const __component__ =')) {
22
+ scriptCode = scriptCode.replace('exports.default =', 'const __component__ =');
23
+ }
20
24
  // 3. Style
21
25
  const styleResult = (0, style_compiler_1.compileStyle)(descriptor, filename, options);
22
26
  // 4. Template (Use the new unified No-VDOM compiler!)
@@ -153,7 +157,7 @@ if (typeof module !== 'undefined' && module.hot) {
153
157
  }
154
158
 
155
159
  // console.log('[MulanJS] Component created:', __component__);
156
- export default __component__;
160
+ module.exports = __component__;
157
161
 
158
162
  // Helper to get relative path for sourceURL
159
163
  // This ensures the generated file appears in a readable folder structure in DevTools
@@ -167,3 +171,42 @@ export default __component__;
167
171
  };
168
172
  }
169
173
  exports.compileSFC = compileSFC;
174
+ // --- Mulan Server Components (MSC) ---
175
+ async function compileSFCForSSR(source, filename, options) {
176
+ const descriptor = (0, sfc_parser_1.parseMUJS)(source, filename);
177
+ const scriptResult = await (0, script_compiler_1.compileScript)(descriptor, options);
178
+ let scriptCode = scriptResult.code;
179
+ // Robust replacement for various export styles
180
+ if (scriptCode.includes('export default')) {
181
+ scriptCode = scriptCode.replace('export default', 'const __component__ =');
182
+ }
183
+ else if (scriptCode.includes('exports.default =')) {
184
+ scriptCode = scriptCode.replace('exports.default =', 'const __component__ =');
185
+ }
186
+ else if (scriptCode.includes('module.exports =')) {
187
+ scriptCode = scriptCode.replace('module.exports =', 'const __component__ =');
188
+ }
189
+ else {
190
+ // Fallback: search for defineComponent if it's there
191
+ scriptCode += `\nconst __component__ = typeof exports !== 'undefined' ? (exports.default || module.exports) : {};`;
192
+ }
193
+ const templateResult = (0, ssr_compiler_1.compileToSSR)(descriptor, scriptResult, undefined);
194
+ const finalCode = `${scriptCode}
195
+
196
+ ${templateResult.code}
197
+
198
+ if (__component__.prototype) {
199
+ __component__.prototype.renderToString = render;
200
+ } else if (typeof __component__ === 'object') {
201
+ __component__.renderToString = render;
202
+ }
203
+
204
+ module.exports = __component__;
205
+ `;
206
+ return {
207
+ code: finalCode,
208
+ css: '', // CSS handling for SSR can be extracted separately
209
+ errors: [...scriptResult.errors, ...templateResult.errors],
210
+ };
211
+ }
212
+ exports.compileSFCForSSR = compileSFCForSSR;
@@ -0,0 +1,7 @@
1
+ import { SFCDescriptor } from './sfc-parser';
2
+ import { ScriptCompileResult } from './script-compiler';
3
+ export interface DOMCompileResult {
4
+ code: string;
5
+ errors: string[];
6
+ }
7
+ export declare function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptCompileResult, scopedId?: string): DOMCompileResult;
@@ -16,18 +16,23 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
16
16
  // 3. Codegen Phase (AST -> JS DOM Instructions)
17
17
  let uidRef = { current: 0 };
18
18
  const getUid = () => `_el${uidRef.current++}`;
19
+ const hoists = [];
20
+ const getHoistId = () => `_hoist${hoists.length}`;
19
21
  // We generate a DocumentFragment at the root
20
22
  let codeChunks = [];
21
- codeChunks.push(`const _frag = document.createDocumentFragment();`);
23
+ codeChunks.push(`const _frag = this._recoveryMode ? null : document.createDocumentFragment();`);
22
24
  // Generate code for all top-level children and append them to the fragment
23
25
  ast.children.forEach(child => {
24
- const rootId = generateDOMInstruction(child, codeChunks, getUid, uidRef, scriptResult.bindings || [], []);
26
+ const rootId = generateDOMInstruction(child, codeChunks, getUid, getHoistId, hoists, uidRef, scriptResult.bindings || [], []);
25
27
  if (rootId) {
26
- codeChunks.push(`_frag.appendChild(${rootId});`);
28
+ codeChunks.push(`if (_frag) _frag.appendChild(${rootId});`);
27
29
  }
28
30
  });
29
31
  const bodyFn = codeChunks.join('\n ');
30
- const renderFn = `function render() {
32
+ const hoistsFn = hoists.join('\n ');
33
+ const renderFn = `
34
+ ${hoistsFn}
35
+ function render() {
31
36
  const _s = (v) => (v && typeof v === 'object' && 'value' in v) ? v.value : v;
32
37
  const _h = (v, raw) => {
33
38
  const val = _s(v);
@@ -50,18 +55,73 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
50
55
  }
51
56
  exports.compileToDOM = compileToDOM;
52
57
  // --- Code Generator (AST -> Instructions) ---
53
- function generateDOMInstruction(node, chunks, getUid, uidRef, bindings, localScope) {
58
+ function renderStaticHTML(node) {
59
+ if (node.type === 'Text') {
60
+ const text = node;
61
+ // Escape HTML for innerHTML injection
62
+ return htmlEscape(text.content);
63
+ }
64
+ if (node.type === 'Element') {
65
+ const el = node;
66
+ let html = `<${el.tag}`;
67
+ for (const key in el.props) {
68
+ html += ` ${key}="${htmlEscape(el.props[key])}"`;
69
+ }
70
+ const isSelfClosing = ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(el.tag.toLowerCase());
71
+ if (isSelfClosing) {
72
+ html += '/>';
73
+ return html;
74
+ }
75
+ html += '>';
76
+ for (const child of el.children) {
77
+ html += renderStaticHTML(child);
78
+ }
79
+ html += `</${el.tag}>`;
80
+ return html;
81
+ }
82
+ return '';
83
+ }
84
+ function htmlEscape(str) {
85
+ return str.replace(/&/g, '&amp;')
86
+ .replace(/</g, '&lt;')
87
+ .replace(/>/g, '&gt;')
88
+ .replace(/"/g, '&quot;')
89
+ .replace(/'/g, '&#039;');
90
+ }
91
+ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef, bindings, localScope) {
92
+ // --- STATIC NODE HOISTING (THE COMPILER INTELLIGENCE) ---
93
+ // If the node and ALL its children contain ZERO reactivity, we hoist it.
94
+ if (node.type === 'Element' && node.isStatic) {
95
+ // We only hoist elements that have children or attributes to save bytes,
96
+ // a simple empty <div> might be faster to just createElement.
97
+ const el = node;
98
+ if (el.tag !== 'fragment' && (el.children.length > 0 || Object.keys(el.props).length > 0)) {
99
+ const hoistId = getHoistId();
100
+ const rawHtml = renderStaticHTML(node);
101
+ // Generate the hoisted template ONCE outside the render function
102
+ hoists.push(`const ${hoistId} = (function() {
103
+ const t = document.createElement("template");
104
+ t.innerHTML = \`${rawHtml}\`;
105
+ return t.content.firstChild;
106
+ })();`);
107
+ // Inside render function, just clone it (O(1) creation cost)
108
+ const id = getUid();
109
+ chunks.push(`const ${id} = ${hoistId}.cloneNode(true);`);
110
+ return id;
111
+ }
112
+ }
54
113
  if (node.type === 'Text') {
55
114
  const text = node;
56
115
  const id = getUid();
116
+ const numericId = uidRef.current - 1; // Not used for text nodes in SSR but kept for parity if needed
57
117
  // Native template literal interpolation
58
118
  if (text.content.includes('${')) {
59
- chunks.push(`const ${id} = document.createTextNode("");`);
119
+ chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode("");`);
60
120
  // Wrap in effect for reactivity
61
- chunks.push(`this._bindEffect(() => { ${id}.textContent = \`${text.content}\`; }, ${id});`);
121
+ chunks.push(`this._bindEffect(() => { if (!${id}) return; ${id}.textContent = \`${text.content}\`; }, ${id});`);
62
122
  }
63
123
  else {
64
- chunks.push(`const ${id} = document.createTextNode(${JSON.stringify(text.content)});`);
124
+ chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode(${JSON.stringify(text.content)});`);
65
125
  }
66
126
  return id;
67
127
  }
@@ -75,25 +135,25 @@ function generateDOMInstruction(node, chunks, getUid, uidRef, bindings, localSco
75
135
  }
76
136
  if (node.type === 'Element') {
77
137
  const element = node;
78
- const id = getUid();
79
138
  // Handle mu-if (Phase 4 - Dynamic Fragments)
80
139
  if (element.directives.vIf) {
140
+ const id = getUid();
81
141
  const condition = `_s(${processBindings(element.directives.vIf, bindings, localScope)})`;
82
142
  // 1. Create the anchor Comment node where the block belongs
83
143
  chunks.push(`const ${id} = document.createComment("mu-if:${element.directives.vIf}");`);
84
144
  // 2. Generate the inner block rendering function
85
145
  const blockId = `_if_${uidRef.current++}`;
86
146
  const blockChunks = [];
87
- blockChunks.push(`const ${blockId}_frag = document.createDocumentFragment();`);
147
+ blockChunks.push(`const ${blockId}_frag = this._recoveryMode ? null : document.createDocumentFragment();`);
88
148
  blockChunks.push(`const _block_effects = [];`);
89
149
  // 2a. Override bindings for inner effects
90
150
  const localBindMacro = `(fn, targetNode) => { const stop = this._bindEffect(fn, targetNode); _block_effects.push(stop); return stop; }`;
91
151
  let originalLength = blockChunks.length;
92
152
  // 2b. Generate the actual block
93
153
  const elementWithoutIf = { ...element, directives: { ...element.directives, vIf: undefined } };
94
- const clonedChildId = generateDOMInstruction(elementWithoutIf, blockChunks, getUid, uidRef, bindings, localScope);
154
+ const clonedChildId = generateDOMInstruction(elementWithoutIf, blockChunks, getUid, getHoistId, hoists, uidRef, bindings, localScope);
95
155
  if (clonedChildId) {
96
- blockChunks.push(`${blockId}_frag.appendChild(${clonedChildId});`);
156
+ blockChunks.push(`if (${blockId}_frag && ${clonedChildId} && !this._recoveryMode) ${blockId}_frag.appendChild(${clonedChildId});`);
97
157
  }
98
158
  for (let i = originalLength; i < blockChunks.length; i++) {
99
159
  blockChunks[i] = blockChunks[i].replace(/this\._bindEffect/g, `(${localBindMacro})`);
@@ -110,26 +170,23 @@ function generateDOMInstruction(node, chunks, getUid, uidRef, bindings, localSco
110
170
  const { item, list } = element.directives.vFor;
111
171
  const boundList = `_s(${processBindings(list, bindings, localScope)})`;
112
172
  // 1. Create the anchor Comment node where the list begins
113
- chunks.push(`const ${id} = document.createComment("mu-for:${list}");`);
173
+ const id = getUid();
174
+ chunks.push(`const ${id} = this._recoveryMode ? null : document.createComment("mu-for:${list}");`);
114
175
  // 2. Generate the inner row rendering function
115
176
  const rowChildScope = [...localScope, item];
116
177
  const rowId = `_row_${uidRef.current++}`;
117
178
  const rowChunks = [];
118
- rowChunks.push(`const ${rowId}_frag = document.createDocumentFragment();`);
179
+ rowChunks.push(`const ${rowId}_frag = this._recoveryMode ? null : document.createDocumentFragment();`);
119
180
  rowChunks.push(`const _row_effects = [];`);
120
181
  // 2a. Override the main chunk array temporarily to capture inner bindings
121
- const originalBindEffect = 'this._bindEffect';
122
- // We use a local array to capture effects created *inside* the row, so we can clean them up if the row is removed
123
182
  const localBindMacro = `(fn, targetNode) => { const stop = this._bindEffect(fn, targetNode); _row_effects.push(stop); return stop; }`;
124
- // In the compiler, when traversing children of a mu-for, we need to instruct _bindEffect calls to use the localMacro
125
- // For simplicity in this AST generator, we use a string replacement trick on the generated chunks for this subtree
126
183
  let originalLength = rowChunks.length;
127
184
  // 2b. Generate the actual repeating element (the template root of the mu-for)
128
185
  // We strip the vFor directive so it doesn't recurse infinitely
129
186
  const elementWithoutFor = { ...element, directives: { ...element.directives, vFor: undefined } };
130
- const clonedChildId = generateDOMInstruction(elementWithoutFor, rowChunks, getUid, uidRef, bindings, rowChildScope);
187
+ const clonedChildId = generateDOMInstruction(elementWithoutFor, rowChunks, getUid, getHoistId, hoists, uidRef, bindings, rowChildScope);
131
188
  if (clonedChildId) {
132
- rowChunks.push(`${rowId}_frag.appendChild(${clonedChildId});`);
189
+ rowChunks.push(`if (${rowId}_frag && ${clonedChildId} && !this._recoveryMode) ${rowId}_frag.appendChild(${clonedChildId});`);
133
190
  }
134
191
  // Note: In a robust compiler, we'd pass an `effectRegistry` flag. Here we just regex patch the internal effects.
135
192
  for (let i = originalLength; i < rowChunks.length; i++) {
@@ -138,39 +195,39 @@ function generateDOMInstruction(node, chunks, getUid, uidRef, bindings, localSco
138
195
  rowChunks.push(`return { fragment: ${rowId}_frag, effects: _row_effects };`);
139
196
  const renderRowFn = `(${item}, _index) => {\n ${rowChunks.join('\n ')}\n }`;
140
197
  // 3. Register the macro-effect that calls the Reconciler when the array changes
141
- // The reconciler will only create new rows or move them natively
142
198
  const listIdHash = `list_${Math.random().toString(36).substr(2, 6)}`;
143
- // We assume mu-key is passed as a prop, e.g., mu-key="id"
144
199
  let keyProp = 'null';
145
200
  if (element.props['mu-key']) {
146
201
  keyProp = `"${element.props['mu-key']}"`;
147
202
  }
148
- chunks.push(`this._bindEffect(() => { this._reconcileList("${listIdHash}", ${id}, ${boundList}, ${keyProp}, ${renderRowFn}); }, ${id});`);
203
+ chunks.push(`this._bindEffect(() => { if (${id}) this._reconcileList("${listIdHash}", ${id}, ${boundList}, ${keyProp}, ${renderRowFn}); }, ${id});`);
149
204
  return id;
150
205
  }
151
- chunks.push(`const ${id} = document.createElement("${element.tag}");`);
206
+ const numericId = uidRef.current;
207
+ const id = getUid();
208
+ chunks.push(`const ${id} = this._recoveryMode ? (this.container.getAttribute('data-mu-node-id') === "${numericId}" ? this.container : this.container.querySelector('[data-mu-node-id="${numericId}"]')) : document.createElement("${element.tag}");`);
152
209
  // Handle standard properties and classes
153
210
  for (const [key, value] of Object.entries(element.props)) {
154
211
  if (key === 'class') {
155
212
  if (value.includes('${')) {
156
- chunks.push(`this._bindEffect(() => { ${id}.className = \`${value}\`; }, ${id});`);
213
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}.className = \`${value}\`; }, ${id});`);
157
214
  }
158
215
  else {
159
- chunks.push(`${id}.className = ${JSON.stringify(value)};`);
216
+ chunks.push(`if (${id}) ${id}.className = ${JSON.stringify(value)};`);
160
217
  }
161
218
  }
162
219
  else if (key === 'id') {
163
- chunks.push(`${id}.id = ${JSON.stringify(value)};`);
220
+ chunks.push(`if (${id}) ${id}.id = ${JSON.stringify(value)};`);
164
221
  }
165
222
  else if (key === 'data-mu-id') {
166
223
  // Ignore internal string compiler metadata
167
224
  }
168
225
  else {
169
226
  if (value.includes('${')) {
170
- chunks.push(`this._bindEffect(() => { ${id}.setAttribute("${key}", \`${value}\`); }, ${id});`);
227
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}.setAttribute("${key}", \`${value}\`); }, ${id});`);
171
228
  }
172
229
  else {
173
- chunks.push(`${id}.setAttribute("${key}", ${JSON.stringify(value)});`);
230
+ chunks.push(`if (${id}) ${id}.setAttribute("${key}", ${JSON.stringify(value)});`);
174
231
  }
175
232
  }
176
233
  }
@@ -185,19 +242,19 @@ function generateDOMInstruction(node, chunks, getUid, uidRef, bindings, localSco
185
242
  if (element._domBindings) {
186
243
  for (const b of element._domBindings) {
187
244
  if (b.type === 'prop') {
188
- chunks.push(`this._bindEffect(() => { ${id}['${b.name}'] = ${b.expr}; }, ${id});`);
245
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}['${b.name}'] = ${b.expr}; }, ${id});`);
189
246
  }
190
247
  else if (b.type === 'event') {
191
- chunks.push(`${id}.addEventListener('${b.name}', (${b.expr}).bind(this));`);
248
+ chunks.push(`if (${id}) ${id}.addEventListener('${b.name}', (${b.expr}).bind(this));`);
192
249
  }
193
250
  }
194
251
  }
195
252
  // Recursively generate children
196
253
  const childScope = [...localScope];
197
254
  element.children.forEach(child => {
198
- const childId = generateDOMInstruction(child, chunks, getUid, uidRef, bindings, childScope);
255
+ const childId = generateDOMInstruction(child, chunks, getUid, getHoistId, hoists, uidRef, bindings, childScope);
199
256
  if (childId) {
200
- chunks.push(`${id}.appendChild(${childId});`);
257
+ chunks.push(`if (${id} && ${childId} && !this._recoveryMode) ${id}.appendChild(${childId});`);
201
258
  }
202
259
  });
203
260
  return id;
@@ -0,0 +1,11 @@
1
+ import { SFCDescriptor } from './sfc-parser';
2
+ export interface ScriptCompileResult {
3
+ code: string;
4
+ bindings?: string[];
5
+ errors: string[];
6
+ map?: string;
7
+ }
8
+ export interface CompilerOptions {
9
+ readFileSync?: (file: string) => string;
10
+ }
11
+ export declare function compileScript(descriptor: SFCDescriptor, options?: CompilerOptions): Promise<ScriptCompileResult>;