├── .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 = '') { 160 | if (! isEndTag && Object.keys(currentAttrs).length) 161 | currentVNode.attrs = currentAttrs 162 | 163 | tagStatus = false 164 | isEndTag = false 165 | isSelfClosingTag = false 166 | } 167 | else if (! tagStatus) { 168 | let lastNode = this.readPath(res, currentPath) || {} 169 | if (! lastNode.tag && lastNode.type == VMLNodeType.text) { 170 | currentVNode.text += v 171 | this.pushPath(res, currentPath, currentVNode) 172 | } 173 | else { 174 | currentPath = this.nextPath(res, openedPaths, currentPath) 175 | currentVNode = new VMLNode() 176 | currentVNode.type = VMLNodeType.text 177 | currentVNode.text = v 178 | this.pushPath(res, currentPath, currentVNode) 179 | } 180 | } 181 | } 182 | else if (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 | --------------------------------------------------------------------------------