report-size.yml 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. name: Report size
  2. on:
  3. workflow_run:
  4. workflows: ["Read size"]
  5. types:
  6. - completed
  7. # This workflow needs to be run with "pull-requests: write" permissions to
  8. # be able to comment on the pull request. We can't checkout the PR code
  9. # in this workflow.
  10. # Reference:
  11. # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
  12. permissions:
  13. pull-requests: write
  14. jobs:
  15. report-size:
  16. name: Comment on PR
  17. runs-on: ubuntu-latest
  18. if: github.event.workflow_run.event == 'pull_request' &&
  19. github.event.workflow_run.conclusion == 'success'
  20. steps:
  21. - name: Log GitHub context
  22. env:
  23. GITHUB_CONTEXT: ${{ toJson(github) }}
  24. run: echo "$GITHUB_CONTEXT"
  25. # Using actions/download-artifact doesn't work here
  26. # https://github.com/actions/download-artifact/issues/60
  27. - name: Download artifact
  28. uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
  29. id: download-artifact
  30. with:
  31. result-encoding: string
  32. script: |
  33. const fs = require('fs/promises');
  34. const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
  35. owner: context.repo.owner,
  36. repo: context.repo.repo,
  37. run_id: context.payload.workflow_run.id,
  38. });
  39. const matchArtifact = artifacts.data.artifacts.find((artifact) => artifact.name === 'sizes');
  40. const download = await github.rest.actions.downloadArtifact({
  41. owner: context.repo.owner,
  42. repo: context.repo.repo,
  43. artifact_id: matchArtifact.id,
  44. archive_format: 'zip',
  45. });
  46. await fs.writeFile('sizes.zip', Buffer.from(download.data));
  47. await exec.exec('unzip sizes.zip');
  48. const json = await fs.readFile('sizes.json', 'utf8');
  49. return json;
  50. # This runs on the base branch of the PR, meaning "dev"
  51. - name: Git checkout
  52. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
  53. - name: Install Node
  54. uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
  55. with:
  56. node-version: 18
  57. cache: 'npm'
  58. - name: Install dependencies
  59. run: npm ci
  60. - name: Build
  61. run: npm run build
  62. - name: === Test tree-shaking ===
  63. run: npm run test-treeshake
  64. - name: Read sizes
  65. id: read-size
  66. run: |
  67. FILESIZE_BASE=$(stat --format=%s build/three.module.min.js)
  68. gzip -k build/three.module.min.js
  69. FILESIZE_BASE_GZIP=$(stat --format=%s build/three.module.min.js.gz)
  70. TREESHAKEN_BASE=$(stat --format=%s test/treeshake/index.bundle.min.js)
  71. gzip -k test/treeshake/index.bundle.min.js
  72. TREESHAKEN_BASE_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz)
  73. # log to console
  74. echo "FILESIZE_BASE=$FILESIZE_BASE"
  75. echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP"
  76. echo "TREESHAKEN_BASE=$TREESHAKEN_BASE"
  77. echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP"
  78. echo "FILESIZE_BASE=$FILESIZE_BASE" >> $GITHUB_OUTPUT
  79. echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP" >> $GITHUB_OUTPUT
  80. echo "TREESHAKEN_BASE=$TREESHAKEN_BASE" >> $GITHUB_OUTPUT
  81. echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP" >> $GITHUB_OUTPUT
  82. - name: Format sizes
  83. id: format
  84. # It's important these are passed as env variables.
  85. # https://securitylab.github.com/research/github-actions-untrusted-input/
  86. env:
  87. FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).filesize }}
  88. FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).gzip }}
  89. FILESIZE_BASE: ${{ steps.read-size.outputs.FILESIZE_BASE }}
  90. FILESIZE_BASE_GZIP: ${{ steps.read-size.outputs.FILESIZE_BASE_GZIP }}
  91. TREESHAKEN: ${{ fromJSON(steps.download-artifact.outputs.result).treeshaken }}
  92. TREESHAKEN_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).treeshakenGzip }}
  93. TREESHAKEN_BASE: ${{ steps.read-size.outputs.TREESHAKEN_BASE }}
  94. TREESHAKEN_BASE_GZIP: ${{ steps.read-size.outputs.TREESHAKEN_BASE_GZIP }}
  95. run: |
  96. FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE")
  97. FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_GZIP")
  98. FILESIZE_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_BASE")
  99. FILESIZE_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_BASE_GZIP")
  100. FILESIZE_DIFF=$(node ./test/treeshake/utils/format-diff.js "$FILESIZE" "$FILESIZE_BASE")
  101. TREESHAKEN_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN")
  102. TREESHAKEN_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_GZIP")
  103. TREESHAKEN_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_BASE")
  104. TREESHAKEN_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_BASE_GZIP")
  105. TREESHAKEN_DIFF=$(node ./test/treeshake/utils/format-diff.js "$TREESHAKEN" "$TREESHAKEN_BASE")
  106. echo "FILESIZE=$FILESIZE_FORM" >> $GITHUB_OUTPUT
  107. echo "FILESIZE_GZIP=$FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT
  108. echo "FILESIZE_BASE=$FILESIZE_BASE_FORM" >> $GITHUB_OUTPUT
  109. echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP_FORM" >> $GITHUB_OUTPUT
  110. echo "FILESIZE_DIFF=$FILESIZE_DIFF" >> $GITHUB_OUTPUT
  111. echo "TREESHAKEN=$TREESHAKEN_FORM" >> $GITHUB_OUTPUT
  112. echo "TREESHAKEN_GZIP=$TREESHAKEN_GZIP_FORM" >> $GITHUB_OUTPUT
  113. echo "TREESHAKEN_BASE=$TREESHAKEN_BASE_FORM" >> $GITHUB_OUTPUT
  114. echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP_FORM" >> $GITHUB_OUTPUT
  115. echo "TREESHAKEN_DIFF=$TREESHAKEN_DIFF" >> $GITHUB_OUTPUT
  116. - name: Find existing comment
  117. uses: peter-evans/find-comment@a54c31d7fa095754bfef525c0c8e5e5674c4b4b1 # v2
  118. id: find-comment
  119. with:
  120. issue-number: ${{ fromJSON(steps.download-artifact.outputs.result).pr }}
  121. comment-author: 'github-actions[bot]'
  122. body-includes: Bundle size
  123. - name: Comment on PR
  124. uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3
  125. with:
  126. issue-number: ${{ fromJSON(steps.download-artifact.outputs.result).pr }}
  127. comment-id: ${{ steps.find-comment.outputs.comment-id }}
  128. edit-mode: replace
  129. body: |
  130. ### 📦 Bundle size
  131. _Full ESM build, minified and gzipped._
  132. | Filesize `${{ github.ref_name }}` | Filesize PR | Diff |
  133. |----------|---------|------|
  134. | ${{ steps.format.outputs.FILESIZE_BASE }} (${{ steps.format.outputs.FILESIZE_BASE_GZIP }}) | ${{ steps.format.outputs.FILESIZE }} (${{ steps.format.outputs.FILESIZE_GZIP }}) | ${{ steps.format.outputs.FILESIZE_DIFF }} |
  135. ### 🌳 Bundle size after tree-shaking
  136. _Minimal build including a renderer, camera, empty scene, and dependencies._
  137. | Filesize `${{ github.ref_name }}` | Filesize PR | Diff |
  138. |----------|---------|------|
  139. | ${{ steps.format.outputs.TREESHAKEN_BASE }} (${{ steps.format.outputs.TREESHAKEN_BASE_GZIP }}) | ${{ steps.format.outputs.TREESHAKEN }} (${{ steps.format.outputs.TREESHAKEN_GZIP }}) | ${{ steps.format.outputs.TREESHAKEN_DIFF }} |