@mulanjs/mulanjs 1.0.1-dev.20260227135307 → 1.0.1-dev.20260227172006

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.
Files changed (52) hide show
  1. package/dist/compiler/ast-parser.d.ts +48 -0
  2. package/dist/compiler/ast-parser.js +54 -9
  3. package/dist/compiler/compiler.d.ts +9 -0
  4. package/dist/compiler/compiler.js +45 -2
  5. package/dist/compiler/dom-compiler.d.ts +7 -0
  6. package/dist/compiler/dom-compiler.js +90 -33
  7. package/dist/compiler/script-compiler.d.ts +11 -0
  8. package/dist/compiler/script-compiler.js +108 -116
  9. package/dist/compiler/sfc-parser.d.ts +21 -0
  10. package/dist/compiler/ssr-compiler.d.ts +7 -0
  11. package/dist/compiler/ssr-compiler.js +142 -0
  12. package/dist/compiler/style-compiler.d.ts +8 -0
  13. package/dist/compiler/template-compiler.d.ts +8 -0
  14. package/dist/components/bloch-sphere.js +9 -3
  15. package/dist/components/infinity-list.js +7 -1
  16. package/dist/core/component.js +66 -15
  17. package/dist/core/hooks.js +53 -29
  18. package/dist/core/quantum.js +30 -17
  19. package/dist/core/query.js +11 -6
  20. package/dist/core/reactive.js +88 -7
  21. package/dist/core/renderer.js +9 -8
  22. package/dist/core/ssr.js +50 -0
  23. package/dist/core/surge.js +7 -2
  24. package/dist/core/vault.js +9 -5
  25. package/dist/index.js +63 -27
  26. package/dist/mulan.esm.js +1933 -1626
  27. package/dist/mulan.esm.js.map +1 -1
  28. package/dist/mulan.js +1890 -1590
  29. package/dist/mulan.js.map +1 -1
  30. package/dist/router/index.js +17 -10
  31. package/dist/security/sanitizer.js +5 -1
  32. package/dist/store/index.js +9 -5
  33. package/dist/types/ast-parser.d.ts +2 -0
  34. package/dist/types/compiler/ast-parser.d.ts +2 -0
  35. package/dist/types/compiler/compiler.d.ts +1 -0
  36. package/dist/types/compiler/ssr-compiler.d.ts +7 -0
  37. package/dist/types/compiler.d.ts +1 -0
  38. package/dist/types/components/bloch-sphere.d.ts +3 -1
  39. package/dist/types/components/infinity-list.d.ts +3 -1
  40. package/dist/types/core/component.d.ts +14 -0
  41. package/dist/types/core/reactive.d.ts +5 -1
  42. package/dist/types/core/renderer.d.ts +0 -1
  43. package/dist/types/core/ssr.d.ts +9 -0
  44. package/dist/types/index.d.ts +1 -0
  45. package/dist/types/ssr-compiler.d.ts +7 -0
  46. package/package.json +1 -1
  47. package/src/compiler/ast-parser.ts +62 -10
  48. package/src/compiler/compiler.ts +46 -1
  49. package/src/compiler/dom-compiler.ts +100 -34
  50. package/src/compiler/script-compiler.ts +117 -126
  51. package/src/compiler/ssr-compiler.ts +157 -0
  52. package/src/loader/index.js +12 -19
@@ -27,21 +27,27 @@ export function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptComp
27
27
  let uidRef = { current: 0 };
28
28
  const getUid = () => `_el${uidRef.current++}`;
29
29
 
30
+ const hoists: string[] = [];
31
+ const getHoistId = () => `_hoist${hoists.length}`;
32
+
30
33
  // We generate a DocumentFragment at the root
31
34
  let codeChunks: string[] = [];
32
- codeChunks.push(`const _frag = document.createDocumentFragment();`);
35
+ codeChunks.push(`const _frag = this._recoveryMode ? null : document.createDocumentFragment();`);
33
36
 
34
37
  // Generate code for all top-level children and append them to the fragment
