@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,253 @@
|
|
|
1
|
+
import { parse } from 'node-html-parser';
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { app } from "../../../../app.js";
|
|
4
|
+
import { printf } from "../../../global/printf.js";
|
|
5
|
+
import { ProjectBuildConfig, ProjectBuildConfigContrast } from "../../../global/project-build-config.js";
|
|
6
|
+
import { macroReplace } from "../macro-replace.js";
|
|
7
|
+
import { _2 } from "./2.js";
|
|
8
|
+
import { bunFS } from "../../../util/bun-fs.js";
|
|
9
|
+
|
|
10
|
+
function parseModuleImportFile( filePath ) {
|
|
11
|
+
if ( !bunFS.existsSync( filePath ) ) {
|
|
12
|
+
throw `module-import 文件不存在 [path:${ filePath }]`;
|
|
13
|
+
}
|
|
14
|
+
const content = bunFS.readFileSync( filePath );
|
|
15
|
+
const lines = content.split( '\n' );
|
|
16
|
+
const result = {
|
|
17
|
+
js : [],
|
|
18
|
+
css : [],
|
|
19
|
+
link : [],
|
|
20
|
+
file : []
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let currentSection = null;
|
|
24
|
+
|
|
25
|
+
for ( const line of lines ) {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if ( trimmed === "" || trimmed.startsWith( "#" ) ) continue;
|
|
28
|
+
|
|
29
|
+
if ( trimmed === "js" ) {
|
|
30
|
+
currentSection = "js";
|
|
31
|
+
} else if ( trimmed === "css" ) {
|
|
32
|
+
currentSection = "css";
|
|
33
|
+
} else if ( trimmed === "link" ) {
|
|
34
|
+
currentSection = "link";
|
|
35
|
+
} else if ( trimmed === "file" ) {
|
|
36
|
+
currentSection = "file";
|
|
37
|
+
} else if ( trimmed.startsWith( "-" ) && currentSection ) {
|
|
38
|
+
let item = trimmed.substring( 1 ).trim();
|
|
39
|
+
let load = "begin";
|
|
40
|
+
|
|
41
|
+
const loadMatch = item.match( /^\[load:(\w+)\]\s*(.+)/ );
|
|
42
|
+
if ( loadMatch ) {
|
|
43
|
+
load = loadMatch[ 1 ];
|
|
44
|
+
item = loadMatch[ 2 ].trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
result[ currentSection ].push( { file : item, load : load } );
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function examine_BuildConfig( root ) {
|
|
55
|
+
printf.outFile.info( `检查项目配置文件` );
|
|
56
|
+
|
|
57
|
+
const build_config = root [ "default" ];
|
|
58
|
+
if ( build_config.build.platform.target === "module" ) {
|
|
59
|
+
ProjectBuildConfigContrast( ProjectBuildConfig.base_module, build_config );
|
|
60
|
+
return build_config;
|
|
61
|
+
}
|
|
62
|
+
const result = ProjectBuildConfigContrast( ProjectBuildConfig.base, build_config );
|
|
63
|
+
if ( result ) {
|
|
64
|
+
if ( !bunFS.existsSync( app.project.dir + build_config.config.src + "/" + build_config.config.main + ".m" ) )
|
|
65
|
+
throw `缺少入口文件! 请确保 ${ build_config.config.main }.m 在 ${ build_config.config.src } 源目录下创建`;
|
|
66
|
+
return build_config;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// group -dir
|
|
71
|
+
// css -src -load:begin|end -...(cover)
|
|
72
|
+
// js -src -load:begin|end -...(cover)
|
|
73
|
+
// link -href -...(cover)
|
|
74
|
+
// file -path
|
|
75
|
+
function examine_AppConfig_parserImport( $import, srcDir ) {
|
|
76
|
+
if ( !$import ) return [];
|
|
77
|
+
!$import.hasAttribute( "dir" ) && $import.setAttribute( "dir", "" );
|
|
78
|
+
const list = [];
|
|
79
|
+
|
|
80
|
+
function initDefault( tagName, element ) {
|
|
81
|
+
if ( tagName === "js" ) {
|
|
82
|
+
!element.hasAttribute( "src" ) && element.setAttribute( "src", "" );
|
|
83
|
+
!element.hasAttribute( "load" ) && element.setAttribute( "load", "begin" );
|
|
84
|
+
} else if ( tagName === "css" ) {
|
|
85
|
+
!element.hasAttribute( "src" ) && element.setAttribute( "src", "" );
|
|
86
|
+
} else if ( tagName === "link" ) {
|
|
87
|
+
!element.hasAttribute( "href" ) && element.setAttribute( "href", "" );
|
|
88
|
+
} else if ( tagName === "group" ) {
|
|
89
|
+
!element.hasAttribute( "dir" ) && element.setAttribute( "dir", "" );
|
|
90
|
+
} else if ( tagName === "file" ) {
|
|
91
|
+
!element.hasAttribute( "path" ) && element.setAttribute( "path", "" );
|
|
92
|
+
!element.hasAttribute( "load" ) && element.setAttribute( "load", "begin" );
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function generateTag( dir, tagName, attribs ) {
|
|
97
|
+
let attrsString = "";
|
|
98
|
+
for ( const attr in attribs ) {
|
|
99
|
+
if ( attr === "src" || attr === "href" || attr === "load" ) continue;
|
|
100
|
+
attrsString += `${ attr.substring( 1 ) }="${ path.normalize( attribs[ attr ] ) }" `.replaceAll( '\n', "" );
|
|
101
|
+
}
|
|
102
|
+
dir = dir.replace( /\\/g, '/' );
|
|
103
|
+
|
|
104
|
+
if ( tagName === "js" ) {
|
|
105
|
+
return `<script src="${ dir }/${ attribs[ "src" ] }" type="text/javascript" ${ attrsString }></script>`;
|
|
106
|
+
} else if ( tagName === "css" ) {
|
|
107
|
+
return `<link rel="stylesheet" type="text/css" href="${ dir }/${ attribs[ "src" ] }" ${ attrsString }/>`;
|
|
108
|
+
} else if ( tagName === "link" ) {
|
|
109
|
+
return `<link href="${ dir }/${ attribs[ "href" ] }" ${ attrsString }/>`;
|
|
110
|
+
}
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function itGroup( group ) {
|
|
115
|
+
!group.hasAttribute( "dir" ) && group.setAttribute( "dir", "" );
|
|
116
|
+
let dir = group.getAttribute( "dir" );
|
|
117
|
+
const attrs = group.attrs;
|
|
118
|
+
delete attrs?.["dir"];
|
|
119
|
+
group.childNodes.forEach( ( element ) => {
|
|
120
|
+
if ( element.nodeType === 3 ) return;
|
|
121
|
+
const tagName = element.rawTagName.toLowerCase();
|
|
122
|
+
if ( tagName === "group" ) return itGroup( element );
|
|
123
|
+
|
|
124
|
+
if ( tagName === "module-import" ) {
|
|
125
|
+
const src = element.getAttribute( "src" );
|
|
126
|
+
if ( !src ) throw `module-import 缺少 src 属性`;
|
|
127
|
+
const moduleDir = path.normalize( srcDir + "/" + dir + "/" + src ).replace( /\\/g, '/' );
|
|
128
|
+
const moduleImportFile = moduleDir + "/.module-import";
|
|
129
|
+
const parsed = parseModuleImportFile( moduleImportFile );
|
|
130
|
+
const moduleRelativeDir = "./" + moduleDir.substring( srcDir.replace( /\\/g, '/' ).length + 1 );
|
|
131
|
+
|
|
132
|
+
parsed.js.forEach( item => {
|
|
133
|
+
list.push( {
|
|
134
|
+
o : {
|
|
135
|
+
element : generateTag( moduleRelativeDir, "js", { "src" : item.file } ),
|
|
136
|
+
load : item.load
|
|
137
|
+
}
|
|
138
|
+
} );
|
|
139
|
+
} );
|
|
140
|
+
|
|
141
|
+
parsed.css.forEach( item => {
|
|
142
|
+
list.push( {
|
|
143
|
+
o : {
|
|
144
|
+
element : generateTag( moduleRelativeDir, "css", { "src" : item.file } ),
|
|
145
|
+
load : item.load
|
|
146
|
+
}
|
|
147
|
+
} );
|
|
148
|
+
} );
|
|
149
|
+
|
|
150
|
+
parsed.link.forEach( item => {
|
|
151
|
+
list.push( {
|
|
152
|
+
o : {
|
|
153
|
+
element : generateTag( moduleRelativeDir, "link", { "href" : item.file } ),
|
|
154
|
+
load : item.load
|
|
155
|
+
}
|
|
156
|
+
} );
|
|
157
|
+
} );
|
|
158
|
+
|
|
159
|
+
parsed.file.forEach( item => {
|
|
160
|
+
const filePath = path.normalize( moduleDir + "/" + item.file );
|
|
161
|
+
if ( !bunFS.existsSync( filePath ) ) throw `module-import 文件不存在 [path:${ filePath }]`;
|
|
162
|
+
const content = bunFS.readFileSync( filePath );
|
|
163
|
+
list.push( {
|
|
164
|
+
o : {
|
|
165
|
+
element : content,
|
|
166
|
+
load : item.load
|
|
167
|
+
}
|
|
168
|
+
} );
|
|
169
|
+
} );
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if ( tagName === "js" || tagName === "css" || tagName === "link" || tagName === "file" ) {
|
|
174
|
+
initDefault( tagName, element );
|
|
175
|
+
} else return;
|
|
176
|
+
|
|
177
|
+
if ( tagName === "js" || tagName === "css" || tagName === "link" ) {
|
|
178
|
+
for ( const attr in attrs ) {
|
|
179
|
+
if ( !element.hasAttribute( attr ) ) element.setAttribute( attr, attrs[ attr ] );
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const load = element.getAttribute( "load" );
|
|
184
|
+
element.removeAttribute( "load" );
|
|
185
|
+
|
|
186
|
+
if ( tagName === "file" ) {
|
|
187
|
+
const content = () => {
|
|
188
|
+
const ep = element.getAttribute( "path" );
|
|
189
|
+
if ( ep.length > 1 ) {
|
|
190
|
+
if ( !bunFS.existsSync( ep ) ) throw `导入的文件不存在 [path:${ ep }]`;
|
|
191
|
+
return bunFS.readFileSync( ep );
|
|
192
|
+
}
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
list.push( {
|
|
196
|
+
o : {
|
|
197
|
+
element : content(),
|
|
198
|
+
load : load
|
|
199
|
+
}
|
|
200
|
+
} );
|
|
201
|
+
} else {
|
|
202
|
+
list.push( {
|
|
203
|
+
o : {
|
|
204
|
+
element : generateTag( dir, tagName, element.attrs ),
|
|
205
|
+
load : load
|
|
206
|
+
}
|
|
207
|
+
} );
|
|
208
|
+
}
|
|
209
|
+
} );
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
itGroup( $import );
|
|
214
|
+
|
|
215
|
+
return list;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function examine_AppConfig( build_config ) {
|
|
219
|
+
const fp = path.normalize( app.project.dir + build_config.config.src + "/app.xml" );
|
|
220
|
+
printf.outFile.info( `预处理 app.xml 配置文件 [path:${ fp }]` );
|
|
221
|
+
|
|
222
|
+
if ( !bunFS.existsSync( fp ) ) {
|
|
223
|
+
throw `源码目录不存在 app.xml`;
|
|
224
|
+
}
|
|
225
|
+
const content = bunFS.readFileSync( fp );
|
|
226
|
+
if ( content.length === 0 ) {
|
|
227
|
+
throw `app.xml 内容是空的`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 检查 app.xml
|
|
231
|
+
const $app = parse( content );
|
|
232
|
+
if ( $app.childNodes.length === 0 ) throw "app.xml 是空的";
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
title : $app.querySelector( "app>title" ) ? $app.querySelector( "app>title" ).text : "magic",
|
|
236
|
+
lang : $app.attrs.lang || "zh",
|
|
237
|
+
icon : $app.attrs.icon || false,
|
|
238
|
+
initScript : $app.querySelector( "app>init-script" ) ? $app.querySelector( "app>init-script" ).text.trim() : null,
|
|
239
|
+
import : examine_AppConfig_parserImport( $app.querySelector( "app>import" ), app.project.dir + build_config.config.src )
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function _1( root ) {
|
|
244
|
+
const config = examine_BuildConfig( root );
|
|
245
|
+
if ( config ) {
|
|
246
|
+
printf.outFile.info( `Build 配置 :`, JSON.stringify( config ) );
|
|
247
|
+
if ( config.build.platform.target === "module" ) {
|
|
248
|
+
_2( config );
|
|
249
|
+
} else {
|
|
250
|
+
_2( config, macroReplace( examine_AppConfig( config ) ) );
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { app } from "../../../../app.js";
|
|
3
|
+
import { printf } from "../../../global/printf.js";
|
|
4
|
+
import { fileUtil } from "../../../util/file-util.js";
|
|
5
|
+
import { filtrationFile } from "../../../util/filtration-file.js";
|
|
6
|
+
import { project } from "../global.js";
|
|
7
|
+
import { _3 } from "./3.js";
|
|
8
|
+
import { bunFS, computeHash } from "../../../util/bun-fs.js";
|
|
9
|
+
|
|
10
|
+
class source {
|
|
11
|
+
#relativePath;
|
|
12
|
+
#absolutePath;
|
|
13
|
+
#buildPath;
|
|
14
|
+
#contentHash;
|
|
15
|
+
|
|
16
|
+
constructor( file, ext, contentHash = null ) {
|
|
17
|
+
this.ext = ext;
|
|
18
|
+
this.#relativePath = path.normalize( file );
|
|
19
|
+
this.#absolutePath = path.normalize( app.project.dir + project.build_config.config.src + '/' + file );
|
|
20
|
+
this.#buildPath = path.normalize( app.project.dir + project.build_config.build.out + '/' + file );
|
|
21
|
+
this.#contentHash = contentHash;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
build() {
|
|
25
|
+
return this.#buildPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
relative() {
|
|
29
|
+
return this.#relativePath;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
absolute() {
|
|
33
|
+
return this.#absolutePath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get contentHash() {
|
|
37
|
+
return this.#contentHash;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function _2( build_config, app_config ) {
|
|
42
|
+
printf.outFile.info( `处理源文件` );
|
|
43
|
+
const rootDir = app.project.dir + build_config.config.src;
|
|
44
|
+
|
|
45
|
+
build_config.build.exclude.file.push( "app.xml" );
|
|
46
|
+
build_config.build.exclude.dir.push( "magic" );
|
|
47
|
+
|
|
48
|
+
const files = filtrationFile(
|
|
49
|
+
rootDir,
|
|
50
|
+
build_config.build.exclude
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
project[ "build_config" ] = build_config;
|
|
54
|
+
project[ "app_config" ] = app_config;
|
|
55
|
+
project[ "source_file" ] = ( () => {
|
|
56
|
+
const obj = {};
|
|
57
|
+
files.forEach( file => {
|
|
58
|
+
const ext = fileUtil.getExtensionName( file );
|
|
59
|
+
if ( !obj[ ext ] ) obj[ ext ] = [];
|
|
60
|
+
const hash = computeHash( bunFS.readFileSync( path.normalize( rootDir + '/' + file ) ) );
|
|
61
|
+
obj[ ext ].push( new source( file, ext, hash ) );
|
|
62
|
+
} );
|
|
63
|
+
return {
|
|
64
|
+
...obj,
|
|
65
|
+
"*it" : () => {
|
|
66
|
+
const result = [];
|
|
67
|
+
for ( const key in obj ) {
|
|
68
|
+
result.push( ...obj[ key ] );
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
} )();
|
|
74
|
+
project[ "outDir" ] = path.normalize( app.project.dir + project.build_config.build.out + '/' );
|
|
75
|
+
project[ "outDirMagic" ] = path.normalize( project[ "outDir" ] + "magic/" );
|
|
76
|
+
project[ "srcDir" ] = path.normalize( app.project.dir + project.build_config.config.src + '/' );
|
|
77
|
+
|
|
78
|
+
_3();
|
|
79
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { app } from "../../../../app.js";
|
|
3
|
+
import { printf } from "../../../global/printf.js";
|
|
4
|
+
import { project } from "../global.js";
|
|
5
|
+
import { _4 } from "./4.js";
|
|
6
|
+
import { bunFS } from "../../../util/bun-fs.js";
|
|
7
|
+
|
|
8
|
+
export function _3() {
|
|
9
|
+
printf.outFile.info( `初始化构建项目` );
|
|
10
|
+
|
|
11
|
+
const outDir = project.outDir;
|
|
12
|
+
const outDirMagic = project.outDirMagic;
|
|
13
|
+
const isModule = project.build_config.build.platform.target === "module";
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
bunFS.rmSync( outDir, {
|
|
17
|
+
recursive : true,
|
|
18
|
+
force : true
|
|
19
|
+
} );
|
|
20
|
+
} catch {}
|
|
21
|
+
|
|
22
|
+
if ( !bunFS.existsSync( outDir ) ) {
|
|
23
|
+
bunFS.mkdirSync( outDir, { recursive : true } );
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if ( isModule ) {
|
|
27
|
+
project.outDirMagic = outDir;
|
|
28
|
+
} else {
|
|
29
|
+
if ( !bunFS.existsSync( outDirMagic ) ) {
|
|
30
|
+
bunFS.mkdirSync( outDirMagic, { recursive : true } );
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
bunFS.writeFileSync( `${ outDirMagic }/runtime.js`, `window[ "magic_version" ] = "${ app.version }";\n` + app.templateDir.runtime.get( "runtime.js" ) );
|
|
34
|
+
bunFS.writeFileSync( `${ outDirMagic }/runtime.css`, app.templateDir.runtime.get( "runtime.css" ) );
|
|
35
|
+
}
|
|
36
|
+
_4();
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { printf } from "../../../global/printf.js";
|
|
2
|
+
import { project } from "../global.js";
|
|
3
|
+
import { _5 } from "./5.js";
|
|
4
|
+
import { bunFS } from "../../../util/bun-fs.js";
|
|
5
|
+
|
|
6
|
+
export function _4() {
|
|
7
|
+
printf.outFile.info( `操作源文件` );
|
|
8
|
+
|
|
9
|
+
const m = [];
|
|
10
|
+
|
|
11
|
+
project.source_file[ "*it" ]().forEach( s => {
|
|
12
|
+
if ( s.ext === "m" ) {
|
|
13
|
+
m.push( s );
|
|
14
|
+
} else {
|
|
15
|
+
bunFS.cpSync( s.absolute(), s.build(), { recursive : true } );
|
|
16
|
+
}
|
|
17
|
+
} );
|
|
18
|
+
|
|
19
|
+
_5( m );
|
|
20
|
+
}
|