@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.
Files changed (56) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +93 -0
  3. package/app.js +51 -0
  4. package/doc/api/examples.md +563 -0
  5. package/doc/api/index.md +563 -0
  6. package/doc/architecture.md +322 -0
  7. package/doc/config-reference.md +646 -0
  8. package/doc/data-model.md +622 -0
  9. package/doc/development-guide.md +582 -0
  10. package/doc/magic-file/index.md +610 -0
  11. package/doc/user-guide/index.md +485 -0
  12. package/doc/workflow.md +548 -0
  13. package/index.js +157 -0
  14. package/magic.bat +2 -0
  15. package/magic.ps1 +5 -0
  16. package/package.json +44 -0
  17. package/script/build-project.js +16 -0
  18. package/script/config.js +23 -0
  19. package/script/create-project.js +73 -0
  20. package/script/global/printf.js +13 -0
  21. package/script/global/project-build-config.js +161 -0
  22. package/script/global/support-platform.js +5 -0
  23. package/script/module/compiler/global.js +43 -0
  24. package/script/module/compiler/id-generate.js +18 -0
  25. package/script/module/compiler/index-dom.js +78 -0
  26. package/script/module/compiler/macro-replace.js +22 -0
  27. package/script/module/compiler/macro.js +6 -0
  28. package/script/module/compiler/start.js +10 -0
  29. package/script/module/compiler/step/1.js +253 -0
  30. package/script/module/compiler/step/2.js +79 -0
  31. package/script/module/compiler/step/3.js +37 -0
  32. package/script/module/compiler/step/4.js +20 -0
  33. package/script/module/compiler/step/5.js +634 -0
  34. package/script/module/compiler/step/6.js +304 -0
  35. package/script/module/compiler/step/end.js +124 -0
  36. package/script/run-project.js +249 -0
  37. package/script/util/bun-fs.js +40 -0
  38. package/script/util/copy-dir.js +21 -0
  39. package/script/util/create-simple-dom-element.js +23 -0
  40. package/script/util/file-util.js +95 -0
  41. package/script/util/filtration-file.js +20 -0
  42. package/script/util/get-dir-all-file.js +28 -0
  43. package/script/util/get-first-object-key.js +9 -0
  44. package/script/util/is-empty-object.js +8 -0
  45. package/script/util/is-string-over-size.js +4 -0
  46. package/script/util/is.js +18 -0
  47. package/script/util/logging.js +142 -0
  48. package/script/util/task.js +16 -0
  49. package/script/util/traversal.js +28 -0
  50. package/template/platform-config/node-webkit +23 -0
  51. package/template/platform-config/web +1 -0
  52. package/template/project-base/app.xml +5 -0
  53. package/template/project-base/build.module.toml +37 -0
  54. package/template/project-base/build.toml +43 -0
  55. package/template/runtime/runtime.css +3 -0
  56. package/template/runtime/runtime.js +895 -0
