@mulanjs/mulanjs 1.0.1-dev.20260226191839 → 1.0.1-dev.20260227135307
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.js +221 -0
- package/dist/compiler/compiler.js +1 -1
- package/dist/compiler/dom-compiler.js +5 -126
- package/dist/compiler/template-compiler.js +11 -238
- package/dist/core/component.js +4 -0
- package/dist/mulan.esm.js +4 -0
- package/dist/mulan.esm.js.map +1 -1
- package/dist/mulan.js +4 -0
- package/dist/mulan.js.map +1 -1
- package/dist/types/ast-parser.d.ts +46 -0
- package/dist/types/compiler/ast-parser.d.ts +46 -0
- package/package.json +1 -1
- package/src/compiler/ast-parser.ts +258 -0
- package/src/compiler/compiler.ts +1 -1
- package/src/compiler/dom-compiler.ts +5 -133
- package/src/compiler/template-compiler.ts +11 -283
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type NodeType = 'Element' | 'Text' | 'Interpolation';
|
|
2
|
+
export type Node = ElementNode | TextNode | InterpolationNode;
|
|
3
|
+
export interface BaseNode {
|
|
4
|
+
type: NodeType;
|
|
5
|
+
}
|
|
6
|
+
export interface ElementNode extends BaseNode {
|
|
7
|
+
type: 'Element';
|
|
8
|
+
tag: string;
|
|
9
|
+
props: Record<string, string>;
|
|
10
|
+
children: Node[];
|
|
11
|
+
directives: {
|
|
12
|
+
vFor?: {
|
|
13
|
+
item: string;
|
|
14
|
+
list: string;
|
|
15
|
+
};
|
|
16
|
+
vIf?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface TextNode extends BaseNode {
|
|
20
|
+
type: 'Text';
|
|
21
|
+
content: string;
|
|
22
|
+
}
|
|
23
|
+
export interface InterpolationNode extends BaseNode {
|
|
24
|
+
type: 'Interpolation';
|
|
25
|
+
content: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Decodes standard HTML entities.
|
|
29
|
+
*/
|
|
30
|
+
export declare function decodeEntities(str: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Unified MulanJS Template Parser
|
|
33
|
+
* Handles HTML tags, {{ }} interpolation, and is robust against raw < symbols in text.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parse(template: string, errors: string[]): ElementNode;
|
|
36
|
+
export declare function parseTag(content: string): {
|
|
37
|
+
tag: string;
|
|
38
|
+
props: Record<string, string>;
|
|
39
|
+
directives: {
|
|
40
|
+
vFor?: {
|
|
41
|
+
item: string;
|
|
42
|
+
list: string;
|
|
43
|
+
} | undefined;
|
|
44
|
+
vIf?: string | undefined;
|
|
45
|
+
};
|
|
46
|
+
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
|
|
2
|
+
// --- AST Definitions ---
|
|
3
|
+
|
|
4
|
+
export type NodeType = 'Element' | 'Text' | 'Interpolation';
|
|
5
|
+
|
|
6
|
+
export type Node = ElementNode | TextNode | InterpolationNode;
|
|
7
|
+
|
|
8
|
+
export interface BaseNode {
|
|
9
|
+
type: NodeType;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ElementNode extends BaseNode {
|
|
13
|
+
type: 'Element';
|
|
14
|
+
tag: string;
|
|
15
|
+
props: Record<string, string>;
|
|
16
|
+
children: Node[];
|
|
17
|
+
directives: {
|
|
18
|
+
vFor?: { item: string; list: string };
|
|
19
|
+
vIf?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TextNode extends BaseNode {
|
|
24
|
+
type: 'Text';
|
|
25
|
+
content: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface InterpolationNode extends BaseNode {
|
|
29
|
+
type: 'Interpolation';
|
|
30
|
+
content: string; // The expression inside {{ }}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Decodes standard HTML entities.
|
|
35
|
+
*/
|
|
36
|
+
export function decodeEntities(str: string): string {
|
|
37
|
+
return str
|
|
38
|
+
.replace(/</g, '<')
|
|
39
|
+
.replace(/>/g, '>')
|
|
40
|
+
.replace(/&/g, '&')
|
|
41
|
+
.replace(/"/g, '"')
|
|
42
|
+
.replace(/'/g, "'")
|
|
43
|
+
.replace(/ /g, '\u00A0');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Unified MulanJS Template Parser
|
|
48
|
+
* Handles HTML tags, {{ }} interpolation, and is robust against raw < symbols in text.
|
|
49
|
+
*/
|
|
50
|
+
export function parse(template: string, errors: string[]): ElementNode {
|
|
51
|
+
const root: ElementNode = {
|
|
52
|
+
type: 'Element',
|
|
53
|
+
tag: 'fragment',
|
|
54
|
+
props: {},
|
|
55
|
+
children: [],
|
|
56
|
+
directives: {}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const stack: ElementNode[] = [root];
|
|
60
|
+
let cursor = 0;
|
|
61
|
+
|
|
62
|
+
while (cursor < template.length) {
|
|
63
|
+
const char = template[cursor];
|
|
64
|
+
|
|
65
|
+
// 1. Comments
|
|
66
|
+
if (template.startsWith('<!--', cursor)) {
|
|
67
|
+
const end = template.indexOf('-->', cursor);
|
|
68
|
+
if (end === -1) {
|
|
69
|
+
errors.push('Unclosed HTML comment.');
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
cursor = end + 3;
|
|
73
|
+
}
|
|
74
|
+
// 2. Tags (with lookahead check for robustness)
|
|
75
|
+
else if (char === '<' && /[\/a-zA-Z!]/.test(template[cursor + 1])) {
|
|
76
|
+
if (template[cursor + 1] === '/') {
|
|
77
|
+
// Closing Tag
|
|
78
|
+
const end = template.indexOf('>', cursor);
|
|
79
|
+
if (end === -1) {
|
|
80
|
+
errors.push('Unclosed closing tag.');
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
const tagName = template.slice(cursor + 2, end).trim();
|
|
84
|
+
// Find matching element in stack to pop
|
|
85
|
+
if (stack.length > 1 && stack[stack.length - 1].tag === tagName) {
|
|
86
|
+
stack.pop();
|
|
87
|
+
} else {
|
|
88
|
+
errors.push(`Mismatched closing tag </${tagName}>.`);
|
|
89
|
+
}
|
|
90
|
+
cursor = end + 1;
|
|
91
|
+
} else {
|
|
92
|
+
// Opening Tag
|
|
93
|
+
const end = template.indexOf('>', cursor);
|
|
94
|
+
if (end === -1) {
|
|
95
|
+
errors.push('Unclosed opening tag.');
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let tagContent = template.slice(cursor + 1, end);
|
|
100
|
+
const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(tagContent.split(' ')[0].toLowerCase());
|
|
101
|
+
|
|
102
|
+
if (tagContent.endsWith('/')) {
|
|
103
|
+
tagContent = tagContent.slice(0, -1).trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { tag, props, directives } = parseTag(tagContent);
|
|
107
|
+
|
|
108
|
+
const element: ElementNode = { type: 'Element', tag, props, children: [], directives };
|
|
109
|
+
stack[stack.length - 1].children.push(element);
|
|
110
|
+
|
|
111
|
+
if (!isSelfClosing) {
|
|
112
|
+
stack.push(element);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
cursor = end + 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// 3. Interpolation {{ }}
|
|
119
|
+
else if (template.startsWith('{{', cursor)) {
|
|
120
|
+
const end = template.indexOf('}}', cursor);
|
|
121
|
+
if (end === -1) {
|
|
122
|
+
errors.push('Unclosed interpolation {{ }}.');
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const content = template.slice(cursor + 2, end).trim();
|
|
127
|
+
stack[stack.length - 1].children.push({ type: 'Interpolation', content });
|
|
128
|
+
cursor = end + 2;
|
|
129
|
+
}
|
|
130
|
+
// 4. Native Template Literals ${ } (Protection)
|
|
131
|
+
else if (template.startsWith('${', cursor)) {
|
|
132
|
+
// Find end of ${ } while respecting nested braces
|
|
133
|
+
let innerCursor = cursor + 2;
|
|
134
|
+
let depth = 1;
|
|
135
|
+
while (innerCursor < template.length && depth > 0) {
|
|
136
|
+
if (template[innerCursor] === '{') depth++;
|
|
137
|
+
if (template[innerCursor] === '}') depth--;
|
|
138
|
+
innerCursor++;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const content = template.slice(cursor, innerCursor);
|
|
142
|
+
// Treat as text but this block is now "atomic" and won't be split by < checks
|
|
143
|
+
const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
|
|
144
|
+
if (lastChild && lastChild.type === 'Text') {
|
|
145
|
+
lastChild.content += content;
|
|
146
|
+
} else {
|
|
147
|
+
stack[stack.length - 1].children.push({ type: 'Text', content });
|
|
148
|
+
}
|
|
149
|
+
cursor = innerCursor;
|
|
150
|
+
}
|
|
151
|
+
// 5. Text Nodes
|
|
152
|
+
else {
|
|
153
|
+
let nextTag = template.indexOf('<', cursor);
|
|
154
|
+
let nextInterp = template.indexOf('{{', cursor);
|
|
155
|
+
let nextNative = template.indexOf('${', cursor);
|
|
156
|
+
|
|
157
|
+
let end = template.length;
|
|
158
|
+
|
|
159
|
+
// Heuristic for nextTag: it must look like a tag start
|
|
160
|
+
while (nextTag !== -1) {
|
|
161
|
+
if (/[\/a-zA-Z!]/.test(template[nextTag + 1])) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
nextTag = template.indexOf('<', nextTag + 1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (nextTag !== -1 && nextTag < end) end = nextTag;
|
|
168
|
+
if (nextInterp !== -1 && nextInterp < end) end = nextInterp;
|
|
169
|
+
if (nextNative !== -1 && nextNative < end) end = nextNative;
|
|
170
|
+
|
|
171
|
+
const rawContent = template.slice(cursor, end);
|
|
172
|
+
const content = decodeEntities(rawContent);
|
|
173
|
+
|
|
174
|
+
if (content) {
|
|
175
|
+
const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
|
|
176
|
+
if (lastChild && lastChild.type === 'Text') {
|
|
177
|
+
lastChild.content += content;
|
|
178
|
+
} else {
|
|
179
|
+
stack[stack.length - 1].children.push({ type: 'Text', content });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
cursor = end;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return root;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function parseTag(content: string) {
|
|
190
|
+
const parts = content.split(' ');
|
|
191
|
+
const tag = parts[0];
|
|
192
|
+
const props: Record<string, string> = {};
|
|
193
|
+
const directives: ElementNode['directives'] = {};
|
|
194
|
+
|
|
195
|
+
const attrStr = content.slice(tag.length).trim();
|
|
196
|
+
|
|
197
|
+
let i = 0;
|
|
198
|
+
while (i < attrStr.length) {
|
|
199
|
+
if (/\s/.test(attrStr[i])) {
|
|
200
|
+
i++;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const keyStart = i;
|
|
205
|
+
while (i < attrStr.length && !/\s|=/.test(attrStr[i])) {
|
|
206
|
+
i++;
|
|
207
|
+
}
|
|
208
|
+
const key = attrStr.slice(keyStart, i);
|
|
209
|
+
|
|
210
|
+
let value = 'true';
|
|
211
|
+
|
|
212
|
+
let peek = i;
|
|
213
|
+
while (peek < attrStr.length && /\s/.test(attrStr[peek])) peek++;
|
|
214
|
+
|
|
215
|
+
if (peek < attrStr.length && attrStr[peek] === '=') {
|
|
216
|
+
i = peek + 1;
|
|
217
|
+
while (i < attrStr.length && /\s/.test(attrStr[i])) i++;
|
|
218
|
+
|
|
219
|
+
if (i < attrStr.length && (attrStr[i] === '"' || attrStr[i] === "'")) {
|
|
220
|
+
const quote = attrStr[i];
|
|
221
|
+
i++;
|
|
222
|
+
const valStart = i;
|
|
223
|
+
while (i < attrStr.length && attrStr[i] !== quote) {
|
|
224
|
+
if (attrStr[i] === '\\' && attrStr[i + 1] === quote) {
|
|
225
|
+
i += 2;
|
|
226
|
+
} else {
|
|
227
|
+
i++;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
value = attrStr.slice(valStart, i);
|
|
231
|
+
i++;
|
|
232
|
+
} else {
|
|
233
|
+
const valStart = i;
|
|
234
|
+
while (i < attrStr.length && !/\s/.test(attrStr[i])) {
|
|
235
|
+
i++;
|
|
236
|
+
}
|
|
237
|
+
value = attrStr.slice(valStart, i);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (key === 'v-if' || key === 'mu-if') {
|
|
242
|
+
directives.vIf = value;
|
|
243
|
+
} else if (key === 'v-for' || key === 'mu-for') {
|
|
244
|
+
const parts = value.split(' in ');
|
|
245
|
+
if (parts.length < 2) {
|
|
246
|
+
directives.vFor = { item: '_item', list: '[]' };
|
|
247
|
+
} else {
|
|
248
|
+
const item = parts[0];
|
|
249
|
+
const list = parts.slice(1).join(' in ');
|
|
250
|
+
directives.vFor = { item: item.trim(), list: list.trim() };
|
|
251
|
+
}
|
|
252
|
+
} else if (key) {
|
|
253
|
+
props[key] = value;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { tag, props, directives };
|
|
258
|
+
}
|
package/src/compiler/compiler.ts
CHANGED
|
@@ -30,7 +30,7 @@ export async function compileSFC(source: string, filename: string, options?: Com
|
|
|
30
30
|
// 3. Style
|
|
31
31
|
const styleResult = compileStyle(descriptor, filename, options);
|
|
32
32
|
|
|
33
|
-
// 4. Template (Use the new No-VDOM compiler!)
|
|
33
|
+
// 4. Template (Use the new unified No-VDOM compiler!)
|
|
34
34
|
const templateResult = compileToDOM(descriptor, scriptResult, styleResult.scopedId);
|
|
35
35
|
|
|
36
36
|
// Calculate offsets for source map merging
|
|
@@ -1,36 +1,7 @@
|
|
|
1
|
+
|
|
1
2
|
import { SFCDescriptor } from './sfc-parser';
|
|
2
3
|
import { ScriptCompileResult } from './script-compiler';
|
|
3
|
-
|
|
4
|
-
// --- AST Definitions (Reused from template-compiler) ---
|
|
5
|
-
|
|
6
|
-
type NodeType = 'Element' | 'Text' | 'Interpolation';
|
|
7
|
-
|
|
8
|
-
type Node = ElementNode | TextNode | InterpolationNode;
|
|
9
|
-
|
|
10
|
-
interface BaseNode {
|
|
11
|
-
type: NodeType;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface ElementNode extends BaseNode {
|
|
15
|
-
type: 'Element';
|
|
16
|
-
tag: string;
|
|
17
|
-
props: Record<string, string>;
|
|
18
|
-
children: Node[];
|
|
19
|
-
directives: {
|
|
20
|
-
vFor?: { item: string; list: string };
|
|
21
|
-
vIf?: string;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface TextNode extends BaseNode {
|
|
26
|
-
type: 'Text';
|
|
27
|
-
content: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface InterpolationNode extends BaseNode {
|
|
31
|
-
type: 'Interpolation';
|
|
32
|
-
content: string; // The expression inside {{ }}
|
|
33
|
-
}
|
|
4
|
+
import { parse, Node, ElementNode, TextNode, InterpolationNode } from './ast-parser';
|
|
34
5
|
|
|
35
6
|
// --- DOM Compiler Result ---
|
|
36
7
|
export interface DOMCompileResult {
|
|
@@ -46,7 +17,7 @@ export function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptComp
|
|
|
46
17
|
let html = template.content;
|
|
47
18
|
const errors: string[] = [];
|
|
48
19
|
|
|
49
|
-
// 1. Parsing Phase (HTML -> AST) -
|
|
20
|
+
// 1. Parsing Phase (HTML -> AST) - Unified Parser
|
|
50
21
|
const ast = parse(html, errors);
|
|
51
22
|
|
|
52
23
|
// 2. Transform Phase (Scopes, Bindings)
|
|
@@ -247,9 +218,7 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
|
|
|
247
218
|
if ((element as any)._propertySideEffects) {
|
|
248
219
|
const effects: string[] = (element as any)._propertySideEffects;
|
|
249
220
|
for (const effect of effects) {
|
|
250
|
-
//
|
|
251
|
-
// We need to translate them to direct assignments, but the original transform already stripped the 'data-mu-id'.
|
|
252
|
-
// Let's rely on the DOM-specific transform logic we will write below.
|
|
221
|
+
// ...
|
|
253
222
|
}
|
|
254
223
|
}
|
|
255
224
|
|
|
@@ -279,104 +248,7 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
|
|
|
279
248
|
return null;
|
|
280
249
|
}
|
|
281
250
|
|
|
282
|
-
// ---
|
|
283
|
-
// For this standalone compiler test, we duplicate the parser functions.
|
|
284
|
-
// In the final refactor, these will be extracted to a shared `parse.ts` utility.
|
|
285
|
-
|
|
286
|
-
function parse(template: string, errors: string[]): ElementNode {
|
|
287
|
-
const root: ElementNode = {
|
|
288
|
-
type: 'Element', tag: 'fragment', props: {}, children: [], directives: {}
|
|
289
|
-
};
|
|
290
|
-
const stack: ElementNode[] = [root];
|
|
291
|
-
let cursor = 0;
|
|
292
|
-
|
|
293
|
-
while (cursor < template.length) {
|
|
294
|
-
const char = template[cursor];
|
|
295
|
-
if (template.startsWith('<!--', cursor)) {
|
|
296
|
-
const end = template.indexOf('-->', cursor);
|
|
297
|
-
if (end === -1) break;
|
|
298
|
-
cursor = end + 3;
|
|
299
|
-
} else if (char === '<') {
|
|
300
|
-
if (template[cursor + 1] === '/') {
|
|
301
|
-
const end = template.indexOf('>', cursor);
|
|
302
|
-
if (end === -1) break;
|
|
303
|
-
stack.pop();
|
|
304
|
-
cursor = end + 1;
|
|
305
|
-
} else {
|
|
306
|
-
const end = template.indexOf('>', cursor);
|
|
307
|
-
if (end === -1) break;
|
|
308
|
-
let tagContent = template.slice(cursor + 1, end);
|
|
309
|
-
const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr'].includes(tagContent.split(' ')[0]);
|
|
310
|
-
if (tagContent.endsWith('/')) tagContent = tagContent.slice(0, -1).trim();
|
|
311
|
-
const { tag, props, directives } = parseTag(tagContent);
|
|
312
|
-
const element: ElementNode = { type: 'Element', tag, props, children: [], directives };
|
|
313
|
-
stack[stack.length - 1].children.push(element);
|
|
314
|
-
if (!isSelfClosing) stack.push(element);
|
|
315
|
-
cursor = end + 1;
|
|
316
|
-
}
|
|
317
|
-
} else if (char === '{' && template[cursor + 1] === '{') {
|
|
318
|
-
const end = template.indexOf('}}', cursor);
|
|
319
|
-
if (end === -1) break;
|
|
320
|
-
const content = template.slice(cursor + 2, end).trim();
|
|
321
|
-
stack[stack.length - 1].children.push({ type: 'Interpolation', content });
|
|
322
|
-
cursor = end + 2;
|
|
323
|
-
} else {
|
|
324
|
-
let nextTag = template.indexOf('<', cursor);
|
|
325
|
-
let nextInterp = template.indexOf('{{', cursor);
|
|
326
|
-
let end = template.length;
|
|
327
|
-
if (nextTag !== -1 && nextTag < end) end = nextTag;
|
|
328
|
-
if (nextInterp !== -1 && nextInterp < end) end = nextInterp;
|
|
329
|
-
const content = template.slice(cursor, end);
|
|
330
|
-
if (content.trim() || content === ' ') {
|
|
331
|
-
stack[stack.length - 1].children.push({ type: 'Text', content });
|
|
332
|
-
}
|
|
333
|
-
cursor = end;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return root;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function parseTag(content: string) {
|
|
340
|
-
const parts = content.split(' ');
|
|
341
|
-
const tag = parts[0];
|
|
342
|
-
const props: Record<string, string> = {};
|
|
343
|
-
const directives: ElementNode['directives'] = {};
|
|
344
|
-
const attrStr = content.slice(tag.length).trim();
|
|
345
|
-
let i = 0;
|
|
346
|
-
while (i < attrStr.length) {
|
|
347
|
-
if (/\s/.test(attrStr[i])) { i++; continue; }
|
|
348
|
-
const keyStart = i;
|
|
349
|
-
while (i < attrStr.length && !/\s|=/.test(attrStr[i])) i++;
|
|
350
|
-
const key = attrStr.slice(keyStart, i);
|
|
351
|
-
let value = 'true';
|
|
352
|
-
let peek = i;
|
|
353
|
-
while (peek < attrStr.length && /\s/.test(attrStr[peek])) peek++;
|
|
354
|
-
if (peek < attrStr.length && attrStr[peek] === '=') {
|
|
355
|
-
i = peek + 1;
|
|
356
|
-
while (i < attrStr.length && /\s/.test(attrStr[i])) i++;
|
|
357
|
-
if (i < attrStr.length && (attrStr[i] === '"' || attrStr[i] === "'")) {
|
|
358
|
-
const quote = attrStr[i];
|
|
359
|
-
i++;
|
|
360
|
-
const valStart = i;
|
|
361
|
-
while (i < attrStr.length && attrStr[i] !== quote) {
|
|
362
|
-
if (attrStr[i] === '\\' && attrStr[i + 1] === quote) i += 2; else i++;
|
|
363
|
-
}
|
|
364
|
-
value = attrStr.slice(valStart, i);
|
|
365
|
-
i++;
|
|
366
|
-
} else {
|
|
367
|
-
const valStart = i;
|
|
368
|
-
while (i < attrStr.length && !/\s/.test(attrStr[i])) i++;
|
|
369
|
-
value = attrStr.slice(valStart, i);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (key === 'v-if' || key === 'mu-if') directives.vIf = value;
|
|
373
|
-
else if (key === 'v-for' || key === 'mu-for') {
|
|
374
|
-
const parts = value.split(' in ');
|
|
375
|
-
directives.vFor = { item: parts[0].trim(), list: parts.slice(1).join(' in ').trim() };
|
|
376
|
-
} else if (key) props[key] = value;
|
|
377
|
-
}
|
|
378
|
-
return { tag, props, directives };
|
|
379
|
-
}
|
|
251
|
+
// --- Transformer Re-implementation ---
|
|
380
252
|
|
|
381
253
|
const JS_KEYWORDS = new Set(['true', 'false', 'null', 'undefined', 'this', 'window', 'if', 'else', 'for', 'while', 'return', 'let', 'const', 'typeof', 'instanceof', 'Math', 'Object', 'Array', 'JSON', 'console']);
|
|
382
254
|
|