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

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.
@@ -3,6 +3,10 @@ export type Node = ElementNode | TextNode | InterpolationNode;
3
3
  export interface BaseNode {
4
4
  type: NodeType;
5
5
  isStatic?: boolean;
6
+ startLine: number;
7
+ startColumn: number;
8
+ endLine: number;
9
+ endColumn: number;
6
10
  }
7
11
  export interface ElementNode extends BaseNode {
8
12
  type: 'Element';
@@ -3,6 +3,10 @@ export type Node = ElementNode | TextNode | InterpolationNode;
3
3
  export interface BaseNode {
4
4
  type: NodeType;
5
5
  isStatic?: boolean;
6
+ startLine: number;
7
+ startColumn: number;
8
+ endLine: number;
9
+ endColumn: number;
6
10
  }
7
11
  export interface ElementNode extends BaseNode {
8
12
  type: 'Element';
@@ -3,5 +3,6 @@ import { ScriptCompileResult } from './script-compiler';
3
3
  export interface DOMCompileResult {
4
4
  code: string;
5
5
  errors: string[];
6
+ map?: string;
6
7
  }
7
8
  export declare function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptCompileResult, scopedId?: string): DOMCompileResult;
@@ -3,5 +3,6 @@ import { ScriptCompileResult } from './script-compiler';
3
3
  export interface DOMCompileResult {
4
4
  code: string;
5
5
  errors: string[];
6
+ map?: string;
6
7
  }
7
8
  export declare function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptCompileResult, scopedId?: string): DOMCompileResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulanjs/mulanjs",
3
- "version": "1.0.1-dev.20260227173253",
3
+ "version": "1.0.1-dev.20260227175607",
4
4
  "description": "A powerful, secure, and enterprise-grade JavaScript framework.",
5
5
  "main": "dist/mulan.js",
6
6
  "module": "dist/mulan.esm.js",
@@ -8,6 +8,10 @@ export type Node = ElementNode | TextNode | InterpolationNode;
8
8
  export interface BaseNode {
9
9
  type: NodeType;
10
10
  isStatic?: boolean;
11
+ startLine: number;
12
+ startColumn: number;
13
+ endLine: number;
14
+ endColumn: number;
11
15
  }
12
16
 
