├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── api-extractor.json ├── gulpfile.ts ├── jest.config.js ├── package.json ├── pnpm-lock.yaml ├── rollup.config.ts ├── src ├── EnumData.ts ├── getStringByteLength.ts ├── index.ts ├── toUnicode.ts └── type.ts ├── test ├── EnumData.test.js ├── getStringByteLength.test.ts ├── toUnicode.test.ts └── type.test.ts ├── tsconfig.eslint.json ├── tsconfig.json └── typings └── index.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "@babel/plugin-transform-runtime" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ 4 | public/ 5 | config/ 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const eslintrc = { 2 | extends: [require.resolve('@yueqing/lint/lib/ts-eslint')], 3 | parserOptions: { 4 | project: './tsconfig.eslint.json', 5 | }, 6 | rules: { 7 | // custom rules 8 | }, 9 | } 10 | 11 | module.exports = eslintrc 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ master, next ] 6 | pull_request: 7 | branches: [ master, next ] 8 | 9 | jobs: 10 | setup: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [14] 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Cache .pnpm-store 25 | uses: actions/cache@v1 26 | with: 27 | path: ~/.pnpm-store 28 | key: ${{ runner.os }}-node${{ matrix.node-version }}-${{ hashFiles('**/pnpm-lock.yaml') }} 29 | 30 | - name: Install pnpm 31 | run: curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6 32 | 33 | - name: Install Dependencies 34 | run: pnpm install 35 | 36 | - name: Run Test 37 | run: pnpm test 38 | 39 | - name: Upload coverage to Codecov 40 | uses: codecov/codecov-action@v2 41 | with: 42 | files: coverage/coverage-final.json 43 | flags: unittests,${{ matrix.os }} 44 | name: fly-helper 45 | fail_ci_if_error: true 46 | verbose: true 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript cache 45 | *.tsbuildinfo 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Microbundle cache 54 | .rpt2_cache/ 55 | .rts2_cache_cjs/ 56 | .rts2_cache_es/ 57 | .rts2_cache_umd/ 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # Next.js build output 76 | .next 77 | 78 | # Nuxt.js build / generate output 79 | .nuxt 80 | dist 81 | 82 | # Gatsby files 83 | .cache/ 84 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 85 | # https://nextjs.org/blog/next-9-1#public-directory-support 86 | # public 87 | 88 | # vuepress build output 89 | .vuepress/dist 90 | 91 | # Serverless directories 92 | .serverless/ 93 | 94 | # FuseBox cache 95 | .fusebox/ 96 | 97 | # DynamoDB Local files 98 | .dynamodb/ 99 | 100 | # TernJS port file 101 | .tern-port 102 | 103 | lib 104 | temp 105 | *.bak 106 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged && npx jest -u 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@yueqing/lint'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 工具库更新日志 2 | 3 | ## [1.2.1](https://github.com/simonwong/fly-helper/compare/v1.2.0...v1.2.1) (2022-04-24) 4 | 5 | 6 | 7 | # [1.2.0](https://github.com/simonwong/fly-helper/compare/v1.1.0...v1.2.0) (2022-02-14) 8 | 9 | 10 | ### Features 11 | 12 | * **EnumData:** 新增 EnumData 方法,来处理枚举数据 ([8eb3c77](https://github.com/simonwong/fly-helper/commit/8eb3c77b0b5f6d3699c79815d58cff0b53142f0d)) 13 | * **toUnicode:** add toUnicode methods ([e198e4e](https://github.com/simonwong/fly-helper/commit/e198e4ea746c82fcada7faa31ab8ec9428f04a5e)) 14 | 15 | 16 | 17 | # [1.1.0](https://github.com/simonwong/fly-helper/compare/v1.0.3...v1.1.0) (2021-01-18) 18 | 19 | 20 | 21 | ## [1.0.3](https://github.com/simonwong/fly-helper/compare/v1.0.2...v1.0.3) (2020-12-21) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * **workflows.test:** remove workflows test hit ([90eabfd](https://github.com/simonwong/fly-helper/commit/90eabfd0b49a0340cd0ef76a9e3bd05cd9cd65a0)) 27 | 28 | 29 | ### Features 30 | 31 | * upgrade packages, remove lodash in dependencies ([8c59fd9](https://github.com/simonwong/fly-helper/commit/8c59fd9febde20ec0de3af142ef1550b95b8c1da)) 32 | 33 | 34 | 35 | ## [1.0.2](https://github.com/simonwong/fly-helper/compare/v1.0.1...v1.0.2) (2020-09-29) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * **typing:** remove ignore typings, add typings ([81a41be](https://github.com/simonwong/fly-helper/commit/81a41beee9bec7ae1573657a3bb6dc79a5b21587)) 41 | 42 | 43 | 44 | ## [1.0.1](https://github.com/simonwong/fly-helper/compare/f0a4fefe507f7d6c8717c05d2f0c638407ca87a3...v1.0.1) (2020-02-05) 45 | 46 | 47 | ### Features 48 | 49 | * **type:** 新增 isNumber 、 isString 类型判断方法 ([f0a4fef](https://github.com/simonwong/fly-helper/commit/f0a4fefe507f7d6c8717c05d2f0c638407ca87a3)) 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 王思杰 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fly-helper 2 | 3 | It's a Tool library, method collection. 4 | 5 | ![test](https://github.com/simonwong/fly-helper/workflows/test/badge.svg) 6 | [![codecov](https://codecov.io/gh/simonwong/fly-helper/branch/master/graph/badge.svg)](https://codecov.io/gh/simonwong/fly-helper) 7 | [![Build Status](https://travis-ci.com/simonwong/fly-helper.svg?branch=master)](https://travis-ci.com/simonwong/fly-helper) 8 | 9 | ## Usage 10 | 11 | - install 12 | 13 | ```shell 14 | npm install fly-helper --save 15 | 16 | # or 17 | 18 | yarn add fly-helper 19 | ``` 20 | 21 | - example 22 | 23 | ```javascript 24 | import { isNumber } from 'fly-helper' 25 | 26 | isNumber() 27 | ``` 28 | 29 | 30 | ## License 31 | 32 | The scripts and documentation in this project are released under the [MIT License](https://github.com/simonwong/fly-helper/blob/master/LICENSE) 33 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "mainEntryPointFilePath": "./lib/index.d.ts", 4 | "bundledPackages": [ ], 5 | "apiReport": { 6 | "enabled": false 7 | }, 8 | "docModel": { 9 | "enabled": true 10 | }, 11 | "dtsRollup": { 12 | "enabled": true, 13 | "untrimmedFilePath": "./lib/index.d.ts" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gulpfile.ts: -------------------------------------------------------------------------------- 1 | import { series } from 'gulp' 2 | import path from 'path' 3 | import fse from 'fs-extra' 4 | import chalk from 'chalk' 5 | import { rollup } from 'rollup' 6 | import { 7 | Extractor, 8 | ExtractorConfig, 9 | ExtractorResult, 10 | } from '@microsoft/api-extractor' 11 | import conventionalChangelog from 'conventional-changelog' 12 | import rollupConfig from './rollup.config' 13 | 14 | interface TaskFunc { 15 | (cb: Function): void 16 | } 17 | 18 | const log = { 19 | progress: (text: string) => { 20 | console.log(chalk.green(text)) 21 | }, 22 | error: (text: string) => { 23 | console.log(chalk.red(text)) 24 | }, 25 | } 26 | 27 | const paths = { 28 | root: path.join(__dirname, '/'), 29 | lib: path.join(__dirname, '/lib'), 30 | } 31 | 32 | // 删除 lib 文件 33 | const clearLibFile: TaskFunc = async cb => { 34 | fse.removeSync(paths.lib) 35 | log.progress('Deleted lib file') 36 | cb() 37 | } 38 | 39 | // rollup 打包 40 | const buildByRollup: TaskFunc = async cb => { 41 | const inputOptions = { 42 | input: rollupConfig.input, 43 | external: rollupConfig.external, 44 | plugins: rollupConfig.plugins, 45 | } 46 | const outOptions = rollupConfig.output 47 | let bundle 48 | 49 | try { 50 | bundle = await rollup(inputOptions) 51 | 52 | // 写入需要遍历输出配置 53 | if (Array.isArray(outOptions)) { 54 | outOptions.forEach(async outOption => { 55 | await bundle.write(outOption) 56 | }) 57 | } 58 | } catch (e) { 59 | if (e instanceof Error) { 60 | log.error(e.message) 61 | } 62 | } 63 | 64 | if (bundle !== null) { 65 | // closes the bundle 66 | await bundle.close() 67 | cb() 68 | log.progress('Rollup built successfully') 69 | } 70 | } 71 | 72 | // api-extractor 整理 .d.ts 文件 73 | const apiExtractorGenerate: TaskFunc = async cb => { 74 | const apiExtractorJsonPath: string = path.join( 75 | __dirname, 76 | './api-extractor.json', 77 | ) 78 | // 判断是否存在 index.d.ts 文件,这里必须先等会儿,rollup 的 bundle write 是结束了, 79 | // 但是 ts 的 typings 编译还没结束 80 | const isExist = await new Promise(resolve => { 81 | let intervalTimes = 5 82 | let exitFlag = false 83 | const timer = setInterval(async () => { 84 | exitFlag = await fse.pathExists('./lib/index.d.ts') 85 | intervalTimes-- 86 | if (exitFlag || intervalTimes === 0) { 87 | clearInterval(timer) 88 | resolve(exitFlag) 89 | } 90 | }, 100) 91 | }) 92 | 93 | if (!isExist) { 94 | log.error('API Extractor not find index.d.ts') 95 | return 96 | } 97 | // 加载并解析 api-extractor.json 文件 98 | const extractorConfig: ExtractorConfig = 99 | ExtractorConfig.loadFileAndPrepare(apiExtractorJsonPath) 100 | 101 | // 调用 API 102 | const extractorResult: ExtractorResult = Extractor.invoke(extractorConfig, { 103 | localBuild: true, 104 | // 在输出中显示信息 105 | showVerboseMessages: true, 106 | }) 107 | 108 | if (extractorResult.succeeded) { 109 | // 删除多余的 .d.ts 文件 110 | const libFiles: string[] = await fse.readdir(paths.lib) 111 | libFiles.forEach(async file => { 112 | if (file.endsWith('.d.ts') && !file.includes('index')) { 113 | await fse.remove(path.join(paths.lib, file)) 114 | } 115 | }) 116 | log.progress('API Extractor completed successfully') 117 | cb() 118 | } else { 119 | log.error( 120 | `API Extractor completed with ${extractorResult.errorCount} errors` + 121 | ` and ${extractorResult.warningCount} warnings`, 122 | ) 123 | } 124 | } 125 | 126 | const complete: TaskFunc = cb => { 127 | log.progress('---- end ----') 128 | cb() 129 | } 130 | 131 | // 构建过程 132 | // 1. 删除 lib 文件夹 133 | // 2. rollup 打包 134 | // 3. api-extractor 生成统一的声明文件, 删除多余的声明文件 135 | // 4. 完成 136 | export const build = series( 137 | clearLibFile, 138 | buildByRollup, 139 | apiExtractorGenerate, 140 | complete, 141 | ) 142 | 143 | // 自定义生成 changelog 144 | export const changelog: TaskFunc = async cb => { 145 | const changelogPath: string = path.join(paths.root, 'CHANGELOG.md') 146 | // 对命令 conventional-changelog -p angular -i CHANGELOG.md -w -r 0 147 | const changelogPipe = await conventionalChangelog({ 148 | preset: 'angular', 149 | releaseCount: 0, 150 | }) 151 | changelogPipe.setEncoding('utf8') 152 | 153 | const resultArray = ['# 工具库更新日志\n\n'] 154 | changelogPipe.on('data', chunk => { 155 | // 原来的 commits 路径是进入提交列表 156 | chunk = chunk.replace(/\/commits\//g, '/commit/') 157 | resultArray.push(chunk) 158 | }) 159 | changelogPipe.on('end', async () => { 160 | await fse.createWriteStream(changelogPath).write(resultArray.join('')) 161 | cb() 162 | }) 163 | } 164 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | transform: { 4 | '^.+\\.[t|j]sx?$': [ 5 | 'babel-jest', 6 | { 7 | presets: ['@babel/preset-env', '@babel/preset-typescript'], 8 | }, 9 | ], 10 | }, 11 | testEnvironment: 'jsdom', 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fly-helper", 3 | "version": "1.2.1", 4 | "description": "工具库", 5 | "main": "lib/index.js", 6 | "module": "lib/index.esm.js", 7 | "typings": "lib/index.d.ts", 8 | "files": [ 9 | "lib", 10 | "LICENSE", 11 | "CHANGELOG.md", 12 | "README.md" 13 | ], 14 | "sideEffects": "false", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/simonwong/fly-helper.git" 18 | }, 19 | "author": "Simon", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/simonwong/fly-helper/issues" 23 | }, 24 | "publishConfig": { 25 | "access": "public", 26 | "registry": "https://registry.npmjs.org/" 27 | }, 28 | "homepage": "https://github.com/simonwong/fly-helper#readme", 29 | "scripts": { 30 | "build": "gulp build", 31 | "api": "api-extractor run", 32 | "test": "jest --coverage --verbose -u", 33 | "test:watch": "jest --coverage --verbose --watch", 34 | "lint": "eslint --ext .js,.ts --format=pretty ./src", 35 | "lint:fix": "eslint --fix --ext .js,.ts --format=pretty ./src", 36 | "changelog": "gulp changelog", 37 | "version": "yarn changelog && git add CHANGELOG.md", 38 | "release": "yarn test && yarn build && np --no-cleanup --yolo --any-branch", 39 | "prepare": "husky install" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.17.9", 43 | "@babel/plugin-transform-runtime": "^7.17.0", 44 | "@babel/preset-env": "^7.16.11", 45 | "@babel/preset-typescript": "^7.16.7", 46 | "@microsoft/api-extractor": "^7.23.0", 47 | "@rollup/plugin-babel": "^5.3.1", 48 | "@rollup/plugin-commonjs": "^22.0.0", 49 | "@rollup/plugin-json": "^4.1.0", 50 | "@rollup/plugin-node-resolve": "^13.2.1", 51 | "@rollup/plugin-typescript": "^8.3.2", 52 | "@types/fs-extra": "^9.0.13", 53 | "@types/gulp": "^4.0.9", 54 | "@types/jest": "^27.4.1", 55 | "@types/node": "^17.0.25", 56 | "@yueqing/lint": "^2.2.0", 57 | "chalk": "^4.1.2", 58 | "conventional-changelog": "^3.1.25", 59 | "fs-extra": "^10.1.0", 60 | "gulp": "^4.0.2", 61 | "husky": "^7.0.4", 62 | "jest": "^27.5.1", 63 | "lint-staged": "^12.4.0", 64 | "np": "^7.6.1", 65 | "rollup": "^2.70.2", 66 | "ts-jest": "^27.1.4", 67 | "ts-node": "^10.7.0", 68 | "tslib": "^2.4.0", 69 | "typescript": "^4.6.3" 70 | }, 71 | "dependencies": { 72 | "@babel/runtime": "^7.17.9" 73 | }, 74 | "lint-staged": { 75 | "*.{ts,js}": [ 76 | "eslint --fix --format=pretty" 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import path from 'path' 3 | import { RollupOptions } from 'rollup' 4 | import typescript from '@rollup/plugin-typescript' 5 | import babel from '@rollup/plugin-babel' 6 | import resolve from '@rollup/plugin-node-resolve' 7 | import commonjs from '@rollup/plugin-commonjs' 8 | import { DEFAULT_EXTENSIONS } from '@babel/core' 9 | 10 | import pkg from './package.json' 11 | 12 | const paths = { 13 | input: path.join(__dirname, '/src/index.ts'), 14 | output: path.join(__dirname, '/lib'), 15 | } 16 | 17 | // rollup 配置项 18 | const rollupConfig: RollupOptions = { 19 | input: paths.input, 20 | output: [ 21 | // 输出 commonjs 规范的代码 22 | { 23 | file: path.join(paths.output, 'index.js'), 24 | format: 'cjs', 25 | name: pkg.name, 26 | }, 27 | // 输出 es 规范的代码 28 | { 29 | file: path.join(paths.output, 'index.esm.js'), 30 | format: 'es', 31 | name: pkg.name, 32 | }, 33 | ], 34 | external: [ 35 | // ...Object.keys(pkg.dependencies), 36 | /@babel\/runtime/, 37 | ], // 指出应将哪些模块视为外部模块,如 Peer dependencies 中的依赖 38 | // plugins 需要注意引用顺序 39 | plugins: [ 40 | // ts 的功能只在于编译出声明文件,所以 target 为 ESNext,编译交给 babel 来做 41 | typescript({ 42 | tsconfig: './tsconfig.json', 43 | }), 44 | // 配合 commnjs 解析第三方模块 45 | resolve(), 46 | 47 | // 使得 rollup 支持 commonjs 规范,识别 commonjs 规范的依赖 48 | commonjs(), 49 | 50 | babel({ 51 | babelHelpers: 'runtime', 52 | // 只转换源代码,不运行外部依赖 53 | exclude: 'node_modules/**', 54 | // babel 默认不支持 ts 需要手动添加 55 | extensions: [...DEFAULT_EXTENSIONS, '.ts'], 56 | }), 57 | ], 58 | } 59 | 60 | export default rollupConfig 61 | -------------------------------------------------------------------------------- /src/EnumData.ts: -------------------------------------------------------------------------------- 1 | type EnumDataItem = readonly [string, number | string, string] 2 | /** @beta */ 3 | export type EnumDataList = readonly EnumDataItem[] 4 | type EnumDataRecord = Record 5 | 6 | type TransformArrayToObject< 7 | Tuple extends EnumDataList, 8 | Result extends EnumDataRecord = {}, 9 | > = Tuple extends [] 10 | ? Result // last call 11 | : Tuple extends readonly [infer Head, ...infer Tail] 12 | ? Head extends readonly [infer Key, infer Value, infer Text] 13 | ? Tail extends EnumDataList 14 | ? Value extends PropertyKey 15 | ? Key extends PropertyKey 16 | ? TransformArrayToObject< 17 | Tail, 18 | Result & Record & Record 19 | > // recursive call 20 | : Result 21 | : Result 22 | : Result 23 | : Result 24 | : Result 25 | 26 | type ReadonlyMergedRecord = T extends EnumDataRecord 27 | ? { 28 | readonly [K in keyof T]: T[K] 29 | } 30 | : never 31 | 32 | /** @beta */ 33 | export type EnumDataResult = ReadonlyMergedRecord< 34 | TransformArrayToObject 35 | > & 36 | Array 37 | 38 | /** 39 | * 常量枚举类,传入多组 [key, value, text] 数据 40 | * 就可以通过 value 获得 text,通过 key 获得 value 41 | * 无法通过 index 来拿 某一组数据,可以使用迭代器,length 等 42 | * 43 | * @param data - 元数据 44 | * @returns data 45 | * 46 | * @example 47 | * ```ts 48 | * const YES_OR_NO = EnumData([ 49 | * ['YES', 1, '是'], 50 | * ['NO', 0, '否'], 51 | * ]) 52 | * console.log(YES_OR_NO.YES === 1) // true 53 | * console.log(YES_OR_NO['1']) // 是 54 | * console.log('YES' in YES_OR_NO) // false 55 | * console.log('forEach' in YES_OR_NO) // true 56 | * YES_OR_NO.map([key, value, text] => ({ 57 | * key, 58 | * value, 59 | * text, 60 | * })) 61 | * ``` 62 | * 63 | * @beta 64 | */ 65 | export function EnumData(data: T) { 66 | const keyMap = {} 67 | const valueMap = {} 68 | data.forEach(([key, value, text]) => { 69 | keyMap[key] = value 70 | valueMap[value] = text 71 | }) 72 | 73 | const ans = new Proxy(data, { 74 | get(target, propKey) { 75 | if (keyMap.hasOwnProperty(propKey)) { 76 | return keyMap[propKey] 77 | } 78 | if (valueMap.hasOwnProperty(propKey)) { 79 | return valueMap[propKey] 80 | } 81 | // 除了可以使用 Array 的方法外,也应该允许使用 Object 上的方法 82 | // 用 in 可以获取到继承对象的属性,而 hasOwnProperty 不能 83 | if (propKey in Array.prototype) { 84 | if (typeof Array.prototype[propKey] === 'function') { 85 | return Array.prototype[propKey].bind(target) 86 | } 87 | return target[propKey] 88 | } 89 | return undefined 90 | }, 91 | set() { 92 | throw TypeError(`Don't allow assignment to constant variable`) 93 | }, 94 | }) 95 | return ans as unknown as EnumDataResult 96 | } 97 | -------------------------------------------------------------------------------- /src/getStringByteLength.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取字符串的 UTF-8 子节长度 3 | * 4 | * @param str - 字符串 5 | * @param index - unicode 的下标 6 | * @returns 字节长度 7 | * 8 | * @example 9 | * ```ts 10 | * getStringByteLength(‘ABC’) // 3 11 | * getStringByteLength(‘哈喽’) // 6 12 | * ``` 13 | * @see [Stack Overflow: String length in bytes in JavaScript](https://stackoverflow.com/questions/5515869/string-length-in-bytes-in-javascript) 14 | * @beta 15 | */ 16 | export const getStringByteLength = (str: string) => 17 | new TextEncoder().encode(str).length 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './type' 2 | export * from './EnumData' 3 | export * from './toUnicode' 4 | -------------------------------------------------------------------------------- /src/toUnicode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取字符串指定下标的 unicode 3 | * 4 | * @param str - 字符串 5 | * @param index - unicode 的下标 6 | * @returns data 7 | * 8 | * @example 9 | * ```ts 10 | * unicodeAt('ABC', 1) // -> '\\u0042' 11 | * ``` 12 | * 13 | * @beta 14 | */ 15 | export function toUnicodeAt(str: string, index: number = 0) { 16 | let code = str.charCodeAt(index).toString(16).toUpperCase() 17 | while (code.length < 4) { 18 | code = `0${code}` 19 | } 20 | return `\\u${code}` 21 | } 22 | 23 | /** 24 | * 获取字符串的 unicode 25 | * 26 | * @param str - 字符串 27 | * @returns data 28 | * 29 | * @example 30 | * ```ts 31 | * toUnicode('ABC', 1) // -> '\\u0041\\u0042\\u0043' 32 | * ``` 33 | * 34 | * @beta 35 | */ 36 | export function toUnicode(str: string) { 37 | if (!str) { 38 | return '' 39 | } 40 | 41 | return Array.prototype.reduce.call( 42 | str, 43 | (pre, cur, index) => `${pre}${toUnicodeAt(str, index)}`, 44 | '', 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/type.ts: -------------------------------------------------------------------------------- 1 | const { toString } = Object.prototype 2 | 3 | /** 4 | * 获取值的类型标签 5 | * @param value - 任意值 6 | * @returns [object Xxxx] 7 | * 8 | * @public 9 | */ 10 | export function getTag(value: any): string { 11 | if (value == null) { 12 | return value === undefined ? '[object Undefined]' : '[object Null]' 13 | } 14 | return toString.call(value) 15 | } 16 | 17 | /** 18 | * 判断是否是数值类型 19 | * @param value - 任意值 20 | * @returns true / false 21 | * @example 22 | * ```ts 23 | * isNumber(2) // => true 24 | * ``` 25 | * 26 | * @public 27 | */ 28 | export function isNumber(value: any): boolean { 29 | return getTag(value) === '[object Number]' 30 | } 31 | 32 | /** 33 | * 判断是否是字符串类型 34 | * @param value - 任意值 35 | * @returns true / false 36 | * @example 37 | * ```ts 38 | * isString('abc') // => true 39 | * ``` 40 | * 41 | * @public 42 | */ 43 | export function isString(value: any): boolean { 44 | return getTag(value) === '[object String]' 45 | } 46 | -------------------------------------------------------------------------------- /test/EnumData.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { EnumData } from '../src' 3 | 4 | describe('EnumData:', () => { 5 | /** 6 | * EnumData base function 7 | */ 8 | describe('EnumData base function', () => { 9 | const COLOR_DATA = EnumData([ 10 | ['RED', 1, '红色'], 11 | ['BLUE', 2, '蓝色'], 12 | ['GREEN', 3, '绿色'], 13 | ['YELLOW', 4, '黄色'], 14 | ]) 15 | 16 | test(' match color key / value ', () => { 17 | assert.strictEqual(COLOR_DATA.RED, 1) 18 | assert.strictEqual(COLOR_DATA.YELLOW, 4) 19 | assert.strictEqual(COLOR_DATA['2'], '蓝色') 20 | assert.strictEqual(COLOR_DATA['3'], '绿色') 21 | }) 22 | 23 | test(' match color undefined key / value ', () => { 24 | assert.strictEqual(COLOR_DATA.BLACK, undefined) 25 | assert.strictEqual(COLOR_DATA['红色'], undefined) 26 | assert.strictEqual(COLOR_DATA['5'], undefined) 27 | }) 28 | }) 29 | 30 | describe('EnumData 0 1', () => { 31 | const ENABLE_DISABLE = EnumData([ 32 | ['ENABLE', 1, '启用'], 33 | ['DISABLE', 0, '停用'], 34 | ]) 35 | 36 | test(' enable disable ', () => { 37 | assert.strictEqual(ENABLE_DISABLE.DISABLE, 0) 38 | assert.strictEqual(ENABLE_DISABLE['0'], '停用') 39 | assert.strictEqual(ENABLE_DISABLE.ENABLE, 1) 40 | assert.strictEqual(ENABLE_DISABLE['1'], '启用') 41 | }) 42 | }) 43 | 44 | /** 45 | * EnumData array function 46 | */ 47 | describe('EnumData array function', () => { 48 | const STATUS_MAP = EnumData([ 49 | ['PAY', 10, '待支付'], 50 | ['BALANCE', 20, '待回款'], 51 | ['REVIEW', 30, '待审核'], 52 | ]) 53 | 54 | test(' match array prototype function ', () => { 55 | assert.strictEqual( 56 | STATUS_MAP.forEach.constructor, 57 | Array.prototype.forEach.constructor, 58 | ) 59 | assert.strictEqual( 60 | STATUS_MAP.map.constructor, 61 | Array.prototype.map.constructor, 62 | ) 63 | 64 | assert.strictEqual(STATUS_MAP.length, 3) 65 | 66 | assert.strictEqual( 67 | STATUS_MAP.map(([, , text]) => text).join(','), 68 | '待支付,待回款,待审核', 69 | ) 70 | assert.strictEqual( 71 | STATUS_MAP.reduce((prevText, [key]) => `${prevText}${key},`, ''), 72 | 'PAY,BALANCE,REVIEW,', 73 | ) 74 | }) 75 | 76 | test(' match in EnumData', () => { 77 | assert.strictEqual('PAY' in STATUS_MAP, false) 78 | assert.strictEqual(20 in STATUS_MAP, false) 79 | assert.strictEqual('待审核' in STATUS_MAP, false) 80 | assert.strictEqual('every' in STATUS_MAP, true) 81 | assert.strictEqual(Symbol.iterator in STATUS_MAP, true) 82 | }) 83 | }) 84 | /** 85 | * EnumData object function 86 | */ 87 | describe('EnumData object function', () => { 88 | const originData = [ 89 | ['A', 0, '大A'], 90 | ['B', 1, '大B'], 91 | ] 92 | const ABC_MAP = EnumData(originData) 93 | 94 | test(' match object prototype function ', () => { 95 | assert.strictEqual( 96 | ABC_MAP.toString.constructor, 97 | Object.prototype.toString.constructor, 98 | ) 99 | assert.strictEqual( 100 | ABC_MAP.hasOwnProperty.constructor, 101 | Object.prototype.hasOwnProperty.constructor, 102 | ) 103 | }) 104 | 105 | test(' run object function ', () => { 106 | assert.strictEqual(ABC_MAP.toString(), originData.toString()) 107 | assert.strictEqual(ABC_MAP.valueOf(), originData.valueOf()) 108 | }) 109 | }) 110 | 111 | /** 112 | * EnumData conflict 113 | */ 114 | describe('EnumData conflict', () => { 115 | // 字段冲突、方法名冲突 116 | const STATUS_MAP = EnumData([ 117 | ['PAY', 10, '待支付'], 118 | ['PAYED', 'PAY', '已支付'], 119 | ['forEach', 30, '每一个'], 120 | ]) 121 | 122 | test(' fields reflect ', () => { 123 | assert.strictEqual(typeof STATUS_MAP.forEach === 'function', false) 124 | assert.strictEqual(STATUS_MAP.forEach === 30, true) 125 | assert.strictEqual(STATUS_MAP.PAY === '已支付', false) 126 | assert.strictEqual(STATUS_MAP.PAY === 10, true) 127 | }) 128 | }) 129 | 130 | /** 131 | * EnumData Set 132 | */ 133 | describe('EnumData Set', () => { 134 | // 字段冲突、方法名冲突 135 | const STATUS_MAP = EnumData([ 136 | ['YES', 1, '是'], 137 | ['NO', 0, '否'], 138 | ]) 139 | 140 | test(' cannot set value ', () => { 141 | assert.strictEqual(STATUS_MAP.YES === 1, true) 142 | expect(() => { 143 | STATUS_MAP.YES = '2' 144 | }).toThrowError(`Don't allow assignment to constant variable`) 145 | assert.strictEqual(STATUS_MAP.YES === 1, true) 146 | }) 147 | }) 148 | }) 149 | -------------------------------------------------------------------------------- /test/getStringByteLength.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { TextEncoder } from 'util' 3 | import { getStringByteLength } from '../src/getStringByteLength' 4 | 5 | global.TextEncoder = TextEncoder 6 | 7 | describe('getStringByteLength:', () => { 8 | /** 9 | * number and letter 10 | */ 11 | describe('number and letter', () => { 12 | test(' "123" => 3 ', () => { 13 | assert.strictEqual(getStringByteLength('123'), 3) 14 | }) 15 | test(' "ABC" => 3 ', () => { 16 | assert.strictEqual(getStringByteLength('ABC'), 3) 17 | }) 18 | }) 19 | 20 | /** 21 | * Chinese 22 | */ 23 | describe('Chinese', () => { 24 | test(' "哈" => 3 ', () => { 25 | assert.strictEqual(getStringByteLength('哈'), 3) 26 | }) 27 | 28 | test(' "阿斯顿" => 9 ', () => { 29 | assert.strictEqual(getStringByteLength('阿斯顿'), 9) 30 | }) 31 | }) 32 | 33 | /** 34 | * Emoji 35 | */ 36 | describe('Emoji', () => { 37 | test(' "😂" => 4 ', () => { 38 | assert.strictEqual(getStringByteLength('😂'), 4) 39 | }) 40 | 41 | test(' "👨‍👩‍👦" => 18 ', () => { 42 | assert.strictEqual(getStringByteLength('👨‍👩‍👦'), 18) 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /test/toUnicode.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { toUnicodeAt, toUnicode } from '../src/toUnicode' 3 | 4 | describe('type:', () => { 5 | /** 6 | * toUnicodeAt 7 | */ 8 | describe('toUnicodeAt', () => { 9 | test(' "ABC" => \\u0041 ', () => { 10 | assert.strictEqual(toUnicodeAt('ABC'), '\\u0041') 11 | }) 12 | test(' "ABC", 1 => \\u0042 ', () => { 13 | assert.strictEqual(toUnicodeAt('ABC', 1), '\\u0042') 14 | }) 15 | }) 16 | 17 | /** 18 | * toUnicode 19 | */ 20 | describe('toUnicode', () => { 21 | test(' "ABC" => \\u0041\\u0042\\u0043 ', () => { 22 | assert.strictEqual(toUnicode('ABC'), '\\u0041\\u0042\\u0043') 23 | }) 24 | 25 | test(' "" => "" ', () => { 26 | assert.strictEqual(toUnicode(''), '') 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/type.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { getTag, isNumber, isString } from '../src/type' 3 | 4 | describe('type:', () => { 5 | /** 6 | * getTag 7 | */ 8 | describe('getTag', () => { 9 | test(' undefined => [object Undefined] ', () => { 10 | assert.strictEqual(getTag(undefined), '[object Undefined]') 11 | }) 12 | 13 | test(' null => [object Null] ', () => { 14 | assert.strictEqual(getTag(null), '[object Null]') 15 | }) 16 | 17 | test(' 1 => [object Number] ', () => { 18 | assert.strictEqual(getTag(1), '[object Number]') 19 | }) 20 | 21 | test(' "abc" => [object String] ', () => { 22 | assert.strictEqual(getTag('abc'), '[object String]') 23 | }) 24 | 25 | test(' {} => [object Object] ', () => { 26 | assert.strictEqual(getTag({}), '[object Object]') 27 | }) 28 | 29 | test(' [] => [object Array] ', () => { 30 | assert.strictEqual(getTag([]), '[object Array]') 31 | }) 32 | 33 | test(' Symbol => [object Symbol] ', () => { 34 | assert.strictEqual(getTag(Symbol('key')), '[object Symbol]') 35 | }) 36 | }) 37 | 38 | /** 39 | * isNumber 40 | */ 41 | describe('isNumber', () => { 42 | test(' 1 => true ', () => { 43 | assert.strictEqual(isNumber(1), true) 44 | }) 45 | 46 | test(' NaN => true ', () => { 47 | assert.strictEqual(isNumber(NaN), true) 48 | }) 49 | 50 | test(" '2' => false ", () => { 51 | assert.strictEqual(isNumber('2'), false) 52 | }) 53 | }) 54 | 55 | /** 56 | * isString 57 | */ 58 | describe('isString', () => { 59 | test(" 'abc' => string ", () => { 60 | assert.strictEqual(isString('abc'), true) 61 | }) 62 | 63 | test(' 1 => false ', () => { 64 | assert.strictEqual(isString(1), false) 65 | }) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "resolveJsonModule": true, 5 | }, 6 | "include": [ 7 | "**/*.ts", 8 | "**/*.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* 基础配置 */ 4 | "target": "ES5", 5 | "lib": [ 6 | "DOM", 7 | "DOM.Iterable", 8 | "ESNext" 9 | ], 10 | "removeComments": false, 11 | "declaration": true, 12 | "sourceMap": false, 13 | 14 | /* 强类型检查配置 */ 15 | "strict": true, 16 | "noImplicitAny": false, 17 | 18 | /* 模块分析配置 */ 19 | "baseUrl": ".", 20 | "outDir": "dist", 21 | "declarationDir": ".", 22 | "esModuleInterop": true, 23 | "moduleResolution": "node", 24 | "resolveJsonModule": true 25 | }, 26 | "include": ["src"], 27 | "exclude": ["dist"] 28 | } 29 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rollup-plugin-babel' 2 | declare module 'rollup-plugin-eslint' 3 | declare module 'conventional-changelog' 4 | --------------------------------------------------------------------------------