Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change how squoosh is loaded in the image integration #6548

Merged
merged 13 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/green-apes-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astrojs/image': patch
---

Use base64 encoded modules for Squoosh integration

This moves `@astrojs/image` to use base64 encoded versions of the Squoosh wasm modules. This is in order to prevent breakage in SSR environments where your files are moved around. This will fix usage of the integration in Netlify.
1 change: 1 addition & 0 deletions packages/integrations/image/package.json
bluwy marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"astro-scripts": "workspace:*",
"chai": "^4.3.6",
"cheerio": "^1.0.0-rc.11",
"fast-glob": "^3.2.11",
"mocha": "^9.2.2",
"rollup-plugin-copy": "^3.4.0",
"sharp": "^0.31.0",
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

47 changes: 23 additions & 24 deletions packages/integrations/image/src/vendor/squoosh/codecs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { promises as fsp } from 'node:fs'
import { getModuleURL, instantiateEmscriptenWasm, pathify } from './emscripten-utils.js'
import { instantiateEmscriptenWasm } from './emscripten-utils.js'

interface DecodeModule extends EmscriptenWasm.Module {
decode: (data: Uint8Array) => ImageData
Expand Down Expand Up @@ -37,41 +36,41 @@ export interface RotateOptions {
import type { MozJPEGModule as MozJPEGEncodeModule } from './mozjpeg/mozjpeg_enc'
import mozDec from './mozjpeg/mozjpeg_node_dec.js'
import mozEnc from './mozjpeg/mozjpeg_node_enc.js'
const mozEncWasm = new URL('./mozjpeg/mozjpeg_node_enc.wasm', getModuleURL(import.meta.url))
const mozDecWasm = new URL('./mozjpeg/mozjpeg_node_dec.wasm', getModuleURL(import.meta.url))
import mozEncWasm from './mozjpeg/mozjpeg_node_enc.wasm.js';
import mozDecWasm from './mozjpeg/mozjpeg_node_dec.wasm.js';

// WebP
import type { WebPModule as WebPEncodeModule } from './webp/webp_enc'
import webpDec from './webp/webp_node_dec.js'
import webpEnc from './webp/webp_node_enc.js'
const webpEncWasm = new URL('./webp/webp_node_enc.wasm', getModuleURL(import.meta.url))
const webpDecWasm = new URL('./webp/webp_node_dec.wasm', getModuleURL(import.meta.url))
import webpEncWasm from './webp/webp_node_enc.wasm.js';
import webpDecWasm from './webp/webp_node_dec.wasm.js';

// AVIF
import type { AVIFModule as AVIFEncodeModule } from './avif/avif_enc'
import avifDec from './avif/avif_node_dec.js'
import avifEnc from './avif/avif_node_enc.js'
const avifEncWasm = new URL('./avif/avif_node_enc.wasm', getModuleURL(import.meta.url))
const avifDecWasm = new URL('./avif/avif_node_dec.wasm', getModuleURL(import.meta.url))
import avifEncWasm from './avif/avif_node_enc.wasm.js';
import avifDecWasm from './avif/avif_node_dec.wasm.js';

// PNG
import * as pngEncDec from './png/squoosh_png.js'
const pngEncDecWasm = new URL('./png/squoosh_png_bg.wasm', getModuleURL(import.meta.url))
import * as pngEncDec from './png/squoosh_png.js';
import pngEncDecWasm from './png/squoosh_png_bg.wasm.js';
const pngEncDecInit = () =>
pngEncDec.default(fsp.readFile(pathify(pngEncDecWasm.toString())))
pngEncDec.default(pngEncDecWasm)

// OxiPNG
import * as oxipng from './png/squoosh_oxipng.js'
const oxipngWasm = new URL('./png/squoosh_oxipng_bg.wasm', getModuleURL(import.meta.url))
const oxipngInit = () => oxipng.default(fsp.readFile(pathify(oxipngWasm.toString())))
import * as oxipng from './png/squoosh_oxipng.js';
import oxipngWasm from './png/squoosh_oxipng_bg.wasm.js';
const oxipngInit = () => oxipng.default(oxipngWasm)

// Resize
import * as resize from './resize/squoosh_resize.js'
const resizeWasm = new URL('./resize/squoosh_resize_bg.wasm', getModuleURL(import.meta.url))
const resizeInit = () => resize.default(fsp.readFile(pathify(resizeWasm.toString())))
import resizeWasm from './resize/squoosh_resize_bg.wasm.js';
const resizeInit = () => resize.default(resizeWasm)

// rotate
const rotateWasm = new URL('./rotate/rotate.wasm', getModuleURL(import.meta.url))
import rotateWasm from './rotate/rotate.wasm.js';

// Our decoders currently rely on a `ImageData` global.
import ImageData from './image_data.js'
Expand Down Expand Up @@ -178,7 +177,7 @@ export const preprocessors = {
const sameDimensions = degrees === 0 || degrees === 180
const size = width * height * 4
const instance = (
await WebAssembly.instantiate(await fsp.readFile(pathify(rotateWasm.toString())))
await WebAssembly.instantiate(rotateWasm)
).instance as RotateModuleInstance
const { memory } = instance.exports
const additionalPagesNeeded = Math.ceil(
Expand Down Expand Up @@ -209,11 +208,11 @@ export const codecs = {
extension: 'jpg',
detectors: [/^\xFF\xD8\xFF/],
dec: () =>
instantiateEmscriptenWasm(mozDec as DecodeModuleFactory, mozDecWasm.toString()),
instantiateEmscriptenWasm(mozDec as DecodeModuleFactory, mozDecWasm),
enc: () =>
instantiateEmscriptenWasm(
mozEnc as EmscriptenWasm.ModuleFactory<MozJPEGEncodeModule>,
mozEncWasm.toString()
mozEncWasm
),
defaultEncoderOptions: {
quality: 75,
Expand Down Expand Up @@ -244,11 +243,11 @@ export const codecs = {
extension: 'webp',
detectors: [/^RIFF....WEBPVP8[LX ]/s],
dec: () =>
instantiateEmscriptenWasm(webpDec as DecodeModuleFactory, webpDecWasm.toString()),
instantiateEmscriptenWasm(webpDec as DecodeModuleFactory, webpDecWasm),
enc: () =>
instantiateEmscriptenWasm(
webpEnc as EmscriptenWasm.ModuleFactory<WebPEncodeModule>,
webpEncWasm.toString()
webpEncWasm
),
defaultEncoderOptions: {
quality: 75,
Expand Down Expand Up @@ -291,11 +290,11 @@ export const codecs = {
// eslint-disable-next-line no-control-regex
detectors: [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/],
dec: () =>
instantiateEmscriptenWasm(avifDec as DecodeModuleFactory, avifDecWasm.toString()),
instantiateEmscriptenWasm(avifDec as DecodeModuleFactory, avifDecWasm),
enc: async () => {
return instantiateEmscriptenWasm(
avifEnc as EmscriptenWasm.ModuleFactory<AVIFEncodeModule>,
avifEncWasm.toString()
avifEncWasm
)
},
defaultEncoderOptions: {
Expand Down
21 changes: 8 additions & 13 deletions packages/integrations/image/src/vendor/squoosh/emscripten-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,14 @@ export function pathify(path: string): string {

export function instantiateEmscriptenWasm<T extends EmscriptenWasm.Module>(
factory: EmscriptenWasm.ModuleFactory<T>,
path: string,
workerJS = ''
bytes: Uint8Array,
): Promise<T> {
return factory({
locateFile(requestPath) {
// The glue code generated by emscripten uses the original
// file names of the worker file and the wasm binary.
// These will have changed in the bundling process and
// we need to inject them here.
if (requestPath.endsWith('.wasm')) return pathify(path)
if (requestPath.endsWith('.worker.js')) return pathify(workerJS)
return requestPath
},
// @ts-expect-error
wasmBinary: bytes,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The emscripten wrapper code accepts this property and it's the way to provide raw bytes, but the TS types do not allow it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to have this comment in the code comment!

locateFile(file: string) {
return file
}
})
}

Expand All @@ -32,12 +27,12 @@ export function dirname(url: string) {

/**
* 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
* import.meta.url is undefined, so we'll fall back to __filename 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 pathToFileURL(__filename).toString();
}

return url
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Buffer.from("AGFzbQEAAAABDAJgAn9/AGADf39/AAMGBQAAAAABBQMBABAGEQJ/AEGAgMAAC38AQYCAwAALBy4EBm1lbW9yeQIABnJvdGF0ZQAECl9fZGF0YV9lbmQDAAtfX2hlYXBfYmFzZQMBCpsJBUkBAX8gACABbCIAQf////8DcSICBEBBCCEBIABBAnRBCGohAANAIAAgASgCADYCACABQQRqIQEgAEEEaiEAIAJBf2oiAg0ACwsLzQMBFH8gAUECdCERIAAgAWwiDEECdEEEaiESA0ACQAJAAkACQCAEQQFxRQRAIAMgAU8NAiADQQFqIQgMAQsgA0EPaiICIANJIggNASACIAFJIgVFDQEgASADQRBqIAgbIAEgBRshCCACIQMLIAEgA0EQaiICIAIgAUsbIQ0gA0F/cyETIBIgA0ECdGshFEEAIQVBACEOA0ACQAJAIA5FBEAgBSAASQ0BQQEhBAwGCyAAIAVBEGogBUEPaiICIAVJIgcbIAAgAiAASRshBUEBIQQgByACIABPcg0FDAELIAUiAkEBaiEFC0EBIQ4gAyANTw0AIAAgAkEQaiIPIAAgD0kbQQJ0IAJBAnRrIRUgEyABIAJsaiEHIBQgASACQQFqbEECdGohCSADIQoDQCAAIApsIgYgAmoiBEEQaiAAIAZqIA8gAEkbIgYgBEkgDCAGSXINAyAEIAZHBEAgBEECdEEIaiELIBUhBiAHIRAgCSEEA0AgDCABIBBqIhBNDQUgBCALKAIANgIAIAQgEWohBCALQQRqIQsgBkF8aiIGDQALCyAHQX9qIQcgCUF8aiEJIA0gCkEBaiIKRw0ACwwACwALDwsACyAIIQMMAAsAC1MBAX8CQCAAIAFsQQJ0IgJBCGoiAEEIRg0AIAAgAmpBfGohAEEAIQEDQCABIAJGDQEgACABQQhqKAIANgIAIABBfGohACACIAFBBGoiAUcNAAsLC9oDARN/IABBf2ohEEEAIAFBAnRrIREgACABbCIMQQJ0QQhqIRIDQAJAAkACQAJAIARBAXFFBEAgAyABTw0CIANBAWohCQwBCyADQQ9qIgIgA0kiCQ0BIAIgAUkiBUUNASABIANBEGogCRsgASAFGyEJIAIhAwsgASADQRBqIgIgAiABSxshDSASIANBAnRqIRNBACEFQQAhBgNAAkACQCAGQQFxRQRAIAUgAEkNAUEBIQQMBgsgACAFQRBqIAVBD2oiAiAFSSIIGyAAIAIgAEkbIQVBASEEIAggAiAAT3INBQwBCyAFIgJBAWohBQtBASEGIAMgDU8NACAAIAJBEGoiDiAAIA5JG0ECdCACQQJ0ayEUIAMgASAAIAJrbGohCCATIAEgECACa2xBAnRqIQogAyELA0AgACALbCIHIAJqIgRBEGogACAHaiAOIABJGyIHIARJIAwgB0lyDQMgBCAHRwRAIARBAnRBCGohBiAUIQcgCCEPIAohBANAIAwgDyABayIPTQ0FIAQgBigCADYCACAEIBFqIQQgBkEEaiEGIAdBfGoiBw0ACwtBASEGIAhBAWohCCAKQQRqIQogDSALQQFqIgtHDQALDAALAAsPCwALIAkhAwwACwALUAACQAJAAkACQCACQbMBTARAIAJFDQIgAkHaAEcNASAAIAEQAQ8LIAJBtAFGDQIgAkGOAkYNAwsACyAAIAEQAA8LIAAgARACDwsgACABEAMLAE0JcHJvZHVjZXJzAghsYW5ndWFnZQEEUnVzdAAMcHJvY2Vzc2VkLWJ5AQVydXN0Yx0xLjQ3LjAgKDE4YmY2YjRmMCAyMDIwLTEwLTA3KQ==", 'base64');

Large diffs are not rendered by default.

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions packages/integrations/image/wasmize.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import fastglob from 'fast-glob';
import { fileURLToPath } from 'node:url';
import * as fs from 'node:fs';

const result = await fastglob(fileURLToPath(new URL('./src/**/*.wasm', import.meta.url)));

for(const filepath of result) {
const buffer = await fs.promises.readFile(filepath);
const base64 = buffer.toString('base64');
const source = `export default Buffer.from(${JSON.stringify(base64)}, 'base64');`;
const outpath = filepath + '.ts';
await fs.promises.writeFile(outpath, source, 'utf-8');
}
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.