diff --git a/.github/workflows/frontend-bundle-size-nightly.yml b/.github/workflows/frontend-bundle-size-nightly.yml new file mode 100644 index 00000000000..c4e6bdbc9b1 --- /dev/null +++ b/.github/workflows/frontend-bundle-size-nightly.yml @@ -0,0 +1,100 @@ +name: Frontend bundle size (nightly baseline) + +# Refreshes the bundle-size baseline that superset-frontend.yml's `bundle-size` +# job compares PRs against. Deliberately NOT triggered on every push to +# master: a day-old baseline is fine for catching relative regressions on +# PRs, and building the production bundle on every one of the many pushes +# master gets per day would burn CI time for no benefit a nightly refresh +# doesn't already cover. +on: + schedule: + - cron: "0 6 * * *" + workflow_dispatch: {} + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +env: + TAG: apache/superset:bundle-size-nightly-${{ github.run_id }} + +permissions: + contents: read + +jobs: + refresh-baseline: + runs-on: ubuntu-26.04 + timeout-minutes: 30 + steps: + - name: "Checkout master" + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: master + + - name: Build Docker Image + run: | + docker buildx build \ + -t $TAG \ + --cache-from=type=registry,ref=apache/superset-cache:3.11-slim-trixie \ + --target superset-node-ci \ + . + + # Same cache the PR-time bundle-size job restores/writes -- webpack's + # persistent filesystem cache turns a warm production build into ~20s + # instead of several minutes. See superset-frontend.yml for the + # matching restore step and why it's keyed this way. + - name: Restore webpack build cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: superset-frontend/.temp_cache + key: >- + webpack-prod-cache-${{ hashFiles('superset-frontend/package-lock.json', + 'superset-frontend/babel.config.js', 'superset-frontend/tsconfig.json', + 'superset-frontend/webpack.config.js') }} + + # Only ever pull the last recorded data point off the cache, keyed by + # run ID -- `restore-keys` prefix-matches the most recently created + # entry. Absent on the very first run ever; benchmark-action starts a + # fresh history in that case. + - name: Restore bundle size history + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: bundle-size-history.json + key: bundle-size-history-${{ github.run_id }} + restore-keys: | + bundle-size-history- + + - name: Build production bundle with stats + run: | + mkdir -p ${{ github.workspace }}/superset-frontend/bundle-stats + mkdir -p ${{ github.workspace }}/superset-frontend/.temp_cache + docker run \ + -v ${{ github.workspace }}/superset-frontend/bundle-stats:/app/superset-frontend/bundle-stats \ + -v ${{ github.workspace }}/superset-frontend/.temp_cache:/app/superset-frontend/.temp_cache \ + --rm $TAG \ + bash -c \ + "npm i && BUNDLE_SIZE_STATS=true npm run build -- --json=bundle-stats/stats.json" + + - name: Summarize bundle size + run: | + node superset-frontend/scripts/bundle-size-summary.js \ + superset-frontend/bundle-stats/stats.json > bundle-size-summary.json + rm -rf superset-frontend/bundle-stats + + # No PR to comment on here, so comment-on-alert is off -- the job + # summary (summary-always) is the only surface for this run. + - name: Update bundle size baseline + uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1 + with: + tool: customSmallerIsBetter + output-file-path: bundle-size-summary.json + external-data-json-path: bundle-size-history.json + fail-on-alert: false + summary-always: true + + - name: Save bundle size history + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: bundle-size-history.json + key: bundle-size-history-${{ github.run_id }} diff --git a/.github/workflows/superset-frontend.yml b/.github/workflows/superset-frontend.yml index e828cc27276..6b138b6a110 100644 --- a/.github/workflows/superset-frontend.yml +++ b/.github/workflows/superset-frontend.yml @@ -196,9 +196,15 @@ jobs: docker run --rm $TAG bash -c \ "npm run build-storybook && npx playwright install-deps && npx playwright install chromium && npm run test-storybook:ci" + # Compares a PR's own bundle size against the last nightly-recorded + # baseline (see frontend-bundle-size-nightly.yml, which owns actually + # persisting new baselines). PR-only: a push to master doesn't need this + # check re-run against itself, and re-persisting the baseline on every + # push to master -- which happens many times a day -- would burn a full + # production build for no benefit nightly refresh doesn't already cover. bundle-size: needs: frontend-build - if: needs.frontend-build.outputs.should-run == 'true' + if: needs.frontend-build.outputs.should-run == 'true' && github.event_name == 'pull_request' runs-on: ubuntu-26.04 timeout-minutes: 15 permissions: @@ -219,10 +225,26 @@ jobs: run: | zstd -d < docker-image.tar.zst | docker load + # webpack's persistent filesystem cache (superset-frontend/webpack.config.js) + # turns a warm production build into ~20s instead of several minutes, + # but GH-hosted runners are fresh VMs with nothing carried over between + # jobs -- without restoring it explicitly, every single PR would pay + # the full cold-build cost. Keyed on the same files webpack's own + # `buildDependencies` invalidates on, so a stale cache is never used. + - name: Restore webpack build cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: superset-frontend/.temp_cache + key: >- + webpack-prod-cache-${{ hashFiles('superset-frontend/package-lock.json', + 'superset-frontend/babel.config.js', 'superset-frontend/tsconfig.json', + 'superset-frontend/webpack.config.js') }} + # Only ever pull the last recorded data point off the cache, keyed by # run ID -- `restore-keys` prefix-matches the most recently created - # entry. Absent on the very first run ever; benchmark-action starts a - # fresh history in that case. + # entry, which is always the latest nightly run. Absent before the + # first nightly run ever happens; benchmark-action starts a fresh + # history in that case. - name: Restore bundle size history uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -234,8 +256,10 @@ jobs: - name: Build production bundle with stats run: | mkdir -p ${{ github.workspace }}/superset-frontend/bundle-stats + mkdir -p ${{ github.workspace }}/superset-frontend/.temp_cache docker run \ -v ${{ github.workspace }}/superset-frontend/bundle-stats:/app/superset-frontend/bundle-stats \ + -v ${{ github.workspace }}/superset-frontend/.temp_cache:/app/superset-frontend/.temp_cache \ --rm $TAG \ bash -c \ "npm i && BUNDLE_SIZE_STATS=true npm run build -- --json=bundle-stats/stats.json" @@ -246,7 +270,9 @@ jobs: superset-frontend/bundle-stats/stats.json > bundle-size-summary.json rm -rf superset-frontend/bundle-stats - - name: Track bundle size + # Comparison + alert only -- this job never persists. See + # frontend-bundle-size-nightly.yml for why. + - name: Compare bundle size against nightly baseline uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1 with: tool: customSmallerIsBetter @@ -257,13 +283,3 @@ jobs: alert-threshold: "110%" fail-on-alert: false summary-always: true - - # Only the trunk's own bundle size becomes the baseline everyone else - # is compared against -- an unmerged PR's numbers (including ones that - # regress on purpose to test something) never get persisted. - - name: Save bundle size history - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: bundle-size-history.json - key: bundle-size-history-${{ github.run_id }}