-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
report-bundle-size.js
133 lines (107 loc) · 3.91 KB
/
report-bundle-size.js
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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"
}