@mulanjs/mulanjs 1.0.1-dev.20260227173253 → 1.0.1-dev.20260227175607
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/components/bloch-sphere.js +1 -1
- package/dist/mulan.esm.js +1 -1
- package/dist/mulan.esm.js.map +1 -1
- package/dist/mulan.js +1 -1
- package/dist/mulan.js.map +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
|
@@ -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
|