From 4dd265a81cced79ea1e1460b13c0ace510a17929 Mon Sep 17 00:00:00 2001 From: DigitalBrainJS Date: Tue, 18 Oct 2022 02:33:19 +0300 Subject: [PATCH 1/2] Refactored AxiosHeaders class; --- index.d.ts | 7 +- lib/adapters/http.js | 4 +- lib/core/Axios.js | 2 +- lib/core/AxiosHeaders.js | 132 ++++++++++++++++++--------------- lib/core/dispatchRequest.js | 4 + lib/defaults/index.js | 2 +- lib/utils.js | 7 +- test/specs/headers.spec.js | 5 +- test/unit/core/AxiosHeaders.js | 56 +++++++++++++- 9 files changed, 150 insertions(+), 69 deletions(-) diff --git a/index.d.ts b/index.d.ts index d125d0bb80..152df90f1b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -21,8 +21,7 @@ type AxiosHeaderTester = (matcher?: AxiosHeaderMatcher) => boolean; export class AxiosHeaders { constructor( - headers?: RawAxiosHeaders | AxiosHeaders, - defaultHeaders?: RawAxiosHeaders | AxiosHeaders + headers?: RawAxiosHeaders | AxiosHeaders ); set(headerName?: string, value?: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders; @@ -39,12 +38,16 @@ export class AxiosHeaders { normalize(format: boolean): AxiosHeaders; + concat(...targets: Array): AxiosHeaders; + toJSON(asStrings?: boolean): RawAxiosHeaders; static from(thing?: AxiosHeaders | RawAxiosHeaders | string): AxiosHeaders; static accessor(header: string | string[]): AxiosHeaders; + static concat(...targets: Array): AxiosHeaders; + setContentType: AxiosHeaderSetter; getContentType: AxiosHeaderGetter; hasContentType: AxiosHeaderTester; diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 9e56fdad6b..b561645d2f 100755 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -203,7 +203,7 @@ export default function httpAdapter(config) { data: convertedData, status: 200, statusText: 'OK', - headers: {}, + headers: new AxiosHeaders(), config }); } @@ -588,4 +588,4 @@ export default function httpAdapter(config) { }); } -export const __setProxy = setProxy; \ No newline at end of file +export const __setProxy = setProxy; diff --git a/lib/core/Axios.js b/lib/core/Axios.js index 31f8b531dc..5fa57c419b 100644 --- a/lib/core/Axios.js +++ b/lib/core/Axios.js @@ -80,7 +80,7 @@ class Axios { } ); - config.headers = new AxiosHeaders(config.headers, defaultHeaders); + config.headers = AxiosHeaders.concat(defaultHeaders, config.headers); // filter out skipped interceptors const requestInterceptorChain = []; diff --git a/lib/core/AxiosHeaders.js b/lib/core/AxiosHeaders.js index 68e098a0f7..1acd9ce89a 100644 --- a/lib/core/AxiosHeaders.js +++ b/lib/core/AxiosHeaders.js @@ -4,7 +4,6 @@ import utils from '../utils.js'; import parseHeaders from '../helpers/parseHeaders.js'; const $internals = Symbol('internals'); -const $defaults = Symbol('defaults'); function normalizeHeader(header) { return header && String(header).trim().toLowerCase(); @@ -30,6 +29,10 @@ function parseTokens(str) { return tokens; } +function isValidHeaderName(str) { + return /^[-_a-zA-Z]+$/.test(str.trim()); +} + function matchHeaderValue(context, value, header, filter) { if (utils.isFunction(filter)) { return filter.call(this, value, header); @@ -80,13 +83,12 @@ function findKey(obj, key) { return null; } -function AxiosHeaders(headers, defaults) { - headers && this.set(headers); - this[$defaults] = defaults || null; -} +class AxiosHeaders { + constructor(headers) { + headers && this.set(headers); + } -Object.assign(AxiosHeaders.prototype, { - set: function(header, valueOrRewrite, rewrite) { + set(header, valueOrRewrite, rewrite) { const self = this; function setHeader(_value, _header, _rewrite) { @@ -98,55 +100,56 @@ Object.assign(AxiosHeaders.prototype, { const key = findKey(self, lHeader); - if (key && _rewrite !== true && (self[key] === false || _rewrite === false)) { - return; + if(!key || self[key] === undefined || _rewrite === true || (_rewrite === undefined && self[key] !== false)) { + self[key || _header] = normalizeValue(_value); } - - self[key || _header] = normalizeValue(_value); } - if (utils.isPlainObject(header)) { - utils.forEach(header, (_value, _header) => { - setHeader(_value, _header, valueOrRewrite); - }); + const setHeaders = (headers, _rewrite) => + utils.forEach(headers, (_value, _header) => setHeader(_value, _header, _rewrite)); + + if (utils.isPlainObject(header) || header instanceof this.constructor) { + setHeaders(header, valueOrRewrite) + } else if(utils.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) { + setHeaders(parseHeaders(header), valueOrRewrite); } else { setHeader(valueOrRewrite, header, rewrite); } return this; - }, + } - get: function(header, parser) { + get(header, parser) { header = normalizeHeader(header); - if (!header) return undefined; + if (header) { + const key = findKey(this, header); - const key = findKey(this, header); + if (key) { + const value = this[key]; - if (key) { - const value = this[key]; + if (!parser) { + return value; + } - if (!parser) { - return value; - } + if (parser === true) { + return parseTokens(value); + } - if (parser === true) { - return parseTokens(value); - } + if (utils.isFunction(parser)) { + return parser.call(this, value, key); + } - if (utils.isFunction(parser)) { - return parser.call(this, value, key); - } + if (utils.isRegExp(parser)) { + return parser.exec(value); + } - if (utils.isRegExp(parser)) { - return parser.exec(value); + throw new TypeError('parser must be boolean|regexp|function'); } - - throw new TypeError('parser must be boolean|regexp|function'); } - }, + } - has: function(header, matcher) { + has(header, matcher) { header = normalizeHeader(header); if (header) { @@ -156,9 +159,9 @@ Object.assign(AxiosHeaders.prototype, { } return false; - }, + } - delete: function(header, matcher) { + delete(header, matcher) { const self = this; let deleted = false; @@ -183,13 +186,13 @@ Object.assign(AxiosHeaders.prototype, { } return deleted; - }, + } - clear: function() { + clear() { return Object.keys(this).forEach(this.delete.bind(this)); - }, + } - normalize: function(format) { + normalize(format) { const self = this; const headers = {}; @@ -214,30 +217,43 @@ Object.assign(AxiosHeaders.prototype, { }); return this; - }, + } + + concat(...targets) { + return this.constructor.concat(this, ...targets); + } - toJSON: function(asStrings) { + toJSON(asStrings) { const obj = Object.create(null); - utils.forEach(Object.assign({}, this[$defaults] || null, this), - (value, header) => { - if (value == null || value === false) return; - obj[header] = asStrings && utils.isArray(value) ? value.join(', ') : value; - }); + utils.forEach(this, (value, header) => { + value != null && value !== false && (obj[header] = asStrings && utils.isArray(value) ? value.join(', ') : value); + }); return obj; } -}); -Object.assign(AxiosHeaders, { - from: function(thing) { - if (utils.isString(thing)) { - return new this(parseHeaders(thing)); - } + [Symbol.iterator]() { + return Object.entries(this.toJSON())[Symbol.iterator](); + } + + toString() { + return Object.entries(this.toJSON()).map(([header, value]) => header + ': ' + value).join('\n'); + } + + static from(thing) { return thing instanceof this ? thing : new this(thing); - }, + } + + static concat(first, ...targets) { + const computed = new this(first); - accessor: function(header) { + targets.forEach((target) => computed.set(target)); + + return computed; + } + + static accessor(header) { const internals = this[$internals] = (this[$internals] = { accessors: {} }); @@ -258,7 +274,7 @@ Object.assign(AxiosHeaders, { return this; } -}); +} AxiosHeaders.accessor(['Content-Type', 'Content-Length', 'Accept', 'Accept-Encoding', 'User-Agent']); diff --git a/lib/core/dispatchRequest.js b/lib/core/dispatchRequest.js index 46ced28e33..dfe4c41d66 100644 --- a/lib/core/dispatchRequest.js +++ b/lib/core/dispatchRequest.js @@ -41,6 +41,10 @@ export default function dispatchRequest(config) { config.transformRequest ); + if (['post', 'put', 'patch'].indexOf(config.method) !== -1) { + config.headers.setContentType('application/x-www-form-urlencoded', false); + } + const adapter = config.adapter || defaults.adapter; return adapter(config).then(function onAdapterResolution(response) { diff --git a/lib/defaults/index.js b/lib/defaults/index.js index b4b9db22bd..b68ab0eae6 100644 --- a/lib/defaults/index.js +++ b/lib/defaults/index.js @@ -10,7 +10,7 @@ import formDataToJSON from '../helpers/formDataToJSON.js'; import adapters from '../adapters/index.js'; const DEFAULT_CONTENT_TYPE = { - 'Content-Type': 'application/x-www-form-urlencoded' + 'Content-Type': undefined }; /** diff --git a/lib/utils.js b/lib/utils.js index e075f9e2ab..56aa9e588b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -527,6 +527,11 @@ const reduceDescriptors = (obj, reducer) => { const freezeMethods = (obj) => { reduceDescriptors(obj, (descriptor, name) => { + // skip restricted props in strict mode + if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) { + return false; + } + const value = obj[name]; if (!isFunction(value)) return; @@ -540,7 +545,7 @@ const freezeMethods = (obj) => { if (!descriptor.set) { descriptor.set = () => { - throw Error('Can not read-only method \'' + name + '\''); + throw Error('Can not rewrite read-only method \'' + name + '\''); }; } }); diff --git a/test/specs/headers.spec.js b/test/specs/headers.spec.js index 92516c343d..ad9407206d 100644 --- a/test/specs/headers.spec.js +++ b/test/specs/headers.spec.js @@ -106,12 +106,11 @@ describe('headers', function () { }); }); - it('should preserve content-type if data is false', function (done) { + it('should preserve content-type if data is false', async function () { axios.post('/foo', false); - getAjaxRequest().then(function (request) { + await getAjaxRequest().then(function (request) { testHeaderValue(request.requestHeaders, 'Content-Type', 'application/x-www-form-urlencoded'); - done(); }); }); }); diff --git a/test/unit/core/AxiosHeaders.js b/test/unit/core/AxiosHeaders.js index aa30091982..758a7d4cbb 100644 --- a/test/unit/core/AxiosHeaders.js +++ b/test/unit/core/AxiosHeaders.js @@ -33,7 +33,16 @@ describe('AxiosHeaders', function () { assert.strictEqual(headers.get('foo'), 'value1'); assert.strictEqual(headers.get('bar'), 'value2'); - }) + }); + + it('should support adding multiple headers from raw headers string', function(){ + const headers = new AxiosHeaders(); + + headers.set(`foo:value1\nbar:value2`); + + assert.strictEqual(headers.get('foo'), 'value1'); + assert.strictEqual(headers.get('bar'), 'value2'); + }); it('should not rewrite header the header if the value is false', function(){ const headers = new AxiosHeaders(); @@ -338,4 +347,49 @@ describe('AxiosHeaders', function () { }); }); }); + + describe('AxiosHeaders.concat', function () { + it('should concatenate plain headers into an AxiosHeader instance', function () { + const a = {a: 1}; + const b = {b: 2}; + const c = {c: 3}; + const headers = AxiosHeaders.concat(a, b, c); + + assert.deepStrictEqual({...headers.toJSON()}, { + a: '1', + b: '2', + c: '3' + }); + }); + + it('should concatenate raw headers into an AxiosHeader instance', function () { + const a = 'a:1\nb:2'; + const b = 'c:3\nx:4'; + const headers = AxiosHeaders.concat(a, b); + + assert.deepStrictEqual({...headers.toJSON()}, { + a: '1', + b: '2', + c: '3', + x: '4' + }); + }); + + it('should concatenate Axios headers into a new AxiosHeader instance', function () { + const a = new AxiosHeaders({x: 1}); + const b = new AxiosHeaders({y: 2}); + const headers = AxiosHeaders.concat(a, b); + + assert.deepStrictEqual({...headers.toJSON()}, { + x: '1', + y: '2' + }); + }); + }); + + describe('toString', function () { + it('should serialize AxiosHeader instance to a raw headers string', function () { + assert.deepStrictEqual(new AxiosHeaders({x:1, y:2}).toString(), 'x: 1\ny: 2'); + }); + }); }); From d4ab9954858ae5a845f1175679a1344c4198b0f9 Mon Sep 17 00:00:00 2001 From: DigitalBrainJS Date: Mon, 31 Oct 2022 15:46:09 +0200 Subject: [PATCH 2/2] Added support for instances of AxiosHeaders as a value for the headers option; --- .gitignore | 3 + .npmignore | 1 + CHANGELOG.md | 23 +++--- ECOSYSTEM.md | 1 + UPGRADE_GUIDE.md => MIGRATION_GUIDE.md | 2 +- README.md | 69 +++++++++++----- examples/get/index.html | 2 +- index.d.ts | 28 +++++-- index.js | 11 ++- karma.conf.cjs | 2 +- lib/axios.js | 8 +- lib/core/Axios.js | 18 +++-- lib/core/AxiosHeaders.js | 30 +++---- lib/core/mergeConfig.js | 96 +++++++++++----------- lib/utils.js | 37 +++++++-- package.json | 13 +-- rollup.config.js | 47 ++++++++--- sandbox/client.html | 33 ++++++-- test/module/cjs/index.js | 11 +++ test/module/cjs/package.json | 15 ++++ test/module/esm/index.js | 13 +++ test/module/esm/package.json | 16 ++++ test/module/test.js | 106 +++++++++++++++++++++++++ test/module/ts/index.ts | 24 ++++++ test/module/ts/package.json | 20 +++++ test/module/ts/tsconfig.json | 103 ++++++++++++++++++++++++ test/specs/core/mergeConfig.spec.js | 22 +++++ test/specs/headers.spec.js | 26 ++++++ test/specs/instance.spec.js | 3 +- test/specs/options.spec.js | 2 + test/specs/utils/merge.spec.js | 10 +++ test/typescript/axios.ts | 47 ++++++++++- test/unit/adapters/http.js | 1 + tslint.json | 7 +- 34 files changed, 699 insertions(+), 151 deletions(-) rename UPGRADE_GUIDE.md => MIGRATION_GUIDE.md (51%) create mode 100644 test/module/cjs/index.js create mode 100644 test/module/cjs/package.json create mode 100644 test/module/esm/index.js create mode 100644 test/module/esm/package.json create mode 100644 test/module/test.js create mode 100644 test/module/ts/index.ts create mode 100644 test/module/ts/package.json create mode 100644 test/module/ts/tsconfig.json diff --git a/.gitignore b/.gitignore index ca56b6361a..68af93b92e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ typings/ coverage/ test/typescript/axios.js* sauce_connect.log +test/module/cjs/node_modules/ +test/module/cjs/package-lock.json +backup/ diff --git a/.npmignore b/.npmignore index 09739a34f8..f94390d8c9 100644 --- a/.npmignore +++ b/.npmignore @@ -16,3 +16,4 @@ Gruntfile.js karma.conf.js webpack.*.js sauce_connect.log +backup/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7765b53816..5a4b84e896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -222,23 +222,24 @@ ## [1.1.3] - 2022-10-15 ### Added -Added custom params serializer support [#5113](https://github.com/axios/axios/pull/5113) + +- Added custom params serializer support [#5113](https://github.com/axios/axios/pull/5113) ### Fixed -Fixed top-level export to keep them in-line with static properties [#5109](https://github.com/axios/axios/pull/5109) -Stopped including null values to query string. [#5108](https://github.com/axios/axios/pull/5108) -Restored proxy config backwards compatibility with 0.x [#5097](https://github.com/axios/axios/pull/5097) -Added back AxiosHeaders in AxiosHeaderValue [#5103](https://github.com/axios/axios/pull/5103) -Pin CDN install instructions to a specific version [#5060](https://github.com/axios/axios/pull/5060) -Handling of array values fixed for AxiosHeaders [#5085](https://github.com/axios/axios/pull/5085) +- Fixed top-level export to keep them in-line with static properties [#5109](https://github.com/axios/axios/pull/5109) +- Stopped including null values to query string. [#5108](https://github.com/axios/axios/pull/5108) +- Restored proxy config backwards compatibility with 0.x [#5097](https://github.com/axios/axios/pull/5097) +- Added back AxiosHeaders in AxiosHeaderValue [#5103](https://github.com/axios/axios/pull/5103) +- Pin CDN install instructions to a specific version [#5060](https://github.com/axios/axios/pull/5060) +- Handling of array values fixed for AxiosHeaders [#5085](https://github.com/axios/axios/pull/5085) ### Chores -docs: match badge style, add link to them [#5046](https://github.com/axios/axios/pull/5046) -chore: fixing comments typo [#5054](https://github.com/axios/axios/pull/5054) -chore: update issue template [#5061](https://github.com/axios/axios/pull/5061) -chore: added progress capturing section to the docs; [#5084](https://github.com/axios/axios/pull/5084) +- docs: match badge style, add link to them [#5046](https://github.com/axios/axios/pull/5046) +- chore: fixing comments typo [#5054](https://github.com/axios/axios/pull/5054) +- chore: update issue template [#5061](https://github.com/axios/axios/pull/5061) +- chore: added progress capturing section to the docs; [#5084](https://github.com/axios/axios/pull/5084) ### Contributors to this release diff --git a/ECOSYSTEM.md b/ECOSYSTEM.md index 9e55bc21db..e6886c8f9b 100644 --- a/ECOSYSTEM.md +++ b/ECOSYSTEM.md @@ -20,6 +20,7 @@ This is a list of axios related libraries and resources. If you have a suggestio * [axios-endpoints](https://github.com/renancaraujo/axios-endpoints) - Axios endpoints helps you to create a more concise endpoint mapping with axios. * [axios-multi-api](https://github.com/MattCCC/axios-multi-api) - Easy API handling whenever there are many endpoints to add. It helps to make Axios requests in an easy and declarative manner. * [axios-url-template](https://github.com/rafw87/axios-url-template) - Axios interceptor adding support for URL templates. +* [zodios](https://www.zodios.org) - Typesafe API client based on axios ### Logging and debugging diff --git a/UPGRADE_GUIDE.md b/MIGRATION_GUIDE.md similarity index 51% rename from UPGRADE_GUIDE.md rename to MIGRATION_GUIDE.md index e0febe5d88..ec3ae0da9b 100644 --- a/UPGRADE_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -1,3 +1,3 @@ -# Upgrade Guide +# Migration Guide ## 0.x.x -> 1.1.0 diff --git a/README.md b/README.md index b7260413fc..096d6ca00d 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ - [Features](#features) - [Browser Support](#browser-support) - [Installing](#installing) + - [Package manager](#package-manager) + - [CDN](#cdn) - [Example](#example) - [Axios API](#axios-api) - [Request method aliases](#request-method-aliases) @@ -93,6 +95,8 @@ Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | 11 ✔ | ## Installing +### Package manager + Using npm: ```bash @@ -117,7 +121,39 @@ Using pnpm: $ pnpm add axios ``` -Using jsDelivr CDN: +Once the package is installed, you can import the library using `import` or `require` approach: + +```js +import axios, {isCancel, AxiosError} from 'axios'; +``` + +You can also use the default export, since the named export is just a re-export from the Axios factory: + +```js +import axios from 'axios'; + +console.log(axios.isCancel('something')); +```` + +If you use `require` for importing, **only default export is available**: + +```js +const axios = require('axios'); + +console.log(axios.isCancel('something')); +``` + +For cases where something went wrong when trying to import a module into a custom or legacy environment, +you can try importing the module package directly: + +```js +const axios = require('axios/dist/browser/axios.cjs'); // browser commonJS bundle (ES2017) +// const axios = require('axios/dist/node/axios.cjs'); // node commonJS bundle (ES2017) +``` + +### CDN + +Using jsDelivr CDN (ES5 UMD browser module): ```html @@ -131,19 +167,12 @@ Using unpkg CDN: ## Example -### note: CommonJS usage -In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with `require()` use the following approach: +> **Note** CommonJS usage +> In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with `require()`, use the following approach: ```js -const axios = require('axios').default; - -// axios. will now provide autocomplete and parameter typings -``` - -Performing a `GET` request - -```js -const axios = require('axios').default; +import axios from 'axios'; +//const axios = require('axios'); // legacy way // Make a request for a user with a given ID axios.get('/user?ID=12345') @@ -797,7 +826,7 @@ controller.abort() You can also cancel a request using a *CancelToken*. -> The axios cancel token API is based on the withdrawn [cancelable promises proposal](https://github.com/tc39/proposal-cancelable-promises). +> The axios cancel token API is based on the withdrawn [cancellable promises proposal](https://github.com/tc39/proposal-cancelable-promises). > This API is deprecated since v0.22.0 and shouldn't be used in new projects @@ -853,7 +882,7 @@ cancel(); ### URLSearchParams -By default, axios serializes JavaScript objects to `JSON`. To send data in the [`application/x-www-form-urlencoded` format](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) instead, you can use the [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) API, which is [supported](http://www.caniuse.com/#feat=urlsearchparams) in the vast majority of browsers, [and Node](https://nodejs.org/api/url.html#url_class_urlsearchparams) starting with v10 (released in 2018). +By default, axios serializes JavaScript objects to `JSON`. To send data in the [`application/x-www-form-urlencoded` format](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) instead, you can use the [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) API, which is [supported](http://www.caniuse.com/#feat=urlsearchparams) in the vast majority of browsers,and [ Node](https://nodejs.org/api/url.html#url_class_urlsearchparams) starting with v10 (released in 2018). ```js const params = new URLSearchParams({ foo: 'bar' }); @@ -917,7 +946,7 @@ await axios.postForm('https://postman-echo.com/post', data, ); ``` -The server will handle it as +The server will handle it as: ```js { @@ -1259,11 +1288,11 @@ You can use Gitpod, an online IDE(which is free for Open Source) for contributin ## Resources -* [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md) -* [Upgrade Guide](https://github.com/axios/axios/blob/master/UPGRADE_GUIDE.md) -* [Ecosystem](https://github.com/axios/axios/blob/master/ECOSYSTEM.md) -* [Contributing Guide](https://github.com/axios/axios/blob/master/CONTRIBUTING.md) -* [Code of Conduct](https://github.com/axios/axios/blob/master/CODE_OF_CONDUCT.md) +* [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) +* [Upgrade Guide](https://github.com/axios/axios/blob/v1.x/UPGRADE_GUIDE.md) +* [Ecosystem](https://github.com/axios/axios/blob/v1.x/ECOSYSTEM.md) +* [Contributing Guide](https://github.com/axios/axios/blob/v1.x/CONTRIBUTING.md) +* [Code of Conduct](https://github.com/axios/axios/blob/v1.x/CODE_OF_CONDUCT.md) ## Credits diff --git a/examples/get/index.html b/examples/get/index.html index 86433235a3..f09c9ae317 100644 --- a/examples/get/index.html +++ b/examples/get/index.html @@ -18,7 +18,7 @@

