Skip to content

Commit

Permalink
feat: make bundle analysis work with app dir
Browse files Browse the repository at this point in the history
  • Loading branch information
bmstefanski committed Nov 25, 2023
1 parent 38fc128 commit c7165fa
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nextjs_bundle_analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
# Here's the first place where next-bundle-analysis' own script is used
# This step pulls the raw bundle stats for the current bundle
- name: Analyze bundle
run: npx -p nextjs-bundle-analysis report
run: node report-bundle-size.js

- name: Upload bundle
uses: actions/upload-artifact@v3
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-tailwindcss": "^3.13.0",
"fetch-mock": "^9.11.0",
"gzip-size": "6",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"mkdirp": "^3.0.1",
"npm-only-allow": "^1.2.6",
"patch-package": "^8.0.0",
"postcss": "^8.4.31",
Expand Down
133 changes: 133 additions & 0 deletions report-bundle-size.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env node
/* eslint-disable no-console */
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

// edited to work with the appdir by @raphaelbadia

const gzSize = require("gzip-size")
const mkdirp = require("mkdirp")
const fs = require("fs")
const path = require("path")

// Pull options from `package.json`
const options = getOptions()
const BUILD_OUTPUT_DIRECTORY = getBuildOutputDirectory(options)

// first we check to make sure that the build output directory exists
const nextMetaRoot = path.join(process.cwd(), BUILD_OUTPUT_DIRECTORY)
try {
fs.accessSync(nextMetaRoot, fs.constants.R_OK)
} catch (err) {
console.error(
`No build output found at "${nextMetaRoot}" - you may not have your working directory set correctly, or not have run "next build".`
)
process.exit(1)
}

// if so, we can import the build manifest
const buildMeta = require(path.join(nextMetaRoot, "build-manifest.json"))
const appDirMeta = require(path.join(nextMetaRoot, "app-build-manifest.json"))

// this memory cache ensures we dont read any script file more than once
// bundles are often shared between pages
const memoryCache = {}

// since _app is the template that all other pages are rendered into,
// every page must load its scripts. we'll measure its size here
const globalBundle = buildMeta.pages["/_app"]
const globalBundleSizes = getScriptSizes(globalBundle)

// next, we calculate the size of each page's scripts, after
// subtracting out the global scripts
const allPageSizes = Object.values(buildMeta.pages).reduce((acc, scriptPaths, i) => {
const pagePath = Object.keys(buildMeta.pages)[i]
const scriptSizes = getScriptSizes(scriptPaths.filter((scriptPath) => !globalBundle.includes(scriptPath)))

acc[pagePath] = scriptSizes

return acc
}, {})

const globalAppDirBundle = buildMeta.rootMainFiles
const globalAppDirBundleSizes = getScriptSizes(globalAppDirBundle)

const allAppDirSizes = Object.values(appDirMeta.pages).reduce((acc, scriptPaths, i) => {
const pagePath = Object.keys(appDirMeta.pages)[i]
const scriptSizes = getScriptSizes(scriptPaths.filter((scriptPath) => !globalAppDirBundle.includes(scriptPath)))
acc[pagePath] = scriptSizes

return acc
}, {})

// format and write the output
const rawData = JSON.stringify({
...allAppDirSizes,
__global: globalAppDirBundleSizes,
})

// log ouputs to the gh actions panel
console.log(rawData)

mkdirp.sync(path.join(nextMetaRoot, "analyze/"))
fs.writeFileSync(path.join(nextMetaRoot, "analyze/__bundle_analysis.json"), rawData)

// --------------
// Util Functions
// --------------

// given an array of scripts, return the total of their combined file sizes
function getScriptSizes(scriptPaths) {
const res = scriptPaths.reduce(
(acc, scriptPath) => {
const [rawSize, gzipSize] = getScriptSize(scriptPath)
acc.raw += rawSize
acc.gzip += gzipSize

return acc
},
{ raw: 0, gzip: 0 }
)

return res
}

// given an individual path to a script, return its file size
function getScriptSize(scriptPath) {
const encoding = "utf8"
const p = path.join(nextMetaRoot, scriptPath)

let rawSize, gzipSize
if (Object.keys(memoryCache).includes(p)) {
rawSize = memoryCache[p][0]
gzipSize = memoryCache[p][1]
} else {
const textContent = fs.readFileSync(p, encoding)
rawSize = Buffer.byteLength(textContent, encoding)
gzipSize = gzSize.sync(textContent)
memoryCache[p] = [rawSize, gzipSize]
}

return [rawSize, gzipSize]
}

/**
* Reads options from `package.json`
*/
function getOptions(pathPrefix = process.cwd()) {
const pkg = require(path.join(pathPrefix, "package.json"))

return { ...pkg.nextBundleAnalysis, name: pkg.name }
}

/**
* Gets the output build directory, defaults to `.next`
*
* @param {object} options the options parsed from package.json.nextBundleAnalysis using `getOptions`
* @returns {string}
*/
function getBuildOutputDirectory(options) {
return options.buildOutputDirectory || ".next"
}
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9419,7 +9419,7 @@ gunzip-maybe@^1.4.2:
pumpify "^1.3.3"
through2 "^2.0.3"

gzip-size@^6.0.0:
gzip-size@6, gzip-size@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==
Expand Down Expand Up @@ -11813,6 +11813,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

mkdirp@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==

module-details-from-path@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b"
Expand Down

0 comments on commit c7165fa

Please sign in to comment.