13
17
  export interface ElementNode extends BaseNode {
@@ -54,12 +58,24 @@ export function parse(template: string, errors: string[]): ElementNode {
54
58
  tag: 'fragment',
55
59
  props: {},
56
60
  children: [],
57
- directives: {}
61
+ directives: {},
62
+ startLine: 1,
63
+ startColumn: 0,
64
+ endLine: 1,
65
+ endColumn: 0
58
66
  };
59
67
 
60
68
  const stack: ElementNode[] = [root];
61
69
  let cursor = 0;
62
70
 
71
+ const getPos = (idx: number) => {
72
+ const lines = template.slice(0, idx).split('\n');
73
+ return {
74
+ startLine: lines.length,
75
+ startColumn: lines[lines.length - 1].length
76
+ };
77
+ };
78
+
63
79
  while (cursor < template.length) {
64
80
  const char = template[cursor];
65
81
 
@@ -101,7 +117,19 @@ export function parse(template: string, errors: string[]): ElementNode {
101
117
  const { tag, props, directives } = parseTag(tagContent);
102
118
  const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(tag.toLowerCase());
103
119
 
104
- const element: ElementNode = { type: 'Element', tag, props, children: [], directives };
120
+ const startPos = getPos(cursor);
121
+ const endPos = getPos(end + 1);
122
+
123
+ const element: ElementNode = {
124
+ type: 'Element',
125
+ tag,
126
+ props,
127
+ children: [],
128
+ directives,
129
+ ...startPos,
130
+ endLine: endPos.startLine,
131
+ endColumn: endPos.startColumn
132
+ };
105
133
  stack[stack.length - 1].children.push(element);
106
134
 
107
135
  if (!isSelfClosing) {
@@ -120,7 +148,15 @@ export function parse(template: string, errors: string[]): ElementNode {
120
148
  }
121
149
 
122
150
  const content = template.slice(cursor + 2, end).trim();
123
- stack[stack.length - 1].children.push({ type: 'Interpolation', content });
151
+ const startPos = getPos(cursor);
152
+ const endPos = getPos(end + 2);
153
+ stack[stack.length - 1].children.push({
154
+ type: 'Interpolation',
155
+ content,
156
+ ...startPos,
157
+ endLine: endPos.startLine,
158
+ endColumn: endPos.startColumn
159
+ });
124
160
  cursor = end + 2;
125
161
  }
126
162
  // 4. Native Template Literals ${ } (Protection)
@@ -139,8 +175,19 @@ export function parse(template: string, errors: string[]): ElementNode {
139
175
  const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
140
176
  if (lastChild && lastChild.type === 'Text') {
141
177
  lastChild.content += content;
178
+ const endPos = getPos(innerCursor);
179
+ lastChild.endLine = endPos.startLine;
180
+ lastChild.endColumn = endPos.startColumn;
142
181
  } else {
143
- stack[stack.length - 1].children.push({ type: 'Text', content });
182
+ const startPos = getPos(cursor);
183
+ const endPos = getPos(innerCursor);
184
+ stack[stack.length - 1].children.push({
185
+ type: 'Text',
186
+ content,
187
+ ...startPos,
188
+ endLine: endPos.startLine,
189
+ endColumn: endPos.startColumn
190
+ });
144
191
  }
145
192
  cursor = innerCursor;
146
193
  }
@@ -171,8 +218,19 @@ export function parse(template: string, errors: string[]): ElementNode {
171
218
  const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
172
219
  if (lastChild && lastChild.type === 'Text') {
173
220
  lastChild.content += content;
221
+ const endPos = getPos(end);
222
+ lastChild.endLine = endPos.startLine;
223
+ lastChild.endColumn = endPos.startColumn;
174
224
  } else {
175
- stack[stack.length - 1].children.push({ type: 'Text', content });
225
+ const startPos = getPos(cursor);
226
+ const endPos = getPos(end);
227
+ stack[stack.length - 1].children.push({
228
+ type: 'Text',
229
+ content,
230
+ ...startPos,
231
+ endLine: endPos.startLine,
232
+ endColumn: endPos.startColumn
233
+ });
176
234
  }
177
235
  }
178
236
  cursor = end;
@@ -22,13 +22,18 @@ export async function compileSFC(source: string, filename: string, options?: Com
22
22
  // 2. Script
23
23
  const scriptResult = await compileScript(descriptor, options);
24
24
  // Replace export default to capture the component
25
- // If the script already has 'export default', we intercept it.
26
- let scriptCode = scriptResult.code.replace('export default', 'const __component__ =');
27
- if (!scriptCode.includes('const __component__ =')) {
28
- scriptCode = scriptCode.replace(/export\s+default/, 'const __component__ =');
29
- }
30
- if (!scriptCode.includes('const __component__ =')) {
25
+ let scriptCode = scriptResult.code;
26
+
27
+ // Improved export capture
28
+ if (scriptCode.includes('export default')) {
29
+ scriptCode = scriptCode.replace('export default', 'const __component__ =');
30
+ } else if (scriptCode.includes('exports.default =')) {
31
31
  scriptCode = scriptCode.replace('exports.default =', 'const __component__ =');
32
+ } else if (scriptCode.includes('module.exports =')) {
33
+ scriptCode = scriptCode.replace('module.exports =', 'const __component__ =');
34
+ } else {
35
+ // Fallback for simple assignment
36
+ scriptCode += '\nconst __component__ = exports.default || module.exports || {};';
32
37
  }
33
38
 
34
39
  // 3. Style
@@ -188,7 +193,7 @@ module.exports = __component__;
188
193
  code: finalCode,
189
194
  css: styleResult.css,
190
195
  errors: [...scriptResult.errors, ...templateResult.errors],
191
- map: mergedMap || scriptResult.map // Fallback to script map if merge fails
196
+ map: mergedMap || scriptResult.map || (templateResult as any).map
192
197
  };
193
198
  }
194
199
 
@@ -1,12 +1,13 @@
1
-
2
1
  import { SFCDescriptor } from './sfc-parser';
3
2
  import { ScriptCompileResult } from './script-compiler';
4
3
  import { parse, Node, ElementNode, TextNode, InterpolationNode } from './ast-parser';
4
+ import { SourceMapGenerator } from 'source-map';
5
5
 
6
6
  // --- DOM Compiler Result ---
7
7
  export interface DOMCompileResult {
8
8
  code: string;
9
9
  errors: string[];
10
+ map?: string;
10
11
  }
11
12
 
12
13
  export function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptCompileResult, scopedId?: string): DOMCompileResult {
@@ -34,9 +35,13 @@ export function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptComp
34
35
  let codeChunks: string[] = [];
35
36
  codeChunks.push(`const _frag = this._recoveryMode ? null : document.createDocumentFragment();`);
36
37
 
38
+ const generator = descriptor.filename ? new SourceMapGenerator({
39
+ file: descriptor.filename + '.template.js'
40
+ }) : null;
41
+
37
42
  // Generate code for all top-level children and append them to the fragment
38
43
  ast.children.forEach(child => {
39
- const rootId = generateDOMInstruction(child, codeChunks, getUid, getHoistId, hoists, uidRef, scriptResult.bindings || [], []);
44
+ const rootId = generateDOMInstruction(child, codeChunks, getUid, getHoistId, hoists, uidRef, scriptResult.bindings || [], [], generator, descriptor.filename);
40
45
  if (rootId) {
41
46
  codeChunks.push(`if (_frag) _frag.appendChild(${rootId});`);
42
47
  }
@@ -66,7 +71,8 @@ export function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptComp
66
71
 
67
72
  return {
68
73
  code: renderFn,
69
- errors
74
+ errors,
75
+ map: generator ? generator.toString() : undefined
70
76
  };
71
77
  }
72
78
 
@@ -108,7 +114,23 @@ function htmlEscape(str: string): string {
108
114
  .replace(/'/g, '&#039;');
109
115
  }
110
116
 
111
- function generateDOMInstruction(node: Node, chunks: string[], getUid: () => string, getHoistId: () => string, hoists: string[], uidRef: { current: number }, bindings: string[], localScope: string[]): string | null {
117
+ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => string, getHoistId: () => string, hoists: string[], uidRef: { current: number }, bindings: string[], localScope: string[], generator: SourceMapGenerator | null, filename: string): string | null {
118
+
119
+ const addMapping = (generatedLine: number) => {
120
+ if (generator && filename) {
121
+ generator.addMapping({
122
+ generated: {
123
+ line: generatedLine,
124
+ column: 0
125
+ },
126
+ original: {
127
+ line: node.startLine,
128
+ column: node.startColumn
129
+ },
130
+ source: filename
131
+ });
132
+ }
133
+ };
112
134
 
113
135
  // --- STATIC NODE HOISTING (THE COMPILER INTELLIGENCE) ---
114
136
  // If the node and ALL its children contain ZERO reactivity, we hoist it.
@@ -141,10 +163,12 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
141
163
 
142
164
  // Native template literal interpolation
143
165
  if (text.content.includes('${')) {
166
+ addMapping(chunks.length + 1);
144
167
  chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode("");`);
145
168
  // Wrap in effect for reactivity
146
169
  chunks.push(`this._bindEffect(() => { if (!${id}) return; ${id}.textContent = \`${text.content}\`; }, ${id});`);
147
170
  } else {
171
+ addMapping(chunks.length + 1);
148
172
  chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode(${JSON.stringify(text.content)});`);
149
173
  }
150
174
  return id;
@@ -153,6 +177,7 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
153
177
  if (node.type === 'Interpolation') {
154
178
  const interp = node as InterpolationNode;
155
179
  const id = getUid();
180
+ addMapping(chunks.length + 1);
156
181
  chunks.push(`const ${id} = document.createTextNode("");`);
157
182
  // Fine-grained reactivity!
158
183
  chunks.push(`this._bindEffect(() => { ${id}.textContent = _h(${interp.content}, false); }, ${id});`);
@@ -182,7 +207,7 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
182
207
 
183
208
  // 2b. Generate the actual block
184
209
  const elementWithoutIf = { ...element, directives: { ...element.directives, vIf: undefined } };
185
- const clonedChildId = generateDOMInstruction(elementWithoutIf as ElementNode, blockChunks, getUid, getHoistId, hoists, uidRef, bindings, localScope);
210
+ const clonedChildId = generateDOMInstruction(elementWithoutIf as ElementNode, blockChunks, getUid, getHoistId, hoists, uidRef, bindings, localScope, generator, filename);
186
211
 
187
212
  if (clonedChildId) {
188
213
  blockChunks.push(`if (${blockId}_frag && ${clonedChildId} && !this._recoveryMode) ${blockId}_frag.appendChild(${clonedChildId});`);
@@ -227,7 +252,7 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
227
252
  // 2b. Generate the actual repeating element (the template root of the mu-for)
228
253
  // We strip the vFor directive so it doesn't recurse infinitely
229
254
  const elementWithoutFor = { ...element, directives: { ...element.directives, vFor: undefined } };
230
- const clonedChildId = generateDOMInstruction(elementWithoutFor as ElementNode, rowChunks, getUid, getHoistId, hoists, uidRef, bindings, rowChildScope);
255
+ const clonedChildId = generateDOMInstruction(elementWithoutFor as ElementNode, rowChunks, getUid, getHoistId, hoists, uidRef, bindings, rowChildScope, generator, filename);
231
256
 
232
257
  if (clonedChildId) {
233
258
  rowChunks.push(`if (${rowId}_frag && ${clonedChildId} && !this._recoveryMode) ${rowId}_frag.appendChild(${clonedChildId});`);
@@ -256,6 +281,7 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
256
281
 
257
282
  const numericId = uidRef.current;
258
283
  const id = getUid();
284
+ addMapping(chunks.length + 1);
259
285
  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}");`);
260
286
 
261
287
 
@@ -302,7 +328,7 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
302
328
  // Recursively generate children
303
329
  const childScope = [...localScope];
304
330
  element.children.forEach(child => {
305
- const childId = generateDOMInstruction(child, chunks, getUid, getHoistId, hoists, uidRef, bindings, childScope);
331
+ const childId = generateDOMInstruction(child, chunks, getUid, getHoistId, hoists, uidRef, bindings, childScope, generator, filename);
306
332
  if (childId) {
307
333
  chunks.push(`if (${id} && ${childId} && !this._recoveryMode) ${id}.appendChild(${childId});`);
308
334
  }
@@ -17,7 +17,28 @@ export interface CompilerOptions {
17
17
 
18
18
  export async function compileScript(descriptor: SFCDescriptor, options?: CompilerOptions): Promise<ScriptCompileResult> {
19
19
  const script = descriptor.script;
20
- if (!script) return { code: 'export default {}', errors: [] };
20
+ if (!script) {
21
+ let finalMap: string | undefined;
22
+ if (descriptor.filename) {
23
+ const generator = new SourceMapGenerator({
24
+ file: descriptor.filename
25
+ });
26
+ let relativePath = descriptor.filename.split(/[/\\]/).pop() || 'unknown.mujs';
27
+ const normalized = descriptor.filename.replace(/\\/g, '/');
28
+ const srcIdx = normalized.indexOf('/src/');
29
+ if (srcIdx !== -1) {
30
+ relativePath = 'webpack:///' + normalized.substring(srcIdx + 1);
31
+ }
32
+ generator.setSourceContent(relativePath, descriptor.source);
33
+ generator.addMapping({
34
+ generated: { line: 1, column: 0 },
35
+ original: { line: 1, column: 0 },
36
+ source: relativePath
37
+ });
38
+ finalMap = generator.toString();
39
+ }
40
+ return { code: 'export default {}', errors: [], map: finalMap };
41
+ }
21
42
 
22
43
  let content = script.content;
23
44
  let filename = descriptor.filename;
@@ -91,7 +91,7 @@ export function parseMUJS(source: string, filename: string): SFCDescriptor {
91
91
  const content = source.slice(contentStart, contentEnd);
92
92
  const block: SFCBlock = {
93
93
  type: tagName as any,
94
- content: content.trim(), // Trim content for cleanliness
94
+ content: content,
95
95
  attrs,
96
96
  start: start,
97
97
  end: contentEnd + closeTag.length