axios.get

'' + '
' + '' + person.name + '' + - '' + + '' + '' + '
' + '
' diff --git a/index.d.ts b/index.d.ts index f862b6ba58..287d4c3ab9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -419,7 +419,7 @@ export interface AxiosInterceptorOptions { } export interface AxiosInterceptorManager { - use(onFulfilled?: (value: V) => V | Promise, onRejected?: (error: any) => any, options?: AxiosInterceptorOptions): number; + use(onFulfilled?: ((value: V) => V | Promise) | null, onRejected?: ((error: any) => any) | null, options?: AxiosInterceptorOptions): number; eject(id: number): void; clear(): void; } @@ -466,6 +466,18 @@ export interface GenericHTMLFormElement { submit(): void; } +export function toFormData(sourceObj: object, targetFormData?: GenericFormData, options?: FormSerializerOptions): GenericFormData; + +export function formToJSON(form: GenericFormData|GenericHTMLFormElement): object; + +export function isAxiosError(payload: any): payload is AxiosError; + +export function spread(callback: (...args: T[]) => R): (array: T[]) => R; + +export function isCancel(value: any): value is Cancel; + +export function all(values: Array>): Promise; + export interface AxiosStatic extends AxiosInstance { create(config?: CreateAxiosDefaults): AxiosInstance; Cancel: CancelStatic; @@ -473,12 +485,14 @@ export interface AxiosStatic extends AxiosInstance { Axios: typeof Axios; AxiosError: typeof AxiosError; readonly VERSION: string; - isCancel(value: any): value is Cancel; - all(values: Array>): Promise; - spread(callback: (...args: T[]) => R): (array: T[]) => R; - isAxiosError(payload: any): payload is AxiosError; - toFormData(sourceObj: object, targetFormData?: GenericFormData, options?: FormSerializerOptions): GenericFormData; - formToJSON(form: GenericFormData|GenericHTMLFormElement): object; + isCancel: typeof isCancel; + all: typeof all; + spread: typeof spread; + isAxiosError: typeof isAxiosError; + toFormData: typeof toFormData; + formToJSON: typeof formToJSON; + CanceledError: typeof CanceledError; + AxiosHeaders: typeof AxiosHeaders; } declare const axios: AxiosStatic; diff --git a/index.js b/index.js index b5369d7842..f91032c024 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ import axios from './lib/axios.js'; +// This module is intended to unwrap Axios default export as named. // Keep top-level export same with static properties // so that it can keep same with es module or cjs const { @@ -13,11 +14,13 @@ const { Cancel, isAxiosError, spread, - toFormData + toFormData, + AxiosHeaders, + formToJSON } = axios; -export default axios; export { + axios as default, Axios, AxiosError, CanceledError, @@ -28,5 +31,7 @@ export { Cancel, isAxiosError, spread, - toFormData + toFormData, + AxiosHeaders, + formToJSON } diff --git a/karma.conf.cjs b/karma.conf.cjs index fca512f3d3..9e69f5e103 100644 --- a/karma.conf.cjs +++ b/karma.conf.cjs @@ -129,7 +129,7 @@ module.exports = function(config) { ); browsers = ['Firefox']; } else if (process.env.GITHUB_ACTIONS === 'true') { - console.log('Running ci on Github Actions.'); + console.log('Running ci on GitHub Actions.'); browsers = ['FirefoxHeadless', 'ChromeHeadless']; } else { browsers = browsers || ['Chrome']; diff --git a/lib/axios.js b/lib/axios.js index e79082ceb3..31b75b162b 100644 --- a/lib/axios.js +++ b/lib/axios.js @@ -14,6 +14,7 @@ import toFormData from './helpers/toFormData.js'; import AxiosError from './core/AxiosError.js'; import spread from './helpers/spread.js'; import isAxiosError from './helpers/isAxiosError.js'; +import AxiosHeaders from "./core/AxiosHeaders.js"; /** * Create an instance of Axios @@ -69,8 +70,9 @@ axios.spread = spread; // Expose isAxiosError axios.isAxiosError = isAxiosError; -axios.formToJSON = thing => { - return formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing); -}; +axios.AxiosHeaders = AxiosHeaders; + +axios.formToJSON = thing => formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing); +// this module should only have a default export export default axios diff --git a/lib/core/Axios.js b/lib/core/Axios.js index 5fa57c419b..ff602ba52c 100644 --- a/lib/core/Axios.js +++ b/lib/core/Axios.js @@ -47,7 +47,7 @@ class Axios { config = mergeConfig(this.defaults, config); - const {transitional, paramsSerializer} = config; + const {transitional, paramsSerializer, headers} = config; if (transitional !== undefined) { validator.assertOptions(transitional, { @@ -67,20 +67,22 @@ class Axios { // Set config.method config.method = (config.method || this.defaults.method || 'get').toLowerCase(); + let contextHeaders; + // Flatten headers - const defaultHeaders = config.headers && utils.merge( - config.headers.common, - config.headers[config.method] + contextHeaders = headers && utils.merge( + headers.common, + headers[config.method] ); - defaultHeaders && utils.forEach( + contextHeaders && utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], - function cleanHeaderConfig(method) { - delete config.headers[method]; + (method) => { + delete headers[method]; } ); - config.headers = AxiosHeaders.concat(defaultHeaders, config.headers); + config.headers = AxiosHeaders.concat(contextHeaders, headers); // filter out skipped interceptors const requestInterceptorChain = []; diff --git a/lib/core/AxiosHeaders.js b/lib/core/AxiosHeaders.js index 1acd9ce89a..b2f1c2fe76 100644 --- a/lib/core/AxiosHeaders.js +++ b/lib/core/AxiosHeaders.js @@ -69,20 +69,6 @@ function buildAccessors(obj, header) { }); } -function findKey(obj, key) { - key = key.toLowerCase(); - const keys = Object.keys(obj); - let i = keys.length; - let _key; - while (i-- > 0) { - _key = keys[i]; - if (key === _key.toLowerCase()) { - return _key; - } - } - return null; -} - class AxiosHeaders { constructor(headers) { headers && this.set(headers); @@ -98,7 +84,7 @@ class AxiosHeaders { throw new Error('header name must be a non-empty string'); } - const key = findKey(self, lHeader); + const key = utils.findKey(self, lHeader); if(!key || self[key] === undefined || _rewrite === true || (_rewrite === undefined && self[key] !== false)) { self[key || _header] = normalizeValue(_value); @@ -113,7 +99,7 @@ class AxiosHeaders { } else if(utils.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) { setHeaders(parseHeaders(header), valueOrRewrite); } else { - setHeader(valueOrRewrite, header, rewrite); + header != null && setHeader(valueOrRewrite, header, rewrite); } return this; @@ -123,7 +109,7 @@ class AxiosHeaders { header = normalizeHeader(header); if (header) { - const key = findKey(this, header); + const key = utils.findKey(this, header); if (key) { const value = this[key]; @@ -153,7 +139,7 @@ class AxiosHeaders { header = normalizeHeader(header); if (header) { - const key = findKey(this, header); + const key = utils.findKey(this, header); return !!(key && (!matcher || matchHeaderValue(this, this[key], key, matcher))); } @@ -169,7 +155,7 @@ class AxiosHeaders { _header = normalizeHeader(_header); if (_header) { - const key = findKey(self, _header); + const key = utils.findKey(self, _header); if (key && (!matcher || matchHeaderValue(self, self[key], key, matcher))) { delete self[key]; @@ -197,7 +183,7 @@ class AxiosHeaders { const headers = {}; utils.forEach(this, (value, header) => { - const key = findKey(headers, header); + const key = utils.findKey(headers, header); if (key) { self[key] = normalizeValue(value); @@ -241,6 +227,10 @@ class AxiosHeaders { return Object.entries(this.toJSON()).map(([header, value]) => header + ': ' + value).join('\n'); } + get [Symbol.toStringTag]() { + return 'AxiosHeaders'; + } + static from(thing) { return thing instanceof this ? thing : new this(thing); } diff --git a/lib/core/mergeConfig.js b/lib/core/mergeConfig.js index 328e631824..2aee6b895c 100644 --- a/lib/core/mergeConfig.js +++ b/lib/core/mergeConfig.js @@ -1,6 +1,9 @@ 'use strict'; import utils from '../utils.js'; +import AxiosHeaders from "./AxiosHeaders.js"; + +const headersToObject = (thing) => thing instanceof AxiosHeaders ? thing.toJSON() : thing; /** * Config-specific merge-function which creates a new config-object @@ -16,9 +19,9 @@ export default function mergeConfig(config1, config2) { config2 = config2 || {}; const config = {}; - function getMergedValue(target, source) { + function getMergedValue(target, source, caseless) { if (utils.isPlainObject(target) && utils.isPlainObject(source)) { - return utils.merge(target, source); + return utils.merge.call({caseless}, target, source); } else if (utils.isPlainObject(source)) { return utils.merge({}, source); } else if (utils.isArray(source)) { @@ -28,72 +31,73 @@ export default function mergeConfig(config1, config2) { } // eslint-disable-next-line consistent-return - function mergeDeepProperties(prop) { - if (!utils.isUndefined(config2[prop])) { - return getMergedValue(config1[prop], config2[prop]); - } else if (!utils.isUndefined(config1[prop])) { - return getMergedValue(undefined, config1[prop]); + function mergeDeepProperties(a, b, caseless) { + if (!utils.isUndefined(b)) { + return getMergedValue(a, b, caseless); + } else if (!utils.isUndefined(a)) { + return getMergedValue(undefined, a, caseless); } } // eslint-disable-next-line consistent-return - function valueFromConfig2(prop) { - if (!utils.isUndefined(config2[prop])) { - return getMergedValue(undefined, config2[prop]); + function valueFromConfig2(a, b) { + if (!utils.isUndefined(b)) { + return getMergedValue(undefined, b); } } // eslint-disable-next-line consistent-return - function defaultToConfig2(prop) { - if (!utils.isUndefined(config2[prop])) { - return getMergedValue(undefined, config2[prop]); - } else if (!utils.isUndefined(config1[prop])) { - return getMergedValue(undefined, config1[prop]); + function defaultToConfig2(a, b) { + if (!utils.isUndefined(b)) { + return getMergedValue(undefined, b); + } else if (!utils.isUndefined(a)) { + return getMergedValue(undefined, a); } } // eslint-disable-next-line consistent-return - function mergeDirectKeys(prop) { + function mergeDirectKeys(a, b, prop) { if (prop in config2) { - return getMergedValue(config1[prop], config2[prop]); + return getMergedValue(a, b); } else if (prop in config1) { - return getMergedValue(undefined, config1[prop]); + return getMergedValue(undefined, a); } } const mergeMap = { - 'url': valueFromConfig2, - 'method': valueFromConfig2, - 'data': valueFromConfig2, - 'baseURL': defaultToConfig2, - 'transformRequest': defaultToConfig2, - 'transformResponse': defaultToConfig2, - 'paramsSerializer': defaultToConfig2, - 'timeout': defaultToConfig2, - 'timeoutMessage': defaultToConfig2, - 'withCredentials': defaultToConfig2, - 'adapter': defaultToConfig2, - 'responseType': defaultToConfig2, - 'xsrfCookieName': defaultToConfig2, - 'xsrfHeaderName': defaultToConfig2, - 'onUploadProgress': defaultToConfig2, - 'onDownloadProgress': defaultToConfig2, - 'decompress': defaultToConfig2, - 'maxContentLength': defaultToConfig2, - 'maxBodyLength': defaultToConfig2, - 'beforeRedirect': defaultToConfig2, - 'transport': defaultToConfig2, - 'httpAgent': defaultToConfig2, - 'httpsAgent': defaultToConfig2, - 'cancelToken': defaultToConfig2, - 'socketPath': defaultToConfig2, - 'responseEncoding': defaultToConfig2, - 'validateStatus': mergeDirectKeys + url: valueFromConfig2, + method: valueFromConfig2, + data: valueFromConfig2, + baseURL: defaultToConfig2, + transformRequest: defaultToConfig2, + transformResponse: defaultToConfig2, + paramsSerializer: defaultToConfig2, + timeout: defaultToConfig2, + timeoutMessage: defaultToConfig2, + withCredentials: defaultToConfig2, + adapter: defaultToConfig2, + responseType: defaultToConfig2, + xsrfCookieName: defaultToConfig2, + xsrfHeaderName: defaultToConfig2, + onUploadProgress: defaultToConfig2, + onDownloadProgress: defaultToConfig2, + decompress: defaultToConfig2, + maxContentLength: defaultToConfig2, + maxBodyLength: defaultToConfig2, + beforeRedirect: defaultToConfig2, + transport: defaultToConfig2, + httpAgent: defaultToConfig2, + httpsAgent: defaultToConfig2, + cancelToken: defaultToConfig2, + socketPath: defaultToConfig2, + responseEncoding: defaultToConfig2, + validateStatus: mergeDirectKeys, + headers: (a, b) => mergeDeepProperties(headersToObject(a), headersToObject(b), true) }; utils.forEach(Object.keys(config1).concat(Object.keys(config2)), function computeConfigValue(prop) { const merge = mergeMap[prop] || mergeDeepProperties; - const configValue = merge(prop); + const configValue = merge(config1[prop], config2[prop], prop); (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); }); diff --git a/lib/utils.js b/lib/utils.js index 56aa9e588b..849d0444b1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -228,7 +228,7 @@ const trim = (str) => str.trim ? * @param {Function} fn The callback to invoke for each item * * @param {Boolean} [allOwnKeys = false] - * @returns {void} + * @returns {any} */ function forEach(obj, fn, {allOwnKeys = false} = {}) { // Don't bother if no value provided @@ -263,6 +263,24 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) { } } +function findKey(obj, key) { + key = key.toLowerCase(); + const keys = Object.keys(obj); + let i = keys.length; + let _key; + while (i-- > 0) { + _key = keys[i]; + if (key === _key.toLowerCase()) { + return _key; + } + } + return null; +} + +const _global = typeof self === "undefined" ? typeof global === "undefined" ? this : global : self; + +const isContextDefined = (context) => !isUndefined(context) && context !== _global; + /** * Accepts varargs expecting each argument to be an object, then * immutably merges the properties of each object and returns result. @@ -282,16 +300,18 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) { * @returns {Object} Result of all merge properties */ function merge(/* obj1, obj2, obj3, ... */) { + const {caseless} = isContextDefined(this) && this || {}; const result = {}; const assignValue = (val, key) => { - if (isPlainObject(result[key]) && isPlainObject(val)) { - result[key] = merge(result[key], val); + const targetKey = caseless && findKey(result, key) || key; + if (isPlainObject(result[targetKey]) && isPlainObject(val)) { + result[targetKey] = merge(result[targetKey], val); } else if (isPlainObject(val)) { - result[key] = merge({}, val); + result[targetKey] = merge({}, val); } else if (isArray(val)) { - result[key] = val.slice(); + result[targetKey] = val.slice(); } else { - result[key] = val; + result[targetKey] = val; } } @@ -614,5 +634,8 @@ export default { toObjectSet, toCamelCase, noop, - toFiniteNumber + toFiniteNumber, + findKey, + global: _global, + isContextDefined }; diff --git a/package.json b/package.json index 685f3fc86f..e39cf61975 100644 --- a/package.json +++ b/package.json @@ -6,22 +6,24 @@ "exports": { ".": { "browser": { - "require": "./dist/node/axios.cjs", + "require": "./dist/browser/axios.cjs", "default": "./index.js" }, "default": { "require": "./dist/node/axios.cjs", "default": "./index.js" } - } + }, + "./package.json": "./package.json" }, "type": "module", "types": "index.d.ts", "scripts": { - "test": "npm run test:eslint && npm run test:mocha && npm run test:karma && npm run test:dtslint", + "test": "npm run test:eslint && npm run test:mocha && npm run test:karma && npm run test:exports && npm run test:dtslint", "test:eslint": "node bin/ssl_hotfix.js eslint lib/**/*.js", "test:dtslint": "node bin/ssl_hotfix.js dtslint", "test:mocha": "node bin/ssl_hotfix.js mocha test/unit/**/*.js --timeout 30000 --exit", + "test:exports": "node bin/ssl_hotfix.js mocha test/module/test.js --timeout 30000 --exit", "test:karma": "node bin/ssl_hotfix.js cross-env LISTEN_ADDR=:: karma start karma.conf.cjs --single-run", "test:karma:server": "node bin/ssl_hotfix.js cross-env karma start karma.conf.cjs", "start": "node ./sandbox/server.js", @@ -130,5 +132,6 @@ "Yasu Flores (https://github.com/yasuf)", "Ben Carp (https://github.com/carpben)", "Daniel Lopretto (https://github.com/timemachine3030)" - ] -} \ No newline at end of file + ], + "sideEffects": false +} diff --git a/rollup.config.js b/rollup.config.js index 9fd12d1bac..31cc7bebce 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -5,20 +5,28 @@ import json from '@rollup/plugin-json'; import { babel } from '@rollup/plugin-babel'; import autoExternal from 'rollup-plugin-auto-external'; import bundleSize from 'rollup-plugin-bundle-size' +import path from 'path'; const lib = require("./package.json"); const outputFileName = 'axios'; const name = "axios"; -const input = './lib/axios.js'; +const namedInput = './index.js'; +const defaultInput = './lib/axios.js'; const buildConfig = ({es5, browser = true, minifiedVersion = true, ...config}) => { + const {file} = config.output; + const ext = path.extname(file); + const basename = path.basename(file, ext); + const extArr = ext.split('.'); + extArr.shift(); + const build = ({minified}) => ({ - input, + input: namedInput, ...config, output: { ...config.output, - file: `${config.output.file}.${minified ? "min.js" : "js"}` + file: `${path.dirname(file)}/${basename}.${(minified ? ['min', ...extArr] : extArr).join('.')}` }, plugins: [ json(), @@ -50,10 +58,24 @@ export default async () => { const banner = `// Axios v${lib.version} Copyright (c) ${year} ${lib.author} and contributors`; return [ + // browser ESM bundle for CDN ...buildConfig({ + input: namedInput, + output: { + file: `dist/esm/${outputFileName}.js`, + format: "esm", + preferConst: true, + exports: "named", + banner + } + }), + + // Browser UMD bundle for CDN + ...buildConfig({ + input: defaultInput, es5: true, output: { - file: `dist/${outputFileName}`, + file: `dist/${outputFileName}.js`, name, format: "umd", exports: "default", @@ -61,18 +83,23 @@ export default async () => { } }), + // Browser CJS bundle ...buildConfig({ + input: defaultInput, + es5: false, + minifiedVersion: false, output: { - file: `dist/esm/${outputFileName}`, - format: "esm", - preferConst: true, - exports: "named", + file: `dist/browser/${name}.cjs`, + name, + format: "cjs", + exports: "default", banner } }), - // Node.js commonjs build + + // Node.js commonjs bundle { - input, + input: defaultInput, output: { file: `dist/node/${name}.cjs`, format: "cjs", diff --git a/sandbox/client.html b/sandbox/client.html index c06ae16940..599e462a98 100644 --- a/sandbox/client.html +++ b/sandbox/client.html @@ -1,19 +1,39 @@ - axios + AXIOS | Sandbox -

axios

- -
+
+ axios + +

 | Sandbox

+
+ +
+

Input

@@ -47,7 +67,8 @@

Input

-
+
+

Request

No Data
@@ -61,6 +82,8 @@

Response

Error

None
+
+