diff --git a/packages/integrations/image/package.json b/packages/integrations/image/package.json index 65e1ecc2c..931a3504f 100644 --- a/packages/integrations/image/package.json +++ b/packages/integrations/image/package.json @@ -22,6 +22,7 @@ "exports": { ".": "./dist/index.js", "./sharp": "./dist/loaders/sharp.js", + "./squoosh": "./dist/loaders/squoosh.js", "./endpoints/dev": "./dist/endpoints/dev.js", "./endpoints/prod": "./dist/endpoints/prod.js", "./components": "./components/index.js", @@ -41,10 +42,10 @@ "test": "mocha --exit --timeout 20000 test" }, "dependencies": { + "@squoosh/lib": "^0.4.0", "etag": "^1.8.1", "image-size": "^1.0.1", "mrmime": "^1.0.0", - "sharp": "^0.30.6", "slash": "^4.0.0", "tiny-glob": "^0.2.9" }, diff --git a/packages/integrations/image/src/endpoints/dev.ts b/packages/integrations/image/src/endpoints/dev.ts index dfa7f4900..0924bedcc 100644 --- a/packages/integrations/image/src/endpoints/dev.ts +++ b/packages/integrations/image/src/endpoints/dev.ts @@ -1,9 +1,11 @@ import type { APIRoute } from 'astro'; import { lookup } from 'mrmime'; -import loader from '../loaders/sharp.js'; import { loadImage } from '../utils/images.js'; export const get: APIRoute = async ({ request }) => { + // @ts-ignore + const { default: loader } = await import('virtual:image-loader'); + try { const url = new URL(request.url); const transform = loader.parseTransform(url.searchParams); diff --git a/packages/integrations/image/src/index.ts b/packages/integrations/image/src/index.ts index 28df1ec38..2d6c6f02f 100644 --- a/packages/integrations/image/src/index.ts +++ b/packages/integrations/image/src/index.ts @@ -33,7 +33,7 @@ export interface IntegrationOptions { export default function integration(options: IntegrationOptions = {}): AstroIntegration { const resolvedOptions = { - serviceEntryPoint: '@astrojs/image/sharp', + serviceEntryPoint: '@astrojs/image/squoosh', logLevel: 'info' as LoggerLevel, ...options, }; @@ -48,11 +48,15 @@ export default function integration(options: IntegrationOptions = {}): AstroInte return { plugins: [createPlugin(_config, resolvedOptions)], optimizeDeps: { - include: ['image-size', 'sharp'], + include: [ + 'image-size', + resolvedOptions.serviceEntryPoint === '@astrojs/image/sharp' && 'sharp', + resolvedOptions.serviceEntryPoint === '@astrojs/image/squoosh' && '@squoosh/lib', + ].filter(Boolean), }, ssr: { noExternal: ['@astrojs/image', resolvedOptions.serviceEntryPoint], - }, + } }; } diff --git a/packages/integrations/image/src/lib/get-image.ts b/packages/integrations/image/src/lib/get-image.ts index e2fabda55..1b88eba0d 100644 --- a/packages/integrations/image/src/lib/get-image.ts +++ b/packages/integrations/image/src/lib/get-image.ts @@ -2,7 +2,6 @@ import slash from 'slash'; import { ROUTE_PATTERN } from '../constants.js'; import { ImageService, isSSRService, OutputFormat, TransformOptions } from '../loaders/index.js'; -import sharp from '../loaders/sharp.js'; import { isRemoteImage, parseAspectRatio } from '../utils/images.js'; import { ImageMetadata } from '../vite-plugin-astro-image.js'; @@ -126,15 +125,9 @@ export async function getImage( const isDev = import.meta.env?.DEV; const isLocalImage = !isRemoteImage(resolved.src); - const _loader = isDev && isLocalImage ? sharp : loader; - - if (!_loader) { - throw new Error('@astrojs/image: loader not found!'); - } - // For SSR services, build URLs for the injected route - if (isSSRService(_loader)) { - const { searchParams } = _loader.serializeTransform(resolved); + if (isSSRService(loader)) { + const { searchParams } = loader.serializeTransform(resolved); // cache all images rendered to HTML if (globalThis.astroImage?.addStaticImage) { diff --git a/packages/integrations/image/src/loaders/index.ts b/packages/integrations/image/src/loaders/index.ts index 7681f25d4..2593531a5 100644 --- a/packages/integrations/image/src/loaders/index.ts +++ b/packages/integrations/image/src/loaders/index.ts @@ -1,4 +1,8 @@ /// + +import { isAspectRatioString, isOutputFormat } from "../utils/images.js"; + + export type InputFormat = | 'heic' | 'heif' @@ -10,7 +14,7 @@ export type InputFormat = | 'webp' | 'gif'; -export type OutputFormat = 'avif' | 'jpeg' | 'png' | 'webp'; +export type OutputFormat = 'avif' | 'jpeg' | 'jpg' | 'png' | 'webp'; /** * Defines the original image and transforms that need to be applied to it. @@ -103,3 +107,85 @@ export function isHostedService(service: ImageService): service is ImageService export function isSSRService(service: ImageService): service is SSRImageService { return 'transform' in service; } + +export abstract class BaseSSRService implements SSRImageService { + async getImageAttributes(transform: TransformOptions) { + // strip off the known attributes + const { width, height, src, format, quality, aspectRatio, ...rest } = transform; + + return { + ...rest, + width: width, + height: height, + }; + } + + serializeTransform(transform: TransformOptions) { + const searchParams = new URLSearchParams(); + + if (transform.quality) { + searchParams.append('q', transform.quality.toString()); + } + + if (transform.format) { + searchParams.append('f', transform.format); + } + + if (transform.width) { + searchParams.append('w', transform.width.toString()); + } + + if (transform.height) { + searchParams.append('h', transform.height.toString()); + } + + if (transform.aspectRatio) { + searchParams.append('ar', transform.aspectRatio.toString()); + } + + searchParams.append('href', transform.src); + + return { searchParams }; + } + + parseTransform(searchParams: URLSearchParams) { + if (!searchParams.has('href')) { + return undefined; + } + + let transform: TransformOptions = { src: searchParams.get('href')! }; + + if (searchParams.has('q')) { + transform.quality = parseInt(searchParams.get('q')!); + } + + if (searchParams.has('f')) { + const format = searchParams.get('f')!; + if (isOutputFormat(format)) { + transform.format = format; + } + } + + if (searchParams.has('w')) { + transform.width = parseInt(searchParams.get('w')!); + } + + if (searchParams.has('h')) { + transform.height = parseInt(searchParams.get('h')!); + } + + if (searchParams.has('ar')) { + const ratio = searchParams.get('ar')!; + + if (isAspectRatioString(ratio)) { + transform.aspectRatio = ratio; + } else { + transform.aspectRatio = parseFloat(ratio); + } + } + + return transform; + } + + abstract transform(inputBuffer: Buffer, transform: TransformOptions): Promise<{ data: Buffer, format: OutputFormat }>; +} diff --git a/packages/integrations/image/src/loaders/sharp.ts b/packages/integrations/image/src/loaders/sharp.ts index 2368e43d1..1ff35b368 100644 --- a/packages/integrations/image/src/loaders/sharp.ts +++ b/packages/integrations/image/src/loaders/sharp.ts @@ -1,86 +1,9 @@ import sharp from 'sharp'; import { isAspectRatioString, isOutputFormat } from '../utils/images.js'; +import { BaseSSRService } from './index.js'; import type { OutputFormat, SSRImageService, TransformOptions } from './index.js'; -class SharpService implements SSRImageService { - async getImageAttributes(transform: TransformOptions) { - // strip off the known attributes - const { width, height, src, format, quality, aspectRatio, ...rest } = transform; - - return { - ...rest, - width: width, - height: height, - }; - } - - serializeTransform(transform: TransformOptions) { - const searchParams = new URLSearchParams(); - - if (transform.quality) { - searchParams.append('q', transform.quality.toString()); - } - - if (transform.format) { - searchParams.append('f', transform.format); - } - - if (transform.width) { - searchParams.append('w', transform.width.toString()); - } - - if (transform.height) { - searchParams.append('h', transform.height.toString()); - } - - if (transform.aspectRatio) { - searchParams.append('ar', transform.aspectRatio.toString()); - } - - searchParams.append('href', transform.src); - - return { searchParams }; - } - - parseTransform(searchParams: URLSearchParams) { - if (!searchParams.has('href')) { - return undefined; - } - - let transform: TransformOptions = { src: searchParams.get('href')! }; - - if (searchParams.has('q')) { - transform.quality = parseInt(searchParams.get('q')!); - } - - if (searchParams.has('f')) { - const format = searchParams.get('f')!; - if (isOutputFormat(format)) { - transform.format = format; - } - } - - if (searchParams.has('w')) { - transform.width = parseInt(searchParams.get('w')!); - } - - if (searchParams.has('h')) { - transform.height = parseInt(searchParams.get('h')!); - } - - if (searchParams.has('ar')) { - const ratio = searchParams.get('ar')!; - - if (isAspectRatioString(ratio)) { - transform.aspectRatio = ratio; - } else { - transform.aspectRatio = parseFloat(ratio); - } - } - - return transform; - } - +class SharpService extends BaseSSRService { async transform(inputBuffer: Buffer, transform: TransformOptions) { const sharpImage = sharp(inputBuffer, { failOnError: false, pages: -1 }); diff --git a/packages/integrations/image/src/loaders/squoosh.ts b/packages/integrations/image/src/loaders/squoosh.ts new file mode 100644 index 000000000..29654c978 --- /dev/null +++ b/packages/integrations/image/src/loaders/squoosh.ts @@ -0,0 +1,130 @@ +// @ts-ignore +import { ImagePool } from '@squoosh/lib'; +import { red } from 'kleur/colors'; +import { BaseSSRService } from './index.js'; +import { isAspectRatioString, isOutputFormat } from '../utils/images.js'; +import { error } from '../utils/logger.js'; +import { metadata } from '../utils/metadata.js'; +import type { OutputFormat, SSRImageService, TransformOptions } from './index.js'; + +class SquooshService extends BaseSSRService { + /** + * Squoosh doesn't support multithreading when transforming to AVIF files. + * + * https://github.com/GoogleChromeLabs/squoosh/issues/1111 + */ + #imagePool = new ImagePool(1); + + async processAvif(image: any, transform: TransformOptions) { + const encodeOptions = transform.quality + ? { avif: { quality: transform.quality } } + : { avif: {} }; + await image.encode(encodeOptions); + const data = await image.encodedWith.avif; + + return { + data: data.binary, + format: 'avif' as OutputFormat, + }; + } + + async processJpeg(image: any, transform: TransformOptions) { + const encodeOptions = transform.quality + ? { mozjpeg: { quality: transform.quality } } + : { mozjpeg: {} }; + await image.encode(encodeOptions); + const data = await image.encodedWith.mozjpeg; + + return { + data: data.binary, + format: 'jpeg' as OutputFormat, + }; + } + + async processPng(image: any, transform: TransformOptions) { + await image.encode({ oxipng: {} }); + const data = await image.encodedWith.oxipng; + + return { + data: data.binary, + format: 'png' as OutputFormat, + }; + } + + async processWebp(image: any, transform: TransformOptions) { + const encodeOptions = transform.quality + ? { webp: { quality: transform.quality } } + : { webp: {} }; + await image.encode(encodeOptions); + const data = await image.encodedWith.webp; + + return { + data: data.binary, + format: 'png' as OutputFormat, + }; + } + + async autorotate(image: any, transform: TransformOptions, inputBuffer: Buffer) { + // check EXIF orientation data and rotate the image if needed + const meta = await metadata(transform.src, inputBuffer); + + switch (meta?.orientation) { + case 3: + case 4: + await image.preprocess({ rotate: { numRotations: 2 } }); + break; + case 5: + case 6: + await image.preprocess({ rotate: { numRotations: 1 } }); + break; + case 7: + case 8: + await image.preprocess({ rotate: { numRotations: 3 } }); + break; + } + } + + async transform(inputBuffer: Buffer, transform: TransformOptions) { + const image = this.#imagePool.ingestImage(inputBuffer); + + let preprocessOptions: any = {}; + + // Image files lie! Rotate the image based on EXIF data + await this.autorotate(image, transform, inputBuffer); + + if (transform.width || transform.height) { + const width = transform.width && Math.round(transform.width); + const height = transform.height && Math.round(transform.height); + + preprocessOptions.resize = { + width, + height, + }; + + await image.preprocess({ resize: { width, height } }); + } + + switch (transform.format) { + case 'avif': + return await this.processAvif(image, transform); + case 'jpg': + case 'jpeg': + return await this.processJpeg(image, transform); + case 'png': + return await this.processPng(image, transform); + case 'webp': + return await this.processWebp(image, transform); + default: + error({ + level: 'info', + prefix: false, + message: red(`Unknown image output: "${transform.format}" used for ${transform.src}`), + }); + throw new Error(`Unknown image output: "${transform.format}" used for ${transform.src}`); + } + } +} + +const service = new SquooshService(); + +export default service; diff --git a/packages/integrations/image/src/utils/images.ts b/packages/integrations/image/src/utils/images.ts index cc5a26cdc..5961a1c99 100644 --- a/packages/integrations/image/src/utils/images.ts +++ b/packages/integrations/image/src/utils/images.ts @@ -2,7 +2,7 @@ import fs from 'node:fs/promises'; import type { OutputFormat, TransformOptions } from '../loaders/index.js'; export function isOutputFormat(value: string): value is OutputFormat { - return ['avif', 'jpeg', 'png', 'webp'].includes(value); + return ['avif', 'jpeg', 'jpg', 'png', 'webp'].includes(value); } export function isAspectRatioString(value: string): value is `${number}:${number}` { diff --git a/packages/integrations/image/src/utils/metadata.ts b/packages/integrations/image/src/utils/metadata.ts index 349a37535..df3be38a3 100644 --- a/packages/integrations/image/src/utils/metadata.ts +++ b/packages/integrations/image/src/utils/metadata.ts @@ -3,8 +3,12 @@ import fs from 'node:fs/promises'; import { InputFormat } from '../loaders/index.js'; import { ImageMetadata } from '../vite-plugin-astro-image.js'; -export async function metadata(src: string): Promise { - const file = await fs.readFile(src); +export interface Metadata extends ImageMetadata { + orientation?: number; +} + +export async function metadata(src: string, data?: Buffer): Promise { + const file = data || await fs.readFile(src); const { width, height, type, orientation } = await sizeOf(file); const isPortrait = (orientation || 0) >= 5; @@ -18,5 +22,6 @@ export async function metadata(src: string): Promise width: isPortrait ? height : width, height: isPortrait ? width : height, format: type as InputFormat, + orientation, }; } diff --git a/packages/integrations/image/test/rotation.test.js b/packages/integrations/image/test/rotation.test.js index 19333e572..d0b1211f7 100644 --- a/packages/integrations/image/test/rotation.test.js +++ b/packages/integrations/image/test/rotation.test.js @@ -15,7 +15,7 @@ describe('Image rotation', function () { const url = new URL('./fixtures/rotation/dist/' + pathname, import.meta.url); const dist = fileURLToPath(url); const result = sizeOf(dist); - expect(result).to.deep.equal(expected); + expect(result, pathname).to.deep.equal(expected); } describe('build', () => { diff --git a/packages/integrations/image/test/sharp.test.js b/packages/integrations/image/test/sharp.test.js index 82e332e3d..fc7b22995 100644 --- a/packages/integrations/image/test/sharp.test.js +++ b/packages/integrations/image/test/sharp.test.js @@ -1,7 +1,9 @@ import { expect } from 'chai'; -import sharp from '../dist/loaders/sharp.js'; +import { BaseSSRService } from '../dist/loaders/index.js'; + +describe('abstract SSR service', () => { + const service = new BaseSSRService(); -describe('Sharp service', () => { describe('serializeTransform', () => { const src = '/assets/image.png'; @@ -16,7 +18,7 @@ describe('Sharp service', () => { ['aspect ratio float', { src, aspectRatio: 1.7 }], ].forEach(([description, props]) => { it(description, async () => { - const { searchParams } = await sharp.serializeTransform(props); + const { searchParams } = await service.serializeTransform(props); function verifyProp(expected, search) { if (expected) { @@ -52,7 +54,7 @@ describe('Sharp service', () => { ].forEach(([description, params, expected]) => { it(description, async () => { const searchParams = new URLSearchParams(params); - const props = sharp.parseTransform(searchParams); + const props = service.parseTransform(searchParams); expect(props).to.deep.equal(expected); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c19dc7e09..36ef5d0c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2192,6 +2192,7 @@ importers: packages/integrations/image: specifiers: + '@squoosh/lib': ^0.4.0 '@types/etag': ^1.8.1 '@types/sharp': ^0.30.4 astro: workspace:* @@ -2200,14 +2201,13 @@ importers: image-size: ^1.0.1 kleur: ^4.1.4 mrmime: ^1.0.0 - sharp: ^0.30.6 slash: ^4.0.0 tiny-glob: ^0.2.9 dependencies: + '@squoosh/lib': 0.4.0 etag: 1.8.1 image-size: 1.0.2 mrmime: 1.0.1 - sharp: 0.30.7 slash: 4.0.0 tiny-glob: 0.2.9 devDependencies: @@ -2242,10 +2242,13 @@ importers: '@astrojs/image': workspace:* '@astrojs/node': workspace:* astro: workspace:* + sharp: ^0.30.7 dependencies: '@astrojs/image': link:../../.. '@astrojs/node': link:../../../../node astro: link:../../../../../astro + devDependencies: + sharp: 0.30.7 packages/integrations/lit: specifiers: @@ -8440,6 +8443,14 @@ packages: - react-dom dev: false + /@squoosh/lib/0.4.0: + resolution: {integrity: sha512-O1LyugWLZjMI4JZeZMA5vzfhfPjfMZXH5/HmVkRagP8B70wH3uoR7tjxfGNdSavey357MwL8YJDxbGwBBdHp7Q==} + engines: {node: ' ^12.5.0 || ^14.0.0 || ^16.0.0 '} + dependencies: + wasm-feature-detect: 1.2.11 + web-streams-polyfill: 3.2.1 + dev: false + /@surma/rollup-plugin-off-main-thread/2.2.3: resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} dependencies: @@ -9627,6 +9638,7 @@ packages: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.0 + dev: true /bl/5.0.0: resolution: {integrity: sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==} @@ -9708,6 +9720,7 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: true /buffer/6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -9976,6 +9989,7 @@ packages: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 + dev: true /color-support/1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} @@ -9988,6 +10002,7 @@ packages: dependencies: color-convert: 2.0.1 color-string: 1.9.1 + dev: true /colorette/2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} @@ -10262,6 +10277,7 @@ packages: engines: {node: '>=10'} dependencies: mimic-response: 3.1.0 + dev: true /dedent-js/1.0.1: resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} @@ -10513,6 +10529,7 @@ packages: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 + dev: true /enquirer/2.3.6: resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} @@ -11259,6 +11276,7 @@ packages: /expand-template/2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + dev: true /extend-shallow/2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} @@ -11440,6 +11458,7 @@ packages: /fs-constants/1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: true /fs-extra/7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} @@ -11601,7 +11620,8 @@ packages: dev: true /github-from-package/0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + resolution: {integrity: sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=} + dev: true /github-slugger/1.4.0: resolution: {integrity: sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==} @@ -12132,6 +12152,7 @@ packages: /is-arrayish/0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: true /is-bigint/1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -13355,6 +13376,7 @@ packages: /mimic-response/3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + dev: true /min-indent/1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} @@ -13466,6 +13488,7 @@ packages: /mkdirp-classic/0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: true /mkdirp/0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} @@ -13547,6 +13570,7 @@ packages: /napi-build-utils/1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: true /natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -13596,9 +13620,11 @@ packages: engines: {node: '>=10'} dependencies: semver: 7.3.7 + dev: true /node-addon-api/5.0.0: resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==} + dev: true /node-domexception/1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} @@ -14564,6 +14590,7 @@ packages: simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 + dev: true /preferred-pm/3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} @@ -14684,6 +14711,7 @@ packages: dependencies: end-of-stream: 1.4.4 once: 1.4.0 + dev: true /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} @@ -15357,6 +15385,7 @@ packages: simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 + dev: true /shebang-command/1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} @@ -15419,6 +15448,7 @@ packages: /simple-concat/1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: true /simple-get/4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} @@ -15426,11 +15456,13 @@ packages: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 + dev: true /simple-swizzle/0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: is-arrayish: 0.3.2 + dev: true /sirv/1.0.19: resolution: {integrity: sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==} @@ -15934,6 +15966,7 @@ packages: mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 + dev: true /tar-stream/2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -15944,6 +15977,7 @@ packages: fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.0 + dev: true /tar/4.4.19: resolution: {integrity: sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==} @@ -16158,6 +16192,7 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: safe-buffer: 5.2.1 + dev: true /turbo-darwin-64/1.2.5: resolution: {integrity: sha512-AjMEF8hlA9vy1gXLHBruqgO42s0M0rKKZLQPM239wli5lKEprmxd8WMSjd9YmxRflS+/fwrXfjVl0QRhHjDIww==} @@ -16846,6 +16881,10 @@ packages: '@vue/server-renderer': 3.2.37_vue@3.2.37 '@vue/shared': 3.2.37 + /wasm-feature-detect/1.2.11: + resolution: {integrity: sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w==} + dev: false + /wcwidth/1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: