├── .gitignore
├── .npmignore
├── LICENSE
├── README.chinese.md
├── README.md
├── global.d.ts
├── package.json
├── src
├── bin
│ ├── vepp-auto.ts
│ ├── vepp-compile.ts
│ ├── vepp-init.ts
│ └── vepp.ts
├── core
│ ├── config.ts
│ ├── index.ts
│ ├── main.ts
│ ├── polyfills.ts
│ └── proxy.ts
└── utils
│ ├── cli.ts
│ ├── general.ts
│ └── vml.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | .VSCodeCounter/
4 | package-lock.json
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | src/
3 | .VSCodeCounter/
4 | package-lock.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Zhenyang, Tan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.chinese.md:
--------------------------------------------------------------------------------
1 | # Vepp
2 |
3 | 一个为 ZeppOS 打造的轻量级 TypeScript 框架。其起初目的是改善原生 ZeppOS 中控件系统的糟糕体验,使开发更加现代化。
4 |
5 | # 多语言
6 |
7 | - [English](https://github.com/jwhgzs/vepp/blob/master/README.md)
8 | - 中文(当前)
9 |
10 | # 开始之前
11 |
12 | 首先你得知道,在 ZeppOS 中有很多难解决的开发限制:
13 |
14 | - `eval()` 和 `new Function()` 被禁用
15 | - 糟糕的控件系统
16 | - 糟糕的运行效率(也许是普遍的硬件问题)
17 | - 坑人的 API 特性
18 |
19 | 你可能不相信, Vepp 使这些问题迎刃而解!
20 |
21 | - 巧妙地绕过 `new Function()` 的禁用限制
22 | - 重新打造一个完美的控件系统
23 | - 尽量精简代码,提高代码效率
24 | - 采用 CLI 方式提前编译 VML 语法(见下~),提高运行效率
25 | - 更改 API 特性,使其陷阱更少
26 |
27 | 现在,让我们开始吧~
28 |
29 | # 快速开始
30 |
31 | 让我们先来看看用 Vepp 开发的 ZeppOS 程序长什么样吧:
32 |
33 | ```html
34 |
35 |
36 |
37 |
38 |
58 |
59 |
60 |
61 |
62 |
63 |
74 |
75 |
85 | ```
86 |
87 | 很惊讶吧?我当然猜到了——这是 Vepp 最引以为傲的一点,贴近 Vue 等主流前端框架的设计思路和特性,对有过前端开发经历的各位很友好哦!
88 |
89 | 你就要去试啦?稍等一下,还没介绍完呢——由于这骚气 VML 的语法,你需要 VeppCLI 的一臂之力。来看看 Vepp “独具特色”的使用指南:
90 |
91 | ```bash
92 | # Vepp 把核心部分和 CLI 部分合并在一个 NPM 包里了~
93 | # 警告:在 ZeppOS 项目里直接 import 全局包是不管用的哦!所以才需要像我这样,分别在你的项目和全局都安装一次
94 | npm install vepp
95 | npm install vepp -g
96 | ```
97 |
98 | 安装好了,接下来就试试编译吧……
99 |
100 | ```bash
101 | vepp init test-project
102 | cd ./test-project
103 | vepp compile
104 | ```
105 |
106 | OK 啦!你也可以使用观察者模式, Vepp 会监听文件更改并自动进行编译!
107 |
108 | ```bash
109 | # 警告: Vepp 的观察者模式不能和其他应用的观察者模式(如 ZeppCLI 的)一起使用,否则可能会监听失效!
110 | vepp auto
111 | ```
112 |
113 | # 进阶功能
114 |
115 | ### 双向绑定
116 |
117 | 尽管 Vepp 的设计大体上遵循原生 ZeppOS 的接口,力求使原生 API 的特征都能在 Vepp 中映射。但是对于有些很令人难受的控件 API(比如表单、列表等),我们还是自己动手、丰衣足食吧!
118 |
119 | 让我们先来看个表单的小例子:
120 |
121 | ```html
122 |
123 |
124 |
125 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
140 | ```
141 |
142 | 够简单了吧?除了设置名为 `vepp_value` 的 property ,你无需写任何逻辑代码即可实现表单的数据流。我认为没有必要介绍更多详细内容了!
143 |
144 | # Polyfills
145 |
146 | Vepp 为你准备了多样、实用的 polyfills ,这里给出它们的列表:
147 |
148 | - class `Buffer`
149 | - class `Function`
150 | - class `Logger`
151 | - function `setTimeout`
152 | - function `clearTimeout`
153 | - function `setInterval`
154 | - function `clearInterval`
155 | - function `setImmediate`
156 | - function `clearImmediate`
157 | - function `console.time`
158 | - function `console.timeEnd`
159 |
160 | # 其他细节
161 |
162 | - Vepp 的依赖追踪仅限于 `Array` 和纯粹 `Object` (构造函数为 `Object` 的对象),其他诸如 `Map` 、 `Set` 等未做适配
163 | - VML 中的字符 `-` 和 `_` 等效; VML 对大小写不敏感
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vepp
2 |
3 | A light TypeScript framework for ZeppOS, be maked to solve the rubbish widget system in native ZeppOS.
4 |
5 | # Languagues
6 |
7 | - English (current)
8 | - [Chinese](https://github.com/jwhgzs/vepp/blob/master/README.chinese.md)
9 |
10 | # Before start
11 |
12 | You have to know, in ZeppOS, there are so many hard-to-solve limits:
13 |
14 | - `eval()` and `new Function()` are disabled
15 | - Rubbish widget system
16 | - Rubbish running efficiency (may be caused by hardware)
17 | - Deceptive APIs
18 |
19 | But maybe you don't believe, Vepp solves all of them:
20 |
21 | - Skip `new Function()`'s limit by a special way
22 | - Build a pefect widget system in your favourite way
23 | - Make core codes short, easy and fast
24 | - Precompile the VML syntax (see below) by CLI, make it faster and faster
25 | - Deal with deceptive APIs for you
26 |
27 | Now, start learning and enjoy!
28 |
29 | # Quick start
30 |
31 | Let's see what ZeppOS apps made by Vepp is like:
32 |
33 | ```html
34 |
35 |
36 |
37 |
38 |
59 |
60 |
61 |
62 |
63 |
64 |
75 |
76 |
86 | ```
87 |
88 | Are you surprised? Yes, that's the way to work with her: everyone who has experienced front-end development knows how to use it now!
89 |
90 | But wait a moment! This syntax which seems like HTML cannot be compiled by ZeppCLI directly. You may use Vepp's CLI and be with its help. Here is how to install Vepp and its CLI:
91 |
92 | ```bash
93 | # Vepp merges its core part and its CLI in a package.
94 | # WARNING: it is invalid to import global packages in ZeppOS's project, so you MUST do like this:
95 | npm install vepp
96 | npm install vepp -g
97 | ```
98 |
99 | And then initialize and compile your `.vepp` files in the project:
100 |
101 | ```bash
102 | vepp init test-project
103 | cd ./test-project
104 | vepp compile
105 | ```
106 |
107 | It's done! What's more, you can do this and use the watcher mode to compile automatically when changing:
108 |
109 | ```bash
110 | # WARNING: although this mode is cool, it may not work as usual while other file watchers are working (like ZeppCLI)
111 | vepp auto
112 | ```
113 |
114 | # Advanced Features
115 |
116 | ### Two-way Bindings
117 |
118 | Although Vepp follows the native ZeppOS's APIs overall, for example, you can do anything in Vepp as you are in native ZeppOS, there are many troublesome and trivial things: e.g. forms, lists, etc. But Vepp has already considered!
119 |
120 | Let's see an easy example of form:
121 |
122 | ```html
123 |
124 |
125 |
126 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
141 | ```
142 |
143 | Is it pretty enough? You needn't do anything but to set the special built-in property named `vepp_value`, and then you achieve the data stream on forms! I think it's no need for me to introduce more in this example :)
144 |
145 | # Polyfills
146 |
147 | Vepp prepares varied polyfills for you, here we have the list of them:
148 |
149 | - class `Buffer`
150 | - class `Function`
151 | - class `Logger`
152 | - function `setTimeout`
153 | - function `clearTimeout`
154 | - function `setInterval`
155 | - function `clearInterval`
156 | - function `setImmediate`
157 | - function `clearImmediate`
158 | - function `console.time`
159 | - function `console.timeEnd`
160 |
161 | # Other details
162 |
163 | - The dependencies tracking feature is only for `Array` and plain `Object` (with native constructor `Object`), other objects like `Map` and `Set` are NOT including
164 | - `-` and `_` are the same in VML; VML ignores case
--------------------------------------------------------------------------------
/global.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | const DeviceRuntimeCore: any
4 |
5 | declare module 'download-git-repo'
6 | declare module 'ora'
7 | declare module 'inquirer'
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vepp",
3 | "type": "module",
4 | "version": "0.12.0",
5 | "description": "A powerful framework for ZeppOS.",
6 | "main": "dist/core/index.js",
7 | "bin": {
8 | "vepp": "dist/bin/vepp.js",
9 | "vepp-init": "dist/bin/vepp-init.js",
10 | "vepp-complile": "dist/bin/vepp-compile.js",
11 | "vepp-auto": "dist/bin/vepp-auto.js"
12 | },
13 | "scripts": {
14 | "test": "echo \"Error: no test specified\" && exit 1",
15 | "dev": "tsc --watch"
16 | },
17 | "keywords": [
18 | "zepp",
19 | "vue",
20 | "vior"
21 | ],
22 | "author": "jwhgzs",
23 | "license": "MIT",
24 | "dependencies": {
25 | "chalk": "^2.3.0",
26 | "commander": "^2.11.0",
27 | "download-git-repo": "^1.0.1",
28 | "inquirer": "^4.0.2",
29 | "node-watch": "^0.7.3",
30 | "ora": "^1.3.0",
31 | "rimraf": "^2.6.2"
32 | },
33 | "devDependencies": {
34 | "@types/node": "^18.14.6",
35 | "typescript": "^4.9.5",
36 | "zeppos-device-types-v1": "latest"
37 | },
38 | "repository": {
39 | "type": "git",
40 | "url": "git+https://github.com/jwhgzs/vepp.git"
41 | },
42 | "bugs": {
43 | "url": "https://github.com/jwhgzs/vepp/issues"
44 | },
45 | "homepage": "https://github.com/jwhgzs/vepp#readme"
46 | }
47 |
--------------------------------------------------------------------------------
/src/bin/vepp-auto.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import path from 'path'
3 | import program from 'commander'
4 | import chalk from 'chalk'
5 | import ora from 'ora'
6 | import { execSync } from 'child_process'
7 | import watch from 'node-watch'
8 |
9 | import { GeneralUtil as GUtil } from '../utils/general.js'
10 |
11 | program.parse(process.argv)
12 | let rpath = path.resolve('.')
13 |
14 | console.log(chalk.bgBlue('NOTICE! If this does not work as usual, please make sure that other file watchers (like ZeppCLI) are not working!'))
15 |
16 | let handler = (_: any = null, fname: string = '') => {
17 | let spath = path.relative(rpath, fname)
18 | if (fname && (path.extname(fname) != GUtil.ext || spath.indexOf('node_modules/') >= 0))
19 | return
20 | if (fname)
21 | console.log(chalk.blue(`- file changed: ${spath}`))
22 | let spinner = ora('compiling...').start()
23 | console.log()
24 | console.time('* time')
25 | try { execSync(`vepp compile`) } catch (ex) {
26 | console.log(chalk.red(`error when compiling! please simply run 'vepp compile' to display the errors.`))
27 | process.exit(1)
28 | }
29 | console.log(chalk.green(`* compiled automatically!`))
30 | console.timeEnd('* time')
31 | spinner.stop()
32 | }
33 | watch(rpath, { recursive: true }, handler)
34 |
35 | handler()
--------------------------------------------------------------------------------
/src/bin/vepp-compile.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import path from 'path'
3 | import program from 'commander'
4 | import chalk from 'chalk'
5 | import fs from 'fs'
6 | import ora from 'ora'
7 |
8 | import { T_VeppCtorUIOption } from '../core/main.js'
9 | import { GeneralUtil as GUtil, T_JSON } from '../utils/general.js'
10 | import { VMLParser, VMLNode, VMLNodeAttrs } from '../utils/vml.js'
11 | import { CLIUtil as CUtil } from '../utils/cli.js'
12 |
13 | program.parse(process.argv)
14 | let rpath = path.resolve('.') + '/'
15 |
16 | console.time('* time')
17 | let spinner = ora('compiling project...').start()
18 | console.log()
19 |
20 | let walk = (dir: string, handler: Function) => {
21 | let arr = fs.readdirSync(dir)
22 | for (let k in arr) {
23 | let v = arr[k]
24 | let cpath = dir + v
25 | let stat = fs.statSync(cpath)
26 | if (stat.isDirectory())
27 | walk(cpath + '/', handler)
28 | else if (path.extname(cpath) == GUtil.ext)
29 | handler(cpath)
30 | }
31 | }
32 | let err = (msg: string) => {
33 | console.log(chalk.red('* Error when compiling: ' + msg))
34 | process.exit(1)
35 | }
36 | let warn = (msg: string) => {
37 | console.log(chalk.yellow('* Warning when compiling: ' + msg))
38 | }
39 | let compileUI = (fpath: string, vml: VMLNode[], dest: T_VeppCtorUIOption[], data: T_JSON, pid: string = 'ROOT') => {
40 | for (let k in vml) {
41 | let v = vml[k]
42 | let tag = v.tag
43 | if (tag == 'script')
44 | err(`ignored 'script' element: ` + fpath)
45 | else if (! tag) continue
46 |
47 | let id = GUtil.tmpPrefix + GUtil.randomText()
48 | let props: VMLNodeAttrs = GUtil.deepCopy(v.attrs) as VMLNodeAttrs
49 | for (let k2 in props) {
50 | let v2 = props[k2]
51 | if (! k2.startsWith(':') && ! k2.startsWith('@')) {
52 | delete props[k2]
53 | k2 = ':' + k2
54 | v2 = `\`${v2.replace(/`/g, '\\`')}\``
55 | props[k2] = v2
56 | }
57 | }
58 | for (let k2 in props) {
59 | let v2 = props[k2]
60 | if (k2.startsWith(':')) {
61 | delete props[k2]
62 | k2 = k2.substring(1)
63 | props[k2] = v2
64 | }
65 | }
66 | for (let k2 in props) {
67 | let v2 = props[k2]
68 | if (! k2.startsWith('@')) {
69 | if (k2 == 'vepp_value') {
70 | delete props[k2]
71 | if (tag == 'radio_group' || tag == 'checkbox_group') {
72 | const tmpid = id + '_options', tmpid2 = id + '_buf'
73 | if ('init' in props)
74 | err(`ignored 'init' property: ` + fpath)
75 | data[tmpid] = {}
76 | data[tmpid2] = null
77 | const oldcode = props.check_func
78 | ? `(${props.check_func})(...$args)`
79 | : ''
80 | if (tag == 'radio_group') {
81 | props.init = props.checked = `${tmpid}[${v2}]`
82 | props.check_func = `(...$args)=>{if($args[2])${v2}=Object.keys(${tmpid})[$args[1]];${oldcode}}`
83 | }
84 | else {
85 | props.init = `${tmpid}[${v2}[0]]`
86 | if (! ('@vepp_init' in props))
87 | props['@vepp_init'] = ''
88 | props['@vepp_init'] = `!${tmpid2}&&(${tmpid2}=new Set([${v2}[0]]));$vepp.watch(()=>{$vepp.constructor.util.diff(${tmpid2},${v2},v=>$widget.setProperty(hmUI.prop.CHECKED,${tmpid}[v]),v=>$widget.setProperty(hmUI.prop.UNCHECKED,${tmpid}[v]))});${props['@vepp_init']}`
89 | props.check_func = `(...$args)=>{const k=$args[2]?'add':'delete',v=Object.keys(${tmpid})[$args[1]];v&&(${tmpid2}[k](v),${v2}[k](v));${oldcode}}`
90 | }
91 | }
92 | else if (tag == 'state_button') {
93 | const tmpid = pid + '_options'
94 | if (! (tmpid in data))
95 | err(`invalid 'vepp_value' property: ` + fpath)
96 | if (! ('@vepp_init' in props))
97 | props['@vepp_init'] = ''
98 | props['@vepp_init'] = `${tmpid}[${v2}]=$widget;${props['@vepp_init']}`
99 | }
100 | else if (tag == 'slide_switch') {
101 | props.checked = `${v2}`
102 | const oldcode = props.checked_change_func
103 | ? `(${props.checked_change_func})(...$args)`
104 | : ''
105 | props.checked_change_func = `$vepp.constructor.util.delay((...$args)=>{${v2}=$args[1];${oldcode}})`
106 | }
107 | else {
108 | err(`invalid 'vepp_value' property: ` + fpath)
109 | }
110 | }
111 | }
112 | }
113 | for (let k2 in props) {
114 | let v2 = props[k2]
115 | if (k2.startsWith('@')) {
116 | props[k2] = `($arg)=>{${v2}}`
117 | }
118 | }
119 | let children: T_VeppCtorUIOption[] = []
120 | compileUI(fpath, v.children, children, data, id)
121 |
122 | let d: VMLNodeAttrs = Object.assign(
123 | props,
124 | {
125 | $tag: tag,
126 | $children: children
127 | }
128 | )
129 | let buf1: VMLNodeAttrs = {}, buf2: VMLNodeAttrs = {}
130 | for (let k2 in d) {
131 | let v2 = d[k2]
132 | if (CUtil.priorAttrs.includes(k2))
133 | buf1[k2] = v2
134 | else
135 | buf2[k2] = v2
136 | }
137 | d = Object.assign(buf1, buf2)
138 | for (let k2 in d) {
139 | if (k2.endsWith('_func')) {
140 | let v2 = d[k2]
141 | delete d[k2]
142 | d[k2] = v2
143 | }
144 | }
145 | for (let k2 of CUtil.laterAttrs) {
146 | if (k2 in d) {
147 | let v2 = d[k2]
148 | delete d[k2]
149 | d[k2] = v2
150 | }
151 | }
152 | dest.push(d)
153 | }
154 | }
155 | let compile = (fpath: string) => {
156 | let src = fs.readFileSync(fpath, 'utf-8')
157 | let rfname = path.basename(fpath)
158 | let fname = rfname.substring(0, rfname.length - path.extname(fpath).length)
159 | let c_my = '', c_mypre = '', c_gen = ''
160 | let vml: VMLNode[] | undefined, tmp: VMLNode | string
161 | const ui: T_VeppCtorUIOption[] = [], data: T_JSON = { $w: 0, $h: 0 }
162 | tmp = VMLParser.read(src)
163 | try { vml = (tmp as VMLNode).children } catch {}
164 | if (! vml) {
165 | err(tmp + ': ' + fpath)
166 | return
167 | }
168 |
169 | let k = 0
170 | for (let v of vml) {
171 | if (v.tag == 'script' && 0 in v.children) {
172 | if ('pre' in v.attrs)
173 | c_mypre += v.children[0].text
174 | else
175 | c_my += v.children[0].text
176 | vml.splice(k, 1)
177 | }
178 | k ++
179 | }
180 | let ids = GUtil.scriptReg(c_my, /this\s*\.\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\b/ig) as RegExpExecArray[]
181 | for (let v of ids) {
182 | data[v[1]] = null
183 | }
184 | compileUI(fpath, vml, ui, data)
185 |
186 | c_gen += `$vepp = new ${GUtil.tmpPrefix}Vepp({ ui: ${JSON.stringify(ui)}, data: {} }, true); `
187 | c_gen += `let $pre_data = ${JSON.stringify(data)}; for (let k in $pre_data) { $vepp.data[k] = $pre_data[k]; }; $pre_data = null; (function () { ${c_mypre} }).call($vepp.data); $vepp.init(); `
188 | c_gen += `(function () { this.$w = $w; this.$h = $h; ${c_my}; }).call($vepp.data); `
189 |
190 | let aname = path.dirname(fpath) + '/' + fname + '.js'
191 | let res = `var { width: $w, height: $h } = hmSetting.getDeviceInfo(); import { Vepp as ${GUtil.tmpPrefix}Vepp } from 'vepp/dist/core/main.js'; var $vepp; Page({ build() { ${c_gen} }, onDestroy() { $vepp = null; } });`
192 | fs.writeFileSync(aname, res)
193 | }
194 | if (fs.existsSync(rpath + 'page/'))
195 | walk(rpath + 'page/', compile)
196 | if (fs.existsSync(rpath + 'watchface/'))
197 | walk(rpath + 'watchface/', compile)
198 |
199 | spinner.stop()
200 | console.log(chalk.green('* compilation complete!'))
201 | console.timeEnd('* time')
--------------------------------------------------------------------------------
/src/bin/vepp-init.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import path from 'path'
3 | import download from 'download-git-repo'
4 | import program from 'commander'
5 | import chalk from 'chalk'
6 | import fs from 'fs'
7 | import ora from 'ora'
8 | import { execSync } from 'child_process'
9 | import inquirer from 'inquirer'
10 |
11 | import { CLIUtil as CUtil } from '../utils/cli.js'
12 |
13 | program
14 | .usage(``)
15 | .parse(process.argv)
16 |
17 | let projectName = program.args[0]
18 | if (! projectName) {
19 | console.log(chalk.red(`missing argument 'project-name'!`))
20 | process.exit(1)
21 | }
22 | let choose = await inquirer.prompt([{
23 | type: 'list',
24 | name: 'type',
25 | message: 'please choose your app type:',
26 | choices: [
27 | 'app', 'watchface'
28 | ]
29 | }])
30 | choose = choose.type
31 |
32 | let rpath = path.resolve(projectName)
33 |
34 | console.time('* time')
35 | let spinner = ora('downloading template...').start()
36 | console.log()
37 | if (fs.existsSync(rpath))
38 | CUtil.delDir(rpath)
39 |
40 | download('jwhgzs/vepp-template', rpath, { clone: false }, function (err: any) {
41 | try {
42 | spinner.stop()
43 | if (err) {
44 | console.log(chalk.red('* downloaded failed!'))
45 | process.exit(1)
46 | }
47 | const tmpSuffix = '_______tmp'
48 | console.log(chalk.green('* downloaded successfully!'))
49 | if (fs.existsSync(rpath + tmpSuffix))
50 | CUtil.delDir(rpath + tmpSuffix)
51 | fs.renameSync(rpath + '/' + choose, rpath + tmpSuffix)
52 | CUtil.delDir(rpath)
53 | fs.renameSync(rpath + tmpSuffix, rpath)
54 |
55 | spinner = ora('installing dependencies...').start()
56 | console.log()
57 | execSync(`cd ${rpath} && npm install`)
58 | spinner.stop()
59 | console.log(chalk.green('* installed successfully!'))
60 | console.timeEnd('* time')
61 | }
62 | catch (ex) {
63 | console.log(chalk.red('* initialization failed!' + ex))
64 | }
65 | })
--------------------------------------------------------------------------------
/src/bin/vepp.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import program from 'commander'
3 | import path from 'path'
4 | import url from 'url'
5 | import fs from 'fs'
6 |
7 | globalThis.__dirname = path.dirname(url.fileURLToPath(import.meta.url))
8 | let packageJson = JSON.parse(
9 | fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8')
10 | )
11 |
12 | program
13 | .version(packageJson.version)
14 | .command('init', 'initialize a new Vepp project.')
15 | .command('compile', 'compile the current Vepp project.')
16 | .command('auto', 'enable automatically compiling on current Vepp project.')
17 | .parse(process.argv)
--------------------------------------------------------------------------------
/src/core/config.ts:
--------------------------------------------------------------------------------
1 | import { T_FREE } from '../utils/general'
2 |
3 | export const DEVICE_WIDTH = hmSetting.getDeviceInfo().width
4 | export const DEVICE_HEIGHT = hmSetting.getDeviceInfo().height
5 |
6 | export const noTrackingProps = ['init']
7 | export const needToFuckWidgets = [
8 | 'button', 'fill_rect', 'radio_group', 'checkbox_group', 'slide_switch'
9 | ]
10 | export const needToFuckProps = [
11 | 'x', 'y', 'w', 'h', 'slide_select_x', 'slide_un_select_x'
12 | ]
13 | export const defaultConfig: { [_: string]: T_FREE } = {
14 | '': {
15 | x: 0,
16 | y: 0,
17 | w: DEVICE_WIDTH,
18 | h: DEVICE_HEIGHT
19 | },
20 | text: {
21 | x: 0,
22 | y: 0,
23 | w: DEVICE_WIDTH,
24 | h: DEVICE_HEIGHT,
25 | align_h: hmUI.align.CENTER_H,
26 | align_v: hmUI.align.CENTER_V,
27 | color: 0xffffff
28 | },
29 | button: {
30 | x: 0,
31 | y: 0,
32 | w: 100,
33 | h: 40
34 | },
35 | slide_switch: {
36 | x: 0,
37 | y: 0,
38 | w: 96,
39 | h: 64,
40 | slide_select_x: 40,
41 | slide_un_select_x: 8
42 | }
43 | }
--------------------------------------------------------------------------------
/src/core/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nonafox/vepp/9ae8ada6038b88b11970dd1fbc3846b8b5173c37/src/core/index.ts
--------------------------------------------------------------------------------
/src/core/main.ts:
--------------------------------------------------------------------------------
1 | import './polyfills.js'
2 |
3 | import { GeneralUtil as GUtil, T_JSON, T_FREE } from '../utils/general.js'
4 | import { createProxy, createReactiveContext } from './proxy.js'
5 | import { noTrackingProps, needToFuckWidgets, needToFuckProps, defaultConfig } from './config.js'
6 |
7 | export type T_VeppCtorUIOption = { [_: string]: string | T_VeppCtorUIOption[] }
8 |
9 | interface I_VeppCtorOption {
10 | ui: T_VeppCtorUIOption[]
11 | data: T_JSON
12 | }
13 |
14 | export class Vepp {
15 | private ui: T_VeppCtorUIOption[]
16 | public data: any
17 | public static restStack: number = 0
18 | public static util: GUtil = GUtil
19 |
20 | public constructor(opts: I_VeppCtorOption, builtin: boolean = false) {
21 | if (! builtin) {
22 | throw new Error(`Invalid usage of Vepp.`)
23 | }
24 | this.ui = opts.ui || []
25 | this.data = createProxy(opts.data || {}, this)
26 | }
27 | public init(json: T_VeppCtorUIOption[] = this.ui, ctor: any = hmUI): void {
28 | try {
29 | const initXpropsBuf = (props: T_FREE): T_FREE => {
30 | const ret: T_FREE = {}
31 | for (let k of needToFuckProps)
32 | ret[k] = props[k]
33 | return ret
34 | }
35 |
36 | for (let comp of json) {
37 | const rtag = comp.$tag as string
38 | const tag = rtag.toUpperCase()
39 | const needToFuck = needToFuckWidgets.includes(rtag)
40 |
41 | const defaultProps = GUtil.deepCopy(defaultConfig[rtag] || defaultConfig[''])
42 | const widget = ctor.createWidget(
43 | (hmUI.widget as T_JSON)[tag],
44 | defaultProps
45 | )
46 | if (! widget) continue
47 |
48 | const eventsBuf: { [_: string]: Function } = {},
49 | xpropsBuf: T_FREE | null = needToFuck
50 | ? initXpropsBuf(defaultProps) : null
51 | let initEvent = null
52 |
53 | for (let k in comp) {
54 | let v = comp[k], cv: any
55 | const handledFunc = typeof v == 'string'
56 | ? new Function(
57 | '$vepp', '$widget',
58 | `with(this){return(${v})}`
59 | )
60 | : null
61 | const funcCalc = () => {
62 | cv = handledFunc!.call(this.data, this, widget)
63 | }
64 |
65 | if (k.startsWith('@')) {
66 | let rk2 = k.substring(1),
67 | rk = (hmUI.event as T_JSON)[rk2.toUpperCase()]
68 | funcCalc()
69 | if (rk2 == 'vepp_init') {
70 | initEvent = cv
71 | }
72 | else {
73 | if (rk in eventsBuf)
74 | widget.removeEventListener(rk, eventsBuf[rk])
75 | widget.addEventListener(rk, cv)
76 | eventsBuf[rk] = cv
77 | }
78 | }
79 | else if (k.startsWith('$')) {
80 | let rk = k.substring(1)
81 | if (rk == 'children') {
82 | if (v.length && widget.createWidget) {
83 | this.init(v as T_VeppCtorUIOption[], widget)
84 | }
85 | }
86 | }
87 | else {
88 | let rk = (hmUI.prop as T_JSON)[k.toUpperCase()]
89 | const propUpdater = () => {
90 | if (Vepp.restStack) return
91 | funcCalc()
92 |
93 | if (typeof rk == 'number') {
94 | widget.setProperty(rk, cv)
95 | }
96 | else if (needToFuck) {
97 | xpropsBuf![k] = cv
98 | widget.setProperty(hmUI.prop.MORE, xpropsBuf)
99 | if (! needToFuckProps.includes(k))
100 | delete xpropsBuf![k]
101 | }
102 | else {
103 | widget.setProperty(hmUI.prop.MORE, {
104 | [k]: cv
105 | })
106 | }
107 | if (xpropsBuf && k in xpropsBuf)
108 | xpropsBuf[k] = cv
109 | }
110 | if (noTrackingProps.includes(k) || k.endsWith('_func'))
111 | propUpdater()
112 | else
113 | createReactiveContext(propUpdater, this)
114 | }
115 | }
116 |
117 | if (typeof initEvent == 'function')
118 | initEvent(widget)
119 | }
120 | }
121 | catch (ex) {
122 | throw new Error(`Error when initializing Vepp: ` + ex)
123 | }
124 | }
125 | public watch(
126 | dest: string | Function,
127 | handler: Function = typeof dest == 'function' ? dest : () => {}
128 | ): any {
129 | let ret
130 | createReactiveContext(function (this: Vepp) {
131 | if (Vepp.restStack) return
132 | ret = typeof dest == 'string'
133 | ? (handler.call(this.data, this), this.data[dest])
134 | : dest.call(this.data, this)
135 | }, this)
136 | return ret
137 | }
138 | public static rest(): void {
139 | Vepp.restStack ++
140 | }
141 | public static wake(): void {
142 | Vepp.restStack --
143 | }
144 | }
--------------------------------------------------------------------------------
/src/core/polyfills.ts:
--------------------------------------------------------------------------------
1 | const glob: any = globalThis
2 |
3 | if (! glob.Buffer)
4 | glob.Buffer = DeviceRuntimeCore.Buffer
5 | if (! glob.Logger)
6 | glob.Buffer = DeviceRuntimeCore.HmLogger
7 |
8 | if (! glob.setTimeout) {
9 | glob.setTimeout = (func: Function, interval: number = 1) => {
10 | const tmp = timer.createTimer(
11 | interval,
12 | Number.MAX_SAFE_INTEGER,
13 | () => {
14 | glob.clearTimeout(tmp)
15 | func()
16 | },
17 | {}
18 | )
19 | return tmp
20 | }
21 | glob.setInterval = (func: Function, interval: number) => {
22 | return timer.createTimer(
23 | 1,
24 | interval,
25 | () => func(),
26 | {}
27 | )
28 | }
29 | glob.setImmediate = (func: Function) => glob.setTimeout(func)
30 | glob.clearTimeout = glob.clearInterval = glob.clearImmediate = (ref: any) => timer.stopTimer(ref)
31 | }
32 |
33 | const functionCtor: any = (function () {}).constructor
34 | glob.Function = function (...args) {
35 | return new functionCtor(...args)
36 | } as FunctionConstructor
37 |
38 | const consoleTime: { [_: string]: number } = {}
39 | glob.console.time = function (tag: string) {
40 | if (tag in consoleTime)
41 | console.log(`Timer '${tag}' already exists`)
42 | consoleTime[tag] = new Date().valueOf()
43 | }
44 | glob.console.timeEnd = function (tag: string) {
45 | if (! (tag in consoleTime)) {
46 | console.log(`Timer '${tag}' does not exist`)
47 | }
48 | else {
49 | console.log(`${tag}: ${new Date().valueOf() - consoleTime[tag]} ms`)
50 | delete consoleTime[tag]
51 | }
52 | }
--------------------------------------------------------------------------------
/src/core/proxy.ts:
--------------------------------------------------------------------------------
1 | import { GeneralUtil as GUtil, T_JSON } from '../utils/general.js'
2 | import { Vepp } from './main.js'
3 |
4 | class Deps {
5 | private deps: { [_: string]: Set } = {}
6 |
7 | public add(key: string, func: Function) {
8 | if (! (key in this.deps))
9 | this.deps[key] = new Set()
10 | this.deps[key].add(func)
11 | }
12 | public notify(key: string, _this: any): void {
13 | if (key in this.deps) {
14 | for (let v of this.deps[key])
15 | v.call(_this, key)
16 | }
17 | }
18 | }
19 |
20 | let reactiveContext: Function | null = null
21 | export function createReactiveContext(func: (key: string | null) => void, _this: any): void {
22 | let handling = false
23 | const debts: (string | null)[] = []
24 | let handler = (key: string | null) => {
25 | if (handling) {
26 | debts.push(key)
27 | return
28 | }
29 | handling = true
30 |
31 | const oldContext = reactiveContext
32 | reactiveContext = handler
33 | func.call(_this, key)
34 | reactiveContext = oldContext
35 |
36 | if (debts.length > 0) {
37 | let buf = debts[0]
38 | debts.splice(0, 1)
39 | handler(buf)
40 | }
41 | handling = false
42 | }
43 | handler(null)
44 | }
45 |
46 | const proxyUpdateSymbol: Symbol = Symbol('proxyUpdate')
47 | export function createProxy(obj: T_JSON, _this: any, key: string | null = null, deps: Deps | null = null): any {
48 | const rdeps = deps || new Deps()
49 |
50 | for (let k in obj) {
51 | let v = obj[k]
52 | if (GUtil.isPlainObject(v)) {
53 | obj[k] = createProxy(v, _this, key || k, rdeps)
54 | }
55 | }
56 |
57 | const proxy = new Proxy(obj, {
58 | get(t: T_JSON, k: string | symbol): any {
59 | const rk = key || k
60 | if (k == proxyUpdateSymbol)
61 | return key ? () => rdeps.notify(key, _this) : () => {}
62 | if (reactiveContext && typeof rk == 'string')
63 | rdeps.add(rk, reactiveContext)
64 | return t[k]
65 | },
66 | set(t: T_JSON, k: string | symbol, v: any): boolean {
67 | const rk = key || k
68 | if (! (k in t) || t[k] !== v) {
69 | if (GUtil.isPlainObject(v) && typeof rk == 'string')
70 | t[k] = createProxy(v, _this, rk, rdeps)
71 | else
72 | t[k] = v
73 | if (typeof rk == 'string')
74 | rdeps.notify(rk, _this)
75 | }
76 | return true
77 | },
78 | deleteProperty(t: T_JSON, k: string | symbol): boolean {
79 | const rk = key || k
80 | delete t[k]
81 | if (typeof rk == 'string')
82 | rdeps.notify(rk, _this)
83 | return true
84 | },
85 | getOwnPropertyDescriptor(t: T_JSON, k: string | symbol): PropertyDescriptor | undefined {
86 | return Object.getOwnPropertyDescriptor(t, k)
87 | },
88 | ownKeys(t: T_JSON): (string | symbol)[] {
89 | return Reflect.ownKeys(t)
90 | },
91 | has(t: T_JSON, k: string | symbol) {
92 | if (k == proxyUpdateSymbol)
93 | return true
94 | return k in t
95 | }
96 | })
97 |
98 | return proxy
99 | }
100 |
101 | const arrayProto = Array.prototype as any
102 | const oldArraySplice = arrayProto.splice
103 | arrayProto.splice = function (this: any[], ...args: any[]) {
104 | if ((proxyUpdateSymbol as any) in this) {
105 | Vepp.rest()
106 | oldArraySplice.call(this, ...args)
107 | Vepp.wake()
108 | this[proxyUpdateSymbol as any]()
109 | }
110 | else {
111 | oldArraySplice.call(this, ...args)
112 | }
113 | return this
114 | }
115 | arrayProto.has = arrayProto.includes
116 | arrayProto.add = function (this: any[], item: any): any[] {
117 | if (! this.includes(item))
118 | this.push(item)
119 | return this
120 | }
121 | arrayProto.delete = function (this: any[], item: any): boolean {
122 | if ((item = this.indexOf(item)) >= 0)
123 | return ! void this.splice(item, 1)
124 | return false
125 | }
--------------------------------------------------------------------------------
/src/utils/cli.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 |
3 | export class CLIUtil {
4 | public static priorAttrs: string[] = ['ok_text', 'cancel_text']
5 | public static laterAttrs: string[] = ['checked', 'init']
6 |
7 | public static delDir(path: string): void {
8 | let arr = fs.readdirSync(path)
9 | for (let k in arr) {
10 | let v = path + '/' + arr[k]
11 | if (fs.statSync(v).isDirectory()) {
12 | this.delDir(v)
13 | }
14 | else {
15 | fs.unlinkSync(v)
16 | }
17 | }
18 | fs.rmdirSync(path)
19 | }
20 | }
--------------------------------------------------------------------------------
/src/utils/general.ts:
--------------------------------------------------------------------------------
1 | export type T_JSON = { [_: string | symbol]: any }
2 | export type T_FREE = { [_: string]: any }
3 |
4 | export class GeneralUtil {
5 | public static ext: string = '.vepp'
6 | public static selfClosingTags: string[] = []
7 | public static textOnlyTags: string[] = ['script']
8 | public static tmpPrefix: string = '$$'
9 | private static voidChar: string = '\uffff'
10 |
11 | public static randomText(len: number = 6): string {
12 | let sets = 'abcdefghijklmnopqrstuvwxyz0123456789',
13 | res = ''
14 | for (let k = 0; k < len; k ++) {
15 | let c = sets[Math.floor(Math.random() * sets.length)]
16 | res += c
17 | }
18 | return res
19 | }
20 | public static isPlainObject(obj: any): boolean {
21 | return obj && (obj.constructor === Object || Array.isArray(obj))
22 | }
23 | private static deepCopyRaw(obj: any, dest?: any): any {
24 | if (! this.isPlainObject(obj))
25 | return obj
26 | if (Array.isArray(obj)) {
27 | dest = dest || []
28 | if (Array.isArray(dest)) {
29 | for (let v of obj) {
30 | dest.push(this.deepCopyRaw(v))
31 | }
32 | }
33 | else {
34 | let k = 0
35 | for (let v of obj) {
36 | dest[k] = this.deepCopyRaw(v)
37 | k ++
38 | }
39 | }
40 | }
41 | else {
42 | dest = dest || {}
43 | for (let k in obj) {
44 | dest[k] = this.deepCopyRaw(obj[k])
45 | }
46 | }
47 | return dest
48 | }
49 | public static deepCopy(...objs: any[]): any {
50 | if (objs.length == 1)
51 | return this.deepCopyRaw(objs[0])
52 | const ret = {}
53 | for (let v of objs) {
54 | this.deepCopyRaw(v, ret)
55 | }
56 | return ret
57 | }
58 | private static isScriptQuote(text: string, k: number): boolean {
59 | return (text[k] == `'` || text[k] == `"` || text[k] == '`') && text[k - 1] != '\\'
60 | }
61 | public static scriptReg(script: string, regexp: RegExp, replace?: string): string | RegExpExecArray[] {
62 | let quoteStarter = null,
63 | addup = ''
64 |
65 | let reps = [], repsid = -1
66 | for (let k = 0; k < script.length; k ++) {
67 | let v = script[k]
68 | if (! quoteStarter) {
69 | if (! this.isScriptQuote(script, k)) {
70 | addup += v
71 | }
72 | else {
73 | quoteStarter = v
74 | repsid ++
75 | reps[repsid] = ''
76 | addup += quoteStarter + this.voidChar + repsid + this.voidChar
77 | }
78 | }
79 | else {
80 | if (this.isScriptQuote(script, k) && v == quoteStarter) {
81 | addup += v
82 | quoteStarter = null
83 | }
84 | else {
85 | reps[repsid] += v
86 | }
87 | }
88 | }
89 |
90 | if (replace) {
91 | addup = addup.replace(regexp, replace)
92 | for (let k in reps) {
93 | let v = reps[k]
94 | addup = addup.replace(this.voidChar + k + this.voidChar, v)
95 | }
96 | return addup
97 | }
98 | else {
99 | let res = [], v
100 | while (v = regexp.exec(addup)) {
101 | res.push(v)
102 | if (! /\/.*\/[a-z]*g[a-z]*$/i.test(regexp.toString()))
103 | break
104 | }
105 | return res
106 | }
107 | }
108 | public static diff(
109 | buf: Set, newest: Set,
110 | addsCallback: (key: any) => void, delsCallback: (key: any) => void
111 | ): void {
112 | const adds: Set<(key: any) => void> = new Set,
113 | dels: Set<(key: any) => void> = new Set
114 | for (let v of buf) {
115 | if (! newest.has(v)) {
116 | dels.add(v)
117 | buf.delete(v)
118 | }
119 | }
120 | for (let v of newest) {
121 | if (! buf.has(v)) {
122 | adds.add(v)
123 | buf.add(v)
124 | }
125 | }
126 | for (let v of dels)
127 | delsCallback(v)
128 | for (let v of adds)
129 | addsCallback(v)
130 | }
131 | public static delay(func: Function): Function {
132 | let first = true
133 | return (...args: any[]) => {
134 | if (first) {
135 | first = false
136 | return
137 | }
138 | return func(...args)
139 | }
140 | }
141 | }
--------------------------------------------------------------------------------
/src/utils/vml.ts:
--------------------------------------------------------------------------------
1 | import { GeneralUtil as GUtil } from './general.js'
2 |
3 | export enum VMLNodeType {
4 | root, common, text, comment
5 | }
6 |
7 | export type VMLNodeAttrs = { [_: string]: string }
8 |
9 | export class VMLNode {
10 | public type: VMLNodeType = VMLNodeType.common
11 | public tag: string | null = null
12 | public attrs: VMLNodeAttrs = {}
13 | public text: string | null = null
14 | public children: VMLNode[] = []
15 | }
16 |
17 | enum VMLParserStatus {
18 | common, quote
19 | }
20 |
21 | export class VMLParser {
22 | private static isValidName(text: string): boolean {
23 | return /^[a-zA-Z\-_\@\:][a-zA-Z0-9\-_\@\:]*$/.test(text)
24 | }
25 | private static isQuote(v: string): boolean {
26 | return v == `'` || v == `"`
27 | }
28 | private static isVoidChar(char: string): boolean {
29 | return ! char || char == ' ' || char == '\t' || char == '\n' || char == '\r'
30 | }
31 | private static readPath(res: VMLNode, path: number[]): VMLNode {
32 | if (! path) return res
33 | for (let v of path) {
34 | res = res.children[v]
35 | }
36 | return res
37 | }
38 | private static pushPath(res: VMLNode, path: number[], value: VMLNode): void {
39 | if (! path) return
40 | let k = 0
41 | for (let v of path) {
42 | if (path.length - 1 > k)
43 | res = res.children[v]
44 | k ++
45 | }
46 | res.children[path[path.length - 1]] = value
47 | }
48 | private static nextSibilingPath(path: number[]): number[] {
49 | let tmp = GUtil.deepCopy(path)
50 | tmp[path.length ? path.length - 1 : 0] = (path[path.length - 1] || 0) + 1
51 | return tmp
52 | }
53 | private static nextChildPath(res: VMLNode, path: number[]): number[] {
54 | let tmp = GUtil.deepCopy(path)
55 | tmp.push(this.readPath(res, path).children.length)
56 | return tmp
57 | }
58 | private static nextPath(res: VMLNode, openedPaths: number[][], oldPath: number[]): number[] {
59 | if (openedPaths.indexOf(oldPath) < 0)
60 | return this.nextSibilingPath(oldPath)
61 | else
62 | return this.nextChildPath(res, oldPath)
63 | }
64 | private static formatName(name: string): string {
65 | return name.toLowerCase().replace(/\-/g, '_')
66 | }
67 | public static read(src: string): VMLNode | string {
68 | let arr = (src + ' ').split('')
69 |
70 | const res = new VMLNode(), openedPaths = []
71 | res.type = VMLNodeType.root
72 | let currentPath = [-1], currentVNode = new VMLNode(), status = VMLParserStatus.common
73 | let quoteStarter = null, quoteText = '', quoteAvailable = false
74 | let tagStatus = false, isEndTag = false, isSelfClosingTag = false, currentTag = '', isTagRead = false
75 | let currentAttrsRaw = '', currentAttrs: VMLNodeAttrs = {}
76 | let isComment = false, commentText = ''
77 | let isTextOnly = false
78 |
79 | for (let k = 0; k < arr.length; k ++) {
80 | let v = arr[k]
81 |
82 | if (status == VMLParserStatus.common) {
83 | if (! isComment) {
84 | if (tagStatus) {
85 | if (! isTagRead && this.isValidName(v)) {
86 | currentTag += v
87 | }
88 | else if (! isTagRead && ! this.isValidName(v) && currentTag) {
89 | isTagRead = true
90 | currentTag = this.formatName(currentTag)
91 | currentVNode.tag = currentTag
92 |
93 | isSelfClosingTag = GUtil.selfClosingTags.indexOf(currentTag) >= 0
94 | isTextOnly = GUtil.textOnlyTags.indexOf(currentTag) >= 0
95 | if (isSelfClosingTag)
96 | openedPaths.splice(openedPaths.indexOf(currentPath), 1)
97 | }
98 | else if (isTagRead
99 | && (this.isValidName(v) || this.isQuote(v))) {
100 | if (this.isQuote(v)) {
101 | quoteStarter = v
102 | status = VMLParserStatus.quote
103 | }
104 | else if (v != '=') {
105 | currentAttrsRaw += v
106 | }
107 | }
108 | if (! isEndTag && v == '>' && quoteAvailable) {
109 | currentAttrs[this.formatName(currentAttrsRaw)] = quoteText || ''
110 | quoteText = currentAttrsRaw = ''
111 | quoteAvailable = false
112 | }
113 | if (! isEndTag && (this.isVoidChar(v) || v == '>')
114 | && quoteAvailable) {
115 | currentAttrs[this.formatName(currentAttrsRaw)] = quoteText || ''
116 | quoteText = currentAttrsRaw = ''
117 | quoteAvailable = false
118 | }
119 | else if (! isEndTag && (this.isVoidChar(v) || v == '>')
120 | && currentAttrsRaw) {
121 | currentAttrs[this.formatName(currentAttrsRaw)] = ''
122 | quoteText = currentAttrsRaw = ''
123 | quoteAvailable = false
124 | }
125 | }
126 | let path = openedPaths[openedPaths.length - 1],
127 | tag = this.readPath(res, path).tag || '',
128 | isValidEndTag = '' + tag == this.formatName(src.substring(k, tag.length + 2 + k))
129 | if ((! isTextOnly && v == '<') || (isTextOnly && isValidEndTag)) {
130 | if (! isTextOnly && src.substring(k, ``.length + k) == `-->`) {
183 | currentPath = this.nextPath(res, openedPaths, currentPath)
184 | currentVNode = new VMLNode()
185 | currentVNode.type = VMLNodeType.comment
186 | currentVNode.text = commentText
187 | this.pushPath(res, currentPath, currentVNode)
188 | isComment = false
189 | commentText = ''
190 | k += `-->`.length - 1
191 | }
192 | else {
193 | commentText += v
194 | }
195 | }
196 | else if (status == VMLParserStatus.quote) {
197 | quoteAvailable = true
198 | if (v == quoteStarter) {
199 | quoteStarter = null
200 | status = VMLParserStatus.common
201 | continue
202 | }
203 | quoteText += v
204 | }
205 | }
206 |
207 | if (openedPaths.length || status != VMLParserStatus.common)
208 | return `Node tags tree is not valid, maybe there are some unclosed tags.`
209 |
210 | res.children.pop()
211 | return res
212 | }
213 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "es2022", /* Specify what module code is generated. */
29 | "rootDir": "./src/", /* Specify the root folder within your source files. */
30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | // "resolveJsonModule": true, /* Enable importing .json files. */
39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
40 |
41 | /* JavaScript Support */
42 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45 |
46 | /* Emit */
47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
52 | "outDir": "./dist/", /* Specify an output folder for all emitted files. */
53 | // "removeComments": true, /* Disable emitting comments. */
54 | // "noEmit": true, /* Disable emitting files from a compilation. */
55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
63 | // "newLine": "crlf", /* Set the newline character for emitting files. */
64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
70 |
71 | /* Interop Constraints */
72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
77 |
78 | /* Type Checking */
79 | "strict": true, /* Enable all strict type-checking options. */
80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
98 |
99 | /* Completeness */
100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
102 | }
103 | }
104 |
--------------------------------------------------------------------------------