@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.
- package/dist/compiler/ast-parser.js +57 -5
- package/dist/compiler/compiler.js +13 -6
- package/dist/compiler/dom-compiler.js +30 -6
- package/dist/compiler/script-compiler.js +22 -2
- package/dist/compiler/sfc-parser.js +1 -1
- package/dist/types/ast-parser.d.ts +4 -0
- package/dist/types/compiler/ast-parser.d.ts +4 -0
- package/dist/types/compiler/dom-compiler.d.ts +1 -0
- package/dist/types/dom-compiler.d.ts +1 -0
- package/package.json +1 -1
- package/src/compiler/ast-parser.ts +63 -5
- package/src/compiler/compiler.ts +12 -7
- package/src/compiler/dom-compiler.ts +33 -7
- package/src/compiler/script-compiler.ts +22 -1
- package/src/compiler/sfc-parser.ts +1 -1
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
scriptCode = scriptCode.replace(
|
|
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 (
|
|
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
|
|
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, '"')
|
|
89
94
|
.replace(/'/g, ''');
|
|
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
|
-
|
|
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
|
|
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
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/src/compiler/compiler.ts
CHANGED
|
@@ -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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
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
|
|
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, ''');
|
|
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)
|
|
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
|
|
94
|
+
content: content,
|
|
95
95
|
attrs,
|
|
96
96
|
start: start,
|
|
97
97
|
end: contentEnd + closeTag.length
|