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