diff --git a/services/bundlejs/bundlejs-package.service.js b/services/bundlejs/bundlejs-package.service.js new file mode 100644 index 0000000000000..4d44c1bca16e1 --- /dev/null +++ b/services/bundlejs/bundlejs-package.service.js @@ -0,0 +1,139 @@ +import Joi from 'joi' +import { BaseJsonService } from '../index.js' + +const schema = Joi.object({ + size: Joi.object({ + compressedSize: Joi.string().required(), + }).required(), +}).required() + +const queryParamSchema = Joi.object({ + exports: Joi.string(), +}).required() + +const keywords = ['node', 'bundlejs'] + +const esbuild = + 'esbuild' +const denoflate = + 'denoflate' +const bundlejs = + 'bundlejs' + +const documentation = ` +

+ View ${esbuild} minified and ${denoflate} gzipped size of a package or selected exports, via ${bundlejs}. +

+` + +export default class BundlejsPackage extends BaseJsonService { + static category = 'size' + + static route = { + base: 'bundlejs/size', + pattern: ':scope(@[^/]+)?/:packageName', + queryParamSchema, + } + + static examples = [ + { + title: 'npm package minimized gzipped size', + pattern: ':packageName', + namedParams: { + packageName: 'react', + }, + staticPreview: this.render({ size: '2.94 kB' }), + keywords, + documentation, + }, + { + title: 'npm package minimized gzipped size (version)', + pattern: ':packageName', + namedParams: { + packageName: 'react@18.2.0', + }, + staticPreview: this.render({ size: '2.94 kB' }), + keywords, + documentation, + }, + { + title: 'npm package minimized gzipped size (scoped)', + pattern: ':scope/:packageName', + namedParams: { + scope: '@cycle', + packageName: 'rx-run', + }, + staticPreview: this.render({ size: '32.3 kB' }), + keywords, + documentation, + }, + { + title: 'npm package minimized gzipped size (select exports)', + pattern: ':packageName', + namedParams: { + packageName: 'value-enhancer', + }, + queryParams: { + exports: 'isVal,val', + }, + staticPreview: this.render({ size: '823 B' }), + keywords, + documentation, + }, + { + title: + 'npm package minimized gzipped size (scoped version select exports)', + pattern: ':scope/:packageName', + namedParams: { + scope: '@ngneat', + packageName: 'falso@6.4.0', + }, + queryParams: { + exports: 'randEmail,randFullName', + }, + staticPreview: this.render({ size: '17.8 kB' }), + keywords, + documentation, + }, + ] + + static defaultBadgeData = { label: 'bundlejs', color: 'informational' } + + static render({ size }) { + return { + label: 'minified size (gzip)', + message: size, + } + } + + async fetch({ scope, packageName, exports }) { + const searchParams = { + q: `${scope ? `${scope}/` : ''}${packageName}`, + } + if (exports) { + searchParams.treeshake = `[{${exports}}]` + } + return this._requestJson({ + schema, + url: 'https://deno.bundlejs.com', + options: { + searchParams, + timeout: { + request: 3500, + }, + }, + systemErrors: { + ETIMEDOUT: { prettyMessage: 'timeout', cacheSeconds: 10 }, + }, + httpErrors: { + 404: 'package or version not found', + }, + }) + } + + async handle({ scope, packageName }, { exports }) { + const json = await this.fetch({ scope, packageName, exports }) + const size = json.size.compressedSize + return this.constructor.render({ size }) + } +} diff --git a/services/bundlejs/bundlejs-package.tester.js b/services/bundlejs/bundlejs-package.tester.js new file mode 100644 index 0000000000000..1509826f3dbef --- /dev/null +++ b/services/bundlejs/bundlejs-package.tester.js @@ -0,0 +1,43 @@ +import { isFileSize } from '../test-validators.js' +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +t.create('bundlejs/package (packageName)') + .get('/jquery.json') + .expectBadge({ label: 'minified size (gzip)', message: isFileSize }) + +t.create('bundlejs/package (version)') + .get('/react@18.2.0.json') + .expectBadge({ label: 'minified size (gzip)', message: isFileSize }) + +t.create('bundlejs/package (scoped)') + .get('/@cycle/rx-run.json') + .expectBadge({ label: 'minified size (gzip)', message: isFileSize }) + +t.create('bundlejs/package (select exports)') + .get('/value-enhancer.json?exports=isVal,val') + .expectBadge({ label: 'minified size (gzip)', message: isFileSize }) + +t.create('bundlejs/package (scoped version select exports)') + .get('/@ngneat/falso@6.4.0.json?exports=randEmail,randFullName') + .expectBadge({ label: 'minified size (gzip)', message: isFileSize }) + +t.create('bundlejs/package (not found)') + .get('/react@18.2.0.json') + .intercept(nock => + nock('https://deno.bundlejs.com') + .get(/./) + .query({ q: 'react@18.2.0' }) + .reply(404), + ) + .expectBadge({ label: 'bundlejs', message: 'package or version not found' }) + +t.create('bundlejs/package (timeout)') + .get('/react@18.2.0.json') + .intercept(nock => + nock('https://deno.bundlejs.com') + .get(/./) + .query({ q: 'react@18.2.0' }) + .replyWithError({ code: 'ETIMEDOUT' }), + ) + .expectBadge({ label: 'bundlejs', message: 'timeout' })