Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic bundle size calculations #47

Merged
merged 3 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
81 changes: 68 additions & 13 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127755,6 +127755,11 @@ function getStaticBundleSizes(workingDir) {
const manifest = loadBuildManifest(workingDir);
return getPageSizesFromManifest(manifest, workingDir);
}
function getDynamicBundleSizes(workingDir) {
const staticManifest = loadBuildManifest(workingDir);
const manifest = loadReactLoadableManifest(staticManifest.pages['/_app'], workingDir);
return getPageSizesFromManifest(manifest, workingDir);
}
function getPageSizesFromManifest(manifest, workingDir) {
return Object.entries(manifest.pages).map(([page, files]) => {
const size = files
Expand All @@ -127772,6 +127777,28 @@ function loadBuildManifest(workingDir) {
const file = external_fs_default().readFileSync(external_path_default().join(process.cwd(), workingDir, '.next', 'build-manifest.json'), 'utf-8');
return JSON.parse(file);
}
function loadReactLoadableManifest(appChunks, workingDir) {
const file = external_fs_default().readFileSync(external_path_default().join(process.cwd(), workingDir, '.next', 'react-loadable-manifest.json'), 'utf-8');
const content = JSON.parse(file);
const pages = {};
Object.keys(content).forEach((item) => {
if (item.includes('/node_modules/')) {
return;
}
const fileList = getFiles(content[item]);
const uniqueFileList = Array.from(new Set(fileList));
pages[item] = uniqueFileList.filter((file) => !appChunks.find((chunkFile) => file === chunkFile));
});
return {
pages,
};
}
function getFiles(chunks) {
if (chunks.files) {
return chunks.files;
}
return chunks.map(({ file }) => file);
}
function getSingleColumnMarkdownTable({ bundleSizes, name, }) {
const rows = getPageChangeInfo([], bundleSizes);
return formatTableNoDiff(name, rows);
Expand Down Expand Up @@ -127851,7 +127878,7 @@ function getSign(bytes) {
;// CONCATENATED MODULE: ./src/text-format.ts
function formatTextFragments(...text) {
return text
.map((fragment) => fragment.trim())
.map((fragment) => fragment === null || fragment === void 0 ? void 0 : fragment.trim())
.filter(Boolean)
.join('\n\n');
}
Expand All @@ -127864,19 +127891,22 @@ async function findCommentByTextMatch({ octokit, issueNumber, text, }) {
const { data: comments } = await octokit.rest.issues.listComments(Object.assign(Object.assign({}, github.context.repo), { issue_number: issueNumber }));
return comments.find((comment) => { var _a; return (_a = comment.body) === null || _a === void 0 ? void 0 : _a.includes(text); });
}
async function createOrReplaceComment({ octokit, issueNumber, title, shaInfo, routesTable, strategy, }) {
async function createOrReplaceComment({ octokit, issueNumber, title, shaInfo, routesTable, dynamicTable, strategy, }) {
const existingComment = await findCommentByTextMatch({
octokit,
issueNumber,
text: title,
});
const body = formatTextFragments(title, shaInfo, routesTable !== null && routesTable !== void 0 ? routesTable : FALLBACK_COMPARISON_TEXT);
const body = formatTextFragments(title, shaInfo, routesTable, dynamicTable, !(routesTable === null || routesTable === void 0 ? void 0 : routesTable.trim()) && !(dynamicTable === null || dynamicTable === void 0 ? void 0 : dynamicTable.trim()) ? FALLBACK_COMPARISON_TEXT : null);
if (existingComment) {
console.log(`Updating comment ${existingComment.id}`);
const response = await octokit.rest.issues.updateComment(Object.assign(Object.assign({}, github.context.repo), { comment_id: existingComment.id, body }));
console.log(`Done with status ${response.status}`);
}
else if (!existingComment && !routesTable && strategy === 'skip-insignificant') {
else if (!existingComment &&
!routesTable &&
!dynamicTable &&
strategy === 'skip-insignificant') {
console.log(`Skipping comment [${title}]: no significant changes`);
}
else {
Expand Down Expand Up @@ -127991,20 +128021,23 @@ function getInputs() {

;// CONCATENATED MODULE: ./src/issue.ts


async function findIssueByTitleMatch({ octokit, title }) {
const { data: issues } = await octokit.rest.issues.listForRepo(github.context.repo);
return issues.find((issue) => issue.title === title);
}
async function createOrReplaceIssue({ octokit, title, routesTable, }) {
async function createOrReplaceIssue({ octokit, title, routesTable, dynamicTable, }) {
const existingIssue = await findIssueByTitleMatch({ octokit, title });
const body = formatTextFragments(routesTable, dynamicTable);
if (existingIssue) {
console.log(`Updating issue ${existingIssue.number} with latest bundle sizes`);
const response = await octokit.rest.issues.update(Object.assign(Object.assign({}, github.context.repo), { body: routesTable, issue_number: existingIssue.number }));
const response = await octokit.rest.issues.update(Object.assign(Object.assign({}, github.context.repo), { body, issue_number: existingIssue.number }));
console.log(`Issue update response status ${response.status}`);
}
else {
console.log(`Creating issue "${title}" to show latest bundle sizes`);
const response = await octokit.rest.issues.create(Object.assign(Object.assign({}, github.context.repo), { body: routesTable, title }));
const response = await octokit.rest.issues.create(Object.assign(Object.assign({}, github.context.repo), { body,
title }));
console.log(`Issue creation response status ${response.status}`);
}
}
Expand All @@ -128017,13 +128050,16 @@ var tmp = __nccwpck_require__(38766);



async function uploadJsonAsArtifact(artifactName, fileName, data) {
async function uploadJsonAsArtifact(artifactName, artifactFiles) {
const artifactClient = new artifact.DefaultArtifactClient();
const dir = tmp/* dirSync */.op();
const file = tmp/* fileSync */.yd({ name: fileName, dir: dir.name });
external_fs_.writeFileSync(file.name, JSON.stringify(data, null, 2));
console.log(`Uploading ${file.name}`);
const response = await artifactClient.uploadArtifact(artifactName, [file.name], dir.name);
const filenames = artifactFiles.map(({ fileName, data }) => {
const file = tmp/* fileSync */.yd({ name: fileName, dir: dir.name });
external_fs_.writeFileSync(file.name, JSON.stringify(data, null, 2));
return file.name;
});
console.log(`Uploading ${filenames.join(', ')}`);
const response = await artifactClient.uploadArtifact(artifactName, filenames, dir.name);
console.log('Artifact uploaded', response);
}

Expand All @@ -128039,6 +128075,7 @@ async function uploadJsonAsArtifact(artifactName, fileName, data) {

const ARTIFACT_NAME_PREFIX = 'next-bundle-analyzer__';
const FILE_NAME = 'bundle-sizes.json';
const DYNAMIC_FILE_NAME = 'dynamic-bundle-sizes.json';
async function run() {
var _a;
try {
Expand All @@ -128051,11 +128088,18 @@ async function run() {
console.log(`> Downloading bundle sizes from ${default_branch}`);
const referenceBundleSizes = (await downloadArtifactAsJson(octokit, default_branch, artifactName, FILE_NAME)) || { sha: 'none', data: [] };
console.log(referenceBundleSizes);
const referenceDynamicBundleSizes = (await downloadArtifactAsJson(octokit, default_branch, artifactName, DYNAMIC_FILE_NAME)) || { sha: 'none', data: [] };
console.log(referenceDynamicBundleSizes);
console.log('> Calculating local bundle sizes');
const bundleSizes = getStaticBundleSizes(inputs.workingDirectory);
console.log(bundleSizes);
const dynamicBundleSizes = getDynamicBundleSizes(inputs.workingDirectory);
console.log(dynamicBundleSizes);
console.log('> Uploading local bundle sizes');
await uploadJsonAsArtifact(artifactName, FILE_NAME, bundleSizes);
await uploadJsonAsArtifact(artifactName, [
{ fileName: FILE_NAME, data: bundleSizes },
{ fileName: DYNAMIC_FILE_NAME, data: dynamicBundleSizes },
]);
if (issueNumber) {
const title = `### Bundle sizes [${appName}]`;
const shaInfo = `Compared against ${referenceBundleSizes.sha}`;
Expand All @@ -128064,23 +128108,34 @@ async function run() {
actualBundleSizes: bundleSizes,
name: 'Route',
});
const dynamicTable = getComparisonMarkdownTable({
referenceBundleSizes: referenceDynamicBundleSizes.data,
actualBundleSizes: dynamicBundleSizes,
name: 'Dynamic import',
});
createOrReplaceComment({
octokit,
issueNumber,
title,
shaInfo,
routesTable,
dynamicTable,
strategy: inputs.commentStrategy,
});
}
else if (github.context.ref === `refs/heads/${default_branch}` && inputs.createIssue) {
console.log('> Creating/updating bundle size issue');
const title = `Bundle sizes [${appName}]`;
const routesTable = getSingleColumnMarkdownTable({ bundleSizes, name: 'Route' });
const dynamicTable = getSingleColumnMarkdownTable({
bundleSizes: dynamicBundleSizes,
name: 'Dynamic import',
});
createOrReplaceIssue({
octokit,
title,
routesTable,
dynamicTable,
});
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/bundle-size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ type BuildManifest = {
pages: Record<string, string[]>;
};

type ReactLoadableManifest = Record<string, Next10Chunks | Next12Chunks>;
type Next10Chunks = { id: string; file: string }[];
type Next12Chunks = { id: string; files: string[] };

export type PageBundleSizes = { page: string; size: number }[];

export function getStaticBundleSizes(workingDir: string): PageBundleSizes {
Expand All @@ -14,6 +18,13 @@ export function getStaticBundleSizes(workingDir: string): PageBundleSizes {
return getPageSizesFromManifest(manifest, workingDir);
}

export function getDynamicBundleSizes(workingDir: string): PageBundleSizes {
const staticManifest = loadBuildManifest(workingDir);
const manifest = loadReactLoadableManifest(staticManifest.pages['/_app'], workingDir);

return getPageSizesFromManifest(manifest, workingDir);
}

function getPageSizesFromManifest(manifest: BuildManifest, workingDir: string): PageBundleSizes {
return Object.entries(manifest.pages).map(([page, files]) => {
const size = files
Expand All @@ -37,6 +48,35 @@ function loadBuildManifest(workingDir: string): BuildManifest {
return JSON.parse(file);
}

function loadReactLoadableManifest(appChunks: string[], workingDir: string): BuildManifest {
const file = fs.readFileSync(
path.join(process.cwd(), workingDir, '.next', 'react-loadable-manifest.json'),
'utf-8',
);
const content = JSON.parse(file) as ReactLoadableManifest;
const pages: BuildManifest['pages'] = {};
Object.keys(content).forEach((item) => {
if (item.includes('/node_modules/')) {
return;
}
const fileList = getFiles(content[item]);
const uniqueFileList = Array.from(new Set(fileList));
pages[item] = uniqueFileList.filter(
(file) => !appChunks.find((chunkFile) => file === chunkFile),
);
});
return {
pages,
};
}

function getFiles(chunks: Next10Chunks | Next12Chunks): string[] {
if ((chunks as Next12Chunks).files) {
return (chunks as Next12Chunks).files;
}
return (chunks as Next10Chunks).map(({ file }) => file);
}

export function getSingleColumnMarkdownTable({
bundleSizes,
name,
Expand Down
17 changes: 15 additions & 2 deletions src/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export async function createOrReplaceComment({
title,
shaInfo,
routesTable,
dynamicTable,
strategy,
}: {
octokit: Octokit;
issueNumber: number;
title: string;
shaInfo: string;
routesTable: string | null;
dynamicTable: string | null;
canac marked this conversation as resolved.
Show resolved Hide resolved
strategy: ActionInputs['commentStrategy'];
}): Promise<void> {
const existingComment = await findCommentByTextMatch({
Expand All @@ -43,7 +45,13 @@ export async function createOrReplaceComment({
text: title,
});

const body = formatTextFragments(title, shaInfo, routesTable ?? FALLBACK_COMPARISON_TEXT);
const body = formatTextFragments(
title,
shaInfo,
routesTable,
dynamicTable,
!routesTable?.trim() && !dynamicTable?.trim() ? FALLBACK_COMPARISON_TEXT : null,
);

if (existingComment) {
console.log(`Updating comment ${existingComment.id}`);
Expand All @@ -53,7 +61,12 @@ export async function createOrReplaceComment({
body,
});
console.log(`Done with status ${response.status}`);
} else if (!existingComment && !routesTable && strategy === 'skip-insignificant') {
} else if (
!existingComment &&
!routesTable &&
!dynamicTable &&
strategy === 'skip-insignificant'
) {
console.log(`Skipping comment [${title}]: no significant changes`);
} else {
console.log(`Creating comment on PR ${issueNumber}`);
Expand Down
27 changes: 26 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as core from '@actions/core';
import { context, getOctokit } from '@actions/github';
import {
getComparisonMarkdownTable,
getDynamicBundleSizes,
getSingleColumnMarkdownTable,
getStaticBundleSizes,
} from './bundle-size';
Expand All @@ -15,6 +16,7 @@ import { uploadJsonAsArtifact } from './upload-artifacts';

const ARTIFACT_NAME_PREFIX = 'next-bundle-analyzer__';
const FILE_NAME = 'bundle-sizes.json';
const DYNAMIC_FILE_NAME = 'dynamic-bundle-sizes.json';

async function run() {
try {
Expand All @@ -38,13 +40,25 @@ async function run() {
FILE_NAME,
)) || { sha: 'none', data: [] };
console.log(referenceBundleSizes);
const referenceDynamicBundleSizes = (await downloadArtifactAsJson(
octokit,
default_branch,
artifactName,
DYNAMIC_FILE_NAME,
)) || { sha: 'none', data: [] };
console.log(referenceDynamicBundleSizes);

console.log('> Calculating local bundle sizes');
const bundleSizes = getStaticBundleSizes(inputs.workingDirectory);
console.log(bundleSizes);
const dynamicBundleSizes = getDynamicBundleSizes(inputs.workingDirectory);
console.log(dynamicBundleSizes);

console.log('> Uploading local bundle sizes');
await uploadJsonAsArtifact(artifactName, FILE_NAME, bundleSizes);
await uploadJsonAsArtifact(artifactName, [
{ fileName: FILE_NAME, data: bundleSizes },
{ fileName: DYNAMIC_FILE_NAME, data: dynamicBundleSizes },
]);

if (issueNumber) {
const title = `### Bundle sizes [${appName}]`;
Expand All @@ -54,22 +68,33 @@ async function run() {
actualBundleSizes: bundleSizes,
name: 'Route',
});
const dynamicTable = getComparisonMarkdownTable({
referenceBundleSizes: referenceDynamicBundleSizes.data,
actualBundleSizes: dynamicBundleSizes,
name: 'Dynamic import',
});
createOrReplaceComment({
octokit,
issueNumber,
title,
shaInfo,
routesTable,
dynamicTable,
strategy: inputs.commentStrategy,
});
} else if (context.ref === `refs/heads/${default_branch}` && inputs.createIssue) {
console.log('> Creating/updating bundle size issue');
const title = `Bundle sizes [${appName}]`;
const routesTable = getSingleColumnMarkdownTable({ bundleSizes, name: 'Route' });
const dynamicTable = getSingleColumnMarkdownTable({
bundleSizes: dynamicBundleSizes,
name: 'Dynamic import',
});
createOrReplaceIssue({
octokit,
title,
routesTable,
dynamicTable,
});
}
} catch (e) {
Expand Down
Loading
Loading