@mulanjs/mulanjs 1.0.1-dev.20260227135307 → 1.0.1-dev.20260227173253
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler/ast-parser.d.ts +48 -0
- package/dist/compiler/ast-parser.js +54 -9
- package/dist/compiler/compiler.d.ts +9 -0
- package/dist/compiler/compiler.js +45 -2
- package/dist/compiler/dom-compiler.d.ts +7 -0
- package/dist/compiler/dom-compiler.js +90 -33
- package/dist/compiler/script-compiler.d.ts +11 -0
- package/dist/compiler/script-compiler.js +108 -116
- package/dist/compiler/sfc-parser.d.ts +21 -0
- package/dist/compiler/ssr-compiler.d.ts +7 -0
- package/dist/compiler/ssr-compiler.js +142 -0
- package/dist/compiler/style-compiler.d.ts +8 -0
- package/dist/compiler/template-compiler.d.ts +8 -0
- package/dist/components/bloch-sphere.js +9 -3
- package/dist/components/infinity-list.js +7 -1
- package/dist/core/component.js +66 -15
- package/dist/core/hooks.js +53 -29
- package/dist/core/quantum.js +30 -17
- package/dist/core/query.js +11 -6
- package/dist/core/reactive.js +88 -7
- package/dist/core/renderer.js +9 -8
- package/dist/core/ssr.js +50 -0
- package/dist/core/surge.js +7 -2
- package/dist/core/vault.js +9 -5
- package/dist/index.js +63 -27
- package/dist/mulan.esm.js +187 -19
- package/dist/mulan.esm.js.map +1 -1
- package/dist/mulan.js +1890 -1590
- package/dist/mulan.js.map +1 -1
- package/dist/router/index.js +17 -10
- package/dist/security/sanitizer.js +5 -1
- package/dist/store/index.js +9 -5
- package/dist/types/ast-parser.d.ts +2 -0
- package/dist/types/compiler/ast-parser.d.ts +2 -0
- package/dist/types/compiler/compiler.d.ts +1 -0
- package/dist/types/compiler/ssr-compiler.d.ts +7 -0
- package/dist/types/compiler.d.ts +1 -0
- package/dist/types/components/bloch-sphere.d.ts +3 -1
- package/dist/types/components/infinity-list.d.ts +3 -1
- package/dist/types/core/component.d.ts +14 -0
- package/dist/types/core/reactive.d.ts +5 -1
- package/dist/types/core/renderer.d.ts +0 -1
- package/dist/types/core/ssr.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/ssr-compiler.d.ts +7 -0
- package/package.json +1 -1
- package/src/compiler/ast-parser.ts +62 -10
- package/src/compiler/compiler.ts +46 -1
- package/src/compiler/dom-compiler.ts +100 -34
- package/src/compiler/script-compiler.ts +117 -126
- package/src/compiler/ssr-compiler.ts +157 -0
- package/src/loader/index.js +12 -19
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { SFCDescriptor } from './sfc-parser';
|
|
2
|
+
import { ScriptCompileResult } from './script-compiler';
|
|
3
|
+
import { parse, Node, ElementNode, TextNode, InterpolationNode } from './ast-parser';
|
|
4
|
+
|
|
5
|
+
export interface SSRCompileResult {
|
|
6
|
+
code: string;
|
|
7
|
+
errors: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function compileToSSR(descriptor: SFCDescriptor, scriptResult: ScriptCompileResult, scopedId?: string): SSRCompileResult {
|
|
11
|
+
const template = descriptor.template;
|
|
12
|
+
if (!template) return { code: 'function render() { return ""; }', errors: [] };
|
|
13
|
+
|
|
14
|
+
const errors: string[] = [];
|
|
15
|
+
const ast = parse(template.content, errors);
|
|
16
|
+
|
|
17
|
+
// Simplistic Transformer (Options API bindings mapping)
|
|
18
|
+
const bindings = scriptResult.bindings || [];
|
|
19
|
+
const localScope: string[] = [];
|
|
20
|
+
|
|
21
|
+
let uidRef = { current: 0 };
|
|
22
|
+
const getUid = () => uidRef.current++;
|
|
23
|
+
|
|
24
|
+
let codeChunks: string[] = [];
|
|
25
|
+
ast.children.forEach(child => {
|
|
26
|
+
const chunk = generateSSRInstruction(child, bindings, localScope, getUid);
|
|
27
|
+
if (chunk) codeChunks.push(chunk);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// The render function generates a giant template literal
|
|
31
|
+
const bodyFn = `return \`${codeChunks.join('')}\`;`;
|
|
32
|
+
|
|
33
|
+
const renderFn = `function render() {
|
|
34
|
+
const _s = (v) => (v && typeof v === 'object' && 'value' in v) ? v.value : (v === null || v === undefined ? '' : v);
|
|
35
|
+
const _h = (v) => {
|
|
36
|
+
const val = _s(v);
|
|
37
|
+
if (val === null || val === undefined) return '';
|
|
38
|
+
if (typeof val !== 'string') return val;
|
|
39
|
+
if (typeof Mulan !== 'undefined' && Mulan.Security) {
|
|
40
|
+
return Mulan.Security.sanitize(val);
|
|
41
|
+
}
|
|
42
|
+
return val.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
${bodyFn}
|
|
46
|
+
}`;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
code: renderFn,
|
|
50
|
+
errors
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function processBindings(exp: string, bindings: string[], localScope: string[]) {
|
|
55
|
+
const isOptionsAPI = !bindings || bindings.length === 0;
|
|
56
|
+
const bindingSet = new Set(bindings || []);
|
|
57
|
+
return exp.replace(/\b([a-zA-Z_$][\w$]*)\b/g, (match, id, offset, str) => {
|
|
58
|
+
if (offset > 0 && str[offset - 1] === '.') return match;
|
|
59
|
+
// simplistic keyword check
|
|
60
|
+
if (['true', 'false', 'null', 'undefined', 'this', 'Math'].includes(id) || localScope.includes(id)) return match;
|
|
61
|
+
if (isOptionsAPI) return `this.${id}`;
|
|
62
|
+
if (bindingSet.has(id)) return `this.${id}`;
|
|
63
|
+
return match;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function generateSSRInstruction(node: Node, bindings: string[], localScope: string[], getUid: () => number): string {
|
|
68
|
+
if (node.type === 'Text') {
|
|
69
|
+
const text = node as TextNode;
|
|
70
|
+
// we can preserve interpolation \${ } because the outer string is also a template literal
|
|
71
|
+
// but we need to ensure the variables map to 'this'
|
|
72
|
+
if (text.content.includes('${')) {
|
|
73
|
+
let processed = text.content.replace(/\$\{(.*?)\}/g, (_: any, expr: string) => {
|
|
74
|
+
return `\${_h(${processBindings(expr, bindings, localScope)})}`;
|
|
75
|
+
});
|
|
76
|
+
return processed;
|
|
77
|
+
}
|
|
78
|
+
return text.content;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (node.type === 'Interpolation') {
|
|
82
|
+
const interp = node as InterpolationNode;
|
|
83
|
+
return `\${_h(${processBindings(interp.content, bindings, localScope)})}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (node.type === 'Element') {
|
|
87
|
+
const el = node as ElementNode;
|
|
88
|
+
const uid = getUid();
|
|
89
|
+
let html = `<${el.tag} data-mu-node-id="${uid}"`;
|
|
90
|
+
|
|
91
|
+
let vIfCondition = '';
|
|
92
|
+
let vForDirective: any = null;
|
|
93
|
+
|
|
94
|
+
for (const key in el.props) {
|
|
95
|
+
if (key === 'v-if' || key === 'mu-if') {
|
|
96
|
+
vIfCondition = processBindings(el.props[key], bindings, localScope);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (key === 'v-for' || key === 'mu-for') {
|
|
100
|
+
const parts = el.props[key].split(' in ');
|
|
101
|
+
if (parts.length >= 2) {
|
|
102
|
+
vForDirective = { item: parts[0].trim(), list: processBindings(parts.slice(1).join(' in ').trim(), bindings, localScope) };
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Client side events are ignored in SSR HTML output
|
|
108
|
+
if (key.startsWith('@') || key.startsWith('v-on:') || key.startsWith('on')) {
|
|
109
|
+
// To support Resumability, we'd inject a serialize marker here
|
|
110
|
+
// html += ` mu-event="${key}"`;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let value = el.props[key];
|
|
115
|
+
if (key.startsWith(':') || key.startsWith('.')) {
|
|
116
|
+
let attrName = key.slice(1);
|
|
117
|
+
let expr = processBindings(value, bindings, localScope);
|
|
118
|
+
html += ` ${attrName}="\${_h(${expr})}"`;
|
|
119
|
+
} else {
|
|
120
|
+
if (value.includes('${')) {
|
|
121
|
+
value = value.replace(/\$\{(.*?)\}/g, (_: any, expr: string) => {
|
|
122
|
+
return `\${_h(${processBindings(expr, bindings, localScope)})}`;
|
|
123
|
+
});
|
|
124
|
+
html += ` ${key}="${value}"`;
|
|
125
|
+
} else {
|
|
126
|
+
html += ` ${key}="${value.replace(/"/g, '"')}"`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const isSelfClosing = ['img', 'br', 'input', 'hr', 'link', 'meta'].includes(el.tag.toLowerCase());
|
|
132
|
+
if (isSelfClosing) {
|
|
133
|
+
html += '/>';
|
|
134
|
+
} else {
|
|
135
|
+
html += '>';
|
|
136
|
+
for (const child of el.children) {
|
|
137
|
+
html += generateSSRInstruction(child, bindings, localScope, getUid);
|
|
138
|
+
}
|
|
139
|
+
html += `</${el.tag}>`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Apply wrappers for Control Flow (v-if / v-for)
|
|
143
|
+
if (vIfCondition) {
|
|
144
|
+
html = `\${(${vIfCondition}) ? \`${html}\` : '<!--mu-if-->'}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (vForDirective) {
|
|
148
|
+
const { item, list } = vForDirective;
|
|
149
|
+
// The mapping function returns strings which we join
|
|
150
|
+
html = `\${(${list}).map((${item}, _index) => \`${html}\`).join('')}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return html;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return '';
|
|
157
|
+
}
|
package/src/loader/index.js
CHANGED
|
@@ -25,34 +25,27 @@ module.exports = function (content) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// Resolve path to the compiled compiler module (
|
|
29
|
-
// Assuming structure: dist/loader/index.js and dist/compiler/compiler.js
|
|
30
|
-
// OR src/loader/index.js and dist/compiler/compiler.js
|
|
31
|
-
// We'll target the dist folder for the compiler.
|
|
28
|
+
// Resolve path to the compiled compiler module (CJS)
|
|
32
29
|
const compilerRef = path.resolve(__dirname, '../../dist/compiler/compiler.js');
|
|
33
|
-
const compilerUrl = pathToFileURL(compilerRef).href;
|
|
34
|
-
|
|
35
|
-
// Use dynamic import to load ESM compiler from CommonJS loader
|
|
36
|
-
import(compilerUrl).then(async compiler => {
|
|
37
|
-
try {
|
|
38
|
-
const result = await compiler.compileSFC(content, filePath, {
|
|
39
|
-
readFileSync: (file) => fs.readFileSync(file, 'utf-8')
|
|
40
|
-
});
|
|
41
30
|
|
|
31
|
+
try {
|
|
32
|
+
const compiler = require(compilerRef);
|
|
33
|
+
compiler.compileSFC(content, filePath, {
|
|
34
|
+
readFileSync: (file) => fs.readFileSync(file, 'utf-8')
|
|
35
|
+
}).then(result => {
|
|
42
36
|
if (result.errors && result.errors.length > 0) {
|
|
43
37
|
this.emitError(new Error(result.errors.join('\n')));
|
|
44
38
|
}
|
|
45
39
|
|
|
46
40
|
// Pass source map to Webpack if available
|
|
47
|
-
const map = result.map ? JSON.parse(result.map) : null;
|
|
41
|
+
const map = result.map ? (typeof result.map === 'string' ? JSON.parse(result.map) : result.map) : null;
|
|
48
42
|
callback(null, result.code, map);
|
|
49
|
-
}
|
|
43
|
+
}).catch(e => {
|
|
50
44
|
callback(e);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
console.error(`[MulanJS Loader] Failed to load compiler from ${compilerUrl}`);
|
|
45
|
+
});
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(`[MulanJS Loader] Failed to load compiler from ${compilerRef}`);
|
|
55
48
|
console.error(`Make sure to run 'npm run build' to generate the compiler.`);
|
|
56
49
|
callback(err);
|
|
57
|
-
}
|
|
50
|
+
}
|
|
58
51
|
};
|