Skip to content

Commit

Permalink
Fix image integration crash on Netlify Functions due to `import.meta.…
Browse files Browse the repository at this point in the history
…url` (#5888)

* fix(image): Fix immediate crash on Netlify functions due to `import.meta.url`

* chore: changeset
  • Loading branch information
Princesseuh committed Jan 19, 2023
1 parent ce5c5db commit 35b26f3
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 102 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-boxes-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/image': patch
---

Fix crash on Netlify Functions due to `import.meta.url`
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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')
Expand Down
22 changes: 11 additions & 11 deletions packages/integrations/image/src/vendor/squoosh/codecs.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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'
Expand Down
17 changes: 15 additions & 2 deletions packages/integrations/image/src/vendor/squoosh/emscripten-utils.ts
Original file line number Diff line number Diff line change
@@ -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://')) {
Expand Down Expand Up @@ -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
}
152 changes: 81 additions & 71 deletions packages/integrations/image/src/vendor/squoosh/image-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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')
Expand Down
Loading

0 comments on commit 35b26f3

Please sign in to comment.