@mulanjs/mulanjs 1.0.1-dev.20260227135307 → 1.0.1-dev.20260227173253
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.d.ts +48 -0
- package/dist/compiler/ast-parser.js +54 -9
- package/dist/compiler/compiler.d.ts +9 -0
- package/dist/compiler/compiler.js +45 -2
- package/dist/compiler/dom-compiler.d.ts +7 -0
- package/dist/compiler/dom-compiler.js +90 -33
- package/dist/compiler/script-compiler.d.ts +11 -0
- package/dist/compiler/script-compiler.js +108 -116
- package/dist/compiler/sfc-parser.d.ts +21 -0
- package/dist/compiler/ssr-compiler.d.ts +7 -0
- package/dist/compiler/ssr-compiler.js +142 -0
- package/dist/compiler/style-compiler.d.ts +8 -0
- package/dist/compiler/template-compiler.d.ts +8 -0
- package/dist/components/bloch-sphere.js +9 -3
- package/dist/components/infinity-list.js +7 -1
- package/dist/core/component.js +66 -15
- package/dist/core/hooks.js +53 -29
- package/dist/core/quantum.js +30 -17
- package/dist/core/query.js +11 -6
- package/dist/core/reactive.js +88 -7
- package/dist/core/renderer.js +9 -8
- package/dist/core/ssr.js +50 -0
- package/dist/core/surge.js +7 -2
- package/dist/core/vault.js +9 -5
- package/dist/index.js +63 -27
- package/dist/mulan.esm.js +187 -19
- package/dist/mulan.esm.js.map +1 -1
- package/dist/mulan.js +1890 -1590
- package/dist/mulan.js.map +1 -1
- package/dist/router/index.js +17 -10
- package/dist/security/sanitizer.js +5 -1
- package/dist/store/index.js +9 -5
- package/dist/types/ast-parser.d.ts +2 -0
- package/dist/types/compiler/ast-parser.d.ts +2 -0
- package/dist/types/compiler/compiler.d.ts +1 -0
- package/dist/types/compiler/ssr-compiler.d.ts +7 -0
- package/dist/types/compiler.d.ts +1 -0
- package/dist/types/components/bloch-sphere.d.ts +3 -1
- package/dist/types/components/infinity-list.d.ts +3 -1
- package/dist/types/core/component.d.ts +14 -0
- package/dist/types/core/reactive.d.ts +5 -1
- package/dist/types/core/renderer.d.ts +0 -1
- package/dist/types/core/ssr.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/ssr-compiler.d.ts +7 -0
- package/package.json +1 -1
- package/src/compiler/ast-parser.ts +62 -10
- package/src/compiler/compiler.ts +46 -1
- package/src/compiler/dom-compiler.ts +100 -34
- package/src/compiler/script-compiler.ts +117 -126
- package/src/compiler/ssr-compiler.ts +157 -0
- 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 = `
|
|
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, '&')
|
|
105
|
+
.replace(/</g, '<')
|
|
106
|
+
.replace(/>/g, '>')
|
|
107
|
+
.replace(/"/g, '"')
|
|
108
|
+
.replace(/'/g, ''');
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
268
|
+
chunks.push(`if (${id}) ${id}.className = ${JSON.stringify(value)};`);
|
|
203
269
|
}
|
|
204
270
|
} else if (key === 'id') {
|
|
205
|
-
chunks.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
file: filename,
|
|
231
|
-
sourceRoot: ''
|
|
232
|
-
});
|
|
206
|
+
const codeWithoutMapComment = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
|
|
207
|
+
let finalMap = result.sourceMapText;
|
|
233
208
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
299
|
-
|
|
263
|
+
(consumer as any).eachMapping((m: any) => {
|
|
264
|
+
if (m.originalLine === null) return;
|
|
300
265
|
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
307
|
-
|
|
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:
|
|
308
|
+
code: codeWithoutMapComment,
|
|
316
309
|
bindings,
|
|
317
310
|
errors: [],
|
|
318
|
-
map:
|
|
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
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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) {
|