@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.
@@ -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(/&lt;/g, '<')
11
+ .replace(/&gt;/g, '>')
12
+ .replace(/&amp;/g, '&')
13
+ .replace(/&quot;/g, '"')
14
+ .replace(/&#039;/g, "'")
15
+ .replace(/&nbsp;/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) - We can reuse the parser logic, but for now we duplicate it to keep it isolated from the old string compiler
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
- // effect strings look like: `this._b('mu_abc', 'style', ...)`
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
- // --- Copied Parser and Transformer ---
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;