mirror of
https://github.com/apache/superset.git
synced 2026-08-05 13:32:41 +00:00
Superset's frontend bundle size is a recurring complaint, but until now
there's been no ongoing visibility into it -- only periodic manual cleanup
efforts. This adds a `bundle-size` job to superset-frontend.yml that builds
the real production bundle (npm run build), reduces its per-entrypoint
sizes to a few headline numbers, and tracks them over time via
benchmark-action/github-action-benchmark, posting a PR comment when a
change regresses past 110% of the last recorded baseline.
Reuses the already-built CI Docker image (same image sharded-jest-tests /
lint-frontend / etc. already download), so the only new cost is the
production webpack build itself -- and that's cheap after the first run:
webpack's persistent filesystem cache (already configured in
webpack.config.js) makes warm rebuilds ~20s locally vs several minutes
cold, confirmed by hand against this repo's real build.
webpack.config.js gains a BUNDLE_SIZE_STATS env-gated stats override
(mirrors the existing BUNDLE_ANALYZER pattern). The default `stats:
'minimal'` omits per-asset sizes entirely; `--stats=normal` includes them
but also serializes the full ~15k-module dependency graph, producing a
560+MB stats.json for this app -- large enough to exceed Node's max
string length on a plain fs.readFileSync. The env-gated override requests
just `{ assets: true, entrypoints: true }`, verified end-to-end against a
minimal synthetic webpack project (same webpack/webpack-cli versions) and
against real stats pulled from this repo's actual production build.
History storage deliberately avoids the gh-pages branch (benchmark-action's
usual default) since that branch is the live Helm chart index published by
superset-helm-release.yml, not free real estate. Instead uses
external-data-json-path with actions/cache: restored on every run (PR or
push) so PRs get a same-baseline comparison and regression comment, but
only saved back to the cache on push to master, so an unmerged PR's numbers
never become the shared baseline. No gh-pages branch is touched in any
code path, and no PAT/GitHub App is needed -- comment-on-alert only needs
the default per-job GITHUB_TOKEN.
79 lines
2.7 KiB
JavaScript
79 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing
|
|
* permissions and limitations under the License.
|
|
*/
|
|
|
|
// Reduces a webpack `--json` stats file down to the handful of headline
|
|
// numbers worth tracking over time, in the flat array format
|
|
// benchmark-action/github-action-benchmark expects for its
|
|
// "customSmallerIsBetter" tool. The full stats file also includes a
|
|
// `modules`/`chunks` graph across ~15k modules, which is enormous and not
|
|
// useful for this purpose, so we only ever read `entrypoints`.
|
|
//
|
|
// Usage: node scripts/bundle-size-summary.js <path-to-stats.json>
|
|
|
|
const fs = require('fs');
|
|
|
|
// Entrypoints worth tracking: the two user-facing app shells. `menu`,
|
|
// `preamble`, `theme`, and `service-worker` are small, low-variance
|
|
// infrastructure chunks, not where bundle bloat actually shows up.
|
|
const TRACKED_ENTRYPOINTS = ['spa', 'embedded'];
|
|
|
|
function entrypointSizeByExt(entrypoint, ext) {
|
|
return (entrypoint.assets || [])
|
|
.filter(asset => asset.name.endsWith(ext))
|
|
.reduce((total, asset) => total + asset.size, 0);
|
|
}
|
|
|
|
function main() {
|
|
const statsPath = process.argv[2];
|
|
if (!statsPath) {
|
|
console.error('Usage: bundle-size-summary.js <path-to-stats.json>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
|
|
const { entrypoints } = stats;
|
|
if (!entrypoints) {
|
|
console.error(
|
|
'stats.json has no `entrypoints` key -- was it generated with ' +
|
|
'`--stats=normal` (or richer)? `minimal`/`errors-only` stats omit it.',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const results = [];
|
|
TRACKED_ENTRYPOINTS.forEach(name => {
|
|
const entrypoint = entrypoints[name];
|
|
if (!entrypoint) return;
|
|
results.push({
|
|
name: `${name} entrypoint (JS)`,
|
|
unit: 'bytes',
|
|
value: entrypointSizeByExt(entrypoint, '.js'),
|
|
});
|
|
results.push({
|
|
name: `${name} entrypoint (CSS)`,
|
|
unit: 'bytes',
|
|
value: entrypointSizeByExt(entrypoint, '.css'),
|
|
});
|
|
});
|
|
|
|
console.log(JSON.stringify(results, null, 2));
|
|
}
|
|
|
|
main();
|