@mulanjs/mulanjs 1.0.1-dev.20260227173747 → 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.
@@ -25,10 +25,21 @@ function parse(template, errors) {
25
25
  tag: 'fragment',
26
26
  props: {},
27
27
  children: [],
28
- directives: {}
28
+ directives: {},
29
+ startLine: 1,
30
+ startColumn: 0,
31
+ endLine: 1,
32
+ endColumn: 0
29
33
  };
30
34
  const stack = [root];
31
35
  let cursor = 0;
36
+ const getPos = (idx) => {
37
+ const lines = template.slice(0, idx).split('\n');
38
+ return {
39
+ startLine: lines.length,
40
+ startColumn: lines[lines.length - 1].length
41
+ };
42
+ };
32
43
  while (cursor < template.length) {
33
44
  const char = template[cursor];
34
45
  // 1. Comments
@@ -69,7 +80,18 @@ function parse(template, errors) {
69
80
  const tagContent = template.slice(cursor + 1, end);
70
81
  const { tag, props, directives } = parseTag(tagContent);
71
82
  const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(tag.toLowerCase());
72
- const element = { type: 'Element', tag, props, children: [], directives };
83
+ const startPos = getPos(cursor);
84
+ const endPos = getPos(end + 1);
85
+ const element = {
86
+ type: 'Element',
87
+ tag,
88
+ props,
89
+ children: [],
90
+ directives,
91
+ ...startPos,
92
+ endLine: endPos.startLine,
93
+ endColumn: endPos.startColumn
94
+ };
73
95
  stack[stack.length - 1].children.push(element);
74
96
  if (!isSelfClosing) {
75
97
  stack.push(element);
@@ -85,7 +107,15 @@ function parse(template, errors) {
85
107
  break;
86
108
  }
87
109
  const content = template.slice(cursor + 2, end).trim();
88
- stack[stack.length - 1].children.push({ type: 'Interpolation', content });
110
+ const startPos = getPos(cursor);
111
+ const endPos = getPos(end + 2);
112
+ stack[stack.length - 1].children.push({
113
+ type: 'Interpolation',
114
+ content,
115
+ ...startPos,
116
+ endLine: endPos.startLine,
117
+ endColumn: endPos.startColumn
118
+ });
89
119
  cursor = end + 2;
90
120
  }
91
121
  // 4. Native Template Literals ${ } (Protection)
@@ -105,9 +135,20 @@ function parse(template, errors) {
105
135
  const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
106
136
  if (lastChild && lastChild.type === 'Text') {
107
137
  lastChild.content += content;
138
+ const endPos = getPos(innerCursor);
139
+ lastChild.endLine = endPos.startLine;
140
+ lastChild.endColumn = endPos.startColumn;
108
141
  }
109
142
  else {
110
- stack[stack.length - 1].children.push({ type: 'Text', content });
143
+ const startPos = getPos(cursor);
144
+ const endPos = getPos(innerCursor);
145
+ stack[stack.length - 1].children.push({
146
+ type: 'Text',
147
+ content,
148
+ ...startPos,
149
+ endLine: endPos.startLine,
150
+ endColumn: endPos.startColumn
151
+ });
111
152
  }
112
153
  cursor = innerCursor;
113
154
  }
@@ -136,9 +177,20 @@ function parse(template, errors) {
136
177
  const lastChild = stack[stack.length - 1].children[stack[stack.length - 1].children.length - 1];
137
178
  if (lastChild && lastChild.type === 'Text') {
138
179
  lastChild.content += content;
180
+ const endPos = getPos(end);
181
+ lastChild.endLine = endPos.startLine;
182
+ lastChild.endColumn = endPos.startColumn;
139
183
  }
140
184
  else {
141
- stack[stack.length - 1].children.push({ type: 'Text', content });
185
+ const startPos = getPos(cursor);
186
+ const endPos = getPos(end);
187
+ stack[stack.length - 1].children.push({
188
+ type: 'Text',
189
+ content,
190
+ ...startPos,
191
+ endLine: endPos.startLine,
192
+ endColumn: endPos.startColumn
193
+ });
142
194
  }
143
195
  }
144
196
  cursor = end;
@@ -13,14 +13,21 @@ async function compileSFC(source, filename, options) {
13
13
  // 2. Script
14
14
  const scriptResult = await (0, script_compiler_1.compileScript)(descriptor, options);
15
15
  // Replace export default to capture the component
16
- // If the script already has 'export default', we intercept it.
17
- let scriptCode = scriptResult.code.replace('export default', 'const __component__ =');
18
- if (!scriptCode.includes('const __component__ =')) {
19
- scriptCode = scriptCode.replace(/export\s+default/, 'const __component__ =');
16
+ let scriptCode = scriptResult.code;
17
+ // Improved export capture
18
+ if (scriptCode.includes('export default')) {
19
+ scriptCode = scriptCode.replace('export default', 'const __component__ =');
20
20
  }
21
- if (!scriptCode.includes('const __component__ =')) {
21
+ else if (scriptCode.includes('exports.default =')) {
22
22
  scriptCode = scriptCode.replace('exports.default =', 'const __component__ =');
23
23
  }
24
+ else if (scriptCode.includes('module.exports =')) {
25
+ scriptCode = scriptCode.replace('module.exports =', 'const __component__ =');
26
+ }
27
+ else {
28
+ // Fallback for simple assignment
29
+ scriptCode += '\nconst __component__ = exports.default || module.exports || {};';
30
+ }
24
31
  // 3. Style
25
32
  const styleResult = (0, style_compiler_1.compileStyle)(descriptor, filename, options);
26
33
  // 4. Template (Use the new unified No-VDOM compiler!)
@@ -167,7 +174,7 @@ module.exports = __component__;
167
174
  code: finalCode,
168
175
  css: styleResult.css,
169
176
  errors: [...scriptResult.errors, ...templateResult.errors],
170
- map: mergedMap || scriptResult.map // Fallback to script map if merge fails
177
+ map: mergedMap || scriptResult.map || templateResult.map
171
178
  };
172
179
  }
173
180
  exports.compileSFC = compileSFC;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.compileToDOM = void 0;
4
4
  const ast_parser_1 = require("./ast-parser");
5
+ const source_map_1 = require("source-map");
5
6
  function compileToDOM(descriptor, scriptResult, scopedId) {
6
7
  console.log(`[MulanJS DOM Compiler v2.0] Compiling template for: ${descriptor.filename || 'anonymous'}`);
7
8
  const template = descriptor.template;
@@ -21,9 +22,12 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
21
22
  // We generate a DocumentFragment at the root
22
23
  let codeChunks = [];
23
24
  codeChunks.push(`const _frag = this._recoveryMode ? null : document.createDocumentFragment();`);
25
+ const generator = descriptor.filename ? new source_map_1.SourceMapGenerator({
26
+ file: descriptor.filename + '.template.js'
27
+ }) : null;
24
28
  // Generate code for all top-level children and append them to the fragment
25
29
  ast.children.forEach(child => {
26
- const rootId = generateDOMInstruction(child, codeChunks, getUid, getHoistId, hoists, uidRef, scriptResult.bindings || [], []);
30
+ const rootId = generateDOMInstruction(child, codeChunks, getUid, getHoistId, hoists, uidRef, scriptResult.bindings || [], [], generator, descriptor.filename);
27
31
  if (rootId) {
28
32
  codeChunks.push(`if (_frag) _frag.appendChild(${rootId});`);
29
33
  }
@@ -50,7 +54,8 @@ function compileToDOM(descriptor, scriptResult, scopedId) {
50
54
  }`;
51
55
  return {
52
56
  code: renderFn,
53
- errors
57
+ errors,
58
+ map: generator ? generator.toString() : undefined
54
59
  };
55
60
  }
56
61
  exports.compileToDOM = compileToDOM;
@@ -88,7 +93,22 @@ function htmlEscape(str) {
88
93
  .replace(/"/g, '&quot;')
89
94
  .replace(/'/g, '&#039;');
90
95
  }
91
- function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef, bindings, localScope) {
96
+ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef, bindings, localScope, generator, filename) {
97
+ const addMapping = (generatedLine) => {
98
+ if (generator && filename) {
99
+ generator.addMapping({
100
+ generated: {
101
+ line: generatedLine,
102
+ column: 0
103
+ },
104
+ original: {
105
+ line: node.startLine,
106
+ column: node.startColumn
107
+ },
108
+ source: filename
109
+ });
110
+ }
111
+ };
92
112
  // --- STATIC NODE HOISTING (THE COMPILER INTELLIGENCE) ---
93
113
  // If the node and ALL its children contain ZERO reactivity, we hoist it.
94
114
  if (node.type === 'Element' && node.isStatic) {
@@ -116,11 +136,13 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
116
136
  const numericId = uidRef.current - 1; // Not used for text nodes in SSR but kept for parity if needed
117
137
  // Native template literal interpolation
118
138
  if (text.content.includes('${')) {
139
+ addMapping(chunks.length + 1);
119
140
  chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode("");`);
120
141
  // Wrap in effect for reactivity
121
142
  chunks.push(`this._bindEffect(() => { if (!${id}) return; ${id}.textContent = \`${text.content}\`; }, ${id});`);
122
143
  }
123
144
  else {
145
+ addMapping(chunks.length + 1);
124
146
  chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode(${JSON.stringify(text.content)});`);
125
147
  }
126
148
  return id;
@@ -128,6 +150,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
128
150
  if (node.type === 'Interpolation') {
129
151
  const interp = node;
130
152
  const id = getUid();
153
+ addMapping(chunks.length + 1);
131
154
  chunks.push(`const ${id} = document.createTextNode("");`);
132
155
  // Fine-grained reactivity!
133
156
  chunks.push(`this._bindEffect(() => { ${id}.textContent = _h(${interp.content}, false); }, ${id});`);
@@ -151,7 +174,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
151
174
  let originalLength = blockChunks.length;
152
175
  // 2b. Generate the actual block
153
176
  const elementWithoutIf = { ...element, directives: { ...element.directives, vIf: undefined } };
154
- const clonedChildId = generateDOMInstruction(elementWithoutIf, blockChunks, getUid, getHoistId, hoists, uidRef, bindings, localScope);
177
+ const clonedChildId = generateDOMInstruction(elementWithoutIf, blockChunks, getUid, getHoistId, hoists, uidRef, bindings, localScope, generator, filename);
155
178
  if (clonedChildId) {
156
179
  blockChunks.push(`if (${blockId}_frag && ${clonedChildId} && !this._recoveryMode) ${blockId}_frag.appendChild(${clonedChildId});`);
157
180
  }
@@ -184,7 +207,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
184
207
  // 2b. Generate the actual repeating element (the template root of the mu-for)
185
208
  // We strip the vFor directive so it doesn't recurse infinitely
186
209
  const elementWithoutFor = { ...element, directives: { ...element.directives, vFor: undefined } };
187
- const clonedChildId = generateDOMInstruction(elementWithoutFor, rowChunks, getUid, getHoistId, hoists, uidRef, bindings, rowChildScope);
210
+ const clonedChildId = generateDOMInstruction(elementWithoutFor, rowChunks, getUid, getHoistId, hoists, uidRef, bindings, rowChildScope, generator, filename);
188
211
  if (clonedChildId) {
189
212
  rowChunks.push(`if (${rowId}_frag && ${clonedChildId} && !this._recoveryMode) ${rowId}_frag.appendChild(${clonedChildId});`);
190
213
  }
@@ -205,6 +228,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
205
228
  }
206
229
  const numericId = uidRef.current;
207
230
  const id = getUid();
231
+ addMapping(chunks.length + 1);
208
232
  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}");`);
209
233
  // Handle standard properties and classes
210
234
  for (const [key, value] of Object.entries(element.props)) {
@@ -252,7 +276,7 @@ function generateDOMInstruction(node, chunks, getUid, getHoistId, hoists, uidRef
252
276
  // Recursively generate children
253
277
  const childScope = [...localScope];
254
278
  element.children.forEach(child => {
255
- const childId = generateDOMInstruction(child, chunks, getUid, getHoistId, hoists, uidRef, bindings, childScope);
279
+ const childId = generateDOMInstruction(child, chunks, getUid, getHoistId, hoists, uidRef, bindings, childScope, generator, filename);
256
280
  if (childId) {
257
281
  chunks.push(`if (${id} && ${childId} && !this._recoveryMode) ${id}.appendChild(${childId});`);
258
282
  }
@@ -29,8 +29,28 @@ const source_map_1 = require("source-map");
29
29
  const path = __importStar(require("path"));
30
30
  async function compileScript(descriptor, options) {
31
31
  const script = descriptor.script;
32
- if (!script)
33
- return { code: 'export default {}', errors: [] };
32
+ if (!script) {
33
+ let finalMap;
34
+ if (descriptor.filename) {
35
+ const generator = new source_map_1.SourceMapGenerator({
36
+ file: descriptor.filename
37
+ });
38
+ let relativePath = descriptor.filename.split(/[/\\]/).pop() || 'unknown.mujs';
39
+ const normalized = descriptor.filename.replace(/\\/g, '/');
40
+ const srcIdx = normalized.indexOf('/src/');
41
+ if (srcIdx !== -1) {
42
+ relativePath = 'webpack:///' + normalized.substring(srcIdx + 1);
43
+ }
44
+ generator.setSourceContent(relativePath, descriptor.source);
45
+ generator.addMapping({
46
+ generated: { line: 1, column: 0 },
47
+ original: { line: 1, column: 0 },
48
+ source: relativePath
49
+ });
50
+ finalMap = generator.toString();
51
+ }
52
+ return { code: 'export default {}', errors: [], map: finalMap };
53
+ }
34
54
  let content = script.content;
35
55
  let filename = descriptor.filename;
36
56
  let isExternal = false;
@@ -66,7 +66,7 @@ function parseMUJS(source, filename) {
66
66
  const content = source.slice(contentStart, contentEnd);
67
67
  const block = {
68
68
  type: tagName,
69
- content: content.trim(), // Trim content for cleanliness
69
+ content: content,
70
70
  attrs,
71
71
  start: start,
72
72
  end: contentEnd + closeTag.length
@@ -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.20260227173747",
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