next-enterprise/report-bundle-size.js

134 lines
3.9 KiB
JavaScript
Raw Normal View History

#!/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"
}