mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-21 10:54:10 +08:00
## Summary - Extends client-side image optimization to convert **JXL → JPEG**, **HEIC → JPEG**, and **animated GIF → animated WEBP** using jSquash WASM packages before upload - Transparent JXL/HEIC images are converted to **WEBP** instead of JPEG, so transparency is preserved (WEBP compresses much better than PNG for this case) - Gated as an **upcoming change** via `composer_media_optimization_image_convert_enabled` (experimental, `feature,all_members`, opt-in per group through the upcoming changes admin UI) - Sends raw file bytes to Web Worker for formats browsers can't decode natively (JXL, HEIC), with new `"convert"` and `"convertAnimated"` worker message types - Adds JXL to `authorized_extensions` and `supported_images`; adds HEIC/HEIF to `supported_images` for consistency - Skips GIF→WEBP when output is larger than input - Falls back to filename when MIME type is missing (browsers may not recognize JXL/HEIC) ### Note on jSquash packages This depends on Discourse-scoped forks of the jSquash packages (`@discourse/jxl`, `@discourse/heic`, `@discourse/webp`, `@discourse/gif`, `@discourse/jpeg`, `@discourse/resize`). The following upstream PRs would let us move back to the canonical `@jsquash/*` packages, but the upstream maintainer is currently unresponsive: - https://github.com/jamsinclair/jSquash/pull/101 (`@jsquash/heic`) - https://github.com/jamsinclair/jSquash/pull/103 (`@jsquash/webp` animated support) - https://github.com/jamsinclair/jSquash/pull/104 (`@jsquash/gif`) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
121 lines
3.1 KiB
JavaScript
Vendored
121 lines
3.1 KiB
JavaScript
Vendored
// The media optimization bundle uses webpack's import-scripts chunk loader,
|
|
// so worker-only dynamic chunks can be loaded without a DOM shim.
|
|
onmessage = async function (e) {
|
|
switch (e.data.type) {
|
|
case "compress":
|
|
try {
|
|
globalThis.debugMode = e.data.settings.debug_mode;
|
|
|
|
let optimized = await globalThis.optimize(
|
|
e.data.file,
|
|
e.data.fileName,
|
|
e.data.width,
|
|
e.data.height,
|
|
e.data.originalFileSize,
|
|
e.data.settings
|
|
);
|
|
postMessage(
|
|
{
|
|
type: "file",
|
|
file: optimized,
|
|
fileName: e.data.fileName,
|
|
fileId: e.data.fileId,
|
|
},
|
|
[optimized]
|
|
);
|
|
} catch (error) {
|
|
console.error(error);
|
|
postMessage({
|
|
type: "error",
|
|
file: e.data.file,
|
|
fileName: e.data.fileName,
|
|
fileId: e.data.fileId,
|
|
});
|
|
}
|
|
break;
|
|
case "convert":
|
|
try {
|
|
globalThis.debugMode = e.data.settings.debug_mode;
|
|
|
|
let converted = await globalThis.convert(
|
|
e.data.file,
|
|
e.data.fileName,
|
|
e.data.fileType,
|
|
e.data.originalFileSize,
|
|
e.data.settings
|
|
);
|
|
postMessage(
|
|
{
|
|
type: "file",
|
|
file: converted.data,
|
|
fileName: e.data.fileName,
|
|
fileId: e.data.fileId,
|
|
outputType: converted.outputType,
|
|
},
|
|
[converted.data]
|
|
);
|
|
} catch (error) {
|
|
console.error(error);
|
|
postMessage({
|
|
type: "error",
|
|
file: e.data.file,
|
|
fileName: e.data.fileName,
|
|
fileId: e.data.fileId,
|
|
});
|
|
}
|
|
break;
|
|
case "convertAnimated":
|
|
try {
|
|
globalThis.debugMode = e.data.settings.debug_mode;
|
|
|
|
let animatedResult = await globalThis.convertAnimated(
|
|
e.data.file,
|
|
e.data.fileName,
|
|
e.data.originalFileSize,
|
|
e.data.settings
|
|
);
|
|
if (animatedResult) {
|
|
postMessage(
|
|
{
|
|
type: "file",
|
|
file: animatedResult.data,
|
|
fileName: e.data.fileName,
|
|
fileId: e.data.fileId,
|
|
outputType: animatedResult.outputType,
|
|
},
|
|
[animatedResult.data]
|
|
);
|
|
} else {
|
|
postMessage({
|
|
type: "skipped",
|
|
fileName: e.data.fileName,
|
|
fileId: e.data.fileId,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
postMessage({
|
|
type: "error",
|
|
file: e.data.file,
|
|
fileName: e.data.fileName,
|
|
fileId: e.data.fileId,
|
|
});
|
|
}
|
|
break;
|
|
case "install":
|
|
try {
|
|
await loadLibs(e.data.settings);
|
|
postMessage({ type: "installed" });
|
|
} catch (error) {
|
|
console.error(error);
|
|
postMessage({ type: "installFailed", errorMessage: error.message });
|
|
}
|
|
break;
|
|
default:
|
|
logIfDebug(`Sorry, we are out of ${e}.`);
|
|
}
|
|
};
|
|
|
|
async function loadLibs(settings) {
|
|
importScripts(settings.mediaOptimizationBundle);
|
|
}
|