@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,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// --- AST Definitions ---
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.parseTag = exports.parse = exports.decodeEntities = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* Decodes standard HTML entities.
|
|
7
|
+
*/
|
|
8
|
+
function decodeEntities(str) {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/</g, '<')
|
|
11
|
+
.replace(/>/g, '>')
|
|
12
|
+
.replace(/&/g, '&')
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
.replace(/'/g, "'")
|
|
15
|
+
.replace(/ /g, '\u00A0');
|
|
16
|
+
}
|
|
17
|
+
exports.decodeEntities = decodeEntities;
|
|
18
|
+
/**
|
|
19
|
+
* Unified MulanJS Template Parser
|
|
20
|
+
* Handles HTML tags, {{ }} interpolation, and is robust against raw < symbols in text.
|
|
21
|
+
*/
|
|
22
|
+
function parse(template, errors) {
|
|
23
|
+
const root = {
|
|
24
|
+
type: 'Element',
|
|
25
|
+
tag: 'fragment',
|
|
26
|
+
props: {},
|
|
27
|
+
children: [],
|
|
28
|
+
directives: {}
|
|
29
|
+
};
|
|
30
|
+
const stack = [root];
|
|
31
|
+
let cursor = 0;
|
|
32
|
+
while (cursor < template.length) {
|
|
33
|
+
const char = template[cursor];
|
|
34
|
+
// 1. Comments
|
|
35
|
+
if (template.startsWith('<!--', cursor)) {
|
|
36
|
+
const end = template.indexOf('-->', cursor);
|
|
37
|
+
if (end === -1) {
|
|
38
|
+
errors.push('Unclosed HTML comment.');
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
cursor = end + 3;
|
|
42
|
+
}
|
|
43
|
+
// 2. Tags (with lookahead check for robustness)
|
|
44
|
+
else if (char === '<' && /[\/a-zA-Z!]/.test(template[cursor + 1])) {
|
|
45
|
+
if (template[cursor + 1] === '/') {
|
|
46
|
+
// Closing Tag
|
|
47
|
+
const end = template.indexOf('>', cursor);
|
|
48
|
+
if (end === -1) {
|
|
49
|
+
errors.push('Unclosed closing tag.');
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
const tagName = template.slice(cursor + 2, end).trim();
|
|
53
|
+
// Find matching element in stack to pop
|
|
54
|
+
if (stack.length > 1 && stack[stack.length - 1].tag === tagName) {
|
|
55
|
+
stack.pop();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
errors.push(`Mismatched closing tag </${tagName}>.`);
|
|
59
|
+
}
|
|
60
|
+
cursor = end + 1;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Opening Tag
|
|
64
|
+
const end = template.indexOf('>', cursor);
|
|
65
|
+
if (end === -1) {
|
|
66
|
+
errors.push('Unclosed opening tag.');
|
|
67
|
+
break;
|
|
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
|
+
}
|
|
74
|
+
const { tag, props, directives } = parseTag(tagContent);
|
|
75
|
+
const element = { type: 'Element', tag, props, children: [], directives };
|
|
76
|
+
stack[stack.length - 1].children.push(element);
|
|
77
|
+
if (!isSelfClosing) {
|
|
78
|
+
stack.push(element);
|
|
79
|
+
}
|
|
80
|
+
cursor = end + 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// 3. Interpolation {{ }}
|
|
84
|
+
else if (template.startsWith('{{', cursor)) {
|
|
85
|
+
const end = template.indexOf('}}', cursor);
|
|
86
|
+
if (end === -1) {
|
|
87
|
+
errors.push('Unclosed interpolation {{ }}.');
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
const content = template.slice(cursor + 2, end).trim();
|
|
91
|
+
stack[stack.length - 1].children.push({ type: 'Interpolation', content });
|
|
92
|
+
cursor = end + 2;
|
|
93
|
+
}
|
|
94
|
+
// 4. Native Template Literals ${ } (Protection)
|
|
95
|
+
else if (template.startsWith('${', cursor)) {
|
|
96
|
+
// Find end of ${ } while respecting nested braces
|
|
97
|
+
let innerCursor = cursor + 2;
|
|
98
|
+
let depth = 1;
|
|
99
|
+
while (innerCursor < template.length && depth > 0) {
|
|
100
|
+
if (template[innerCursor] === '{')
|
|
101
|
+
depth++;
|
|
102
|
+
if (template[innerCursor] === '}')
|
|
103
|
+
depth--;
|
|
104
|
+
innerCursor++;
|
|
105
|
+
}
|
|
106
|
+
const content = template.slice(cursor, innerCursor);
|
|
107
|
+
// Treat as text but this block is now "atomic" and won't be split by < checks
|
|
108
|
+
const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
|
|
109
|
+
if (lastChild && lastChild.type === 'Text') {
|
|
110
|
+
lastChild.content += content;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
stack[stack.length - 1].children.push({ type: 'Text', content });
|
|
114
|
+
}
|
|
115
|
+
cursor = innerCursor;
|
|
116
|
+
}
|
|
117
|
+
// 5. Text Nodes
|
|
118
|
+
else {
|
|
119
|
+
let nextTag = template.indexOf('<', cursor);
|
|
120
|
+
let nextInterp = template.indexOf('{{', cursor);
|
|
121
|
+
let nextNative = template.indexOf('${', cursor);
|
|
122
|
+
let end = template.length;
|
|
123
|
+
// Heuristic for nextTag: it must look like a tag start
|
|
124
|
+
while (nextTag !== -1) {
|
|
125
|
+
if (/[\/a-zA-Z!]/.test(template[nextTag + 1])) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
nextTag = template.indexOf('<', nextTag + 1);
|
|
129
|
+
}
|
|
130
|
+
if (nextTag !== -1 && nextTag < end)
|
|
131
|
+
end = nextTag;
|
|
132
|
+
if (nextInterp !== -1 && nextInterp < end)
|
|
133
|
+
end = nextInterp;
|
|
134
|
+
if (nextNative !== -1 && nextNative < end)
|
|
135
|
+
end = nextNative;
|
|
136
|
+
const rawContent = template.slice(cursor, end);
|
|
137
|
+
const content = decodeEntities(rawContent);
|
|
138
|
+
if (content) {
|
|
139
|
+
const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
|
|
140
|
+
if (lastChild && lastChild.type === 'Text') {
|
|
141
|
+
lastChild.content += content;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
stack[stack.length - 1].children.push({ type: 'Text', content });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
cursor = end;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return root;
|
|
151
|
+
}
|
|
152
|
+
exports.parse = parse;
|
|
153
|
+
function parseTag(content) {
|
|
154
|
+
const parts = content.split(' ');
|
|
155
|
+
const tag = parts[0];
|
|
156
|
+
const props = {};
|
|
157
|
+
const directives = {};
|
|
158
|
+
const attrStr = content.slice(tag.length).trim();
|
|
159
|
+
let i = 0;
|
|
160
|
+
while (i < attrStr.length) {
|
|
161
|
+
if (/\s/.test(attrStr[i])) {
|
|
162
|
+
i++;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const keyStart = i;
|
|
166
|
+
while (i < attrStr.length && !/\s|=/.test(attrStr[i])) {
|
|
167
|
+
i++;
|
|
168
|
+
}
|
|
169
|
+
const key = attrStr.slice(keyStart, i);
|
|
170
|
+
let value = 'true';
|
|
171
|
+
let peek = i;
|
|
172
|
+
while (peek < attrStr.length && /\s/.test(attrStr[peek]))
|
|
173
|
+
peek++;
|
|
174
|
+
if (peek < attrStr.length && attrStr[peek] === '=') {
|
|
175
|
+
i = peek + 1;
|
|
176
|
+
while (i < attrStr.length && /\s/.test(attrStr[i]))
|
|
177
|
+
i++;
|
|
178
|
+
if (i < attrStr.length && (attrStr[i] === '"' || attrStr[i] === "'")) {
|
|
179
|
+
const quote = attrStr[i];
|
|
180
|
+
i++;
|
|
181
|
+
const valStart = i;
|
|
182
|
+
while (i < attrStr.length && attrStr[i] !== quote) {
|
|
183
|
+
if (attrStr[i] === '\\' && attrStr[i + 1] === quote) {
|
|
184
|
+
i += 2;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
i++;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
value = attrStr.slice(valStart, i);
|
|
191
|
+
i++;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const valStart = i;
|
|
195
|
+
while (i < attrStr.length && !/\s/.test(attrStr[i])) {
|
|
196
|
+
i++;
|
|
197
|
+
}
|
|
198
|
+
value = attrStr.slice(valStart, i);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (key === 'v-if' || key === 'mu-if') {
|
|
202
|
+
directives.vIf = value;
|
|
203
|
+
}
|
|
204
|
+
else if (key === 'v-for' || key === 'mu-for') {
|
|
205
|
+
const parts = value.split(' in ');
|
|
206
|
+
if (parts.length < 2) {
|
|
207
|
+
directives.vFor = { item: '_item', list: '[]' };
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const item = parts[0];
|
|
211
|
+
const list = parts.slice(1).join(' in ');
|
|
212
|
+
directives.vFor = { item: item.trim(), list: list.trim() };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if (key) {
|
|
216
|
+
props[key] = value;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { tag, props, directives };
|
|
220
|
+
}
|
|
221
|
+
exports.parseTag = parseTag;
|
|
@@ -19,7 +19,7 @@ async function compileSFC(source, filename, options) {
|
|
|
19
19
|
}
|
|
20
20
|
// 3. Style
|
|
21
21
|
const styleResult = (0, style_compiler_1.compileStyle)(descriptor, filename, options);
|
|
22
|
-
// 4. Template (Use the new No-VDOM compiler!)
|
|
22
|
+
// 4. Template (Use the new unified No-VDOM compiler!)
|
|
23
23
|
const templateResult = (0, dom_compiler_1.compileToDOM)(descriptor, scriptResult, styleResult.scopedId);
|
|
24
24
|
// Calculate offsets for source map merging
|
|
25
25
|
// 1. Script Code
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.compileToDOM = void 0;
|
|
4
|
+
const ast_parser_1 = require("./ast-parser");
|
|
4
5
|
function compileToDOM(descriptor, scriptResult, scopedId) {
|
|
5
6
|
console.log(`[MulanJS DOM Compiler v2.0] Compiling template for: ${descriptor.filename || 'anonymous'}`);
|
|
6
7
|
const template = descriptor.template;
|
|
@@ -8,8 +9,8 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
|
|
|
8
9
|
return { code: 'function render() { return document.createDocumentFragment(); }', errors: [] };
|
|
9
10
|
let html = template.content;
|
|
10
11
|
const errors = [];
|
|
11
|
-
// 1. Parsing Phase (HTML -> AST) -
|
|
12
|
-
const ast = parse(html, errors);
|
|
12
|
+
// 1. Parsing Phase (HTML -> AST) - Unified Parser
|
|
13
|
+
const ast = (0, ast_parser_1.parse)(html, errors);
|
|
13
14
|
// 2. Transform Phase (Scopes, Bindings)
|
|
14
15
|
transform(ast, scriptResult, scopedId, [], descriptor.filename);
|
|
15
16
|
// 3. Codegen Phase (AST -> JS DOM Instructions)
|
|
@@ -177,9 +178,7 @@ function generateDOMInstruction(node, chunks, getUid, uidRef, bindings, localSco
|
|
|
177
178
|
if (element._propertySideEffects) {
|
|
178
179
|
const effects = element._propertySideEffects;
|
|
179
180
|
for (const effect of effects) {
|
|
180
|
-
//
|
|
181
|
-
// We need to translate them to direct assignments, but the original transform already stripped the 'data-mu-id'.
|
|
182
|
-
// Let's rely on the DOM-specific transform logic we will write below.
|
|
181
|
+
// ...
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
184
|
// Handle DOM-specific Bindings
|
|
@@ -205,127 +204,7 @@ function generateDOMInstruction(node, chunks, getUid, uidRef, bindings, localSco
|
|
|
205
204
|
}
|
|
206
205
|
return null;
|
|
207
206
|
}
|
|
208
|
-
// ---
|
|
209
|
-
// For this standalone compiler test, we duplicate the parser functions.
|
|
210
|
-
// In the final refactor, these will be extracted to a shared `parse.ts` utility.
|
|
211
|
-
function parse(template, errors) {
|
|
212
|
-
const root = {
|
|
213
|
-
type: 'Element', tag: 'fragment', props: {}, children: [], directives: {}
|
|
214
|
-
};
|
|
215
|
-
const stack = [root];
|
|
216
|
-
let cursor = 0;
|
|
217
|
-
while (cursor < template.length) {
|
|
218
|
-
const char = template[cursor];
|
|
219
|
-
if (template.startsWith('<!--', cursor)) {
|
|
220
|
-
const end = template.indexOf('-->', cursor);
|
|
221
|
-
if (end === -1)
|
|
222
|
-
break;
|
|
223
|
-
cursor = end + 3;
|
|
224
|
-
}
|
|
225
|
-
else if (char === '<') {
|
|
226
|
-
if (template[cursor + 1] === '/') {
|
|
227
|
-
const end = template.indexOf('>', cursor);
|
|
228
|
-
if (end === -1)
|
|
229
|
-
break;
|
|
230
|
-
stack.pop();
|
|
231
|
-
cursor = end + 1;
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
const end = template.indexOf('>', cursor);
|
|
235
|
-
if (end === -1)
|
|
236
|
-
break;
|
|
237
|
-
let tagContent = template.slice(cursor + 1, end);
|
|
238
|
-
const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr'].includes(tagContent.split(' ')[0]);
|
|
239
|
-
if (tagContent.endsWith('/'))
|
|
240
|
-
tagContent = tagContent.slice(0, -1).trim();
|
|
241
|
-
const { tag, props, directives } = parseTag(tagContent);
|
|
242
|
-
const element = { type: 'Element', tag, props, children: [], directives };
|
|
243
|
-
stack[stack.length - 1].children.push(element);
|
|
244
|
-
if (!isSelfClosing)
|
|
245
|
-
stack.push(element);
|
|
246
|
-
cursor = end + 1;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
else if (char === '{' && template[cursor + 1] === '{') {
|
|
250
|
-
const end = template.indexOf('}}', cursor);
|
|
251
|
-
if (end === -1)
|
|
252
|
-
break;
|
|
253
|
-
const content = template.slice(cursor + 2, end).trim();
|
|
254
|
-
stack[stack.length - 1].children.push({ type: 'Interpolation', content });
|
|
255
|
-
cursor = end + 2;
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
let nextTag = template.indexOf('<', cursor);
|
|
259
|
-
let nextInterp = template.indexOf('{{', cursor);
|
|
260
|
-
let end = template.length;
|
|
261
|
-
if (nextTag !== -1 && nextTag < end)
|
|
262
|
-
end = nextTag;
|
|
263
|
-
if (nextInterp !== -1 && nextInterp < end)
|
|
264
|
-
end = nextInterp;
|
|
265
|
-
const content = template.slice(cursor, end);
|
|
266
|
-
if (content.trim() || content === ' ') {
|
|
267
|
-
stack[stack.length - 1].children.push({ type: 'Text', content });
|
|
268
|
-
}
|
|
269
|
-
cursor = end;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return root;
|
|
273
|
-
}
|
|
274
|
-
function parseTag(content) {
|
|
275
|
-
const parts = content.split(' ');
|
|
276
|
-
const tag = parts[0];
|
|
277
|
-
const props = {};
|
|
278
|
-
const directives = {};
|
|
279
|
-
const attrStr = content.slice(tag.length).trim();
|
|
280
|
-
let i = 0;
|
|
281
|
-
while (i < attrStr.length) {
|
|
282
|
-
if (/\s/.test(attrStr[i])) {
|
|
283
|
-
i++;
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
const keyStart = i;
|
|
287
|
-
while (i < attrStr.length && !/\s|=/.test(attrStr[i]))
|
|
288
|
-
i++;
|
|
289
|
-
const key = attrStr.slice(keyStart, i);
|
|
290
|
-
let value = 'true';
|
|
291
|
-
let peek = i;
|
|
292
|
-
while (peek < attrStr.length && /\s/.test(attrStr[peek]))
|
|
293
|
-
peek++;
|
|
294
|
-
if (peek < attrStr.length && attrStr[peek] === '=') {
|
|
295
|
-
i = peek + 1;
|
|
296
|
-
while (i < attrStr.length && /\s/.test(attrStr[i]))
|
|
297
|
-
i++;
|
|
298
|
-
if (i < attrStr.length && (attrStr[i] === '"' || attrStr[i] === "'")) {
|
|
299
|
-
const quote = attrStr[i];
|
|
300
|
-
i++;
|
|
301
|
-
const valStart = i;
|
|
302
|
-
while (i < attrStr.length && attrStr[i] !== quote) {
|
|
303
|
-
if (attrStr[i] === '\\' && attrStr[i + 1] === quote)
|
|
304
|
-
i += 2;
|
|
305
|
-
else
|
|
306
|
-
i++;
|
|
307
|
-
}
|
|
308
|
-
value = attrStr.slice(valStart, i);
|
|
309
|
-
i++;
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
const valStart = i;
|
|
313
|
-
while (i < attrStr.length && !/\s/.test(attrStr[i]))
|
|
314
|
-
i++;
|
|
315
|
-
value = attrStr.slice(valStart, i);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
if (key === 'v-if' || key === 'mu-if')
|
|
319
|
-
directives.vIf = value;
|
|
320
|
-
else if (key === 'v-for' || key === 'mu-for') {
|
|
321
|
-
const parts = value.split(' in ');
|
|
322
|
-
directives.vFor = { item: parts[0].trim(), list: parts.slice(1).join(' in ').trim() };
|
|
323
|
-
}
|
|
324
|
-
else if (key)
|
|
325
|
-
props[key] = value;
|
|
326
|
-
}
|
|
327
|
-
return { tag, props, directives };
|
|
328
|
-
}
|
|
207
|
+
// --- Transformer Re-implementation ---
|
|
329
208
|
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']);
|
|
330
209
|
function processBindings(exp, bindings, localScope) {
|
|
331
210
|
const isOptionsAPI = !bindings || bindings.length === 0;
|