astro-minify-html-swc
Version:
An Astro integration that minifies HTML
92 lines (74 loc) • 2.73 kB
text/typescript
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { formatBytes } from '@ocavue/utils'
import type * as SWC from '@swc/html'
import type { AstroIntegration, AstroIntegrationLogger } from 'astro'
import glob from 'fast-glob'
type SizeChange = [sizeBefore: number, sizeAfter: number]
function formatSizeChange([sizeBefore, sizeAfter]: SizeChange): string {
const diff = sizeAfter - sizeBefore
const percent = (100 * diff) / sizeBefore
return `${formatBytes(diff)} (${percent.toFixed(1)}%)`
}
async function compress(
logger: AstroIntegrationLogger,
minify: typeof SWC.minify,
dirPath: string,
filePath: string,
isDebug: boolean,
): Promise<SizeChange> {
try {
const fullPath = path.join(dirPath, filePath)
const textBefore = await fs.readFile(fullPath, 'utf-8')
const sizeBefore = Buffer.byteLength(textBefore, 'utf-8')
const result = await minify(textBefore, {
collapseWhitespaces: 'conservative',
removeComments: true,
minifyJson: true,
minifyJs: true,
minifyCss: true,
})
const textAfter = result.code
const sizeAfter = Buffer.byteLength(textAfter, 'utf-8')
await fs.writeFile(fullPath, textAfter, 'utf-8')
if (isDebug) {
logger.debug(`${formatSizeChange([sizeBefore, sizeAfter])} ${filePath}`)
}
return [sizeBefore, sizeAfter]
} catch (error) {
logger.error(`Failed to minify ${filePath}:`)
logger.error(String(error))
return [0, 0]
}
}
export default function integration(): AstroIntegration {
return {
name: 'astro-minify-html-swc',
hooks: {
'astro:build:done': async ({ logger, dir }) => {
const timeStart = performance.now()
const dirPath = fileURLToPath(dir)
logger.debug(`Scanning ${dirPath}`)
const filePaths = await glob('**/*.html', { cwd: dirPath })
logger.debug(`Found ${filePaths.length} HTML files in ${dirPath}`)
const { minify } = await import('@swc/html')
const isDebug = logger.options.level === 'debug'
const sizeChanges = await Promise.all(
filePaths.map((filePath) => {
return compress(logger, minify, dirPath, filePath, isDebug)
}),
)
const totalSizeChange: SizeChange = [0, 0]
for (const sizeChange of sizeChanges) {
totalSizeChange[0] += sizeChange[0]
totalSizeChange[1] += sizeChange[1]
}
const timeEnd = performance.now()
logger.info(
`${formatSizeChange(totalSizeChange)} Compressed ${filePaths.length} HTML files in ${Math.round(timeEnd - timeStart)}ms`,
)
},
},
} satisfies AstroIntegration
}