Replace the vite compression plugin with our own

This has a few advantages:

 - it runs at the generateBundle stage
 - it reads files only once, doing all the compression in parallel
 - it supports zstd compression
 - it uses emitFile, which works in watch mode, instead of writing files
   to the disk directly
This commit is contained in:
Quentin Gliech
2025-12-18 13:46:44 +01:00
parent d523715473
commit 8a6fe631b3
3 changed files with 57 additions and 76 deletions

View File

@@ -67,7 +67,6 @@
"tinyglobby": "^0.2.15",
"typescript": "^5.9.3",
"vite": "7.3.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-graphql-codegen": "^3.7.0",
"vite-plugin-manifest-sri": "^0.2.0",
"vitest": "^4.0.15"
@@ -8088,21 +8087,6 @@
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -8275,13 +8259,6 @@
"csstype": "^3.0.10"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/graphql": {
"version": "16.12.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
@@ -9886,19 +9863,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/knip": {
"version": "5.74.0",
"resolved": "https://registry.npmjs.org/knip/-/knip-5.74.0.tgz",
@@ -13062,16 +13026,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/unixify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz",
@@ -13344,21 +13298,6 @@
}
}
},
"node_modules/vite-plugin-compression": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz",
"integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"debug": "^4.3.3",
"fs-extra": "^10.0.0"
},
"peerDependencies": {
"vite": ">=2.0.0"
}
},
"node_modules/vite-plugin-graphql-codegen": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/vite-plugin-graphql-codegen/-/vite-plugin-graphql-codegen-3.7.0.tgz",

View File

@@ -77,7 +77,6 @@
"tinyglobby": "^0.2.15",
"typescript": "^5.9.3",
"vite": "7.3.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-graphql-codegen": "^3.7.0",
"vite-plugin-manifest-sri": "^0.2.0",
"vitest": "^4.0.15"

View File

@@ -6,12 +6,13 @@
import { readFile, writeFile } from "node:fs/promises";
import { resolve } from "node:path";
import { promisify } from "node:util";
import zlib from "node:zlib";
import { tanstackRouter } from "@tanstack/router-plugin/vite";
import react from "@vitejs/plugin-react";
import browserslistToEsbuild from "browserslist-to-esbuild";
import { globSync } from "tinyglobby";
import type { Manifest, PluginOption } from "vite";
import compression from "vite-plugin-compression";
import codegen from "vite-plugin-graphql-codegen";
import manifestSRI from "vite-plugin-manifest-sri";
import { defineConfig } from "vitest/config";
@@ -31,6 +32,60 @@ function i18nHotReload(): PluginOption {
};
}
// Pre-compress the assets, so that the server can serve them directly
function compression(): PluginOption {
const gzip = promisify(zlib.gzip);
const brotliCompress = promisify(zlib.brotliCompress);
return {
name: "asset-compression",
apply: "build",
enforce: "post",
async generateBundle(_outputOptions, bundle) {
const promises = Object.entries(bundle).flatMap(
([fileName, assetOrChunk]) => {
const source =
assetOrChunk.type === "asset"
? assetOrChunk.source
: assetOrChunk.code;
// Don't compress empty files, only compress CSS, JS and JSON files
if (
!source ||
!(
fileName.endsWith(".js") ||
fileName.endsWith(".css") ||
fileName.endsWith(".json")
)
) {
return [];
}
const uncompressed = Buffer.from(source);
// We pre-compress assets with brotli as it offers the best
// compression ratios compared to even zstd, and gzip as a fallback
return [
{ compress: gzip, ext: "gz" },
{ compress: brotliCompress, ext: "br" },
].map(async ({ compress, ext }) => {
const compressed = await compress(uncompressed);
this.emitFile({
type: "asset",
fileName: `${fileName}.${ext}`,
source: compressed,
});
});
},
);
await Promise.all(promises);
},
};
}
export default defineConfig((env) => ({
base: "./",
@@ -117,19 +172,7 @@ export default defineConfig((env) => ({
manifestSRI(),
// Pre-compress the assets, so that the server can serve them directly
compression({
algorithm: "gzip",
ext: ".gz",
}),
compression({
algorithm: "brotliCompress",
ext: ".br",
}),
compression({
algorithm: "deflate",
ext: ".zz",
}),
compression(),
i18nHotReload(),
],