summaryrefslogtreecommitdiff
path: root/.github/scripts/bundle-size.mjs
blob: f1c9ceab02b19652835491bde4c185c73572d8d6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { build } from 'esbuild';
import { existsSync } from 'node:fs';

const CLIENT_RUNTIME_PATH = 'packages/astro/src/runtime/client/';

function formatBytes(bytes, decimals = 2) {
    if (bytes === 0) return '0 B';

    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

    const i = Math.floor(Math.log(bytes) / Math.log(k));

    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}

export default async function checkBundleSize({ github, context }) {
	const PR_NUM = context.payload.pull_request.number;
	const SHA = context.payload.pull_request.head.sha;

	const { data: files } = await github.rest.pulls.listFiles({
		...context.repo,
		pull_number: PR_NUM,
	});
	const clientRuntimeFiles = files.filter((file) => {
		return file.filename.startsWith(CLIENT_RUNTIME_PATH) && file.status !== 'removed'
	});
	if (clientRuntimeFiles.length === 0) return;

	const table = [
		'| File | Old Size | New Size | Change |',
		'| ---- | -------- | -------- | ------ |',
	];
	const output = await bundle(clientRuntimeFiles);

	for (let [filename, { oldSize, newSize, sourceFile }] of Object.entries(output)) {
		filename = ['idle', 'load', 'media', 'only', 'visible'].includes(filename) ? `client:${filename}` : filename;
		const prefix = (newSize - oldSize) === 0 ? '' : (newSize - oldSize) > 0 ? '+ ' : '- ';
		const change = `${prefix}${formatBytes(newSize - oldSize)}`;
		table.push(`| [\`${filename}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/tree/${context.payload.pull_request.head.ref}/${sourceFile}) | ${formatBytes(oldSize)} | ${formatBytes(newSize)} | ${change} |`);
	}

	const { data: comments } = await github.rest.issues.listComments({
		...context.repo,
		issue_number: PR_NUM
	})
	const comment = comments.find(comment => comment.user.login === 'github-actions[bot]' && comment.body.includes('Bundle Size Check'));
	const method = comment ? 'updateComment' : 'createComment';
	const payload = comment ? { comment_id: comment.id } : { issue_number: PR_NUM };
	await github.rest.issues[method]({
		...context.repo,
		...payload,
		body: `###  ⚖️  Bundle Size Check

Latest commit: ${SHA}

${table.join('\n')}`,
	});
}

async function bundle(files) {

	const { metafile } = await build({
		entryPoints: [...files.map(({ filename }) => filename), ...files.map(({ filename }) => `main/${filename}`).filter(f => existsSync(f))],
		bundle: true,
		minify: true,
		sourcemap: false,
		target: ['es2018'],
		outdir: 'out',
		external: ['astro:*'],
		metafile: true,
	})

	return Object.entries(metafile.outputs).reduce((acc, [filename, info]) => {
		filename = filename.slice('out/'.length);
		if (filename.startsWith('main/')) {
			filename = filename.slice('main/'.length).replace(CLIENT_RUNTIME_PATH, '').replace('.js', '');
			const oldSize = info.bytes;
			return Object.assign(acc, { [filename]: Object.assign(acc[filename] ?? { oldSize: 0, newSize: 0 }, { oldSize }) });
		}
		filename = filename.replace(CLIENT_RUNTIME_PATH, '').replace('.js', '');
		const newSize = info.bytes;
		return Object.assign(acc, { [filename]: Object.assign(acc[filename] ?? { oldSize: 0, newSize: 0 }, { newSize, sourceFile: Object.keys(info.inputs).find(src => src.endsWith('.ts')) }) });
	}, {});
}