From 47b52d6daa407bd939015c0fe8858771d35b9b25 Mon Sep 17 00:00:00 2001 From: JounQin Date: Sat, 30 Jul 2022 17:56:23 +0800 Subject: [PATCH] feat: first blood, should just work --- .changeset/config.json | 2 +- .eslintignore | 1 + .eslintrc | 6 +++ .github/workflows/pages.yml | 42 +++++++++++++++++++ .github/workflows/release.yml | 4 +- .github/workflows/vercel.yml | 1 + README.md | 40 ++++++++++++------ docs/package.json | 4 +- package.json | 20 +++++---- pnpm-lock.yaml | 77 ++++++++++++++++++++++++++++++++++- src/browser.ts | 43 +++++++++++++++++++ src/index.ts | 9 +++- test/basic.spec.ts | 26 ++++++++++-- tsconfig.json | 3 +- vercel.json | 2 +- vitest.config.ts | 12 +++++- 16 files changed, 257 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/pages.yml create mode 100644 src/browser.ts diff --git a/.changeset/config.json b/.changeset/config.json index 50ee5ae..64987d8 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,7 +3,7 @@ "changelog": [ "@changesets/changelog-github", { - "repo": "un-ts/lib-boilerplate" + "repo": "un-ts/ab64" } ], "commit": false, diff --git a/.eslintignore b/.eslintignore index a45726f..8a7b586 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,5 @@ dist lib CHANGELOG.md /pnpm-lock.yaml +!/.github !/.*.cjs diff --git a/.eslintrc b/.eslintrc index f608b51..f73fb22 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,6 +2,12 @@ "root": true, "extends": "@1stg", "overrides": [ + { + "files": ".github/*.yml", + "rules": { + "unicorn/filename-case": "off" + } + }, { "files": "docs/**/*.tsx", "rules": { diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..5dd29ab --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,42 @@ +name: Deploy GitHub Pages + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: latest + + - name: Setup Node.js 16 + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: pnpm + + - name: Install Dependencies + run: pnpm i + + - name: Build Docs + run: yarn docs:build + + - name: Add misc files + run: | + touch dist/.nojekyll + echo ab64.js.org > dist/CNAME + + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ccc0762..ec71470 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,8 +35,8 @@ jobs: with: publish: pnpm release version: pnpm run version - commit: 'chore: release lib-boilerplate' - title: 'chore: release lib-boilerplate' + commit: 'chore: release ab64' + title: 'chore: release ab64' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/vercel.yml b/.github/workflows/vercel.yml index 825c3bf..42bffeb 100644 --- a/.github/workflows/vercel.yml +++ b/.github/workflows/vercel.yml @@ -4,6 +4,7 @@ on: push: branches: - main + # eslint-disable-next-line yml/no-empty-mapping-value pull_request_target: jobs: diff --git a/README.md b/README.md index 55c5619..a31fb18 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# lib-boilerplate +# ab64 -[![GitHub Actions](https://github.com/un-ts/lib-boilerplate/workflows/CI/badge.svg)](https://github.com/un-ts/lib-boilerplate/actions/workflows/ci.yml) -[![Codecov](https://img.shields.io/codecov/c/github/un-ts/lib-boilerplate.svg)](https://codecov.io/gh/un-ts/lib-boilerplate) -[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/un-ts/lib-boilerplate.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/un-ts/lib-boilerplate/context:javascript) -[![type-coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fun-ts%2Flib-boilerplate%2Fmain%2Fpackage.json)](https://github.com/plantain-00/type-coverage) -[![npm](https://img.shields.io/npm/v/lib-boilerplate.svg)](https://www.npmjs.com/package/lib-boilerplate) -[![GitHub Release](https://img.shields.io/github/release/un-ts/lib-boilerplate)](https://github.com/un-ts/lib-boilerplate/releases) +[![GitHub Actions](https://github.com/un-ts/ab64/workflows/CI/badge.svg)](https://github.com/un-ts/ab64/actions/workflows/ci.yml) +[![Codecov](https://img.shields.io/codecov/c/github/un-ts/ab64.svg)](https://codecov.io/gh/un-ts/ab64) +[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/un-ts/ab64.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/un-ts/ab64/context:javascript) +[![type-coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fun-ts%2Fab64%2Fmain%2Fpackage.json)](https://github.com/plantain-00/type-coverage) +[![npm](https://img.shields.io/npm/v/ab64.svg)](https://www.npmjs.com/package/ab64) +[![GitHub Release](https://img.shields.io/github/release/un-ts/ab64)](https://github.com/un-ts/ab64/releases) [![Conventional Commits](https://img.shields.io/badge/conventional%20commits-1.0.0-yellow.svg)](https://conventionalcommits.org) [![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com) @@ -13,7 +13,7 @@ [![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![changesets](https://img.shields.io/badge/maintained%20with-changesets-176de3.svg)](https://github.com/changesets/changesets) -A simple library boilerplate. +The smallest and fastest Base64 implementation in JavaScript ## TOC @@ -31,21 +31,35 @@ A simple library boilerplate. ```sh # pnpm -pnpm add lib-boilerplate +pnpm add ab64 # yarn -yarn add lib-boilerplate +yarn add ab64 # npm -npm i lib-boilerplate +npm i ab64 ``` ### API ```js -import echo from 'lib-boilerplate' +import { decode, decodeUrl, encode, encodeUrl } from 'ab64' -echo() +encode('Hello World!') // SGVsbG8gV29ybGQh + +encode('dankogai') // ZGFua29nYWk= +encodeUrl('dankogai') // ZGFua29nYWk + +encode('小飼弾') // 5bCP6aO85by+ +encodeUrl('小飼弾') // 5bCP6aO85by- + +decode('SGVsbG8gV29ybGQh') // Hello World! + +decode('ZGFua29nYWk=') // dankogai +decodeUrl('ZGFua29nYWk') // dankogai + +decode('5bCP6aO85by+') // 小飼弾 +decodeUrl('5bCP6aO85by-') // 小飼弾 ``` ## Sponsors diff --git a/docs/package.json b/docs/package.json index 0500511..98a661a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,5 +1,5 @@ { - "name": "lib-boilerplate-docs", + "name": "ab64-docs", "type": "commonjs", - "description": "A simple library boilerplate." + "description": "The smallest and fastest Base64 implementation in JavaScript" } diff --git a/package.json b/package.json index 93eae3e..9234ad8 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "lib-boilerplate", - "version": "0.0.2", + "name": "ab64", + "version": "0.0.0", "type": "module", - "description": "A simple library boilerplate.", - "repository": "git+https://github.com/un-ts/lib-boilerplate.git", + "description": "The smallest and fastest Base64 implementation in JavaScript", + "repository": "git+https://github.com/un-ts/ab64.git", "author": "JounQin (https://www.1stG.me) ", "donate": { "recipients": [ @@ -62,10 +62,11 @@ "postversion": "pnpm i --no-frozen-lockfile", "prepare": "simple-git-hooks", "prerelease": "pnpm build", - "prevercel-build": "pnpm build", "release": "changeset publish", "serve": "sirv dist -s", - "test": "vitest run --coverage", + "test": "run-p test:*", + "test:browser": "vitest run --coverage", + "test:node": "cross-env TEST_ENV=node vitest run --coverage", "typecov": "type-coverage", "vercel-build": "pnpm docs:build", "version": "changeset version" @@ -87,8 +88,9 @@ "@types/react-dom": "^18.0.6", "@types/web": "^0.0.70", "c8": "^7.12.0", + "cross-env": "^7.0.3", + "edge-runtime": "1.1.0-beta.23", "github-markdown-css": "^5.1.0", - "lib-boilerplate": "link:.", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.3.0", @@ -104,6 +106,7 @@ "module": "./lib/index.js", "exports": { "types": "./lib/index.d.ts", + "browser": "./lib/browser.js", "import": "./lib/index.js", "require": "./lib/index.cjs" }, @@ -111,7 +114,8 @@ }, "size-limit": [ { - "path": "lib/index.js" + "path": "lib/browser.js", + "limit": "280B" } ], "typeCoverage": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5c267a..a43b1d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,8 +14,9 @@ specifiers: '@types/react-dom': ^18.0.6 '@types/web': ^0.0.70 c8: ^7.12.0 + cross-env: ^7.0.3 + edge-runtime: 1.1.0-beta.23 github-markdown-css: ^5.1.0 - lib-boilerplate: link:. react: ^18.2.0 react-dom: ^18.2.0 react-router-dom: ^6.3.0 @@ -44,8 +45,9 @@ devDependencies: '@types/react-dom': 18.0.6 '@types/web': 0.0.70 c8: 7.12.0 + cross-env: 7.0.3 + edge-runtime: 1.1.0-beta.23 github-markdown-css: 5.1.0 - lib-boilerplate: 'link:' react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router-dom: 6.3.0_biqbaboplfbrettd7655fr4n2y @@ -2502,6 +2504,20 @@ packages: postcss-selector-parser: 6.0.10 dev: true + /@edge-runtime/format/1.1.0-beta.23: + resolution: {integrity: sha512-TuY7ywLzp2XQQZpM8cX1dzm2QMK2juvtpMVR8K61utL2qvokzJ4gBYLcPSKhH0EWAt4WwgymNVRX0cpdSLAqAg==} + dev: true + + /@edge-runtime/primitives/1.1.0-beta.23: + resolution: {integrity: sha512-0vHcZZwyxjmw/so9irYtA82/+nAlJRs+1WpRYBx7iae1FOGCPM4BIKEmboWmwTuj7c6avz9kIbptokdMUPgV9A==} + dev: true + + /@edge-runtime/vm/1.1.0-beta.23: + resolution: {integrity: sha512-XBp3rCuX4scJVOo2KconAotL5XGX3zdd8IkfDNr5VVSQ/B6HkiTNuf+EvzSQTpplF+fiyLTpfcP9EbNLibwLTA==} + dependencies: + '@edge-runtime/primitives': 1.1.0-beta.23 + dev: true + /@es-joy/jsdoccomment/0.31.0: resolution: {integrity: sha512-tc1/iuQcnaiSIUVad72PBierDFpsxdUHtEF/OrfqvM1CBAsIoMP51j52jTMb3dXriwhieTo289InzZj72jL3EQ==} engines: {node: ^14 || ^16 || ^17 || ^18} @@ -5528,6 +5544,11 @@ packages: through2: 4.0.2 dev: true + /convert-hrtime/3.0.0: + resolution: {integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==} + engines: {node: '>=8'} + dev: true + /convert-source-map/1.8.0: resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} dependencies: @@ -5611,6 +5632,14 @@ packages: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true + /cross-env/7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + dev: true + /cross-spawn/5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -6311,6 +6340,21 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true + /edge-runtime/1.1.0-beta.23: + resolution: {integrity: sha512-A7dO/Y+4UJnaxFcdz6pepL+0GcvvViWvf201oFQXepgdSxPDKiqxaayCag0eiirQ6OfF+cSTmPD3xrfEoAIjiQ==} + hasBin: true + dependencies: + '@edge-runtime/format': 1.1.0-beta.23 + '@edge-runtime/vm': 1.1.0-beta.23 + exit-hook: 2.2.1 + http-status: 1.5.2 + mri: 1.2.0 + picocolors: 1.0.0 + pretty-bytes: 5.6.0 + pretty-ms: 7.0.1 + time-span: 4.0.0 + dev: true + /ee-first/1.1.1: resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} dev: true @@ -7599,6 +7643,11 @@ packages: pify: 2.3.0 dev: true + /exit-hook/2.2.1: + resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} + engines: {node: '>=6'} + dev: true + /expand-tilde/2.0.2: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} @@ -8709,6 +8758,11 @@ packages: - debug dev: true + /http-status/1.5.2: + resolution: {integrity: sha512-HzxX+/hV/8US1Gq4V6R6PgUmJ5Pt/DGATs4QhdEOpG8LrdS9/3UG2nnOvkqUpRks04yjVtV5p/NODjO+wvf6vg==} + engines: {node: '>= 0.4.0'} + dev: true + /human-id/1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} dev: true @@ -11683,6 +11737,11 @@ packages: lines-and-columns: 2.0.3 dev: true + /parse-ms/2.1.0: + resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} + engines: {node: '>=6'} + dev: true + /parse-node-version/1.0.1: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} @@ -12931,6 +12990,13 @@ packages: renderkid: 3.0.0 dev: true + /pretty-ms/7.0.1: + resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} + engines: {node: '>=10'} + dependencies: + parse-ms: 2.1.0 + dev: true + /proc-log/2.0.1: resolution: {integrity: sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -15803,6 +15869,13 @@ packages: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} dev: true + /time-span/4.0.0: + resolution: {integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==} + engines: {node: '>=10'} + dependencies: + convert-hrtime: 3.0.0 + dev: true + /timed-out/4.0.1: resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==} engines: {node: '>=0.10.0'} diff --git a/src/browser.ts b/src/browser.ts new file mode 100644 index 0000000..9445f95 --- /dev/null +++ b/src/browser.ts @@ -0,0 +1,43 @@ +const HEX = 16 +const CHUNK = 4 + +export const decode = (val: string) => + decodeURIComponent( + [ + // eslint-disable-next-line sonar/deprecation -- it's fine on browser + ...atob(val), + ] + .map( + char => '%' + ('00' + char.codePointAt(0)!.toString(HEX)).slice(-1 * 2), + ) + .join(''), + ) + +export const encode = (val: string) => + // eslint-disable-next-line sonar/deprecation -- it's fine on browser + btoa( + encodeURIComponent(val).replace(/%([\dA-F]{2})/g, (_, $1: string) => + // eslint-disable-next-line unicorn/prefer-number-properties -- for smaller size + String.fromCodePoint(parseInt($1, HEX)), + ), + ) + +export const decodeUrl = (val: string) => { + let output = val.replace(/-/g, '+').replace(/_/g, '/') + switch (output.length % CHUNK) { + case 0: + break + case 2: + output += '==' + break + case 3: + output += '=' + break + default: + break + } + return decode(output) +} + +export const encodeUrl = (val: string) => + encode(val).replace(/\+/g, '-').replace(/=/g, '').replace(/\//g, '_') diff --git a/src/index.ts b/src/index.ts index f3bea86..98eb927 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,8 @@ -export default () => 'Hello World!' +export const decode = (val: string) => Buffer.from(val, 'base64').toString() + +export const encode = (val: string) => Buffer.from(val).toString('base64') + +export const decodeUrl = (val: string) => + Buffer.from(val, 'base64url').toString() + +export const encodeUrl = (val: string) => Buffer.from(val).toString('base64url') diff --git a/test/basic.spec.ts b/test/basic.spec.ts index 482d0dc..c7e7cf2 100644 --- a/test/basic.spec.ts +++ b/test/basic.spec.ts @@ -1,5 +1,25 @@ -import echo from 'lib-boilerplate' +import { decode, decodeUrl, encode, encodeUrl } from 'ab64' -test('it should just work', () => { - expect(echo()).toBe('Hello World!') +describe('encoding', () => { + test('it should just work', () => { + expect(encode('Hello World!')).toBe('SGVsbG8gV29ybGQh') + + expect(encode('dankogai')).toBe('ZGFua29nYWk=') + expect(encodeUrl('dankogai')).toBe('ZGFua29nYWk') + + expect(encode('小飼弾')).toBe('5bCP6aO85by+') + expect(encodeUrl('小飼弾')).toBe('5bCP6aO85by-') + }) +}) + +describe('decoding', () => { + test('it should just work', () => { + expect(decode('SGVsbG8gV29ybGQh')).toBe('Hello World!') + + expect(decode('ZGFua29nYWk=')).toBe('dankogai') + expect(decodeUrl('ZGFua29nYWk')).toBe('dankogai') + + expect(decode('5bCP6aO85by+')).toBe('小飼弾') + expect(decodeUrl('5bCP6aO85by-')).toBe('小飼弾') + }) }) diff --git a/tsconfig.json b/tsconfig.json index cf038c3..7a545eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "@1stg/tsconfig/node16", "compilerOptions": { - "rootDir": "." + "rootDir": ".", + "removeComments": true } } diff --git a/vercel.json b/vercel.json index 3ba006f..6606ec2 100644 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,7 @@ { "version": 2, "alias": [ - "lib-boilerplate.vercel.app" + "ab64.vercel.app" ], "github": { "silent": true diff --git a/vitest.config.ts b/vitest.config.ts index 4a665dd..7272107 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,9 +1,18 @@ +import path from 'node:path' + import autoImport from 'unplugin-auto-import/vite' import { defineConfig } from 'vitest/config' +const isNode = process.env.TEST_ENV === 'node' + export default defineConfig({ + resolve: { + alias: { + ab64: path.resolve(`src/${isNode ? 'index' : 'browser'}`), + }, + }, plugins: [ - // TODO: report the TypeScript issue + /** @see https://github.com/microsoft/TypeScript/issues/50067 */ ( autoImport as unknown as typeof import('unplugin-auto-import/vite')['default'] )({ @@ -14,5 +23,6 @@ export default defineConfig({ coverage: { reporter: ['lcov', 'json', 'text'], }, + environment: isNode ? 'node' : 'edge-runtime', }, })