Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce bundle size #672

Merged
merged 4 commits into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/olive-bottles-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@httpx/exception': minor
---

Reduce bundle size by using class names rather than strings

Importing all exceptions (excluding utilities, typeguards...) now top at 1Kb

Example based on ESM (min+gzip)

| Scenario | Size |
|-----------------------------|--------|
| one exception | ~ 450b |
| all exceptions | < 1kb |
| everything (typeguards,...) | 1.7kb |


12 changes: 6 additions & 6 deletions examples/nextjs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@
},
"dependencies": {
"@httpx/exception": "workspace:^",
"axios": "1.5.1",
"axios": "1.6.0",
"ky": "1.1.1",
"next": "13.5.6",
"next": "14.0.0",
"pino": "8.16.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"superjson": "2.2.0",
"zod": "3.22.4"
},
"devDependencies": {
"@belgattitude/eslint-config-bases": "2.10.0",
"@types/node": "20.8.8",
"@types/react": "18.2.32",
"@belgattitude/eslint-config-bases": "3.0.0",
"@types/node": "20.8.9",
"@types/react": "18.2.33",
"@types/react-dom": "18.2.14",
"cross-env": "7.0.3",
"eslint": "8.52.0",
"eslint-config-next": "13.5.6",
"eslint-config-next": "14.0.0",
"postcss": "8.4.31",
"rimraf": "5.0.5",
"tailwindcss": "3.3.5",
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@
"homepage": "https://github.com/belgattitude/httpx",
"repository": "belgattitude/httpx",
"resolutions?": {
"eslint-plugin-react-hooks": "https://github.com/vercel/next.js/issues/52365",
"vite": "https://github.com/vitest-dev/vitest/issues/4112"
},
"resolutions": {
"eslint-plugin-react-hooks": "^4.6.0",
"vite": "^4.5.0"
},
"scripts": {
Expand Down
24 changes: 15 additions & 9 deletions packages/exception/.size-limit.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-check

const fullEsmMaxSize = "2070B";
const fullCjsMaxSize = "2700B";
const fullEsmMaxSize = "1760B";
const fullCjsMaxSize = "2300B";

/**
* Will ensure esm tree-shakeability and total size are within expectations.
Expand All @@ -19,17 +19,23 @@ module.exports = [
import: "*",
limit: fullEsmMaxSize,
},
{
name: "ESM (only HttpException exception)",
path: ["dist/index.mjs"],
import: "{ HttpException }",
limit: "395B",
},
{
name: "ESM (only HttpNotFound exception)",
path: ["dist/index.mjs"],
import: "{ HttpNotFound }",
limit: "460B",
limit: "455B",
},
{
name: "ESM (two exceptions: HttpNotFound + HttpInternalServerError)",
name: "ESM (two client exceptions: HttpNotFound + HttpRequestTimeout)",
path: ["dist/index.mjs"],
import: "{ HttpNotFound, HttpInternalServerError }",
limit: "510B",
import: "{ HttpNotFound, HttpRequestTimeout }",
limit: "485B",
},
{
name: "ESM (only isHttpException)",
Expand All @@ -41,14 +47,14 @@ module.exports = [
name: "ESM (only createHttpException)",
path: ["dist/index.mjs"],
import: "{ createHttpException }",
limit: "1500B", // Will import all server/client exceptions
limit: "900B", // Will import all server/client exceptions
},

{
name: "ESM ({ toJson })",
path: ["dist/serializer/index.mjs"],
import: "{ toJson }",
limit: "800B",
limit: "805B",
},
{
name: "ESM ({ fromJson })",
Expand All @@ -71,6 +77,6 @@ module.exports = [
path: ["dist/index.cjs"],
import: "{ isHttpException }",
webpack: true,
limit: '2500B',
limit: '1355B',
}
];
5 changes: 2 additions & 3 deletions packages/exception/src/base/HttpClientException.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import { HttpException } from './HttpException';
*/
export class HttpClientException extends HttpException {
constructor(statusCode: number, msgOrParams?: HttpExceptionParams | string) {
const name = 'HttpClientException';
super(statusCode, getSuper(name, msgOrParams));
super(statusCode, getSuper(HttpClientException.name, msgOrParams));
Object.setPrototypeOf(this, HttpClientException.prototype);
this.name = name;
this.name = HttpClientException.name;
}
}
32 changes: 16 additions & 16 deletions packages/exception/src/base/HttpException.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import type { HttpMethod } from '../types/HttpMethod';
import { getSuper } from '../utils';

export class HttpException extends Error {
export class HttpException extends Error implements HttpExceptionParams {
/**
* Http error status code (400-599)
*/
Expand All @@ -19,7 +19,7 @@ export class HttpException extends Error {
public readonly method: HttpMethod | undefined;

/**
* Custom additional code (ie: 'AbortError', 'CODE-1234'...)
* Custom additional code (ie: 'ERR_UNREACHABLE_SERVICE', 'AbortError', 'cdg1::h99k2-1664884491087-b41a2832f559'...)
*/
public readonly code: string | undefined;

Expand All @@ -42,22 +42,22 @@ export class HttpException extends Error {
* @param msgOrParams either a message or an object containing HttpExceptionParams
*/
constructor(statusCode: number, msgOrParams?: HttpExceptionParams | string) {
const name = 'HttpException';
const { message, url, cause, errorId, code, method } = getSuper(
name,
msgOrParams
);
super(message);
if (supportsErrorCause() && cause instanceof Error) {
this.cause = cause;
const {
message: m,
cause: c,

...p
} = getSuper(HttpException.name, msgOrParams);
super(m);
if (supportsErrorCause() && c instanceof Error) {
this.cause = c;
}
this.statusCode = statusCode;
this.url = url;
this.errorId = errorId;
this.code = code;
this.method = method;

this.url = p.url;
this.errorId = p.errorId;
this.code = p.code;
this.method = p.method;
Object.setPrototypeOf(this, HttpException.prototype);
this.name = name;
this.name = HttpException.name;
}
}
7 changes: 3 additions & 4 deletions packages/exception/src/base/HttpServerException.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import { getSuper } from '../utils';
import { getSuper, initProtoAndName } from '../utils';
import { HttpException } from './HttpException';

/**
Expand All @@ -10,8 +10,7 @@ import { HttpException } from './HttpException';
*/
export class HttpServerException extends HttpException {
constructor(statusCode: number, msgOrParams?: HttpExceptionParams | string) {
super(statusCode, getSuper('HttpServerException', msgOrParams));
Object.setPrototypeOf(this, HttpServerException.prototype);
this.name = 'HttpServerException';
super(statusCode, getSuper(HttpServerException.name, msgOrParams));
initProtoAndName(this, HttpServerException);
}
}
4 changes: 4 additions & 0 deletions packages/exception/src/base/__tests__/HttpException.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { HttpException } from '../HttpException';

describe('HttpException', () => {
it('should be instance of Error', () => {
const exception = new HttpException(500);
expect(exception).toBeInstanceOf(Error);
});
it('should be instance of HttpException', () => {
const exception = new HttpException(500);
expect(exception).toBeInstanceOf(HttpException);
});
Expand Down
41 changes: 18 additions & 23 deletions packages/exception/src/client/HttpBadRequest.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
import { HttpClientException } from '../base';
import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import type { HttpValidationIssue } from '../types/HttpValidationIssue';
import { getSuper } from '../utils';
import type { ValidationError } from '../types/ValidationError';
import { getSuperArgs, initProtoAndName } from '../utils';

type HttpExceptionParamsWithErrors = HttpExceptionParams & {
/** @deprecated use issues in 422 HttpUnprocessableEntity instead */
errors?: HttpValidationIssue[];
};

/**
* 400 Bad Request (client)
*
* Be aware that a lot of apis/frameworks will use 422 Unprocessable Entity to indicate (form field) validation errors
* when posting data (rails, github, api-platform...).
*
* @see https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#client-errors
*
* The server cannot or will not process the request due to something that is perceived to be a client error
* (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
*
* Note that a lot of apis/frameworks uses 422 Unprocessable Entity to indicate (form field) validation errors
* rather the 400 Bad Request status code.
*
* @see https://httpstatus.in/400/
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400
*/
export class HttpBadRequest extends HttpClientException {
static readonly STATUS = 400;
public readonly errors: HttpValidationIssue[];
constructor(
msgOrParams?:
| (HttpExceptionParams & {
/** @deprecated use errors 422 HttpUnprocessableEntity instead */
errors?: HttpValidationIssue[];
})
| string
) {
const name = 'BadRequest';
super(400, getSuper(name, msgOrParams));
this.errors =
typeof msgOrParams === 'object' && msgOrParams.errors
? msgOrParams.errors
: [];
Object.setPrototypeOf(this, HttpBadRequest.prototype);
this.name = `Http${name}`;
/** @deprecated use issues in 422 HttpUnprocessableEntity instead */
public readonly errors: ValidationError[];
constructor(msgOrParams?: HttpExceptionParamsWithErrors | string) {
const { errors = [], ...p } =
typeof msgOrParams === 'string' ? {} : msgOrParams ?? {};
super(...getSuperArgs(HttpBadRequest, p));
this.errors = errors;
initProtoAndName(this, HttpBadRequest);
}
}
8 changes: 3 additions & 5 deletions packages/exception/src/client/HttpConflict.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClientException } from '../base';
import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import { getSuper } from '../utils';
import { getSuperArgs, initProtoAndName } from '../utils';

/**
* 409 Conflict (client)
Expand All @@ -13,9 +13,7 @@ import { getSuper } from '../utils';
export class HttpConflict extends HttpClientException {
static readonly STATUS = 409;
constructor(msgOrParams?: HttpExceptionParams | string) {
const name = 'Conflict';
super(409, getSuper(name, msgOrParams));
Object.setPrototypeOf(this, HttpConflict.prototype);
this.name = `Http${name}`;
super(...getSuperArgs(HttpConflict, msgOrParams));
initProtoAndName(this, HttpConflict);
}
}
8 changes: 3 additions & 5 deletions packages/exception/src/client/HttpExpectationFailed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClientException } from '../base';
import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import { getSuper } from '../utils';
import { getSuperArgs, initProtoAndName } from '../utils';

/**
* Client status 417
Expand All @@ -14,9 +14,7 @@ import { getSuper } from '../utils';
export class HttpExpectationFailed extends HttpClientException {
static readonly STATUS = 417;
constructor(msgOrParams?: HttpExceptionParams | string) {
const name = 'ExpectationFailed';
super(417, getSuper(name, msgOrParams));
Object.setPrototypeOf(this, HttpExpectationFailed.prototype);
this.name = `Http${name}`;
super(...getSuperArgs(HttpExpectationFailed, msgOrParams));
initProtoAndName(this, HttpExpectationFailed);
}
}
8 changes: 3 additions & 5 deletions packages/exception/src/client/HttpFailedDependency.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClientException } from '../base';
import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import { getSuper } from '../utils';
import { getSuperArgs, initProtoAndName } from '../utils';

/**
* 424 Failed dependency (client / webdav specific)
Expand All @@ -16,9 +16,7 @@ import { getSuper } from '../utils';
export class HttpFailedDependency extends HttpClientException {
static readonly STATUS = 424;
constructor(msgOrParams?: HttpExceptionParams | string) {
const name = 'FailedDependency';
super(424, getSuper(name, msgOrParams));
Object.setPrototypeOf(this, HttpFailedDependency.prototype);
this.name = `Http${name}`;
super(...getSuperArgs(HttpFailedDependency, msgOrParams));
initProtoAndName(this, HttpFailedDependency);
}
}
8 changes: 3 additions & 5 deletions packages/exception/src/client/HttpForbidden.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClientException } from '../base';
import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import { getSuper } from '../utils';
import { getSuperArgs, initProtoAndName } from '../utils';

/**
* 403 Forbidden (client)
Expand All @@ -14,9 +14,7 @@ import { getSuper } from '../utils';
export class HttpForbidden extends HttpClientException {
static readonly STATUS = 403;
constructor(msgOrParams?: HttpExceptionParams | string) {
const name = 'Forbidden';
super(403, getSuper(name, msgOrParams));
Object.setPrototypeOf(this, HttpForbidden.prototype);
this.name = `Http${name}`;
super(...getSuperArgs(HttpForbidden, msgOrParams));
initProtoAndName(this, HttpForbidden);
}
}
8 changes: 3 additions & 5 deletions packages/exception/src/client/HttpGone.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClientException } from '../base';
import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import { getSuper } from '../utils';
import { getSuperArgs, initProtoAndName } from '../utils';

/**
* 410 Gone (client)
Expand All @@ -17,9 +17,7 @@ import { getSuper } from '../utils';
export class HttpGone extends HttpClientException {
static readonly STATUS = 410;
constructor(msgOrParams?: HttpExceptionParams | string) {
const name = 'Gone';
super(410, getSuper(name, msgOrParams));
Object.setPrototypeOf(this, HttpGone.prototype);
this.name = `Http${name}`;
super(...getSuperArgs(HttpGone, msgOrParams));
initProtoAndName(this, HttpGone);
}
}
8 changes: 3 additions & 5 deletions packages/exception/src/client/HttpImATeapot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClientException } from '../base';
import type { HttpExceptionParams } from '../types/HttpExceptionParams';
import { getSuper } from '../utils';
import { getSuperArgs, initProtoAndName } from '../utils';

/**
* 418 I'm a teapot (client)
Expand All @@ -13,9 +13,7 @@ import { getSuper } from '../utils';
export class HttpImATeapot extends HttpClientException {
static readonly STATUS = 418;
constructor(msgOrParams?: HttpExceptionParams | string) {
const name = 'ImATeapot';
super(418, getSuper(name, msgOrParams));
Object.setPrototypeOf(this, HttpImATeapot.prototype);
this.name = `Http${name}`;
super(...getSuperArgs(HttpImATeapot, msgOrParams));
initProtoAndName(this, HttpImATeapot);
}
}
Loading