Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3296426
add: aggressive cleanup.
ItzNotABug Nov 12, 2025
6001954
Merge branch 'tests' into 'aggressive-improvements'.
ItzNotABug Nov 12, 2025
7bb74d0
fixes and lint.
ItzNotABug Nov 12, 2025
29e89ea
address comment.
ItzNotABug Nov 13, 2025
a81fad6
Merge branch 'main' into aggressive-improvements
ItzNotABug Nov 13, 2025
0c569bf
remove: more unneeded files.
ItzNotABug Nov 16, 2025
6e8a2f9
use: chrome-headless and bun debian!
ItzNotABug Nov 16, 2025
e4e61bd
Clean up unused modules from node_modules
ItzNotABug Nov 16, 2025
50808d8
update: improve the speed!
ItzNotABug Nov 16, 2025
28e8118
use: specific image version!
ItzNotABug Nov 16, 2025
e7ba28c
Update base image version in Dockerfile
ItzNotABug Nov 16, 2025
4e6b20d
add: proper, nicer router support.
ItzNotABug Nov 16, 2025
8e055b6
Merge branch 'aggressive-improvements' into improve-screenshots-perf
ItzNotABug Nov 16, 2025
f999d75
update: address commment from rabbit.
ItzNotABug Nov 16, 2025
ff2bc5b
Merge branch 'aggressive-improvements' into improve-screenshots-perf
ItzNotABug Nov 16, 2025
f8f05c8
add: test stats to post on PR.
ItzNotABug Nov 16, 2025
7cee01d
fix: ci.
ItzNotABug Nov 16, 2025
8575c8d
fix: ci.
ItzNotABug Nov 16, 2025
4204a3c
update: ci.
ItzNotABug Nov 16, 2025
231a696
update: ci for comment spam.
ItzNotABug Nov 16, 2025
e51309c
Merge pull request #13 from appwrite/add-stats-on-pr
ItzNotABug Nov 16, 2025
0580dac
fix: startup time.
ItzNotABug Nov 16, 2025
68decdf
Apply suggestion from @ItzNotABug
ItzNotABug Nov 16, 2025
b614f34
Merge branch 'aggressive-improvements' into improve-screenshots-perf
ItzNotABug Nov 16, 2025
77cd303
lint.
ItzNotABug Nov 16, 2025
b913791
Merge pull request #12 from appwrite/improve-screenshots-perf
eldadfux Nov 20, 2025
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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ LICENSE
test-screenshots/
comparison/
.lighthouse/
lighthouse/

# Build output
dist/
Expand Down
48 changes: 27 additions & 21 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,41 +1,47 @@
FROM oven/bun:1.3.2-alpine AS base
# debian so we can re-use!
FROM oven/bun:1.3.2-debian AS base

WORKDIR /app

COPY package.json bun.lock ./
COPY src/utils/clean-modules.ts ./src/utils/clean-modules.ts