@@ -0,0 +1,40 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, rmSync, cpSync, existsSync } from "bun:fs";
2
+
3
+ export const bunFS = {
4
+ readFileSync( path ) {
5
+ return readFileSync( path ).toString();
6
+ },
7
+
8
+ writeFileSync( path, data ) {
9
+ writeFileSync( path, data );
10
+ },
11
+
12
+ existsSync( path ) {
13
+ return existsSync( path );
14
+ },
15
+
16
+ mkdirSync( path, options = {} ) {
17
+ mkdirSync( path, options );
18
+ },
19
+
20
+ rmSync( path, options = {} ) {
21
+ rmSync( path, options );
22
+ },
23
+
24
+ cpSync( src, dest, options = {} ) {
25
+ cpSync( src, dest, options );
26
+ },
27
+
28
+ appendFileSync( path, data ) {
29
+ if ( existsSync( path ) ) {
30
+ const existing = readFileSync( path );
31
+ writeFileSync( path, existing + data );
32
+ } else {
33
+ writeFileSync( path, data );
34
+ }
35
+ }
36
+ };
37
+
38
+ export function computeHash( content ) {
39
+ return Bun.hash( content ).toString( 16 );
40
+ }
@@ -0,0 +1,21 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export function copyDir( src, dest ) {
5
+ if ( !fs.existsSync( dest ) ) {
6
+ fs.mkdirSync( dest, { recursive : true } );
7
+ }
8
+
9
+ const files = fs.readdirSync( src, { withFileTypes : true } );
10
+
11
+ files.forEach( file => {
12
+ const srcPath = path.join( src, file.name );
13
+ const destPath = path.join( dest, file.name );
14
+
15
+ if ( file.isDirectory() ) {
16
+ copyDir( srcPath, destPath );
17
+ } else {
18
+ fs.copyFileSync( srcPath, destPath );
19
+ }
20
+ } );
21
+ }
@@ -0,0 +1,23 @@
1
+ import { getFirstObjectKey } from "./get-first-object-key.js";
2
+
3
+ /**
4
+ * 创建一个简单的 Dom 元素
5
+ * @param obj {object}
6
+ * @param obj.attrs {Array<{ [key: string]: string }>}
7
+ * @param obj.one {boolean}
8
+ * @param obj.tag {string}
9
+ * @param obj.content {string}
10
+ * @returns {string}
11
+ */
12
+ export function createSimpleDomElement( obj ) {
13
+ let attrs = "";
14
+ obj.attrs.forEach( ( attr ) => {
15
+ const name = getFirstObjectKey( attr );
16
+ attrs += `${ name }="${ attr[ name ] }"`;
17
+ } );
18
+
19
+ if ( obj.one ) {
20
+ return `<${ obj.tag } ${ attrs }/>`
21
+ }
22
+ return `<${ obj.tag } ${ attrs }>${ obj.content || "" }</${ obj.tag }>`
23
+ }
@@ -0,0 +1,95 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import * as readline from "node:readline";
4
+
5
+ /**
6
+ * 创建文件及其所有必需的目录路径
7
+ * 如果文件已存在,则不会覆盖
8
+ * @param {string} filePath - 要创建的文件的路径
9
+ * @param {string} data - 要写入文件的数据
10
+ */
11
+ function createFileWithDirectories( filePath, data ) {
12
+ const directoryPath = path.dirname( filePath );
13
+
14
+ function createDirectories( dirPath ) {
15
+ if ( !fs.existsSync( dirPath ) ) {
16
+ createDirectories( path.dirname( dirPath ) );
17
+ fs.mkdirSync( dirPath );
18
+ }
19
+ }
20
+
21
+ createDirectories( directoryPath );
22
+
23
+ if ( !fs.existsSync( filePath ) ) fs.writeFileSync( filePath, data );
24
+ }
25
+
26
+ /**
27
+ * 复制文件及其所有必需的目录路径
28
+ * 如果目标文件已存在,则会被覆盖
29
+ * @param {string} srcPath - 源文件的路径
30
+ * @param {string} targetPath - 目标文件的路径
31
+ */
32
+ function copyFileWithDirectories( srcPath, targetPath ) {
33
+ const directoryPath = path.dirname( targetPath );
34
+
35
+ function createDirectories( dirPath ) {
36
+ if ( !fs.existsSync( dirPath ) ) {
37
+ createDirectories( path.dirname( dirPath ) );
38
+ fs.mkdirSync( dirPath );
39
+ }
40
+ }
41
+
42
+ createDirectories( directoryPath );
43
+
44
+ fs.copyFileSync( srcPath, targetPath );
45
+ }
46
+
47
+ /**
48
+ * 读取文件的第一行内容
49
+ * @param {string} filePath
50
+ * @returns {Promise<string>}
51
+ */
52
+ function readFileLine( filePath ) {
53
+ const fileStream = fs.createReadStream( filePath );
54
+
55
+ const reader = readline.createInterface( {
56
+ input : fileStream,
57
+ crlfDelay : Infinity
58
+ } );
59
+
60
+ return new Promise( ( resolve, reject ) => {
61
+ reader.on( 'line', ( line ) => {
62
+ resolve( line );
63
+ reader.close();
64
+ } );
65
+
66
+ reader.on( 'error', ( error ) => {
67
+ reject( error );
68
+ } );
69
+
70
+ reader.on( 'close', () => {
71
+ fileStream.destroy();
72
+ } );
73
+ } );
74
+ }
75
+
76
+ /**
77
+ * 获取文件的完整扩展名
78
+ * @param {string} filePath - 文件的路径
79
+ * @returns {string} - 文件的扩展名
80
+ */
81
+ function getExtensionName( filePath ) {
82
+ const name = path.basename( filePath );
83
+ const lastDotIndex = name.lastIndexOf( '.' );
84
+ if ( lastDotIndex === -1 || lastDotIndex === name.length - 1 ) {
85
+ return name;
86
+ }
87
+ return name.substring( lastDotIndex + 1 );
88
+ }
89
+
90
+ export const fileUtil = {
91
+ createFileWithDirectories,
92
+ copyFileWithDirectories,
93
+ readFileLine,
94
+ getExtensionName
95
+ };
@@ -0,0 +1,20 @@
1
+ import FastGlob from 'fast-glob';
2
+
3
+ /**
4
+ * 按照配置过滤文件
5
+ * @param rootDir {string}
6
+ * @param exclude {object}
7
+ * @param exclude.dir {string[]}
8
+ * @param exclude.file {string[]}
9
+ * @returns {string[]}
10
+ */
11
+ export function filtrationFile( rootDir, exclude ) {
12
+ return FastGlob.sync( [ '**/*' ], {
13
+ cwd : rootDir,
14
+ onlyFiles : true,
15
+ ignore : [
16
+ ...exclude.dir,
17
+ ...exclude.file
18
+ ]
19
+ } );
20
+ }
@@ -0,0 +1,28 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ /**
5
+ * 获取一个文件夹下的所有文件路径
6
+ * @param dir {string}
7
+ * @returns {string[]}
8
+ */
9
+ export function getDirAllFile( dir ) {
10
+ const arr = [];
11
+
12
+ function it( currentPath ) {
13
+ try {
14
+ fs.readdirSync( currentPath ).forEach( file => {
15
+ const filePath = path.join( currentPath, file );
16
+ if ( fs.statSync( filePath ).isDirectory() )
17
+ it( filePath );
18
+ else
19
+ arr.push( filePath );
20
+ } );
21
+ } catch ( e ) {
22
+ throw `无法读取目录: ${ e }`;
23
+ }
24
+ }
25
+
26
+ it( dir );
27
+ return arr;
28
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 获取一个对象的第一个键名字
3
+ * @param obj {object}
4
+ * @returns {string|undefined}
5
+ */
6
+ export function getFirstObjectKey( obj ) {
7
+ const keys = Object.keys( obj );
8
+ return keys.length > 0 ? keys[ 0 ] : undefined;
9
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 判断一个对象的键长度是否为 0
3
+ * @param obj {object}
4
+ * @returns {boolean}
5
+ */
6
+ export function isEmptyObject( obj ) {
7
+ return Object.keys( obj ).length === 0;
8
+ }
@@ -0,0 +1,4 @@
1
+ export const isStringOverSize = ( str, maxSize ) => {
2
+ const byteLength = new TextEncoder().encode( str ).length;
3
+ return byteLength > maxSize;
4
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @param target {function}
3
+ * @param succeed {function}
4
+ * @param fail {function}
5
+ * @returns {boolean}
6
+ */
7
+ export function is( target, succeed = () => true, fail = () => false ) {
8
+ if ( typeof target !== "function" ) {
9
+ throw "Error: target must be a function";
10
+ }
11
+ const {
12
+ bool,
13
+ result
14
+ } = target();
15
+ if ( bool )
16
+ return succeed( result );
17
+ return fail( result );
18
+ }
@@ -0,0 +1,142 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ export class Logging {
5
+ #IS_USE = false;
6
+
7
+ constructor( name, logPath, option ) {
8
+ this.oldCount = 1;
9
+ this.logPath = path.normalize( logPath );
10
+ this.oldData = null;
11
+ this.name = name;
12
+ this.option = Object.assign( {
13
+ maxSize : 2 * 1024
14
+ }, option );
15
+
16
+ try {
17
+ if ( fs.existsSync( this.logPath ) ) {
18
+ const stats = fs.statSync( this.logPath );
19
+ if ( stats.size > this.option.maxSize ) {
20
+ fs.writeFileSync( this.logPath, this.fileHead( name ) );
21
+ }
22
+ } else {
23
+ fs.writeFileSync( this.logPath, this.fileHead( name ) );
24
+ }
25
+ } catch ( e ) {
26
+ console.error( "Error initializing log file:", e );
27
+ }
28
+ }
29
+
30
+ get isUse() {
31
+ return this.#IS_USE;
32
+ }
33
+
34
+ get outFile() {
35
+ return {
36
+ log : ( ...args ) => {
37
+ this.write( this.body( 'LOG', args ), args.join( ' ' ) );
38
+ },
39
+ error : ( ...args ) => {
40
+ this.write( this.body( 'ERROR', args ), args.join( ' ' ) );
41
+ },
42
+ warning : ( ...args ) => {
43
+ this.write( this.body( 'WARNING', args ), args.join( ' ' ) );
44
+ },
45
+ info : ( ...args ) => {
46
+ this.write( this.body( 'INFO', args ), args.join( ' ' ) );
47
+ }
48
+ }
49
+ }
50
+
51
+ get outConsole() {
52
+ return {
53
+ log : console.log,
54
+ error : console.error,
55
+ warning : console.warn,
56
+ info : console.info
57
+ }
58
+ }
59
+
60
+ fileHead( name ) {
61
+ return `Logging - 1.0.0 [${ name }]\n`;
62
+ }
63
+
64
+ getDate() {
65
+ const date = new Date();
66
+ return date.toISOString().replace( 'T', ' ' ).substring( 0, 19 );
67
+ }
68
+
69
+ head( level ) {
70
+ return `[${ this.getDate() }] [${ level.toUpperCase() }] > `;
71
+ }
72
+
73
+ body( level, data ) {
74
+ let str = "";
75
+
76
+ const toString = ( item ) => {
77
+ try {
78
+ if ( typeof item === 'string' ) str += `${ item } `;
79
+ else if ( typeof item === 'object' ) str += JSON.stringify( item ) + " ";
80
+ else str += `${ item } `;
81
+ } catch ( e ) {
82
+ str += `${ item } `;
83
+ }
84
+ };
85
+
86
+ data.forEach( toString );
87
+
88
+ if ( str.trim() === "" ) return false;
89
+ return this.head( level ) + str.trim();
90
+ }
91
+
92
+ write( data, _data ) {
93
+ if ( data === false ) return;
94
+ if ( this.oldData === _data ) {
95
+ this.oldCount++;
96
+ return;
97
+ }
98
+ if ( this.oldCount !== 1 ) {
99
+ fs.appendFileSync( this.logPath, `${ this.oldCount }...^\n` );
100
+ }
101
+ this.oldCount = 1;
102
+ this.oldData = _data;
103
+ fs.appendFileSync( this.logPath, data + '\n' );
104
+ this.#IS_USE = true;
105
+ }
106
+
107
+ log( ...args ) {
108
+ console.log( ...args );
109
+ this.write( this.body( 'LOG', args ), args.join( ' ' ) );
110
+ }
111
+
112
+ error( ...args ) {
113
+ console.error( ...args );
114
+ this.write( this.body( 'ERROR', args ), args.join( ' ' ) );
115
+ }
116
+
117
+ warning( ...args ) {
118
+ console.warn( ...args );
119
+ this.write( this.body( 'WARNING', args ), args.join( ' ' ) );
120
+ }
121
+
122
+ info( ...args ) {
123
+ console.info( ...args );
124
+ this.write( this.body( 'INFO', args ), args.join( ' ' ) );
125
+ }
126
+
127
+ remove() {
128
+ try {
129
+ fs.unlinkSync( this.logPath );
130
+ } catch ( e ) {
131
+ console.error( "Error removing log file:", e );
132
+ }
133
+ }
134
+
135
+ clear() {
136
+ try {
137
+ fs.writeFileSync( this.logPath, this.fileHead( this.name ) );
138
+ } catch ( e ) {
139
+ console.error( "Error clearing log file:", e );
140
+ }
141
+ }
142
+ }
@@ -0,0 +1,16 @@
1
+ import { printf } from "../global/printf.js";
2
+
3
+ /**
4
+ * 运行一个任务
5
+ * @param func {function}
6
+ * @param name {string|boolean}
7
+ */
8
+ export function task( func, name = false ) {
9
+ try {
10
+ ( typeof func === "function" ) && func();
11
+ } catch ( e ) {
12
+ printf.error( e );
13
+ console.info( `任务 ${ name } 出错啦 ~~o(>_<)o ~~` );
14
+ throw "";
15
+ }
16
+ }
@@ -0,0 +1,28 @@
1
+ import { isEmptyObject } from "./is-empty-object.js";
2
+
3
+ /**
4
+ * @param obj {object}
5
+ * @param callback {function}
6
+ * @param emCallback {function}
7
+ * @returns {{value: *, index: number, key: string}|undefined}
8
+ */
9
+ function object( obj, callback = () => {}, emCallback = () => {} ) {
10
+ if ( isEmptyObject( obj ) ) {
11
+ return emCallback();
12
+ }
13
+ let i = 0;
14
+ for ( const objKey in obj ) {
15
+ const r = callback( obj[ objKey ], i++, objKey );
16
+ if ( r === "return" ) return {
17
+ value : obj[ objKey ],
18
+ index : i,
19
+ key : objKey
20
+ };
21
+ else if ( r === "break" ) break;
22
+ else if ( r === "continue" ) continue;
23
+ }
24
+ }
25
+
26
+ export const traversal = {
27
+ object
28
+ };
@@ -0,0 +1,23 @@
1
+ app = "nw.exe"
2
+ name = "$name"
3
+ version = "1.0.0"
4
+ description = ""
5
+ author = ""
6
+ license = ""
7
+ domain = "$name"
8
+ chromium-args = "--enable-gpu-rasterization --enable-zero-copy"
9
+ js-flags = "--harmony_collections --jit"
10
+ "node.js" = true
11
+
12
+ [build.platform.config.webkit]
13
+ plugin = true
14
+
15
+ [build.platform.config.window]
16
+ icon = "icon.png"
17
+ width = 1000
18
+ height = 700
19
+ min_width = 1000
20
+ min_height = 700
21
+ frame = true
22
+ transparent = false
23
+ position = "center"
@@ -0,0 +1 @@
1
+ server = { port = 8088, host = "127.0.0.1" }
@@ -0,0 +1,5 @@
1
+ <app lang="zh">
2
+ <title>$name</title>
3
+ <import>
4
+ </import>
5
+ </app>
@@ -0,0 +1,37 @@
1
+ # 配置
2
+ [config]
3
+ # 项目名字
4
+ name = "$name"
5
+ # 源码目录
6
+ src = "app"
7
+ version = "1.0.0"
8
+ description = ""
9
+ author = ""
10
+ license = ""
11
+
12
+ # 构建配置
13
+ [build]
14
+ # 构建目录名
15
+ out = "$name-module"
16
+ # 发布模式 debug | release
17
+ model = "debug"
18
+
19
+ # 构建平台
20
+ [build.platform]
21
+ target = "$target"
22
+
23
+ # 排除项
24
+ [build.exclude]
25
+ file = []
26
+ dir = []
27
+
28
+ # 优化
29
+ [build.optimize]
30
+ # 编译 default-theme 文件
31
+ out-default-theme = true
32
+
33
+ # 压缩代码
34
+ [build.optimize.min-code]
35
+ js = false
36
+ css = false
37
+ html = false
@@ -0,0 +1,43 @@
1
+ # 配置
2
+ [config]
3
+ # 项目名字
4
+ name = "$name"
5
+ # 源码目录
6
+ src = "app"
7
+ # 入口文件名
8
+ main = "index"
9
+
10
+ # 构建配置
11
+ [build]
12
+ # 构建目录名
13
+ out = "build"
14
+ # 发布模式 debug | release
15
+ model = "debug"
16
+
17
+ # 构建平台
18
+ [build.platform]
19
+ target = "$target"
20
+
21
+ # 构建导入
22
+ [build.import]
23
+ module = []
24
+
25
+ # 排除项
26
+ [build.exclude]
27
+ file = []
28
+ dir = []
29
+
30
+ # 优化
31
+ [build.optimize]
32
+ # 编译 default-theme 文件
33
+ out-default-theme = true
34
+
35
+ # 压缩代码
36
+ [build.optimize.min-code]
37
+ js = false
38
+ css = false
39
+ html = false
40
+
41
+ # 构建平台配置
42
+ [build.platform.config]
43
+ $config
@@ -0,0 +1,3 @@
1
+ body > m-cache-element:first-child {
2
+ display: none !important;
3
+ }