@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,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
+ }