@mulanjs/mulanjs 1.0.1-dev.20260219164037 → 1.0.1-dev.20260219183205
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/README.md +1 -0
- package/dist/compiler/compiler.js +81 -4
- package/dist/compiler/script-compiler.js +158 -21
- package/dist/compiler/style-compiler.js +61 -4
- package/dist/compiler/template-compiler.js +44 -4
- package/dist/types/compiler/compiler.d.ts +2 -1
- package/dist/types/compiler/script-compiler.d.ts +4 -1
- package/dist/types/compiler/style-compiler.d.ts +2 -1
- package/dist/types/compiler/template-compiler.d.ts +1 -0
- package/dist/types/compiler.d.ts +2 -1
- package/dist/types/script-compiler.d.ts +4 -1
- package/dist/types/style-compiler.d.ts +2 -1
- package/dist/types/template-compiler.d.ts +1 -0
- package/package.json +1 -1
- package/src/compiler/compiler.ts +90 -5
- package/src/compiler/script-compiler.ts +189 -21
- package/src/compiler/style-compiler.ts +28 -4
- package/src/compiler/template-compiler.ts +52 -4
- package/src/loader/index.js +16 -41
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ MulanJS is a high-performance, next-generation web framework designed to bridge
|
|
|
16
16
|
> Coming soon!
|
|
17
17
|
|
|
18
18
|
### 🚧 Development Release (Bleeding Edge)
|
|
19
|
+
**Current Version**: `1.0.1-dev.20260219182444`
|
|
19
20
|
|
|
20
21
|
Want to try the latest features before they are stable? Use the development build.
|
|
21
22
|
|
|
@@ -5,11 +5,12 @@ const sfc_parser_1 = require("./sfc-parser");
|
|
|
5
5
|
const script_compiler_1 = require("./script-compiler");
|
|
6
6
|
const template_compiler_1 = require("./template-compiler");
|
|
7
7
|
const style_compiler_1 = require("./style-compiler");
|
|
8
|
-
|
|
8
|
+
const source_map_1 = require("source-map");
|
|
9
|
+
async function compileSFC(source, filename, options) {
|
|
9
10
|
// 1. Parse
|
|
10
11
|
const descriptor = (0, sfc_parser_1.parseMUJS)(source, filename);
|
|
11
12
|
// 2. Script
|
|
12
|
-
const scriptResult = await (0, script_compiler_1.compileScript)(descriptor);
|
|
13
|
+
const scriptResult = await (0, script_compiler_1.compileScript)(descriptor, options);
|
|
13
14
|
// Replace export default to capture the component
|
|
14
15
|
// If the script already has 'export default', we intercept it.
|
|
15
16
|
let scriptCode = scriptResult.code.replace('export default', 'const __component__ =');
|
|
@@ -17,9 +18,85 @@ async function compileSFC(source, filename) {
|
|
|
17
18
|
scriptCode = scriptCode.replace(/export\s+default/, 'const __component__ =');
|
|
18
19
|
}
|
|
19
20
|
// 3. Style
|
|
20
|
-
const styleResult = (0, style_compiler_1.compileStyle)(descriptor, filename);
|
|
21
|
+
const styleResult = (0, style_compiler_1.compileStyle)(descriptor, filename, options);
|
|
21
22
|
// 4. Template
|
|
22
23
|
const templateResult = (0, template_compiler_1.compileTemplate)(descriptor, scriptResult, styleResult.scopedId);
|
|
24
|
+
// Calculate offsets for source map merging
|
|
25
|
+
// 1. Script Code
|
|
26
|
+
// 2. Padding (newlines)
|
|
27
|
+
// 3. Template Code
|
|
28
|
+
// We need to count lines in scriptCode to know where template code starts in the final file
|
|
29
|
+
const scriptLineCount = scriptCode.split(/\r?\n/).length;
|
|
30
|
+
const paddingLines = 2; // Two newlines between script and template
|
|
31
|
+
const templateOffset = scriptLineCount + paddingLines;
|
|
32
|
+
let mergedMap;
|
|
33
|
+
// Merge Maps
|
|
34
|
+
if (filename) {
|
|
35
|
+
const generator = new source_map_1.SourceMapGenerator({
|
|
36
|
+
file: filename + '.js',
|
|
37
|
+
});
|
|
38
|
+
// 1. Apply Script Map
|
|
39
|
+
if (scriptResult.map) {
|
|
40
|
+
const scriptConsumer = await new source_map_1.SourceMapConsumer(JSON.parse(scriptResult.map));
|
|
41
|
+
scriptConsumer.eachMapping(mapping => {
|
|
42
|
+
if (mapping.source) {
|
|
43
|
+
generator.addMapping({
|
|
44
|
+
generated: {
|
|
45
|
+
line: mapping.generatedLine, // Script is at the top, so no offset needed
|
|
46
|
+
column: mapping.generatedColumn
|
|
47
|
+
},
|
|
48
|
+
original: {
|
|
49
|
+
line: mapping.originalLine,
|
|
50
|
+
column: mapping.originalColumn
|
|
51
|
+
},
|
|
52
|
+
source: mapping.source,
|
|
53
|
+
name: mapping.name
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Copy source content from script map
|
|
58
|
+
if (scriptConsumer.sources) {
|
|
59
|
+
scriptConsumer.sources.forEach(sourceFile => {
|
|
60
|
+
const content = scriptConsumer.sourceContentFor(sourceFile);
|
|
61
|
+
if (content) {
|
|
62
|
+
generator.setSourceContent(sourceFile, content);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
scriptConsumer.destroy();
|
|
67
|
+
}
|
|
68
|
+
// 2. Apply Template Map
|
|
69
|
+
if (templateResult.map) {
|
|
70
|
+
const templateConsumer = await new source_map_1.SourceMapConsumer(JSON.parse(templateResult.map));
|
|
71
|
+
templateConsumer.eachMapping(mapping => {
|
|
72
|
+
if (mapping.source) {
|
|
73
|
+
generator.addMapping({
|
|
74
|
+
generated: {
|
|
75
|
+
line: mapping.generatedLine + templateOffset, // Shift by script length
|
|
76
|
+
column: mapping.generatedColumn
|
|
77
|
+
},
|
|
78
|
+
original: {
|
|
79
|
+
line: mapping.originalLine, // Original line in .mujs
|
|
80
|
+
column: mapping.originalColumn
|
|
81
|
+
},
|
|
82
|
+
source: mapping.source, // This should be the .mujs file
|
|
83
|
+
name: mapping.name
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
// Copy source content from template map
|
|
88
|
+
if (templateConsumer.sources) {
|
|
89
|
+
templateConsumer.sources.forEach(sourceFile => {
|
|
90
|
+
const content = templateConsumer.sourceContentFor(sourceFile);
|
|
91
|
+
if (content) {
|
|
92
|
+
generator.setSourceContent(sourceFile, content);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
templateConsumer.destroy();
|
|
97
|
+
}
|
|
98
|
+
mergedMap = generator.toString();
|
|
99
|
+
}
|
|
23
100
|
// 5. Assembly
|
|
24
101
|
const cssInjection = styleResult.css ? `
|
|
25
102
|
if (typeof document !== 'undefined') {
|
|
@@ -86,6 +163,6 @@ export default __component__;
|
|
|
86
163
|
code: finalCode,
|
|
87
164
|
css: styleResult.css,
|
|
88
165
|
errors: [...scriptResult.errors, ...templateResult.errors],
|
|
89
|
-
map: scriptResult.map //
|
|
166
|
+
map: mergedMap || scriptResult.map // Fallback to script map if merge fails
|
|
90
167
|
};
|
|
91
168
|
}
|
|
@@ -36,27 +36,55 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.compileScript = compileScript;
|
|
37
37
|
const ts = __importStar(require("typescript"));
|
|
38
38
|
const source_map_1 = require("source-map");
|
|
39
|
-
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
async function compileScript(descriptor, options) {
|
|
40
41
|
const script = descriptor.script;
|
|
41
42
|
if (!script)
|
|
42
43
|
return { code: 'export default {}', errors: [] };
|
|
44
|
+
let content = script.content;
|
|
45
|
+
let filename = descriptor.filename;
|
|
46
|
+
let isExternal = false;
|
|
47
|
+
let externalPath = '';
|
|
48
|
+
// Handle External Script
|
|
49
|
+
if (script.attrs.src) {
|
|
50
|
+
if (options && options.readFileSync) {
|
|
51
|
+
try {
|
|
52
|
+
// Resolve path relative to the SFC file
|
|
53
|
+
const sfcDir = path.dirname(descriptor.filename);
|
|
54
|
+
externalPath = path.resolve(sfcDir, script.attrs.src);
|
|
55
|
+
content = options.readFileSync(externalPath);
|
|
56
|
+
// Keep filename as .mujs for now, but we will use externalPath for source mapping of the content
|
|
57
|
+
// Actually, if we want the external file to show up as ITSELF in devtools, we should map to externalPath.
|
|
58
|
+
isExternal = true;
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return { code: 'export default {}', errors: [`Failed to read external script: ${e.message}`] };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return { code: 'export default {}', errors: [`External script found but no readFileSync option provided.`] };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
43
68
|
const isSetup = !!script.attrs.setup;
|
|
44
|
-
const isTs = script.attrs.lang === 'ts' || script.attrs.lang === 'tsx';
|
|
69
|
+
const isTs = script.attrs.lang === 'ts' || script.attrs.lang === 'tsx' || externalPath.endsWith('.ts') || externalPath.endsWith('.tsx');
|
|
45
70
|
if (isSetup) {
|
|
46
|
-
return await compileSetupScript(script, isTs, descriptor);
|
|
71
|
+
return await compileSetupScript(script, isTs, descriptor, content, filename, isExternal, externalPath);
|
|
72
|
+
}
|
|
73
|
+
else if (isTs) {
|
|
74
|
+
return await compileStandardScript(script, descriptor, content, filename, isExternal, externalPath);
|
|
47
75
|
}
|
|
48
76
|
else {
|
|
49
77
|
// Standard Options API - just pass through, assuming it has export default
|
|
50
|
-
return { code:
|
|
78
|
+
return { code: content, errors: [] };
|
|
51
79
|
}
|
|
52
80
|
}
|
|
53
81
|
// Global list of Mulan hooks for auto-imports and reactivity exemption
|
|
54
82
|
const COMMON_HOOKS = ['muState', 'onMuMount', 'onMuInit', 'onMuDestroy', 'muEffect', 'muMemo', 'muQubit', 'muGate', 'muMeasure', 'muRegister', 'muEntangle', 'persistent'];
|
|
55
|
-
async function compileSetupScript(script, isTs, descriptor) {
|
|
83
|
+
async function compileSetupScript(script, isTs, descriptor, content, filename, isExternal, externalPath) {
|
|
56
84
|
var _a;
|
|
57
85
|
// 1. Pre-process: Natural Reactivity Transformation ($ syntax)
|
|
58
86
|
// We do this BEFORE extraction so that the rest of the compiler sees standard 'muState' code.
|
|
59
|
-
const transformedContent = transformNaturalReactivity(
|
|
87
|
+
const transformedContent = transformNaturalReactivity(content, isTs);
|
|
60
88
|
// 2. Parse the TRANSFORMED code
|
|
61
89
|
const sourceFile = ts.createSourceFile('script.' + (isTs ? 'ts' : 'js'), transformedContent, ts.ScriptTarget.Latest, true);
|
|
62
90
|
const imports = [];
|
|
@@ -129,7 +157,6 @@ async function compileSetupScript(script, isTs, descriptor) {
|
|
|
129
157
|
const autoImportStmt = autoImports.length > 0
|
|
130
158
|
? `import { ${autoImports.join(', ')} } from '@mulanjs/mulanjs';`
|
|
131
159
|
: '';
|
|
132
|
-
const filename = descriptor.filename;
|
|
133
160
|
const componentName = filename ? ((_a = filename.split(/[/\\]/).pop()) === null || _a === void 0 ? void 0 : _a.split('.')[0].replace(/\W/g, '')) || 'App' : 'App';
|
|
134
161
|
const bindingString = bindings.length > 0
|
|
135
162
|
? `
|
|
@@ -171,27 +198,53 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
171
198
|
});
|
|
172
199
|
// Fix: Remove the //# sourceMappingURL= comment added by TS
|
|
173
200
|
const codeWithoutMapComment = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
|
|
174
|
-
// Enhance Source Map to show ORIGINAL .mujs content
|
|
201
|
+
// Enhance Source Map to show ORIGINAL .mujs content or EXTERNAL content
|
|
175
202
|
let finalMap = result.sourceMapText;
|
|
176
203
|
if (finalMap) {
|
|
177
204
|
try {
|
|
178
205
|
// Determine offsets
|
|
179
206
|
const preambleLines = preamble.split('\n').length - 1;
|
|
180
207
|
// scriptBody starts at line `preambleLines` (0-indexed) in finalCode.
|
|
181
|
-
// Determine where script starts
|
|
182
|
-
|
|
183
|
-
|
|
208
|
+
// Determine where script starts
|
|
209
|
+
// If External: Start at 0
|
|
210
|
+
// If Inline: Start at script.start line
|
|
211
|
+
let linesBeforeScript = 0;
|
|
212
|
+
let originalSource = content;
|
|
213
|
+
if (!isExternal) {
|
|
214
|
+
originalSource = descriptor.source;
|
|
215
|
+
linesBeforeScript = originalSource.substring(0, script.start).split('\n').length - 1;
|
|
216
|
+
}
|
|
184
217
|
const consumer = await new source_map_1.SourceMapConsumer(finalMap);
|
|
185
218
|
const generator = new source_map_1.SourceMapGenerator({
|
|
186
219
|
file: filename,
|
|
187
220
|
sourceRoot: ''
|
|
188
221
|
});
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
222
|
+
// Determine relative path for source
|
|
223
|
+
let relativePath = filename.split(/[/\\]/).pop() || 'unknown.mujs'; // Default to basename
|
|
224
|
+
if (isExternal) {
|
|
225
|
+
// For external files, we want Source Map to point to the actual TS file
|
|
226
|
+
// externalPath is absolute. Make it relative to project root or something reasonable?
|
|
227
|
+
// Webpack often likes 'webpack:///[resource-path]'
|
|
228
|
+
// We'll try to map it so it looks like a separate file
|
|
229
|
+
const normalized = externalPath.replace(/\\/g, '/');
|
|
230
|
+
const srcIdx = normalized.indexOf('/src/');
|
|
231
|
+
if (srcIdx !== -1) {
|
|
232
|
+
relativePath = 'webpack:///' + normalized.substring(srcIdx + 1); // e.g. webpack:///src/logic.ts
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
relativePath = 'webpack:///' + path.basename(externalPath);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// Inline: Point to .mujs file
|
|
240
|
+
const normalized = filename.replace(/\\/g, '/');
|
|
241
|
+
const srcIdx = normalized.indexOf('/src/');
|
|
242
|
+
if (srcIdx !== -1) {
|
|
243
|
+
relativePath = 'webpack:///' + normalized.substring(srcIdx + 1); // e.g. webpack:///src/App.mujs
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// If inline, we set source content of .mujs (the whole file)
|
|
247
|
+
// If external, we set source content of the .ts file
|
|
195
248
|
generator.setSourceContent(relativePath, originalSource);
|
|
196
249
|
consumer.eachMapping(m => {
|
|
197
250
|
if (m.originalLine === null)
|
|
@@ -205,9 +258,10 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
205
258
|
if (lineInFinalCode > preambleLines) {
|
|
206
259
|
// Calculate offset inside scriptBody
|
|
207
260
|
const lineOffsetInBody = lineInFinalCode - preambleLines; // 1-based offset (1st line of body is 1)
|
|
208
|
-
// Calculate original line
|
|
209
|
-
// If lineOffsetInBody is
|
|
210
|
-
|
|
261
|
+
// Calculate original line
|
|
262
|
+
// If External: lineOffsetInBody is the line number
|
|
263
|
+
// If Inline: linesBeforeScript + lineOffsetInBody
|
|
264
|
+
const originalLine = linesBeforeScript + lineOffsetInBody;
|
|
211
265
|
// Add mapping
|
|
212
266
|
generator.addMapping({
|
|
213
267
|
generated: {
|
|
@@ -215,7 +269,7 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
215
269
|
column: m.generatedColumn
|
|
216
270
|
},
|
|
217
271
|
original: {
|
|
218
|
-
line:
|
|
272
|
+
line: originalLine,
|
|
219
273
|
column: m.originalColumn // Column should be preserved if we didn't change indentation
|
|
220
274
|
},
|
|
221
275
|
source: relativePath,
|
|
@@ -244,6 +298,89 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
244
298
|
map: undefined
|
|
245
299
|
};
|
|
246
300
|
}
|
|
301
|
+
async function compileStandardScript(script, descriptor, content, filename, isExternal, externalPath) {
|
|
302
|
+
// For standard script, we also might need source map if we transpiled it (e.g. from TS)
|
|
303
|
+
// and/or just to map it back to .mujs
|
|
304
|
+
let finalMap;
|
|
305
|
+
let code = content;
|
|
306
|
+
// Transpile if TS
|
|
307
|
+
if (externalPath.endsWith('.ts') || script.attrs.lang === 'ts') {
|
|
308
|
+
const result = ts.transpileModule(content, {
|
|
309
|
+
compilerOptions: {
|
|
310
|
+
module: ts.ModuleKind.ESNext,
|
|
311
|
+
target: ts.ScriptTarget.ES2019,
|
|
312
|
+
sourceMap: true,
|
|
313
|
+
inlineSources: true,
|
|
314
|
+
},
|
|
315
|
+
fileName: filename
|
|
316
|
+
});
|
|
317
|
+
code = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
|
|
318
|
+
finalMap = result.sourceMapText;
|
|
319
|
+
}
|
|
320
|
+
// Adjust Source Map
|
|
321
|
+
if (finalMap) {
|
|
322
|
+
try {
|
|
323
|
+
// Standard script content is usually 1:1 with source lines if we ignore transpilation shifts (which TS handles)
|
|
324
|
+
// But if it's inline, TS thinks it starts at line 0. We need to shift it by `linesBeforeScript`.
|
|
325
|
+
let linesBeforeScript = 0;
|
|
326
|
+
let originalSource = content;
|
|
327
|
+
if (!isExternal) {
|
|
328
|
+
originalSource = descriptor.source;
|
|
329
|
+
linesBeforeScript = descriptor.source.substring(0, script.start).split('\n').length - 1;
|
|
330
|
+
}
|
|
331
|
+
const consumer = await new source_map_1.SourceMapConsumer(finalMap);
|
|
332
|
+
const generator = new source_map_1.SourceMapGenerator({
|
|
333
|
+
file: filename,
|
|
334
|
+
sourceRoot: ''
|
|
335
|
+
});
|
|
336
|
+
// Determine relative path for source (same logic as Setup)
|
|
337
|
+
let relativePath = filename.split(/[/\\]/).pop() || 'unknown.mujs';
|
|
338
|
+
if (isExternal) {
|
|
339
|
+
const normalized = externalPath.replace(/\\/g, '/');
|
|
340
|
+
const srcIdx = normalized.indexOf('/src/');
|
|
341
|
+
if (srcIdx !== -1) {
|
|
342
|
+
relativePath = 'webpack:///' + normalized.substring(srcIdx + 1);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
relativePath = 'webpack:///' + path.basename(externalPath);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
const normalized = filename.replace(/\\/g, '/');
|
|
350
|
+
const srcIdx = normalized.indexOf('/src/');
|
|
351
|
+
if (srcIdx !== -1) {
|
|
352
|
+
relativePath = 'webpack:///' + normalized.substring(srcIdx + 1);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
generator.setSourceContent(relativePath, originalSource);
|
|
356
|
+
consumer.eachMapping(m => {
|
|
357
|
+
if (m.originalLine === null)
|
|
358
|
+
return;
|
|
359
|
+
const originalLine = m.originalLine + linesBeforeScript;
|
|
360
|
+
generator.addMapping({
|
|
361
|
+
generated: {
|
|
362
|
+
line: m.generatedLine,
|
|
363
|
+
column: m.generatedColumn
|
|
364
|
+
},
|
|
365
|
+
original: {
|
|
366
|
+
line: originalLine,
|
|
367
|
+
column: m.originalColumn
|
|
368
|
+
},
|
|
369
|
+
source: relativePath,
|
|
370
|
+
name: m.name
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
finalMap = generator.toString();
|
|
374
|
+
consumer.destroy();
|
|
375
|
+
}
|
|
376
|
+
catch (e) { /* ignore */ }
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
code: code,
|
|
380
|
+
errors: [],
|
|
381
|
+
map: finalMap
|
|
382
|
+
};
|
|
383
|
+
}
|
|
247
384
|
// --- Natural Reactivity Transformer ---
|
|
248
385
|
function transformNaturalReactivity(code, isTs) {
|
|
249
386
|
// If no '$(' or '$q(' or '$qr(' usage, return original code for safety/speed
|
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.compileStyle = compileStyle;
|
|
4
|
-
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
function compileStyle(descriptor, filename, options) {
|
|
5
39
|
const styles = descriptor.styles;
|
|
6
40
|
if (!styles || styles.length === 0)
|
|
7
41
|
return { css: '', errors: [] };
|
|
@@ -9,18 +43,41 @@ function compileStyle(descriptor, filename) {
|
|
|
9
43
|
const scopedId = 'data-v-' + hash(filename);
|
|
10
44
|
styles.forEach(style => {
|
|
11
45
|
let content = style.content;
|
|
46
|
+
// Handle External Style
|
|
47
|
+
if (style.attrs.src) {
|
|
48
|
+
if (options && options.readFileSync) {
|
|
49
|
+
try {
|
|
50
|
+
const sfcDir = path.dirname(filename);
|
|
51
|
+
const externalPath = path.resolve(sfcDir, style.attrs.src);
|
|
52
|
+
content = options.readFileSync(externalPath);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
// Start error with "Style Compilation Error" to be consistent? Or just add to errors list.
|
|
56
|
+
// But we are inside forEach, so we can't return easily. We'll add a comment in CSS or log.
|
|
57
|
+
// Better to collect errors.
|
|
58
|
+
// But function signature returns { errors: string[] }.
|
|
59
|
+
// We'll just append to errors? But we need to change forEach loop or use functional approach.
|
|
60
|
+
// For now, let's just ignore/log in CSS content?
|
|
61
|
+
content = `/* Failed to read external style: ${e.message} */`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
content = `/* External style found but no readFileSync option provided */`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
12
68
|
if (style.attrs.scoped) {
|
|
13
69
|
// SCSS Compilation
|
|
14
|
-
if (style.attrs.lang === 'scss' || style.attrs.lang === 'sass') {
|
|
70
|
+
if (style.attrs.lang === 'scss' || style.attrs.lang === 'sass' || (style.attrs.src && (style.attrs.src.endsWith('.scss') || style.attrs.src.endsWith('.sass')))) {
|
|
15
71
|
try {
|
|
16
72
|
const sass = require('sass');
|
|
17
73
|
const result = sass.compileString(content, {
|
|
18
|
-
syntax: style.attrs.lang === 'sass' ? 'indented' : 'scss'
|
|
74
|
+
syntax: (style.attrs.lang === 'sass' || (style.attrs.src && style.attrs.src.endsWith('.sass'))) ? 'indented' : 'scss'
|
|
19
75
|
});
|
|
20
76
|
content = result.css;
|
|
21
77
|
}
|
|
22
78
|
catch (e) {
|
|
23
|
-
return { css: '', errors: [`SCSS Compilation Error: ${e.message}`] };
|
|
79
|
+
// return { css: '', errors: [`SCSS Compilation Error: ${e.message}`] };
|
|
80
|
+
content = `/* SCSS Compilation Error: ${e.message} */`;
|
|
24
81
|
}
|
|
25
82
|
}
|
|
26
83
|
// Rewrite selectors
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.compileTemplate = compileTemplate;
|
|
4
|
+
// --- Compile Function ---
|
|
5
|
+
const source_map_1 = require("source-map");
|
|
4
6
|
function compileTemplate(descriptor, scriptResult, scopedId) {
|
|
5
7
|
console.log(`[MulanJS Compiler v1.0.1-dev.2] Compiling template for: ${descriptor.filename || 'anonymous'}`);
|
|
6
8
|
const template = descriptor.template;
|
|
@@ -14,8 +16,7 @@ function compileTemplate(descriptor, scriptResult, scopedId) {
|
|
|
14
16
|
transform(ast, scriptResult, scopedId, [], descriptor.filename);
|
|
15
17
|
// 3. Codegen Phase (AST -> JS Function)
|
|
16
18
|
const code = generate(ast, scriptResult.bindings || []);
|
|
17
|
-
|
|
18
|
-
code: `function render() {
|
|
19
|
+
const renderFn = `function render() {
|
|
19
20
|
const _s = (v) => (v && typeof v === 'object' && 'value' in v) ? v.value : v;
|
|
20
21
|
const _h = (v, raw) => {
|
|
21
22
|
const val = _s(v);
|
|
@@ -27,8 +28,47 @@ function compileTemplate(descriptor, scriptResult, scopedId) {
|
|
|
27
28
|
return val.replace(/</g, '<').replace(/>/g, '>');
|
|
28
29
|
};
|
|
29
30
|
return ${code};
|
|
30
|
-
}
|
|
31
|
-
|
|
31
|
+
}`;
|
|
32
|
+
// 4. Source Map Generation
|
|
33
|
+
// We map the entire render function to the starting line of the template in the source file
|
|
34
|
+
let map = undefined;
|
|
35
|
+
if (descriptor.filename) {
|
|
36
|
+
const generator = new source_map_1.SourceMapGenerator({
|
|
37
|
+
file: descriptor.filename + '.template.js', // Virtual file name
|
|
38
|
+
});
|
|
39
|
+
// Find start line of template in original source
|
|
40
|
+
// descriptor.template.start is the index of content start
|
|
41
|
+
const sourceBefore = descriptor.source.substring(0, template.start);
|
|
42
|
+
const startLine = sourceBefore.split(/\r?\n/).length;
|
|
43
|
+
// Simple mapping: One-to-one mapping isn't easy without AST location tracking
|
|
44
|
+
// But we can map the 'return' statement to the template start
|
|
45
|
+
// The render function has about 12 lines of preamble.
|
|
46
|
+
// Let's just mapping the whole block to the start line for now.
|
|
47
|
+
// This ensures the file shows up in devtools.
|
|
48
|
+
generator.addMapping({
|
|
49
|
+
generated: { line: 1, column: 0 },
|
|
50
|
+
source: descriptor.filename, // This should be the .mujs file path
|
|
51
|
+
original: { line: startLine, column: 0 }
|
|
52
|
+
});
|
|
53
|
+
// Also map the return line (approx line 12)
|
|
54
|
+
generator.addMapping({
|
|
55
|
+
generated: { line: 12, column: 0 },
|
|
56
|
+
source: descriptor.filename,
|
|
57
|
+
original: { line: startLine, column: 0 }
|
|
58
|
+
});
|
|
59
|
+
// We must include the "source content" so DevTools can display it!
|
|
60
|
+
// IMPORTANT: We want the FULL .mujs content here, so it matches what Webpack sees?
|
|
61
|
+
// Actually, if we use the same filename as script-compiler, they might conflict or merge.
|
|
62
|
+
// Script compiler uses 'webpack:///...'
|
|
63
|
+
// Let's use the same convention.
|
|
64
|
+
// But we just use the filename here, compiler.ts will handle the final path adjustment if needed.
|
|
65
|
+
generator.setSourceContent(descriptor.filename, descriptor.source);
|
|
66
|
+
map = generator.toString();
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
code: renderFn,
|
|
70
|
+
errors,
|
|
71
|
+
map
|
|
32
72
|
};
|
|
33
73
|
}
|
|
34
74
|
// --- Parser (Recursive Descent) ---
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { CompilerOptions } from './script-compiler';
|
|
1
2
|
export interface CompileResult {
|
|
2
3
|
code: string;
|
|
3
4
|
css: string;
|
|
4
5
|
errors: string[];
|
|
5
6
|
map?: string;
|
|
6
7
|
}
|
|
7
|
-
export declare function compileSFC(source: string, filename: string): Promise<CompileResult>;
|
|
8
|
+
export declare function compileSFC(source: string, filename: string, options?: CompilerOptions): Promise<CompileResult>;
|
|
@@ -5,4 +5,7 @@ export interface ScriptCompileResult {
|
|
|
5
5
|
errors: string[];
|
|
6
6
|
map?: string;
|
|
7
7
|
}
|
|
8
|
-
export
|
|
8
|
+
export interface CompilerOptions {
|
|
9
|
+
readFileSync?: (file: string) => string;
|
|
10
|
+
}
|
|
11
|
+
export declare function compileScript(descriptor: SFCDescriptor, options?: CompilerOptions): Promise<ScriptCompileResult>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { SFCDescriptor } from './sfc-parser';
|
|
2
|
+
import { CompilerOptions } from './script-compiler';
|
|
2
3
|
export interface StyleCompileResult {
|
|
3
4
|
css: string;
|
|
4
5
|
scopedId?: string;
|
|
5
6
|
errors: string[];
|
|
6
7
|
}
|
|
7
|
-
export declare function compileStyle(descriptor: SFCDescriptor, filename: string): StyleCompileResult;
|
|
8
|
+
export declare function compileStyle(descriptor: SFCDescriptor, filename: string, options?: CompilerOptions): StyleCompileResult;
|
|
@@ -3,5 +3,6 @@ import { ScriptCompileResult } from './script-compiler';
|
|
|
3
3
|
export interface TemplateCompileResult {
|
|
4
4
|
code: string;
|
|
5
5
|
errors: string[];
|
|
6
|
+
map?: string;
|
|
6
7
|
}
|
|
7
8
|
export declare function compileTemplate(descriptor: SFCDescriptor, scriptResult: ScriptCompileResult, scopedId?: string): TemplateCompileResult;
|
package/dist/types/compiler.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { CompilerOptions } from './script-compiler';
|
|
1
2
|
export interface CompileResult {
|
|
2
3
|
code: string;
|
|
3
4
|
css: string;
|
|
4
5
|
errors: string[];
|
|
5
6
|
map?: string;
|
|
6
7
|
}
|
|
7
|
-
export declare function compileSFC(source: string, filename: string): Promise<CompileResult>;
|
|
8
|
+
export declare function compileSFC(source: string, filename: string, options?: CompilerOptions): Promise<CompileResult>;
|
|
@@ -5,4 +5,7 @@ export interface ScriptCompileResult {
|
|
|
5
5
|
errors: string[];
|
|
6
6
|
map?: string;
|
|
7
7
|
}
|
|
8
|
-
export
|
|
8
|
+
export interface CompilerOptions {
|
|
9
|
+
readFileSync?: (file: string) => string;
|
|
10
|
+
}
|
|
11
|
+
export declare function compileScript(descriptor: SFCDescriptor, options?: CompilerOptions): Promise<ScriptCompileResult>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { SFCDescriptor } from './sfc-parser';
|
|
2
|
+
import { CompilerOptions } from './script-compiler';
|
|
2
3
|
export interface StyleCompileResult {
|
|
3
4
|
css: string;
|
|
4
5
|
scopedId?: string;
|
|
5
6
|
errors: string[];
|
|
6
7
|
}
|
|
7
|
-
export declare function compileStyle(descriptor: SFCDescriptor, filename: string): StyleCompileResult;
|
|
8
|
+
export declare function compileStyle(descriptor: SFCDescriptor, filename: string, options?: CompilerOptions): StyleCompileResult;
|
|
@@ -3,5 +3,6 @@ import { ScriptCompileResult } from './script-compiler';
|
|
|
3
3
|
export interface TemplateCompileResult {
|
|
4
4
|
code: string;
|
|
5
5
|
errors: string[];
|
|
6
|
+
map?: string;
|
|
6
7
|
}
|
|
7
8
|
export declare function compileTemplate(descriptor: SFCDescriptor, scriptResult: ScriptCompileResult, scopedId?: string): TemplateCompileResult;
|
package/package.json
CHANGED
package/src/compiler/compiler.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { parseMUJS } from './sfc-parser';
|
|
3
|
-
import { compileScript } from './script-compiler';
|
|
3
|
+
import { compileScript, CompilerOptions } from './script-compiler';
|
|
4
4
|
import { compileTemplate } from './template-compiler';
|
|
5
5
|
import { compileStyle } from './style-compiler';
|
|
6
6
|
|
|
@@ -11,12 +11,14 @@ export interface CompileResult {
|
|
|
11
11
|
map?: string; // Source Map
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
import { SourceMapGenerator, SourceMapConsumer } from 'source-map';
|
|
15
|
+
|
|
16
|
+
export async function compileSFC(source: string, filename: string, options?: CompilerOptions): Promise<CompileResult> {
|
|
15
17
|
// 1. Parse
|
|
16
18
|
const descriptor = parseMUJS(source, filename);
|
|
17
19
|
|
|
18
20
|
// 2. Script
|
|
19
|
-
const scriptResult = await compileScript(descriptor);
|
|
21
|
+
const scriptResult = await compileScript(descriptor, options);
|
|
20
22
|
// Replace export default to capture the component
|
|
21
23
|
// If the script already has 'export default', we intercept it.
|
|
22
24
|
let scriptCode = scriptResult.code.replace('export default', 'const __component__ =');
|
|
@@ -25,11 +27,94 @@ export async function compileSFC(source: string, filename: string): Promise<Comp
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
// 3. Style
|
|
28
|
-
const styleResult = compileStyle(descriptor, filename);
|
|
30
|
+
const styleResult = compileStyle(descriptor, filename, options);
|
|
29
31
|
|
|
30
32
|
// 4. Template
|
|
31
33
|
const templateResult = compileTemplate(descriptor, scriptResult, styleResult.scopedId);
|
|
32
34
|
|
|
35
|
+
// Calculate offsets for source map merging
|
|
36
|
+
// 1. Script Code
|
|
37
|
+
// 2. Padding (newlines)
|
|
38
|
+
// 3. Template Code
|
|
39
|
+
|
|
40
|
+
// We need to count lines in scriptCode to know where template code starts in the final file
|
|
41
|
+
const scriptLineCount = scriptCode.split(/\r?\n/).length;
|
|
42
|
+
const paddingLines = 2; // Two newlines between script and template
|
|
43
|
+
const templateOffset = scriptLineCount + paddingLines;
|
|
44
|
+
|
|
45
|
+
let mergedMap: string | undefined;
|
|
46
|
+
|
|
47
|
+
// Merge Maps
|
|
48
|
+
if (filename) {
|
|
49
|
+
const generator = new SourceMapGenerator({
|
|
50
|
+
file: filename + '.js',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 1. Apply Script Map
|
|
54
|
+
if (scriptResult.map) {
|
|
55
|
+
const scriptConsumer = await new SourceMapConsumer(JSON.parse(scriptResult.map));
|
|
56
|
+
scriptConsumer.eachMapping(mapping => {
|
|
57
|
+
if (mapping.source) {
|
|
58
|
+
generator.addMapping({
|
|
59
|
+
generated: {
|
|
60
|
+
line: mapping.generatedLine, // Script is at the top, so no offset needed
|
|
61
|
+
column: mapping.generatedColumn
|
|
62
|
+
},
|
|
63
|
+
original: {
|
|
64
|
+
line: mapping.originalLine,
|
|
65
|
+
column: mapping.originalColumn
|
|
66
|
+
},
|
|
67
|
+
source: mapping.source,
|
|
68
|
+
name: mapping.name
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// Copy source content from script map
|
|
73
|
+
if (scriptConsumer.sources) {
|
|
74
|
+
scriptConsumer.sources.forEach(sourceFile => {
|
|
75
|
+
const content = scriptConsumer.sourceContentFor(sourceFile);
|
|
76
|
+
if (content) {
|
|
77
|
+
generator.setSourceContent(sourceFile, content);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
scriptConsumer.destroy();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 2. Apply Template Map
|
|
85
|
+
if (templateResult.map) {
|
|
86
|
+
const templateConsumer = await new SourceMapConsumer(JSON.parse(templateResult.map));
|
|
87
|
+
templateConsumer.eachMapping(mapping => {
|
|
88
|
+
if (mapping.source) {
|
|
89
|
+
generator.addMapping({
|
|
90
|
+
generated: {
|
|
91
|
+
line: mapping.generatedLine + templateOffset, // Shift by script length
|
|
92
|
+
column: mapping.generatedColumn
|
|
93
|
+
},
|
|
94
|
+
original: {
|
|
95
|
+
line: mapping.originalLine, // Original line in .mujs
|
|
96
|
+
column: mapping.originalColumn
|
|
97
|
+
},
|
|
98
|
+
source: mapping.source, // This should be the .mujs file
|
|
99
|
+
name: mapping.name
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// Copy source content from template map
|
|
104
|
+
if (templateConsumer.sources) {
|
|
105
|
+
templateConsumer.sources.forEach(sourceFile => {
|
|
106
|
+
const content = templateConsumer.sourceContentFor(sourceFile);
|
|
107
|
+
if (content) {
|
|
108
|
+
generator.setSourceContent(sourceFile, content);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
templateConsumer.destroy();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
mergedMap = generator.toString();
|
|
116
|
+
}
|
|
117
|
+
|
|
33
118
|
// 5. Assembly
|
|
34
119
|
const cssInjection = styleResult.css ? `
|
|
35
120
|
if (typeof document !== 'undefined') {
|
|
@@ -98,6 +183,6 @@ export default __component__;
|
|
|
98
183
|
code: finalCode,
|
|
99
184
|
css: styleResult.css,
|
|
100
185
|
errors: [...scriptResult.errors, ...templateResult.errors],
|
|
101
|
-
map: scriptResult.map //
|
|
186
|
+
map: mergedMap || scriptResult.map // Fallback to script map if merge fails
|
|
102
187
|
};
|
|
103
188
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { SFCDescriptor, SFCBlock } from './sfc-parser';
|
|
3
3
|
import * as ts from 'typescript';
|
|
4
4
|
import { SourceMapConsumer, SourceMapGenerator } from 'source-map';
|
|
5
|
+
import * as path from 'path';
|
|
5
6
|
|
|
6
7
|
export interface ScriptCompileResult {
|
|
7
8
|
code: string;
|
|
@@ -10,28 +11,66 @@ export interface ScriptCompileResult {
|
|
|
10
11
|
map?: string; // Source Map
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export
|
|
14
|
+
export interface CompilerOptions {
|
|
15
|
+
readFileSync?: (file: string) => string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function compileScript(descriptor: SFCDescriptor, options?: CompilerOptions): Promise<ScriptCompileResult> {
|
|
14
19
|
const script = descriptor.script;
|
|
15
20
|
if (!script) return { code: 'export default {}', errors: [] };
|
|
16
21
|
|
|
22
|
+
let content = script.content;
|
|
23
|
+
let filename = descriptor.filename;
|
|
24
|
+
let isExternal = false;
|
|
25
|
+
let externalPath = '';
|
|
26
|
+
|
|
27
|
+
// Handle External Script
|
|
28
|
+
if (script.attrs.src) {
|
|
29
|
+
if (options && options.readFileSync) {
|
|
30
|
+
try {
|
|
31
|
+
// Resolve path relative to the SFC file
|
|
32
|
+
const sfcDir = path.dirname(descriptor.filename);
|
|
33
|
+
externalPath = path.resolve(sfcDir, script.attrs.src);
|
|
34
|
+
content = options.readFileSync(externalPath);
|
|
35
|
+
// Keep filename as .mujs for now, but we will use externalPath for source mapping of the content
|
|
36
|
+
// Actually, if we want the external file to show up as ITSELF in devtools, we should map to externalPath.
|
|
37
|
+
isExternal = true;
|
|
38
|
+
} catch (e: any) {
|
|
39
|
+
return { code: 'export default {}', errors: [`Failed to read external script: ${e.message}`] };
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
return { code: 'export default {}', errors: [`External script found but no readFileSync option provided.`] };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
17
46
|
const isSetup = !!script.attrs.setup;
|
|
18
|
-
const isTs = script.attrs.lang === 'ts' || script.attrs.lang === 'tsx';
|
|
47
|
+
const isTs = script.attrs.lang === 'ts' || script.attrs.lang === 'tsx' || externalPath.endsWith('.ts') || externalPath.endsWith('.tsx');
|
|
19
48
|
|
|
20
49
|
if (isSetup) {
|
|
21
|
-
return await compileSetupScript(script, isTs, descriptor);
|
|
50
|
+
return await compileSetupScript(script, isTs, descriptor, content, filename, isExternal, externalPath);
|
|
51
|
+
} else if (isTs) {
|
|
52
|
+
return await compileStandardScript(script, descriptor, content, filename, isExternal, externalPath);
|
|
22
53
|
} else {
|
|
23
54
|
// Standard Options API - just pass through, assuming it has export default
|
|
24
|
-
return { code:
|
|
55
|
+
return { code: content, errors: [] };
|
|
25
56
|
}
|
|
26
57
|
}
|
|
27
58
|
|
|
28
59
|
// Global list of Mulan hooks for auto-imports and reactivity exemption
|
|
29
60
|
const COMMON_HOOKS = ['muState', 'onMuMount', 'onMuInit', 'onMuDestroy', 'muEffect', 'muMemo', 'muQubit', 'muGate', 'muMeasure', 'muRegister', 'muEntangle', 'persistent'];
|
|
30
61
|
|
|
31
|
-
async function compileSetupScript(
|
|
62
|
+
async function compileSetupScript(
|
|
63
|
+
script: SFCBlock,
|
|
64
|
+
isTs: boolean,
|
|
65
|
+
descriptor: SFCDescriptor,
|
|
66
|
+
content: string,
|
|
67
|
+
filename: string,
|
|
68
|
+
isExternal: boolean,
|
|
69
|
+
externalPath: string
|
|
70
|
+
): Promise<ScriptCompileResult> {
|
|
32
71
|
// 1. Pre-process: Natural Reactivity Transformation ($ syntax)
|
|
33
72
|
// We do this BEFORE extraction so that the rest of the compiler sees standard 'muState' code.
|
|
34
|
-
const transformedContent = transformNaturalReactivity(
|
|
73
|
+
const transformedContent = transformNaturalReactivity(content, isTs);
|
|
35
74
|
|
|
36
75
|
// 2. Parse the TRANSFORMED code
|
|
37
76
|
const sourceFile = ts.createSourceFile(
|
|
@@ -119,7 +158,6 @@ async function compileSetupScript(script: SFCBlock, isTs: boolean, descriptor: S
|
|
|
119
158
|
? `import { ${autoImports.join(', ')} } from '@mulanjs/mulanjs';`
|
|
120
159
|
: '';
|
|
121
160
|
|
|
122
|
-
const filename = descriptor.filename;
|
|
123
161
|
const componentName = filename ? filename.split(/[/\\]/).pop()?.split('.')[0].replace(/\W/g, '') || 'App' : 'App';
|
|
124
162
|
|
|
125
163
|
const bindingString = bindings.length > 0
|
|
@@ -168,7 +206,7 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
168
206
|
// Fix: Remove the //# sourceMappingURL= comment added by TS
|
|
169
207
|
const codeWithoutMapComment = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
|
|
170
208
|
|
|
171
|
-
// Enhance Source Map to show ORIGINAL .mujs content
|
|
209
|
+
// Enhance Source Map to show ORIGINAL .mujs content or EXTERNAL content
|
|
172
210
|
let finalMap = result.sourceMapText;
|
|
173
211
|
if (finalMap) {
|
|
174
212
|
try {
|
|
@@ -176,9 +214,16 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
176
214
|
const preambleLines = preamble.split('\n').length - 1;
|
|
177
215
|
// scriptBody starts at line `preambleLines` (0-indexed) in finalCode.
|
|
178
216
|
|
|
179
|
-
// Determine where script starts
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
}
|
|
182
227
|
|
|
183
228
|
const consumer = await new SourceMapConsumer(finalMap);
|
|
184
229
|
const generator = new SourceMapGenerator({
|
|
@@ -186,13 +231,32 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
186
231
|
sourceRoot: ''
|
|
187
232
|
});
|
|
188
233
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
}
|
|
249
|
+
} 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
|
+
}
|
|
256
|
+
}
|
|
195
257
|
|
|
258
|
+
// If inline, we set source content of .mujs (the whole file)
|
|
259
|
+
// If external, we set source content of the .ts file
|
|
196
260
|
generator.setSourceContent(relativePath, originalSource);
|
|
197
261
|
|
|
198
262
|
consumer.eachMapping(m => {
|
|
@@ -210,9 +274,10 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
210
274
|
// Calculate offset inside scriptBody
|
|
211
275
|
const lineOffsetInBody = lineInFinalCode - preambleLines; // 1-based offset (1st line of body is 1)
|
|
212
276
|
|
|
213
|
-
// Calculate original line
|
|
214
|
-
// If lineOffsetInBody is
|
|
215
|
-
|
|
277
|
+
// Calculate original line
|
|
278
|
+
// If External: lineOffsetInBody is the line number
|
|
279
|
+
// If Inline: linesBeforeScript + lineOffsetInBody
|
|
280
|
+
const originalLine = linesBeforeScript + lineOffsetInBody;
|
|
216
281
|
|
|
217
282
|
// Add mapping
|
|
218
283
|
generator.addMapping({
|
|
@@ -221,7 +286,7 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
221
286
|
column: m.generatedColumn
|
|
222
287
|
},
|
|
223
288
|
original: {
|
|
224
|
-
line:
|
|
289
|
+
line: originalLine,
|
|
225
290
|
column: m.originalColumn // Column should be preserved if we didn't change indentation
|
|
226
291
|
},
|
|
227
292
|
source: relativePath,
|
|
@@ -254,6 +319,109 @@ export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
|
254
319
|
};
|
|
255
320
|
}
|
|
256
321
|
|
|
322
|
+
async function compileStandardScript(
|
|
323
|
+
script: SFCBlock,
|
|
324
|
+
descriptor: SFCDescriptor,
|
|
325
|
+
content: string,
|
|
326
|
+
filename: string,
|
|
327
|
+
isExternal: boolean,
|
|
328
|
+
externalPath: string
|
|
329
|
+
): Promise<ScriptCompileResult> {
|
|
330
|
+
|
|
331
|
+
// For standard script, we also might need source map if we transpiled it (e.g. from TS)
|
|
332
|
+
// and/or just to map it back to .mujs
|
|
333
|
+
|
|
334
|
+
let finalMap: string | undefined;
|
|
335
|
+
let code = content;
|
|
336
|
+
|
|
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
|
+
}
|
|
351
|
+
|
|
352
|
+
// Adjust Source Map
|
|
353
|
+
if (finalMap) {
|
|
354
|
+
try {
|
|
355
|
+
// Standard script content is usually 1:1 with source lines if we ignore transpilation shifts (which TS handles)
|
|
356
|
+
// But if it's inline, TS thinks it starts at line 0. We need to shift it by `linesBeforeScript`.
|
|
357
|
+
|
|
358
|
+
let linesBeforeScript = 0;
|
|
359
|
+
let originalSource = content;
|
|
360
|
+
|
|
361
|
+
if (!isExternal) {
|
|
362
|
+
originalSource = descriptor.source;
|
|
363
|
+
linesBeforeScript = descriptor.source.substring(0, script.start).split('\n').length - 1;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const consumer = await new SourceMapConsumer(finalMap);
|
|
367
|
+
const generator = new SourceMapGenerator({
|
|
368
|
+
file: filename,
|
|
369
|
+
sourceRoot: ''
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Determine relative path for source (same logic as Setup)
|
|
373
|
+
let relativePath = filename.split(/[/\\]/).pop() || 'unknown.mujs';
|
|
374
|
+
|
|
375
|
+
if (isExternal) {
|
|
376
|
+
const normalized = externalPath.replace(/\\/g, '/');
|
|
377
|
+
const srcIdx = normalized.indexOf('/src/');
|
|
378
|
+
if (srcIdx !== -1) {
|
|
379
|
+
relativePath = 'webpack:///' + normalized.substring(srcIdx + 1);
|
|
380
|
+
} else {
|
|
381
|
+
relativePath = 'webpack:///' + path.basename(externalPath);
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
const normalized = filename.replace(/\\/g, '/');
|
|
385
|
+
const srcIdx = normalized.indexOf('/src/');
|
|
386
|
+
if (srcIdx !== -1) {
|
|
387
|
+
relativePath = 'webpack:///' + normalized.substring(srcIdx + 1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
generator.setSourceContent(relativePath, originalSource);
|
|
392
|
+
|
|
393
|
+
consumer.eachMapping(m => {
|
|
394
|
+
if (m.originalLine === null) return;
|
|
395
|
+
|
|
396
|
+
const originalLine = m.originalLine + linesBeforeScript;
|
|
397
|
+
|
|
398
|
+
generator.addMapping({
|
|
399
|
+
generated: {
|
|
400
|
+
line: m.generatedLine,
|
|
401
|
+
column: m.generatedColumn
|
|
402
|
+
},
|
|
403
|
+
original: {
|
|
404
|
+
line: originalLine,
|
|
405
|
+
column: m.originalColumn
|
|
406
|
+
},
|
|
407
|
+
source: relativePath,
|
|
408
|
+
name: m.name
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
finalMap = generator.toString();
|
|
413
|
+
consumer.destroy();
|
|
414
|
+
|
|
415
|
+
} catch (e) { /* ignore */ }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
code: code,
|
|
420
|
+
errors: [],
|
|
421
|
+
map: finalMap
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
257
425
|
// --- Natural Reactivity Transformer ---
|
|
258
426
|
|
|
259
427
|
function transformNaturalReactivity(code: string, isTs: boolean): string {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
|
|
2
|
+
import * as path from 'path';
|
|
2
3
|
import { SFCDescriptor } from './sfc-parser';
|
|
4
|
+
import { CompilerOptions } from './script-compiler'; // Re-use options interface
|
|
3
5
|
|
|
4
6
|
export interface StyleCompileResult {
|
|
5
7
|
css: string;
|
|
@@ -7,7 +9,7 @@ export interface StyleCompileResult {
|
|
|
7
9
|
errors: string[];
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
export function compileStyle(descriptor: SFCDescriptor, filename: string): StyleCompileResult {
|
|
12
|
+
export function compileStyle(descriptor: SFCDescriptor, filename: string, options?: CompilerOptions): StyleCompileResult {
|
|
11
13
|
const styles = descriptor.styles;
|
|
12
14
|
if (!styles || styles.length === 0) return { css: '', errors: [] };
|
|
13
15
|
|
|
@@ -17,17 +19,39 @@ export function compileStyle(descriptor: SFCDescriptor, filename: string): Style
|
|
|
17
19
|
styles.forEach(style => {
|
|
18
20
|
let content = style.content;
|
|
19
21
|
|
|
22
|
+
// Handle External Style
|
|
23
|
+
if (style.attrs.src) {
|
|
24
|
+
if (options && options.readFileSync) {
|
|
25
|
+
try {
|
|
26
|
+
const sfcDir = path.dirname(filename);
|
|
27
|
+
const externalPath = path.resolve(sfcDir, style.attrs.src);
|
|
28
|
+
content = options.readFileSync(externalPath);
|
|
29
|
+
} catch (e: any) {
|
|
30
|
+
// Start error with "Style Compilation Error" to be consistent? Or just add to errors list.
|
|
31
|
+
// But we are inside forEach, so we can't return easily. We'll add a comment in CSS or log.
|
|
32
|
+
// Better to collect errors.
|
|
33
|
+
// But function signature returns { errors: string[] }.
|
|
34
|
+
// We'll just append to errors? But we need to change forEach loop or use functional approach.
|
|
35
|
+
// For now, let's just ignore/log in CSS content?
|
|
36
|
+
content = `/* Failed to read external style: ${e.message} */`;
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
content = `/* External style found but no readFileSync option provided */`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
20
43
|
if (style.attrs.scoped) {
|
|
21
44
|
// SCSS Compilation
|
|
22
|
-
if (style.attrs.lang === 'scss' || style.attrs.lang === 'sass') {
|
|
45
|
+
if (style.attrs.lang === 'scss' || style.attrs.lang === 'sass' || (style.attrs.src && (style.attrs.src.endsWith('.scss') || style.attrs.src.endsWith('.sass')))) {
|
|
23
46
|
try {
|
|
24
47
|
const sass = require('sass');
|
|
25
48
|
const result = sass.compileString(content, {
|
|
26
|
-
syntax: style.attrs.lang === 'sass' ? 'indented' : 'scss'
|
|
49
|
+
syntax: (style.attrs.lang === 'sass' || (style.attrs.src && style.attrs.src.endsWith('.sass'))) ? 'indented' : 'scss'
|
|
27
50
|
});
|
|
28
51
|
content = result.css;
|
|
29
52
|
} catch (e: any) {
|
|
30
|
-
return { css: '', errors: [`SCSS Compilation Error: ${e.message}`] };
|
|
53
|
+
// return { css: '', errors: [`SCSS Compilation Error: ${e.message}`] };
|
|
54
|
+
content = `/* SCSS Compilation Error: ${e.message} */`;
|
|
31
55
|
}
|
|
32
56
|
}
|
|
33
57
|
|
|
@@ -34,9 +34,12 @@ interface InterpolationNode extends BaseNode {
|
|
|
34
34
|
|
|
35
35
|
// --- Compile Function ---
|
|
36
36
|
|
|
37
|
+
import { SourceMapGenerator } from 'source-map';
|
|
38
|
+
|
|
37
39
|
export interface TemplateCompileResult {
|
|
38
40
|
code: string;
|
|
39
41
|
errors: string[];
|
|
42
|
+
map?: string;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export function compileTemplate(descriptor: SFCDescriptor, scriptResult: ScriptCompileResult, scopedId?: string): TemplateCompileResult {
|
|
@@ -56,8 +59,7 @@ export function compileTemplate(descriptor: SFCDescriptor, scriptResult: ScriptC
|
|
|
56
59
|
// 3. Codegen Phase (AST -> JS Function)
|
|
57
60
|
const code = generate(ast, scriptResult.bindings || []);
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
code: `function render() {
|
|
62
|
+
const renderFn = `function render() {
|
|
61
63
|
const _s = (v) => (v && typeof v === 'object' && 'value' in v) ? v.value : v;
|
|
62
64
|
const _h = (v, raw) => {
|
|
63
65
|
const val = _s(v);
|
|
@@ -69,8 +71,54 @@ export function compileTemplate(descriptor: SFCDescriptor, scriptResult: ScriptC
|
|
|
69
71
|
return val.replace(/</g, '<').replace(/>/g, '>');
|
|
70
72
|
};
|
|
71
73
|
return ${code};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
+
}`;
|
|
75
|
+
|
|
76
|
+
// 4. Source Map Generation
|
|
77
|
+
// We map the entire render function to the starting line of the template in the source file
|
|
78
|
+
let map = undefined;
|
|
79
|
+
if (descriptor.filename) {
|
|
80
|
+
const generator = new SourceMapGenerator({
|
|
81
|
+
file: descriptor.filename + '.template.js', // Virtual file name
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Find start line of template in original source
|
|
85
|
+
// descriptor.template.start is the index of content start
|
|
86
|
+
const sourceBefore = descriptor.source.substring(0, template.start);
|
|
87
|
+
const startLine = sourceBefore.split(/\r?\n/).length;
|
|
88
|
+
|
|
89
|
+
// Simple mapping: One-to-one mapping isn't easy without AST location tracking
|
|
90
|
+
// But we can map the 'return' statement to the template start
|
|
91
|
+
// The render function has about 12 lines of preamble.
|
|
92
|
+
// Let's just mapping the whole block to the start line for now.
|
|
93
|
+
// This ensures the file shows up in devtools.
|
|
94
|
+
generator.addMapping({
|
|
95
|
+
generated: { line: 1, column: 0 },
|
|
96
|
+
source: descriptor.filename, // This should be the .mujs file path
|
|
97
|
+
original: { line: startLine, column: 0 }
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Also map the return line (approx line 12)
|
|
101
|
+
generator.addMapping({
|
|
102
|
+
generated: { line: 12, column: 0 },
|
|
103
|
+
source: descriptor.filename,
|
|
104
|
+
original: { line: startLine, column: 0 }
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// We must include the "source content" so DevTools can display it!
|
|
108
|
+
// IMPORTANT: We want the FULL .mujs content here, so it matches what Webpack sees?
|
|
109
|
+
// Actually, if we use the same filename as script-compiler, they might conflict or merge.
|
|
110
|
+
// Script compiler uses 'webpack:///...'
|
|
111
|
+
// Let's use the same convention.
|
|
112
|
+
// But we just use the filename here, compiler.ts will handle the final path adjustment if needed.
|
|
113
|
+
generator.setSourceContent(descriptor.filename, descriptor.source);
|
|
114
|
+
|
|
115
|
+
map = generator.toString();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
code: renderFn,
|
|
120
|
+
errors,
|
|
121
|
+
map
|
|
74
122
|
};
|
|
75
123
|
}
|
|
76
124
|
|
package/src/loader/index.js
CHANGED
|
@@ -9,45 +9,20 @@ module.exports = function (content) {
|
|
|
9
9
|
const context = this.context || path.dirname(filePath);
|
|
10
10
|
|
|
11
11
|
// --- External File Processor ---
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
// Regex to match <tag ... src="...">... </tag> or <tag ... src="..." />
|
|
15
|
-
// Captures: 1=TagName, 2=Attributes before src, 3=Quote, 4=Path, 5=Attributes after src
|
|
16
|
-
const tagRegex = /<(script|style)([\s\S]*?)src=(["'])(.*?)\3([\s\S]*?)>(?:<\/\1>|\s*\/>)?/gi;
|
|
12
|
+
// REMOVED: Inlining is now handled by the compiler to support correct source maps.
|
|
13
|
+
// We still scan for dependencies.
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
console.log(`[MulanJS Loader] Inlined external ${tagName}: ${srcPath}`);
|
|
30
|
-
|
|
31
|
-
// Auto-detect TypeScript env
|
|
32
|
-
let finalAttrs = attrsBefore + attrsAfter;
|
|
33
|
-
if (srcPath.endsWith('.ts') && !finalAttrs.includes('lang=')) {
|
|
34
|
-
finalAttrs += ' lang="ts"';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Return new tag with content, removing the src attribute
|
|
38
|
-
return `<${tagName}${finalAttrs}>\n${externalContent}\n</${tagName}>`;
|
|
39
|
-
} catch (err) {
|
|
40
|
-
this.emitError(new Error(`[MulanJS Loader] Failed to load external file: ${srcPath}\n${err.message}`));
|
|
41
|
-
return match; // Return original on error to likely fail later or show error
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
content = inlineExternalFiles(content);
|
|
48
|
-
} catch (e) {
|
|
49
|
-
callback(e);
|
|
50
|
-
return;
|
|
15
|
+
// Regex to match <tag ... src="...">... </tag> or <tag ... src="..." /> for DEPENDENCY TRACKING ONLY
|
|
16
|
+
const tagRegex = /<(script|style)([\s\S]*?)src=(["'])(.*?)\3/gi;
|
|
17
|
+
let match;
|
|
18
|
+
while ((match = tagRegex.exec(content)) !== null) {
|
|
19
|
+
try {
|
|
20
|
+
const srcPath = match[4];
|
|
21
|
+
const absolutePath = path.resolve(context, srcPath);
|
|
22
|
+
this.addDependency(absolutePath);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
// Ignore resolution errors here, compiler will handle them
|
|
25
|
+
}
|
|
51
26
|
}
|
|
52
27
|
|
|
53
28
|
// Resolve path to the compiled compiler module (ESM)
|
|
@@ -57,12 +32,12 @@ module.exports = function (content) {
|
|
|
57
32
|
const compilerRef = path.resolve(__dirname, '../../dist/compiler/compiler.js');
|
|
58
33
|
const compilerUrl = pathToFileURL(compilerRef).href;
|
|
59
34
|
|
|
60
|
-
// Use dynamic import to load ESM compiler from CommonJS loader
|
|
61
|
-
|
|
62
35
|
// Use dynamic import to load ESM compiler from CommonJS loader
|
|
63
36
|
import(compilerUrl).then(async compiler => {
|
|
64
37
|
try {
|
|
65
|
-
const result = await compiler.compileSFC(content, filePath
|
|
38
|
+
const result = await compiler.compileSFC(content, filePath, {
|
|
39
|
+
readFileSync: (file) => fs.readFileSync(file, 'utf-8')
|
|
40
|
+
});
|
|
66
41
|
|
|
67
42
|
if (result.errors && result.errors.length > 0) {
|
|
68
43
|
this.emitError(new Error(result.errors.join('\n')));
|