Skip to content

Commit

Permalink
CI: Report tree-shaking size in PR (#25615)
Browse files Browse the repository at this point in the history
* Add report-size.yml action

* Format

* Report normal bundle size as well

* Wording

* Remove pretty-bytes dependency

* Add clarification about tree-shaking in message
  • Loading branch information
marcofugaro authored Mar 6, 2023
1 parent c8b99b2 commit 3987bf5
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 0 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/read-size.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Read size

on:
pull_request:
paths:
- 'src/**'
- 'package.json'

# This workflow runs in a read-only environment. We can safely checkout
# the PR code here.
# Reference:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
permissions:
contents: read

jobs:
read-size:
name: Tree-shaking
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build-module
- name: === Test tree-shaking ===
run: npm run test-treeshake
- name: Read bundle sizes
id: read-size
run: |
FILESIZE=$(stat --format=%s test/treeshake/three.module.min.js)
gzip -k test/treeshake/three.module.min.js
FILESIZE_GZIP=$(stat --format=%s test/treeshake/three.module.min.js.gz)
TREESHAKEN=$(stat --format=%s test/treeshake/index.bundle.min.js)
gzip -k test/treeshake/index.bundle.min.js
TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz)
# write the output in a json file to upload it as artifact
node -pe "JSON.stringify({ filesize: $FILESIZE, gzip: $FILESIZE_GZIP, treeshaken: $TREESHAKEN, treeshakenGzip: $TREESHAKEN_GZIP })" > sizes.json
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: sizes
path: sizes.json
125 changes: 125 additions & 0 deletions .github/workflows/report-size.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
name: Report size

on:
workflow_run:
workflows: ["Read size"]
types:
- completed

# This workflow needs to be run with "pull-requests: write" permissions to
# be able to comment on the pull request. We can't checkout the PR code
# in this workflow.
# Reference:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
permissions:
pull-requests: write

