├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ ├── pkg.pr.new.yml │ └── release.yml ├── .gitignore ├── .node-version ├── .npmrc ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── biome.json ├── commitlint.config.mjs ├── docs ├── .vitepress │ ├── config.ts │ └── theme │ │ ├── custom.css │ │ └── index.ts ├── advanced │ ├── cancellation.md │ ├── composition-api.md │ ├── enhancements.md │ ├── handling-errors.md │ ├── interceptors.md │ └── typescript-support.md ├── api │ ├── config-defaults.md │ ├── instance.md │ ├── introduction.md │ ├── request-config.md │ └── response-schema.md ├── guide │ ├── example.md │ ├── installation.md │ └── introduction.md ├── index.md ├── other │ ├── build.md │ ├── comparison.md │ └── thank.md ├── package.json └── public │ ├── ads.txt │ └── logo.png ├── lerna.json ├── lint-staged.config.mjs ├── netlify.toml ├── package.json ├── packages └── core │ ├── CHANGELOG.md │ ├── build.config.ts │ ├── package.json │ └── src │ ├── adapters │ ├── download.ts │ ├── index.ts │ ├── request.ts │ └── upload.ts │ ├── composables.ts │ ├── core │ ├── HttpStatusCode.ts │ ├── Un.ts │ ├── UnCancelToken.test.ts │ ├── UnCancelToken.ts │ ├── UnCanceledError.test.ts │ ├── UnCanceledError.ts │ ├── UnError.test.ts │ ├── UnError.ts │ ├── UnInterceptorManager.ts │ ├── dispatchRequest.ts │ ├── index.ts │ ├── isUnCancel.test.ts │ ├── isUnCancel.ts │ ├── isUnError.test.ts │ ├── isUnError.ts │ ├── settle.test.ts │ └── settle.ts │ ├── defaults │ └── index.ts │ ├── index.test.ts │ ├── index.ts │ ├── types │ ├── adapter.ts │ ├── common.ts │ ├── config.ts │ ├── index.ts │ ├── promise.ts │ ├── response.ts │ └── task.ts │ └── utils │ ├── buildDownloadConfig.ts │ ├── buildFullPath.test.ts │ ├── buildFullPath.ts │ ├── buildRequestConfig.ts │ ├── buildUploadConfig.ts │ ├── buildUrl.test.ts │ ├── buildUrl.ts │ ├── combineUrls.test.ts │ ├── combineUrls.ts │ ├── extend.test.ts │ ├── extend.ts │ ├── forEach.test.ts │ ├── forEach.ts │ ├── index.ts │ ├── isAbsoluteUrl.test.ts │ ├── isAbsoluteUrl.ts │ └── mergeConfig.ts ├── playground ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── main.ts │ ├── manifest.json │ ├── pages.json │ ├── pages │ │ └── index │ │ │ └── index.vue │ ├── static │ │ └── logo.png │ └── uni.scss ├── tsconfig.json └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── simple-git-hooks.cjs ├── tsconfig.json └── vitest.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ModyQyW] 2 | custom: ["https://github.com/ModyQyW/sponsors"] 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/unocss/unocss/blob/fe83a90b59cf4599be57ea825166bb74d92b104c/.github/workflows/ci.yml 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | merge_group: {} 14 | 15 | jobs: 16 | test: 17 | runs-on: ${{ matrix.os }} 18 | 19 | strategy: 20 | matrix: 21 | os: [ubuntu-latest, macos-latest, windows-latest] 22 | node_version: [18, 20, 22] 23 | fail-fast: false 24 | 25 | steps: 26 | - name: Set git to use LF 27 | run: | 28 | git config --global core.autocrlf false 29 | git config --global core.eol lf 30 | 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Install pnpm 35 | uses: pnpm/action-setup@v4 36 | 37 | - name: Setup Node.js ${{ matrix.node_version }} 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: ${{ matrix.node_version }} 41 | cache: pnpm 42 | registry-url: https://registry.npmjs.org 43 | 44 | - name: Setup corepack 45 | run: corepack enable 46 | 47 | - name: Install Dependencies 48 | run: pnpm install --frozen-lockfile 49 | 50 | - name: Build 51 | run: pnpm build 52 | 53 | - name: Test 54 | run: pnpm test 55 | 56 | - name: Type Check 57 | run: pnpm type-check 58 | -------------------------------------------------------------------------------- /.github/workflows/pkg.pr.new.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/unocss/unocss/blob/fe83a90b59cf4599be57ea825166bb74d92b104c/.github/workflows/pkg.pr.new.yml 2 | name: Publish Any Commit 3 | 4 | on: 5 | pull_request: 6 | push: 7 | branches: 8 | - '**' 9 | tags: 10 | - '!**' 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Install pnpm 23 | uses: pnpm/action-setup@v4 24 | 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version-file: .node-version 29 | cache: pnpm 30 | registry-url: https://registry.npmjs.org 31 | 32 | - name: Setup corepack 33 | run: corepack enable 34 | 35 | - name: Install Dependencies 36 | run: pnpm install 37 | 38 | - name: Build 39 | run: pnpm build 40 | 41 | - name: Release 42 | run: pnpx pkg-pr-new publish --compact --pnpm './packages/*' 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/unocss/unocss/blob/fe83a90b59cf4599be57ea825166bb74d92b104c/.github/workflows/release.yml 2 | name: Release 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | release: 11 | permissions: 12 | id-token: write 13 | contents: write 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Install pnpm 22 | uses: pnpm/action-setup@v4 23 | 24 | - name: Setup Node.js 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version-file: .node-version 28 | cache: pnpm 29 | registry-url: https://registry.npmjs.org 30 | 31 | - name: Setup corepack 32 | run: corepack enable 33 | 34 | - name: GitHub Release 35 | run: pnpx changelogithub 36 | env: 37 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 38 | 39 | - name: Install Dependencies 40 | run: pnpm install 41 | 42 | - name: Build 43 | run: pnpm build 44 | 45 | - name: Publish to NPM 46 | run: pnpm -r publish --access public --no-git-checks 47 | env: 48 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 49 | NPM_CONFIG_PROVENANCE: true 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | pnpm-debug.log* 9 | run 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules 44 | jspm_packages 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules 48 | 49 | # Temp directory 50 | .temp 51 | .tmp 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache 64 | .rts2_cache_cjs 65 | .rts2_cache_es 66 | .rts2_cache_umd 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | .env.test 80 | .env.*.test 81 | .env.local 82 | .env.*.local 83 | *.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Electron 90 | dist-electron 91 | dist_electron 92 | 93 | # Tauri 94 | dist-tauri 95 | dist_tauri 96 | 97 | # Cordova 98 | dist-cordova 99 | dist_cordova 100 | 101 | # Capacitor 102 | dist-capacitor 103 | dist_capacitor 104 | 105 | # Next.js build output 106 | .next 107 | out 108 | 109 | # Umi.js build output 110 | .umi 111 | .umi-production 112 | .umi-test 113 | 114 | # Nuxt.js build / generate output 115 | .nuxt 116 | dist 117 | 118 | # Rax.js build 119 | .rax 120 | 121 | # Vuepress build output 122 | .vuepress/dist 123 | 124 | # SSR 125 | dist-ssr 126 | dist_ssr 127 | 128 | # SSG 129 | dist-ssg 130 | dist_ssg 131 | 132 | # Serverless 133 | .serverless 134 | .dynamodb 135 | .s3 136 | .buckets 137 | .seeds 138 | 139 | # FuseBox cache 140 | .fusebox 141 | 142 | # TernJS port file 143 | .tern-port 144 | 145 | # Cypress 146 | /cypress/videos/ 147 | /cypress/screenshots/ 148 | 149 | # Editor 150 | .vscode-test 151 | .vscode/** 152 | !.vscode/extensions.json 153 | !.vscode/settings.json 154 | *.vsix 155 | .idea 156 | .hbuilder 157 | .hbuilderx 158 | *.suo 159 | *.ntvs* 160 | *.njsproj 161 | *.sln 162 | *.sw? 163 | 164 | # yarn v2 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # Apple 172 | .DS_Store 173 | *.p12 174 | *.mobileprovision 175 | 176 | # Android 177 | *.keystore 178 | 179 | # Vitepress 180 | cache 181 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | registry=https://registry.npmjs.org/ 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome", 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "quickfix.biome": "explicit", 6 | "source.organizeImports.biome": "explicit" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.21.3](https://github.com/uni-helper/uni-network/compare/v0.21.2...v0.21.3) (2025-05-15) 7 | 8 | ### Bug Fixes 9 | 10 | * useUn narrow type of data when initialValue is provided, https://github.com/vueuse/vueuse/pull/4419/files ([33ef53b](https://github.com/uni-helper/uni-network/commit/33ef53bb266d2d158ed2f3bff440348634bab051)) - by @ 11 | 12 | ## [0.21.2](https://github.com/uni-helper/uni-network/compare/v0.21.1...v0.21.2) (2025-05-14) 13 | 14 | ### Bug Fixes 15 | 16 | * fix the Un constructor implementation to treat the config argument as optional, https://github.com/axios/axios/pull/6881 ([7086fb8](https://github.com/uni-helper/uni-network/commit/7086fb86f159428e4b0eb2ac46328d7b72afe506)) - by @ 17 | 18 | ## [0.21.1](https://github.com/uni-helper/uni-network/compare/v0.21.0...v0.21.1) (2025-03-25) 19 | 20 | ### Bug Fixes 21 | 22 | * fix buildFullPath judgement ([fbe706a](https://github.com/uni-helper/uni-network/commit/fbe706a16d9abed6fe39b15c259c764874f5e63b)) - by @ 23 | 24 | ## [0.21.0](https://github.com/uni-helper/uni-network/compare/v0.20.0...v0.21.0) (2025-03-18) 25 | 26 | ### Features 27 | 28 | * add allowAbsoluteUrls option ([79c4581](https://github.com/uni-helper/uni-network/commit/79c4581620717485ff2e83b0fecde92c44707c64)) - by @ 29 | 30 | ## [0.20.0](https://github.com/uni-helper/uni-network/compare/v0.19.3...v0.20.0) (2025-01-06) 31 | 32 | ### ⚠ BREAKING CHANGES 33 | 34 | * improve error handling (#56) 35 | 36 | ### Features 37 | 38 | * improve error handling ([#56](https://github.com/uni-helper/uni-network/issues/56)) ([0b6aa80](https://github.com/uni-helper/uni-network/commit/0b6aa80afa231cce891e288c309d278bf86fd7f4)) - by @peerless-hero 39 | 40 | ### Bug Fixes 41 | 42 | * syntax error on ios, axios/axios[#6608](https://github.com/uni-helper/uni-network/issues/6608) ([2f51786](https://github.com/uni-helper/uni-network/commit/2f51786702e6f0c69930002356941fce8f1db2c7)) - by @ 43 | 44 | ## [0.19.3](https://github.com/uni-helper/uni-network/compare/v0.19.2...v0.19.3) (2024-09-20) 45 | 46 | ### Features 47 | 48 | * add toAbortSignal to UnCancelToken, axios[#6582](https://github.com/uni-helper/uni-network/issues/6582) ([738486c](https://github.com/uni-helper/uni-network/commit/738486cc1404ffc6ed1df3103a23f55f2ecaf563)) - by @ModyQyW 49 | 50 | ### Bug Fixes 51 | 52 | * add the missed implementation of UnError[#status](https://github.com/uni-helper/uni-network/issues/status) property, axios[#6573](https://github.com/uni-helper/uni-network/issues/6573) ([43b5bc5](https://github.com/uni-helper/uni-network/commit/43b5bc541f79f00d6cd95e406205ecfe95f484dd)) - by @ModyQyW 53 | * allow vueuse v11 ([cd84a78](https://github.com/uni-helper/uni-network/commit/cd84a784a9f0ed67d018a19da48c267f1e3ed274)) - by @ModyQyW 54 | * disregard protocol-relative URL to remediate SSRF, axios[#6539](https://github.com/uni-helper/uni-network/issues/6539) ([025cd49](https://github.com/uni-helper/uni-network/commit/025cd49fbf44493f97db3f340762de1599d31910)) - by @ModyQyW 55 | * fix main entry ([7c6561c](https://github.com/uni-helper/uni-network/commit/7c6561ca770a9cdc0f1c861a0d9506cb3f31fe86)) - by @ModyQyW 56 | * fix node10 ts support ([ad3b98c](https://github.com/uni-helper/uni-network/commit/ad3b98cf958bfdb0aeff06dafe7dd3996bcbbd9b)) - by @ModyQyW 57 | * replace statuses with statuses-es for better compact compatibility ([#53](https://github.com/uni-helper/uni-network/issues/53)) ([4806357](https://github.com/uni-helper/uni-network/commit/48063578403e1cbd1f8dcfc602c7d0df026bb995)) - by @wtto00 58 | 59 | ## 0.19.2 (2024-07-10) 60 | 61 | * fix: 迭代对象键时跳过 constructor 和私有属性 62 | 63 | ## 0.19.1 (2024-07-05) 64 | 65 | * fix: 修复类型 66 | 67 | ## 0.19.0 (2024-07-05) 68 | 69 | * feat!: 优化类型 70 | * 移除了不安全的声明合并,[@typescript-eslint/no-unsafe-declaration-merging](https://typescript-eslint.io/rules/no-unsafe-declaration-merging/) 71 | * 移除了潜在有害的方法速记语法 Method Shorthand Syntax,[Method Shorthand Syntax Considered Harmful](https://www.totaltypescript.com/method-shorthand-syntax-considered-harmful) 72 | * fix(composables): 对齐 vueuse v10.8.0 改动 73 | * fix: 封装错误以改进异步堆栈跟踪,仅捕获处理原生错误对象的异步堆栈 74 | 75 | ## 0.18.1 (2024-02-20) 76 | 77 | * fix(composables): 对齐 vueuse v10.7.2 改动 78 | 79 | ## 0.18.0 (2024-01-03) 80 | 81 | * feat!: 默认使用 [fast-querystring](https://github.com/anonrig/fast-querystring) 而不是 [query-string](https://github.com/sindresorhus/query-string) 序列化 `params` 82 | * `query-string@8.1.0` 和它所依赖的库存在 `try {} catch {}` 的用法,该用法不受支付宝小程序支持,需要用户侧额外处理 83 | 84 | ## 0.17.0 (2023-11-04) 85 | 86 | * feat!: 现在要求 `node>=18` 87 | 88 | ## 0.16.1 (2023-08-08) 89 | 90 | * fix: 移除默认的 timeout 值,修复 H5 端请求立即超时的问题,关闭 [#37](https://github.com/uni-helper/uni-network/issues/37) 91 | 92 | ## 0.16.0 (2023-07-03) 93 | 94 | * feat!: 默认使用 [query-string](https://github.com/sindresorhus/query-string) 而不是 [qs](https://github.com/ljharb/qs) 序列化 `params` 95 | 96 | * `query-string@8.1.0` 支持 `node >= 14.16`,没有过多的历史包袱,而 `qs@6.11.2` 至今还在支持 `node >= 0.6` 97 | * `qs@6.10.0` 开始引入了 `get-intrinsic`,结合微信小程序和微信小程序插件使用时会出现报错,参考 [#31](https://github.com/uni-helper/uni-network/issues/31),而 `query-string@8.1.0` 没有这个问题 98 | * 如果你的 `params` 对象内某个键的值为对象、数组或 Date,表现行为会不一致 99 | * 如果你更倾向于使用该库 0.15.0 版本或更早版本的默认设置,请安装 `qs@6.9.7`,并设置 `paramsSerializer` 100 | 101 | ```typescript 102 | { 103 | ..., 104 | paramsSerializer: (params: Record) => { 105 | return Object.prototype.toString.call(params).includes('URLSearchParams') 106 | ? params.toString() 107 | : qs.stringify(params); 108 | }, 109 | ..., 110 | } 111 | ``` 112 | 113 | ## 0.15.0 (2023-04-16) 114 | 115 | * feat(composables)!: 调整返回的 `error` Ref 类型为 `unknown`,对齐 `vueuse@10` 行为,查看 [vueuse/vueuse#2807](https://github.com/vueuse/vueuse/pull/2807) 了解更多 116 | * feat(composables)!: 使用 `PromiseLike` 替换 `Promise`,对齐 `vueuse@10` 行为,查看 [vueuse/vueuse#2485](https://github.com/vueuse/vueuse/pull/2485) 了解更多 117 | * feat(composables): 增加 `onFinish` 选项,对齐 `vueuse@10` 行为,查看 [vueuse/vueuse#2829](https://github.com/vueuse/vueuse/pull/2829) 了解更多 118 | * feat(composables): 增加 `initialData` 和 `resetOnExecute` 选项,对齐 `vueuse@10` 行为,查看 [vueuse/vueuse#2791](https://github.com/vueuse/vueuse/pull/2791) 了解更多 119 | * fix(composables): 修复 `UnConfig` 类型使用 120 | 121 | ## 0.14.0 (2023-02-28) 122 | 123 | * feat: 新增组合式函数 124 | 125 | ## 0.13.3 (2023-02-08) 126 | 127 | 和 0.13.1 一样,但又修复了一个 ci 问题。 128 | 129 | ## 0.13.2 (2023-02-08) 130 | 131 | 和 0.13.1 一样,但修复了一个 ci 问题。 132 | 133 | ## 0.13.1 (2023-02-08) 134 | 135 | * fix: 修复了 `un.download` 和 `un.upload` 没有正确设置 `adapter` 的问题,感谢 [@edazh](https://github.com/edazh) 在 [#25](https://github.com/uni-helper/uni-network/pull/25) 的贡献 136 | * build: 构建工具切换到 `unbuild` 137 | 138 | ## 0.13.0 (2023-02-06) 139 | 140 | 调整了部分代码风格,同时更新了文档。 141 | 142 | ## 0.12.6 (2023-01-10) 143 | 144 | * fix: 处理 `statuses` 可能抛出的错误,感谢 [@edazh](https://github.com/edazh) 在 [#18](https://github.com/uni-helper/uni-network/pull/18) 的贡献 145 | 146 | ## 0.12.5 (2023-01-04) 147 | 148 | * build: 使用 `rollup` 构建 149 | * perf: `mergeConfig` 实现调整 150 | 151 | ## 0.12.4 (2022-12-30) 152 | 153 | * fix: 移除构建中 `qs` 相关代码以修复构建 154 | 155 | ## 0.12.3 (2022-12-30) 156 | 157 | 误发布,请勿使用。 158 | 159 | ## 0.12.2 (2022-12-30) 160 | 161 | * fix: 修复构建 162 | 163 | ## 0.12.1 (2022-12-29) 164 | 165 | * perf: 明确导入类型 166 | * perf: 分离 `HttpStatusCode` 167 | * perf: 暴露 `mergeConfig` 和 `HttpStatusCode` 168 | 169 | ## 0.12.0 (2022-12-28) 170 | 171 | * feat!: 要求 `node >= 14.18`,这是为了对标 `rollup` 和 `vite` 172 | * feat: esm 优先,但仍然提供 cjs 支持 173 | * fix: 修复导出 174 | * perf: 构建包含 `ramda`、`statuses` 和 `qs` 相关代码 175 | 176 | ## 0.11.1 (2022-11-16) 177 | 178 | * fix: 修复构建 179 | 180 | ## 0.11.0 (2022-11-16) 181 | 182 | * feat!: 迁移到 `@uni-helper/uni-network`,`Uan` 前缀调整为 `Un` 183 | * perf: 移除 `lodash-es` 184 | 185 | ## 0.10.2 (2022-10-18) 186 | 187 | * perf: 优化 `params` 类型 188 | * perf: 分离 `mergeConfig` 方法 189 | * fix: 修复 `UanCanceler` 类型 190 | 191 | ## 0.10.1 (2022-10-12) 192 | 193 | * fix: 修复构建 194 | 195 | ## 0.10.0 (2022-10-11) 196 | 197 | * feat!: 重命名一些 `request` 为 `task` 避免误导 198 | * feat!: 调整类型,移除了 `UanBaseXxx`、`UanRequestXxx`、`UanDownloadXxx`、`UanUploadXxx` 等类型,可直接使用 `UanXxx` 199 | * feat: 支持 `onProgress`、`onProgressUpdate`、`onDownloadProgress`、`onUploadProgressUpdate`、`onUploadProgress`、`onUploadProgressUpdate` 200 | 201 | ## 0.8.0 (2022-10-07) 202 | 203 | * feat: 增加枚举数据 204 | * fix: 修复类型定义 205 | * perf: 优化类型定义 206 | * feat!: 导出的 `isCancel` 调整为 `isUanCancel` 207 | 208 | ## 0.7.1 (2022-09-30) 209 | 210 | * fix: 修复了构建不正常的问题 211 | 212 | ## 0.7.0 (2022-09-29) 213 | 214 | * feat!: 现在要求使用 `node >= 14.16` 215 | * feat!: 现在构建目标是 `esnext` 216 | * fix: 修复了构建不正常的问题 217 | 218 | ## 0.6.0 219 | 220 | * feat!: 重命名为 `uni-app-network` 221 | 222 | ## 0.4.8 223 | 224 | * perf: 更新 `utils/extend`,匹配 `axios` 改动 225 | 226 | ## 0.4.7 227 | 228 | * fix: 修复导出 229 | 230 | ## 0.4.6 231 | 232 | * fix: 修复导出 233 | 234 | ## 0.4.5 235 | 236 | * fix: 修复类型 237 | 238 | ## 0.4.4 239 | 240 | * fix: 修复类型 241 | 242 | ## 0.4.3 243 | 244 | * fix: 导出全部类型 245 | 246 | ## 0.4.2 247 | 248 | 修复说明文档错误。 249 | 250 | ## 0.4.1 251 | 252 | * fix: 修复类型 253 | 254 | ## 0.4.0 255 | 256 | * feat: 迁移到 TypeScript 257 | * feat: 增加导出 258 | * perf: 移除多余的向后兼容代码 259 | * fix: 修复构建 260 | * test: 修复测试 261 | 262 | ## 0.3.2 263 | 264 | * fix: 修复类型 265 | 266 | ## 0.3.1 267 | 268 | * fix: 修复方法没有正确挂载的问题 269 | * fix: 修复错误解构的问题 270 | 271 | ## 0.3.0 272 | 273 | 完全重写 274 | 275 | ## 0.2.1 276 | 277 | * fix: 修复类型定义 278 | 279 | ## 0.2.0 280 | 281 | * feat: `request` 支持使用字符串作为第一个参数 282 | * feat: `useRequest` 支持使用字符串作为第一个参数 283 | * fix: 修复类型定义 284 | * fix: 修复导出 285 | 286 | ## 0.1.1 287 | 288 | * fix: 修复 `request` 配置错误的问题 289 | 290 | ## 0.1.0 291 | 292 | * feat: 提供 `request` 293 | * feat: 提供 `useRequest` 294 | * feat: 提供 `requestAdapter` 295 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present uni-helper 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 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "files": { 4 | "ignore": [ 5 | "**/node_modules", 6 | "**/.git", 7 | "**/.DS_Store", 8 | "**/dist", 9 | "**/cache", 10 | "./playground", 11 | "./**/package.json", 12 | "./lerna.json", 13 | "./.vscode", 14 | "./coverage" 15 | ] 16 | }, 17 | "organizeImports": { 18 | "enabled": true 19 | }, 20 | "linter": { 21 | "enabled": true, 22 | "rules": { 23 | "recommended": true, 24 | "suspicious": { 25 | "noExplicitAny": "off" 26 | }, 27 | "complexity": { 28 | "noBannedTypes": "off" 29 | } 30 | } 31 | }, 32 | "formatter": { 33 | "enabled": true, 34 | "indentStyle": "space" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /commitlint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: [ 3 | "@commitlint/config-conventional", 4 | "@commitlint/config-pnpm-scopes", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitepress"; 2 | 3 | export default defineConfig({ 4 | title: "Uni Network", 5 | description: "为 uni-app 打造的基于 Promise 的 HTTP 客户端。", 6 | head: [ 7 | // icon 8 | ["link", { rel: "icon", type: "image/png", href: "/logo.png" }], 9 | // Open Graph 10 | ["meta", { name: "og:type", content: "website" }], 11 | ["meta", { name: "og:locale", content: "zh-cn" }], 12 | ["meta", { name: "og:site_name", content: "Uni Network" }], 13 | [ 14 | "meta", 15 | { 16 | name: "og:image", 17 | content: 18 | "https://github.com/uni-helper/website/raw/main/.github/assets/uni-helper-banner.png", 19 | }, 20 | ], 21 | // Google Analytics 22 | [ 23 | "script", 24 | { 25 | async: "", 26 | src: "https://www.googletagmanager.com/gtag/js?id=G-7L81RK6W5F", 27 | }, 28 | ], 29 | [ 30 | "script", 31 | {}, 32 | `window.dataLayer = window.dataLayer || []; 33 | function gtag(){dataLayer.push(arguments);} 34 | gtag('js', new Date()); 35 | gtag('config', 'G-7L81RK6W5F');`, 36 | ], 37 | ], 38 | themeConfig: { 39 | logo: { src: "/logo.png", width: 24, height: 24 }, 40 | nav: [ 41 | { text: "指南", link: "/guide/introduction" }, 42 | { 43 | text: "更新日志", 44 | link: "https://github.com/uni-helper/uni-network/tree/main/CHANGELOG.md", 45 | }, 46 | ], 47 | sidebar: [ 48 | { 49 | text: "指南", 50 | items: [ 51 | { text: "介绍", link: "/guide/introduction" }, 52 | { text: "起步", link: "/guide/installation" }, 53 | { text: "基本用例", link: "/guide/example" }, 54 | ], 55 | }, 56 | { 57 | text: "API", 58 | items: [ 59 | { text: "API", link: "/api/introduction" }, 60 | { text: "实例", link: "/api/instance" }, 61 | { text: "请求配置", link: "/api/request-config" }, 62 | { text: "响应结构", link: "/api/response-schema" }, 63 | { text: "默认配置", link: "/api/config-defaults" }, 64 | ], 65 | }, 66 | { 67 | text: "进阶", 68 | items: [ 69 | { text: "拦截器", link: "/advanced/interceptors" }, 70 | { text: "错误处理", link: "/advanced/handling-errors" }, 71 | { text: "取消请求", link: "/advanced/cancellation" }, 72 | { text: "TypeScript 支持", link: "/advanced/typescript-support" }, 73 | { text: "高级功能", link: "/advanced/enhancements" }, 74 | { text: "组合式函数", link: "/advanced/composition-api" }, 75 | ], 76 | }, 77 | { 78 | text: "其它", 79 | items: [ 80 | { text: "构建与环境支持", link: "/other/build" }, 81 | { text: "比较", link: "/other/comparison" }, 82 | { text: "致谢", link: "/other/thank" }, 83 | ], 84 | }, 85 | ], 86 | editLink: { 87 | pattern: "https://github.com/uni-helper/uni-network/edit/main/docs/:path", 88 | }, 89 | lastUpdated: {}, 90 | socialLinks: [ 91 | { icon: "github", link: "https://github.com/uni-helper/uni-network" }, 92 | ], 93 | search: { 94 | provider: "local", 95 | }, 96 | }, 97 | }); 98 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Colors 3 | * -------------------------------------------------------------------------- */ 4 | :root { 5 | --vp-c-brand-1: hsl(128, 56%, 38%); 6 | --vp-c-brand-2: hsl(128, 56%, 55%); 7 | --vp-c-brand-3: hsl(128, 56%, 45%); 8 | --vp-c-brand-soft: rgba(98, 133, 208, 0.16); 9 | } 10 | 11 | /** 12 | * Component: Home 13 | * -------------------------------------------------------------------------- */ 14 | 15 | :root { 16 | --vp-home-hero-name-color: transparent; 17 | --vp-home-hero-name-background: -webkit-linear-gradient( 18 | 120deg, 19 | hsl(128, 56%, 38%) 30%, 20 | hsl(128, 56%, 60%) 21 | ); 22 | --vp-home-hero-image-background-image: linear-gradient( 23 | 120deg, 24 | hsl(100, 56%, 45%) 30%, 25 | hsl(120, 56%, 38%) 26 | ); 27 | 28 | --vp-home-hero-image-filter: blur(40px); 29 | } 30 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from "vitepress"; 2 | import DefaultTheme from "vitepress/theme"; 3 | import "./custom.css"; 4 | 5 | const theme: Theme = { 6 | extends: DefaultTheme, 7 | }; 8 | 9 | export default theme; 10 | -------------------------------------------------------------------------------- /docs/advanced/cancellation.md: -------------------------------------------------------------------------------- 1 | # 取消请求 2 | 3 | ## AbortController 4 | 5 | 支持使用 [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) 取消请求。要使用 AbortController,请使用 Polyfill。 6 | 7 | ### abortcontroller-polyfill 8 | 9 | ::: code-group 10 | 11 | ```sh [npm] 12 | npm install abortcontroller-polyfill@^1.7.5 13 | ``` 14 | 15 | ```sh [yarn] 16 | yarn add abortcontroller-polyfill@^1.7.5 17 | ``` 18 | 19 | ````sh [pnpm] 20 | pnpm add abortcontroller-polyfill@^1.7.5 21 | 22 | ::: 23 | 24 | 在 `App.vue` 中仅可能早地导入,后续可全局使用。 25 | 26 | ```vue 27 | 31 | ``` 32 | 33 | ### abort-controller 34 | 35 | ::: code-group 36 | 37 | ```sh [npm] 38 | npm install abort-controller@^3.0.0 39 | ```` 40 | 41 | ```sh [yarn] 42 | yarn add abort-controller@^3.0.0 43 | ``` 44 | 45 | ```sh [pnpm] 46 | pnpm add abort-controller@^3.0.0 47 | ``` 48 | 49 | ::: 50 | 51 | 必须导入后使用,不可全局使用。 52 | 53 | ```typescript 54 | import { un } from "@uni-helper/uni-network"; 55 | // 必须导入后使用 56 | import AbortController from "abort-controller/dist/abort-controller"; 57 | // ❌ 错误做法 1 58 | // import AbortController from 'abort-controller'; 59 | // ❌ 错误做法 2 60 | // import 'abort-controller/polyfill'; 61 | 62 | const controller = new AbortController(); 63 | 64 | un.get("/foo/bar", { 65 | signal: controller.signal, 66 | }).then(function (response) { 67 | //... 68 | }); 69 | // 取消请求 70 | controller.abort(); 71 | ``` 72 | 73 | ## CancelToken 74 | 75 | 你也可以使用 `CancelToken` 来取消请求。 76 | 77 | ```typescript 78 | import { un } from "@uni-helper/uni-network"; 79 | 80 | const CancelToken = un.CancelToken; 81 | const source = CancelToken.source(); 82 | 83 | un.get("/user/12345", { 84 | cancelToken: source.token, 85 | }).catch(function (error) { 86 | if (un.isCancel(error)) { 87 | console.log("Request canceled", error.message); 88 | } else { 89 | // 处理错误 90 | } 91 | }); 92 | 93 | un.post( 94 | "/user/12345", 95 | { 96 | name: "new name", 97 | }, 98 | { 99 | cancelToken: source.token, 100 | } 101 | ); 102 | // 取消请求(信息是可选的) 103 | source.cancel("Operation canceled by the user."); 104 | ``` 105 | 106 | 你也可以通过向 `CancelToken` 构造函数传递一个执行函数来创建一个 `CancelToken` 实例。 107 | 108 | ```typescript 109 | import { un } from "@uni-helper/uni-network"; 110 | 111 | const CancelToken = un.CancelToken; 112 | let cancel; 113 | 114 | un.get("/user/12345", { 115 | cancelToken: new CancelToken(function executor(c) { 116 | cancel = c; 117 | }), 118 | }); 119 | 120 | // 取消请求 121 | cancel(); 122 | ``` 123 | 124 | ::: tip 取消请求的数量 125 | 126 | 你可以用同一个 `CancelToken` / `AbortController` 取消几个请求。 127 | 128 | ::: 129 | 130 | ::: tip 发起请求时取消请求 131 | 132 | 如果在发起请求的时候已经取消请求,那么该请求就会被立即取消,不会真正发起请求。 133 | 134 | ::: 135 | -------------------------------------------------------------------------------- /docs/advanced/composition-api.md: -------------------------------------------------------------------------------- 1 | # 组合式函数 2 | 3 | 如果你还不了解组合式函数,请先阅读 [组合式 API 常见问答](https://cn.vuejs.org/guide/extras/composition-api-faq.html) 和 [组合式函数](https://cn.vuejs.org/guide/reusability/composables.html)。 4 | 5 | 我们使用 [vue-demi](https://github.com/vueuse/vue-demi) 和 [vue-use](https://vueuse.org/) 来同时支持 `vue2` 和 `vue3`。请先阅读它们的使用说明。 6 | 7 | - npm 8 | 9 | ```shell 10 | npm install @vueuse/core@^9.13.0 11 | ``` 12 | 13 | - yarn 14 | 15 | ```shell 16 | yarn add @vueuse/core@^9.13.0 17 | ``` 18 | 19 | - pnpm 20 | 21 | ```shell 22 | pnpm add @vueuse/core@^9.13.0 23 | ``` 24 | 25 | 如果你希望使用 `@vueuse/core>=10`,请查看 [dcloudio/uni-app#4604](https://github.com/dcloudio/uni-app/issues/4604) 内提供的解决方案。 26 | 27 | 从 `@uni-helper/uni-network/composables` 中导入组合式函数后即可使用。 28 | 29 | ```typescript 30 | import { useUn } from "@uni-helper/uni-network/composables"; 31 | ``` 32 | 33 | `useUn` 的用法和 [useAxios](https://vueuse.org/integrations/useaxios/) 几乎完全一致。这里不再赘述。 34 | -------------------------------------------------------------------------------- /docs/advanced/enhancements.md: -------------------------------------------------------------------------------- 1 | # 高级功能 2 | 3 | ::: tip WIP 4 | 5 | 该部分目前较为简陋。欢迎 PR 贡献!🫡 6 | 7 | ::: 8 | 9 | 对于缓存、去重、重试的高级功能,建议结合 [@tanstack/query](https://tanstack.com/query/)、[swrv](https://docs-swrv.netlify.app/)、[vue-request](https://www.attojs.com/)、[alova](https://alova.js.org/zh-CN/) 等库使用。 10 | 11 | 如果你不希望引入过多的库导致占用体积过多,你也可以参考以下内容以实现部分高级功能。 12 | 13 | ## 缓存 14 | 15 | 请参考 [Axios 如何缓存请求数据](https://juejin.cn/post/6974902702400602148)。 16 | 17 | ## 去重 18 | 19 | 请参考 [Axios 如何取消重复请求](https://juejin.cn/post/6955610207036801031) 和 [Axios 如何取消重复请求?取消重复请求方法有哪几种?](https://apifox.com/apiskills/axios-repeated-request/)。 20 | 21 | ## 重试 22 | 23 | 请参考 [Axios 如何实现请求重试?](https://juejin.cn/post/6973812686584807432)。 24 | 25 | ## 响应失败不抛出错误 26 | 27 | 在某些情况下,你可能不希望响应失败抛出错误,这时候可以使用响应拦截器来处理。 28 | 29 | ```typescript 30 | import { un } from "@uni-helper/uni-network"; 31 | 32 | // 添加响应拦截器 33 | un.interceptors.response.use( 34 | (response) => response, 35 | // 直接返回错误,不再需要使用 catch 来捕获 36 | // 需要注意返回值可能是 UnError 类型 37 | (error) => error 38 | ); 39 | ``` 40 | 41 | ## 无感刷新登录态 42 | 43 | 在某些情况下,你可能希望无感刷新登录态,避免当前登录态过期后用户手动登录。 44 | 45 | 如果你有一个可以使用过期登录态换取新鲜登录态的接口,请参考 [uni-ajax - FAQ - 无感刷新 Token](https://uniajax.ponjs.com/guide/question#%E6%97%A0%E6%84%9F%E5%88%B7%E6%96%B0-token)。 46 | 该部分代码实现略经修改也适用于使用双登录态的认证系统。 47 | 48 | 如果你正在使用一个使用双登录态的认证系统,请参考 [项目中前端如何实现无感刷新 token!](https://juejin.cn/post/7254572706536734781) 和 [基于 Axios 封装一个完美的双 token 无感刷新](https://juejin.cn/post/7271139265442021391)。 49 | 50 | ## 全局请求加载 51 | 52 | 请参考 [uni-ajax - FAQ - 配置全局请求加载](https://uniajax.ponjs.com/guide/question#%E9%85%8D%E7%BD%AE%E5%85%A8%E5%B1%80%E8%AF%B7%E6%B1%82%E5%8A%A0%E8%BD%BD)。这类做法不适用于局部加载展示。 53 | -------------------------------------------------------------------------------- /docs/advanced/handling-errors.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | 默认把每一个返回的状态代码不在 `2xx` 范围内的响应视为错误。 4 | 5 | ```typescript 6 | import { un } from "@uni-helper/uni-network"; 7 | 8 | un.get("/user/12345").catch((error) => { 9 | if (error.response) { 10 | // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围 11 | console.log(error.response.data); 12 | console.log(error.response.status); 13 | console.log(error.response.headers); 14 | } else if (error.task) { 15 | // 请求已经成功发起,但没有收到响应 16 | // `error.task` 是 task 实例 17 | console.log(error.task); 18 | } else { 19 | // 发送请求时出了点问题 20 | console.log("Error", error.message); 21 | } 22 | console.log(error.config); 23 | }); 24 | ``` 25 | 26 | 使用 `validateStatus` 配置选项,可以自定义抛出错误的 HTTP code。 27 | 28 | ```typescript 29 | import { un } from "@uni-helper/uni-network"; 30 | 31 | un.get("/user/12345", { 32 | validateStatus: (status) => { 33 | return status < 500; // 处理状态码小于 500 的情况 34 | }, 35 | }); 36 | ``` 37 | 38 | 如果你追求语义化,可以使用导出的和挂载的状态码、[statuses-es](https://github.com/esm-ts/statuses-es)、[http-status-codes](https://github.com/prettymuchbryce/http-status-codes) 或 [node-http-status](https://github.com/adaltas/node-http-status)。 39 | 40 | ```typescript 41 | import { un, HttpStatusCode } from "@uni-helper/uni-network"; 42 | 43 | un.get("/user/12345", { 44 | validateStatus: (status) => { 45 | return status < HttpStatusCode.InternalServerError; // 处理状态码小于 500 的情况 46 | // return status < un.HttpStatusCode.InternalServerError; // 也可以使用挂载在 un 上的状态码 47 | }, 48 | }); 49 | ``` 50 | 51 | 使用 `toJSON` 可以获取更多关于 HTTP 错误的信息。 52 | 53 | ```typescript 54 | un.get("/user/12345").catch((error) => { 55 | console.log(error.toJSON()); 56 | }); 57 | ``` 58 | 59 | 如果需要针对 `UnError` 和非 `UnError` 做处理,可以使用导出的 `isUnError` 方法判断。 60 | 61 | ```typescript 62 | import { un, isUnError } from "@uni-helper/uni-network"; 63 | 64 | un.get("/user/12345").catch((error) => { 65 | if (isUnError(error)) { 66 | /* ... */ 67 | } else { 68 | /* ... */ 69 | } 70 | }); 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/advanced/interceptors.md: -------------------------------------------------------------------------------- 1 | # 拦截器 2 | 3 | 在请求或响应被 `then` 或 `catch` 处理前拦截它们。 4 | 5 | ## 添加拦截器 6 | 7 | 可以全局添加请求或响应的拦截器。 8 | 9 | ```typescript 10 | import { un } from "@uni-helper/uni-network"; 11 | 12 | // 添加请求拦截器 13 | un.interceptors.request.use( 14 | function (config) { 15 | // 在发送请求之前做些什么 16 | return config; 17 | }, 18 | function (error) { 19 | // 对请求错误做些什么 20 | return Promise.reject(error); 21 | } 22 | ); 23 | 24 | // 添加响应拦截器 25 | un.interceptors.response.use( 26 | function (response) { 27 | // 2xx 范围内的状态码都会触发该函数 28 | // 对响应数据做点什么 29 | return response; 30 | }, 31 | function (error) { 32 | // 超出 2xx 范围的状态码都会触发该函数 33 | // 对响应错误做点什么 34 | return Promise.reject(error); 35 | } 36 | ); 37 | ``` 38 | 39 | 也可以给自定义实例添加请求或响应的拦截器。 40 | 41 | ```typescript 42 | import { un } from "@uni-helper/uni-network"; 43 | 44 | // 创建实例 45 | const instance = un.create(); 46 | 47 | // 添加请求拦截器 48 | instance.interceptors.request.use(() => { 49 | /* ... */ 50 | }); 51 | 52 | // 添加响应拦截器 53 | instance.interceptors.response.use(() => { 54 | /* ... */ 55 | }); 56 | ``` 57 | 58 | ## 移除拦截器 59 | 60 | 可以移除单个请求或响应的拦截器。 61 | 62 | ```typescript 63 | import { un } from "@uni-helper/uni-network"; 64 | 65 | // 添加请求拦截器 66 | const requestInterceptor = un.interceptors.request.use(() => { 67 | /* ... */ 68 | }); 69 | // 移除请求拦截器 70 | un.interceptors.request.eject(requestInterceptor); 71 | 72 | // 添加响应拦截器 73 | const responseInterceptor = un.interceptors.response.use(() => { 74 | /* ... */ 75 | }); 76 | // 移除响应拦截器 77 | un.interceptors.response.eject(responseInterceptor); 78 | ``` 79 | 80 | 也可以移除所有请求或响应的拦截器。 81 | 82 | ```typescript 83 | import { un } from "@uni-helper/uni-network"; 84 | 85 | // 创建实例 86 | const instance = un.create(); 87 | 88 | // 添加请求拦截器 89 | instance.interceptors.request.use(() => { 90 | /* ... */ 91 | }); 92 | // 移除所有请求拦截器 93 | instance.interceptors.request.clear(); 94 | 95 | // 添加响应拦截器 96 | instance.interceptors.response.use(() => { 97 | /* ... */ 98 | }); 99 | // 移除所有响应拦截器 100 | instance.interceptors.response.clear(); 101 | ``` 102 | 103 | ## 拦截器选项 104 | 105 | 当你添加请求拦截器时,`@uni-helper/uni-network` 默认认为它们是异步的。当主线程被阻塞时,这可能会导致 `@uni-helper/uni-network` 请求的执行延迟(底层为拦截器创建了一个 `Promise`,你的请求被放在了调用栈的底部)。 106 | 107 | 如果你的请求拦截器是同步的,你可以在选项对象中添加一个标志,告诉 `@uni-helper/uni-network` 同步运行代码,避免请求执行中的任何延迟。 108 | 109 | ```typescript 110 | import { un } from "@uni-helper/uni-network"; 111 | 112 | un.interceptors.request.use( 113 | (config) => { 114 | config.headers.test = "I am only a header!"; 115 | return config; 116 | }, 117 | null, 118 | { synchronous: true } 119 | ); 120 | ``` 121 | 122 | 如果你想根据运行时检查来执行某个拦截器,你可以在 `options` 对象中设置 `runWhen` 函数。**当且仅当** `runWhen` 的返回值为 `false` 时,拦截器不会被执行。该函数将和 `config` 对象一起被调用(别忘了,你也可以绑定你自己的参数)。当你有一个只需要在特定时间运行的异步请求拦截器时,这可能会很方便。 123 | 124 | ```typescript 125 | import { un } from "@uni-helper/uni-network"; 126 | 127 | const onGetCall = (config) => config.method.toUpperCase() === "GET"; 128 | un.interceptors.request.use( 129 | (config) => { 130 | config.headers.test = "special get headers"; 131 | return config; 132 | }, 133 | null, 134 | { runWhen: onGetCall } 135 | ); 136 | ``` 137 | 138 | ## 多个拦截器 139 | 140 | 假设你添加了多个响应拦截器,并且响应是 `fulfilled` 状态时: 141 | 142 | - 按照添加的顺序执行每个拦截器 143 | - 只返回最后一个拦截器的结果 144 | - 每个拦截器都会收到其前一个拦截器的结果 145 | - 当 `fulfilled` 拦截器抛出时 146 | - 后面的 `fulfilled` 拦截器不会被调用 147 | - 后面的 `rejection` 拦截器会被调用 148 | - 一旦被捕获,后面的另一个 `fulfilled` 拦截器会被再次调用(就像在一个 `Promise` 链中一样) 149 | -------------------------------------------------------------------------------- /docs/advanced/typescript-support.md: -------------------------------------------------------------------------------- 1 | # TypeScript 支持 2 | 3 | `@uni-helper/uni-network` 使用 TypeScript 编写,你可以享受到完整的 TypeScript 支持。 4 | 5 | 调用 API 时得不到响应数据和请求数据的类型是最常见的类型问题之一。 6 | 7 | ```typescript 8 | import { un } from "@uni-helper/uni-network"; 9 | 10 | // response 的类型是 UnResponse 11 | // response.data 的类型是 UnData,你希望是 Record 12 | const response = await un({ 13 | method: "post", 14 | url: "/user/12345", 15 | // 以下 data 的类型是 UnData,你希望是 Record 16 | data: { 17 | firstName: "Fred", 18 | lastName: "Flintstone", 19 | }, 20 | }); 21 | ``` 22 | 23 | 这可以通过设置两个泛型类型来解决,两个泛型类型依次分别对应响应数据和请求数据的类型。 24 | 25 | ```typescript 26 | import { un } from "@uni-helper/uni-network"; 27 | 28 | // response 的类型是 UnResponse, Record> 29 | // response.data 的类型是 Record 30 | const response = await un< 31 | Record, // 对应 response.data 类型 32 | Record // 对应传参中 data 类型 33 | >({ 34 | method: "post", 35 | url: "/user/12345", 36 | // 以下 data 的类型是 Record 37 | data: { 38 | firstName: "Fred", 39 | lastName: "Flintstone", 40 | }, 41 | }); 42 | ``` 43 | 44 | 而另一个常见的类型问题是,使用响应拦截器后响应类型不正确。 45 | 46 | ```typescript 47 | import { un } from "@uni-helper/uni-network"; 48 | 49 | // 添加响应拦截器直接返回 response.data 50 | un.interceptors.response.use((response) => response.data as any); 51 | 52 | // response 的类型是 UnResponse,你希望是 Record 53 | // response.data 的类型是 UnData,你希望是 Record 54 | const response = await un({ 55 | method: "post", 56 | url: "/user/12345", 57 | // 以下 data 的类型是 UnData,你希望是 Record 58 | data: { 59 | firstName: "Fred", 60 | lastName: "Flintstone", 61 | }, 62 | }); 63 | ``` 64 | 65 | 这需要设置三个泛型类型来解决,三个泛型类型依次分别对应响应数据、请求数据、响应的类型。 66 | 67 | ```typescript 68 | import { un } from "@uni-helper/uni-network"; 69 | 70 | // 添加响应拦截器直接返回 response.data 71 | un.interceptors.response.use((response) => response.data as any); 72 | 73 | // response 的类型是 Record 74 | // response.data 的类型是 Record 75 | const response = await un< 76 | Record, // 对应 response.data 类型 77 | Record, // 对应传参中 data 类型 78 | Record // 对应 response 类型 79 | >({ 80 | method: "post", 81 | url: "/user/12345", 82 | // 以下 data 的类型是 Record 83 | data: { 84 | firstName: "Fred", 85 | lastName: "Flintstone", 86 | }, 87 | }); 88 | ``` 89 | 90 | ::: tip 范型类型设计 91 | 92 | 如果你只想修改响应的类型,而不修改其它类型,你仍然需要书写三个泛型类型。这和 `axios` 的泛型类型设计不同,因为 `uni-app` 对数据类型有更严格的要求。同理,`as any` 的类型断言也是出于同样的原因。 93 | 94 | ::: 95 | 96 | 你可以从 `@uni-helper/uni-network` 中导入 `UnData` 以保持前两个泛型类型的默认值。 97 | 98 | ```typescript 99 | import { un, type UnData } from "@uni-helper/uni-network"; 100 | 101 | // 添加响应拦截器直接返回 response.data 102 | un.interceptors.response.use((response) => response.data as any); 103 | 104 | // response 的类型是 Record 105 | // response.data 的类型是 UnData 106 | const response = await un< 107 | UnData, // 对应 response.data 类型 108 | UnData, // 对应传参中 data 类型 109 | Record // 对应 response 类型 110 | >({ 111 | method: "post", 112 | url: "/user/12345", 113 | // 以下 data 的类型是 UnData 114 | data: { 115 | firstName: "Fred", 116 | lastName: "Flintstone", 117 | }, 118 | }); 119 | ``` 120 | -------------------------------------------------------------------------------- /docs/api/config-defaults.md: -------------------------------------------------------------------------------- 1 | # 默认配置 2 | 3 | 你可以指定默认配置,它将作用于每个请求。 4 | 5 | ## 全局配置默认值 6 | 7 | ```typescript 8 | import { un } from "@uni-helper/uni-network"; 9 | 10 | un.defaults.baseUrl = "https://api.example.com"; 11 | ``` 12 | 13 | ## 自定义实例默认值 14 | 15 | ```typescript 16 | import { un } from "@uni-helper/uni-network"; 17 | 18 | // 创建实例时配置默认值 19 | const instance = un.create({ 20 | baseUrl: "https://api.example.com", 21 | }); 22 | 23 | // 创建实例后修改默认值 24 | instance.defaults.baseUrl = "https://api.another-example.com"; 25 | ``` 26 | 27 | ## 配置的优先级 28 | 29 | 配置将会按优先级进行合并。优先级从低到高是内置的默认值、实例的 `defaults` 配置、请求的 `config`。下面是一个例子。 30 | 31 | ```typescript 32 | // 使用库提供的默认配置创建实例 33 | // 此时超时配置的默认值是实际调用的 API 的默认值 34 | const instance = un.create(); 35 | 36 | // 重写库的超时默认值 37 | // 现在,所有使用此实例的请求都将等待 2.5 秒,然后才会超时 38 | instance.defaults.timeout = 2500; 39 | 40 | // 重写此请求的超时时间,因为该请求需要很长时间 41 | instance.get("/longRequest", { 42 | timeout: 5000, 43 | }); 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/api/instance.md: -------------------------------------------------------------------------------- 1 | # 实例 2 | 3 | 可以使用自定义配置创建一个实例。 4 | 5 | ## un.create([config]) 6 | 7 | ```typescript 8 | const instance = un.create({ 9 | baseUrl: "https://some-domain.com/api/", 10 | timeout: 1000, 11 | headers: { "X-Custom-Header": "foobar" }, 12 | }); 13 | ``` 14 | 15 | ```typescript 16 | instance.request({ 17 | method: "POST", 18 | url: "/user/12345", 19 | data: { 20 | firstName: "Fred", 21 | lastName: "Flintstone", 22 | }, 23 | }); 24 | ``` 25 | 26 | ## 实例方法 27 | 28 | 以下是可用的实例方法。指定的配置将与实例的配置合并。 29 | 30 | - `un.request(config)` 31 | - `un.download(config)` 32 | - `un.upload(config)` 33 | - `un.get(url[, config])` 34 | - `un.delete(url[, config])` 35 | - `un.head(url[, config])` 36 | - `un.options(url[, config])` 37 | - `un.trace(url[, config]])` 38 | - `un.connect(url[, config]])` 39 | - `un.post(url[, data[, config]])` 40 | - `un.put(url[, data[, config]])` 41 | - `un.patch(url[, data[, config]])` 42 | - `un.getUri([config])` 43 | -------------------------------------------------------------------------------- /docs/api/introduction.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | 可以直接向导入的 `un` 传递相关配置来发起请求。 4 | 5 | ## un(config) 6 | 7 | ```typescript 8 | import { un } from "@uni-helper/uni-network"; 9 | 10 | // 发起 POST 请求 11 | un({ 12 | method: "POST", 13 | url: "/user/12345", 14 | data: { 15 | firstName: "Fred", 16 | lastName: "Flintstone", 17 | }, 18 | }); 19 | ``` 20 | 21 | ## un(url[, config]) 22 | 23 | ```typescript 24 | import { un } from "@uni-helper/uni-network"; 25 | 26 | // 发起 GET 请求(默认请求方法) 27 | un("/user/12345"); 28 | ``` 29 | 30 | ## 请求别名 31 | 32 | 为了方便起见,已经为所有支持的请求方法提供了别名。在使用别名方法时,`url`、`method`、`data` 不需要在 `config` 中指定。如果同时指定,`config` 中指定的字段会被覆盖掉。 33 | 34 | - `un.request(config)` 35 | - `un.download(config)` 36 | - `un.upload(config)` 37 | - `un.get(url[, config])` 38 | - `un.delete(url[, config])` 39 | - `un.head(url[, config])` 40 | - `un.options(url[, config])` 41 | - `un.trace(url[, config])` 42 | - `un.connect(url[, config])` 43 | - `un.post(url[, data[, config]])` 44 | - `un.put(url[, data[, config]])` 45 | - `un.patch(url[, data[, config]])` 46 | -------------------------------------------------------------------------------- /docs/api/request-config.md: -------------------------------------------------------------------------------- 1 | # 请求配置 2 | 3 | 以下是创建请求时可以用的配置选项。只有 `url` 是必需的。如果没有指定 `method` 且没有指定 `adapter`,请求将默认使用 `GET` 方法。 4 | 5 | ```typescript 6 | { 7 | // `url` 是用于请求的服务器 URL 8 | url: "/user", 9 | 10 | // `baseUrl` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL 且选项 `allowAbsoluteUrls` 为 true 11 | // 设置一个 `baseUrl` 便于为实例方法传递相对 URL 12 | baseUrl: "https://some-domain.com/api/", 13 | 14 | // 决定是否允许绝对 URL 覆盖配置的 `baseUrl` 15 | // 当设置为 true(默认)时,绝对值的 `url` 会覆盖 `baseUrl` 16 | // 当设置为 false 时,绝对值的 `url` 会始终被 `baseUrl` 前置 17 | allowAbsoluteUrls?: boolean; 18 | 19 | // 自定义请求头 20 | // 不能设置 Referer 21 | headers: { 22 | "content-type": "application/json", 23 | }, 24 | 25 | // `params` 是与请求一起发送的 URL 参数 26 | // 必须是一个普通对象或一个 URLSearchParams 对象 27 | // 要使用 URLSearchParams 对象,请使用 core-js 提供的 polyfill 28 | // 可参考构建与环境支持部分的说明或该仓库提供的 playground 29 | params: { 30 | ID: "12345", 31 | }, 32 | 33 | // `paramsSerializer` 是可选方法,主要用于序列化 `params` 34 | // 默认使用 [fast-querystring](https://github.com/anonrig/fast-querystring) 序列化,需要自行处理嵌套值 35 | // [picoquery](https://github.com/43081j/picoquery) 在 fast-querystring 基础上支持嵌套值、增加可配置性 36 | // [qs](https://github.com/ljharb/qs) 包含大量无用的兼容代码,占用额外体积,如无必要不建议使用 37 | // [qs](https://github.com/ljharb/qs) v6.10.0 引入了 `get-intrinsic` 导致结合微信小程序和微信小程序插件使用时出现报错,可使用 v6.9.7 38 | // [query-string](https://github.com/sindresorhus/query-string) 体积性能都较好,支持完善 39 | // [query-string](https://github.com/sindresorhus/query-string) 基于 [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component),它使用了部分小程序(如支付宝小程序)不支持的语法(可选的 catch 参数,Optional catch Binding),需自行修改处理 40 | paramsSerializer: (params) => { 41 | /* 返回一个字符串 */ 42 | }, 43 | 44 | // `timeout` 指定请求超时的毫秒数 45 | // 如果请求时间超过 `timeout` 的值,则请求会被中断 46 | // 要设置永不超时,可以将其设置为 Number.POSITIVE_INFINITY 47 | // 默认值是实际调用的 API 的默认值,见 https://uniapp.dcloud.net.cn/collocation/manifest.html#networktimeout 48 | timeout: 1000, 49 | 50 | // `adapter` 允许自定义处理请求 51 | // 可以指定为 'request'、'upload' 和 'download' 三者之一 52 | // 也可以指定为一个方法,返回一个 Promise 并提供一个有效的响应 53 | // 如果你正在使用 un.request、un.download、un.upload、un.get 等别名方法,则无需再指定该键的值 54 | // 默认值是 'request' 55 | adapter: (config) => { 56 | /* ... */ 57 | }, 58 | 59 | // `validateStatus` 定义了对于给定的 HTTP 状态码该 resolve 还是 reject 60 | // 如果 `validateStatus` 返回 `true`、`null` 或 `undefined` 61 | // 则 promise 将会被 resolve,否则会被 reject 62 | validateStatus: function (status) { 63 | return status >= 200 && status < 300; // 默认值 64 | }, 65 | 66 | // 用于取消请求 67 | // 可参考取消请求部分的说明 68 | signal: new AbortController().signal, 69 | 70 | // 用于取消请求 71 | // 可参考取消请求部分的说明 72 | cancelToken: new CancelToken(function (cancel) { 73 | /* ... */ 74 | }), 75 | 76 | // 监听 HTTP Response Header 事件 77 | // 会比请求完成事件更早 78 | onHeadersReceived: (result) => { 79 | /* ... */ 80 | }, 81 | 82 | // request 使用 83 | // 创建请求时使用的方法 84 | // 默认值是 'GET' 85 | method: "GET", 86 | 87 | // request 使用 88 | // `data` 是作为请求体被发送的数据 89 | // 必须是以下类型之一:string、object、ArrayBuffer、ArrayBufferView、URLSearchParams 90 | data: { 91 | firstName: "Fred", 92 | }, 93 | // 这也是可行的 94 | // data: 'Country=Brasil&City=Belo Horizonte', 95 | 96 | // request 使用 97 | // 返回的数据类型 98 | // 如果设置为 json,会尝试对返回的数据做一次 JSON.parse 99 | // 默认值是 'json' 100 | dataType: "json", 101 | 102 | // request 使用 103 | // 响应的数据类型,选项包括 'text' 和 'arraybuffer' 104 | // 默认值是 'text' 105 | responseType: "text", 106 | 107 | // request 使用 108 | // 是否开启 http2 109 | // 默认值是 false 110 | enableHttp2: false, 111 | 112 | // request 使用 113 | // 是否开启 quic 114 | // 默认值是 false 115 | enableQuic: false, 116 | 117 | // request 使用 118 | // 是否开启缓存 119 | // 默认值是 false 120 | enableCache: false, 121 | 122 | // request 使用 123 | // 是否开启 HttpDNS 服务 124 | // 默认值是 false 125 | enableHttpDNS: false, 126 | 127 | // request 使用 128 | // HttpDNS 服务商 Id 129 | httpDNSServiceId: "", 130 | 131 | // request 使用 132 | // 是否开启 transfer-encoding chunked 133 | // 默认值是 false 134 | enableChunked: false, 135 | 136 | // request 使用 137 | // 是否在 wifi 下使用移动网络发送请求 138 | // 默认值是 false 139 | forceCellularNetwork: false, 140 | 141 | // request 使用 142 | // 是否验证 ssl 证书 143 | // 默认值是 true 144 | sslVerify: true, 145 | 146 | // request 使用 147 | // 跨域请求时是否需要使用凭证 148 | // 默认值是 false 149 | withCredentials: false, 150 | 151 | // request 使用 152 | // 是否在 DNS 解析时优先使用 ipv4 153 | // 默认值是 false 154 | firstIpv4: false, 155 | 156 | // request 使用 157 | // 监听 Transfer-Encoding Chunk Received 事件 158 | // 当接收到新的 chunk 时触发 159 | onChunkReceived: (response) => { 160 | /* ... */ 161 | }, 162 | 163 | // upload 使用 164 | // 需要上传的文件列表,files 和 filePath 必填一个 165 | // 使用该参数时,filePath 和 name 无效 166 | // 不支持小程序 167 | files: [], 168 | 169 | // upload 使用 170 | // 文件类型 171 | fileType: "image", // image, video, audio 172 | 173 | // upload 使用 174 | // 文件对象 175 | file: new File(), 176 | 177 | // upload 使用 178 | // 文件路径,files 和 filePath 必填一个 179 | // 180 | // download 使用 181 | // 文件下载后存储的本地路径 182 | filePath: "/fake/path", 183 | 184 | // upload 使用 185 | // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容 186 | name: "file", 187 | 188 | // upload 使用 189 | // 一个对象,会作为 HTTP 请求中其它额外的 form data 190 | formData: Record, 191 | 192 | // download 使用 193 | // 下载进度变化时触发 194 | // 优先级 onDownloadProgress > onDownloadProgressUpdate > onProgress > onProgressUpdate 195 | onDownloadProgress: UnOnProgress, 196 | 197 | // download 使用 198 | // 下载进度变化时触发 199 | // 优先级 onDownloadProgress > onDownloadProgressUpdate > onProgress > onProgressUpdate 200 | onDownloadProgressUpdate: UnOnProgress, 201 | 202 | // upload 使用 203 | // 上传进度变化时触发 204 | // 优先级 onUploadProgress > onUploadProgressUpdate > onProgress > onProgressUpdate 205 | onUploadProgress: UnOnProgress, 206 | 207 | // upload 使用 208 | // 上传进度变化时触发 209 | // 优先级 onUploadProgress > onUploadProgressUpdate > onProgress > onProgressUpdate 210 | onUploadProgressUpdate: UnOnProgress, 211 | 212 | // upload / download 使用 213 | // 上传/下载进度变化时触发 214 | // 优先级 onUploadProgress / onDownloadProgress > onUploadProgressUpdate / onDownloadProgressUpdate > onProgress > onProgressUpdate 215 | onProgress: UnOnProgress, 216 | 217 | // upload / download 使用 218 | // 上传/下载进度变化时触发 219 | // 优先级 onUploadProgress / onDownloadProgress > onUploadProgressUpdate / onDownloadProgressUpdate > onProgress > onProgressUpdate 220 | onProgressUpdate: UnOnProgress, 221 | }; 222 | ``` 223 | -------------------------------------------------------------------------------- /docs/api/response-schema.md: -------------------------------------------------------------------------------- 1 | # 响应结构 2 | 3 | 一个请求的响应包应含以下信息。 4 | 5 | ```typescript 6 | const res = { 7 | // `errMsg` 是可选的错误信息 8 | errMsg: "", 9 | 10 | // `errno` 是可选的错误代码 11 | errno: 0, 12 | 13 | // `profile` 是可选的调试信息 14 | profile: {}, 15 | 16 | // `config` 是 `un` 请求的配置信息 17 | config: {}, 18 | 19 | // `task` 是对应的 task 信息 20 | task: {}, 21 | 22 | // `status` 来自服务器响应的 HTTP 状态码 23 | status: 200, 24 | 25 | // `statusText` 来自服务器响应的 HTTP 状态信息 26 | statusText: "OK", 27 | 28 | // `headers` 是服务器响应头 29 | // 所有的 header 名称都是小写,而且可以使用方括号语法访问 30 | // 例如: `response.headers['content-type']` 31 | headers: {}, 32 | 33 | // `data` 是由服务器提供的响应数据 34 | data: {}, 35 | 36 | // request 特有 37 | // 服务器提供的 cookies 数据 38 | cookies: [], 39 | 40 | // download 特有 41 | // 临时本地文件路径 42 | // 没传入 filePath 指定文件存储路径时会返回,下载后的文件会存储到一个临时文件 43 | tempFilePath: "", 44 | 45 | // download 特有 46 | // 用户本地文件路径 47 | // 传入 filePath 时会返回,跟传入的 filePath 一致 48 | filePath: "", 49 | }; 50 | ``` 51 | 52 | 当使用 then 时,你将接收如下响应: 53 | 54 | ```typescript 55 | un.get("/user/12345").then((response) => { 56 | console.log("errMsg", response?.errMsg); 57 | console.log("errno", response?.errno); 58 | console.log("profile", response?.profile); 59 | console.log("config", response?.config); 60 | console.log("status", response?.status); 61 | console.log("statusText", response?.statusText); 62 | console.log("headers", response?.headers); 63 | console.log("data", response?.data); 64 | console.log("cookies", response?.cookies); 65 | console.log("tmpFilePath", response?.tmpFilePath); 66 | console.log("filePath", response?.filePath); 67 | }); 68 | ``` 69 | 70 | 当使用 `catch`,或者传递一个 `rejection callback` 作为 `then` 的第二个参数时,响应可以作为 `error` 对象被使用,正如在 [错误处理](../advanced/handling-errors.md) 部分解释的那样。 71 | -------------------------------------------------------------------------------- /docs/guide/example.md: -------------------------------------------------------------------------------- 1 | # 基本用例 2 | 3 | ## GET 请求 4 | 5 | ```typescript 6 | import { un } from "@uni-helper/uni-network"; 7 | 8 | // 请求特定 ID 的用户数据 9 | un.get("/user?ID=12345") 10 | .then((response) => { 11 | // 处理响应 12 | console.log("response", response); 13 | }) 14 | .catch((error) => { 15 | // 处理错误 16 | console.log("error", error); 17 | }) 18 | .finally(() => { 19 | // 总是会执行 20 | }); 21 | 22 | // 上述请求和以下等同 23 | un.get("/user", { 24 | params: { 25 | ID: "12345", 26 | }, 27 | }) 28 | .then((response) => { 29 | console.log("response", response); 30 | }) 31 | .catch((error) => { 32 | console.log("error", error); 33 | }) 34 | .finally(() => { 35 | // 总是会执行 36 | }); 37 | ``` 38 | 39 | ::: tip 名称 40 | 41 | `un` 是 `uni` 和 `network` 的首字母缩写。如果你不习惯这个名称,你可以在导入时自行调整,比如使用 `uniNetwork`:`import { un as uniNetwork } from '@uni-helper/uni-network';`。 42 | 43 | ::: 44 | 45 | ## 使用 async/await 的 GET 请求 46 | 47 | ```typescript 48 | import { un } from "@uni-helper/uni-network"; 49 | 50 | async function getUser() { 51 | try { 52 | const response = await un.get("/user?ID=12345"); 53 | console.log(response); 54 | } catch (error) { 55 | console.error(error); 56 | } 57 | } 58 | ``` 59 | 60 | ## POST 请求 61 | 62 | ```typescript 63 | import { un } from "@uni-helper/uni-network"; 64 | 65 | un.post("/user", { 66 | firstName: "Fred", 67 | lastName: "Flintstone", 68 | }) 69 | .then(function (response) { 70 | console.log(response); 71 | }) 72 | .catch(function (error) { 73 | console.log(error); 74 | }) 75 | .finally(() => {}); 76 | ``` 77 | 78 | ## 并发请求 79 | 80 | ```typescript 81 | import { un } from "@uni-helper/uni-network"; 82 | 83 | function getUserAccount() { 84 | return un.get("/user/12345"); 85 | } 86 | 87 | function getUserPermissions() { 88 | return un.get("/user/12345/permissions"); 89 | } 90 | 91 | Promise.all([getUserAccount(), getUserPermissions()]).then((responses) => { 92 | const acct = responses[0]; 93 | const perm = responses[1]; 94 | }); 95 | ``` 96 | 97 | 基本用例应该能让你初步上手 `@uni-helper/uni-network`。你可以动手尝试一下,也可以继续往下阅读。 98 | -------------------------------------------------------------------------------- /docs/guide/installation.md: -------------------------------------------------------------------------------- 1 | # 起步 2 | 3 | `@uni-helper/uni-network` 要求你使用 `node>=18`。建议你使用 Node.js 的 LTS 版本。 4 | 5 | ::: code-group 6 | 7 | ```shell [npm] 8 | npm install @uni-helper/uni-network 9 | ``` 10 | 11 | ```shell [yarn] 12 | yarn add @uni-helper/uni-network 13 | ``` 14 | 15 | ```shell [pnpm] 16 | pnpm install @uni-helper/uni-network 17 | ``` 18 | 19 | ::: 20 | 21 | ::: tip yarn v1+ 22 | 23 | 如果你正在使用 yarn v1+,请参考 [文档](https://yarnpkg.com/configuration/yarnrc/#nodeLinker) 设置 `nodeLinker` 为 `node_modules`。 24 | 25 | ::: 26 | 27 | ::: tip pnpm 28 | 29 | 如果你正在使用 pnpm,请参考 [文档](https://pnpm.io/npmrc#shamefully-hoist) 设置 `shamefully-hoist` 为 `true`。 30 | 31 | ::: 32 | 33 | ::: tip 关于 `uni_modules` 34 | 35 | 目前不支持 `uni_modules`,也没有人力、精力和时间支持 `uni_modules`,但欢迎 PR 贡献!🫡 36 | 37 | ::: 38 | -------------------------------------------------------------------------------- /docs/guide/introduction.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | `@uni-helper/uni-network` 是一个为 [uni-app](https://uniapp.dcloud.io/) 打造的 [基于 Promise](https://javascript.info/promise-basics) 的 HTTP 客户端。 4 | 5 | `@uni-helper/uni-network` 灵感和代码绝大部分源于 `axios@0.27.2`,功能包括: 6 | 7 | - 默认请求使用 [uni.request](https://uniapp.dcloud.io/api/request/request.html) 8 | - 上传文件使用 [uni.uploadFile](https://uniapp.dcloud.io/api/request/network-file.html#uploadfile) 9 | - 下载文件使用 [uni.downloadFile](https://uniapp.dcloud.io/api/request/network-file.html#downloadfile) 10 | - 支持 [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) API 11 | - [拦截请求和响应](../advanced/interceptors.md) 12 | - [取消请求](../advanced/cancellation.md) 13 | - [TypeScript 支持](../advanced/typescript-support.md) 14 | - [组合式函数](../advanced/composition-api.md) 15 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | hero: 5 | name: 'Uni Network' 6 | tagline: 为 uni-app 打造的基于 Promise 的 HTTP 客户端。 7 | actions: 8 | - theme: brand 9 | text: 起步 → 10 | link: /guide/installation 11 | - theme: alt 12 | text: 介绍 13 | link: /guide/introduction 14 | image: 15 | src: /logo.png 16 | alt: Uni Helper 17 | 18 | features: 19 | - title: 📝 支持 Promise API 20 | details: 默认支持 Promise API,可直接使用 async/await,更加简洁明了 21 | - title: 🛠 请求响应拦截器 22 | details: 可配置请求和响应拦截器,自定义处理数据 23 | - title: 🔌 取消请求 24 | details: 可扩展请求取消,完全可控请求状态 25 | - title: 🦾 TypeScript 支持 26 | details: 基于 TypeScript 开发,默认支持 TypeScript,安全可控 27 | - title: 🧱 组合式函数 28 | details: 提供组合式函数,更加方便地使用 Uni Network 29 | - title: 📤 支持上传下载 30 | details: 基于 uni.request、uni.uploadFile、uni.downloadFile 封装,支持普通请求、上传、下载 31 | --- 32 | 33 | 34 | 62 | 63 | -------------------------------------------------------------------------------- /docs/other/build.md: -------------------------------------------------------------------------------- 1 | # 构建与环境支持 2 | 3 | 目前 `@uni-helper/uni-network` 会使用 `unbuild` 将 `uni` API 之外的部分转译到 `ES2017`(即 `ES8`)。`uni` API 需要在项目构建时由 `uni-app` 官方提供的插件处理。 4 | 5 | 如果你希望提供更好的兼容性,请参考 [这里](https://vitesse-docs.netlify.app/getting-started/deployment#%E5%85%BC%E5%AE%B9%E6%80%A7)。 6 | -------------------------------------------------------------------------------- /docs/other/comparison.md: -------------------------------------------------------------------------------- 1 | # 比较 2 | 3 | 最常见的比较就是 `axios` 和 `@uni-helper/uni-network` 的比较。 4 | 5 | `axios` 非常棒,`@uni-helper/uni-network` 的灵感也源于 `axios`,但 `axios` 存在几个相对严重的问题。 6 | 7 | - `axios` 面向浏览器和 Node.js,即使使用了 `adapter`,某些底层功能也可能会在小程序内报错。 8 | - `axios` 体积较大,会占用宝贵的小程序空间。 9 | - 如果你想要获取良好的 TypeScript,你需要修改 `axios` 大部分类型定义。 10 | 11 | 如果你因为某些原因坚持使用 `axios`,你可以查看 [@uni-helper/axios-adapter](https://github.com/uni-helper/axios-adapter) 获取 `adapter` 支持。 12 | 13 | 以下是 `@uni-helper/uni-network` 与其它一些库的比较。如果你发现这里信息已经过时,欢迎提交 ISSUE 或 PR。 14 | 15 | | | `axios` | `luch-request` | `uni-ajax` | `@uni-helper/uni-network` | 16 | | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 17 | | 基本信息 | [![npm](https://img.shields.io/npm/v/axios)](https://www.npmjs.com/package/axios) [![npm](https://img.shields.io/npm/dw/axios)](https://www.npmjs.com/package/axios) | [![npm](https://img.shields.io/npm/v/luch-request)](https://www.npmjs.com/package/luch-request) [![npm](https://img.shields.io/npm/dw/luch-request)](https://www.npmjs.com/package/luch-request) | [![npm](https://img.shields.io/npm/v/uni-ajax)](https://www.npmjs.com/package/uni-ajax) [![npm](https://img.shields.io/npm/dw/uni-ajax)](https://www.npmjs.com/package/uni-ajax) | [![npm](https://img.shields.io/npm/v/@uni-helper/uni-network)](https://www.npmjs.com/package/@uni-helper/uni-network) [![npm](https://img.shields.io/npm/dw/@uni-helper/uni-network)](https://www.npmjs.com/package/@uni-helper/uni-network) | 18 | | 开发语言 | JavaScript | JavaScript | JavaScript | TypeScript | 19 | | 类型支持 | `index.d.ts`(没有考虑 `uni-app`) | `index.d.ts`(泛型支持较差) | `index.d.ts` | 包含 | 20 | | 运行环境 | 浏览器和 `Node.js` | `uni-app` | `uni-app` | `uni-app` | 21 | | `Promise` | √ | √ | √ | √ | 22 | | `uni_modules` | × | √ | √ | × | 23 | | `npm` 包 | √ | √ | √ | √ | 24 | | 实例化 | √ | √ | √ | √ | 25 | | 请求说明 | √ | √ | √ | √ | 26 | | 请求头 headers | `AxiosHeaders` | 普通对象 | 普通对象 | 普通对象 | 27 | | 请求参数 params | `AxiosURLSearchParams` | 普通对象 | 普通对象 | 普通对象或 `URLSearchParams` 对象 | 28 | | 请求转换 `transformRequest` | √ | × | × | × | 29 | | 响应说明 | √ | × | √ | √ | 30 | | 响应转换 `transformResponse` | √ | × | × | × | 31 | | 任务说明 | ×(没有考虑 `uni-app` 任务) | × | √(只有 `requestTask` 说明) | √(只有简单说明) | 32 | | 适配器 | √(内置 `xhr` 和 `http`) | × | √ | √ | 33 | | `uni.request` | ×(自行开发,还需要覆写类型) | √ | √ | √ | 34 | | `uni.downloadFile` | ×(自行开发,还需要覆写类型) | √ | ×(自行开发,还需要覆写类型) | √ | 35 | | `uni.uploadFile` | ×(自行开发,还需要覆写类型) | √ | ×(自行开发,还需要覆写类型) | √ | 36 | | 请求拦截器 | √ | √ | √ | √ | 37 | | 响应拦截器 | √ | √ | √ | √ | 38 | | 配置说明 | √ | √ | √ | √ | 39 | | 取消请求说明 | √ | × | √ | √ | 40 | | 错误处理说明 | √ | × | √ | √ | 41 | | 测试 | 完善 | 部分 | 无 | 部分 | 42 | | 使用示例 | √ | √ | √ | √ | 43 | -------------------------------------------------------------------------------- /docs/other/thank.md: -------------------------------------------------------------------------------- 1 | # 致谢 2 | 3 | 根据字母顺序排序。 4 | 5 | - [@tanstack/query](https://tanstack.com/query/) 6 | - [alova](https://alova.js.org/zh-CN/) 7 | - [axios](https://axios-http.com/) 8 | - [luch-request](https://github.com/lei-mu/luch-request) 9 | - [swr](https://swr.vercel.app/) 10 | - [swrv](https://docs-swrv.netlify.app/) 11 | - [uni-ajax](https://uniajax.ponjs.com/) 12 | - [vue-request](https://www.attojs.com/) 13 | - [vue-use](https://vueuse.org/) 14 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "vitepress build .", 8 | "dev": "vitepress dev .", 9 | "preview": "vitepress preview ." 10 | }, 11 | "devDependencies": { 12 | "vitepress": "^1.6.3", 13 | "vue": "^3.5.13" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/public/ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-3102250747488251, DIRECT, f08c47fec0942fa0 2 | -------------------------------------------------------------------------------- /docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uni-helper/uni-network/49216dd91b8aa9e01cb534550d7a9d340d0b7259/docs/public/logo.png -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/@lerna-lite/cli/schemas/lerna-schema.json", 3 | "version": "0.21.3", 4 | "npmClient": "pnpm", 5 | "packages": [ 6 | "packages/*" 7 | ], 8 | "command": { 9 | "version": { 10 | "allowBranch": "main", 11 | "conventionalCommits": true, 12 | "changelogIncludeCommitsClientLogin": " - by @%l", 13 | "message": "chore: release %s", 14 | "remoteClient": "github" 15 | } 16 | }, 17 | "changelogPreset": "conventional-changelog-conventionalcommits", 18 | "ignoreChanges": [ 19 | "**/test/**", 20 | "**/tests/**", 21 | "**/__test__/**", 22 | "**/__tests__/**", 23 | "**/*.md" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | "*": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true", 3 | }; 4 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "docs/.vitepress/dist" 3 | command = "pnpm run docs:build" 4 | 5 | [build.environment] 6 | NODE_VERSION = "20" 7 | NODE_OPTIONS = "--max_old_space_size=4096" 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@monorepo/uni-network", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Monorepo", 6 | "keywords": [ 7 | "uni-app", 8 | "uniapp", 9 | "uni", 10 | "request", 11 | "network", 12 | "upload", 13 | "uploadFile", 14 | "download", 15 | "downloadFile" 16 | ], 17 | "homepage": "https://github.com/uni-helper/uni-network#readme", 18 | "bugs": { 19 | "url": "https://github.com/uni-helper/uni-network/issues" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/uni-helper/uni-network.git" 24 | }, 25 | "funding": "https://github.com/sponsors/modyqyw", 26 | "license": "MIT", 27 | "author": { 28 | "name": "ModyQyW", 29 | "email": "wurui-dev@foxmail.com", 30 | "url": "https://modyqyw.top" 31 | }, 32 | "sideEffects": false, 33 | "type": "module", 34 | "scripts": { 35 | "build": "rimraf packages/*/dist --glob && pnpm -r --filter=./packages/* run build && pnpm -r run build-post", 36 | "check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true", 37 | "dep-update": "taze -fw", 38 | "dev": "pnpm stub", 39 | "docs:build": "pnpm -C docs run build", 40 | "docs:dev": "pnpm -C docs run dev", 41 | "docs:preview": "pnpm -C docs run preview", 42 | "play:build:h5": "pnpm build && pnpm -C playground run build:h5", 43 | "play:build:mp-weixin": "pnpm build && pnpm -C playground run build:mp-weixin", 44 | "play:dev:h5": "pnpm dev && pnpm -C playground run dev:h5", 45 | "play:dev:mp-weixin": "pnpm dev && pnpm -C playground run dev:mp-weixin", 46 | "prepare": "is-ci || simple-git-hooks", 47 | "prepublishOnly": "pnpm run build", 48 | "release": "lerna version", 49 | "stub": "pnpm -r --filter=./packages/* --parallel run stub", 50 | "test": "vitest run", 51 | "test:coverage": "vitest run --coverage", 52 | "type-check": "tsc --noEmit", 53 | "preversion": "pnpm run check" 54 | }, 55 | "devDependencies": { 56 | "@biomejs/biome": "^1.9.4", 57 | "@commitlint/cli": "^19.8.0", 58 | "@commitlint/config-conventional": "^19.8.0", 59 | "@commitlint/config-pnpm-scopes": "^19.8.0", 60 | "@dcloudio/types": "^3.4.14", 61 | "@lerna-lite/cli": "^3.12.3", 62 | "@lerna-lite/version": "^3.12.3", 63 | "@types/node": "^22.13.10", 64 | "@vitest/coverage-v8": "^3.0.9", 65 | "conventional-changelog-conventionalcommits": "^7.0.2", 66 | "is-ci": "^4.1.0", 67 | "lint-staged": "^15.5.0", 68 | "rimraf": "^6.0.1", 69 | "simple-git-hooks": "^2.11.1", 70 | "taze": "^19.0.2", 71 | "typescript": "^5.8.2", 72 | "unbuild": "^3.5.0", 73 | "vitest": "^3.0.9" 74 | }, 75 | "packageManager": "pnpm@9.15.9", 76 | "engines": { 77 | "node": ">=18" 78 | }, 79 | "publishConfig": { 80 | "access": "public", 81 | "registry": "https://registry.npmjs.org/" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.21.3](https://github.com/uni-helper/uni-network/compare/v0.21.2...v0.21.3) (2025-05-15) 7 | 8 | ### Bug Fixes 9 | 10 | * useUn narrow type of data when initialValue is provided, https://github.com/vueuse/vueuse/pull/4419/files ([33ef53b](https://github.com/uni-helper/uni-network/commit/33ef53bb266d2d158ed2f3bff440348634bab051)) - by @ 11 | 12 | ## [0.21.2](https://github.com/uni-helper/uni-network/compare/v0.21.1...v0.21.2) (2025-05-14) 13 | 14 | ### Bug Fixes 15 | 16 | * fix the Un constructor implementation to treat the config argument as optional, https://github.com/axios/axios/pull/6881 ([7086fb8](https://github.com/uni-helper/uni-network/commit/7086fb86f159428e4b0eb2ac46328d7b72afe506)) - by @ 17 | 18 | ## [0.21.1](https://github.com/uni-helper/uni-network/compare/v0.21.0...v0.21.1) (2025-03-25) 19 | 20 | ### Bug Fixes 21 | 22 | * fix buildFullPath judgement ([fbe706a](https://github.com/uni-helper/uni-network/commit/fbe706a16d9abed6fe39b15c259c764874f5e63b)) - by @ 23 | 24 | ## [0.21.0](https://github.com/uni-helper/uni-network/compare/v0.20.0...v0.21.0) (2025-03-18) 25 | 26 | ### Features 27 | 28 | * add allowAbsoluteUrls option ([79c4581](https://github.com/uni-helper/uni-network/commit/79c4581620717485ff2e83b0fecde92c44707c64)) - by @ 29 | 30 | ## [0.20.0](https://github.com/uni-helper/uni-network/compare/v0.19.3...v0.20.0) (2025-01-06) 31 | 32 | ### ⚠ BREAKING CHANGES 33 | 34 | * improve error handling (#56) 35 | 36 | ### Features 37 | 38 | * improve error handling ([#56](https://github.com/uni-helper/uni-network/issues/56)) ([0b6aa80](https://github.com/uni-helper/uni-network/commit/0b6aa80afa231cce891e288c309d278bf86fd7f4)) - by @peerless-hero 39 | 40 | ### Bug Fixes 41 | 42 | * syntax error on ios, axios/axios[#6608](https://github.com/uni-helper/uni-network/issues/6608) ([2f51786](https://github.com/uni-helper/uni-network/commit/2f51786702e6f0c69930002356941fce8f1db2c7)) - by @ 43 | 44 | ## [0.19.3](https://github.com/uni-helper/uni-network/compare/v0.19.2...v0.19.3) (2024-09-20) 45 | 46 | ### Features 47 | 48 | * add toAbortSignal to UnCancelToken, axios[#6582](https://github.com/uni-helper/uni-network/issues/6582) ([738486c](https://github.com/uni-helper/uni-network/commit/738486cc1404ffc6ed1df3103a23f55f2ecaf563)) - by @ModyQyW 49 | 50 | ### Bug Fixes 51 | 52 | * add the missed implementation of UnError[#status](https://github.com/uni-helper/uni-network/issues/status) property, axios[#6573](https://github.com/uni-helper/uni-network/issues/6573) ([43b5bc5](https://github.com/uni-helper/uni-network/commit/43b5bc541f79f00d6cd95e406205ecfe95f484dd)) - by @ModyQyW 53 | * allow vueuse v11 ([cd84a78](https://github.com/uni-helper/uni-network/commit/cd84a784a9f0ed67d018a19da48c267f1e3ed274)) - by @ModyQyW 54 | * disregard protocol-relative URL to remediate SSRF, axios[#6539](https://github.com/uni-helper/uni-network/issues/6539) ([025cd49](https://github.com/uni-helper/uni-network/commit/025cd49fbf44493f97db3f340762de1599d31910)) - by @ModyQyW 55 | * fix main entry ([7c6561c](https://github.com/uni-helper/uni-network/commit/7c6561ca770a9cdc0f1c861a0d9506cb3f31fe86)) - by @ModyQyW 56 | * fix node10 ts support ([ad3b98c](https://github.com/uni-helper/uni-network/commit/ad3b98cf958bfdb0aeff06dafe7dd3996bcbbd9b)) - by @ModyQyW 57 | * replace statuses with statuses-es for better compact compatibility ([#53](https://github.com/uni-helper/uni-network/issues/53)) ([4806357](https://github.com/uni-helper/uni-network/commit/48063578403e1cbd1f8dcfc602c7d0df026bb995)) - by @wtto00 58 | -------------------------------------------------------------------------------- /packages/core/build.config.ts: -------------------------------------------------------------------------------- 1 | import { appendFile, unlink } from "node:fs/promises"; 2 | import { resolve } from "node:path"; 3 | import { defineBuildConfig } from "unbuild"; 4 | 5 | export default defineBuildConfig({ 6 | clean: true, 7 | declaration: true, 8 | entries: ["./src/index", "./src/composables"], 9 | hooks: { 10 | "build:done": async (ctx) => { 11 | const outDir = ctx.options.outDir; 12 | const promises = []; 13 | // remove empty files 14 | const emptyBuildEntries = ctx.buildEntries.filter( 15 | (entry) => entry.exports?.length === 0, 16 | ); 17 | promises.push( 18 | emptyBuildEntries.map((entry) => unlink(resolve(outDir, entry.path))), 19 | ); 20 | // patch cjs entries 21 | const cjsBuildEntries = ctx.buildEntries.filter( 22 | (entry) => 23 | entry.exports && 24 | entry.exports.length > 1 && 25 | entry.exports.includes("default") && 26 | entry.path.endsWith(".cjs"), 27 | ); 28 | promises.push( 29 | cjsBuildEntries.map((entry) => 30 | appendFile( 31 | resolve(outDir, entry.path), 32 | "module.exports = Object.assign(exports.default || {}, exports);", 33 | ), 34 | ), 35 | ); 36 | await Promise.all(promises); 37 | }, 38 | }, 39 | rollup: { 40 | dts: { 41 | // https://github.com/unjs/unbuild/issues/135 42 | respectExternal: false, 43 | }, 44 | emitCJS: true, 45 | esbuild: { 46 | target: "es2017", 47 | }, 48 | inlineDependencies: true, 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uni-helper/uni-network", 3 | "version": "0.21.3", 4 | "description": "为 uni-app 打造的基于 Promise 的 HTTP 客户端", 5 | "keywords": [ 6 | "uni-app", 7 | "uniapp", 8 | "uni", 9 | "request", 10 | "network", 11 | "upload", 12 | "uploadFile", 13 | "download", 14 | "downloadFile" 15 | ], 16 | "homepage": "https://github.com/uni-helper/uni-network#readme", 17 | "bugs": { 18 | "url": "https://github.com/uni-helper/uni-network/issues" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/uni-helper/uni-network.git", 23 | "directory": "packages/core" 24 | }, 25 | "license": "MIT", 26 | "author": { 27 | "name": "ModyQyW", 28 | "email": "wurui-dev@foxmail.com", 29 | "url": "https://modyqyw.github.io" 30 | }, 31 | "type": "module", 32 | "exports": { 33 | ".": { 34 | "import": { 35 | "types": "./dist/index.d.mts", 36 | "default": "./dist/index.mjs" 37 | }, 38 | "require": { 39 | "types": "./dist/index.d.cts", 40 | "default": "./dist/index.cjs" 41 | } 42 | }, 43 | "./composables": { 44 | "import": { 45 | "types": "./dist/composables.d.mts", 46 | "default": "./dist/composables.mjs" 47 | }, 48 | "require": { 49 | "types": "./dist/composables.d.cts", 50 | "default": "./dist/composables.cjs" 51 | } 52 | } 53 | }, 54 | "main": "./dist/index.mjs", 55 | "module": "./dist/index.mjs", 56 | "types": "./dist/index.d.ts", 57 | "typesVersions": { 58 | "*": { 59 | "*": [ 60 | "./dist/*", 61 | "./dist/index.d.ts", 62 | "./dist/composables.d.ts" 63 | ] 64 | } 65 | }, 66 | "files": [ 67 | "dist" 68 | ], 69 | "scripts": { 70 | "build": "unbuild", 71 | "prepublishOnly": "pnpm build", 72 | "stub": "unbuild --stub" 73 | }, 74 | "dependencies": { 75 | "@dcloudio/types": "^3.4.14", 76 | "@types/lodash.merge": "^4.6.9", 77 | "fast-querystring": "^1.1.2", 78 | "lodash.merge": "^4.6.2", 79 | "statuses-es": "^2.0.6", 80 | "vue-demi": "^0.14.10" 81 | }, 82 | "devDependencies": { 83 | "@types/ungap__url-search-params": "^0.1.2", 84 | "@ungap/url-search-params": "^0.2.2", 85 | "@vueuse/core": "^12.8.2", 86 | "vitest": "^3.0.9", 87 | "vue": "^3.5.13" 88 | }, 89 | "peerDependencies": { 90 | "@vue/composition-api": "^1.0.0", 91 | "@vueuse/core": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", 92 | "vue": "^2.6.0 || ^2.7.0 || ^3.2.0" 93 | }, 94 | "peerDependenciesMeta": { 95 | "@vue/composition-api": { 96 | "optional": true 97 | }, 98 | "@vueuse/core": { 99 | "optional": true 100 | }, 101 | "vue": { 102 | "optional": true 103 | } 104 | }, 105 | "packageManager": "pnpm@9.15.9", 106 | "engines": { 107 | "node": ">=18" 108 | }, 109 | "publishConfig": { 110 | "access": "public", 111 | "registry": "https://registry.npmjs.org/" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /packages/core/src/adapters/download.ts: -------------------------------------------------------------------------------- 1 | import statuses from "statuses-es"; 2 | import type { UnCancelTokenListener } from "../core/UnCancelToken"; 3 | import { UnCanceledError } from "../core/UnCanceledError"; 4 | import { UnError } from "../core/UnError"; 5 | import { settle } from "../core/settle"; 6 | import type { UnConfig, UnData, UnResponse } from "../types"; 7 | import { buildDownloadConfig } from "../utils"; 8 | 9 | export const downloadAdapter = ( 10 | config: UnConfig, 11 | ) => 12 | new Promise>((resolve, reject) => { 13 | const { onHeadersReceived, cancelToken, signal } = config; 14 | 15 | const onProgressUpdate = 16 | config?.onDownloadProgress ?? 17 | config?.onDownloadProgressUpdate ?? 18 | config?.onProgress ?? 19 | config?.onProgressUpdate; 20 | 21 | const downloadConfig = buildDownloadConfig(config); 22 | 23 | let onCanceled: UnCancelTokenListener; 24 | const done = () => { 25 | cancelToken?.unsubscribe(onCanceled); 26 | // @ts-expect-error No overload matches this call. 27 | signal?.removeEventListener("abort", onCanceled); 28 | }; 29 | 30 | let task: UniApp.DownloadTask | undefined; 31 | 32 | task = uni.downloadFile({ 33 | ...downloadConfig, 34 | success: (res) => { 35 | let statusText: string | undefined; 36 | try { 37 | statusText = statuses(res?.statusCode)?.toString(); 38 | } catch (_) { 39 | // ↑ 为支付宝小程序保留 (_) 40 | // 当 statusCode 不合法、statuses 抛出错误时,设置 statusText 为 undefined 41 | statusText = undefined; 42 | } 43 | const response: UnResponse = { 44 | // @ts-expect-error no types 45 | errMsg: res?.errMsg ?? res?.errmsg ?? res?.msg ?? res?.message, 46 | // @ts-expect-error no types 47 | errno: res?.errno, 48 | tempFilePath: res?.tempFilePath, 49 | filePath: res?.filePath, 50 | profile: res?.profile, 51 | status: res?.statusCode, 52 | statusText, 53 | // @ts-expect-error no types 54 | headers: res?.header ?? res?.headers, 55 | config, 56 | // @ts-expect-error no types 57 | data: { 58 | tempFilePath: res?.tempFilePath, 59 | filePath: res?.filePath, 60 | }, 61 | task, 62 | }; 63 | settle>( 64 | (val) => { 65 | resolve(val); 66 | done(); 67 | }, 68 | (err) => { 69 | reject(err); 70 | done(); 71 | }, 72 | response, 73 | ); 74 | }, 75 | fail: (err) => { 76 | switch (err.errMsg) { 77 | case "request:fail abort": 78 | reject(new UnError(err.errMsg, UnError.ERR_CANCELED, config, task)); 79 | break; 80 | case "request:fail timeout": 81 | reject(new UnError(err.errMsg, UnError.ETIMEDOUT, config, task)); 82 | break; 83 | default: 84 | reject(new UnError(err.errMsg, UnError.ERR_NETWORK, config, task)); 85 | break; 86 | } 87 | }, 88 | complete: () => { 89 | if (onHeadersReceived) { 90 | task?.offHeadersReceived(onHeadersReceived); 91 | } 92 | if (onProgressUpdate) { 93 | task?.offProgressUpdate(onProgressUpdate); 94 | } 95 | }, 96 | }); 97 | 98 | if (onHeadersReceived) { 99 | task.onHeadersReceived(onHeadersReceived); 100 | } 101 | if (onProgressUpdate) { 102 | task.onProgressUpdate(onProgressUpdate); 103 | } 104 | 105 | if (cancelToken || signal) { 106 | onCanceled = (cancel) => { 107 | if (!task) { 108 | return; 109 | } 110 | reject( 111 | // @ts-expect-error type not existed 112 | !cancel || cancel.type 113 | ? new UnCanceledError(undefined, config, task) 114 | : cancel, 115 | ); 116 | task.abort(); 117 | task = undefined; 118 | }; 119 | 120 | cancelToken?.subscribe(onCanceled); 121 | signal?.aborted 122 | ? onCanceled({}) 123 | : signal?.addEventListener?.("abort", onCanceled); 124 | } 125 | }); 126 | -------------------------------------------------------------------------------- /packages/core/src/adapters/index.ts: -------------------------------------------------------------------------------- 1 | import { downloadAdapter } from "./download"; 2 | import { requestAdapter } from "./request"; 3 | import { uploadAdapter } from "./upload"; 4 | 5 | export const adapters = { 6 | download: downloadAdapter, 7 | request: requestAdapter, 8 | upload: uploadAdapter, 9 | }; 10 | 11 | export * from "./download"; 12 | export * from "./request"; 13 | export * from "./upload"; 14 | -------------------------------------------------------------------------------- /packages/core/src/adapters/request.ts: -------------------------------------------------------------------------------- 1 | import statuses from "statuses-es"; 2 | import type { UnCancelTokenListener } from "../core/UnCancelToken"; 3 | import { UnCanceledError } from "../core/UnCanceledError"; 4 | import { UnError } from "../core/UnError"; 5 | import { settle } from "../core/settle"; 6 | import type { UnConfig, UnData, UnResponse } from "../types"; 7 | import { buildRequestConfig } from "../utils"; 8 | 9 | export const requestAdapter = ( 10 | config: UnConfig, 11 | ) => 12 | new Promise>((resolve, reject) => { 13 | const { onHeadersReceived, onChunkReceived, cancelToken, signal } = config; 14 | 15 | const requestConfig = buildRequestConfig(config); 16 | 17 | let onCanceled: UnCancelTokenListener; 18 | const done = () => { 19 | cancelToken?.unsubscribe(onCanceled); 20 | signal?.removeEventListener?.("abort", onCanceled); 21 | }; 22 | 23 | let task: UniApp.RequestTask | undefined; 24 | 25 | task = uni.request({ 26 | ...requestConfig, 27 | success: (res) => { 28 | let statusText: string | undefined; 29 | try { 30 | statusText = statuses(res?.statusCode)?.toString(); 31 | } catch (_) { 32 | /// ↑ 为支付宝小程序保留 (_) 33 | // 当 statusCode 不合法、statuses 抛出错误时,设置 statusText 为 undefined 34 | statusText = undefined; 35 | } 36 | const response: UnResponse = { 37 | // @ts-expect-error no types 38 | errMsg: res?.errMsg ?? res?.errmsg ?? res?.msg ?? res?.message, 39 | // @ts-expect-error no types 40 | errno: res?.errno, 41 | cookies: res?.cookies, 42 | profile: res?.profile, 43 | status: res?.statusCode, 44 | statusText, 45 | // @ts-expect-error no types 46 | headers: res?.header ?? res?.headers, 47 | config, 48 | // @ts-expect-error no types 49 | data: res?.data, 50 | task, 51 | }; 52 | settle>( 53 | (val) => { 54 | resolve(val); 55 | done(); 56 | }, 57 | (err) => { 58 | reject(err); 59 | done(); 60 | }, 61 | response, 62 | ); 63 | }, 64 | fail: (err) => { 65 | switch (err.errMsg) { 66 | case "request:fail abort": 67 | reject(new UnError(err.errMsg, UnError.ERR_CANCELED, config, task)); 68 | break; 69 | case "request:fail timeout": 70 | reject(new UnError(err.errMsg, UnError.ETIMEDOUT, config, task)); 71 | break; 72 | default: 73 | reject(new UnError(err.errMsg, UnError.ERR_NETWORK, config, task)); 74 | break; 75 | } 76 | }, 77 | complete: () => { 78 | if (onHeadersReceived) { 79 | task?.offHeadersReceived(onHeadersReceived); 80 | } 81 | if (onChunkReceived) { 82 | // @ts-expect-error uni-app types lost 83 | task?.offChunkReceived(onChunkReceived); 84 | } 85 | }, 86 | }); 87 | 88 | if (onHeadersReceived) { 89 | task.onHeadersReceived(onHeadersReceived); 90 | } 91 | if (onChunkReceived) { 92 | // @ts-expect-error uni-app types lost 93 | task.onChunkReceived(onChunkReceived); 94 | } 95 | 96 | if (cancelToken || signal) { 97 | onCanceled = (cancel) => { 98 | if (!task) { 99 | return; 100 | } 101 | reject( 102 | // @ts-expect-error type not existed 103 | !cancel || cancel.type 104 | ? new UnCanceledError(undefined, config, task) 105 | : cancel, 106 | ); 107 | task.abort(); 108 | task = undefined; 109 | }; 110 | 111 | cancelToken?.subscribe(onCanceled); 112 | signal?.aborted 113 | ? onCanceled({}) 114 | : signal?.addEventListener?.("abort", onCanceled); 115 | } 116 | }); 117 | -------------------------------------------------------------------------------- /packages/core/src/adapters/upload.ts: -------------------------------------------------------------------------------- 1 | import statuses from "statuses-es"; 2 | import type { UnCancelTokenListener } from "../core/UnCancelToken"; 3 | import { UnCanceledError } from "../core/UnCanceledError"; 4 | import { UnError } from "../core/UnError"; 5 | import { settle } from "../core/settle"; 6 | import type { UnConfig, UnData, UnResponse } from "../types"; 7 | import { buildUploadConfig } from "../utils"; 8 | 9 | export const uploadAdapter = (config: UnConfig) => 10 | new Promise>((resolve, reject) => { 11 | const { onHeadersReceived, cancelToken, signal } = config; 12 | 13 | const onProgressUpdate = 14 | config?.onUploadProgress ?? 15 | config?.onUploadProgressUpdate ?? 16 | config?.onProgress ?? 17 | config?.onProgressUpdate; 18 | 19 | const uploadConfig = buildUploadConfig(config); 20 | 21 | let onCanceled: UnCancelTokenListener; 22 | const done = () => { 23 | cancelToken?.unsubscribe(onCanceled); 24 | // @ts-expect-error No overload matches this call. 25 | signal?.removeEventListener("abort", onCanceled); 26 | }; 27 | 28 | let task: UniApp.UploadTask | undefined; 29 | 30 | task = uni.uploadFile({ 31 | ...uploadConfig, 32 | success: (res) => { 33 | let statusText: string | undefined; 34 | try { 35 | statusText = statuses(res?.statusCode)?.toString(); 36 | } catch (_) { 37 | // ↑ 为支付宝小程序保留 (_) 38 | // 当 statusCode 不合法、statuses 抛出错误时,设置 statusText 为 undefined 39 | statusText = undefined; 40 | } 41 | const response: UnResponse = { 42 | // @ts-expect-error no types 43 | errMsg: res?.errMsg ?? res?.errmsg ?? res?.msg ?? res?.message, 44 | // @ts-expect-error no types 45 | errno: res?.errno, 46 | status: res?.statusCode, 47 | statusText, 48 | // @ts-expect-error no types 49 | headers: res?.header ?? res?.headers, 50 | config, 51 | // @ts-expect-error no types 52 | data: res?.data, 53 | task, 54 | }; 55 | settle>( 56 | (val) => { 57 | resolve(val); 58 | done(); 59 | }, 60 | (err) => { 61 | reject(err); 62 | done(); 63 | }, 64 | response, 65 | ); 66 | }, 67 | fail: (err) => { 68 | switch (err.errMsg) { 69 | case "request:fail abort": 70 | reject(new UnError(err.errMsg, UnError.ERR_CANCELED, config, task)); 71 | break; 72 | case "request:fail timeout": 73 | reject(new UnError(err.errMsg, UnError.ETIMEDOUT, config, task)); 74 | break; 75 | default: 76 | reject(new UnError(err.errMsg, UnError.ERR_NETWORK, config, task)); 77 | break; 78 | } 79 | }, 80 | complete: () => { 81 | if (onHeadersReceived) { 82 | task?.offHeadersReceived(onHeadersReceived); 83 | } 84 | if (onProgressUpdate) { 85 | task?.offProgressUpdate(onProgressUpdate); 86 | } 87 | }, 88 | }); 89 | 90 | if (onHeadersReceived) { 91 | task.onHeadersReceived(onHeadersReceived); 92 | } 93 | if (onProgressUpdate) { 94 | task.onProgressUpdate(onProgressUpdate); 95 | } 96 | 97 | if (cancelToken || signal) { 98 | onCanceled = (cancel) => { 99 | if (!task) { 100 | return; 101 | } 102 | reject( 103 | // @ts-expect-error type not existed 104 | !cancel || cancel.type 105 | ? new UnCanceledError(undefined, config, task) 106 | : cancel, 107 | ); 108 | task.abort(); 109 | task = undefined; 110 | }; 111 | 112 | cancelToken?.subscribe(onCanceled); 113 | signal?.aborted 114 | ? onCanceled({}) 115 | : signal?.addEventListener?.("abort", onCanceled); 116 | } 117 | }); 118 | -------------------------------------------------------------------------------- /packages/core/src/composables.ts: -------------------------------------------------------------------------------- 1 | import { noop, until } from "@vueuse/core"; 2 | import type { Ref, ShallowRef } from "vue-demi"; 3 | import { ref, shallowRef } from "vue-demi"; 4 | import type { 5 | UnCancelTokenSource, 6 | UnConfig, 7 | UnData, 8 | UnInstance, 9 | UnResponse, 10 | } from "./index"; 11 | import { UnError, un } from "./index"; 12 | 13 | /** Align with v12.3.0 */ 14 | 15 | export interface UseUnReturn< 16 | T = UnData, 17 | R = UnResponse, 18 | D = UnData, 19 | O extends UseUnOptions = UseUnOptions, 20 | > { 21 | /** Un 响应 */ 22 | response: ShallowRef; 23 | /** Un 响应数据 */ 24 | data: O extends UseUnOptionsWithInitialData ? Ref : Ref; 25 | /** 是否已经结束 */ 26 | isFinished: Ref; 27 | /** 是否正在请求 */ 28 | isLoading: Ref; 29 | /** 是否已经取消 */ 30 | isAborted: Ref; 31 | /** `isAborted` 别名 */ 32 | isCanceled: Ref; 33 | /** 发生的错误 */ 34 | error: ShallowRef; 35 | /** 取消当前请求 */ 36 | abort: (message?: string | undefined) => void; 37 | /** `abort` 别名 */ 38 | cancel: (message?: string | undefined) => void; 39 | } 40 | export interface StrictUseUnReturn< 41 | T, 42 | R, 43 | D, 44 | O extends UseUnOptions = UseUnOptions, 45 | > extends UseUnReturn { 46 | /** 手动调用 */ 47 | execute: ( 48 | url?: string | UnConfig, 49 | config?: UnConfig, 50 | ) => Promise>; 51 | } 52 | export interface EasyUseUnReturn extends UseUnReturn { 53 | /** 手动调用 */ 54 | execute: ( 55 | url: string, 56 | config?: UnConfig, 57 | ) => Promise>; 58 | } 59 | export interface UseUnOptionsBase { 60 | /** 当 `useUn` 被调用时,是否自动发起请求 */ 61 | immediate?: boolean; 62 | /** 63 | * 是否使用 shallowRef 64 | * 65 | * @default true 66 | */ 67 | shallow?: boolean; 68 | /** 69 | * 是否在新请求发起时中止之前的请求 70 | * 71 | * @default true 72 | */ 73 | abortPrevious?: boolean; 74 | /** 75 | * 是否在执行前将请求数据重置为 initialData 76 | * 77 | * @default false 78 | */ 79 | resetOnExecute?: boolean; 80 | /** 发生错误时调用 */ 81 | onError?: (e: unknown) => void; 82 | /** 成功请求时调用 */ 83 | onSuccess?: (data: T) => void; 84 | /** 请求结束时调用 */ 85 | onFinish?: () => void; 86 | } 87 | export interface UseUnOptionsWithInitialData extends UseUnOptionsBase { 88 | /** 在请求还未响应时使用的响应数据 */ 89 | initialData: T; 90 | } 91 | export type UseUnOptions = 92 | | UseUnOptionsBase 93 | | UseUnOptionsWithInitialData; 94 | type OverallUseUnReturn = 95 | | StrictUseUnReturn 96 | | EasyUseUnReturn; 97 | 98 | const isUnInstance = (val: any) => 99 | !!val?.request && !!val?.download && !!val?.upload; 100 | 101 | export function useUn< 102 | T = UnData, 103 | R = UnResponse, 104 | D = UnData, 105 | O extends UseUnOptionsWithInitialData = UseUnOptionsWithInitialData, 106 | >( 107 | url: string, 108 | config?: UnConfig, 109 | options?: O, 110 | ): StrictUseUnReturn & Promise>; 111 | export function useUn< 112 | T = UnData, 113 | R = UnResponse, 114 | D = UnData, 115 | O extends UseUnOptionsWithInitialData = UseUnOptionsWithInitialData, 116 | >( 117 | url: string, 118 | instance?: UnInstance, 119 | options?: O, 120 | ): StrictUseUnReturn & Promise>; 121 | export function useUn< 122 | T = UnData, 123 | R = UnResponse, 124 | D = UnData, 125 | O extends UseUnOptionsWithInitialData = UseUnOptionsWithInitialData, 126 | >( 127 | url: string, 128 | config: UnConfig, 129 | instance: UnInstance, 130 | options?: O, 131 | ): StrictUseUnReturn & Promise>; 132 | export function useUn< 133 | T = UnData, 134 | R = UnResponse, 135 | D = UnData, 136 | O extends UseUnOptionsBase = UseUnOptionsBase, 137 | >( 138 | url: string, 139 | config?: UnConfig, 140 | options?: O, 141 | ): StrictUseUnReturn & Promise>; 142 | export function useUn< 143 | T = UnData, 144 | R = UnResponse, 145 | D = UnData, 146 | O extends UseUnOptionsBase = UseUnOptionsBase, 147 | >( 148 | url: string, 149 | instance?: UnInstance, 150 | options?: O, 151 | ): StrictUseUnReturn & Promise>; 152 | export function useUn< 153 | T = UnData, 154 | R = UnResponse, 155 | D = UnData, 156 | O extends UseUnOptionsBase = UseUnOptionsBase, 157 | >( 158 | url: string, 159 | config: UnConfig, 160 | instance: UnInstance, 161 | options?: O, 162 | ): StrictUseUnReturn & Promise>; 163 | export function useUn, D = UnData>( 164 | config?: UnConfig, 165 | ): EasyUseUnReturn & Promise>; 166 | export function useUn, D = UnData>( 167 | instance?: UnInstance, 168 | ): EasyUseUnReturn & Promise>; 169 | export function useUn, D = UnData>( 170 | config?: UnConfig, 171 | instance?: UnInstance, 172 | ): EasyUseUnReturn & Promise>; 173 | 174 | export function useUn, D = UnData>( 175 | ...args: any[] 176 | ): OverallUseUnReturn & Promise> { 177 | const url: string | undefined = 178 | typeof args[0] === "string" ? args[0] : undefined; 179 | const argsPlaceholder = typeof url === "string" ? 1 : 0; 180 | let defaultConfig: UnConfig = {}; 181 | let instance: UnInstance = un; 182 | let options: UseUnOptions = { 183 | immediate: !!argsPlaceholder, 184 | shallow: true, 185 | abortPrevious: true, 186 | }; 187 | 188 | if (args.length > 0 + argsPlaceholder) { 189 | /** 在这里不能使用 `instanceof`,原因请参考 https://github.com/axios/axios/issues/737 */ 190 | if (isUnInstance(args[0 + argsPlaceholder])) 191 | instance = args[0 + argsPlaceholder]; 192 | else defaultConfig = args[0 + argsPlaceholder]; 193 | } 194 | 195 | if ( 196 | args.length > 1 + argsPlaceholder && 197 | isUnInstance(args[1 + argsPlaceholder]) 198 | ) 199 | instance = args[1 + argsPlaceholder]; 200 | if ( 201 | (args.length === 2 + argsPlaceholder && 202 | !isUnInstance(args[1 + argsPlaceholder])) || 203 | args.length === 3 + argsPlaceholder 204 | ) 205 | options = args.at(-1) || options; 206 | 207 | const { 208 | shallow, 209 | onSuccess = noop, 210 | onError = noop, 211 | immediate, 212 | resetOnExecute = false, 213 | } = options; 214 | 215 | const initialData = (options as UseUnOptionsWithInitialData).initialData; 216 | const response = shallowRef>(); 217 | const data = (shallow ? shallowRef : ref)(initialData) as Ref; 218 | const isFinished = ref(false); 219 | const isLoading = ref(false); 220 | const isAborted = ref(false); 221 | const error = shallowRef(); 222 | 223 | const cancelTokenSource = un.CancelToken.source; 224 | let cancelToken: UnCancelTokenSource = cancelTokenSource(); 225 | 226 | const abort = (message?: string) => { 227 | if (isFinished.value || !isLoading.value) return; 228 | cancelToken.cancel(message); 229 | cancelToken = cancelTokenSource(); 230 | isAborted.value = true; 231 | isLoading.value = false; 232 | isFinished.value = false; 233 | }; 234 | const loading = (loading: boolean) => { 235 | isLoading.value = loading; 236 | isFinished.value = !loading; 237 | }; 238 | 239 | /** 重置 data 为 initialData */ 240 | const resetData = () => { 241 | if (resetOnExecute) data.value = initialData; 242 | }; 243 | const waitUntilFinished = () => 244 | new Promise>((resolve, reject) => { 245 | until(isFinished) 246 | .toBe(true) 247 | // eslint-disable-next-line @typescript-eslint/no-use-before-define 248 | .then(() => (error.value ? reject(error.value) : resolve(result))); 249 | }); 250 | const promise = { 251 | // biome-ignore lint/suspicious/noThenProperty: 252 | then: (...args) => waitUntilFinished().then(...args), 253 | catch: (...args) => waitUntilFinished().catch(...args), 254 | } as Promise>; 255 | 256 | let executeCounter = 0; 257 | const execute: OverallUseUnReturn["execute"] = ( 258 | executeUrl: string | UnConfig | undefined = url, 259 | config: UnConfig = {}, 260 | ) => { 261 | error.value = undefined; 262 | const _url = 263 | typeof executeUrl === "string" ? executeUrl : (url ?? config.url); 264 | 265 | if (_url === undefined) { 266 | error.value = new UnError(UnError.ERR_INVALID_URL); 267 | isFinished.value = true; 268 | return promise; 269 | } 270 | resetData(); 271 | 272 | if (options.abortPrevious) abort(); 273 | 274 | loading(true); 275 | 276 | executeCounter += 1; 277 | const currentExecuteCounter = executeCounter; 278 | isAborted.value = false; 279 | 280 | instance(_url, { 281 | ...defaultConfig, 282 | ...(typeof executeUrl === "object" ? executeUrl : config), 283 | cancelToken: cancelToken.token, 284 | }) 285 | .then((r: any) => { 286 | if (isAborted.value) return; 287 | response.value = r; 288 | const result = r.data; 289 | data.value = result; 290 | onSuccess(result); 291 | }) 292 | .catch((error_: any) => { 293 | error.value = error_; 294 | onError(error_); 295 | }) 296 | .finally(() => { 297 | options.onFinish?.(); 298 | if (currentExecuteCounter === executeCounter) loading(false); 299 | }); 300 | return promise; 301 | }; 302 | 303 | if (immediate && url) (execute as StrictUseUnReturn["execute"])(); 304 | 305 | const result = { 306 | response, 307 | data, 308 | error, 309 | finished: isFinished, 310 | loading: isLoading, 311 | isFinished, 312 | isLoading, 313 | cancel: abort, 314 | isAborted, 315 | canceled: isAborted, 316 | aborted: isAborted, 317 | isCanceled: isAborted, 318 | abort, 319 | execute, 320 | } as OverallUseUnReturn; 321 | 322 | return { 323 | ...result, 324 | ...promise, 325 | }; 326 | } 327 | -------------------------------------------------------------------------------- /packages/core/src/core/HttpStatusCode.ts: -------------------------------------------------------------------------------- 1 | export const HttpStatusCode = { 2 | Continue: 100, 3 | 100: "Continue", 4 | 5 | SwitchingProtocols: 101, 6 | 101: "SwitchingProtocols", 7 | 8 | Processing: 102, 9 | 102: "Processing", 10 | 11 | EarlyHints: 103, 12 | 103: "EarlyHints", 13 | 14 | Ok: 200, 15 | 200: "Ok", 16 | 17 | Created: 201, 18 | 201: "Created", 19 | 20 | Accepted: 202, 21 | 202: "Accepted", 22 | 23 | NonAuthoritativeInformation: 203, 24 | 203: "NonAuthoritativeInformation", 25 | 26 | NoContent: 204, 27 | 204: "NoContent", 28 | 29 | ResetContent: 205, 30 | 205: "ResetContent", 31 | 32 | PartialContent: 206, 33 | 206: "PartialContent", 34 | 35 | MultiStatus: 207, 36 | 207: "MultiStatus", 37 | 38 | AlreadyReported: 208, 39 | 208: "AlreadyReported", 40 | 41 | ImUsed: 226, 42 | 226: "ImUsed", 43 | 44 | MultipleChoices: 300, 45 | 300: "MultipleChoices", 46 | 47 | MovedPermanently: 301, 48 | 301: "MovedPermanently", 49 | 50 | Found: 302, 51 | 302: "Found", 52 | 53 | SeeOther: 303, 54 | 303: "SeeOther", 55 | 56 | NotModified: 304, 57 | 304: "NotModified", 58 | 59 | UseProxy: 305, 60 | 305: "UseProxy", 61 | 62 | Unused: 306, 63 | 306: "Unused", 64 | 65 | TemporaryRedirect: 307, 66 | 307: "TemporaryRedirect", 67 | 68 | PermanentRedirect: 308, 69 | 308: "PermanentRedirect", 70 | 71 | BadRequest: 400, 72 | 400: "BadRequest", 73 | 74 | Unauthorized: 401, 75 | 401: "Unauthorized", 76 | 77 | PaymentRequired: 402, 78 | 402: "PaymentRequired", 79 | 80 | Forbidden: 403, 81 | 403: "Forbidden", 82 | 83 | NotFound: 404, 84 | 404: "NotFound", 85 | 86 | MethodNotAllowed: 405, 87 | 405: "MethodNotAllowed", 88 | 89 | NotAcceptable: 406, 90 | 406: "NotAcceptable", 91 | 92 | ProxyAuthenticationRequired: 407, 93 | 407: "ProxyAuthenticationRequired", 94 | 95 | RequestTimeout: 408, 96 | 408: "RequestTimeout", 97 | 98 | Conflict: 409, 99 | 409: "Conflict", 100 | 101 | Gone: 410, 102 | 410: "Gone", 103 | 104 | LengthRequired: 411, 105 | 411: "LengthRequired", 106 | 107 | PreconditionFailed: 412, 108 | 412: "PreconditionFailed", 109 | 110 | PayloadTooLarge: 413, 111 | 413: "PayloadTooLarge", 112 | 113 | UriTooLong: 414, 114 | 414: "UriTooLong", 115 | 116 | UnsupportedMediaType: 415, 117 | 415: "UnsupportedMediaType", 118 | 119 | RangeNotSatisfiable: 416, 120 | 416: "RangeNotSatisfiable", 121 | 122 | ExpectationFailed: 417, 123 | 417: "ExpectationFailed", 124 | 125 | ImATeapot: 418, 126 | 418: "ImATeapot", 127 | 128 | MisdirectedRequest: 421, 129 | 421: "MisdirectedRequest", 130 | 131 | UnprocessableEntity: 422, 132 | 422: "UnprocessableEntity", 133 | 134 | Locked: 423, 135 | 423: "Locked", 136 | 137 | FailedDependency: 424, 138 | 424: "FailedDependency", 139 | 140 | TooEarly: 425, 141 | 425: "TooEarly", 142 | 143 | UpgradeRequired: 426, 144 | 426: "UpgradeRequired", 145 | 146 | PreconditionRequired: 428, 147 | 428: "PreconditionRequired", 148 | 149 | TooManyRequests: 429, 150 | 429: "TooManyRequests", 151 | 152 | RequestHeaderFieldsTooLarge: 431, 153 | 431: "RequestHeaderFieldsTooLarge", 154 | 155 | UnavailableForLegalReasons: 451, 156 | 451: "UnavailableForLegalReasons", 157 | 158 | InternalServerError: 500, 159 | 500: "InternalServerError", 160 | 161 | NotImplemented: 501, 162 | 501: "NotImplemented", 163 | 164 | BadGateway: 502, 165 | 502: "BadGateway", 166 | 167 | ServiceUnavailable: 503, 168 | 503: "ServiceUnavailable", 169 | 170 | GatewayTimeout: 504, 171 | 504: "GatewayTimeout", 172 | 173 | HttpVersionNotSupported: 505, 174 | 505: "HttpVersionNotSupported", 175 | 176 | VariantAlsoNegotiates: 506, 177 | 506: "VariantAlsoNegotiates", 178 | 179 | InsufficientStorage: 507, 180 | 507: "InsufficientStorage", 181 | 182 | LoopDetected: 508, 183 | 508: "LoopDetected", 184 | 185 | NotExtended: 510, 186 | 510: "NotExtended", 187 | 188 | NetworkAuthenticationRequired: 511, 189 | 511: "NetworkAuthenticationRequired", 190 | } as const; 191 | -------------------------------------------------------------------------------- /packages/core/src/core/Un.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig, UnData, UnResponse } from "../types"; 2 | import { buildFullPath, buildUrl, mergeConfig } from "../utils"; 3 | import { 4 | UnInterceptorManager, 5 | type UnInterceptorManagerHandlerFulfilled, 6 | type UnInterceptorManagerHandlerRejected, 7 | } from "./UnInterceptorManager"; 8 | import { dispatchRequest } from "./dispatchRequest"; 9 | 10 | export class Un { 11 | defaults: UnConfig; 12 | 13 | interceptors: { 14 | request: UnInterceptorManager, T, D>; 15 | response: UnInterceptorManager, T, D>; 16 | }; 17 | 18 | constructor(instanceConfig: UnConfig) { 19 | this.defaults = instanceConfig || {}; 20 | this.interceptors = { 21 | request: new UnInterceptorManager(), 22 | response: new UnInterceptorManager(), 23 | }; 24 | } 25 | 26 | private _request>( 27 | configOrUrl: string | UnConfig, 28 | config?: UnConfig, 29 | ): Promise { 30 | const _config = 31 | typeof configOrUrl === "string" 32 | ? { ...config, url: configOrUrl } 33 | : { ...configOrUrl, ...config }; 34 | 35 | const mergedConfig = mergeConfig(this.defaults, _config); 36 | 37 | // filter out skipped interceptors 38 | const requestInterceptorChain: ( 39 | | UnInterceptorManagerHandlerFulfilled> 40 | | UnInterceptorManagerHandlerRejected 41 | | undefined 42 | )[] = []; 43 | let synchronousRequestInterceptors = true; 44 | this.interceptors.request.each((interceptor) => { 45 | if ( 46 | typeof interceptor.runWhen === "function" && 47 | interceptor.runWhen(mergedConfig) === false 48 | ) { 49 | return; 50 | } 51 | synchronousRequestInterceptors = 52 | synchronousRequestInterceptors && (interceptor?.synchronous ?? false); 53 | requestInterceptorChain.unshift( 54 | interceptor.fulfilled, 55 | interceptor.rejected, 56 | ); 57 | }); 58 | 59 | const responseInterceptorChain: ( 60 | | UnInterceptorManagerHandlerFulfilled> 61 | | UnInterceptorManagerHandlerRejected 62 | | undefined 63 | )[] = []; 64 | this.interceptors.response.each((interceptor) => { 65 | responseInterceptorChain.push( 66 | interceptor.fulfilled, 67 | interceptor.rejected, 68 | ); 69 | }); 70 | 71 | // TODO: better types 72 | let promise: any; 73 | let i = 0; 74 | let len = 0; 75 | 76 | if (!synchronousRequestInterceptors) { 77 | const chain: ( 78 | | UnInterceptorManagerHandlerFulfilled> 79 | | UnInterceptorManagerHandlerRejected 80 | | UnInterceptorManagerHandlerFulfilled> 81 | | undefined 82 | )[] = [dispatchRequest.bind(this), undefined]; 83 | chain.unshift(...requestInterceptorChain); 84 | chain.push(...responseInterceptorChain); 85 | len = chain.length; 86 | 87 | promise = Promise.resolve(mergedConfig); 88 | 89 | while (i < len) { 90 | promise = promise.then(chain[i++], chain[i++]); 91 | } 92 | 93 | return promise; 94 | } 95 | 96 | len = requestInterceptorChain.length; 97 | 98 | let newConfig = mergedConfig; 99 | 100 | i = 0; 101 | 102 | while (i < len) { 103 | const onFulfilled = requestInterceptorChain[ 104 | i++ 105 | ] as UnInterceptorManagerHandlerFulfilled>; 106 | const onRejected = requestInterceptorChain[ 107 | i++ 108 | ] as UnInterceptorManagerHandlerRejected; 109 | try { 110 | newConfig = onFulfilled(newConfig); 111 | } catch (error) { 112 | onRejected.call(this, error); 113 | break; 114 | } 115 | } 116 | 117 | try { 118 | promise = dispatchRequest.call(this, newConfig); 119 | } catch (error) { 120 | return Promise.reject(error); 121 | } 122 | 123 | i = 0; 124 | len = responseInterceptorChain.length; 125 | 126 | while (i < len) { 127 | promise = promise.then( 128 | responseInterceptorChain[i++], 129 | responseInterceptorChain[i++], 130 | ); 131 | } 132 | 133 | return promise; 134 | } 135 | 136 | async request>( 137 | configOrUrl: string | UnConfig, 138 | config?: UnConfig, 139 | ): Promise { 140 | try { 141 | return await this._request(configOrUrl, config); 142 | } catch (error) { 143 | if (error instanceof Error) { 144 | let dummy: any = {}; 145 | Error.captureStackTrace 146 | ? Error.captureStackTrace(dummy) 147 | : // biome-ignore lint/suspicious/noAssignInExpressions: follow axios implementation 148 | (dummy = new Error()); 149 | // slice off the Error: ... line 150 | const stack = dummy.stack ? dummy.stack.replace(/^.+\n/, "") : ""; 151 | if (!error.stack) { 152 | error.stack = stack; 153 | // match without the 2 top stack lines 154 | } else if ( 155 | stack && 156 | !String(error.stack).endsWith(stack.replace(/^.+\n.+\n/, "")) 157 | ) { 158 | error.stack += `\n${stack}`; 159 | } 160 | } 161 | throw error; 162 | } 163 | } 164 | 165 | download>( 166 | configOrUrl: string | UnConfig, 167 | config?: UnConfig, 168 | ): Promise { 169 | return this.request(configOrUrl, { ...config, adapter: "download" }); 170 | } 171 | 172 | upload>( 173 | configOrUrl: string | UnConfig, 174 | config?: UnConfig, 175 | ): Promise { 176 | return this.request(configOrUrl, { ...config, adapter: "upload" }); 177 | } 178 | 179 | get>( 180 | url: string, 181 | config?: UnConfig, 182 | ): Promise { 183 | return this.request({ 184 | ...config, 185 | method: "GET", 186 | url, 187 | }); 188 | } 189 | 190 | delete>( 191 | url: string, 192 | config?: UnConfig, 193 | ): Promise { 194 | return this.request({ 195 | ...config, 196 | method: "DELETE", 197 | url, 198 | }); 199 | } 200 | 201 | head>( 202 | url: string, 203 | config?: UnConfig, 204 | ): Promise { 205 | return this.request({ 206 | ...config, 207 | method: "HEAD", 208 | url, 209 | }); 210 | } 211 | 212 | options>( 213 | url: string, 214 | config?: UnConfig, 215 | ): Promise { 216 | return this.request({ 217 | ...config, 218 | method: "OPTIONS", 219 | url, 220 | }); 221 | } 222 | 223 | trace>( 224 | url: string, 225 | config?: UnConfig, 226 | ): Promise { 227 | return this.request({ 228 | ...config, 229 | method: "TRACE", 230 | url, 231 | }); 232 | } 233 | 234 | connect>( 235 | url: string, 236 | config?: UnConfig, 237 | ): Promise { 238 | return this.request({ 239 | ...config, 240 | method: "CONNECT", 241 | url, 242 | }); 243 | } 244 | 245 | post>( 246 | url: string, 247 | data?: DD, 248 | config?: UnConfig, 249 | ): Promise { 250 | return this.request({ 251 | ...config, 252 | method: "POST", 253 | url, 254 | data, 255 | }); 256 | } 257 | 258 | put>( 259 | url: string, 260 | data?: DD, 261 | config?: UnConfig, 262 | ): Promise { 263 | return this.request({ 264 | ...config, 265 | method: "PUT", 266 | url, 267 | data, 268 | }); 269 | } 270 | 271 | patch>( 272 | url: string, 273 | data?: DD, 274 | config?: UnConfig, 275 | ): Promise { 276 | return this.request({ 277 | ...config, 278 | method: "PATCH", 279 | url, 280 | data, 281 | }); 282 | } 283 | 284 | getUri(config: UnConfig) { 285 | const mergedConfig = mergeConfig(this.defaults, config); 286 | const fullPath = buildFullPath( 287 | mergedConfig?.baseUrl ?? "", 288 | mergedConfig?.url ?? "", 289 | mergedConfig?.allowAbsoluteUrls ?? true, 290 | ); 291 | return buildUrl( 292 | fullPath, 293 | mergedConfig?.params, 294 | mergedConfig?.paramsSerializer, 295 | ); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /packages/core/src/core/UnCancelToken.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { UnCancelToken, type UnCanceler } from "./UnCancelToken"; 3 | import { UnCanceledError } from "./UnCanceledError"; 4 | 5 | function noop() {} 6 | 7 | describe("core:UnCancelToken", () => { 8 | describe("constructor", () => { 9 | it("throws when executor is not specified", () => { 10 | expect(() => { 11 | // @ts-expect-error Expected 1 arguments, but got 0.ts(2554) 12 | new UnCancelToken(); 13 | }).toThrowError(new TypeError("executor must be a function.")); 14 | }); 15 | 16 | it("throws when executor is not a function", () => { 17 | expect(() => { 18 | // @ts-expect-error argument of type 'number' is not assignable to parameter of type '(cancel: UnCanceler) => void'.ts(2345) 19 | new UnCancelToken(123); 20 | }).toThrowError(new TypeError("executor must be a function.")); 21 | }); 22 | }); 23 | 24 | describe("reason", () => { 25 | it("returns a UnCanceledError if cancellation has been requested", () => { 26 | let cancel: UnCanceler; 27 | const token = new UnCancelToken((c) => { 28 | cancel = c; 29 | }); 30 | // @ts-expect-error Variable 'cancel' is used before being assigned.ts(2454) 31 | cancel("Operation has been canceled."); 32 | expect(token.reason).toEqual(expect.any(UnCanceledError)); 33 | expect(token.reason?.message).toBe("Operation has been canceled."); 34 | }); 35 | 36 | it("returns undefined if cancellation has not been requested", () => { 37 | const token = new UnCancelToken(noop); 38 | expect(token.reason).toBeUndefined(); 39 | }); 40 | }); 41 | 42 | describe("promise", () => { 43 | it("returns a Promise that resolves when cancellation is requested", () => 44 | new Promise((done) => { 45 | let cancel: UnCanceler; 46 | const token = new UnCancelToken((c) => { 47 | cancel = c; 48 | }); 49 | token.promise.then((value) => { 50 | expect(value).toEqual(expect.any(UnCanceledError)); 51 | expect(value.message).toBe("Operation has been canceled."); 52 | done(); 53 | }); 54 | // @ts-expect-error Variable 'cancel' is used before being assigned.ts(2454) 55 | cancel("Operation has been canceled."); 56 | })); 57 | }); 58 | 59 | describe("throwIfRequested", () => { 60 | it("throws if cancellation has been requested", () => { 61 | // Note: we cannot use expect.toThrowError here as UnCanceledError does not inherit from Error 62 | let cancel: UnCanceler; 63 | const token = new UnCancelToken((c) => { 64 | cancel = c; 65 | }); 66 | // @ts-expect-error Variable 'cancel' is used before being assigned.ts(2454) 67 | cancel("Operation has been canceled."); 68 | try { 69 | token.throwIfRequested(); 70 | } catch (error) { 71 | expect(error).toBeInstanceOf(UnCanceledError); 72 | expect((error as UnCanceledError).message).toBe( 73 | "Operation has been canceled.", 74 | ); 75 | } 76 | }); 77 | 78 | it("does not throw if cancellation has not been requested", () => { 79 | const token = new UnCancelToken(noop); 80 | token.throwIfRequested(); 81 | }); 82 | }); 83 | 84 | describe("source", () => { 85 | it("returns an object containing token and cancel function", () => { 86 | const source = UnCancelToken.source(); 87 | expect(source.token).toEqual(expect.any(UnCancelToken)); 88 | expect(source.cancel).toEqual(expect.any(Function)); 89 | expect(source.token.reason).toBeUndefined(); 90 | source.cancel("Operation has been canceled."); 91 | expect(source.token.reason).toEqual(expect.any(UnCanceledError)); 92 | expect(source.token.reason?.message).toBe("Operation has been canceled."); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /packages/core/src/core/UnCancelToken.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig, UnData, UnTask } from "../types"; 2 | import { UnCanceledError } from "./UnCanceledError"; 3 | 4 | export interface UnCancel { 5 | message?: string; 6 | } 7 | 8 | export interface UnCancelStatic { 9 | new (message?: string): UnCancel; 10 | } 11 | 12 | export type UnCanceler = ( 13 | message?: string, 14 | config?: UnConfig, 15 | task?: UnTask, 16 | ) => void; 17 | 18 | export type UnCancelTokenListener = ( 19 | reason: UnCancel | PromiseLike, 20 | ) => void; 21 | 22 | export interface UnCancelTokenSource { 23 | token: UnCancelToken; 24 | cancel: UnCanceler; 25 | } 26 | 27 | export interface UnCancelTokenStatic { 28 | new (executor: (cancel: UnCanceler) => void): UnCancelToken; 29 | source: () => UnCancelTokenSource; 30 | } 31 | 32 | export class UnCancelToken { 33 | promise: Promise; 34 | reason?: UnCancel; 35 | 36 | private listeners: UnCancelTokenListener[] = []; 37 | 38 | constructor(executor: (cancel: UnCanceler) => void) { 39 | if (typeof executor !== "function") { 40 | throw new TypeError("executor must be a function."); 41 | } 42 | 43 | let resolvePromise: UnCancelTokenListener; 44 | 45 | this.promise = new Promise((resolve) => { 46 | resolvePromise = resolve; 47 | }); 48 | 49 | this.promise.then((cancel) => { 50 | for (const listener of this.listeners) { 51 | listener(cancel); 52 | } 53 | this.listeners = []; 54 | }); 55 | 56 | // biome-ignore lint/suspicious/noThenProperty: 57 | this.promise.then = (onfulfilled) => { 58 | let _resolve: UnCancelTokenListener; 59 | const promise = new Promise((resolve) => { 60 | this.subscribe(resolve); 61 | _resolve = resolve; 62 | }).then(onfulfilled); 63 | 64 | // @ts-expect-error Property 'cancel' does not exist on type 'Promise'.ts(2339) 65 | promise.cancel = () => { 66 | this.unsubscribe(_resolve); 67 | }; 68 | 69 | return promise; 70 | }; 71 | 72 | executor((message, config, request) => { 73 | if (this.reason) { 74 | return; 75 | } 76 | this.reason = new UnCanceledError(message, config, request); 77 | resolvePromise(this.reason); 78 | }); 79 | } 80 | 81 | throwIfRequested() { 82 | if (this.reason) { 83 | throw this.reason; 84 | } 85 | } 86 | 87 | subscribe(listener: UnCancelTokenListener) { 88 | if (this.reason) { 89 | listener(this.reason); 90 | return; 91 | } 92 | this.listeners.push(listener); 93 | } 94 | 95 | unsubscribe(listener: UnCancelTokenListener) { 96 | const index = this.listeners.indexOf(listener); 97 | if (index !== -1) { 98 | this.listeners.splice(index, 1); 99 | } 100 | } 101 | 102 | toAbortSignal() { 103 | const controller = new AbortController(); 104 | 105 | const abort: UnCancelTokenListener = (error) => { 106 | controller.abort(error); 107 | }; 108 | 109 | this.subscribe(abort); 110 | 111 | // @ts-expect-error Property 'unsubscribe' does not exist on type 'AbortSignal'.ts(2339) 112 | controller.signal.unsubscribe = () => this.unsubscribe(abort); 113 | 114 | return controller.signal as AbortSignal & { 115 | unsubscribe: () => void; 116 | }; 117 | } 118 | 119 | static source(): UnCancelTokenSource { 120 | let cancel: UnCanceler; 121 | const token = new UnCancelToken((c) => { 122 | cancel = c; 123 | }); 124 | return { 125 | token, 126 | // @ts-expect-error Variable 'cancel' is used before being assigned.ts(2454) 127 | cancel, 128 | }; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /packages/core/src/core/UnCanceledError.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { UnCanceledError } from "./UnCanceledError"; 3 | 4 | describe("core:UnCanceledError", () => { 5 | describe("toString", () => { 6 | it("returns correct result when message is not specified", () => { 7 | const cancel = new UnCanceledError(); 8 | expect(cancel.toString()).toBe("CanceledError: canceled"); 9 | }); 10 | 11 | it("returns correct result when message is specified", () => { 12 | const cancel = new UnCanceledError("Operation has been canceled."); 13 | expect(cancel.toString()).toBe( 14 | "CanceledError: Operation has been canceled.", 15 | ); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/core/src/core/UnCanceledError.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig, UnData, UnTask } from "../types"; 2 | import { UnError } from "./UnError"; 3 | 4 | class UnCanceledError extends UnError { 5 | isUnCanceledError = true; 6 | 7 | constructor(message?: string, config?: UnConfig, task?: UnTask) { 8 | super(message ?? "canceled"); 9 | 10 | this.name = "CanceledError"; 11 | this.message = message ?? "canceled"; 12 | 13 | this.code = UnError.ERR_CANCELED; 14 | this.config = config; 15 | this.task = task; 16 | } 17 | } 18 | 19 | UnCanceledError.prototype.isUnCanceledError = true; 20 | 21 | export { UnCanceledError }; 22 | -------------------------------------------------------------------------------- /packages/core/src/core/UnError.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { UnError } from "./UnError"; 3 | 4 | describe("core::UnError", () => { 5 | it("should create an Error with message, config, code, task, response, stack and isUnError", () => { 6 | const task = { path: "/foo" }; 7 | const response = { status: 200, data: { foo: "bar" } }; 8 | const error = new UnError( 9 | "Boom!", 10 | "ESOMETHING", 11 | { foo: "bar" }, 12 | task, 13 | response, 14 | ); 15 | expect(error instanceof Error).toBe(true); 16 | expect(error.message).toBe("Boom!"); 17 | expect(error.config).toEqual({ foo: "bar" }); 18 | expect(error.code).toBe("ESOMETHING"); 19 | expect(error.task).toBe(task); 20 | expect(error.response).toBe(response); 21 | expect(error.isUnError).toBe(true); 22 | expect(error.stack).toBeDefined(); 23 | }); 24 | it("should create an Error that can be serialized to JSON", () => { 25 | // Attempting to serialize task and response results in 26 | // TypeError: Converting circular structure to JSON 27 | const task = { path: "/foo" }; 28 | const response = { status: 200, data: { foo: "bar" } }; 29 | const error = new UnError( 30 | "Boom!", 31 | "ESOMETHING", 32 | { foo: "bar" }, 33 | task, 34 | response, 35 | ); 36 | const json = error.toJSON(); 37 | expect(json.message).toBe("Boom!"); 38 | expect(json.config).toEqual({ foo: "bar" }); 39 | expect(json.code).toBe("ESOMETHING"); 40 | expect(json.status).toBe(200); 41 | expect(json.task).toBe(undefined); 42 | expect(json.response).toBe(undefined); 43 | }); 44 | 45 | describe("core::createError.from", () => { 46 | it("should add config, config, task and response to error", () => { 47 | const error = new Error("Boom!"); 48 | const task = { path: "/foo" }; 49 | const response = { status: 200, data: { foo: "bar" } }; 50 | 51 | const urError = UnError.from( 52 | error, 53 | "ESOMETHING", 54 | { foo: "bar" }, 55 | task, 56 | response, 57 | ); 58 | expect(urError.config).toEqual({ foo: "bar" }); 59 | expect(urError.code).toBe("ESOMETHING"); 60 | expect(urError.task).toBe(task); 61 | expect(urError.response).toBe(response); 62 | expect(urError.isUnError).toBe(true); 63 | }); 64 | 65 | it("should return error", () => { 66 | const error = new Error("Boom!"); 67 | expect( 68 | UnError.from(error, "ESOMETHING", { foo: "bar" }) instanceof UnError, 69 | ).toBeTruthy(); 70 | }); 71 | }); 72 | 73 | it("should have status property when response was passed to the constructor", () => { 74 | const err = new UnError("test", "foo", {}, {}, { status: 400 }); 75 | expect(err.status).toBe(400); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /packages/core/src/core/UnError.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig, UnData, UnResponse, UnTask } from "../types"; 2 | 3 | export class UnError extends Error { 4 | static ERR_FR_TOO_MANY_REDIRECTS = "ERR_FR_TOO_MANY_REDIRECTS"; 5 | static ERR_BAD_OPTION_VALUE = "ERR_BAD_OPTION_VALUE"; 6 | static ERR_BAD_OPTION = "ERR_BAD_OPTION"; 7 | static ERR_NETWORK = "ERR_NETWORK"; 8 | static ERR_DEPRECATED = "ERR_DEPRECATED"; 9 | static ERR_BAD_RESPONSE = "ERR_BAD_RESPONSE"; 10 | static ERR_BAD_REQUEST = "ERR_BAD_REQUEST"; 11 | static ERR_NOT_SUPPORT = "ERR_NOT_SUPPORT"; 12 | static ERR_INVALID_URL = "ERR_INVALID_URL"; 13 | static ERR_CANCELED = "ERR_CANCELED"; 14 | static ECONNABORTED = "ECONNABORTED"; 15 | static ETIMEDOUT = "ETIMEDOUT"; 16 | 17 | code?: string; 18 | config?: UnConfig; 19 | task?: UnTask; 20 | response?: UnResponse; 21 | isUnError: boolean; 22 | status?: number; 23 | cause?: Error; 24 | 25 | constructor( 26 | message?: string, 27 | code?: string, 28 | config?: UnConfig, 29 | task?: UnTask, 30 | response?: UnResponse, 31 | ) { 32 | super(message); 33 | 34 | this.name = "UnError"; 35 | this.message = message ?? ""; 36 | if (Error.captureStackTrace) { 37 | Error.captureStackTrace(this, this.constructor); 38 | } else { 39 | this.stack = new Error().stack; 40 | } 41 | 42 | this.code = code; 43 | this.config = config; 44 | this.task = task; 45 | if (response) { 46 | this.response = response; 47 | this.status = response.status ?? undefined; 48 | } 49 | 50 | this.isUnError = true; 51 | } 52 | 53 | toJSON() { 54 | return { 55 | message: this.message, 56 | name: this.name, 57 | // @ts-expect-error no types 58 | description: this.description, 59 | // @ts-expect-error no types 60 | number: this.number, 61 | // @ts-expect-error no types 62 | fileName: this.fileName, 63 | // @ts-expect-error no types 64 | lineNumber: this.lineNumber, 65 | // @ts-expect-error no types 66 | columnNumber: this.columnNumber, 67 | stack: this.stack, 68 | config: this.config, 69 | code: this.code, 70 | status: this.status, 71 | } as { 72 | name: string; 73 | message?: string; 74 | stack?: string; 75 | config?: UnConfig; 76 | code?: string; 77 | status?: number; 78 | [key: string]: any; 79 | }; 80 | } 81 | 82 | static from( 83 | error?: Error, 84 | code?: string, 85 | config?: UnConfig, 86 | task?: UnTask, 87 | response?: UnResponse, 88 | customProps?: Record, 89 | ) { 90 | const urError = new UnError(error?.message, code, config, task, response); 91 | if (customProps) { 92 | Object.assign(urError, customProps); 93 | } 94 | return urError; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/core/src/core/UnInterceptorManager.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig, UnData } from "../types"; 2 | 3 | export interface UnInterceptorOptions { 4 | synchronous?: boolean; 5 | runWhen?: (config: UnConfig) => boolean; 6 | } 7 | 8 | export type UnInterceptorManagerHandlerFulfilled = ( 9 | value: V, 10 | ) => V | Promise; 11 | 12 | export type UnInterceptorManagerHandlerRejected = (error: any) => any; 13 | 14 | export interface UnInterceptorManagerHandler 15 | extends UnInterceptorOptions { 16 | fulfilled?: UnInterceptorManagerHandlerFulfilled; 17 | rejected?: UnInterceptorManagerHandlerRejected; 18 | } 19 | 20 | export class UnInterceptorManager { 21 | private handlers: (UnInterceptorManagerHandler | null)[] = []; 22 | 23 | use( 24 | fulfilled?: UnInterceptorManagerHandlerFulfilled, 25 | rejected?: UnInterceptorManagerHandlerRejected, 26 | options?: UnInterceptorOptions, 27 | ) { 28 | this.handlers.push({ 29 | fulfilled, 30 | rejected, 31 | synchronous: options?.synchronous ?? false, 32 | runWhen: options?.runWhen, 33 | }); 34 | return this.handlers.length - 1; 35 | } 36 | 37 | eject(id: number) { 38 | if (this.handlers[id]) { 39 | this.handlers[id] = null; 40 | } 41 | } 42 | 43 | clear() { 44 | if (this.handlers) { 45 | this.handlers = []; 46 | } 47 | } 48 | 49 | each(fn: (handler: UnInterceptorManagerHandler) => any) { 50 | for (const handler of this.handlers) { 51 | if (handler && fn) { 52 | fn(handler); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/core/src/core/dispatchRequest.ts: -------------------------------------------------------------------------------- 1 | import { adapters, requestAdapter } from "../adapters"; 2 | import type { UnConfig, UnData } from "../types"; 3 | import { UnCanceledError } from "./UnCanceledError"; 4 | import { isUnCancel } from "./isUnCancel"; 5 | 6 | const throwIfCancellationRequested = ( 7 | config: UnConfig, 8 | ) => { 9 | if (config.cancelToken) { 10 | config.cancelToken?.throwIfRequested(); 11 | } 12 | 13 | if (config.signal?.aborted) { 14 | throw new UnCanceledError(); 15 | } 16 | }; 17 | 18 | export const dispatchRequest = ( 19 | config: UnConfig, 20 | ) => { 21 | throwIfCancellationRequested(config); 22 | 23 | let adapter = requestAdapter; 24 | if (typeof config.adapter === "string" && adapters[config.adapter]) { 25 | adapter = adapters[config.adapter]; 26 | } else if (typeof config.adapter === "function") { 27 | adapter = config.adapter; 28 | } 29 | 30 | return adapter(config).then( 31 | (response) => { 32 | throwIfCancellationRequested(config); 33 | return response; 34 | }, 35 | (error) => { 36 | if (!isUnCancel(error)) { 37 | throwIfCancellationRequested(config); 38 | } 39 | throw error; 40 | }, 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/core/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./dispatchRequest"; 2 | export * from "./HttpStatusCode"; 3 | export * from "./isUnCancel"; 4 | export * from "./isUnError"; 5 | export * from "./settle"; 6 | export * from "./Un"; 7 | export * from "./UnCanceledError"; 8 | export * from "./UnCancelToken"; 9 | export * from "./UnError"; 10 | export * from "./UnInterceptorManager"; 11 | -------------------------------------------------------------------------------- /packages/core/src/core/isUnCancel.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { UnCanceledError } from "./UnCanceledError"; 3 | import { isUnCancel } from "./isUnCancel"; 4 | 5 | describe("core:isCancel", () => { 6 | it("returns true if value is a CanceledError", () => { 7 | expect(isUnCancel(new UnCanceledError())).toBe(true); 8 | }); 9 | 10 | it("returns false if value is not a CanceledError", () => { 11 | expect(isUnCancel({ foo: "bar" })).toBe(false); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/core/src/core/isUnCancel.ts: -------------------------------------------------------------------------------- 1 | import type { UnCancel } from "./UnCancelToken"; 2 | 3 | export const isUnCancel = (value: any): value is UnCancel => 4 | value?.isUnCanceledError === true; 5 | -------------------------------------------------------------------------------- /packages/core/src/core/isUnError.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { UnError } from "./UnError"; 3 | import { isUnError } from "./isUnError"; 4 | 5 | describe("core::isUnError", () => { 6 | it("should return true if the error is created by core::createError", () => { 7 | expect(isUnError(new UnError("Boom!", undefined, { foo: "bar" }))).toBe( 8 | true, 9 | ); 10 | }); 11 | 12 | it("should return true if the error is enhanced by core::enhanceError", () => { 13 | expect( 14 | isUnError(UnError.from(new Error("Boom!"), undefined, { foo: "bar" })), 15 | ).toBe(true); 16 | }); 17 | 18 | it("should return false if the error is a normal Error instance", () => { 19 | expect(isUnError(new Error("Boom!"))).toBe(false); 20 | }); 21 | 22 | it("should return false if the error is null", () => { 23 | expect(isUnError(null)).toBe(false); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/core/src/core/isUnError.ts: -------------------------------------------------------------------------------- 1 | import type { UnData } from "../types"; 2 | import type { UnError } from "./UnError"; 3 | 4 | export const isUnError = ( 5 | value: any, 6 | ): value is UnError => value?.isUnError === true; 7 | -------------------------------------------------------------------------------- /packages/core/src/core/settle.test.ts: -------------------------------------------------------------------------------- 1 | import { type Mock, beforeEach, describe, expect, it, vi } from "vitest"; 2 | import type { UnResponse } from "../types"; 3 | import { settle } from "./settle"; 4 | 5 | describe("core::settle", () => { 6 | let resolve: Mock<(response: UnResponse | PromiseLike) => void>; 7 | let reject: Mock<(reason?: any) => void>; 8 | 9 | beforeEach(() => { 10 | resolve = vi.fn(); 11 | reject = vi.fn(); 12 | }); 13 | 14 | it("should resolve promise if status is not set", async () => { 15 | const response = { 16 | config: { 17 | validateStatus: () => true, 18 | }, 19 | }; 20 | settle(resolve, reject, response); 21 | expect(resolve).toHaveBeenCalledWith(response); 22 | expect(reject).not.toHaveBeenCalled(); 23 | }); 24 | 25 | it("should resolve promise if validateStatus is not set", () => { 26 | const response = { 27 | status: 500, 28 | config: {}, 29 | }; 30 | settle(resolve, reject, response); 31 | expect(resolve).toHaveBeenCalledWith(response); 32 | expect(reject).not.toHaveBeenCalled(); 33 | }); 34 | 35 | it("should resolve promise if validateStatus returns true", () => { 36 | const response = { 37 | status: 500, 38 | config: { 39 | validateStatus: () => true, 40 | }, 41 | }; 42 | settle(resolve, reject, response); 43 | expect(resolve).toHaveBeenCalledWith(response); 44 | expect(reject).not.toHaveBeenCalled(); 45 | }); 46 | 47 | it("should reject promise if validateStatus returns false", () => { 48 | const task = { 49 | path: "/foo", 50 | }; 51 | const response = { 52 | status: 500, 53 | config: { 54 | validateStatus: () => false, 55 | }, 56 | task, 57 | }; 58 | settle(resolve, reject, response); 59 | expect(resolve).not.toHaveBeenCalled(); 60 | expect(reject).toHaveBeenCalled(); 61 | const reason = reject.mock.calls[0][0]; 62 | expect(reason instanceof Error).toBe(true); 63 | expect(reason.message).toBe("Request failed with status code 500"); 64 | expect(reason.config).toBe(response.config); 65 | expect(reason.task).toBe(task); 66 | expect(reason.response).toBe(response); 67 | }); 68 | 69 | it("should pass status to validateStatus", () => { 70 | const validateStatus = vi.fn(); 71 | const response = { 72 | status: 500, 73 | config: { 74 | validateStatus: validateStatus, 75 | }, 76 | }; 77 | settle(resolve, reject, response); 78 | expect(validateStatus).toHaveBeenCalledWith(500); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/core/src/core/settle.ts: -------------------------------------------------------------------------------- 1 | import type { UnData, UnResponse } from "../types"; 2 | import { UnError } from "./UnError"; 3 | 4 | export const settle = < 5 | T = UnData, 6 | D = UnData, 7 | R extends UnResponse = UnResponse, 8 | >( 9 | resolve: (value: R | PromiseLike) => void, 10 | reject: (reason?: any) => void, 11 | response: R, 12 | ) => { 13 | const validateStatus = response?.config?.validateStatus; 14 | if (!response.status || !validateStatus || validateStatus(response.status)) { 15 | resolve(response); 16 | } else { 17 | reject( 18 | new UnError( 19 | `Request failed with status code ${response.status}`, 20 | [UnError.ERR_BAD_REQUEST, UnError.ERR_BAD_RESPONSE][ 21 | Math.floor(response.status / 100) - 4 22 | ], 23 | response.config, 24 | response.task, 25 | response, 26 | ), 27 | ); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /packages/core/src/defaults/index.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig } from "../types"; 2 | 3 | export const defaults: Partial = { 4 | adapter: "request", 5 | validateStatus: (status) => status >= 200 && status < 300, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/core/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { un } from "."; 3 | import { version } from "../package.json"; 4 | import { 5 | HttpStatusCode, 6 | Un, 7 | UnCancelToken, 8 | UnCanceledError, 9 | UnError, 10 | UnInterceptorManager, 11 | isUnCancel, 12 | isUnError, 13 | } from "./core"; 14 | import { defaults } from "./defaults"; 15 | import { mergeConfig } from "./utils"; 16 | 17 | describe("index", () => { 18 | it("un should be defined", () => { 19 | expect(un).toBeDefined(); 20 | }); 21 | it("un should be a function", () => { 22 | expect(un).toSatisfy((fn) => typeof fn === "function"); 23 | }); 24 | it("un should have specific properties", () => { 25 | expect(un).toHaveProperty("defaults"); 26 | expect(un).toHaveProperty("interceptors"); 27 | expect(un).toHaveProperty("request"); 28 | expect(un).toHaveProperty("download"); 29 | expect(un).toHaveProperty("upload"); 30 | expect(un).toHaveProperty("get"); 31 | expect(un).toHaveProperty("delete"); 32 | expect(un).toHaveProperty("head"); 33 | expect(un).toHaveProperty("options"); 34 | expect(un).toHaveProperty("trace"); 35 | expect(un).toHaveProperty("connect"); 36 | expect(un).toHaveProperty("post"); 37 | expect(un).toHaveProperty("put"); 38 | expect(un).toHaveProperty("patch"); 39 | expect(un).toHaveProperty("getUri"); 40 | expect(un).toHaveProperty("create"); 41 | expect(un).toHaveProperty("Un"); 42 | expect(un).toHaveProperty("CanceledError"); 43 | expect(un).toHaveProperty("CancelToken"); 44 | expect(un).toHaveProperty("isCancel"); 45 | expect(un).toHaveProperty("VERSION"); 46 | expect(un).toHaveProperty("UnError"); 47 | expect(un).toHaveProperty("isUnError"); 48 | expect(un).toHaveProperty("all"); 49 | expect(un).toHaveProperty("mergeConfig"); 50 | expect(un).toHaveProperty("HttpStatusCode"); 51 | }); 52 | it("un properties expectation", () => { 53 | expect(un.defaults).toBe(defaults); 54 | expect(un.interceptors.request).toBeInstanceOf(UnInterceptorManager); 55 | expect(un.interceptors.response).toBeInstanceOf(UnInterceptorManager); 56 | expect(un.request).toBeInstanceOf(Function); 57 | expect(un.download).toBeInstanceOf(Function); 58 | expect(un.upload).toBeInstanceOf(Function); 59 | expect(un.get).toBeInstanceOf(Function); 60 | expect(un.delete).toBeInstanceOf(Function); 61 | expect(un.head).toBeInstanceOf(Function); 62 | expect(un.options).toBeInstanceOf(Function); 63 | expect(un.trace).toBeInstanceOf(Function); 64 | expect(un.connect).toBeInstanceOf(Function); 65 | expect(un.post).toBeInstanceOf(Function); 66 | expect(un.put).toBeInstanceOf(Function); 67 | expect(un.patch).toBeInstanceOf(Function); 68 | expect(un.getUri).toBeInstanceOf(Function); 69 | expect(un.create).toBeInstanceOf(Function); 70 | expect(un.Un).toBe(Un); 71 | expect(un.CanceledError).toBe(UnCanceledError); 72 | expect(un.CancelToken).toBe(UnCancelToken); 73 | expect(un.isCancel).toBe(isUnCancel); 74 | expect(un.VERSION).toBe(version); 75 | expect(un.UnError).toBe(UnError); 76 | expect(un.isUnError).toBe(isUnError); 77 | expect(un.mergeConfig).toBe(mergeConfig); 78 | expect(un.HttpStatusCode).toBe(HttpStatusCode); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | import { version } from "../package.json"; 2 | import { 3 | HttpStatusCode, 4 | Un, 5 | type UnCancel, 6 | UnCancelToken, 7 | type UnCancelTokenStatic, 8 | UnCanceledError, 9 | UnError, 10 | isUnCancel, 11 | isUnError, 12 | } from "./core"; 13 | import { defaults } from "./defaults"; 14 | import type { UnConfig, UnData, UnResponse } from "./types"; 15 | import { extend, mergeConfig } from "./utils"; 16 | 17 | export interface UnInstance extends Un { 18 | >( 19 | config: UnConfig, 20 | ): Promise; 21 | >( 22 | url: string, 23 | config?: UnConfig, 24 | ): Promise; 25 | 26 | defaults: UnConfig; 27 | } 28 | 29 | export interface UnStatic extends UnInstance { 30 | create: (config?: UnConfig) => UnInstance; 31 | 32 | Un: typeof Un; 33 | 34 | CanceledError: typeof UnCanceledError; 35 | CancelToken: UnCancelTokenStatic; 36 | isCancel: (value: any) => value is UnCancel; 37 | 38 | VERSION: string; 39 | 40 | UnError: typeof UnError; 41 | isUnError: (value: any) => value is UnError; 42 | 43 | all: (values: Array>) => Promise; 44 | 45 | mergeConfig: typeof mergeConfig; 46 | 47 | HttpStatusCode: typeof HttpStatusCode; 48 | } 49 | 50 | const createInstance = ( 51 | defaultConfig: UnConfig, 52 | ) => { 53 | const context = new Un(defaultConfig); 54 | const instance = Un.prototype.request.bind(context) as UnStatic; 55 | 56 | // Copy ur.prototype to instance 57 | extend(instance, Un.prototype, context, { allOwnKeys: true }); 58 | 59 | // Copy context to instance 60 | extend(instance, context, null, { allOwnKeys: true }); 61 | 62 | // Factory for creating new instances 63 | instance.create = (instanceConfig) => 64 | createInstance(mergeConfig(defaultConfig, instanceConfig)); 65 | 66 | return instance; 67 | }; 68 | 69 | // Create the default instance to be exported 70 | const un = createInstance(defaults); 71 | 72 | // Expose Un class to allow class inheritance 73 | un.Un = Un; 74 | 75 | // Expose CanceledError & CancelToken & isCancel 76 | un.CanceledError = UnCanceledError; 77 | un.CancelToken = UnCancelToken; 78 | un.isCancel = isUnCancel; 79 | 80 | // version 81 | un.VERSION = version; 82 | 83 | // Expose UnError & isUnError 84 | un.UnError = UnError; 85 | un.isUnError = isUnError; 86 | 87 | // Expose all/spread 88 | un.all = (promises) => Promise.all(promises); 89 | 90 | // Expose mergeConfig 91 | un.mergeConfig = mergeConfig; 92 | 93 | // Expose HttpStatusCode 94 | un.HttpStatusCode = HttpStatusCode; 95 | 96 | export * from "./adapters"; 97 | export * from "./core"; 98 | export * from "./defaults"; 99 | export * from "./utils"; 100 | export * from "./types"; 101 | export { un }; 102 | 103 | export default un; 104 | -------------------------------------------------------------------------------- /packages/core/src/types/adapter.ts: -------------------------------------------------------------------------------- 1 | import type { UnData } from "./common"; 2 | import type { UnConfig } from "./config"; 3 | import type { UnPromise } from "./promise"; 4 | 5 | export type UnAdapter = ( 6 | config: UnConfig, 7 | ) => UnPromise; 8 | -------------------------------------------------------------------------------- /packages/core/src/types/common.ts: -------------------------------------------------------------------------------- 1 | // T 表示响应数据 2 | // D 表示请求数据 3 | export type UnData = string | Record | ArrayBuffer; 4 | 5 | export type UnMethod = 6 | | "get" 7 | | "GET" 8 | | "delete" 9 | | "DELETE" 10 | | "head" 11 | | "HEAD" 12 | | "options" 13 | | "OPTIONS" 14 | | "post" 15 | | "POST" 16 | | "put" 17 | | "PUT" 18 | | "patch" 19 | | "PATCH" 20 | | "trace" 21 | | "TRACE" 22 | | "connect" 23 | | "CONNECT"; 24 | 25 | export type UnHeaders = Record; 26 | 27 | export type UnParams = Record; 28 | 29 | export type UnParamsSerializer = (params?: UnParams) => string; 30 | 31 | export type UnValidateStatus = (status: number) => boolean | null; 32 | 33 | export interface UnGenericAbortSignal { 34 | readonly aborted: boolean; 35 | onabort?: ((...args: any) => any) | null; 36 | addEventListener?: (...args: any) => any; 37 | removeEventListener?: (...args: any) => any; 38 | } 39 | 40 | export type UnDataType = "json" | string; 41 | 42 | export type UnResponseType = "text" | "arraybuffer"; 43 | 44 | export interface UnFile { 45 | name?: string; 46 | file?: File; 47 | uri?: string; 48 | } 49 | 50 | export type UnOnProgress = (response?: { 51 | /** 当前上传/下载百分比 */ 52 | progress?: number; 53 | /** 已经上传的数据长度,单位 Bytes */ 54 | totalBytesSent?: number; 55 | /** 预期需要上传的数据总长度,单位 Bytes */ 56 | totalBytesExpectedToSend?: number; 57 | /** 已经下载的数据长度,单位 Bytes */ 58 | totalBytesWritten?: number; 59 | /** 预期需要下载的数据总长度,单位 Bytes */ 60 | totalBytesExpectedToWrite?: number; 61 | }) => void; 62 | 63 | export type UnFileType = "image" | "video" | "audio"; 64 | 65 | export interface UnProfile { 66 | /** 67 | * 第一个 HTTP 重定向发生时的时间 68 | * 69 | * 有跳转且是同域名内的重定向才算,否则值为 0 70 | */ 71 | redirectStart?: number; 72 | /** 73 | * 最后一个 HTTP 重定向完成时的时间 74 | * 75 | * 有跳转且是同域名内部的重定向才算,否则值为 0 76 | */ 77 | redirectEnd?: number; 78 | /** 组件准备好使用 HTTP 请求抓取资源的时间,这发生在检查本地缓存之前 */ 79 | fetchStart?: number; 80 | /** 81 | * DNS 域名查询开始的时间 82 | * 83 | * 如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等 84 | */ 85 | domainLookupStart?: number; 86 | /** 87 | * DNS 域名查询完成的时间 88 | * 89 | * 如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等 90 | */ 91 | domainLookupEnd?: number; 92 | /** 93 | * HTTP(TCP) 开始建立连接的时间 94 | * 95 | * 如果是持久连接,则与 fetchStart 值相等 96 | * 97 | * 如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间 98 | */ 99 | connectStart?: number; 100 | /** 101 | * HTTP(TCP) 完成建立连接的时间(完成握手) 102 | * 103 | * 如果是持久连接,则与 fetchStart 值相等 104 | * 105 | * 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间 106 | * 107 | * 这里的完成握手包括安全连接建立完成、SOCKS 授权通过 108 | */ 109 | connectEnd?: number; 110 | /** 111 | * SSL建立连接的时间 112 | * 113 | * 如果不是安全连接,则值为 0 114 | */ 115 | SSLconnectionStart?: number; 116 | /** 117 | * SSL 建立完成的时间 118 | * 119 | * 如果不是安全连接,则值为 0 120 | */ 121 | SSLconnectionEnd?: number; 122 | /** 123 | * HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存 124 | * 125 | * 连接错误重连时,这里显示的也是新建立连接的时间 126 | */ 127 | requestStart?: number; 128 | /** HTTP 请求读取真实文档结束的时间 */ 129 | requestEnd?: number; 130 | /** HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存 */ 131 | responseStart?: number; 132 | /** HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存 */ 133 | responseEnd?: number; 134 | /** 当次请求连接过程中实时 rtt */ 135 | rtt?: number; 136 | /** 评估的网络状态 */ 137 | estimate_nettype?: string | number; 138 | /** 协议层根据多个请求评估当前网络的 rtt(仅供参考) */ 139 | httpRttEstimate?: number; 140 | /** 传输层根据多个请求评估的当前网络的 rtt(仅供参考) */ 141 | transportRttEstimate?: number; 142 | /** 评估当前网络下载的 kbps */ 143 | downstreamThroughputKbpsEstimate?: number; 144 | /** 当前网络的实际下载 kbps */ 145 | throughputKbps?: number; 146 | /** 当前请求的 IP */ 147 | peerIP?: string; 148 | /** 当前请求的端口 */ 149 | port?: number; 150 | /** 是否复用连接 */ 151 | socketReused?: boolean; 152 | /** 发送的字节数 */ 153 | sendBytesCount?: number; 154 | /** 收到字节数 */ 155 | receivedBytedCount?: number; 156 | /** 使用协议类型 */ 157 | protocol?: "http1.1" | "h2" | "quic" | "unknown" | string; 158 | } 159 | -------------------------------------------------------------------------------- /packages/core/src/types/config.ts: -------------------------------------------------------------------------------- 1 | import type { UnCancelToken } from "../core/UnCancelToken"; 2 | import type { UnAdapter } from "./adapter"; 3 | import type { 4 | UnData, 5 | UnDataType, 6 | UnFile, 7 | UnFileType, 8 | UnGenericAbortSignal, 9 | UnHeaders, 10 | UnMethod, 11 | UnOnProgress, 12 | UnParams, 13 | UnParamsSerializer, 14 | UnResponseType, 15 | UnValidateStatus, 16 | } from "./common"; 17 | 18 | export interface UnConfig { 19 | /** 用于请求的服务器 URL */ 20 | url?: string; 21 | /** 22 | * 创建请求时使用的方法 23 | * 24 | * 默认为 'GET' 25 | */ 26 | method?: UnMethod; 27 | /** 自动加在 `url` 前面,除非 `url` 是一个绝对 URL 且选项 `allowAbsoluteUrls` 为 true */ 28 | baseUrl?: string; 29 | /** 30 | * 决定是否允许绝对 URL 覆盖配置的 `baseUrl` 31 | * 32 | * 当设置为 true(默认)时,绝对值的 `url` 会覆盖 `baseUrl` 33 | * 34 | * 当设置为 false 时,绝对值的 `url` 会始终被 `baseUrl` 前置 35 | */ 36 | allowAbsoluteUrls?: boolean; 37 | /** 自定义请求头,不能设置 Referer */ 38 | headers?: UnHeaders; 39 | /** 与请求一起发送的 URL 参数 */ 40 | params?: UnParams; 41 | /** 42 | * 可选方法,主要用于序列化 `params` 43 | * 44 | * 默认使用 [fast-querystring](https://github.com/anonrig/fast-querystring) 45 | * 序列化,需要自行处理嵌套值 46 | * 47 | * [picoquery](https://github.com/43081j/picoquery) 在 fast-querystring 48 | * 基础上支持嵌套值、增加可配置性 49 | * 50 | * [qs](https://github.com/ljharb/qs) 包含大量无用的兼容代码,占用额外体积,如无必要不建议使用 51 | * 52 | * [qs](https://github.com/ljharb/qs) v6.10.0 引入了 `get-intrinsic` 53 | * 导致结合微信小程序和微信小程序插件使用时出现报错,可使用 v6.9.7 54 | * 55 | * [query-string](https://github.com/sindresorhus/query-string) 体积性能都较好,支持完善 56 | * 57 | * [query-string](https://github.com/sindresorhus/query-string) 基于 58 | * [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component),它使用了部分小程序(如支付宝小程序)不支持的语法(可选的 59 | * catch 参数,Optional catch Binding),需自行修改处理 60 | */ 61 | paramsSerializer?: UnParamsSerializer; 62 | /** 63 | * 作为请求体被发送的数据 64 | * 65 | * 必须是以下类型之一:string、ArrayBuffer、Record 66 | */ 67 | data?: D; 68 | /** 69 | * 指定请求超时的毫秒数 70 | * 71 | * 如果请求时间超过 `timeout` 的值,则请求会被中断 72 | * 73 | * 要设置永不超时,可以将其设置为 Number.POSITIVE_INFINITY 74 | * 75 | * 默认值是实际调用的 API 的默认值 76 | */ 77 | timeout?: number; 78 | /** 79 | * 允许自定义处理请求 80 | * 81 | * 可以指定为 'request'、`upload` 和 `download` 三者之一 82 | * 83 | * 也可以指定为一个方法,返回一个 Promise 并提供一个有效的响应 84 | * 85 | * 如果你正在使用 un.request、un.download、un.upload、un.get 等别名方法,则无需再指定该键的值 86 | * 87 | * 默认为 'request' 88 | */ 89 | adapter?: "request" | "download" | "upload" | UnAdapter; 90 | /** 91 | * 定义了对于给定的 HTTP 状态码该 resolve 还是 reject 92 | * 93 | * 如果 `validateStatus` 返回 `true`、`null` 或 `undefined` 94 | * 95 | * 则 promise 将会被 resolve,否则会被 reject 96 | * 97 | * 默认为 (status) => status >= 200 && status < 300 98 | */ 99 | validateStatus?: UnValidateStatus; 100 | /** 用于取消请求 */ 101 | signal?: UnGenericAbortSignal; 102 | /** 用于取消请求 */ 103 | cancelToken?: UnCancelToken; 104 | /** 105 | * 监听 HTTP Response Header 事件 106 | * 107 | * 会比请求完成事件更早 108 | */ 109 | onHeadersReceived?: (response?: { headers?: UnHeaders }) => void; 110 | 111 | /** 112 | * Request 使用 113 | * 114 | * 服务器返回数据的类型 115 | * 116 | * 如果设置为 json,会尝试对返回的数据做一次 JSON.parse 117 | * 118 | * 默认为 json 119 | */ 120 | dataType?: UnDataType; 121 | /** 122 | * Request 使用 123 | * 124 | * 响应的数据类型 125 | * 126 | * 默认为 text 127 | */ 128 | responseType?: UnResponseType; 129 | /** 130 | * Request 使用 131 | * 132 | * 是否开启 http2 133 | * 134 | * 默认为 false 135 | */ 136 | enableHttp2?: boolean; 137 | /** 138 | * Request 使用 139 | * 140 | * 是否开启 quic 141 | * 142 | * 默认为 false 143 | */ 144 | enableQuic?: boolean; 145 | /** 146 | * Request 使用 147 | * 148 | * 是否开启缓存 149 | * 150 | * 默认为 false 151 | */ 152 | enableCache?: boolean; 153 | /** 154 | * Request 使用 155 | * 156 | * 是否开启 HttpDNS 服务 157 | * 158 | * 默认为 false 159 | */ 160 | enableHttpDNS?: boolean; 161 | /** 162 | * Request 使用 163 | * 164 | * HttpDNS 服务商 Id 165 | */ 166 | httpDNSServiceId?: string; 167 | /** 168 | * Request 使用 169 | * 170 | * 是否开启 transfer-encoding chunked 171 | * 172 | * 默认为 false 173 | */ 174 | enableChunked?: boolean; 175 | /** 176 | * Request 使用 177 | * 178 | * 是否在 wifi 下使用移动网络发送请求 179 | * 180 | * 默认为 false 181 | */ 182 | forceCellularNetwork?: boolean; 183 | /** 184 | * Request 使用 185 | * 186 | * 是否验证 ssl 证书 187 | * 188 | * 默认为 true 189 | */ 190 | sslVerify?: boolean; 191 | /** 192 | * Request 使用 193 | * 194 | * 跨域请求时是否需要使用凭证 195 | * 196 | * 默认为 false 197 | */ 198 | withCredentials?: boolean; 199 | /** 200 | * Request 使用 201 | * 202 | * 是否在 DNS 解析时优先使用 ipv4 203 | * 204 | * 默认为 false 205 | */ 206 | firstIpv4?: boolean; 207 | /** 208 | * Request 使用 209 | * 210 | * 监听 Transfer-Encoding Chunk Received 事件 211 | * 212 | * 当接收到新的 chunk 时触发 213 | */ 214 | onChunkReceived?: (response?: { data?: ArrayBuffer }) => void; 215 | 216 | /** 217 | * Upload 使用 218 | * 219 | * 需要上传的文件列表,files 和 filePath 必填一个 220 | * 221 | * 使用该参数时,filePath 和 name 无效 222 | * 223 | * 不支持小程序 224 | */ 225 | files?: UnFile[]; 226 | /** 227 | * Upload 使用 228 | * 229 | * 文件类型 230 | */ 231 | fileType?: UnFileType; 232 | /** 233 | * Upload 使用 234 | * 235 | * 文件对象 236 | */ 237 | file?: File; 238 | /** 239 | * Upload 使用 240 | * 241 | * 文件路径,files 和 filePath 必填一个 242 | * 243 | * Download 使用 244 | * 245 | * 文件下载后存储的本地路径 246 | */ 247 | filePath?: string; 248 | /** 249 | * Upload 使用 250 | * 251 | * 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容 252 | */ 253 | name?: string; 254 | /** 255 | * Upload 使用 256 | * 257 | * 一个对象,会作为 HTTP 请求中其它额外的 form data 258 | */ 259 | formData?: Record; 260 | /** 261 | * Download 使用 262 | * 263 | * 下载进度变化时触发 264 | * 265 | * 优先级 onDownloadProgress > onDownloadProgressUpdate > onProgress > 266 | * onProgressUpdate 267 | */ 268 | onDownloadProgress?: UnOnProgress; 269 | /** 270 | * Download 使用 271 | * 272 | * 下载进度变化时触发 273 | * 274 | * 优先级 onDownloadProgress > onDownloadProgressUpdate > onProgress > 275 | * onProgressUpdate 276 | */ 277 | onDownloadProgressUpdate?: UnOnProgress; 278 | /** 279 | * Upload 使用 280 | * 281 | * 上传进度变化时触发 282 | * 283 | * 优先级 onUploadProgress > onUploadProgressUpdate > onProgress > 284 | * onProgressUpdate 285 | */ 286 | onUploadProgress?: UnOnProgress; 287 | /** 288 | * Upload 使用 289 | * 290 | * 上传进度变化时触发 291 | * 292 | * 优先级 onUploadProgress > onUploadProgressUpdate > onProgress > 293 | * onProgressUpdate 294 | */ 295 | onUploadProgressUpdate?: UnOnProgress; 296 | /** 297 | * Upload / download 使用 298 | * 299 | * 上传/下载进度变化时触发 300 | * 301 | * 优先级 onUploadProgress / onDownloadProgress > onUploadProgressUpdate / 302 | * onDownloadProgressUpdate > onProgress > onProgressUpdate 303 | */ 304 | onProgress?: UnOnProgress; 305 | /** 306 | * Upload / download 使用 307 | * 308 | * 上传/下载进度变化时触发 309 | * 310 | * 优先级 onUploadProgress / onDownloadProgress > onUploadProgressUpdate / 311 | * onDownloadProgressUpdate > onProgress > onProgressUpdate 312 | */ 313 | onProgressUpdate?: UnOnProgress; 314 | [key: string]: any; 315 | } 316 | -------------------------------------------------------------------------------- /packages/core/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; 2 | export * from "./config"; 3 | export * from "./task"; 4 | export * from "./response"; 5 | export * from "./promise"; 6 | export * from "./adapter"; 7 | -------------------------------------------------------------------------------- /packages/core/src/types/promise.ts: -------------------------------------------------------------------------------- 1 | import type { UnData } from "./common"; 2 | import type { UnResponse } from "./response"; 3 | 4 | export type UnPromise = Promise>; 5 | -------------------------------------------------------------------------------- /packages/core/src/types/response.ts: -------------------------------------------------------------------------------- 1 | import type { UnData, UnHeaders, UnProfile } from "./common"; 2 | import type { UnConfig } from "./config"; 3 | import type { UnTask } from "./task"; 4 | 5 | export interface UnResponse { 6 | /** 错误信息 */ 7 | errMsg?: string; 8 | /** 错误代码 */ 9 | errno?: number; 10 | /** 网络请求过程中的调试信息 */ 11 | profile?: UnProfile; 12 | 13 | /** 请求的配置信息 */ 14 | config?: UnConfig; 15 | /** 对应的 task 信息 */ 16 | task?: UnTask; 17 | 18 | /** 服务器响应的 HTTP 状态码 */ 19 | status?: number; 20 | /** 服务器响应的 HTTP 状态信息 */ 21 | statusText?: string; 22 | /** 服务器响应头 */ 23 | headers?: UnHeaders; 24 | /** 服务器响应数据 */ 25 | data?: T; 26 | 27 | /** 28 | * Request 特有 29 | * 30 | * 服务器提供的 cookies 数据 31 | */ 32 | cookies?: string[]; 33 | /** 34 | * Download 特有 35 | * 36 | * 临时本地文件路径 37 | * 38 | * 没传入 filePath 指定文件存储路径时会返回,下载后的文件会存储到一个临时文件 39 | */ 40 | tempFilePath?: string; 41 | /** 42 | * Download 特有 43 | * 44 | * 用户本地文件路径 45 | * 46 | * 传入 filePath 时会返回,跟传入的 filePath 一致 47 | */ 48 | filePath?: string; 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/src/types/task.ts: -------------------------------------------------------------------------------- 1 | import type { UnOnProgress } from "./common"; 2 | 3 | export interface UnTask 4 | extends Partial, 5 | Partial>, 6 | Partial> { 7 | onProgressUpdate?: (callback: UnOnProgress) => void; 8 | [key: string]: any; 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildDownloadConfig.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig, UnData } from "../types"; 2 | import { buildFullPath } from "./buildFullPath"; 3 | import { buildUrl } from "./buildUrl"; 4 | 5 | export const buildDownloadConfig = ( 6 | config: UnConfig, 7 | ) => 8 | ({ 9 | url: buildUrl( 10 | buildFullPath( 11 | config.baseUrl ?? "", 12 | config.url ?? "", 13 | config.allowAbsoluteUrls ?? true, 14 | ), 15 | config.params, 16 | config.paramsSerializer, 17 | ), 18 | header: config.headers, 19 | timeout: config.timeout, 20 | filePath: config.filePath, 21 | }) as UniApp.DownloadFileOption; 22 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildFullPath.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { buildFullPath } from "./buildFullPath"; 3 | 4 | describe("utils::buildFullPath", () => { 5 | it("should combine URLs when the requestedURL is relative", () => { 6 | expect(buildFullPath("https://api.github.com", "/users", true)).toBe( 7 | "https://api.github.com/users", 8 | ); 9 | }); 10 | 11 | it("should not combine the URLs when the requestedURL is absolute", () => { 12 | expect( 13 | buildFullPath( 14 | "https://api.github.com", 15 | "https://api.example.com/users", 16 | true, 17 | ), 18 | ).toBe("https://api.example.com/users"); 19 | }); 20 | 21 | it("should combine the URLs when the requestedURL is absolute and allowAbsoluteUrls is false", () => { 22 | expect( 23 | buildFullPath( 24 | "https://api.github.com", 25 | "https://api.example.com/users", 26 | false, 27 | ), 28 | ).toBe("https://api.github.com/https://api.example.com/users"); 29 | }); 30 | 31 | it("should not combine the URLs when the requestedURL is absolute, allowAbsoluteUrls is false, and the baseURL is not configured", () => { 32 | expect(buildFullPath("", "https://api.example.com/users", false)).toBe( 33 | "https://api.example.com/users", 34 | ); 35 | }); 36 | 37 | it("should not combine URLs when the baseURL is not configured", () => { 38 | expect(buildFullPath("", "/users", true)).toBe("/users"); 39 | }); 40 | 41 | it("should combine URLs when the baseURL and requestedURL are relative", () => { 42 | expect(buildFullPath("/api", "/users", true)).toBe("/api/users"); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildFullPath.ts: -------------------------------------------------------------------------------- 1 | import { combineUrls } from "./combineUrls"; 2 | import { isAbsoluteUrl } from "./isAbsoluteUrl"; 3 | 4 | export const buildFullPath = ( 5 | baseUrl: string, 6 | requestedUrl: string, 7 | allowAbsoluteUrls: boolean, 8 | ) => { 9 | const isRelativeUrl = !isAbsoluteUrl(requestedUrl); 10 | if (baseUrl && (isRelativeUrl || !allowAbsoluteUrls)) { 11 | return combineUrls(baseUrl, requestedUrl); 12 | } 13 | return requestedUrl; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildRequestConfig.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig, UnData } from "../types"; 2 | import { buildFullPath } from "./buildFullPath"; 3 | import { buildUrl } from "./buildUrl"; 4 | 5 | export const buildRequestConfig = ( 6 | config: UnConfig, 7 | ) => 8 | ({ 9 | url: buildUrl( 10 | buildFullPath( 11 | config.baseUrl ?? "", 12 | config.url ?? "", 13 | config.allowAbsoluteUrls ?? true, 14 | ), 15 | config.params, 16 | config.paramsSerializer, 17 | ), 18 | data: config.data, 19 | header: config.headers, 20 | method: config.method?.toUpperCase() ?? "GET", 21 | timeout: config.timeout, 22 | dataType: config.dataType, 23 | responseType: config.responseType, 24 | enableHttp2: config.enableHttp2, 25 | enableQuic: config.enableQuic, 26 | enableCache: config.enableCache, 27 | enableHttpDNS: config.enableHttpDNS, 28 | httpDNSServiceId: config.httpDNSServiceId, 29 | enableChunked: config.enableChunked, 30 | forceCellularNetwork: config.forceCellularNetwork, 31 | sslVerify: config.sslVerify, 32 | withCredentials: config.withCredentials, 33 | firstIpv4: config.firstIpv4, 34 | }) as UniApp.RequestOptions; 35 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildUploadConfig.ts: -------------------------------------------------------------------------------- 1 | import type { UnConfig, UnData } from "../types"; 2 | import { buildFullPath } from "./buildFullPath"; 3 | import { buildUrl } from "./buildUrl"; 4 | 5 | export const buildUploadConfig = ( 6 | config: UnConfig, 7 | ) => 8 | ({ 9 | url: buildUrl( 10 | buildFullPath( 11 | config.baseUrl ?? "", 12 | config.url ?? "", 13 | config.allowAbsoluteUrls ?? true, 14 | ), 15 | config.params, 16 | config.paramsSerializer, 17 | ), 18 | files: config.files, 19 | fileType: config.fileType, 20 | file: config.file, 21 | filePath: config.filePath, 22 | name: config.name, 23 | header: config.headers, 24 | timeout: config.timeout, 25 | formData: config.formData, 26 | }) as UniApp.UploadFileOption; 27 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildUrl.test.ts: -------------------------------------------------------------------------------- 1 | import URLSearchParams from "@ungap/url-search-params"; 2 | import { describe, expect, it } from "vitest"; 3 | import { buildUrl } from "./buildUrl"; 4 | 5 | describe("utils::buildUrl", () => { 6 | it("should support null params", () => { 7 | expect(buildUrl("/foo")).toEqual("/foo"); 8 | }); 9 | 10 | it("should support params", () => { 11 | expect( 12 | buildUrl("/foo", { 13 | foo: "bar", 14 | }), 15 | ).toEqual("/foo?foo=bar"); 16 | }); 17 | 18 | it("should support object params", () => { 19 | expect( 20 | buildUrl("/foo", { 21 | foo: { 22 | bar: "baz", 23 | }, 24 | }), 25 | // ).toEqual('/foo?foo%5Bbar%5D=baz'); // qs 26 | // ).toEqual('/foo?foo=%5Bobject%20Object%5D'); // query-string 27 | ).toEqual("/foo?foo="); // fast-querystring 28 | }); 29 | 30 | it("should support date params", () => { 31 | const date = new Date(); 32 | 33 | expect( 34 | buildUrl("/foo", { 35 | date: date, 36 | }), 37 | // ).toEqual('/foo?date=' + encodeURIComponent(date.toISOString())); // qs 38 | // ).toEqual('/foo?date=' + encodeURIComponent(date.toString()).replace('(', '%28').replace(')', '%29')); // query-string 39 | ).toEqual("/foo?date="); // fast-querystring 40 | }); 41 | 42 | it("should support array params", () => { 43 | expect( 44 | buildUrl("/foo", { 45 | foo: ["bar", "baz"], 46 | }), 47 | // ).toEqual('/foo?foo%5B0%5D=bar&foo%5B1%5D=baz'); // qs 48 | ).toEqual("/foo?foo=bar&foo=baz"); // query-string 49 | }); 50 | 51 | it("should support special char params", () => { 52 | expect( 53 | buildUrl("/foo", { 54 | foo: ":$, ", 55 | }), 56 | ).toEqual("/foo?foo=%3A%24%2C%20"); 57 | }); 58 | 59 | it("should support existing params", () => { 60 | expect( 61 | buildUrl("/foo?foo=bar", { 62 | bar: "baz", 63 | }), 64 | ).toEqual("/foo?foo=bar&bar=baz"); 65 | }); 66 | 67 | it('should support "length" parameter', () => { 68 | expect( 69 | buildUrl("/foo", { 70 | query: "bar", 71 | start: 0, 72 | length: 5, 73 | }), 74 | // ).toEqual('/foo?query=bar&start=0&length=5'); // qs 75 | // ).toEqual('/foo?length=5&query=bar&start=0'); // query-string 76 | ).toEqual("/foo?query=bar&start=0&length=5"); // fast-querystring 77 | }); 78 | 79 | it("should correct discard url hash mark", () => { 80 | expect( 81 | buildUrl("/foo?foo=bar#hash", { 82 | query: "baz", 83 | }), 84 | ).toEqual("/foo?foo=bar&query=baz"); 85 | }); 86 | 87 | it("should support URLSearchParams", () => { 88 | expect(buildUrl("/foo", new URLSearchParams("bar=baz"))).toEqual( 89 | "/foo?bar=baz", 90 | ); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildUrl.ts: -------------------------------------------------------------------------------- 1 | import qs from "fast-querystring"; 2 | import type { UnParams, UnParamsSerializer } from "../types"; 3 | 4 | export const buildUrl = ( 5 | url: string, 6 | params?: UnParams, 7 | paramsSerializer?: UnParamsSerializer, 8 | ) => { 9 | if (!params) { 10 | return url; 11 | } 12 | 13 | let newUrl = url; 14 | const hashIndex = url.indexOf("#"); 15 | if (hashIndex !== -1) { 16 | newUrl = newUrl.slice(0, hashIndex); 17 | } 18 | 19 | const serializerParams = paramsSerializer 20 | ? paramsSerializer(params) 21 | : Object.prototype.toString.call(params).includes("URLSearchParams") 22 | ? params.toString() 23 | : qs.stringify(params); 24 | 25 | if (serializerParams) { 26 | newUrl += (newUrl.includes("?") ? "&" : "?") + serializerParams; 27 | } 28 | 29 | return newUrl; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/core/src/utils/combineUrls.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { combineUrls } from "./combineUrls"; 3 | 4 | describe("utils::combineUrls", () => { 5 | it("should combine URLs", () => { 6 | expect(combineUrls("https://api.github.com", "/users")).toBe( 7 | "https://api.github.com/users", 8 | ); 9 | }); 10 | 11 | it("should remove duplicate slashes", () => { 12 | expect(combineUrls("https://api.github.com/", "/users")).toBe( 13 | "https://api.github.com/users", 14 | ); 15 | }); 16 | 17 | it("should insert missing slash", () => { 18 | expect(combineUrls("https://api.github.com", "users")).toBe( 19 | "https://api.github.com/users", 20 | ); 21 | }); 22 | 23 | it("should not insert slash when relative url missing/empty", () => { 24 | expect(combineUrls("https://api.github.com/users", "")).toBe( 25 | "https://api.github.com/users", 26 | ); 27 | }); 28 | 29 | it("should allow a single slash for relative url", () => { 30 | expect(combineUrls("https://api.github.com/users", "/")).toBe( 31 | "https://api.github.com/users/", 32 | ); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/core/src/utils/combineUrls.ts: -------------------------------------------------------------------------------- 1 | export const combineUrls = (baseUrl: string, relativeUrl: string) => { 2 | return relativeUrl 3 | ? `${baseUrl.replace(/\/?\/$/, "")}/${relativeUrl.replace(/^\/+/, "")}` 4 | : baseUrl; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/core/src/utils/extend.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { extend } from "./extend"; 3 | 4 | describe("utils::extend", () => { 5 | it("should be mutable", () => { 6 | const a: Record = {}; 7 | const b: Record = { foo: 123 }; 8 | 9 | extend(a, b); 10 | 11 | expect(a.foo).toEqual(b.foo); 12 | }); 13 | 14 | it("should extend properties", () => { 15 | let a: Record = { foo: 123, bar: 456 }; 16 | const b: Record = { bar: 789 }; 17 | 18 | a = extend(a, b); 19 | 20 | expect(a.foo).toEqual(123); 21 | expect(a.bar).toEqual(789); 22 | }); 23 | 24 | it("should bind to thisArg", () => { 25 | const a: Record = {}; 26 | const b: Record = { 27 | getFoo: function getFoo() { 28 | return this.foo; 29 | }, 30 | }; 31 | const thisArg = { foo: "barbaz" }; 32 | 33 | extend(a, b, thisArg); 34 | 35 | expect(typeof a.getFoo).toEqual("function"); 36 | expect(a.getFoo()).toEqual(thisArg.foo); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/core/src/utils/extend.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from "./forEach"; 2 | 3 | /** 4 | * 向对象 a 添加对象 b 的属性 5 | * 6 | * 直接改变对象 a 7 | */ 8 | export const extend = ( 9 | a: Record, 10 | b: Record, 11 | thisArg?: Record | null | undefined, 12 | { allOwnKeys = false } = {}, 13 | ) => { 14 | forEach( 15 | b, 16 | (val, key) => { 17 | a[key] = thisArg && typeof val === "function" ? val.bind(thisArg) : val; 18 | }, 19 | { allOwnKeys }, 20 | ); 21 | return a; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/core/src/utils/forEach.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { forEach } from "./forEach"; 3 | 4 | describe("utils::forEach", () => { 5 | it("should loop over an array", () => { 6 | let sum = 0; 7 | 8 | forEach([1, 2, 3, 4, 5], (val) => { 9 | sum += val; 10 | }); 11 | 12 | expect(sum).toEqual(15); 13 | }); 14 | 15 | it("should loop over object keys", () => { 16 | let keys = ""; 17 | let vals = 0; 18 | const obj = { 19 | b: 1, 20 | a: 2, 21 | r: 3, 22 | }; 23 | 24 | forEach(obj, (v, k) => { 25 | keys += k; 26 | vals += v; 27 | }); 28 | 29 | expect(keys).toEqual("bar"); 30 | expect(vals).toEqual(6); 31 | }); 32 | 33 | it("should handle undefined gracefully", () => { 34 | let count = 0; 35 | 36 | forEach(undefined, () => { 37 | count++; 38 | }); 39 | 40 | expect(count).toEqual(0); 41 | }); 42 | 43 | it("should make an array out of non-array argument", () => { 44 | let count = 0; 45 | 46 | forEach( 47 | () => {}, 48 | () => { 49 | count++; 50 | }, 51 | ); 52 | 53 | expect(count).toEqual(1); 54 | }); 55 | 56 | it("should handle non object prototype gracefully", () => { 57 | let count = 0; 58 | const data = Object.create(null); 59 | data.foo = "bar"; 60 | 61 | forEach(data, () => { 62 | count++; 63 | }); 64 | 65 | expect(count).toEqual(1); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/core/src/utils/forEach.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 遍历数组或对象,为每项调用方法 3 | * 4 | * 如果 obj 是一个数组,传递每项的值、索引和完整的数组 5 | * 6 | * 如果 obj 是一个对象,为每个属性传递值、键和完整的对象 7 | */ 8 | export function forEach( 9 | obj: Record | Array | undefined, 10 | fn: (...rest: any) => any, 11 | { allOwnKeys = false } = {}, 12 | ) { 13 | if (obj === null || obj === undefined) return; 14 | 15 | let i: number; 16 | let l: number; 17 | 18 | // Force an array if not already something iterable 19 | const object = typeof obj !== "object" ? [obj] : obj; 20 | 21 | if (Array.isArray(object)) { 22 | // Iterate over array values 23 | for (i = 0, l = object.length; i < l; i++) { 24 | fn.call(null, object[i], i, object); 25 | } 26 | } else { 27 | // Iterate over object keys 28 | const keys = allOwnKeys 29 | ? Object.getOwnPropertyNames(object).filter( 30 | (key) => key !== "constructor" && !key.startsWith("_"), 31 | ) 32 | : Object.keys(object); 33 | const len = keys.length; 34 | let key: string; 35 | 36 | for (i = 0; i < len; i++) { 37 | key = keys[i]; 38 | fn.call(null, object[key], key, object); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./buildDownloadConfig"; 2 | export * from "./buildFullPath"; 3 | export * from "./buildRequestConfig"; 4 | export * from "./buildUploadConfig"; 5 | export * from "./buildUrl"; 6 | export * from "./combineUrls"; 7 | export * from "./extend"; 8 | export * from "./forEach"; 9 | export * from "./isAbsoluteUrl"; 10 | export * from "./mergeConfig"; 11 | -------------------------------------------------------------------------------- /packages/core/src/utils/isAbsoluteUrl.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { isAbsoluteUrl } from "./isAbsoluteUrl"; 3 | 4 | describe("utils::isAbsoluteUrl", () => { 5 | it("should return true if URL begins with valid scheme name", () => { 6 | expect(isAbsoluteUrl("https://api.github.com/users")).toBe(true); 7 | expect(isAbsoluteUrl("custom-scheme-v1.0://example.com/")).toBe(true); 8 | expect(isAbsoluteUrl("HTTP://example.com/")).toBe(true); 9 | }); 10 | 11 | it("should return false if URL begins with invalid scheme name", () => { 12 | expect(isAbsoluteUrl("123://example.com/")).toBe(false); 13 | expect(isAbsoluteUrl("!valid://example.com/")).toBe(false); 14 | }); 15 | 16 | it("should return false if URL is protocol-relative", () => { 17 | expect(isAbsoluteUrl("//example.com/")).toBe(false); 18 | }); 19 | 20 | it("should return false if URL is relative", () => { 21 | expect(isAbsoluteUrl("/foo")).toBe(false); 22 | expect(isAbsoluteUrl("foo")).toBe(false); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/core/src/utils/isAbsoluteUrl.ts: -------------------------------------------------------------------------------- 1 | export const isAbsoluteUrl = (url: string) => { 2 | // A URL is considered absolute if it begins with "://". 3 | // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed 4 | // by any combination of letters, digits, plus, period, or hyphen. 5 | // eslint-disable-next-line regexp/no-unused-capturing-group 6 | return /^([a-z][\d+.a-z-]*:)\/\//i.test(url); 7 | }; 8 | -------------------------------------------------------------------------------- /packages/core/src/utils/mergeConfig.ts: -------------------------------------------------------------------------------- 1 | import merge from "lodash.merge"; 2 | import type { UnConfig, UnData } from "../types"; 3 | 4 | export function mergeConfig( 5 | config1?: UnConfig, 6 | config2?: UnConfig, 7 | ) { 8 | return merge({}, config1 ?? {}, config2 ?? {}); 9 | } 10 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build:app": "uni build -p app", 7 | "build:app-android": "uni build -p app-android", 8 | "build:app-ios": "uni build -p app-ios", 9 | "build:custom": "uni build -p", 10 | "build:h5": "uni build", 11 | "build:h5:ssr": "uni build --ssr", 12 | "build:mp-alipay": "uni build -p mp-alipay", 13 | "build:mp-baidu": "uni build -p mp-baidu", 14 | "build:mp-kuaishou": "uni build -p mp-kuaishou", 15 | "build:mp-lark": "uni build -p mp-lark", 16 | "build:mp-qq": "uni build -p mp-qq", 17 | "build:mp-toutiao": "uni build -p mp-toutiao", 18 | "build:mp-weixin": "uni build -p mp-weixin", 19 | "build:quickapp-webview": "uni build -p quickapp-webview", 20 | "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei", 21 | "build:quickapp-webview-union": "uni build -p quickapp-webview-union", 22 | "dev:app": "uni -p app", 23 | "dev:app-android": "uni -p app-android", 24 | "dev:app-ios": "uni -p app-ios", 25 | "dev:custom": "uni -p", 26 | "dev:h5": "uni", 27 | "dev:h5:ssr": "uni --ssr", 28 | "dev:mp-alipay": "uni -p mp-alipay", 29 | "dev:mp-baidu": "uni -p mp-baidu", 30 | "dev:mp-kuaishou": "uni -p mp-kuaishou", 31 | "dev:mp-lark": "uni -p mp-lark", 32 | "dev:mp-qq": "uni -p mp-qq", 33 | "dev:mp-toutiao": "uni -p mp-toutiao", 34 | "dev:mp-weixin": "uni -p mp-weixin", 35 | "dev:quickapp-webview": "uni -p quickapp-webview", 36 | "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei", 37 | "dev:quickapp-webview-union": "uni -p quickapp-webview-union" 38 | }, 39 | "dependencies": { 40 | "@dcloudio/uni-app": "3.0.0-4050620250312001", 41 | "@dcloudio/uni-app-plus": "3.0.0-4050620250312001", 42 | "@dcloudio/uni-components": "3.0.0-4050620250312001", 43 | "@dcloudio/uni-h5": "3.0.0-4050620250312001", 44 | "@dcloudio/uni-mp-alipay": "3.0.0-4050620250312001", 45 | "@dcloudio/uni-mp-baidu": "3.0.0-4050620250312001", 46 | "@dcloudio/uni-mp-kuaishou": "3.0.0-4050620250312001", 47 | "@dcloudio/uni-mp-lark": "3.0.0-4050620250312001", 48 | "@dcloudio/uni-mp-qq": "3.0.0-4050620250312001", 49 | "@dcloudio/uni-mp-toutiao": "3.0.0-4050620250312001", 50 | "@dcloudio/uni-mp-vue": "3.0.0-4050620250312001", 51 | "@dcloudio/uni-mp-weixin": "3.0.0-4050620250312001", 52 | "@dcloudio/uni-quickapp-webview": "3.0.0-4050620250312001", 53 | "@uni-helper/uni-network": "workspace:*", 54 | "@vueuse/core": "^9.13.0", 55 | "abort-controller": "^3.0.0", 56 | "abortcontroller-polyfill": "^1.7.8", 57 | "core-js": "^3.41.0", 58 | "vue": "~3.4.38", 59 | "vue-i18n": "^9.14.3" 60 | }, 61 | "devDependencies": { 62 | "@dcloudio/types": "^3.4.14", 63 | "@dcloudio/uni-automator": "3.0.0-4050620250312001", 64 | "@dcloudio/uni-cli-shared": "3.0.0-4050620250312001", 65 | "@dcloudio/uni-stacktracey": "3.0.0-4050620250312001", 66 | "@dcloudio/vite-plugin-uni": "3.0.0-4050620250312001", 67 | "typescript": "^5.8.2", 68 | "vite": "^5.4.14" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /playground/src/App.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | -------------------------------------------------------------------------------- /playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/actual/array/iterator'; 2 | import 'core-js/actual/promise'; 3 | import 'core-js/actual/object/assign'; 4 | import 'core-js/actual/promise/finally'; 5 | // import 'core-js/actual/url-search-params'; 6 | import { createSSRApp } from 'vue'; 7 | import App from './App.vue'; 8 | 9 | export function createApp() { 10 | const app = createSSRApp(App); 11 | return { 12 | app, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /playground/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "", 3 | "appid" : "", 4 | "description" : "", 5 | "versionName" : "1.0.0", 6 | "versionCode" : "100", 7 | "transformPx" : false, 8 | /* 5+App特有相关 */ 9 | "app-plus" : { 10 | "usingComponents" : true, 11 | "nvueStyleCompiler" : "uni-app", 12 | "compilerVersion" : 3, 13 | "splashscreen" : { 14 | "alwaysShowBeforeRender" : true, 15 | "waiting" : true, 16 | "autoclose" : true, 17 | "delay" : 0 18 | }, 19 | /* 模块配置 */ 20 | "modules" : {}, 21 | /* 应用发布信息 */ 22 | "distribute" : { 23 | /* android打包配置 */ 24 | "android" : { 25 | "permissions" : [ 26 | "", 27 | "", 28 | "", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "", 39 | "", 40 | "" 41 | ] 42 | }, 43 | /* ios打包配置 */ 44 | "ios" : {}, 45 | /* SDK配置 */ 46 | "sdkConfigs" : {} 47 | } 48 | }, 49 | /* 快应用特有相关 */ 50 | "quickapp" : {}, 51 | /* 小程序特有相关 */ 52 | "mp-weixin" : { 53 | "appid" : "", 54 | "setting" : { 55 | "urlCheck" : false 56 | }, 57 | "usingComponents" : true 58 | }, 59 | "mp-alipay" : { 60 | "usingComponents" : true 61 | }, 62 | "mp-baidu" : { 63 | "usingComponents" : true 64 | }, 65 | "mp-toutiao" : { 66 | "usingComponents" : true 67 | }, 68 | "uniStatistics": { 69 | "enable": false 70 | }, 71 | "vueVersion" : "3" 72 | } 73 | -------------------------------------------------------------------------------- /playground/src/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages 3 | { 4 | "path": "pages/index/index", 5 | "style": { 6 | "navigationBarTitleText": "uni-app" 7 | } 8 | } 9 | ], 10 | "globalStyle": { 11 | "navigationBarTextStyle": "black", 12 | "navigationBarTitleText": "uni-app", 13 | "navigationBarBackgroundColor": "#F8F8F8", 14 | "backgroundColor": "#F8F8F8" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /playground/src/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | 24 | 51 | -------------------------------------------------------------------------------- /playground/src/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uni-helper/uni-network/49216dd91b8aa9e01cb534550d7a9d340d0b7259/playground/src/static/logo.png -------------------------------------------------------------------------------- /playground/src/uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color: #333; // 基本色 25 | $uni-text-color-inverse: #fff; // 反色 26 | $uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable: #c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color: #fff; 32 | $uni-bg-color-grey: #f8f8f8; 33 | $uni-bg-color-hover: #f1f1f1; // 点击状态颜色 34 | $uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color: #c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm: 12px; 43 | $uni-font-size-base: 14px; 44 | $uni-font-size-lg: 16; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm: 20px; 48 | $uni-img-size-base: 26px; 49 | $uni-img-size-lg: 40px; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 2px; 53 | $uni-border-radius-base: 3px; 54 | $uni-border-radius-lg: 6px; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 5px; 59 | $uni-spacing-row-base: 10px; 60 | $uni-spacing-row-lg: 15px; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 4px; 64 | $uni-spacing-col-base: 8px; 65 | $uni-spacing-col-lg: 12px; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2c405a; // 文章标题颜色 72 | $uni-font-size-title: 20px; 73 | $uni-color-subtitle: #555; // 二级标题颜色 74 | $uni-font-size-subtitle: 18px; 75 | $uni-color-paragraph: #3f536e; // 文章段落颜色 76 | $uni-font-size-paragraph: 15px; -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "lib": ["esnext", "dom"], 13 | "types": ["@dcloudio/types", "vite/client"] 14 | }, 15 | "vueCompilerOptions": { 16 | "experimentalRuntimeMode": "runtime-uni-app", 17 | "nativeTags": ["block", "component", "template", "slot"] 18 | }, 19 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 20 | } 21 | -------------------------------------------------------------------------------- /playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import uni from '@dcloudio/vite-plugin-uni'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [uni()], 7 | }); 8 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - docs 3 | - playground 4 | - packages/* 5 | - examples/* 6 | -------------------------------------------------------------------------------- /simple-git-hooks.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "commit-msg": "npx commitlint --edit ${1}", 3 | "pre-commit": "npx lint-staged", 4 | }; 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "target": "ESNext", 8 | "resolveJsonModule": true, 9 | "paths": { 10 | "core": ["./packages/core/src/index.ts"] 11 | }, 12 | "strict": true, 13 | "esModuleInterop": true, 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "types": ["@dcloudio/types", "node"] 17 | }, 18 | "exclude": ["**/node_modules/**", "**/dist/**", "**/playground/**"] 19 | } 20 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | clearMocks: true, 6 | }, 7 | }); 8 | --------------------------------------------------------------------------------