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:
Erika 2023-01-19 14:04:15 +01:00 committed by GitHub
parent ce5c5dbd46
commit 35b26f377f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 130 additions and 102 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/image': patch
---
Fix crash on Netlify Functions due to `import.meta.url`

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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