jobs:
report-size:
name: Comment on PR
runs-on: ubuntu-latest
if: github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
# Using actions/download-artifact doesn't work here
# https://github.com/actions/download-artifact/issues/60
- name: Download artifact
uses: actions/github-script@v6
id: download-artifact
with:
result-encoding: string
script: |
const fs = require('fs/promises');
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
const matchArtifact = artifacts.data.artifacts.find((artifact) => artifact.name === 'sizes');
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
await fs.writeFile('sizes.zip', Buffer.from(download.data));
await exec.exec('unzip sizes.zip');
const json = await fs.readFile('sizes.json', 'utf8');
return json;
# This runs on the base branch of the PR, meaning "dev"
- name: Git checkout
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build-module
- name: === Test tree-shaking ===
run: npm run test-treeshake
- name: Read sizes
id: read-size
run: |
FILESIZE_BASE=$(stat --format=%s test/treeshake/three.module.min.js)
TREESHAKEN_BASE=$(stat --format=%s test/treeshake/index.bundle.min.js)
echo "FILESIZE_BASE=$FILESIZE_BASE" >> $GITHUB_OUTPUT
echo "TREESHAKEN_BASE=$TREESHAKEN_BASE" >> $GITHUB_OUTPUT
- name: Format sizes
id: format
# It's important these are passed as env variables.
# https://securitylab.github.com/research/github-actions-untrusted-input/
env:
FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).filesize }}
FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).gzip }}
FILESIZE_BASE: ${{ steps.read-size.outputs.FILESIZE_BASE }}
TREESHAKEN: ${{ fromJSON(steps.download-artifact.outputs.result).treeshaken }}
TREESHAKEN_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).treeshakenGzip }}
TREESHAKEN_BASE: ${{ steps.read-size.outputs.TREESHAKEN_BASE }}
run: |
FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE")
FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_GZIP")
FILESIZE_DIFF=$(node ./test/treeshake/utils/format-diff.js "$FILESIZE" "$FILESIZE_BASE")
TREESHAKEN_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN")
TREESHAKEN_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_GZIP")
TREESHAKEN_DIFF=$(node ./test/treeshake/utils/format-diff.js "$TREESHAKEN" "$TREESHAKEN_BASE")
echo "FILESIZE=$FILESIZE_FORM" >> $GITHUB_OUTPUT
echo "FILESIZE_GZIP=$FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT
echo "FILESIZE_DIFF=$FILESIZE_DIFF" >> $GITHUB_OUTPUT
echo "TREESHAKEN=$TREESHAKEN_FORM" >> $GITHUB_OUTPUT
echo "TREESHAKEN_GZIP=$TREESHAKEN_GZIP_FORM" >> $GITHUB_OUTPUT
echo "TREESHAKEN_DIFF=$TREESHAKEN_DIFF" >> $GITHUB_OUTPUT
- name: Find existing comment
uses: peter-evans/find-comment@v2
id: find-comment
with:
issue-number: ${{ github.event.workflow_run.pull_requests[0].number }}
comment-author: 'github-actions[bot]'
body-includes: Bundle size
- name: Comment on PR
uses: peter-evans/create-or-update-comment@v2
with:
issue-number: ${{ github.event.workflow_run.pull_requests[0].number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
body: |
### 📦 Bundle size
| Filesize | Gzipped | Diff from `${{ github.ref_name }}` |
|----------|---------|------|
| ${{ steps.format.outputs.FILESIZE }} | ${{ steps.format.outputs.FILESIZE_GZIP }} | ${{ steps.format.outputs.FILESIZE_DIFF }} |
### 🌳 Bundle size after tree-shaking
_Includes a renderer, camera, and empty scene._
| Filesize | Gzipped | Diff from `${{ github.ref_name }}` |
|----------|---------|------|
| ${{ steps.format.outputs.TREESHAKEN }} | ${{ steps.format.outputs.TREESHAKEN_GZIP }} | ${{ steps.format.outputs.TREESHAKEN_DIFF }} |
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ test/unit/build
test/treeshake/index.bundle.js
test/treeshake/index.bundle.min.js
test/treeshake/index-src.bundle.min.js
test/treeshake/three.module.min.js
test/treeshake/stats.html
test/e2e/chromium
test/e2e/output-screenshots
Expand Down
13 changes: 13 additions & 0 deletions test/rollup.treeshake.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,17 @@ export default [
}
]
},
// esm bundle size minified, used in read-size.yml
{
input: 'build/three.module.js',
plugins: [
terser(),
],
output: [
{
format: 'esm',
file: 'test/treeshake/three.module.min.js'
}
]
},
];
10 changes: 10 additions & 0 deletions test/treeshake/utils/format-diff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// used in report-size.yml

const filesize = Number( process.argv[ 2 ] );
const filesizeBase = Number( process.argv[ 3 ] );

const diff = ( filesize - filesizeBase ) * 100 / filesizeBase;
const diffString = diff.toFixed( 2 ).slice( - 1 ) === '0' ? diff.toFixed( 1 ) : diff.toFixed( 2 );
const formatted = `${diff >= 0 ? '+' : ''}${diffString}%`;

console.log( formatted );
20 changes: 20 additions & 0 deletions test/treeshake/utils/format-size.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// used in report-size.yml

export function formatBytes( bytes, decimals = 1 ) {

if ( bytes === 0 ) return '0 B';

const k = 1000;
const dm = decimals < 0 ? 0 : decimals;
const sizes = [ 'B', 'kB', 'MB', 'GB' ];

const i = Math.floor( Math.log( bytes ) / Math.log( k ) );

return parseFloat( ( bytes / Math.pow( k, i ) ).toFixed( dm ) ) + ' ' + sizes[ i ];

}

const n = Number( process.argv[ 2 ] );
const formatted = formatBytes( n );

console.log( formatted );

0 comments on commit 3987bf5

Please sign in to comment.