Fix image integration crash on Netlify Functions due to import.meta.url
(#5888)
* fix(image): Fix immediate crash on Netlify functions due to `import.meta.url` * chore: changeset
This commit is contained in:
parent
ce5c5dbd46
commit
35b26f377f
10 changed files with 130 additions and 102 deletions
5
.changeset/twenty-boxes-know.md
Normal file
5
.changeset/twenty-boxes-know.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/image': patch
|
||||
---
|
||||
|
||||
Fix crash on Netlify Functions due to `import.meta.url`
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
import { createRequire } from 'module';
|
||||
import { dirname } from '../emscripten-utils.js';
|
||||
const require = createRequire(import.meta.url);
|
||||
import { dirname, getModuleURL } from '../emscripten-utils.js';
|
||||
const require = createRequire(getModuleURL(import.meta.url));
|
||||
|
||||
var Module = (function () {
|
||||
return function (Module) {
|
||||
|
@ -43,7 +43,7 @@ var Module = (function () {
|
|||
if (ENVIRONMENT_IS_WORKER) {
|
||||
scriptDirectory = require('path').dirname(scriptDirectory) + '/'
|
||||
} else {
|
||||
scriptDirectory = dirname(import.meta.url) + '/'
|
||||
scriptDirectory = dirname(getModuleURL(import.meta.url)) + '/'
|
||||
}
|
||||
read_ = function shell_read(filename, binary) {
|
||||
if (!nodeFS) nodeFS = require('fs')
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
import { createRequire } from 'module';
|
||||
import { dirname } from '../emscripten-utils.js';
|
||||
const require = createRequire(import.meta.url);
|
||||
import { dirname, getModuleURL } from '../emscripten-utils.js';
|
||||
const require = createRequire(getModuleURL(import.meta.url));
|
||||
|
||||
var Module = (function () {
|
||||
return function (Module) {
|
||||
|
@ -43,7 +43,7 @@ var Module = (function () {
|
|||
if (ENVIRONMENT_IS_WORKER) {
|
||||
scriptDirectory = require('path').dirname(scriptDirectory) + '/'
|
||||
} else {
|
||||
scriptDirectory = dirname(import.meta.url) + '/'
|
||||
scriptDirectory = dirname(getModuleURL(import.meta.url)) + '/'
|
||||
}
|
||||
read_ = function shell_read(filename, binary) {
|
||||
if (!nodeFS) nodeFS = require('fs')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { promises as fsp } from 'node:fs'
|
||||
import { instantiateEmscriptenWasm, pathify } from './emscripten-utils.js'
|
||||
import { getModuleURL, instantiateEmscriptenWasm, pathify } from './emscripten-utils.js'
|
||||
|
||||
interface DecodeModule extends EmscriptenWasm.Module {
|
||||
decode: (data: Uint8Array) => ImageData
|
||||
|
@ -37,50 +37,50 @@ export interface RotateOptions {
|
|||
import type { MozJPEGModule as MozJPEGEncodeModule } from './mozjpeg/mozjpeg_enc'
|
||||
// @ts-ignore
|
||||
import mozEnc from './mozjpeg/mozjpeg_node_enc.js'
|
||||
const mozEncWasm = new URL('./mozjpeg/mozjpeg_node_enc.wasm', import.meta.url)
|
||||
const mozEncWasm = new URL('./mozjpeg/mozjpeg_node_enc.wasm', getModuleURL(import.meta.url))
|
||||
// @ts-ignore
|
||||
import mozDec from './mozjpeg/mozjpeg_node_dec.js'
|
||||
const mozDecWasm = new URL('./mozjpeg/mozjpeg_node_dec.wasm', import.meta.url)
|
||||
const mozDecWasm = new URL('./mozjpeg/mozjpeg_node_dec.wasm', getModuleURL(import.meta.url))
|
||||
|
||||
// WebP
|
||||
import type { WebPModule as WebPEncodeModule } from './webp/webp_enc'
|
||||
// @ts-ignore
|
||||
import webpEnc from './webp/webp_node_enc.js'
|
||||
const webpEncWasm = new URL('./webp/webp_node_enc.wasm', import.meta.url)
|
||||
const webpEncWasm = new URL('./webp/webp_node_enc.wasm', getModuleURL(import.meta.url))
|
||||
// @ts-ignore
|
||||
import webpDec from './webp/webp_node_dec.js'
|
||||
const webpDecWasm = new URL('./webp/webp_node_dec.wasm', import.meta.url)
|
||||
const webpDecWasm = new URL('./webp/webp_node_dec.wasm', getModuleURL(import.meta.url))
|
||||
|
||||
// AVIF
|
||||
import type { AVIFModule as AVIFEncodeModule } from './avif/avif_enc'
|
||||
// @ts-ignore
|
||||
import avifEnc from './avif/avif_node_enc.js'
|
||||
const avifEncWasm = new URL('./avif/avif_node_enc.wasm', import.meta.url)
|
||||
const avifEncWasm = new URL('./avif/avif_node_enc.wasm', getModuleURL(import.meta.url))
|
||||
// @ts-ignore
|
||||
import avifDec from './avif/avif_node_dec.js'
|
||||
const avifDecWasm = new URL('./avif/avif_node_dec.wasm', import.meta.url)
|
||||
const avifDecWasm = new URL('./avif/avif_node_dec.wasm', getModuleURL(import.meta.url))
|
||||
|
||||
// PNG
|
||||
// @ts-ignore
|
||||
import * as pngEncDec from './png/squoosh_png.js'
|
||||
const pngEncDecWasm = new URL('./png/squoosh_png_bg.wasm', import.meta.url)
|
||||
const pngEncDecWasm = new URL('./png/squoosh_png_bg.wasm', getModuleURL(import.meta.url))
|
||||
const pngEncDecInit = () =>
|
||||
pngEncDec.default(fsp.readFile(pathify(pngEncDecWasm.toString())))
|
||||
|
||||
// OxiPNG
|
||||
// @ts-ignore
|
||||
import * as oxipng from './png/squoosh_oxipng.js'
|
||||
const oxipngWasm = new URL('./png/squoosh_oxipng_bg.wasm', import.meta.url)
|
||||
const oxipngWasm = new URL('./png/squoosh_oxipng_bg.wasm', getModuleURL(import.meta.url))
|
||||
const oxipngInit = () => oxipng.default(fsp.readFile(pathify(oxipngWasm.toString())))
|
||||
|
||||
// Resize
|
||||
// @ts-ignore
|
||||
import * as resize from './resize/squoosh_resize.js'
|
||||
const resizeWasm = new URL('./resize/squoosh_resize_bg.wasm', import.meta.url)
|
||||
const resizeWasm = new URL('./resize/squoosh_resize_bg.wasm', getModuleURL(import.meta.url))
|
||||
const resizeInit = () => resize.default(fsp.readFile(pathify(resizeWasm.toString())))
|
||||
|
||||
// rotate
|
||||
const rotateWasm = new URL('./rotate/rotate.wasm', import.meta.url)
|
||||
const rotateWasm = new URL('./rotate/rotate.wasm', getModuleURL(import.meta.url))
|
||||
|
||||
// Our decoders currently rely on a `ImageData` global.
|
||||
import ImageData from './image_data.js'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
import { fileURLToPath } from 'node:url'
|
||||
//
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
|
||||
export function pathify(path: string): string {
|
||||
if (path.startsWith('file://')) {
|
||||
|
@ -29,3 +29,16 @@ export function instantiateEmscriptenWasm<T extends EmscriptenWasm.Module>(
|
|||
export function dirname(url: string) {
|
||||
return url.substring(0, url.lastIndexOf('/'))
|
||||
}
|
||||
|
||||
/**
|
||||
* On certain serverless hosts, our ESM bundle is transpiled to CJS before being run, which means
|
||||
* import.meta.url is undefined, so we'll fall back to __dirname in those cases
|
||||
* We should be able to remove this once https://github.com/netlify/zip-it-and-ship-it/issues/750 is fixed
|
||||
*/
|
||||
export function getModuleURL(url: string | undefined): string {
|
||||
if (!url) {
|
||||
return pathToFileURL(__dirname).toString();
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
|
|
@ -4,137 +4,147 @@ import { fileURLToPath } from 'url';
|
|||
import type { OutputFormat } from '../../loaders/index.js';
|
||||
import execOnce from '../../utils/execOnce.js';
|
||||
import WorkerPool from '../../utils/workerPool.js';
|
||||
import { getModuleURL } from './emscripten-utils.js';
|
||||
import type { Operation } from './image.js';
|
||||
import * as impl from './impl.js';
|
||||
|
||||
const getWorker = execOnce(
|
||||
() => {
|
||||
return new WorkerPool(
|
||||
// There will be at most 7 workers needed since each worker will take
|
||||
// at least 1 operation type.
|
||||
Math.max(1, Math.min(cpus().length - 1, 7)),
|
||||
fileURLToPath(import.meta.url)
|
||||
);
|
||||
}
|
||||
)
|
||||
const getWorker = execOnce(() => {
|
||||
return new WorkerPool(
|
||||
// There will be at most 7 workers needed since each worker will take
|
||||
// at least 1 operation type.
|
||||
Math.max(1, Math.min(cpus().length - 1, 7)),
|
||||
fileURLToPath(getModuleURL(import.meta.url))
|
||||
);
|
||||
});
|
||||
|
||||
type DecodeParams = {
|
||||
operation: 'decode',
|
||||
buffer: Buffer
|
||||
operation: 'decode';
|
||||
buffer: Buffer;
|
||||
};
|
||||
type ResizeParams = {
|
||||
operation: 'resize',
|
||||
imageData: ImageData,
|
||||
height?: number,
|
||||
width?: number
|
||||
operation: 'resize';
|
||||
imageData: ImageData;
|
||||
height?: number;
|
||||
width?: number;
|
||||
};
|
||||
type RotateParams = {
|
||||
operation: 'rotate',
|
||||
imageData: ImageData,
|
||||
numRotations: number
|
||||
operation: 'rotate';
|
||||
imageData: ImageData;
|
||||
numRotations: number;
|
||||
};
|
||||
type EncodeAvifParams = {
|
||||
operation: 'encodeavif',
|
||||
imageData: ImageData,
|
||||
quality: number
|
||||
}
|
||||
operation: 'encodeavif';
|
||||
imageData: ImageData;
|
||||
quality: number;
|
||||
};
|
||||
type EncodeJpegParams = {
|
||||
operation: 'encodejpeg',
|
||||
imageData: ImageData,
|
||||
quality: number
|
||||
}
|
||||
operation: 'encodejpeg';
|
||||
imageData: ImageData;
|
||||
quality: number;
|
||||
};
|
||||
type EncodePngParams = {
|
||||
operation: 'encodepng',
|
||||
imageData: ImageData
|
||||
}
|
||||
operation: 'encodepng';
|
||||
imageData: ImageData;
|
||||
};
|
||||
type EncodeWebpParams = {
|
||||
operation: 'encodewebp',
|
||||
imageData: ImageData,
|
||||
quality: number
|
||||
}
|
||||
type JobMessage = DecodeParams | ResizeParams | RotateParams | EncodeAvifParams | EncodeJpegParams | EncodePngParams | EncodeWebpParams
|
||||
operation: 'encodewebp';
|
||||
imageData: ImageData;
|
||||
quality: number;
|
||||
};
|
||||
type JobMessage =
|
||||
| DecodeParams
|
||||
| ResizeParams
|
||||
| RotateParams
|
||||
| EncodeAvifParams
|
||||
| EncodeJpegParams
|
||||
| EncodePngParams
|
||||
| EncodeWebpParams;
|
||||
|
||||
function handleJob(params: JobMessage) {
|
||||
switch (params.operation) {
|
||||
case 'decode':
|
||||
return impl.decodeBuffer(params.buffer)
|
||||
switch (params.operation) {
|
||||
case 'decode':
|
||||
return impl.decodeBuffer(params.buffer);
|
||||
case 'resize':
|
||||
return impl.resize({ image: params.imageData as any, width: params.width, height: params.height })
|
||||
return impl.resize({
|
||||
image: params.imageData as any,
|
||||
width: params.width,
|
||||
height: params.height,
|
||||
});
|
||||
case 'rotate':
|
||||
return impl.rotate(params.imageData as any, params.numRotations);
|
||||
case 'encodeavif':
|
||||
return impl.encodeAvif(params.imageData as any, { quality: params.quality })
|
||||
return impl.encodeAvif(params.imageData as any, { quality: params.quality });
|
||||
case 'encodejpeg':
|
||||
return impl.encodeJpeg(params.imageData as any, { quality: params.quality })
|
||||
return impl.encodeJpeg(params.imageData as any, { quality: params.quality });
|
||||
case 'encodepng':
|
||||
return impl.encodePng(params.imageData as any)
|
||||
return impl.encodePng(params.imageData as any);
|
||||
case 'encodewebp':
|
||||
return impl.encodeWebp(params.imageData as any, { quality: params.quality })
|
||||
default:
|
||||
throw Error(`Invalid job "${(params as any).operation}"`);
|
||||
}
|
||||
return impl.encodeWebp(params.imageData as any, { quality: params.quality });
|
||||
default:
|
||||
throw Error(`Invalid job "${(params as any).operation}"`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function processBuffer(
|
||||
buffer: Buffer,
|
||||
operations: Operation[],
|
||||
encoding: OutputFormat,
|
||||
quality?: number
|
||||
buffer: Buffer,
|
||||
operations: Operation[],
|
||||
encoding: OutputFormat,
|
||||
quality?: number
|
||||
): Promise<Uint8Array> {
|
||||
// @ts-ignore
|
||||
const worker = await getWorker()
|
||||
const worker = await getWorker();
|
||||
|
||||
let imageData = await worker.dispatchJob({
|
||||
let imageData = await worker.dispatchJob({
|
||||
operation: 'decode',
|
||||
buffer,
|
||||
})
|
||||
for (const operation of operations) {
|
||||
if (operation.type === 'rotate') {
|
||||
});
|
||||
for (const operation of operations) {
|
||||
if (operation.type === 'rotate') {
|
||||
imageData = await worker.dispatchJob({
|
||||
operation: 'rotate',
|
||||
imageData,
|
||||
numRotations: operation.numRotations
|
||||
numRotations: operation.numRotations,
|
||||
});
|
||||
} else if (operation.type === 'resize') {
|
||||
} else if (operation.type === 'resize') {
|
||||
imageData = await worker.dispatchJob({
|
||||
operation: 'resize',
|
||||
imageData,
|
||||
height: operation.height,
|
||||
width: operation.width
|
||||
})
|
||||
}
|
||||
}
|
||||
width: operation.width,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
switch (encoding) {
|
||||
case 'avif':
|
||||
return await worker.dispatchJob({
|
||||
return (await worker.dispatchJob({
|
||||
operation: 'encodeavif',
|
||||
imageData,
|
||||
quality,
|
||||
}) as Uint8Array;
|
||||
})) as Uint8Array;
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
return await worker.dispatchJob({
|
||||
return (await worker.dispatchJob({
|
||||
operation: 'encodejpeg',
|
||||
imageData,
|
||||
quality,
|
||||
}) as Uint8Array;
|
||||
})) as Uint8Array;
|
||||
case 'png':
|
||||
return await worker.dispatchJob({
|
||||
return (await worker.dispatchJob({
|
||||
operation: 'encodepng',
|
||||
imageData,
|
||||
}) as Uint8Array;
|
||||
})) as Uint8Array;
|
||||
case 'webp':
|
||||
return await worker.dispatchJob({
|
||||
return (await worker.dispatchJob({
|
||||
operation: 'encodewebp',
|
||||
imageData,
|
||||
quality,
|
||||
}) as Uint8Array;
|
||||
})) as Uint8Array;
|
||||
default:
|
||||
throw Error(`Unsupported encoding format`)
|
||||
throw Error(`Unsupported encoding format`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMainThread) {
|
||||
WorkerPool.useThisThreadAsWorker(handleJob);
|
||||
WorkerPool.useThisThreadAsWorker(handleJob);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
import { createRequire } from 'module';
|
||||
import { dirname } from '../emscripten-utils.js';
|
||||
const require = createRequire(import.meta.url);
|
||||
import { dirname, getModuleURL } from '../emscripten-utils.js';
|
||||
const require = createRequire(getModuleURL(import.meta.url));
|
||||
|
||||
var Module = (function () {
|
||||
return function (Module) {
|
||||
|
@ -43,7 +43,7 @@ var Module = (function () {
|
|||
if (ENVIRONMENT_IS_WORKER) {
|
||||
scriptDirectory = require('path').dirname(scriptDirectory) + '/'
|
||||
} else {
|
||||
scriptDirectory = dirname(import.meta.url) + '/'
|
||||
scriptDirectory = dirname(getModuleURL(import.meta.url)) + '/'
|
||||
}
|
||||
read_ = function shell_read(filename, binary) {
|
||||
if (!nodeFS) nodeFS = require('fs')
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
import { createRequire } from 'module';
|
||||
import { dirname } from '../emscripten-utils.js';
|
||||
const require = createRequire(import.meta.url);
|
||||
import { dirname, getModuleURL } from '../emscripten-utils.js';
|
||||
const require = createRequire(getModuleURL(import.meta.url));
|
||||
|
||||
var Module = (function () {
|
||||
return function (Module) {
|
||||
|
@ -43,7 +43,7 @@ var Module = (function () {
|
|||
if (ENVIRONMENT_IS_WORKER) {
|
||||
scriptDirectory = require('path').dirname(scriptDirectory) + '/'
|
||||
} else {
|
||||
scriptDirectory = dirname(import.meta.url) + '/'
|
||||
scriptDirectory = dirname(getModuleURL(import.meta.url)) + '/'
|
||||
}
|
||||
read_ = function shell_read(filename, binary) {
|
||||
if (!nodeFS) nodeFS = require('fs')
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
import { createRequire } from 'module';
|
||||
import { dirname } from '../emscripten-utils.js';
|
||||
const require = createRequire(import.meta.url);
|
||||
import { dirname, getModuleURL } from '../emscripten-utils.js';
|
||||
const require = createRequire(getModuleURL(import.meta.url));
|
||||
|
||||
var Module = (function () {
|
||||
return function (Module) {
|
||||
|
@ -43,7 +43,7 @@ var Module = (function () {
|
|||
if (ENVIRONMENT_IS_WORKER) {
|
||||
scriptDirectory = require('path').dirname(scriptDirectory) + '/'
|
||||
} else {
|
||||
scriptDirectory = dirname(import.meta.url) + '/'
|
||||
scriptDirectory = dirname(getModuleURL(import.meta.url)) + '/'
|
||||
}
|
||||
read_ = function shell_read(filename, binary) {
|
||||
if (!nodeFS) nodeFS = require('fs')
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
import { createRequire } from 'module';
|
||||
import { dirname } from '../emscripten-utils.js';
|
||||
const require = createRequire(import.meta.url);
|
||||
import { dirname, getModuleURL } from '../emscripten-utils.js';
|
||||
const require = createRequire(getModuleURL(import.meta.url));
|
||||
|
||||
var Module = (function () {
|
||||
return function (Module) {
|
||||
|
@ -43,7 +43,7 @@ var Module = (function () {
|
|||
if (ENVIRONMENT_IS_WORKER) {
|
||||
scriptDirectory = require('path').dirname(scriptDirectory) + '/'
|
||||
} else {
|
||||
scriptDirectory = dirname(import.meta.url) + '/'
|
||||
scriptDirectory = dirname(getModuleURL(import.meta.url)) + '/'
|
||||
}
|
||||
read_ = function shell_read(filename, binary) {
|
||||
if (!nodeFS) nodeFS = require('fs')
|
||||
|
|
Loading…
Reference in a new issue