@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,442 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileTemplate = compileTemplate;
4
+ function compileTemplate(descriptor, scriptResult, scopedId) {
5
+ console.log(`[MulanJS Compiler v1.0.1-dev.2] Compiling template for: ${descriptor.filename || 'anonymous'}`);
6
+ const template = descriptor.template;
7
+ if (!template)
8
+ return { code: 'function render() { return ""; }', errors: [] };
9
+ let html = template.content;
10
+ const errors = [];
11
+ // 1. Parsing Phase (HTML -> AST)
12
+ const ast = parse(html, errors);
13
+ // 2. Transform Phase (Scopes, Bindings)
14
+ transform(ast, scriptResult, scopedId, [], descriptor.filename);
15
+ // 3. Codegen Phase (AST -> JS Function)
16
+ const code = generate(ast, scriptResult.bindings || []);
17
+ return {
18
+ code: `function render() {
19
+ const _s = (v) => (v && typeof v === 'object' && 'value' in v) ? v.value : v;
20
+ const _h = (v, raw) => {
21
+ const val = _s(v);
22
+ if (raw || typeof val !== 'string') return val;
23
+ // IRON FORTRESS: Automatic XSS Shield
24
+ if (typeof Mulan !== 'undefined' && Mulan.Security) {
25
+ return Mulan.Security.sanitize(val);
26
+ }
27
+ return val.replace(/</g, '&lt;').replace(/>/g, '&gt;');
28
+ };
29
+ return ${code};
30
+ }`,
31
+ errors
32
+ };
33
+ }
34
+ // --- Parser (Recursive Descent) ---
35
+ function parse(template, errors) {
36
+ // Root Wrapper
37
+ const root = {
38
+ type: 'Element',
39
+ tag: 'fragment', // Virtual root
40
+ props: {},
41
+ children: [],
42
+ directives: {}
43
+ };
44
+ const stack = [root];
45
+ let cursor = 0;
46
+ while (cursor < template.length) {
47
+ const char = template[cursor];
48
+ if (template.startsWith('<!--', cursor)) {
49
+ // Comment <!-- ... -->
50
+ const end = template.indexOf('-->', cursor);
51
+ if (end === -1)
52
+ break;
53
+ // Just skip comments or keep them?
54
+ // Let's keep them as raw strings to preserve output structure if needed,
55
+ // but usually valid HTML comments shouldn't affect structure.
56
+ // For now, let's just append them to the previous text node or create a text node.
57
+ // Actually, simply ignoring them is safer for logic, but might remove user comments.
58
+ // Better: treat as Text so they are emitted as-is.
59
+ const content = template.slice(cursor, end + 3);
60
+ stack[stack.length - 1].children.push({ type: 'Text', content });
61
+ cursor = end + 3;
62
+ }
63
+ else if (char === '<') {
64
+ // Tag
65
+ if (template[cursor + 1] === '/') {
66
+ // Closing Tag </tag>
67
+ const end = template.indexOf('>', cursor);
68
+ if (end === -1)
69
+ break;
70
+ stack.pop();
71
+ cursor = end + 1;
72
+ }
73
+ else {
74
+ // Opening Tag <tag ...>
75
+ const end = template.indexOf('>', cursor);
76
+ if (end === -1)
77
+ break;
78
+ let tagContent = template.slice(cursor + 1, end);
79
+ const isSelfClosing = tagContent.endsWith('/') || ['img', 'br', 'input', 'hr'].includes(tagContent.split(' ')[0]);
80
+ if (tagContent.endsWith('/')) {
81
+ tagContent = tagContent.slice(0, -1).trim();
82
+ }
83
+ const { tag, props, directives } = parseTag(tagContent);
84
+ const element = { type: 'Element', tag, props, children: [], directives };
85
+ stack[stack.length - 1].children.push(element);
86
+ if (!isSelfClosing) {
87
+ stack.push(element);
88
+ }
89
+ cursor = end + 1;
90
+ }
91
+ }
92
+ else if (char === '{' && template[cursor + 1] === '{') {
93
+ // Interpolation {{ }}
94
+ const end = template.indexOf('}}', cursor);
95
+ if (end === -1)
96
+ break; // formatting error
97
+ const content = template.slice(cursor + 2, end).trim();
98
+ stack[stack.length - 1].children.push({ type: 'Interpolation', content });
99
+ cursor = end + 2;
100
+ }
101
+ else {
102
+ // Text
103
+ let nextTag = template.indexOf('<', cursor);
104
+ let nextInterp = template.indexOf('{{', cursor);
105
+ let end = template.length;
106
+ if (nextTag !== -1 && nextTag < end)
107
+ end = nextTag;
108
+ if (nextInterp !== -1 && nextInterp < end)
109
+ end = nextInterp;
110
+ const content = template.slice(cursor, end);
111
+ if (content) {
112
+ stack[stack.length - 1].children.push({ type: 'Text', content });
113
+ }
114
+ cursor = end;
115
+ }
116
+ }
117
+ return root;
118
+ }
119
+ function parseTag(content) {
120
+ // console.log('DEBUG: parseTag content:', content);
121
+ const parts = content.split(' ');
122
+ const tag = parts[0];
123
+ const props = {};
124
+ const directives = {};
125
+ // Resume attribute parsing after tag
126
+ const attrStr = content.slice(tag.length).trim();
127
+ // console.log('DEBUG: attrStr:', attrStr);
128
+ let i = 0;
129
+ while (i < attrStr.length) {
130
+ // Skip spaces
131
+ if (/\s/.test(attrStr[i])) {
132
+ i++;
133
+ continue;
134
+ }
135
+ // Find key
136
+ const keyStart = i;
137
+ while (i < attrStr.length && !/\s|=/.test(attrStr[i])) {
138
+ i++;
139
+ }
140
+ const key = attrStr.slice(keyStart, i);
141
+ // Check for value
142
+ let value = 'true'; // Default for boolean attributes
143
+ // Skip potential spaces before '=' (e.g. class = "foo")
144
+ let peek = i;
145
+ while (peek < attrStr.length && /\s/.test(attrStr[peek]))
146
+ peek++;
147
+ if (peek < attrStr.length && attrStr[peek] === '=') {
148
+ i = peek + 1; // Move past '='
149
+ // Skip spaces after '='
150
+ while (i < attrStr.length && /\s/.test(attrStr[i]))
151
+ i++;
152
+ if (i < attrStr.length && (attrStr[i] === '"' || attrStr[i] === "'")) {
153
+ const quote = attrStr[i];
154
+ i++; // skip quote
155
+ const valStart = i;
156
+ while (i < attrStr.length && attrStr[i] !== quote) {
157
+ if (attrStr[i] === '\\' && attrStr[i + 1] === quote) {
158
+ i += 2;
159
+ }
160
+ else {
161
+ i++;
162
+ }
163
+ }
164
+ value = attrStr.slice(valStart, i);
165
+ i++; // skip closing quote
166
+ }
167
+ else {
168
+ // unquoted value
169
+ const valStart = i;
170
+ while (i < attrStr.length && !/\s/.test(attrStr[i])) {
171
+ i++;
172
+ }
173
+ value = attrStr.slice(valStart, i);
174
+ }
175
+ }
176
+ else {
177
+ // Boolean attribute, no value
178
+ // i remains at end of key
179
+ }
180
+ // Store
181
+ if (key === 'v-if' || key === 'mu-if') {
182
+ directives.vIf = value;
183
+ }
184
+ else if (key === 'v-for' || key === 'mu-for') {
185
+ const parts = value.split(' in ');
186
+ if (parts.length < 2) {
187
+ console.warn(`[MulanJS Compiler] Warning: Invalid loop expression "${value}". Expected "item in list".`);
188
+ directives.vFor = { item: '_item', list: '[]' }; // Fallback with safe identifier
189
+ }
190
+ else {
191
+ const item = parts[0];
192
+ const list = parts.slice(1).join(' in '); // Join rest in case list has 'in'
193
+ directives.vFor = { item: item.trim(), list: list.trim() };
194
+ }
195
+ }
196
+ else if (key) {
197
+ props[key] = value;
198
+ }
199
+ }
200
+ return { tag, props, directives };
201
+ }
202
+ // --- Transformer ---
203
+ const JS_KEYWORDS = new Set([
204
+ 'true', 'false', 'null', 'undefined', 'this', 'window',
205
+ 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break', 'return',
206
+ 'var', 'let', 'const', 'new', 'function', 'class', 'import', 'export',
207
+ 'typeof', 'instanceof', 'void', 'delete', 'in', 'of', 'try', 'catch', 'throw', 'finally',
208
+ 'debugger', 'super', 'extends', 'async', 'await', 'yield'
209
+ ]);
210
+ const GLOBALS = new Set([
211
+ 'Math', 'Date', 'String', 'Number', 'Boolean', 'Object', 'Array', 'JSON',
212
+ 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Promise', 'Symbol', 'Error',
213
+ 'console', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'encodeURIComponent', 'decodeURIComponent'
214
+ ]);
215
+ function transform(node, scriptResult, scopedId, localScope = [], filename) {
216
+ var _a;
217
+ if (node.type === 'Element') {
218
+ const element = node;
219
+ // Scoped ID
220
+ if (scopedId && element.tag !== 'fragment') {
221
+ element.props[scopedId] = '';
222
+ }
223
+ // IRON FORTRESS: Detect mu-raw/v-raw for XSS bypass
224
+ const isRaw = 'mu-raw' in element.props || 'v-raw' in element.props;
225
+ if (isRaw) {
226
+ delete element.props['mu-raw'];
227
+ delete element.props['v-raw'];
228
+ }
229
+ // 0. Update Local Scope for Children (v-for)
230
+ const childScope = [...localScope];
231
+ if (element.directives.vFor) {
232
+ childScope.push(element.directives.vFor.item);
233
+ }
234
+ // Bindings (Attributes)
235
+ const propertyBindings = []; // Stores generated side-effect code (props and events)
236
+ for (const key in element.props) {
237
+ // 0. Property Binding: .columns="${state.cols}"
238
+ if (key.startsWith('.')) {
239
+ if (!element.props['data-mu-id']) {
240
+ const id = 'mu_' + Math.random().toString(36).substr(2, 9);
241
+ element.props['data-mu-id'] = id;
242
+ }
243
+ const id = element.props['data-mu-id'];
244
+ const propName = key.slice(1);
245
+ const rawValue = element.props[key];
246
+ let expr = "''";
247
+ if (rawValue.startsWith('${') && rawValue.endsWith('}')) {
248
+ const inner = rawValue.slice(2, -1);
249
+ expr = processBindings(inner, scriptResult.bindings, localScope);
250
+ }
251
+ else {
252
+ expr = JSON.stringify(rawValue);
253
+ }
254
+ propertyBindings.push(`this._b('${id}', '${propName}', ${expr})`);
255
+ delete element.props[key];
256
+ }
257
+ // 1. Event Handlers: @click="count++", v-on:click="toggle", or onclick="increment()"
258
+ else if (key.startsWith('@') || key.startsWith('v-on:') || (key.startsWith('on') && key.length > 2)) {
259
+ if (!element.props['data-mu-id']) {
260
+ const id = 'mu_' + Math.random().toString(36).substr(2, 9);
261
+ element.props['data-mu-id'] = id;
262
+ }
263
+ const id = element.props['data-mu-id'];
264
+ let eventName = '';
265
+ if (key.startsWith('@'))
266
+ eventName = key.slice(1);
267
+ else if (key.startsWith('v-on:'))
268
+ eventName = key.slice(5);
269
+ else
270
+ eventName = key.slice(2); // standard 'on' prefix (onclick -> click)
271
+ const rawHandler = element.props[key];
272
+ let bound = processBindings(rawHandler, scriptResult.bindings, localScope);
273
+ // Wrap in anonymous function if it looks like a statement or expression with side effects
274
+ // Simple heuristic: if it has parentheses and isn't a simple function reference
275
+ const finalHandler = (bound.includes('(') || bound.includes('=') || bound.includes('++'))
276
+ ? `($event) => { ${bound} }`
277
+ : bound;
278
+ propertyBindings.push(`this._e('${id}', '${eventName}', ${finalHandler})`);
279
+ delete element.props[key];
280
+ }
281
+ // 2. Standard Attributes Interpolation: class="{{ active }}"
282
+ else {
283
+ let rawValue = element.props[key];
284
+ // Check for {{ }} -> convert to ${ }
285
+ if (rawValue.includes('{{')) {
286
+ // Capture content and wrap in ${}, trimming whitespace
287
+ rawValue = rawValue.replace(/\{\{\s*(.*?)\s*\}\}/g, '${$1}');
288
+ }
289
+ // Check for ${ } -> process internal bindings
290
+ if (rawValue.includes('${')) {
291
+ // It's a template literal now
292
+ element.props[key] = rawValue.replace(/\$\{(.*?)\}/g, (_, expr) => {
293
+ const bound = processBindings(expr, scriptResult.bindings, localScope);
294
+ return '${_h(' + bound + ')}';
295
+ });
296
+ }
297
+ }
298
+ // ... (rest of attributes)
299
+ }
300
+ // Store side-effects on the node for the Generator to use
301
+ if (propertyBindings.length > 0) {
302
+ // We'll attach it to a temporary property on the AST node
303
+ element._propertySideEffects = propertyBindings;
304
+ }
305
+ element.children.forEach(child => {
306
+ if (isRaw && (child.type === 'Interpolation' || child.type === 'Text')) {
307
+ child.raw = true;
308
+ }
309
+ transform(child, scriptResult, scopedId, childScope, filename);
310
+ });
311
+ }
312
+ else if (node.type === 'Interpolation') {
313
+ const interpolation = node;
314
+ interpolation.content = processBindings(interpolation.content, scriptResult.bindings, localScope);
315
+ }
316
+ else if (node.type === 'Text') {
317
+ const text = node;
318
+ // Support native template literals: ${ expr }
319
+ if (text.content.includes('${')) {
320
+ const componentName = filename ? ((_a = filename.split(/[/\\]/).pop()) === null || _a === void 0 ? void 0 : _a.split('.')[0].replace(/\W/g, '')) || 'App' : 'App';
321
+ const bindPrefix = `window['${componentName}'].`;
322
+ text.content = text.content.replace(/\$\{(.*?)\}/g, (_, expr) => {
323
+ let bound = processBindings(expr, scriptResult.bindings, localScope);
324
+ // If processBindings added 'this.', replace it with global access for maximum safety in string templates
325
+ bound = bound.replace(/this\./g, bindPrefix);
326
+ return '${_h(' + bound + ')}';
327
+ });
328
+ }
329
+ }
330
+ }
331
+ function processBindings(exp, bindings, localScope) {
332
+ // Strategy:
333
+ // 1. Identifiers in 'bindings' (Setup API) -> prefix this.
334
+ // 2. Identifiers in 'localScope' (v-for) -> keep as is.
335
+ // 3. If 'bindings' is empty (Options API), prefix everything NOT in localScope/Keywords/Globals.
336
+ const isOptionsAPI = !bindings || bindings.length === 0;
337
+ const bindingSet = new Set(bindings || []);
338
+ // Regex for identifiers
339
+ return exp.replace(/\b([a-zA-Z_$][\w$]*)\b/g, (match, id, offset, str) => {
340
+ // 1. Skip if property access (dot before) (e.g. user.name -> user is checked, name is skipped)
341
+ if (offset > 0 && str[offset - 1] === '.')
342
+ return match;
343
+ // 2. Skip object key (colon after) (e.g. { name: val } -> name skipped)
344
+ // Simple check: next char is ':'
345
+ let i = offset + match.length;
346
+ while (i < str.length && /\s/.test(str[i]))
347
+ i++;
348
+ if (str[i] === ':' && str[i + 1] !== '=')
349
+ return match; // : but not := (not that valid in JS contexts usually but safe)
350
+ // 3. Skip Keywords / Globals / Local Vars
351
+ if (JS_KEYWORDS.has(id))
352
+ return match;
353
+ if (GLOBALS.has(id))
354
+ return match;
355
+ if (localScope.includes(id))
356
+ return match;
357
+ // 4. Decision
358
+ if (isOptionsAPI) {
359
+ // Options API: Prefix everything unknown
360
+ return `this.${id}`;
361
+ }
362
+ else {
363
+ // Setup API: Only prefix explicit bindings
364
+ if (bindingSet.has(id)) {
365
+ return `this.${id}`;
366
+ }
367
+ }
368
+ return match;
369
+ });
370
+ }
371
+ // --- Generator ---
372
+ function generate(node, bindings, localScope = []) {
373
+ if (node.type === 'Text') {
374
+ const text = node;
375
+ // Escape backticks
376
+ return `\`${text.content.replace(/`/g, '\\`')}\``;
377
+ }
378
+ if (node.type === 'Interpolation') {
379
+ const interp = node;
380
+ const isRaw = node.raw === true;
381
+ return `String(_h(${interp.content}, ${isRaw}))`; // Safe cast with Heimdall Shield
382
+ }
383
+ if (node.type === 'Element') {
384
+ const element = node;
385
+ // Update Local Scope for Children (v-for)
386
+ const childScope = [...localScope];
387
+ if (element.directives.vFor) {
388
+ childScope.push(element.directives.vFor.item);
389
+ }
390
+ if (element.tag === 'fragment') {
391
+ return element.children.map(c => generate(c, bindings, childScope)).join(' + '); // Root fragment join
392
+ }
393
+ // Directives
394
+ if (element.directives.vIf) {
395
+ const condition = element.directives.vIf;
396
+ // Generate ternary: cond ? render() : ''
397
+ const children = element.children.map(c => generate(c, bindings, childScope)).join(' + ') || '""';
398
+ const open = `<${element.tag}${genProps(element.props)}>`;
399
+ const close = `</${element.tag}>`;
400
+ // Apply bindings to v-if condition
401
+ // FIX: Wrap condition in _s() to unwrap signals (handling 'isOpen' vs 'isOpen.value')
402
+ return `(_s(${processBindings(condition, bindings, localScope)}) ? \`${open}\` + (${children}) + \`${close}\` : "")`;
403
+ }
404
+ if (element.directives.vFor) {
405
+ const { item, list } = element.directives.vFor;
406
+ // list.map(item => render).join('')
407
+ // Apply bindings to the list expression
408
+ // FIX: Wrap list in _s() to ensure we map over the Array, not the Signal Object
409
+ const boundList = `_s(${processBindings(list, bindings, localScope)})`;
410
+ const children = element.children.map(c => generate(c, bindings, childScope)).join(' + ') || '""';
411
+ const open = `<${element.tag}${genProps(element.props)}>`;
412
+ const close = `</${element.tag}>`;
413
+ // Add safety check: ensure boundList is an Array before mapping
414
+ return `(Array.isArray(${boundList}) ? ${boundList}.map(${item} => \`${open}\` + (${children}) + \`${close}\`).join('') : "")`;
415
+ }
416
+ // Standard Element
417
+ const children = element.children.map(c => generate(c, bindings, childScope)).join(' + ') || '""';
418
+ const open = `<${element.tag}${genProps(element.props)}>`;
419
+ const close = `</${element.tag}>`;
420
+ let code = `\`${open}\` + (${children}) + \`${close}\``;
421
+ // Inject Property Binding Side Effects using Comma Operator
422
+ // (this._b(..), this._b(..), `html`)
423
+ if (element._propertySideEffects) {
424
+ const effects = element._propertySideEffects.join(', ');
425
+ code = `(${effects}, ${code})`;
426
+ }
427
+ return code;
428
+ }
429
+ return '""';
430
+ }
431
+ function genProps(props) {
432
+ let str = '';
433
+ for (const key in props) {
434
+ str += ` ${key}`;
435
+ if (props[key] !== '') {
436
+ // Escape double quotes in value
437
+ const escaped = props[key].replace(/"/g, '&quot;');
438
+ str += `="${escaped}"`;
439
+ }
440
+ }
441
+ return str;
442
+ }