@love-sqjm/magic 2026.4.15
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 +674 -0
- package/README.md +93 -0
- package/app.js +51 -0
- package/doc/api/examples.md +563 -0
- package/doc/api/index.md +563 -0
- package/doc/architecture.md +322 -0
- package/doc/config-reference.md +646 -0
- package/doc/data-model.md +622 -0
- package/doc/development-guide.md +582 -0
- package/doc/magic-file/index.md +610 -0
- package/doc/user-guide/index.md +485 -0
- package/doc/workflow.md +548 -0
- package/index.js +157 -0
- package/magic.bat +2 -0
- package/magic.ps1 +5 -0
- package/package.json +44 -0
- package/script/build-project.js +16 -0
- package/script/config.js +23 -0
- package/script/create-project.js +73 -0
- package/script/global/printf.js +13 -0
- package/script/global/project-build-config.js +161 -0
- package/script/global/support-platform.js +5 -0
- package/script/module/compiler/global.js +43 -0
- package/script/module/compiler/id-generate.js +18 -0
- package/script/module/compiler/index-dom.js +78 -0
- package/script/module/compiler/macro-replace.js +22 -0
- package/script/module/compiler/macro.js +6 -0
- package/script/module/compiler/start.js +10 -0
- package/script/module/compiler/step/1.js +253 -0
- package/script/module/compiler/step/2.js +79 -0
- package/script/module/compiler/step/3.js +37 -0
- package/script/module/compiler/step/4.js +20 -0
- package/script/module/compiler/step/5.js +634 -0
- package/script/module/compiler/step/6.js +304 -0
- package/script/module/compiler/step/end.js +124 -0
- package/script/run-project.js +249 -0
- package/script/util/bun-fs.js +40 -0
- package/script/util/copy-dir.js +21 -0
- package/script/util/create-simple-dom-element.js +23 -0
- package/script/util/file-util.js +95 -0
- package/script/util/filtration-file.js +20 -0
- package/script/util/get-dir-all-file.js +28 -0
- package/script/util/get-first-object-key.js +9 -0
- package/script/util/is-empty-object.js +8 -0
- package/script/util/is-string-over-size.js +4 -0
- package/script/util/is.js +18 -0
- package/script/util/logging.js +142 -0
- package/script/util/task.js +16 -0
- package/script/util/traversal.js +28 -0
- package/template/platform-config/node-webkit +23 -0
- package/template/platform-config/web +1 -0
- package/template/project-base/app.xml +5 -0
- package/template/project-base/build.module.toml +37 -0
- package/template/project-base/build.toml +43 -0
- package/template/runtime/runtime.css +3 -0
- package/template/runtime/runtime.js +895 -0
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
window[ "magic" ] = ( function () {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
window[ "__MAGIC__" ] = {
|
|
5
|
+
M : {},
|
|
6
|
+
CREATE_ELEMENT_LIST : new Map()
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function idGenerate() {
|
|
10
|
+
const timestamp = `${ new Date().getTime() }`.substring( 2 );
|
|
11
|
+
|
|
12
|
+
function getRandomLetter() {
|
|
13
|
+
const letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
14
|
+
return letters[ Math.floor( Math.random() * letters.length ) ];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let result = '';
|
|
18
|
+
for ( let i = 0; i < timestamp.length; i++ ) {
|
|
19
|
+
result += timestamp[ i ];
|
|
20
|
+
if ( i < timestamp.length - 1 ) {
|
|
21
|
+
result += getRandomLetter();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return "m-id-" + result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const call = function ( scope ) {
|
|
29
|
+
return {
|
|
30
|
+
interface : new Proxy( scope, {
|
|
31
|
+
get( target, prop, receiver ) {
|
|
32
|
+
return Reflect.get( scope[ "__magic_interface" ], prop, receiver );
|
|
33
|
+
},
|
|
34
|
+
set( target, prop, value, receiver ) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
} ),
|
|
38
|
+
event : new Proxy( scope, {
|
|
39
|
+
get( target, prop, receiver ) {
|
|
40
|
+
return Reflect.get( scope[ "__magic_event" ], prop, receiver );
|
|
41
|
+
},
|
|
42
|
+
set( target, prop, value, receiver ) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
} ),
|
|
46
|
+
listen : new Proxy( scope, {
|
|
47
|
+
get( target, prop, receiver ) {
|
|
48
|
+
return scope[ `__magic_listen_${ prop }` ];
|
|
49
|
+
},
|
|
50
|
+
set( target, prop, value, receiver ) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
} )
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const emit = {
|
|
58
|
+
event : function ( listen ) {
|
|
59
|
+
return ( name, ...args ) => {
|
|
60
|
+
if ( listen.hasOwnProperty( name ) )
|
|
61
|
+
return listen[ name ]( ...args );
|
|
62
|
+
else
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const dom = ( () => {
|
|
69
|
+
function element( tag ) {
|
|
70
|
+
const r = document.createElement( tag );
|
|
71
|
+
r.__MAGIC_CREATE_ELEMENT__ = true;
|
|
72
|
+
return r;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function text( content ) {
|
|
76
|
+
return document.createTextNode( content );
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function append( p, ...childs ) {
|
|
80
|
+
const bool = p.templateArgs && p.templateArgs.inline && p.childNodes.length > 0;
|
|
81
|
+
if ( bool && p.interface._inline ) {
|
|
82
|
+
p.interface._inline( ...childs );
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for ( const c of childs ) {
|
|
86
|
+
const child = ( () => {
|
|
87
|
+
if ( c.fragment ) return c.fragment;
|
|
88
|
+
else return c;
|
|
89
|
+
} )();
|
|
90
|
+
if ( bool ) {
|
|
91
|
+
p.childNodes.item( 0 ).appendChild( child );
|
|
92
|
+
} else {
|
|
93
|
+
p.appendChild( child );
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function attribute( e, atts ) {
|
|
99
|
+
for ( const key in atts ) {
|
|
100
|
+
if ( key === "class" ) {
|
|
101
|
+
atts[ key ].split( " " ).forEach( s => {
|
|
102
|
+
e.classList.add( s );
|
|
103
|
+
} );
|
|
104
|
+
} else {
|
|
105
|
+
if ( e[ "__fragment" ] && e[ "mid" ] ) {
|
|
106
|
+
e.childNodes[ 0 ].setAttribute( key, atts[ key ] || "" );
|
|
107
|
+
} else {
|
|
108
|
+
e.setAttribute( key, atts[ key ] || "" );
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function event( e, eventName, _this, fnName, opt ) {
|
|
115
|
+
e.addEventListener( eventName, _this.__magic_event[ fnName ], opt );
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
element,
|
|
120
|
+
text,
|
|
121
|
+
attribute,
|
|
122
|
+
event,
|
|
123
|
+
append
|
|
124
|
+
}
|
|
125
|
+
} )();
|
|
126
|
+
|
|
127
|
+
function importElementInit( ...args ) {
|
|
128
|
+
args.forEach( i => {
|
|
129
|
+
if ( i.interface.hasOwnProperty( "_render" ) ) i.interface[ "_render" ]();
|
|
130
|
+
} );
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function importM( name, args = {}, listen = {} ) {
|
|
134
|
+
name = name.replace( /[^a-zA-Z]/g, '' ).toLowerCase();
|
|
135
|
+
if ( window[ "__MAGIC__" ][ "M" ].hasOwnProperty( name ) ) {
|
|
136
|
+
return new window[ "__MAGIC__" ][ "M" ][ name ]( args, listen );
|
|
137
|
+
} else {
|
|
138
|
+
throw `未知的 ${ name }`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function $id( _$id ) {
|
|
143
|
+
const createNameFinder = ( element ) => ( name ) => {
|
|
144
|
+
const findChildByName = ( parent ) => {
|
|
145
|
+
for ( const child of parent.childNodes ) {
|
|
146
|
+
if ( child.nodeType !== 1 ) continue;
|
|
147
|
+
if ( child._$name === name ) return child;
|
|
148
|
+
if ( child._$id ) return null;
|
|
149
|
+
|
|
150
|
+
const found = findChildByName( child );
|
|
151
|
+
if ( found ) return found;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
return findChildByName( element );
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const convertIdMapToObject = ( map ) => {
|
|
159
|
+
const result = {};
|
|
160
|
+
map.forEach( ( value, key ) => {
|
|
161
|
+
const formattedKey = `$${ key.replace( /-(\w)/g, ( _, c ) => c.toUpperCase() ) }`;
|
|
162
|
+
value.$name = createNameFinder( value );
|
|
163
|
+
result[ formattedKey ] = value;
|
|
164
|
+
} );
|
|
165
|
+
return result;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return ( id ) => {
|
|
169
|
+
if ( id === undefined ) {
|
|
170
|
+
return convertIdMapToObject( _$id );
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const target = _$id.get( id );
|
|
174
|
+
if ( !target ) throw new Error( `没有 id 为 ${ id } 的元素` );
|
|
175
|
+
|
|
176
|
+
target.$name = createNameFinder( target );
|
|
177
|
+
return target;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function $( id, root = document.body ) {
|
|
182
|
+
if ( !id || typeof id !== 'string' || !root || root.nodeType !== 1 ) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const stack = [ root ];
|
|
187
|
+
|
|
188
|
+
while ( stack.length > 0 ) {
|
|
189
|
+
const element = stack.pop();
|
|
190
|
+
|
|
191
|
+
// 检查当前元素
|
|
192
|
+
if ( element.mid === id ) {
|
|
193
|
+
return element;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 将子元素推入栈(从后往前推,保持搜索顺序)
|
|
197
|
+
const children = element.children;
|
|
198
|
+
for ( let i = children.length - 1; i >= 0; i-- ) {
|
|
199
|
+
stack.push( children[ i ] );
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function parserArgs( args ) {
|
|
207
|
+
if ( typeof args === "string" )
|
|
208
|
+
return JSON.parse( args );
|
|
209
|
+
|
|
210
|
+
function transformObject( obj ) {
|
|
211
|
+
const result = {};
|
|
212
|
+
for ( const key in obj ) {
|
|
213
|
+
if ( obj.hasOwnProperty( key ) ) {
|
|
214
|
+
const value = obj[ key ];
|
|
215
|
+
if ( typeof value === 'object' && value !== null ) {
|
|
216
|
+
result[ key ] = transformObject( value );
|
|
217
|
+
} else {
|
|
218
|
+
result[ key ] = value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function splitKeys( obj ) {
|
|
226
|
+
const result = {};
|
|
227
|
+
for ( const key in obj ) {
|
|
228
|
+
if ( obj.hasOwnProperty( key ) ) {
|
|
229
|
+
const value = obj[ key ];
|
|
230
|
+
if ( key.includes( ':' ) ) {
|
|
231
|
+
const keys = key.split( ':' );
|
|
232
|
+
let current = result;
|
|
233
|
+
for ( let i = 0; i < keys.length; i++ ) {
|
|
234
|
+
const k = keys[ i ];
|
|
235
|
+
if ( i === keys.length - 1 ) {
|
|
236
|
+
current[ k ] = value;
|
|
237
|
+
} else {
|
|
238
|
+
if ( !current[ k ] ) {
|
|
239
|
+
current[ k ] = {};
|
|
240
|
+
}
|
|
241
|
+
current = current[ k ];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
result[ key ] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const transformed = transformObject( args );
|
|
253
|
+
return splitKeys( transformed );
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createArgs( obj ) {
|
|
257
|
+
return JSON.stringify( obj );
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function createUiData( target = {}, source = {} ) {
|
|
261
|
+
function _getType( val ) {
|
|
262
|
+
return Object.prototype.toString.call( val ).slice( 8, -1 );
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function _convertType( t, s = "", k ) {
|
|
266
|
+
const type = _getType( t );
|
|
267
|
+
let result = s;
|
|
268
|
+
switch ( type ) {
|
|
269
|
+
case "Boolean": {
|
|
270
|
+
if ( s === "true" ) result = true;
|
|
271
|
+
else if ( s === "false" ) result = false;
|
|
272
|
+
else throw new Error();
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case "Number": {
|
|
276
|
+
if ( s.includes( "." ) ) result = parseFloat( s );
|
|
277
|
+
else result = parseInt( s );
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case "Array": {
|
|
281
|
+
result = JSON.parse( s );
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case "RegExp": {
|
|
285
|
+
const parts = s.match( /^\/(.+)\/([gimuy]*)$/ );
|
|
286
|
+
result = new RegExp( parts[ 1 ], parts[ 2 ] );
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for ( const key in source ) {
|
|
294
|
+
if ( source.hasOwnProperty( key ) ) {
|
|
295
|
+
const sourceValue = source[ key ];
|
|
296
|
+
const targetValue = target[ key ];
|
|
297
|
+
|
|
298
|
+
if ( typeof sourceValue === 'object' && sourceValue !== null && typeof targetValue === 'object' && targetValue !== null ) {
|
|
299
|
+
createUiData( targetValue, sourceValue );
|
|
300
|
+
} else {
|
|
301
|
+
try {
|
|
302
|
+
target[ key ] = _convertType( targetValue, sourceValue, key );
|
|
303
|
+
} catch ( e ) {
|
|
304
|
+
throw `[UiData] 给 ${ key } 传入的键值类型与目标键类型不符合`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const bindings = {};
|
|
311
|
+
|
|
312
|
+
function createDeepProxy( t, path = [] ) {
|
|
313
|
+
if ( !t || typeof t !== 'object' ) {
|
|
314
|
+
return t;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const handler = {
|
|
318
|
+
get( t, prop, receiver ) {
|
|
319
|
+
const value = Reflect.get( t, prop, receiver );
|
|
320
|
+
const currentPath = [ ...path, prop ];
|
|
321
|
+
|
|
322
|
+
// 如果值是对象,递归创建代理
|
|
323
|
+
if ( value && typeof value === 'object' ) {
|
|
324
|
+
return createDeepProxy( value, currentPath );
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return value;
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
set( t, prop, newValue, receiver ) {
|
|
331
|
+
const currentPath = [ ...path, prop ];
|
|
332
|
+
|
|
333
|
+
const p = currentPath.join( '.' );
|
|
334
|
+
if ( bindings.hasOwnProperty( p ) ) {
|
|
335
|
+
bindings[ p ].forEach( n => {
|
|
336
|
+
n.textContent = newValue;
|
|
337
|
+
} );
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 如果新值是对象,确保它也被代理
|
|
341
|
+
if ( newValue && typeof newValue === 'object' ) {
|
|
342
|
+
newValue = createDeepProxy( newValue, currentPath );
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return Reflect.set( t, prop, newValue, receiver );
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
return new Proxy( t, handler );
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 动态值绑定初始化函数
|
|
354
|
+
*/
|
|
355
|
+
target.__DynamicValueBind = function ( bindingMap ) {
|
|
356
|
+
if ( !bindingMap || typeof bindingMap !== 'object' ) {
|
|
357
|
+
console.warn( '[UiData] __DynamicValueBind 需要接收一个对象参数' );
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for ( const key in bindingMap ) {
|
|
362
|
+
if ( bindingMap.hasOwnProperty( key ) ) {
|
|
363
|
+
const textNodes = bindingMap[ key ];
|
|
364
|
+
|
|
365
|
+
if ( !Array.isArray( textNodes ) ) {
|
|
366
|
+
console.warn( `[UiData] 键 "${ key }" 的值必须是数组` );
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const validTextNodes = textNodes.filter( node =>
|
|
371
|
+
node && node.nodeType === 3
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
if ( validTextNodes.length === 0 ) {
|
|
375
|
+
console.warn( `[UiData] 键 "${ key }" 没有有效的文本节点` );
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if ( !bindings[ key ] ) {
|
|
380
|
+
bindings[ key ] = [];
|
|
381
|
+
}
|
|
382
|
+
bindings[ key ].push( ...validTextNodes );
|
|
383
|
+
|
|
384
|
+
const value = this._getValueByPath( key );
|
|
385
|
+
if ( value !== undefined ) {
|
|
386
|
+
validTextNodes.forEach( node => {
|
|
387
|
+
node.textContent = value;
|
|
388
|
+
} );
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return this;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* 辅助方法:通过路径获取值
|
|
398
|
+
* @param {string} path - 属性路径,如 "user.name"
|
|
399
|
+
* @returns {*} - 路径对应的值
|
|
400
|
+
*/
|
|
401
|
+
target._getValueByPath = function ( path ) {
|
|
402
|
+
const parts = path.split( '.' );
|
|
403
|
+
let current = target;
|
|
404
|
+
|
|
405
|
+
for ( const part of parts ) {
|
|
406
|
+
if ( current === null || current === undefined ) {
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
current = current[ part ];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return current;
|
|
413
|
+
};
|
|
414
|
+
return createDeepProxy( target );
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function parserListen( listen ) {
|
|
418
|
+
return listen || {};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function on_event( event, listen ) {
|
|
422
|
+
if ( !event || !listen || !listen.hasOwnProperty( event.type ) ) return;
|
|
423
|
+
event.runState = "on";
|
|
424
|
+
return listen[ event.type ]( event );
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function end_event( event, listen ) {
|
|
428
|
+
if ( !event || !listen || !listen.hasOwnProperty( event.type ) ) return;
|
|
429
|
+
event.runState = "end";
|
|
430
|
+
return listen[ event.type ]( event );
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function mapIdElement() {
|
|
434
|
+
const map = new Map;
|
|
435
|
+
map.s = ( id, e, s = false ) => {
|
|
436
|
+
e._$id = id;
|
|
437
|
+
if ( s && e.__fragment ) {
|
|
438
|
+
e.childNodes.item( 0 ).classList.add( s );
|
|
439
|
+
} else if ( s ) {
|
|
440
|
+
e.classList.add( s );
|
|
441
|
+
}
|
|
442
|
+
map.set( id, e );
|
|
443
|
+
};
|
|
444
|
+
return map;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function exportInterface( scope, target, parcel ) {
|
|
448
|
+
function fn( si ) {
|
|
449
|
+
for ( const pKey in parcel ) {
|
|
450
|
+
if ( scope[ si ].hasOwnProperty( pKey ) ) {
|
|
451
|
+
throw new Error( "导出的接口存在: " + pKey );
|
|
452
|
+
}
|
|
453
|
+
if ( parcel[ pKey ] === nop ) {
|
|
454
|
+
scope[ si ][ pKey ] = target.interface[ pKey ];
|
|
455
|
+
} else if ( typeof parcel[ pKey ] === "function" ) {
|
|
456
|
+
scope[ si ][ pKey ] = ( ...args ) => {
|
|
457
|
+
target.interface[ pKey ]( parcel[ pKey ]( ...args ) );
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if ( scope.__magic_interface ) {
|
|
464
|
+
fn( "__magic_interface" );
|
|
465
|
+
} else if ( scope.interface ) {
|
|
466
|
+
fn( "interface" );
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function GetInterface( ele ) {
|
|
471
|
+
if ( ele.__SCOPE__ ) {
|
|
472
|
+
return ele.__SCOPE__.__magic_interface;
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function BindScope( arr, ths, id ) {
|
|
478
|
+
const bindScope = ( element ) => {
|
|
479
|
+
Object.defineProperty( element, '__SCOPE__', {
|
|
480
|
+
value : ths,
|
|
481
|
+
writable : false,
|
|
482
|
+
configurable : true
|
|
483
|
+
} );
|
|
484
|
+
element.__MAGIC_MODULE_ID = id;
|
|
485
|
+
return element;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
if ( arr.length === 0 ) {
|
|
489
|
+
arr.push( {} );
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return arr.map( bindScope );
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
{
|
|
496
|
+
|
|
497
|
+
const originalRemove = Element.prototype.remove;
|
|
498
|
+
Element.prototype.remove = function () {
|
|
499
|
+
function t_e( e ) {
|
|
500
|
+
if ( e.__MAGIC_CREATE_ELEMENT__ && e.mid ) {
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
this.querySelectorAll( "*" ).forEach( e => {
|
|
505
|
+
t_e( e );
|
|
506
|
+
} );
|
|
507
|
+
t_e( this );
|
|
508
|
+
originalRemove.call( this );
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const replaceMagicComments = ( () => {
|
|
513
|
+
// 缓存正则表达式(模块级常量)
|
|
514
|
+
const MAGIC_PATTERN = /(\\)?\$(\{([^}]*)\}|)/g;
|
|
515
|
+
const COMMENT_PATTERN = /<!--MAGIC:DV\[(.*?)\]-->/g;
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* 处理单个文本节点
|
|
519
|
+
* @param {Text} textNode - 要处理的文本节点
|
|
520
|
+
* @param {Object} resultMap - 结果收集对象
|
|
521
|
+
* @returns {boolean} - 是否处理成功
|
|
522
|
+
*/
|
|
523
|
+
function processTextNode( textNode, resultMap ) {
|
|
524
|
+
const content = textNode.textContent;
|
|
525
|
+
let processed = '';
|
|
526
|
+
let lastIndex = 0;
|
|
527
|
+
let match;
|
|
528
|
+
|
|
529
|
+
MAGIC_PATTERN.lastIndex = 0;
|
|
530
|
+
|
|
531
|
+
while ( ( match = MAGIC_PATTERN.exec( content ) ) !== null ) {
|
|
532
|
+
if ( match.index > lastIndex ) {
|
|
533
|
+
processed += content.substring( lastIndex, match.index );
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if ( match[ 1 ] ) {
|
|
537
|
+
// 转义的\$
|
|
538
|
+
processed += '$';
|
|
539
|
+
if ( match[ 2 ]?.startsWith( '{' ) ) {
|
|
540
|
+
processed += match[ 2 ];
|
|
541
|
+
}
|
|
542
|
+
} else if ( match[ 3 ] !== undefined ) {
|
|
543
|
+
// ${xxx} 模式
|
|
544
|
+
processed += `<!--MAGIC:DV[${ match[ 3 ] }]-->`;
|
|
545
|
+
} else {
|
|
546
|
+
// 单独的$
|
|
547
|
+
processed += '$';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
lastIndex = match.index + match[ 0 ].length;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if ( lastIndex < content.length ) {
|
|
554
|
+
processed += content.substring( lastIndex );
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// 如果没有变化,返回false
|
|
558
|
+
if ( processed === content ) return false;
|
|
559
|
+
|
|
560
|
+
// 构建新内容
|
|
561
|
+
const fragment = document.createDocumentFragment();
|
|
562
|
+
let textStart = 0;
|
|
563
|
+
|
|
564
|
+
COMMENT_PATTERN.lastIndex = 0;
|
|
565
|
+
while ( ( match = COMMENT_PATTERN.exec( processed ) ) !== null ) {
|
|
566
|
+
if ( match.index > textStart ) {
|
|
567
|
+
fragment.appendChild(
|
|
568
|
+
document.createTextNode( processed.substring( textStart, match.index ) )
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// 创建注释节点
|
|
573
|
+
const comment = document.createComment( `MAGIC:DV[${ match[ 1 ] }]` );
|
|
574
|
+
fragment.appendChild( comment );
|
|
575
|
+
|
|
576
|
+
// 在注释后面创建一个空的文本节点
|
|
577
|
+
const emptyTextNode = document.createTextNode( `{${ match[ 1 ] }}` );
|
|
578
|
+
fragment.appendChild( emptyTextNode );
|
|
579
|
+
|
|
580
|
+
// 将空文本节点添加到结果映射中
|
|
581
|
+
const key = match[ 1 ];
|
|
582
|
+
if ( !resultMap[ key ] ) {
|
|
583
|
+
resultMap[ key ] = [];
|
|
584
|
+
}
|
|
585
|
+
resultMap[ key ].push( emptyTextNode );
|
|
586
|
+
|
|
587
|
+
textStart = match.index + match[ 0 ].length;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if ( textStart < processed.length ) {
|
|
591
|
+
fragment.appendChild(
|
|
592
|
+
document.createTextNode( processed.substring( textStart ) )
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// 执行替换
|
|
597
|
+
try {
|
|
598
|
+
textNode.parentNode.replaceChild( fragment, textNode );
|
|
599
|
+
return true;
|
|
600
|
+
} catch ( e ) {
|
|
601
|
+
console.warn( '替换文本节点失败:', e );
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* 收集需要处理的文本节点
|
|
608
|
+
* @param {Element} element - 根元素
|
|
609
|
+
* @returns {Text[]} - 文本节点数组
|
|
610
|
+
*/
|
|
611
|
+
function collectTextNodes( element ) {
|
|
612
|
+
const walker = document.createTreeWalker(
|
|
613
|
+
element,
|
|
614
|
+
NodeFilter.SHOW_TEXT,
|
|
615
|
+
{
|
|
616
|
+
acceptNode : ( node ) => {
|
|
617
|
+
// 只处理包含$符号且非空的文本节点
|
|
618
|
+
return node.textContent.trim() !== '' && node.textContent.includes( '$' )
|
|
619
|
+
? NodeFilter.FILTER_ACCEPT
|
|
620
|
+
: NodeFilter.FILTER_REJECT;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
const textNodes = [];
|
|
626
|
+
let currentNode;
|
|
627
|
+
|
|
628
|
+
while ( ( currentNode = walker.nextNode() ) ) {
|
|
629
|
+
textNodes.push( currentNode );
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return textNodes;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* 处理所有文本节点
|
|
637
|
+
* @param {Element} element - 根元素
|
|
638
|
+
* @returns {Object} - 结果对象
|
|
639
|
+
*/
|
|
640
|
+
function processAllNodes( element ) {
|
|
641
|
+
const result = {};
|
|
642
|
+
const textNodes = collectTextNodes( element );
|
|
643
|
+
|
|
644
|
+
textNodes.forEach( node => {
|
|
645
|
+
processTextNode( node, result );
|
|
646
|
+
} );
|
|
647
|
+
|
|
648
|
+
return result;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Promise 版本的主函数
|
|
653
|
+
* @param {Element} element - 要处理的根元素
|
|
654
|
+
* @returns {Promise<Object>} - 返回 Promise,解析为结果对象
|
|
655
|
+
*/
|
|
656
|
+
return function ( element ) {
|
|
657
|
+
|
|
658
|
+
return new Promise( ( resolve, reject ) => {
|
|
659
|
+
// 快速失败检查
|
|
660
|
+
if ( !element ) {
|
|
661
|
+
reject( new Error( '元素不能为空' ) );
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if ( element.nodeType !== 1 ) {
|
|
666
|
+
reject( new Error( '元素必须是 Element 类型' ) );
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// 处理函数
|
|
671
|
+
const process = () => {
|
|
672
|
+
try {
|
|
673
|
+
const result = processAllNodes( element );
|
|
674
|
+
resolve( result );
|
|
675
|
+
} catch ( error ) {
|
|
676
|
+
reject( error );
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// 使用 requestIdleCallback 或 setTimeout 来避免阻塞主线程
|
|
681
|
+
if ( window.requestIdleCallback ) {
|
|
682
|
+
requestIdleCallback( process, { timeout : 100 } );
|
|
683
|
+
} else {
|
|
684
|
+
setTimeout( process, 0 );
|
|
685
|
+
}
|
|
686
|
+
} );
|
|
687
|
+
};
|
|
688
|
+
} )();
|
|
689
|
+
|
|
690
|
+
function DynamicValueBind( ...ele ) {
|
|
691
|
+
const o = ele.pop();
|
|
692
|
+
ele.forEach( node => {
|
|
693
|
+
replaceMagicComments( node ).then( r => {
|
|
694
|
+
o.__DynamicValueBind( r )
|
|
695
|
+
} );
|
|
696
|
+
} )
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const router = ( () => {
|
|
700
|
+
const routes = [];
|
|
701
|
+
const routeComponents = {};
|
|
702
|
+
let currentRoute = null;
|
|
703
|
+
let rootContainer = null;
|
|
704
|
+
const guards = [];
|
|
705
|
+
|
|
706
|
+
function pathToRegex( path ) {
|
|
707
|
+
const paramNames = [];
|
|
708
|
+
const regexStr = path.replace( /:([^/]+)/g, ( _, name ) => {
|
|
709
|
+
paramNames.push( name );
|
|
710
|
+
return '([^/]+)';
|
|
711
|
+
} );
|
|
712
|
+
return {
|
|
713
|
+
regex: new RegExp( `^${ regexStr }$` ),
|
|
714
|
+
params: paramNames
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function matchRoute( path ) {
|
|
719
|
+
for ( const route of routes ) {
|
|
720
|
+
const { regex, params } = pathToRegex( route.path );
|
|
721
|
+
const match = path.match( regex );
|
|
722
|
+
if ( match ) {
|
|
723
|
+
const paramValues = {};
|
|
724
|
+
params.forEach( ( name, i ) => {
|
|
725
|
+
paramValues[ name ] = match[ i + 1 ];
|
|
726
|
+
} );
|
|
727
|
+
return {
|
|
728
|
+
...route,
|
|
729
|
+
params: paramValues,
|
|
730
|
+
query: parseQuery( path )
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function parseQuery( path ) {
|
|
738
|
+
const queryIndex = path.indexOf( '?' );
|
|
739
|
+
if ( queryIndex === -1 ) return {};
|
|
740
|
+
const queryStr = path.slice( queryIndex + 1 );
|
|
741
|
+
const result = {};
|
|
742
|
+
queryStr.split( '&' ).forEach( pair => {
|
|
743
|
+
const [ key, value ] = pair.split( '=' );
|
|
744
|
+
result[ decodeURIComponent( key ) ] = decodeURIComponent( value || '' );
|
|
745
|
+
} );
|
|
746
|
+
return result;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function extractPath( pathname ) {
|
|
750
|
+
const queryIndex = pathname.indexOf( '?' );
|
|
751
|
+
return queryIndex === -1 ? pathname : pathname.slice( 0, queryIndex );
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async function executeGuards( to, from ) {
|
|
755
|
+
for ( const guard of guards ) {
|
|
756
|
+
const result = await guard( to, from );
|
|
757
|
+
if ( result === false ) return false;
|
|
758
|
+
if ( typeof result === 'string' ) {
|
|
759
|
+
navigate( result, true );
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function renderRoute( route ) {
|
|
767
|
+
if ( !rootContainer ) return;
|
|
768
|
+
|
|
769
|
+
const component = routeComponents[ route.component ];
|
|
770
|
+
if ( !component ) {
|
|
771
|
+
console.error( `Route component "${ route.component }" not found` );
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const element = magic.importM( component, {
|
|
776
|
+
...route.params,
|
|
777
|
+
...route.query
|
|
778
|
+
} );
|
|
779
|
+
|
|
780
|
+
rootContainer.innerHTML = '';
|
|
781
|
+
if ( element.fragment ) {
|
|
782
|
+
rootContainer.appendChild( element.fragment );
|
|
783
|
+
} else {
|
|
784
|
+
rootContainer.appendChild( element );
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
currentRoute = route;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function handleNavigation( path, replace = false ) {
|
|
791
|
+
const pathname = extractPath( path );
|
|
792
|
+
const route = matchRoute( pathname );
|
|
793
|
+
|
|
794
|
+
if ( !route ) {
|
|
795
|
+
console.error( `No route matched for path: ${ path }` );
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const from = currentRoute;
|
|
800
|
+
executeGuards( route, from ).then( ( canContinue ) => {
|
|
801
|
+
if ( canContinue === false ) return;
|
|
802
|
+
|
|
803
|
+
if ( replace ) {
|
|
804
|
+
history.replaceState( null, '', path );
|
|
805
|
+
} else {
|
|
806
|
+
history.pushState( null, '', path );
|
|
807
|
+
}
|
|
808
|
+
renderRoute( route );
|
|
809
|
+
} );
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return {
|
|
813
|
+
getCurrentRoute() {
|
|
814
|
+
return currentRoute;
|
|
815
|
+
},
|
|
816
|
+
|
|
817
|
+
addRoute( path, component ) {
|
|
818
|
+
routes.push( { path, component } );
|
|
819
|
+
routeComponents[ component ] = component;
|
|
820
|
+
},
|
|
821
|
+
|
|
822
|
+
addRoutes( routeList ) {
|
|
823
|
+
routeList.forEach( r => this.addRoute( r.path, r.component ) );
|
|
824
|
+
},
|
|
825
|
+
|
|
826
|
+
init( containerId ) {
|
|
827
|
+
rootContainer = document.getElementById( containerId );
|
|
828
|
+
if ( !rootContainer ) {
|
|
829
|
+
throw new Error( `Router container element "${ containerId }" not found` );
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
window.addEventListener( 'popstate', () => {
|
|
833
|
+
const path = extractPath( location.pathname );
|
|
834
|
+
const route = matchRoute( path );
|
|
835
|
+
if ( route ) renderRoute( route );
|
|
836
|
+
} );
|
|
837
|
+
|
|
838
|
+
this.navigate( extractPath( location.pathname ), true );
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
navigate( path, replace = false ) {
|
|
842
|
+
handleNavigation( path, replace );
|
|
843
|
+
},
|
|
844
|
+
|
|
845
|
+
push( path ) {
|
|
846
|
+
handleNavigation( path, false );
|
|
847
|
+
},
|
|
848
|
+
|
|
849
|
+
replace( path ) {
|
|
850
|
+
handleNavigation( path, true );
|
|
851
|
+
},
|
|
852
|
+
|
|
853
|
+
beforeEach( guard ) {
|
|
854
|
+
guards.push( guard );
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
} )();
|
|
858
|
+
|
|
859
|
+
function init( main ) {
|
|
860
|
+
Object.freeze( window[ "magic_version" ] );
|
|
861
|
+
Object.freeze( window[ "magic" ] );
|
|
862
|
+
|
|
863
|
+
console.log( "%cMagic ヾ(๑╹◡╹)ノ",
|
|
864
|
+
"color:#f8faff;font-weight:bold;font-size:6em;padding:10px 30px;background: linear-gradient(to right top, #FFC0CB, #ADD8E6);" );
|
|
865
|
+
console.log( "magic v" + magic_version );
|
|
866
|
+
|
|
867
|
+
const app = document.getElementById( "app" );
|
|
868
|
+
|
|
869
|
+
app.appendChild( importM( main ).fragment );
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
init,
|
|
874
|
+
dom,
|
|
875
|
+
importM,
|
|
876
|
+
call,
|
|
877
|
+
emit,
|
|
878
|
+
$id,
|
|
879
|
+
idGenerate,
|
|
880
|
+
$,
|
|
881
|
+
parserArgs,
|
|
882
|
+
createArgs,
|
|
883
|
+
parserListen,
|
|
884
|
+
on_event,
|
|
885
|
+
end_event,
|
|
886
|
+
mapIdElement,
|
|
887
|
+
createUiData,
|
|
888
|
+
importElementInit,
|
|
889
|
+
exportInterface,
|
|
890
|
+
GetInterface,
|
|
891
|
+
BindScope,
|
|
892
|
+
DynamicValueBind,
|
|
893
|
+
router
|
|
894
|
+
};
|
|
895
|
+
} )();
|