35
38
  ast.children.forEach(child => {
36
- const rootId = generateDOMInstruction(child, codeChunks, getUid, uidRef, scriptResult.bindings || [], []);
39
+ const rootId = generateDOMInstruction(child, codeChunks, getUid, getHoistId, hoists, uidRef, scriptResult.bindings || [], []);
37
40
  if (rootId) {
38
- codeChunks.push(`_frag.appendChild(${rootId});`);
41
+ codeChunks.push(`if (_frag) _frag.appendChild(${rootId});`);
39
42
  }
40
43
  });
41
44
 
42
45
  const bodyFn = codeChunks.join('\n ');
46
+ const hoistsFn = hoists.join('\n ');
43
47
 
44
- const renderFn = `function render() {
48
+ const renderFn = `
49
+ ${hoistsFn}
50
+ function render() {
45
51
  const _s = (v) => (v && typeof v === 'object' && 'value' in v) ? v.value : v;
46
52
  const _h = (v, raw) => {
47
53
  const val = _s(v);
@@ -65,18 +71,81 @@ export function compileToDOM(descriptor: SFCDescriptor, scriptResult: ScriptComp
65
71
  }
66
72
 
67
73
  // --- Code Generator (AST -> Instructions) ---
74
+ function renderStaticHTML(node: Node): string {
75
+ if (node.type === 'Text') {
76
+ const text = node as TextNode;
77
+ // Escape HTML for innerHTML injection
78
+ return htmlEscape(text.content);
79
+ }
80
+ if (node.type === 'Element') {
81
+ const el = node as ElementNode;
82
+ let html = `<${el.tag}`;
83
+ for (const key in el.props) {
84
+ html += ` ${key}="${htmlEscape(el.props[key])}"`;
85
+ }
86
+
87
+ const isSelfClosing = ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(el.tag.toLowerCase());
88
+ if (isSelfClosing) {
89
+ html += '/>';
90
+ return html;
91
+ }
92
+
93
+ html += '>';
94
+ for (const child of el.children) {
95
+ html += renderStaticHTML(child);
96
+ }
97
+ html += `</${el.tag}>`;
98
+ return html;
99
+ }
100
+ return '';
101
+ }
102
+
103
+ function htmlEscape(str: string): string {
104
+ return str.replace(/&/g, '&amp;')
105
+ .replace(/</g, '&lt;')
106
+ .replace(/>/g, '&gt;')
107
+ .replace(/"/g, '&quot;')
108
+ .replace(/'/g, '&#039;');
109
+ }
110
+
111
+ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => string, getHoistId: () => string, hoists: string[], uidRef: { current: number }, bindings: string[], localScope: string[]): string | null {
112
+
113
+ // --- STATIC NODE HOISTING (THE COMPILER INTELLIGENCE) ---
114
+ // If the node and ALL its children contain ZERO reactivity, we hoist it.
115
+ if (node.type === 'Element' && node.isStatic) {
116
+ // We only hoist elements that have children or attributes to save bytes,
117
+ // a simple empty <div> might be faster to just createElement.
118
+ const el = node as ElementNode;
119
+ if (el.tag !== 'fragment' && (el.children.length > 0 || Object.keys(el.props).length > 0)) {
120
+ const hoistId = getHoistId();
121
+ const rawHtml = renderStaticHTML(node);
122
+
123
+ // Generate the hoisted template ONCE outside the render function
124
+ hoists.push(`const ${hoistId} = (function() {
125
+ const t = document.createElement("template");
126
+ t.innerHTML = \`${rawHtml}\`;
127
+ return t.content.firstChild;
128
+ })();`);
129
+
130
+ // Inside render function, just clone it (O(1) creation cost)
131
+ const id = getUid();
132
+ chunks.push(`const ${id} = ${hoistId}.cloneNode(true);`);
133
+ return id;
134
+ }
135
+ }
68
136
 
69
- function generateDOMInstruction(node: Node, chunks: string[], getUid: () => string, uidRef: { current: number }, bindings: string[], localScope: string[]): string | null {
70
137
  if (node.type === 'Text') {
71
138
  const text = node as TextNode;
72
139
  const id = getUid();
140
+ const numericId = uidRef.current - 1; // Not used for text nodes in SSR but kept for parity if needed
141
+
73
142
  // Native template literal interpolation
74
143
  if (text.content.includes('${')) {
75
- chunks.push(`const ${id} = document.createTextNode("");`);
144
+ chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode("");`);
76
145
  // Wrap in effect for reactivity
77
- chunks.push(`this._bindEffect(() => { ${id}.textContent = \`${text.content}\`; }, ${id});`);
146
+ chunks.push(`this._bindEffect(() => { if (!${id}) return; ${id}.textContent = \`${text.content}\`; }, ${id});`);
78
147
  } else {
79
- chunks.push(`const ${id} = document.createTextNode(${JSON.stringify(text.content)});`);
148
+ chunks.push(`const ${id} = this._recoveryMode ? null : document.createTextNode(${JSON.stringify(text.content)});`);
80
149
  }
81
150
  return id;
82
151
  }
@@ -92,10 +161,10 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
92
161
 
93
162
  if (node.type === 'Element') {
94
163
  const element = node as ElementNode;
95
- const id = getUid();
96
164
 
97
165
  // Handle mu-if (Phase 4 - Dynamic Fragments)
98
166
  if (element.directives.vIf) {
167
+ const id = getUid();
99
168
  const condition = `_s(${processBindings(element.directives.vIf, bindings, localScope)})`;
100
169
 
101
170
  // 1. Create the anchor Comment node where the block belongs
@@ -104,7 +173,7 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
104
173
  // 2. Generate the inner block rendering function
105
174
  const blockId = `_if_${uidRef.current++}`;
106
175
  const blockChunks: string[] = [];
107
- blockChunks.push(`const ${blockId}_frag = document.createDocumentFragment();`);
176
+ blockChunks.push(`const ${blockId}_frag = this._recoveryMode ? null : document.createDocumentFragment();`);
108
177
  blockChunks.push(`const _block_effects = [];`);
109
178
 
110
179
  // 2a. Override bindings for inner effects
@@ -113,10 +182,10 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
113
182
 
114
183
  // 2b. Generate the actual block
115
184
  const elementWithoutIf = { ...element, directives: { ...element.directives, vIf: undefined } };
116
- const clonedChildId = generateDOMInstruction(elementWithoutIf as ElementNode, blockChunks, getUid, uidRef, bindings, localScope);
185
+ const clonedChildId = generateDOMInstruction(elementWithoutIf as ElementNode, blockChunks, getUid, getHoistId, hoists, uidRef, bindings, localScope);
117
186
 
118
187
  if (clonedChildId) {
119
- blockChunks.push(`${blockId}_frag.appendChild(${clonedChildId});`);
188
+ blockChunks.push(`if (${blockId}_frag && ${clonedChildId} && !this._recoveryMode) ${blockId}_frag.appendChild(${clonedChildId});`);
120
189
  }
121
190
 
122
191
  for (let i = originalLength; i < blockChunks.length; i++) {
@@ -140,32 +209,28 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
140
209
  const boundList = `_s(${processBindings(list, bindings, localScope)})`;
141
210
 
142
211
  // 1. Create the anchor Comment node where the list begins
143
- chunks.push(`const ${id} = document.createComment("mu-for:${list}");`);
212
+ const id = getUid();
213
+ chunks.push(`const ${id} = this._recoveryMode ? null : document.createComment("mu-for:${list}");`);
144
214
 
145
215
  // 2. Generate the inner row rendering function
146
216
  const rowChildScope = [...localScope, item];
147
217
  const rowId = `_row_${uidRef.current++}`;
148
218
  const rowChunks: string[] = [];
149
- rowChunks.push(`const ${rowId}_frag = document.createDocumentFragment();`);
219
+ rowChunks.push(`const ${rowId}_frag = this._recoveryMode ? null : document.createDocumentFragment();`);
150
220
  rowChunks.push(`const _row_effects = [];`);
151
221
 
152
222
  // 2a. Override the main chunk array temporarily to capture inner bindings
153
- const originalBindEffect = 'this._bindEffect';
154
- // We use a local array to capture effects created *inside* the row, so we can clean them up if the row is removed
155
223
  const localBindMacro = `(fn, targetNode) => { const stop = this._bindEffect(fn, targetNode); _row_effects.push(stop); return stop; }`;
156
224
 
157
- // In the compiler, when traversing children of a mu-for, we need to instruct _bindEffect calls to use the localMacro
158
- // For simplicity in this AST generator, we use a string replacement trick on the generated chunks for this subtree
159
-
160
225
  let originalLength = rowChunks.length;
161
226
 
162
227
  // 2b. Generate the actual repeating element (the template root of the mu-for)
163
228
  // We strip the vFor directive so it doesn't recurse infinitely
164
229
  const elementWithoutFor = { ...element, directives: { ...element.directives, vFor: undefined } };
165
- const clonedChildId = generateDOMInstruction(elementWithoutFor as ElementNode, rowChunks, getUid, uidRef, bindings, rowChildScope);
230
+ const clonedChildId = generateDOMInstruction(elementWithoutFor as ElementNode, rowChunks, getUid, getHoistId, hoists, uidRef, bindings, rowChildScope);
166
231
 
167
232
  if (clonedChildId) {
168
- rowChunks.push(`${rowId}_frag.appendChild(${clonedChildId});`);
233
+ rowChunks.push(`if (${rowId}_frag && ${clonedChildId} && !this._recoveryMode) ${rowId}_frag.appendChild(${clonedChildId});`);
169
234
  }
170
235
 
171
236
  // Note: In a robust compiler, we'd pass an `effectRegistry` flag. Here we just regex patch the internal effects.
@@ -178,38 +243,39 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
178
243
  const renderRowFn = `(${item}, _index) => {\n ${rowChunks.join('\n ')}\n }`;
179
244
 
180
245
  // 3. Register the macro-effect that calls the Reconciler when the array changes
181
- // The reconciler will only create new rows or move them natively
182
246
  const listIdHash = `list_${Math.random().toString(36).substr(2, 6)}`;
183
- // We assume mu-key is passed as a prop, e.g., mu-key="id"
184
247
  let keyProp = 'null';
185
248
  if (element.props['mu-key']) {
186
249
  keyProp = `"${element.props['mu-key']}"`;
187
250
  }
188
251
 
189
- chunks.push(`this._bindEffect(() => { this._reconcileList("${listIdHash}", ${id}, ${boundList}, ${keyProp}, ${renderRowFn}); }, ${id});`);
252
+ chunks.push(`this._bindEffect(() => { if (${id}) this._reconcileList("${listIdHash}", ${id}, ${boundList}, ${keyProp}, ${renderRowFn}); }, ${id});`);
190
253
 
191
254
  return id;
192
255
  }
193
256
 
194
- chunks.push(`const ${id} = document.createElement("${element.tag}");`);
257
+ const numericId = uidRef.current;
258
+ const id = getUid();
259
+ 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
+
195
261
 
196
262
  // Handle standard properties and classes
197
263
  for (const [key, value] of Object.entries(element.props)) {
198
264
  if (key === 'class') {
199
265
  if (value.includes('${')) {
200
- chunks.push(`this._bindEffect(() => { ${id}.className = \`${value}\`; }, ${id});`);
266
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}.className = \`${value}\`; }, ${id});`);
201
267
  } else {
202
- chunks.push(`${id}.className = ${JSON.stringify(value)};`);
268
+ chunks.push(`if (${id}) ${id}.className = ${JSON.stringify(value)};`);
203
269
  }
204
270
  } else if (key === 'id') {
205
- chunks.push(`${id}.id = ${JSON.stringify(value)};`);
271
+ chunks.push(`if (${id}) ${id}.id = ${JSON.stringify(value)};`);
206
272
  } else if (key === 'data-mu-id') {
207
273
  // Ignore internal string compiler metadata
208
274
  } else {
209
275
  if (value.includes('${')) {
210
- chunks.push(`this._bindEffect(() => { ${id}.setAttribute("${key}", \`${value}\`); }, ${id});`);
276
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}.setAttribute("${key}", \`${value}\`); }, ${id});`);
211
277
  } else {
212
- chunks.push(`${id}.setAttribute("${key}", ${JSON.stringify(value)});`);
278
+ chunks.push(`if (${id}) ${id}.setAttribute("${key}", ${JSON.stringify(value)});`);
213
279
  }
214
280
  }
215
281
  }
@@ -226,9 +292,9 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
226
292
  if ((element as any)._domBindings) {
227
293
  for (const b of (element as any)._domBindings) {
228
294
  if (b.type === 'prop') {
229
- chunks.push(`this._bindEffect(() => { ${id}['${b.name}'] = ${b.expr}; }, ${id});`);
295
+ chunks.push(`this._bindEffect(() => { if (${id}) ${id}['${b.name}'] = ${b.expr}; }, ${id});`);
230
296
  } else if (b.type === 'event') {
231
- chunks.push(`${id}.addEventListener('${b.name}', (${b.expr}).bind(this));`);
297
+ chunks.push(`if (${id}) ${id}.addEventListener('${b.name}', (${b.expr}).bind(this));`);
232
298
  }
233
299
  }
234
300
  }
@@ -236,9 +302,9 @@ function generateDOMInstruction(node: Node, chunks: string[], getUid: () => stri
236
302
  // Recursively generate children
237
303
  const childScope = [...localScope];
238
304
  element.children.forEach(child => {
239
- const childId = generateDOMInstruction(child, chunks, getUid, uidRef, bindings, childScope);
305
+ const childId = generateDOMInstruction(child, chunks, getUid, getHoistId, hoists, uidRef, bindings, childScope);
240
306
  if (childId) {
241
- chunks.push(`${id}.appendChild(${childId});`);
307
+ chunks.push(`if (${id} && ${childId} && !this._recoveryMode) ${id}.appendChild(${childId});`);
242
308
  }
243
309
  });
244
310
 
@@ -190,132 +190,125 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
190
190
 
191
191
  const finalCode = preamble + scriptBody + postamble;
192
192
 
193
- if (isTs) {
194
- // Transpile extracted TS code to JS
195
- const result = ts.transpileModule(finalCode, {
196
- compilerOptions: {
197
- module: ts.ModuleKind.ESNext,
198
- target: ts.ScriptTarget.ES2019,
199
- sourceMap: true, // ENABLE SOURCE MAPS
200
- inlineSources: true,
201
- sourceRoot: '/',
202
- },
203
- fileName: filename // We ask TS to map to filename, but it maps 'finalCode' -> 'filename'
204
- });
205
-
206
- // Fix: Remove the //# sourceMappingURL= comment added by TS
207
- const codeWithoutMapComment = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
208
-
209
- // Enhance Source Map to show ORIGINAL .mujs content or EXTERNAL content
210
- let finalMap = result.sourceMapText;
211
- if (finalMap) {
212
- try {
213
- // Determine offsets
214
- const preambleLines = preamble.split('\n').length - 1;
215
- // scriptBody starts at line `preambleLines` (0-indexed) in finalCode.
216
-
217
- // Determine where script starts
218
- // If External: Start at 0
219
- // If Inline: Start at script.start line
220
- let linesBeforeScript = 0;
221
- let originalSource = content;
222
-
223
- if (!isExternal) {
224
- originalSource = descriptor.source;
225
- linesBeforeScript = originalSource.substring(0, script.start).split('\n').length - 1;
226
- }
193
+ // Standardize to CommonJS for SSR/Node compatibility
194
+ // Transpile extracted code to JS (handles TS and ESM -> CJS)
195
+ const result = ts.transpileModule(finalCode, {
196
+ compilerOptions: {
197
+ module: ts.ModuleKind.CommonJS,
198
+ target: ts.ScriptTarget.ES2019,
199
+ sourceMap: true,
200
+ inlineSources: true,
201
+ sourceRoot: '/',
202
+ },
203
+ fileName: filename
204
+ });
227
205
 
228
- const consumer = new SourceMapConsumer(JSON.parse(finalMap));
229
- const generator = new SourceMapGenerator({
230
- file: filename,
231
- sourceRoot: ''
232
- });
206
+ const codeWithoutMapComment = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
207
+ let finalMap = result.sourceMapText;
233
208
 
234
- // Determine relative path for source
235
- let relativePath = filename.split(/[/\\]/).pop() || 'unknown.mujs'; // Default to basename
236
-
237
- if (isExternal) {
238
- // For external files, we want Source Map to point to the actual TS file
239
- // externalPath is absolute. Make it relative to project root or something reasonable?
240
- // Webpack often likes 'webpack:///[resource-path]'
241
- // We'll try to map it so it looks like a separate file
242
- const normalized = externalPath.replace(/\\/g, '/');
243
- const srcIdx = normalized.indexOf('/src/');
244
- if (srcIdx !== -1) {
245
- relativePath = 'webpack:///' + normalized.substring(srcIdx + 1); // e.g. webpack:///src/logic.ts
246
- } else {
247
- relativePath = 'webpack:///' + path.basename(externalPath);
248
- }
209
+ if (isTs && finalMap) {
210
+ // ... (source map logic)
211
+ }
212
+ if (finalMap) {
213
+ try {
214
+ // Determine offsets
215
+ const preambleLines = preamble.split('\n').length - 1;
216
+ // scriptBody starts at line `preambleLines` (0-indexed) in finalCode.
217
+
218
+ // Determine where script starts
219
+ // If External: Start at 0
220
+ // If Inline: Start at script.start line
221
+ let linesBeforeScript = 0;
222
+ let originalSource = content;
223
+
224
+ if (!isExternal) {
225
+ originalSource = descriptor.source;
226
+ linesBeforeScript = originalSource.substring(0, script.start).split('\n').length - 1;
227
+ }
228
+
229
+ const consumer = new SourceMapConsumer(JSON.parse(finalMap));
230
+ const generator = new SourceMapGenerator({
231
+ file: filename,
232
+ sourceRoot: ''
233
+ });
234
+
235
+ // Determine relative path for source
236
+ let relativePath = filename.split(/[/\\]/).pop() || 'unknown.mujs'; // Default to basename
237
+
238
+ if (isExternal) {
239
+ // For external files, we want Source Map to point to the actual TS file
240
+ // externalPath is absolute. Make it relative to project root or something reasonable?
241
+ // Webpack often likes 'webpack:///[resource-path]'
242
+ // We'll try to map it so it looks like a separate file
243
+ const normalized = externalPath.replace(/\\/g, '/');
244
+ const srcIdx = normalized.indexOf('/src/');
245
+ if (srcIdx !== -1) {
246
+ relativePath = 'webpack:///' + normalized.substring(srcIdx + 1); // e.g. webpack:///src/logic.ts
249
247
  } else {
250
- // Inline: Point to .mujs file
251
- const normalized = filename.replace(/\\/g, '/');
252
- const srcIdx = normalized.indexOf('/src/');
253
- if (srcIdx !== -1) {
254
- relativePath = 'webpack:///' + normalized.substring(srcIdx + 1); // e.g. webpack:///src/App.mujs
255
- }
248
+ relativePath = 'webpack:///' + path.basename(externalPath);
249
+ }
250
+ } else {
251
+ // Inline: Point to .mujs file
252
+ const normalized = filename.replace(/\\/g, '/');
253
+ const srcIdx = normalized.indexOf('/src/');
254
+ if (srcIdx !== -1) {
255
+ relativePath = 'webpack:///' + normalized.substring(srcIdx + 1); // e.g. webpack:///src/App.mujs
256
256
  }
257
+ }
257
258
 
258
- // If inline, we set source content of .mujs (the whole file)
259
- // If external, we set source content of the .ts file
260
- generator.setSourceContent(relativePath, originalSource);
261
-
262
- (consumer as any).eachMapping((m: any) => {
263
- if (m.originalLine === null) return;
264
-
265
- // m.originalLine is the line in `finalCode` (1-based because SourceMapConsumer uses 1-based)
266
- // We need to convert it to line in `.mujs`.
267
-
268
- // Check if this line belongs to scriptBody
269
- // scriptBody spans from (preambleLines + 1) to (preambleLines + scriptBodyLines)
270
- const lineInFinalCode = m.originalLine;
271
-
272
- // Check if strictly inside body
273
- if (lineInFinalCode > preambleLines) {
274
- // Calculate offset inside scriptBody
275
- const lineOffsetInBody = lineInFinalCode - preambleLines; // 1-based offset (1st line of body is 1)
276
-
277
- // Calculate original line
278
- // If External: lineOffsetInBody is the line number
279
- // If Inline: linesBeforeScript + lineOffsetInBody
280
- const originalLine = linesBeforeScript + lineOffsetInBody;
281
-
282
- // Add mapping
283
- generator.addMapping({
284
- generated: {
285
- line: m.generatedLine,
286
- column: m.generatedColumn
287
- },
288
- original: {
289
- line: originalLine,
290
- column: m.originalColumn // Column should be preserved if we didn't change indentation
291
- },
292
- source: relativePath,
293
- name: m.name
294
- });
295
- }
296
- });
259
+ // If inline, we set source content of .mujs (the whole file)
260
+ // If external, we set source content of the .ts file
261
+ generator.setSourceContent(relativePath, originalSource);
297
262
 
298
- finalMap = generator.toString();
299
- // consumer.destroy() not needed in 0.6.x
263
+ (consumer as any).eachMapping((m: any) => {
264
+ if (m.originalLine === null) return;
300
265
 
301
- } catch (e) {
302
- console.warn('[MulanJS] Failed to patch source map:', e);
303
- }
304
- }
266
+ // m.originalLine is the line in `finalCode` (1-based because SourceMapConsumer uses 1-based)
267
+ // We need to convert it to line in `.mujs`.
268
+
269
+ // Check if this line belongs to scriptBody
270
+ // scriptBody spans from (preambleLines + 1) to (preambleLines + scriptBodyLines)
271
+ const lineInFinalCode = m.originalLine;
272
+
273
+ // Check if strictly inside body
274
+ if (lineInFinalCode > preambleLines) {
275
+ // Calculate offset inside scriptBody
276
+ const lineOffsetInBody = lineInFinalCode - preambleLines; // 1-based offset (1st line of body is 1)
277
+
278
+ // Calculate original line
279
+ // If External: lineOffsetInBody is the line number
280
+ // If Inline: linesBeforeScript + lineOffsetInBody
281
+ const originalLine = linesBeforeScript + lineOffsetInBody;
282
+
283
+ // Add mapping
284
+ generator.addMapping({
285
+ generated: {
286
+ line: m.generatedLine,
287
+ column: m.generatedColumn
288
+ },
289
+ original: {
290
+ line: originalLine,
291
+ column: m.originalColumn // Column should be preserved if we didn't change indentation
292
+ },
293
+ source: relativePath,
294
+ name: m.name
295
+ });
296
+ }
297
+ });
298
+
299
+ finalMap = generator.toString();
300
+ // consumer.destroy() not needed in 0.6.x
305
301
 
306
- return {
307
- code: codeWithoutMapComment,
308
- bindings,
309
- errors: [],
310
- map: finalMap // Return the enhanced map
302
+ } catch (e) {
303
+ console.warn('[MulanJS] Failed to patch source map:', e);
311
304
  }
312
305
  }
313
306
 
314
307
  return {
315
- code: finalCode,
308
+ code: codeWithoutMapComment,
316
309
  bindings,
317
310
  errors: [],
318
- map: undefined
311
+ map: finalMap // Return the enhanced map
319
312
  };
320
313
  }
321
314
 
@@ -334,20 +327,18 @@ async function compileStandardScript(
334
327
  let finalMap: string | undefined;
335
328
  let code = content;
336
329
 
337
- // Transpile if TS
338
- if (externalPath.endsWith('.ts') || script.attrs.lang === 'ts') {
339
- const result = ts.transpileModule(content, {
340
- compilerOptions: {
341
- module: ts.ModuleKind.ESNext,
342
- target: ts.ScriptTarget.ES2019,
343
- sourceMap: true,
344
- inlineSources: true,
345
- },
346
- fileName: filename
347
- });
348
- code = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
349
- finalMap = result.sourceMapText;
350
- }
330
+ // Standardize to CommonJS for SSR/Node compatibility
331
+ const result = ts.transpileModule(content, {
332
+ compilerOptions: {
333
+ module: ts.ModuleKind.CommonJS,
334
+ target: ts.ScriptTarget.ES2019,
335
+ sourceMap: true,
336
+ inlineSources: true,
337
+ },
338
+ fileName: filename
339
+ });
340
+ code = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
341
+ finalMap = result.sourceMapText;
351
342
 
352
343
  // Adjust Source Map
353
344
  if (finalMap) {