Skip to content

ci: report size change + always check single file build#320

Merged
zardoy merged 5 commits intonextfrom
ci-report-size-pr
Mar 29, 2025
Merged

ci: report size change + always check single file build#320
zardoy merged 5 commits intonextfrom
ci-report-size-pr

Conversation

@zardoy
Copy link
Owner

@zardoy zardoy commented Mar 29, 2025

PR Type

Enhancement, Tests


Description

  • Added a new GitHub workflow for bundle size comparison and storage.

  • Enhanced single file build validation in rsbuild.config.ts.

  • Updated CI workflow to include bundle size parsing and comparison.

  • Automated PR description updates with bundle size stats.


Changes walkthrough 📝

Relevant files
Enhancement
rsbuild.config.ts
Add validation for single file build output                           

rsbuild.config.ts

  • Added validation for single file build output.
  • Ensured only index.html exists in dist/single.
  • Improved error handling for single file build issues.
  • +6/-0     
    ci.yml
    Enhance CI workflow with bundle size comparison                   

    .github/workflows/ci.yml

  • Added steps to parse and compare bundle stats.
  • Integrated bundle stats workflow into CI.
  • Automated PR description updates with bundle size details.
  • Added zip packaging for size comparison.
  • +59/-0   
    Tests
    bundle-stats.yml
    Introduce bundle stats workflow for size comparison           

    .github/workflows/bundle-stats.yml

  • Created a new workflow for bundle size comparison.
  • Added functionality to store and compare bundle stats.
  • Integrated GitHub Gist for storing bundle stats.
  • +57/-0   

    Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Summary by CodeRabbit

    • New Features
      • Introduced an automated workflow to track and compare bundle size statistics.
      • Updated the continuous integration pipeline to package outputs, calculate sizes, and update pull request details with bundle metrics.
      • Added a build validation step to ensure consistent output for single file builds.

    @codesandbox
    Copy link

    codesandbox bot commented Mar 29, 2025

    Review or Edit in CodeSandbox

    Open the branch in Web EditorVS CodeInsiders

    Open Preview

    @coderabbitai
    Copy link

    coderabbitai bot commented Mar 29, 2025

    Caution

    Review failed

    The pull request is closed.

    Walkthrough

    The pull request introduces a new GitHub Actions workflow for managing bundle statistics and extends the CI pipeline with additional steps. The "Bundle Stats" workflow, triggered via workflow_call, supports two modes—storing and comparing bundle stats using asynchronous functions to interact with a Gist. In parallel, the CI workflow now packages build artifacts, parses size data, and updates the pull request description with bundle information. Additionally, a validation step in the build configuration ensures that single file builds output only an index.html file.

    Changes

    File(s) Change Summary
    .github/workflows/(bundle-stats.yml & ci.yml) Added a new "Bundle Stats" workflow for storing and comparing bundle statistics using inputs (mode and branch). Extended the CI process with steps to package artifacts, parse bundle sizes, invoke the workflow, and update PR descriptions.
    rsbuild.config.ts Introduced a post-build validation that checks the dist/single directory to ensure it contains exactly one file named index.html, throwing an error if this condition is not met.

    Sequence Diagram(s)

    sequenceDiagram
      participant CI as CI Pipeline
      participant BS as Bundle Stats Workflow
      participant FS as File System (/tmp/bundle-stats.json)
      participant GS as Gist Service
    
      CI->>BS: Invoke workflow_call with (mode, branch)
      alt Mode is "store"
        BS->>FS: Read bundle stats JSON file
        BS->>GS: getGistContent(gistID)
        GS-->>BS: Return current stats
        BS->>GS: updateGistContent(new stats)
        BS-->>CI: Return updated stats output
      else Mode is "compare"
        BS->>FS: Read bundle stats JSON file
        BS->>GS: getGistContent(gistID)
        GS-->>BS: Return current stats
        BS->>BS: Compare current vs new stats
        BS-->>CI: Return comparison string as output
      end
    
    Loading
    sequenceDiagram
      participant CI as CI Step
      participant BS as Bundle Stats Workflow
      participant PR as Pull Request API
    
      CI->>CI: Create zip package & parse bundle stats
      CI->>BS: Invoke Bundle Stats workflow (store/compare)
      BS-->>CI: Return stats result
      CI->>PR: Update PR Description with bundle stats
    
    Loading

    Possibly related PRs

    Suggested labels

    Review effort 4/5, Review effort 3/5

    Suggested reviewers

    • Phoenix616

    Poem

    Hoppity hops in code, I sing with glee,
    New stats and checks as bright as can be.
    Bundle stats stored or compared on the fly,
    CI and builds watch my code hop by.
    With index.html all tidy and neat,
    I celebrate each change with a joyful beat! 🐰💻
    Hop on to the future, where every line is sweet!


    📜 Recent review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 258bda6 and fa793e3.

    📒 Files selected for processing (1)
    • .github/workflows/bundle-stats.yml (1 hunks)

    🪧 Tips

    Chat

    There are 3 ways to chat with CodeRabbit:

    • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
      • I pushed a fix in commit <commit_id>, please review it.
      • Generate unit testing code for this file.
      • Open a follow-up GitHub issue for this discussion.
    • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
      • @coderabbitai generate unit testing code for this file.
      • @coderabbitai modularize this function.
    • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
      • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
      • @coderabbitai read src/utils.ts and generate unit testing code.
      • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
      • @coderabbitai help me debug CodeRabbit configuration file.

    Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

    CodeRabbit Commands (Invoked using PR comments)

    • @coderabbitai pause to pause the reviews on a PR.
    • @coderabbitai resume to resume the paused reviews.
    • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
    • @coderabbitai full review to do a full review from scratch and review all the files again.
    • @coderabbitai summary to regenerate the summary of the PR.
    • @coderabbitai generate docstrings to generate docstrings for this PR.
    • @coderabbitai resolve resolve all the CodeRabbit review comments.
    • @coderabbitai plan to trigger planning for file edits and PR creation.
    • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
    • @coderabbitai help to get help.

    Other keywords and placeholders

    • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
    • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
    • Add @coderabbitai anywhere in the PR title to generate the title automatically.

    CodeRabbit Configuration File (.coderabbit.yaml)

    • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
    • Please see the configuration documentation for more information.
    • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

    Documentation and Community

    • Visit our Documentation for detailed information on how to use CodeRabbit.
    • Join our Discord Community to get help, request features, and share feedback.
    • Follow us on X/Twitter for updates and announcements.

    @zardoy zardoy changed the title ci: report size change ci: report size change + always check single file build Mar 29, 2025
    @qodo-free-for-open-source-projects

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Error Handling

    The workflow lacks error handling for cases where the Gist might not exist, the JSON parsing could fail, or when the branch stats aren't available for comparison.

    async function getGistContent() {
      const { data } = await github.rest.gists.get({ gist_id: gistId });
      return JSON.parse(data.files['bundle-stats.json'].content || '{}');
    }
    
    async function updateGistContent(content) {
      await github.rest.gists.update({
        gist_id: gistId,
        files: {
          'bundle-stats.json': {
            content: JSON.stringify(content, null, 2)
          }
        }
      });
    }
    File Path Assumption

    The script assumes dist/single/minecraft.html always exists, but doesn't check for its existence before calculating stats, which could cause the workflow to fail.

    SIZE_BYTES=$(du -s dist/single/minecraft.html 2>/dev/null | cut -f1)
    GZIP_BYTES=$(du -s self-host.zip 2>/dev/null | cut -f1)

    @qodo-free-for-open-source-projects
    Copy link

    qodo-free-for-open-source-projects bot commented Mar 29, 2025

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Validate base stats existence

    Add validation to check if baseStats exists before using it. If the branch
    doesn't have stored stats yet, the comparison will fail with an error when
    trying to access properties of undefined.

    .github/workflows/bundle-stats.yml [45-53]

     if ('${{ inputs.mode }}' === 'store') {
       const stats = require('/tmp/bundle-stats.json');
       const content = await getGistContent();
       content['${{ inputs.branch }}'] = stats;
       await updateGistContent(content);
     } else {
       const content = await getGistContent();
       const baseStats = content['${{ inputs.branch }}'];
    +  if (!baseStats) {
    +    core.setOutput('stats', 'No previous stats available for comparison');
    +    return;
    +  }
       const newStats = require('/tmp/bundle-stats.json');
    • Apply this suggestion
    Suggestion importance[1-10]: 9

    __

    Why: This suggestion prevents a critical runtime error that would occur when comparing against a branch with no stored stats. Without this check, the workflow would fail when trying to access properties of undefined, breaking the entire CI process.

    High
    Check file existence

    Add error checking to ensure the files exist before calculating their sizes. If
    the files don't exist, the script will produce incorrect values (0) without any
    error indication.

    .github/workflows/ci.yml [51-57]

     - name: Parse Bundle Stats
       run: |
    -    SIZE_BYTES=$(du -s dist/single/minecraft.html 2>/dev/null | cut -f1)
    -    GZIP_BYTES=$(du -s self-host.zip 2>/dev/null | cut -f1)
    +    if [ ! -f dist/single/minecraft.html ]; then
    +      echo "Error: minecraft.html not found" >&2
    +      exit 1
    +    fi
    +    if [ ! -f self-host.zip ]; then
    +      echo "Error: self-host.zip not found" >&2
    +      exit 1
    +    fi
    +    SIZE_BYTES=$(du -s dist/single/minecraft.html | cut -f1)
    +    GZIP_BYTES=$(du -s self-host.zip | cut -f1)
         SIZE=$(echo "scale=2; $SIZE_BYTES/1024/1024" | bc)
         GZIP_SIZE=$(echo "scale=2; $GZIP_BYTES/1024/1024" | bc)
         echo "{\"total\": ${SIZE}, \"gzipped\": ${GZIP_SIZE}}" > /tmp/bundle-stats.json
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    __

    Why: The suggestion adds critical error checking to ensure required files exist before calculating bundle stats. Without this validation, the workflow would silently produce incorrect values (0) if files are missing, leading to misleading bundle size comparisons.

    Medium
    Add error handling

    Add error handling for the case when the gist doesn't exist or the file content
    is invalid JSON. The current implementation might throw an uncaught exception if
    the gist API call fails or returns unexpected data.

    .github/workflows/bundle-stats.yml [29-32]

     async function getGistContent() {
    -  const { data } = await github.rest.gists.get({ gist_id: gistId });
    -  return JSON.parse(data.files['bundle-stats.json'].content || '{}');
    +  try {
    +    const { data } = await github.rest.gists.get({ gist_id: gistId });
    +    return JSON.parse(data.files['bundle-stats.json']?.content || '{}');
    +  } catch (error) {
    +    console.error('Error fetching gist:', error);
    +    return {};
    +  }
     }
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    __

    Why: The suggestion adds important error handling to prevent workflow failures if the gist API call fails or returns unexpected data. This improves reliability of the workflow by gracefully handling potential API errors.

    Medium
    • Update

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 3

    🔭 Outside diff range comments (1)
    .github/workflows/bundle-stats.yml (1)

    45-58: 🛠️ Refactor suggestion

    Add error handling for Gist operations

    The current implementation doesn't handle potential errors when interacting with Gists or when parsing the JSON data. Adding proper error handling would make this more robust.

    if ('${{ inputs.mode }}' === 'store') {
    -  const stats = require('/tmp/bundle-stats.json');
    -  const content = await getGistContent();
    -  content['${{ inputs.branch }}'] = stats;
    -  await updateGistContent(content);
    +  try {
    +    const stats = require('/tmp/bundle-stats.json');
    +    const content = await getGistContent();
    +    content['${{ inputs.branch }}'] = stats;
    +    await updateGistContent(content);
    +    console.log(`Successfully stored bundle stats for branch: ${{ inputs.branch }}`);
    +  } catch (error) {
    +    core.setFailed(`Failed to store bundle stats: ${error.message}`);
    +  }
    } else {
    -  const content = await getGistContent();
    -  const baseStats = content['${{ inputs.branch }}'];
    -  const newStats = require('/tmp/bundle-stats.json');
    -
    -  const comparison = `minecraft.html (normal build gzip)\n${baseStats.total}MB (${baseStats.gzipped}MB compressed) -> ${newStats.total}MB (${newStats.gzipped}MB compressed)`;
    -  core.setOutput('stats', comparison);
    +  try {
    +    const content = await getGistContent();
    +    const baseStats = content['${{ inputs.branch }}'];
    +    
    +    if (!baseStats) {
    +      throw new Error(`No baseline stats found for branch: ${{ inputs.branch }}`);
    +    }
    +    
    +    const newStats = require('/tmp/bundle-stats.json');
    +    const comparison = `minecraft.html (normal build gzip)\n${baseStats.total}MB (${baseStats.gzipped}MB compressed) -> ${newStats.total}MB (${newStats.gzipped}MB compressed)`;
    +    core.setOutput('stats', comparison);
    +    console.log(`Successfully compared bundle stats for branch: ${{ inputs.branch }}`);
    +  } catch (error) {
    +    core.setFailed(`Failed to compare bundle stats: ${error.message}`);
    +    // Provide a fallback output so the workflow doesn't fail completely
    +    const newStats = require('/tmp/bundle-stats.json');
    +    core.setOutput('stats', `Current build: ${newStats.total}MB (${newStats.gzipped}MB compressed) [No baseline for comparison]`);
    +  }
    }
    🧹 Nitpick comments (3)
    rsbuild.config.ts (1)

    206-211: Improve error handling in the single file build validation

    The validation check for the single file build is a good addition, but it could benefit from better error handling in case the directory doesn't exist.

    - // check that only index.html is in the dist/single folder
    - const singleBuildFiles = fs.readdirSync('./dist/single')
    - if (singleBuildFiles.length !== 1 || singleBuildFiles[0] !== 'index.html') {
    -     throw new Error('Single file build must only have index.html in the dist/single folder. Ensure workers are imported & built correctly.')
    - }
    
    + // check that only index.html is in the dist/single folder
    + try {
    +     const singleBuildFiles = fs.readdirSync('./dist/single')
    +     if (singleBuildFiles.length !== 1 || singleBuildFiles[0] !== 'index.html') {
    +         throw new Error('Single file build must only have index.html in the dist/single folder. Ensure workers are imported & built correctly.')
    +     }
    + } catch (error) {
    +     if (error.code === 'ENOENT') {
    +         throw new Error('Single file build directory not found. Build may have failed earlier.')
    +     }
    +     throw error
    + }
    .github/workflows/ci.yml (1)

    51-58: Improve error handling for bundle stats calculation

    The current implementation might fail silently if files don't exist or commands fail. Adding error checking would make this more robust.

    - - name: Parse Bundle Stats
    -   run: |
    -     SIZE_BYTES=$(du -s dist/single/minecraft.html 2>/dev/null | cut -f1)
    -     GZIP_BYTES=$(du -s self-host.zip 2>/dev/null | cut -f1)
    -     SIZE=$(echo "scale=2; $SIZE_BYTES/1024/1024" | bc)
    -     GZIP_SIZE=$(echo "scale=2; $GZIP_BYTES/1024/1024" | bc)
    -     echo "{\"total\": ${SIZE}, \"gzipped\": ${GZIP_SIZE}}" > /tmp/bundle-stats.json
    
    + - name: Parse Bundle Stats
    +   run: |
    +     if [ ! -f dist/single/minecraft.html ]; then
    +       echo "Error: minecraft.html not found. Using fallback values."
    +       SIZE="0.00"
    +     else
    +       SIZE_BYTES=$(du -s dist/single/minecraft.html | cut -f1)
    +       SIZE=$(echo "scale=2; $SIZE_BYTES/1024/1024" | bc)
    +     fi
    +     
    +     if [ ! -f self-host.zip ]; then
    +       echo "Error: self-host.zip not found. Using fallback values."
    +       GZIP_SIZE="0.00"
    +     else
    +       GZIP_BYTES=$(du -s self-host.zip | cut -f1)
    +       GZIP_SIZE=$(echo "scale=2; $GZIP_BYTES/1024/1024" | bc)
    +     fi
    +     
    +     echo "{\"total\": ${SIZE}, \"gzipped\": ${GZIP_SIZE}}" > /tmp/bundle-stats.json
    .github/workflows/bundle-stats.yml (1)

    55-55: Improve format of the stats output

    The current output format could be improved to provide more context and better readability in the PR description.

    - const comparison = `minecraft.html (normal build gzip)\n${baseStats.total}MB (${baseStats.gzipped}MB compressed) -> ${newStats.total}MB (${newStats.gzipped}MB compressed)`;
    + const totalDiff = (newStats.total - baseStats.total).toFixed(2);
    + const gzipDiff = (newStats.gzipped - baseStats.gzipped).toFixed(2);
    + const totalDiffStr = totalDiff > 0 ? `+${totalDiff}` : totalDiff;
    + const gzipDiffStr = gzipDiff > 0 ? `+${gzipDiff}` : gzipDiff;
    + const comparison = `minecraft.html bundle size:\n${baseStats.total}MB → ${newStats.total}MB (${totalDiffStr}MB, ${(totalDiff / baseStats.total * 100).toFixed(1)}%)\nCompressed: ${baseStats.gzipped}MB → ${newStats.gzipped}MB (${gzipDiffStr}MB, ${(gzipDiff / baseStats.gzipped * 100).toFixed(1)}%)`;
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 6f15fcc and 258bda6.

    📒 Files selected for processing (3)
    • .github/workflows/bundle-stats.yml (1 hunks)
    • .github/workflows/ci.yml (2 hunks)
    • rsbuild.config.ts (1 hunks)
    🧰 Additional context used
    🧬 Code Definitions (1)
    rsbuild.config.ts (1)
    scripts/build.js (1)
    • fs (5-5)
    🪛 actionlint (1.7.4)
    .github/workflows/ci.yml

    74-74: the runner of "actions/github-script@v6" action is too old to run on GitHub Actions. update the action's version to fix this issue

    (action)

    .github/workflows/bundle-stats.yml

    21-21: property "compare" is not defined in object type {gist-ops: {conclusion: string; outcome: string; outputs: object}}

    (expression)


    23-23: the runner of "actions/github-script@v6" action is too old to run on GitHub Actions. update the action's version to fix this issue

    (action)

    Comment on lines +73 to +101
    - name: Update PR Description
    uses: actions/github-script@v6
    with:
    script: |
    const { data: pr } = await github.rest.pulls.get({
    owner: context.repo.owner,
    repo: context.repo.repo,
    pull_number: context.issue.number
    });

    let body = pr.body || '';
    const statsMarker = '### Bundle Size';
    const comparison = '${{ steps.compare.outputs.stats }}';

    if (body.includes(statsMarker)) {
    body = body.replace(
    new RegExp(`${statsMarker}[^\n]*\n[^\n]*`),
    `${statsMarker}\n${comparison}`
    );
    } else {
    body += `\n\n${statsMarker}\n${comparison}`;
    }

    await github.rest.pulls.update({
    owner: context.repo.owner,
    repo: context.repo.repo,
    pull_number: context.issue.number,
    body
    });
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    ⚠️ Potential issue

    Update GitHub Actions version and improve regex pattern

    According to static analysis, the GitHub Actions runner version is outdated. Also, the regex pattern for updating the PR description could be more robust.

    - - name: Update PR Description
    -   uses: actions/github-script@v6
    +   uses: actions/github-script@v7
    - body = body.replace(
    -   new RegExp(`${statsMarker}[^\n]*\n[^\n]*`),
    -   `${statsMarker}\n${comparison}`
    - );
    + body = body.replace(
    +   new RegExp(`${statsMarker}\\s*\\n[^#]*`, 'g'),
    +   `${statsMarker}\n${comparison}`
    + );

    Committable suggestion skipped: line range outside the PR's diff.

    🧰 Tools
    🪛 actionlint (1.7.4)

    74-74: the runner of "actions/github-script@v6" action is too old to run on GitHub Actions. update the action's version to fix this issue

    (action)

    outputs:
    stats: ${{ steps.compare.outputs.stats }}
    steps:
    - uses: actions/github-script@v6
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    ⚠️ Potential issue

    Update GitHub Actions version

    According to static analysis, the GitHub Actions runner version is outdated.

    - - uses: actions/github-script@v6
    + - uses: actions/github-script@v7
    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    - uses: actions/github-script@v6
    - uses: actions/github-script@v7
    🧰 Tools
    🪛 actionlint (1.7.4)

    23-23: the runner of "actions/github-script@v6" action is too old to run on GitHub Actions. update the action's version to fix this issue

    (action)

    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
    @zardoy zardoy merged commit 47864f0 into next Mar 29, 2025
    1 of 3 checks passed
    @coderabbitai coderabbitai bot mentioned this pull request Jan 10, 2026
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    1 participant