WIP: testing config to switch between Squoosh and Sharp

This commit is contained in:
Tony Sullivan 2022-08-22 17:05:06 -05:00
parent ca0c7e8b83
commit f174b2523e
12 changed files with 290 additions and 105 deletions

View file

@ -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"
},

View file

@ -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);

View file

@ -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],
},
}
};
}

View file

@ -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) {

View file

@ -1,4 +1,8 @@
/// <reference types="astro/astro-jsx" />
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 }>;
}

View file

@ -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 });

View file

@ -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;

View file

@ -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}` {

View file

@ -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<ImageMetadata | undefined> {
const file = await fs.readFile(src);
export interface Metadata extends ImageMetadata {
orientation?: number;
}
export async function metadata(src: string, data?: Buffer): Promise<Metadata | undefined> {
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<ImageMetadata | undefined>
width: isPortrait ? height : width,
height: isPortrait ? width : height,
format: type as InputFormat,
orientation,
};
}

View file

@ -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', () => {

View file

@ -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);
});

View file

@ -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: