@mulanjs/mulanjs 1.0.1-dev.20260212143840
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/compiler/compiler.js +90 -0
- package/dist/compiler/script-compiler.js +314 -0
- package/dist/compiler/sfc-parser.js +93 -0
- package/dist/compiler/style-compiler.js +56 -0
- package/dist/compiler/template-compiler.js +442 -0
- package/dist/components/bloch-sphere.js +252 -0
- package/dist/core/component.js +145 -0
- package/dist/core/hooks.js +229 -0
- package/dist/core/quantum.js +284 -0
- package/dist/core/query.js +63 -0
- package/dist/core/reactive.js +105 -0
- package/dist/core/renderer.js +70 -0
- package/dist/core/vault.js +81 -0
- package/dist/index.js +52 -0
- package/dist/mulan.esm.js +1948 -0
- package/dist/mulan.js +215 -0
- package/dist/router/index.js +210 -0
- package/dist/security/sanitizer.js +47 -0
- package/dist/store/index.js +42 -0
- package/dist/types/compiler/compiler.d.ts +7 -0
- package/dist/types/compiler/script-compiler.d.ts +8 -0
- package/dist/types/compiler/sfc-parser.d.ts +21 -0
- package/dist/types/compiler/style-compiler.d.ts +7 -0
- package/dist/types/compiler/template-compiler.d.ts +7 -0
- package/dist/types/compiler.d.ts +7 -0
- package/dist/types/components/bloch-sphere.d.ts +16 -0
- package/dist/types/core/component.d.ts +54 -0
- package/dist/types/core/hooks.d.ts +49 -0
- package/dist/types/core/quantum.d.ts +50 -0
- package/dist/types/core/query.d.ts +14 -0
- package/dist/types/core/reactive.d.ts +21 -0
- package/dist/types/core/renderer.d.ts +4 -0
- package/dist/types/core/vault.d.ts +12 -0
- package/dist/types/index.d.ts +70 -0
- package/dist/types/router/index.d.ts +24 -0
- package/dist/types/script-compiler.d.ts +8 -0
- package/dist/types/security/sanitizer.d.ts +17 -0
- package/dist/types/sfc-parser.d.ts +21 -0
- package/dist/types/store/index.d.ts +10 -0
- package/dist/types/style-compiler.d.ts +7 -0
- package/dist/types/template-compiler.d.ts +7 -0
- package/package.json +64 -0
- package/src/cli/extensions/mulanjs-vscode-1.0.0.vsix +0 -0
- package/src/cli/index.js +600 -0
- package/src/compiler/compiler.ts +102 -0
- package/src/compiler/script-compiler.ts +336 -0
- package/src/compiler/sfc-parser.ts +118 -0
- package/src/compiler/style-compiler.ts +66 -0
- package/src/compiler/template-compiler.ts +519 -0
- package/src/compiler/tsconfig.json +13 -0
- package/src/loader/index.js +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nitinkumardev09
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# mulanjs_framework
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compileSFC = compileSFC;
|
|
4
|
+
const sfc_parser_1 = require("./sfc-parser");
|
|
5
|
+
const script_compiler_1 = require("./script-compiler");
|
|
6
|
+
const template_compiler_1 = require("./template-compiler");
|
|
7
|
+
const style_compiler_1 = require("./style-compiler");
|
|
8
|
+
function compileSFC(source, filename) {
|
|
9
|
+
// 1. Parse
|
|
10
|
+
const descriptor = (0, sfc_parser_1.parseMUJS)(source, filename);
|
|
11
|
+
// 2. Script
|
|
12
|
+
const scriptResult = (0, script_compiler_1.compileScript)(descriptor);
|
|
13
|
+
// Replace export default to capture the component
|
|
14
|
+
let scriptCode = scriptResult.code.replace('export default', 'const __component__ =');
|
|
15
|
+
if (!scriptCode.includes('const __component__ =')) {
|
|
16
|
+
scriptCode = scriptCode.replace(/export\s+default/, 'const __component__ =');
|
|
17
|
+
}
|
|
18
|
+
// 3. Style
|
|
19
|
+
const styleResult = (0, style_compiler_1.compileStyle)(descriptor, filename);
|
|
20
|
+
// 4. Template
|
|
21
|
+
const templateResult = (0, template_compiler_1.compileTemplate)(descriptor, scriptResult, styleResult.scopedId);
|
|
22
|
+
// 5. Assembly
|
|
23
|
+
const cssInjection = styleResult.css ? `
|
|
24
|
+
if (typeof document !== 'undefined') {
|
|
25
|
+
const style = document.createElement('style');
|
|
26
|
+
style.textContent = \`${styleResult.css}\`;
|
|
27
|
+
document.head.appendChild(style);
|
|
28
|
+
}
|
|
29
|
+
` : '';
|
|
30
|
+
const finalCode = `${scriptCode}
|
|
31
|
+
|
|
32
|
+
${templateResult.code}
|
|
33
|
+
|
|
34
|
+
// Attach template to component
|
|
35
|
+
if (__component__.prototype) {
|
|
36
|
+
// Class component
|
|
37
|
+
__component__.prototype.template = render;
|
|
38
|
+
} else if (typeof __component__ === 'object') {
|
|
39
|
+
// Options object
|
|
40
|
+
__component__.template = render;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
${cssInjection}
|
|
44
|
+
|
|
45
|
+
// --- MulanJS HMR Engine ---
|
|
46
|
+
if (typeof module !== 'undefined' && module.hot) {
|
|
47
|
+
const hmrId = "${filename.replace(/\\/g, '/')}";
|
|
48
|
+
window.__MULAN_HMR__ = window.__MULAN_HMR__ || {
|
|
49
|
+
records: new Map(),
|
|
50
|
+
notify: (id, newComp) => {
|
|
51
|
+
const oldRecord = window.__MULAN_HMR__.records.get(id);
|
|
52
|
+
if (oldRecord) {
|
|
53
|
+
console.log('[MulanJS HMR] Patching:', id);
|
|
54
|
+
if (oldRecord.prototype && newComp.prototype) {
|
|
55
|
+
oldRecord.prototype.template = newComp.prototype.template;
|
|
56
|
+
// Also patch setup if available (for Functional components)
|
|
57
|
+
if (newComp._setup) oldRecord._setup = newComp._setup;
|
|
58
|
+
} else {
|
|
59
|
+
Object.assign(oldRecord, newComp);
|
|
60
|
+
}
|
|
61
|
+
if (window.__MULAN_REFRESH__) {
|
|
62
|
+
console.log('[MulanJS HMR] Triggering Deep Refresh');
|
|
63
|
+
window.__MULAN_REFRESH__();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (window.__MULAN_HMR__.records.has(hmrId)) {
|
|
70
|
+
window.__MULAN_HMR__.notify(hmrId, __component__);
|
|
71
|
+
} else {
|
|
72
|
+
window.__MULAN_HMR__.records.set(hmrId, __component__);
|
|
73
|
+
}
|
|
74
|
+
module.hot.accept();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// console.log('[MulanJS] Component created:', __component__);
|
|
78
|
+
export default __component__;
|
|
79
|
+
|
|
80
|
+
// Helper to get relative path for sourceURL
|
|
81
|
+
// This ensures the generated file appears in a readable folder structure in DevTools
|
|
82
|
+
// SourceURL removed. Using standard Source Maps only.
|
|
83
|
+
`;
|
|
84
|
+
return {
|
|
85
|
+
code: finalCode,
|
|
86
|
+
css: styleResult.css,
|
|
87
|
+
errors: [...scriptResult.errors, ...templateResult.errors],
|
|
88
|
+
map: scriptResult.map // Pass the map
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.compileScript = compileScript;
|
|
37
|
+
const ts = __importStar(require("typescript"));
|
|
38
|
+
function compileScript(descriptor) {
|
|
39
|
+
const script = descriptor.script;
|
|
40
|
+
if (!script)
|
|
41
|
+
return { code: 'export default {}', errors: [] };
|
|
42
|
+
const isSetup = !!script.attrs.setup;
|
|
43
|
+
const isTs = script.attrs.lang === 'ts' || script.attrs.lang === 'tsx';
|
|
44
|
+
if (isSetup) {
|
|
45
|
+
return compileSetupScript(script, isTs, descriptor);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Standard Options API - just pass through, assuming it has export default
|
|
49
|
+
return { code: script.content, errors: [] };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Global list of Mulan hooks for auto-imports and reactivity exemption
|
|
53
|
+
const COMMON_HOOKS = ['muState', 'onMuMount', 'onMuInit', 'onMuDestroy', 'muEffect', 'muMemo', 'muQubit', 'muGate', 'muMeasure', 'muRegister', 'muEntangle', 'persistent'];
|
|
54
|
+
function compileSetupScript(script, isTs, descriptor) {
|
|
55
|
+
var _a;
|
|
56
|
+
// 1. Pre-process: Natural Reactivity Transformation ($ syntax)
|
|
57
|
+
// We do this BEFORE extraction so that the rest of the compiler sees standard 'muState' code.
|
|
58
|
+
const transformedContent = transformNaturalReactivity(script.content, isTs);
|
|
59
|
+
// 2. Parse the TRANSFORMED code
|
|
60
|
+
const sourceFile = ts.createSourceFile('script.' + (isTs ? 'ts' : 'js'), transformedContent, ts.ScriptTarget.Latest, true);
|
|
61
|
+
const imports = [];
|
|
62
|
+
const statements = [];
|
|
63
|
+
const bindings = [];
|
|
64
|
+
sourceFile.statements.forEach(stmt => {
|
|
65
|
+
if (ts.isImportDeclaration(stmt)) {
|
|
66
|
+
imports.push(transformedContent.substring(stmt.pos, stmt.end).trim());
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
statements.push(transformedContent.substring(stmt.pos, stmt.end).trim());
|
|
70
|
+
// Extract top-level declarations for template exposure
|
|
71
|
+
if (ts.isVariableStatement(stmt)) {
|
|
72
|
+
stmt.declarationList.declarations.forEach(decl => {
|
|
73
|
+
if (ts.isIdentifier(decl.name)) {
|
|
74
|
+
bindings.push(decl.name.text);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else if (ts.isFunctionDeclaration(stmt) && stmt.name) {
|
|
79
|
+
bindings.push(stmt.name.text);
|
|
80
|
+
}
|
|
81
|
+
else if (ts.isClassDeclaration(stmt) && stmt.name) {
|
|
82
|
+
bindings.push(stmt.name.text);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// Reconstruct
|
|
87
|
+
// We import defineComponent from mulanjs if not present?
|
|
88
|
+
// Ideally user imports what they need, but for 'setup' wrapping we need defineComponent.
|
|
89
|
+
// Let's assume user might not have imported it, so we import it as _defineComponent to avoid collision.
|
|
90
|
+
// Check if defineComponent is imported
|
|
91
|
+
const hasDefineComponent = imports.some(i => i.includes('defineComponent'));
|
|
92
|
+
const bootImports = hasDefineComponent ? '' : `import { defineComponent as _defineComponent } from 'mulanjs';`;
|
|
93
|
+
// ADDED: Check if we introduced 'muState' but it wasn't imported (because user used $)
|
|
94
|
+
const usesMuState = transformedContent.includes('muState');
|
|
95
|
+
const hasMuStateImport = imports.some(i => i.includes('muState') || i.includes('mulanjs'));
|
|
96
|
+
let autoImports = (usesMuState && !hasMuStateImport) ? [`muState`] : [];
|
|
97
|
+
// Auto-import common hooks
|
|
98
|
+
COMMON_HOOKS.forEach(hook => {
|
|
99
|
+
if (transformedContent.includes(hook) && !imports.some(i => i.includes(hook))) {
|
|
100
|
+
// Avoid duplicates in autoImports
|
|
101
|
+
if (!autoImports.includes(hook)) {
|
|
102
|
+
autoImports.push(hook);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const autoImportStmt = autoImports.length > 0
|
|
107
|
+
? `import { ${autoImports.join(', ')} } from 'mulanjs';`
|
|
108
|
+
: '';
|
|
109
|
+
const filename = descriptor.filename;
|
|
110
|
+
const componentName = filename ? ((_a = filename.split(/[/\\]/).pop()) === null || _a === void 0 ? void 0 : _a.split('.')[0].replace(/\W/g, '')) || 'App' : 'App';
|
|
111
|
+
const bindingString = bindings.length > 0
|
|
112
|
+
? `
|
|
113
|
+
const exposed = { ${bindings.join(', ')} };
|
|
114
|
+
if (typeof window !== 'undefined') {
|
|
115
|
+
window["${componentName}"] = exposed;
|
|
116
|
+
}
|
|
117
|
+
return exposed;
|
|
118
|
+
`
|
|
119
|
+
: 'return {};';
|
|
120
|
+
const finalCode = `
|
|
121
|
+
${bootImports}
|
|
122
|
+
${autoImportStmt}
|
|
123
|
+
${imports.join('\n')}
|
|
124
|
+
|
|
125
|
+
export default ${hasDefineComponent ? 'defineComponent' : '_defineComponent'}({
|
|
126
|
+
setup() {
|
|
127
|
+
${statements.join('\n')}
|
|
128
|
+
${bindingString}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
`;
|
|
132
|
+
if (isTs) {
|
|
133
|
+
// Calculate line offset for Source Maps (Padding Strategy)
|
|
134
|
+
// Find how many newlines are before the script content in the original source
|
|
135
|
+
// This is a rough estimation but better than nothing for 1.0 dev
|
|
136
|
+
const linesBefore = descriptor.source.substring(0, script.start).split('\n').length - 1;
|
|
137
|
+
const padding = '\n'.repeat(linesBefore);
|
|
138
|
+
// Pad the content so line numbers match original file
|
|
139
|
+
// Note: This only works perfectly if we are transpiling the RAW content.
|
|
140
|
+
// Since we are extracting/transforming (Natural Reactivity), line numbers might shift.
|
|
141
|
+
// For accurate 1-to-1 mapping, we rely on TS source maps of the 'finalCode'.
|
|
142
|
+
// To make 'finalCode' map back to 'filename', we need to construct it carefully.
|
|
143
|
+
// Transpile extracted TS code to JS
|
|
144
|
+
const result = ts.transpileModule(finalCode, {
|
|
145
|
+
compilerOptions: {
|
|
146
|
+
module: ts.ModuleKind.ESNext,
|
|
147
|
+
target: ts.ScriptTarget.ES2019,
|
|
148
|
+
sourceMap: true, // ENABLE SOURCE MAPS
|
|
149
|
+
inlineSources: true,
|
|
150
|
+
sourceRoot: '/',
|
|
151
|
+
},
|
|
152
|
+
fileName: filename // Important for source map
|
|
153
|
+
});
|
|
154
|
+
// Fix: Remove the //# sourceMappingURL= comment added by TS
|
|
155
|
+
const codeWithoutMapComment = result.outputText.replace(/\/\/# sourceMappingURL=.*$/gm, '');
|
|
156
|
+
// Enhance Source Map to show ORIGINAL .mujs content
|
|
157
|
+
let finalMap = result.sourceMapText;
|
|
158
|
+
if (finalMap) {
|
|
159
|
+
try {
|
|
160
|
+
const mapObj = JSON.parse(finalMap);
|
|
161
|
+
// Try to get relative path from 'src' to preserve folder structure in DevTools
|
|
162
|
+
const normalizedPath = filename.replace(/\\/g, '/');
|
|
163
|
+
const srcIndex = normalizedPath.indexOf('/src/');
|
|
164
|
+
// If inside src, use path starting from src. Else just basename.
|
|
165
|
+
// Example: c:/.../src/pages/Home.mujs -> src/pages/Home.mujs
|
|
166
|
+
let relativePath = srcIndex !== -1
|
|
167
|
+
? normalizedPath.substring(srcIndex + 1)
|
|
168
|
+
: filename.split(/[/\\]/).pop() || 'unknown.mujs';
|
|
169
|
+
// Use relative path directly. Webpack will handle the namespace.
|
|
170
|
+
mapObj.sources = [relativePath];
|
|
171
|
+
mapObj.sourcesContent = [descriptor.source];
|
|
172
|
+
finalMap = JSON.stringify(mapObj);
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
console.warn('[MulanJS] Failed to patch source map:', e);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
code: codeWithoutMapComment,
|
|
180
|
+
bindings,
|
|
181
|
+
errors: [],
|
|
182
|
+
map: finalMap // Return the enhanced map
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
code: finalCode,
|
|
187
|
+
bindings,
|
|
188
|
+
errors: [],
|
|
189
|
+
map: undefined
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// --- Natural Reactivity Transformer ---
|
|
193
|
+
function transformNaturalReactivity(code, isTs) {
|
|
194
|
+
// If no '$(' or '$q(' or '$qr(' usage, return original code for safety/speed
|
|
195
|
+
if (!code.includes('$(') && !code.includes('$q(') && !code.includes('$qr('))
|
|
196
|
+
return code;
|
|
197
|
+
const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
|
|
198
|
+
const reactiveVars = new Set();
|
|
199
|
+
const quantumVars = new Set();
|
|
200
|
+
const registerVars = new Set();
|
|
201
|
+
// Pass 1: Identification
|
|
202
|
+
function findReactiveVars(node) {
|
|
203
|
+
if (ts.isVariableStatement(node)) {
|
|
204
|
+
node.declarationList.declarations.forEach(decl => {
|
|
205
|
+
if (decl.initializer && ts.isCallExpression(decl.initializer)) {
|
|
206
|
+
const expr = decl.initializer.expression;
|
|
207
|
+
if (ts.isIdentifier(expr)) {
|
|
208
|
+
if (expr.text === '$') {
|
|
209
|
+
if (ts.isIdentifier(decl.name))
|
|
210
|
+
reactiveVars.add(decl.name.text);
|
|
211
|
+
}
|
|
212
|
+
else if (expr.text === '$q') {
|
|
213
|
+
if (ts.isIdentifier(decl.name))
|
|
214
|
+
quantumVars.add(decl.name.text);
|
|
215
|
+
}
|
|
216
|
+
else if (expr.text === '$qr') {
|
|
217
|
+
if (ts.isIdentifier(decl.name))
|
|
218
|
+
registerVars.add(decl.name.text);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
ts.forEachChild(node, findReactiveVars);
|
|
225
|
+
}
|
|
226
|
+
findReactiveVars(sourceFile);
|
|
227
|
+
if (reactiveVars.size === 0 && quantumVars.size === 0 && registerVars.size === 0)
|
|
228
|
+
return code;
|
|
229
|
+
// Pass 2: Transformation
|
|
230
|
+
const transformerFactory = (context) => {
|
|
231
|
+
return (rootNode) => {
|
|
232
|
+
function visit(node) {
|
|
233
|
+
if (ts.isVariableStatement(node)) {
|
|
234
|
+
const newDecls = [];
|
|
235
|
+
let changed = false;
|
|
236
|
+
node.declarationList.declarations.forEach(decl => {
|
|
237
|
+
if (ts.isIdentifier(decl.name)) {
|
|
238
|
+
const name = decl.name.text;
|
|
239
|
+
if (reactiveVars.has(name) || quantumVars.has(name) || registerVars.has(name)) {
|
|
240
|
+
changed = true;
|
|
241
|
+
let hookName = 'muState';
|
|
242
|
+
if (quantumVars.has(name))
|
|
243
|
+
hookName = 'muQubit';
|
|
244
|
+
else if (registerVars.has(name))
|
|
245
|
+
hookName = 'muRegister';
|
|
246
|
+
const init = decl.initializer;
|
|
247
|
+
newDecls.push(ts.factory.updateVariableDeclaration(decl, decl.name, decl.exclamationToken, decl.type, ts.factory.createCallExpression(ts.factory.createIdentifier(hookName), undefined, init.arguments)));
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
newDecls.push(decl);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
newDecls.push(decl);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
if (changed) {
|
|
258
|
+
return ts.factory.createVariableStatement(node.modifiers, ts.factory.createVariableDeclarationList(newDecls, ts.NodeFlags.Const));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (ts.isCallExpression(node)) {
|
|
262
|
+
const expr = node.expression;
|
|
263
|
+
if (ts.isIdentifier(expr)) {
|
|
264
|
+
const name = expr.text;
|
|
265
|
+
const isMacro = name === '$' || name === '$q' || name === '$qr';
|
|
266
|
+
if (isMacro) {
|
|
267
|
+
// If this call is the initializer of a variable declaration being handled,
|
|
268
|
+
// it will be transformed by the VariableStatement logic above.
|
|
269
|
+
// However, we want to transform it here if it's used as a STANDALONE expression (e.g. assignment).
|
|
270
|
+
const parent = node.parent;
|
|
271
|
+
const isDeclarationInit = ts.isVariableDeclaration(parent) && parent.initializer === node;
|
|
272
|
+
if (!isDeclarationInit) {
|
|
273
|
+
let hookName = 'muState';
|
|
274
|
+
if (name === '$q')
|
|
275
|
+
hookName = 'muQubit';
|
|
276
|
+
else if (name === '$qr')
|
|
277
|
+
hookName = 'muRegister';
|
|
278
|
+
// Transform $(x) -> muState(x).value
|
|
279
|
+
return ts.factory.createPropertyAccessExpression(ts.factory.createCallExpression(ts.factory.createIdentifier(hookName), undefined, node.arguments), ts.factory.createIdentifier('value'));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (ts.isIdentifier(node)) {
|
|
285
|
+
const name = node.text;
|
|
286
|
+
if (reactiveVars.has(name) || quantumVars.has(name) || registerVars.has(name)) {
|
|
287
|
+
const parent = node.parent;
|
|
288
|
+
if (ts.isVariableDeclaration(parent) && parent.name === node)
|
|
289
|
+
return node;
|
|
290
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === node)
|
|
291
|
+
return node;
|
|
292
|
+
if (ts.isPropertyAccessExpression(parent) && parent.expression === node && parent.name.text === 'value')
|
|
293
|
+
return node;
|
|
294
|
+
if (ts.isPropertyAssignment(parent) && parent.name === node)
|
|
295
|
+
return node;
|
|
296
|
+
// EXEMPTION: Do not add .value if the identifier is a direct argument to a Mulan hook
|
|
297
|
+
// These hooks (muGate, muMeasure, etc.) expect the signal wrapper.
|
|
298
|
+
if (ts.isCallExpression(parent) && ts.isIdentifier(parent.expression)) {
|
|
299
|
+
const hook = parent.expression.text;
|
|
300
|
+
if (COMMON_HOOKS.includes(hook))
|
|
301
|
+
return node;
|
|
302
|
+
}
|
|
303
|
+
return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), ts.factory.createIdentifier('value'));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return ts.visitEachChild(node, visit, context);
|
|
307
|
+
}
|
|
308
|
+
return ts.visitNode(rootNode, visit);
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
const result = ts.transform(sourceFile, [transformerFactory]);
|
|
312
|
+
const printer = ts.createPrinter();
|
|
313
|
+
return printer.printFile(result.transformed[0]);
|
|
314
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseMUJS = parseMUJS;
|
|
4
|
+
/**
|
|
5
|
+
* Standardized State-Machine Parser for MulanJS
|
|
6
|
+
* Parses .mujs files safely, handling attributes, quotes, and nested content.
|
|
7
|
+
*/
|
|
8
|
+
function parseMUJS(source, filename) {
|
|
9
|
+
const descriptor = {
|
|
10
|
+
filename,
|
|
11
|
+
source,
|
|
12
|
+
template: null,
|
|
13
|
+
script: null,
|
|
14
|
+
scripts: [],
|
|
15
|
+
styles: [],
|
|
16
|
+
customBlocks: []
|
|
17
|
+
};
|
|
18
|
+
let cursor = 0;
|
|
19
|
+
const length = source.length;
|
|
20
|
+
while (cursor < length) {
|
|
21
|
+
// Look for start tag '<'
|
|
22
|
+
const start = source.indexOf('<', cursor);
|
|
23
|
+
if (start === -1)
|
|
24
|
+
break; // End of file
|
|
25
|
+
// Must be safe start (not in comment/string - simplified for top-level blocks)
|
|
26
|
+
// We assume SFC top-level blocks are valid HTML-like tags.
|
|
27
|
+
// Check if closing tag (ignore)
|
|
28
|
+
if (source[start + 1] === '/') {
|
|
29
|
+
cursor = start + 1;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Parse Tag Name
|
|
33
|
+
let nameEnd = start + 1;
|
|
34
|
+
while (nameEnd < length && !/\s|>/.test(source[nameEnd])) {
|
|
35
|
+
nameEnd++;
|
|
36
|
+
}
|
|
37
|
+
const tagName = source.slice(start + 1, nameEnd);
|
|
38
|
+
// Parse Attributes
|
|
39
|
+
let attrStart = nameEnd;
|
|
40
|
+
const attrs = {};
|
|
41
|
+
let tagEnd = source.indexOf('>', attrStart);
|
|
42
|
+
if (tagEnd === -1)
|
|
43
|
+
break;
|
|
44
|
+
const attrString = source.slice(attrStart, tagEnd);
|
|
45
|
+
// Simple regex for attrs is safe *inside* the tag definition
|
|
46
|
+
const attrRegex = /([a-zA-Z0-9:-]+)(?:=["']([^"']*)["'])?/g;
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = attrRegex.exec(attrString)) !== null) {
|
|
49
|
+
attrs[match[1]] = match[2] || 'true';
|
|
50
|
+
}
|
|
51
|
+
// Find Closing Tag
|
|
52
|
+
// We need to handle nested content.
|
|
53
|
+
// For <template>, we just look for </template>.
|
|
54
|
+
// For <script>, same.
|
|
55
|
+
// "Naive" approach: indexOf('</' + tagName) is actually standard for SFC parsers
|
|
56
|
+
// because top-level blocks shouldn't contain their own closing tag as text
|
|
57
|
+
// (except unless escaped, which we can handle if needed, but rarely an issue for top-level).
|
|
58
|
+
const contentStart = tagEnd + 1;
|
|
59
|
+
const closeTag = `</${tagName}>`;
|
|
60
|
+
const contentEnd = source.indexOf(closeTag, contentStart);
|
|
61
|
+
if (contentEnd === -1) {
|
|
62
|
+
// Unclosed block, invalid SFC.
|
|
63
|
+
console.warn(`[MulanJS Parser] Unclosed block: <${tagName}>`);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
const content = source.slice(contentStart, contentEnd);
|
|
67
|
+
const block = {
|
|
68
|
+
type: tagName,
|
|
69
|
+
content: content.trim(), // Trim content for cleanliness
|
|
70
|
+
attrs,
|
|
71
|
+
start: start,
|
|
72
|
+
end: contentEnd + closeTag.length
|
|
73
|
+
};
|
|
74
|
+
// Add to descriptor
|
|
75
|
+
if (tagName === 'template' || tagName === 'mu-template') {
|
|
76
|
+
descriptor.template = block;
|
|
77
|
+
}
|
|
78
|
+
else if (tagName === 'script') {
|
|
79
|
+
descriptor.scripts.push(block);
|
|
80
|
+
if (attrs.setup || !descriptor.script) {
|
|
81
|
+
descriptor.script = block;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (tagName === 'style') {
|
|
85
|
+
descriptor.styles.push(block);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
descriptor.customBlocks.push(block);
|
|
89
|
+
}
|
|
90
|
+
cursor = contentEnd + closeTag.length;
|
|
91
|
+
}
|
|
92
|
+
return descriptor;
|
|
93
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compileStyle = compileStyle;
|
|
4
|
+
function compileStyle(descriptor, filename) {
|
|
5
|
+
const styles = descriptor.styles;
|
|
6
|
+
if (!styles || styles.length === 0)
|
|
7
|
+
return { css: '', errors: [] };
|
|
8
|
+
let combinedCss = '';
|
|
9
|
+
const scopedId = 'data-v-' + hash(filename);
|
|
10
|
+
styles.forEach(style => {
|
|
11
|
+
let content = style.content;
|
|
12
|
+
if (style.attrs.scoped) {
|
|
13
|
+
// SCSS Compilation
|
|
14
|
+
if (style.attrs.lang === 'scss' || style.attrs.lang === 'sass') {
|
|
15
|
+
try {
|
|
16
|
+
const sass = require('sass');
|
|
17
|
+
const result = sass.compileString(content, {
|
|
18
|
+
syntax: style.attrs.lang === 'sass' ? 'indented' : 'scss'
|
|
19
|
+
});
|
|
20
|
+
content = result.css;
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
return { css: '', errors: [`SCSS Compilation Error: ${e.message}`] };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Rewrite selectors
|
|
27
|
+
// Very naive regex replacer: "selector {" -> "selector[data-v-id] {"
|
|
28
|
+
// This is fragile but works for a PoC.
|
|
29
|
+
content = content.replace(/([^\r\n,{}]+)(?=\{)/g, (match) => {
|
|
30
|
+
const selectors = match.split(',');
|
|
31
|
+
return selectors.map(s => {
|
|
32
|
+
const selector = s.trim();
|
|
33
|
+
if (selector.startsWith('@') || selector === 'from' || selector === 'to')
|
|
34
|
+
return selector; // Skip keyframes/media
|
|
35
|
+
return `${selector}[${scopedId}]`;
|
|
36
|
+
}).join(', ');
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
combinedCss += content + '\n';
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
css: combinedCss,
|
|
43
|
+
scopedId,
|
|
44
|
+
errors: []
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Simple hash for ID generation
|
|
48
|
+
function hash(str) {
|
|
49
|
+
let hash = 0;
|
|
50
|
+
for (let i = 0; i < str.length; i++) {
|
|
51
|
+
const char = str.charCodeAt(i);
|
|
52
|
+
hash = (hash << 5) - hash + char;
|
|
53
|
+
hash &= hash; // Convert to 32bit integer
|
|
54
|
+
}
|
|
55
|
+
return Math.abs(hash).toString(36);
|
|
56
|
+
}
|