optimize/index.ts
2026-10-23 09:12:49 -04:00

114 lines
2.9 KiB
TypeScript

import type { Integration, IntegrationContext } from "muddle/contracts";
import * as terser from "terser";
import * as svgo from "svgo";
import * as csso from "csso";
import * as htmlMinifier from "html-minifier-terser";
export function minifyCss(source: string): string {
return csso.minify(source).css;
}
export async function minifyHtml(source: string): Promise<string> {
return await htmlMinifier.minify(source, {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
removeOptionalTags: true,
});
}
export async function minifyJavaScript(source: string): Promise<string> {
return (
(await terser.minify(source, { format: { comments: false } })).code ??
source
);
}
export function optimizeSvg(source: string): string {
return svgo.optimize(source, { multipass: true }).data;
}
export interface ImageOptimizer {
optimize(source: Uint8Array, type: string): Promise<Uint8Array> | Uint8Array;
}
export function imageOptimization(optimizer: ImageOptimizer): ImageOptimizer {
return optimizer;
}
function loaderIntegration(
name: string,
extension: string,
transform: (source: string) => string | Promise<string>,
): Integration {
return {
name,
async transform(asset) {
if (!asset.path.endsWith(extension)) return asset;
const source =
typeof asset.content === "string"
? asset.content
: new TextDecoder().decode(asset.content);
return { ...asset, content: await transform(source) };
},
setup(context: IntegrationContext) {
context.loader(name, {
name,
extensions: [extension],
async load(file) {
return { data: {}, content: await transform(file.source) };
},
});
},
};
}
export function terserIntegration(): Integration {
return loaderIntegration("terser", ".js", minifyJavaScript);
}
export function cssMinifyIntegration(): Integration {
return loaderIntegration("css-minify", ".css", minifyCss);
}
export function htmlMinifyIntegration(): Integration {
return loaderIntegration("html-minify", ".html", minifyHtml);
}
export function svgoIntegration(): Integration {
return loaderIntegration("svgo", ".svg", optimizeSvg);
}
export function imageOptimizationIntegration(
optimizer: ImageOptimizer,
): Integration {
return {
name: "image-optimization",
async transform(asset) {
if (!/\.(png|jpe?g|webp|avif)$/i.test(asset.path)) return asset;
const input =
typeof asset.content === "string"
? new TextEncoder().encode(asset.content)
: asset.content;
return {
...asset,
content: await optimizer.optimize(input, asset.contentType),
};
},
setup(context: IntegrationContext) {
context.loader("image", {
name: "image",
extensions: [".png", ".jpg", ".jpeg", ".webp", ".avif"],
async load(file) {
const optimized = await optimizer.optimize(
new TextEncoder().encode(file.source),
"image/*",
);
return { data: { optimized }, content: file.source };
},
});
},
};
}