RUN bun install --frozen-lockfile --production && \
bun run ./src/utils/clean-modules.ts && \
rm -rf ~/.bun/install/cache /tmp/*

FROM oven/bun:1.3.2-alpine AS final
# well-known OSS docker image
FROM chromedp/headless-shell:143.0.7445.3 AS final

RUN apk upgrade --no-cache --available && \
apk add --no-cache \
chromium \
ttf-freefont \
font-noto-emoji \
tini && \
apk add --no-cache font-wqy-zenhei --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community && \
# remove unnecessary chromium files to save space
rm -rf /usr/lib/chromium/chrome_200_percent.pak \
/usr/lib/chromium/chrome_100_percent.pak \
/usr/lib/chromium/xdg-mime \
/usr/lib/chromium/xdg-settings \
/usr/lib/chromium/chrome-sandbox
# install fonts only
RUN apt-get update && \
apt-get install -y --no-install-recommends \
tini \
ca-certificates \
fonts-liberation \
fonts-noto-color-emoji \
fonts-wqy-zenhei && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/cache/apt/archives/*

RUN addgroup -S chrome && adduser -S -G chrome chrome
# copy bun from debian base above!
COPY --from=base /usr/local/bin/bun /usr/local/bin/bun

# Add chrome user
RUN groupadd -r chrome && useradd -r -g chrome chrome

ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser \
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/headless-shell/headless-shell \
NODE_ENV=production

WORKDIR /app

COPY package.json ./
COPY --from=base /app/node_modules ./node_modules
COPY src/ ./src/
COPY --chown=chrome:chrome src/ ./src/
COPY --chown=chrome:chrome package.json ./
COPY --chown=chrome:chrome --from=base /app/node_modules ./node_modules

RUN chown -R chrome:chrome /app
# for e2e tests and `reports` endpoint!
RUN install -d -o chrome -g chrome lighthouse

USER chrome

Expand Down
1 change: 1 addition & 0 deletions bun.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "appwrite-browser",
Expand Down
207 changes: 207 additions & 0 deletions src/utils/clean-modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { readdirSync, unlinkSync } from "node:fs";
import { join } from "node:path";
import { $ } from "bun";

const NODE_MODULES = "/app/node_modules";

async function getDirSize(path: string): Promise<number> {
const result = await $`du -sm ${path}`.quiet();
return Number.parseInt(result.text().split("\t")[0]);
}

async function deleteFiles(
pattern: string,
description: string,
): Promise<void> {
console.log(`${description}...`);
await $`find ${NODE_MODULES} -name ${pattern} -delete 2>/dev/null || true`.quiet();
}

async function deleteDirectories(
dirName: string,
description: string,
): Promise<void> {
console.log(`${description}...`);
await $`find ${NODE_MODULES} -depth -type d -name ${dirName} -exec rm -rf {} + 2>/dev/null || true`.quiet();
}

async function deletePath(path: string): Promise<void> {
try {
await $`test -e ${path}`.quiet();
await $`rm -rf ${path}`.quiet();
} catch {
// ignore
}
}

async function removeDocumentationFiles(): Promise<void> {
console.log("📝 Removing documentation files...");
await deleteFiles("*.md", " - Markdown files");
await deleteFiles("*.d.ts", " - TypeScript declarations");
await deleteFiles("*.map", " - Source maps");
await deleteFiles("LICENSE*", " - LICENSE files");
await deleteFiles("README*", " - README files");
await deleteFiles("CHANGELOG*", " - CHANGELOG files");
await deleteFiles("AUTHORS*", " - AUTHORS files");
await deleteFiles("CONTRIBUTORS*", " - CONTRIBUTORS files");
await deleteFiles("NOTICE*", " - NOTICE files");
await deleteFiles("HISTORY*", " - HISTORY files");
await deleteFiles("*.txt", " - Text files");
}

async function removeTypeScriptSources(): Promise<void> {
console.log("🔧 Removing TypeScript sources...");
await deleteFiles("*.ts", " - TypeScript files");
await deleteFiles("*.jsx", " - JavaScript JSX files");
await deleteFiles("*.tsx", " - TypeScript JSX files");
await deleteFiles("tsconfig*.json", " - TypeScript configs");
await deleteFiles("*.tsbuildinfo", " - TypeScript build info");
}

async function removeTestFiles(): Promise<void> {
console.log("🧪 Removing test files...");
await deleteFiles("*.test.js", " - JavaScript tests");
await deleteFiles("*.test.ts", " - TypeScript tests");
await deleteFiles("*.spec.js", " - JavaScript specs");
await deleteFiles("*.spec.ts", " - TypeScript specs");
await deleteDirectories("test", " - test/ directories");
await deleteDirectories("tests", " - tests/ directories");
await deleteDirectories("__tests__", " - __tests__/ directories");
await deleteDirectories("__mocks__", " - __mocks__/ directories");
await deleteDirectories("__fixtures__", " - __fixtures__/ directories");
await deleteDirectories("fixtures", " - fixtures/ directories");
await deleteDirectories("coverage", " - coverage/ directories");
}

async function removeDevelopmentDirectories(): Promise<void> {
console.log("🗂️ Removing development directories...");
await deleteDirectories(".github", " - .github/ directories");
await deleteDirectories("docs", " - docs/ directories");
await deleteDirectories("examples", " - examples/ directories");
await deleteDirectories("benchmark", " - benchmark/ directories");
await deleteDirectories("samples", " - samples/ directories");
}

async function removeDevelopmentFiles(): Promise<void> {
console.log("⚙️ Removing development config files...");
await deleteFiles(".eslintrc*", " - ESLint configs");
await deleteFiles(".prettierrc*", " - Prettier configs");
await deleteFiles(".editorconfig", " - EditorConfig files");
await deleteFiles("jest.config.*", " - Jest configs");
await deleteFiles("vitest.config.*", " - Vitest configs");
await deleteFiles(".npmignore", " - NPM ignore files");
await deleteFiles(".gitignore", " - Git ignore files");
await deleteFiles("bun.lock", " - Bun lock files");
await deleteFiles("yarn.lock", " - Yarn lock files");
await deleteFiles("package-lock.json", " - NPM lock files");
await deleteFiles("pnpm-lock.yaml", " - PNPM lock files");
}

async function removeScriptsAndDeclarations(): Promise<void> {
console.log("📜 Removing scripts and declarations...");
await deleteFiles("*.sh", " - Shell scripts");
await deleteFiles("*.ps1", " - PowerShell scripts");
await deleteFiles("*.d.cts", " - CommonJS TypeScript declarations");
await deleteFiles("*.d.mts", " - ES Module TypeScript declarations");
}

async function removeTraceEngineLocales(): Promise<void> {
console.log("🌐 Removing non-English trace_engine locales...");
const traceEngineLocalesPath = `${NODE_MODULES}/@paulirish/trace_engine/locales`;
try {
const traceLocales = readdirSync(traceEngineLocalesPath);
for (const locale of traceLocales) {
if (locale !== "en-US.json") {
unlinkSync(join(traceEngineLocalesPath, locale));
}
}
console.log(
` - Removed ${traceLocales.length - 1} trace_engine locale files`,
);
} catch {
console.log(" - trace_engine locales not found (skipped)");
}
}

async function removeLighthouseLocales(): Promise<void> {
console.log("🌍 Replacing non-English Lighthouse locales with stubs...");
const localesPath = `${NODE_MODULES}/lighthouse/shared/localization/locales`;
try {
const locales = readdirSync(localesPath);
const stubContent = "{}";
for (const locale of locales) {
if (locale !== "en-US.json") {
const filePath = join(localesPath, locale);
unlinkSync(filePath);
await Bun.write(filePath, stubContent);
}
}
} catch {
console.log(" - Lighthouse locales not found (skipped)");
}
}

async function removeAxeCoreLocales(): Promise<void> {
console.log("🌍 Replacing non-English axe-core locales with stubs...");
const localesPath = `${NODE_MODULES}/axe-core/locales`;
try {
const locales = readdirSync(localesPath);
const stubContent = "{}";
for (const locale of locales) {
if (locale !== "en.json" && !locale.startsWith("_")) {
const filePath = join(localesPath, locale);
unlinkSync(filePath);
await Bun.write(filePath, stubContent);
}
}
} catch {
console.log(" - axe-core locales not found (skipped)");
}
}

async function removeUnnecessaryFiles(): Promise<void> {
console.log("🎭 Removing unnecessary files...");
await deletePath(`${NODE_MODULES}/playwright-core/lib/vite`);
await deletePath(`${NODE_MODULES}/puppeteer-core/src`);
await deletePath(`${NODE_MODULES}/zod/src`);
await deletePath(`${NODE_MODULES}/third-party-web/dist/domain-map.csv`);
await deletePath(
`${NODE_MODULES}/puppeteer-core/node_modules/devtools-protocol`,
);
await deletePath(`${NODE_MODULES}/@sentry`);
await deletePath(`${NODE_MODULES}/@opentelemetry`);
await deletePath(`${NODE_MODULES}/axe-core/axe.js`);
await deletePath(`${NODE_MODULES}/lighthouse/cli`);
await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`);
await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`);
await deletePath(`${NODE_MODULES}/lighthouse/eslint.config.mjs`);
}

async function cleanModules(): Promise<void> {
console.log("🧹 Starting node_modules cleanup...");
const startSize = await getDirSize(NODE_MODULES);

await Promise.all([
removeDocumentationFiles(),
removeTypeScriptSources(),
removeTestFiles(),
removeDevelopmentDirectories(),
removeDevelopmentFiles(),
removeScriptsAndDeclarations(),
removeTraceEngineLocales(),
removeLighthouseLocales(),
removeAxeCoreLocales(),
removeUnnecessaryFiles(),
]);

const endSize = await getDirSize(NODE_MODULES);
const saved = startSize - endSize;

console.log("\n✅ Cleanup complete!");
console.log(`📊 ${startSize}MB → ${endSize}MB (Saved: ${saved}MB)`);
}

cleanModules().catch((error) => {
console.error("❌ Cleanup failed:", error);
process.exit(1);
});