@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.
- package/dist/compiler/ast-parser.d.ts +48 -0
- package/dist/compiler/ast-parser.js +54 -9
- package/dist/compiler/compiler.d.ts +9 -0
- package/dist/compiler/compiler.js +45 -2
- package/dist/compiler/dom-compiler.d.ts +7 -0
- package/dist/compiler/dom-compiler.js +90 -33
- package/dist/compiler/script-compiler.d.ts +11 -0
- package/dist/compiler/script-compiler.js +108 -116
- package/dist/compiler/sfc-parser.d.ts +21 -0
- package/dist/compiler/ssr-compiler.d.ts +7 -0
- package/dist/compiler/ssr-compiler.js +142 -0
- package/dist/compiler/style-compiler.d.ts +8 -0
- package/dist/compiler/template-compiler.d.ts +8 -0
- package/dist/components/bloch-sphere.js +9 -3
- package/dist/components/infinity-list.js +7 -1
- package/dist/core/component.js +66 -15
- package/dist/core/hooks.js +53 -29
- package/dist/core/quantum.js +30 -17
- package/dist/core/query.js +11 -6
- package/dist/core/reactive.js +88 -7
- package/dist/core/renderer.js +9 -8
- package/dist/core/ssr.js +50 -0
- package/dist/core/surge.js +7 -2
- package/dist/core/vault.js +9 -5
- package/dist/index.js +63 -27
- package/dist/mulan.esm.js +187 -19
- package/dist/mulan.esm.js.map +1 -1
- package/dist/mulan.js +1890 -1590
- package/dist/mulan.js.map +1 -1
- package/dist/router/index.js +17 -10
- package/dist/security/sanitizer.js +5 -1
- package/dist/store/index.js +9 -5
- package/dist/types/ast-parser.d.ts +2 -0
- package/dist/types/compiler/ast-parser.d.ts +2 -0
- package/dist/types/compiler/compiler.d.ts +1 -0
- package/dist/types/compiler/ssr-compiler.d.ts +7 -0
- package/dist/types/compiler.d.ts +1 -0
- package/dist/types/components/bloch-sphere.d.ts +3 -1
- package/dist/types/components/infinity-list.d.ts +3 -1
- package/dist/types/core/component.d.ts +14 -0
- package/dist/types/core/reactive.d.ts +5 -1
- package/dist/types/core/renderer.d.ts +0 -1
- package/dist/types/core/ssr.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/ssr-compiler.d.ts +7 -0
- package/package.json +1 -1
- package/src/compiler/ast-parser.ts +62 -10
- package/src/compiler/compiler.ts +46 -1
- package/src/compiler/dom-compiler.ts +100 -34
- package/src/compiler/script-compiler.ts +117 -126
- package/src/compiler/ssr-compiler.ts +157 -0
- 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
|
-
|
|
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
|
-
|
|
155
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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, '&')
|
|
86
|
+
.replace(/</g, '<')
|
|
87
|
+
.replace(/>/g, '>')
|
|
88
|
+
.replace(/"/g, '"')
|
|
89
|
+
.replace(/'/g, ''');
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
216
|
+
chunks.push(`if (${id}) ${id}.className = ${JSON.stringify(value)};`);
|
|
160
217
|
}
|
|
161
218
|
}
|
|
162
219
|
else if (key === 'id') {
|
|
163
|
-
chunks.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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>;
|