Skip to content

Commit

Permalink
enhance(build): show bundle size diff on PRs (#11757)
Browse files Browse the repository at this point in the history
also updates the `yarn analyze` command

partially revert 4a5cf81:
although "deterministic" isn't when changing loaders, it *is* when changing import order
- which is more important. we'll probably avoid the prior problem with changing loader
config changing chunk names (but not their contents) if/when we move to named chunks
  • Loading branch information
LeoMcA authored Sep 18, 2024
1 parent f21b111 commit 51a6be3
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 93 deletions.
100 changes: 100 additions & 0 deletions .github/workflows/pr-bundlesize-compare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Compare bundle size

on:
pull_request:

jobs:
# Build current and upload stats.json
build-head:
name: "Build head"
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}

- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: yarn

- name: Cache @vscode/ripgrep bin
uses: actions/cache@v4
with:
key: vscode-ripgrep-bin-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('yarn.lock') }}
path: node_modules/@vscode/ripgrep/bin/

- name: Install all yarn packages
run: yarn --frozen-lockfile
env:
# https://github.com/microsoft/vscode-ripgrep#github-api-limit-note
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Build
run: yarn build:client
env:
ANALYZE_BUNDLE_PR: true

- name: Upload stats.json
uses: actions/upload-artifact@v4
with:
name: head-stats
path: ./client/build/stats.json

# Build base for comparison and upload stats.json
build-base:
name: "Build base"
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.base_ref }}

- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: yarn

- name: Cache @vscode/ripgrep bin
uses: actions/cache@v4
with:
key: vscode-ripgrep-bin-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('yarn.lock') }}
path: node_modules/@vscode/ripgrep/bin/

- name: Install all yarn packages
run: yarn --frozen-lockfile
env:
# https://github.com/microsoft/vscode-ripgrep#github-api-limit-note
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Build
run: yarn build:client
env:
ANALYZE_BUNDLE_PR: true

- name: Upload stats.json
uses: actions/upload-artifact@v4
with:
name: base-stats
path: ./client/build/stats.json

# run the action against the stats.json files
compare:
name: "Compare base & head bundle sizes"
runs-on: ubuntu-latest
needs: [build-base, build-head]
permissions:
pull-requests: write
steps:
- uses: actions/download-artifact@v4
- uses: github/webpack-bundlesize-compare-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
current-stats-json-path: ./head-stats/stats.json
base-stats-json-path: ./base-stats/stats.json
48 changes: 35 additions & 13 deletions client/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CssMinimizerPlugin from "css-minimizer-webpack-plugin";
import { WebpackManifestPlugin } from "webpack-manifest-plugin";
import ESLintPlugin from "eslint-webpack-plugin";
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";

import paths from "./paths.js";
import getClientEnvironment from "./env.js";
Expand All @@ -21,6 +22,10 @@ import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";

// to compare file sizes in PRs we need them to not include hashes
const analyzeBundlePR = process.env.ANALYZE_BUNDLE_PR;
const analyzeBundle = analyzeBundlePR || process.env.ANALYZE_BUNDLE;

// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
function config(webpackEnv) {
Expand Down Expand Up @@ -118,15 +123,20 @@ function config(webpackEnv) {
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? "static/js/[name].[contenthash:8].js"
: isEnvDevelopment && "static/js/bundle.js",
filename: analyzeBundlePR
? "static/js/[name].js"
: isEnvProduction
? "static/js/[name].[contenthash:8].js"
: isEnvDevelopment && "static/js/bundle.js",
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? "static/js/[name].[contenthash:8].chunk.js"
: isEnvDevelopment && "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[name].[hash][ext]",
chunkFilename: analyzeBundlePR
? "static/js/[name].chunk.js"
: isEnvProduction
? "static/js/[name].[contenthash:8].chunk.js"
: isEnvDevelopment && "static/js/[name].chunk.js",
assetModuleFilename: analyzeBundlePR
? "static/media/[file]"
: "static/media/[name].[hash][ext]",
publicPath: "/",
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
Expand All @@ -152,8 +162,8 @@ function config(webpackEnv) {
level: "none",
},
optimization: {
chunkIds: isEnvProduction ? "natural" : "named",
moduleIds: isEnvProduction ? "natural" : "named",
chunkIds: isEnvProduction ? "deterministic" : "named",
moduleIds: isEnvProduction ? "deterministic" : "named",
minimize: isEnvProduction,
minimizer: [
// This is only used in production mode
Expand Down Expand Up @@ -251,7 +261,9 @@ function config(webpackEnv) {
{
loader: resolve.sync("file-loader"),
options: {
name: "static/media/[name].[hash].[ext]",
name: analyzeBundlePR
? "static/media/[path][name].[ext]"
: "static/media/[name].[hash].[ext]",
},
},
],
Expand Down Expand Up @@ -413,8 +425,12 @@ function config(webpackEnv) {
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "static/css/[name].[contenthash:8].css",
chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
filename: analyzeBundlePR
? "static/css/[name].css"
: "static/css/[name].[contenthash:8].css",
chunkFilename: analyzeBundlePR
? "static/css/[name].chunk.css"
: "static/css/[name].[contenthash:8].chunk.css",
}),
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
Expand Down Expand Up @@ -482,6 +498,12 @@ function config(webpackEnv) {
new ESLintPlugin({
extensions: ["js", "mjs", "jsx", "ts", "tsx"],
}),
isEnvProduction &&
analyzeBundle &&
new BundleAnalyzerPlugin({
analyzerMode: "disabled",
generateStatsFile: true,
}),
].filter(Boolean),
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
},
"scripts": {
"ai-help-macros": "cross-env NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node scripts/ai-help-macros.ts",
"analyze": "source-map-explorer 'client/build/static/js/*.js'",
"analyze:css": "source-map-explorer 'client/build/static/css/*.css'",
"analyze": "(test -f client/build/stats.json || cross-env ANALYZE_BUNDLE=true yarn build:client) && webpack-bundle-analyzer client/build/stats.json",
"build": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node build/cli.ts",
"build:blog": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node build/build-blog.ts",
"build:client": "cd client && cross-env NODE_ENV=production BABEL_ENV=production node scripts/build.js",
Expand Down Expand Up @@ -172,6 +171,7 @@
"@types/react": "^18.3.7",
"@types/react-dom": "^18.3.0",
"@types/react-modal": "^3.16.3",
"@types/webpack-bundle-analyzer": "^4.7.0",
"babel-jest": "^29.7.0",
"babel-loader": "^9.2.1",
"babel-plugin-named-asset-import": "^0.3.8",
Expand Down Expand Up @@ -241,7 +241,6 @@
"rough-notation": "^0.5.1",
"sass": "^1.78.0",
"sass-loader": "^16.0.1",
"source-map-explorer": "^2.5.3",
"source-map-loader": "^5.0.0",
"style-loader": "^3.3.4",
"stylelint": "^15.11.0",
Expand All @@ -261,6 +260,7 @@
"typescript": "^5.6.2",
"typescript-eslint": "^8.6.0",
"webpack": "^5.94.0",
"webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0",
"webpack-manifest-plugin": "^5.0.0",
Expand Down
Loading

0 comments on commit 51a6be3

Please sign in to comment.