From 70c5b74b4574d55c9d3c89577de49fc17e1d47e2 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 21 Jun 2026 17:57:34 +0900 Subject: [PATCH] wip --- .../frontend-bundle-visualizer-comment.yml | 142 ++++++++++++++++++ .../workflows/frontend-bundle-visualizer.yml | 88 +++++++++++ packages/frontend/package.json | 1 + packages/frontend/vite.config.ts | 24 ++- 4 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/frontend-bundle-visualizer-comment.yml create mode 100644 .github/workflows/frontend-bundle-visualizer.yml diff --git a/.github/workflows/frontend-bundle-visualizer-comment.yml b/.github/workflows/frontend-bundle-visualizer-comment.yml new file mode 100644 index 0000000000..884eb97673 --- /dev/null +++ b/.github/workflows/frontend-bundle-visualizer-comment.yml @@ -0,0 +1,142 @@ +name: Frontend bundle visualizer comment + +on: + workflow_run: + workflows: + - Frontend bundle visualizer + types: + - completed + +permissions: + actions: read + contents: read + issues: write + pull-requests: read + +concurrency: + group: frontend-bundle-visualizer-comment + cancel-in-progress: false + +jobs: + comment: + name: Comment frontend bundle visualizer + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + steps: + - name: Download visualizer report + uses: actions/download-artifact@v8 + with: + name: frontend-bundle-visualizer-report + path: ${{ runner.temp }}/frontend-bundle-visualizer + github-token: ${{ github.token }} + repository: ${{ github.repository }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Comment on pull request + uses: actions/github-script@v9 + with: + github-token: ${{ secrets.FRONTEND_BUNDLE_VISUALIZER_COMMENT_TOKEN || github.token }} + script: | + const fs = require('node:fs'); + const path = require('node:path'); + + const marker = ''; + const reportDir = path.join(process.env.RUNNER_TEMP, 'frontend-bundle-visualizer'); + const reportPath = path.join(reportDir, 'report.md'); + const prNumberPath = path.join(reportDir, 'pr-number.txt'); + const headShaPath = path.join(reportDir, 'head-sha.txt'); + const workflowRun = context.payload.workflow_run; + const headSha = workflowRun.head_sha; + const { owner, repo } = context.repo; + + if (!fs.existsSync(reportPath)) { + core.setFailed('The frontend bundle visualizer artifact does not contain report.md.'); + return; + } + + const artifactHeadSha = fs.existsSync(headShaPath) + ? fs.readFileSync(headShaPath, 'utf8').trim() + : null; + if (artifactHeadSha != null && artifactHeadSha !== headSha) { + core.setFailed(`The artifact head SHA (${artifactHeadSha}) does not match the workflow run head SHA (${headSha}).`); + return; + } + + const associatedPullRequests = new Map(); + for (const pullRequest of workflowRun.pull_requests ?? []) { + if (Number.isInteger(pullRequest.number)) { + associatedPullRequests.set(pullRequest.number, pullRequest); + } + } + + const pullRequestsForCommit = await github.paginate(github.rest.repos.listPullRequestsAssociatedWithCommit, { + owner, + repo, + commit_sha: headSha, + per_page: 100, + }); + for (const pullRequest of pullRequestsForCommit) { + associatedPullRequests.set(pullRequest.number, pullRequest); + } + + const artifactPrNumber = fs.existsSync(prNumberPath) + ? Number(fs.readFileSync(prNumberPath, 'utf8').trim()) + : null; + let issue_number = null; + if (Number.isInteger(artifactPrNumber) && associatedPullRequests.has(artifactPrNumber)) { + issue_number = artifactPrNumber; + } else if (!Number.isInteger(artifactPrNumber) && associatedPullRequests.size === 1) { + issue_number = [...associatedPullRequests.keys()][0]; + } else if (Number.isInteger(artifactPrNumber)) { + core.setFailed(`The artifact pull request number (${artifactPrNumber}) is not associated with ${headSha}.`); + return; + } else { + core.setFailed(`Could not determine the pull request associated with ${headSha}.`); + return; + } + + const report = fs.readFileSync(reportPath, 'utf8').trim(); + const shortSha = headSha.slice(0, 7); + let body = [ + marker, + '## Frontend bundle visualizer', + '', + `Head: \`${shortSha}\``, + '', + report, + ].join('\n'); + + const maxCommentLength = 65_000; + if (body.length > maxCommentLength) { + const footer = [ + '', + '', + `_Report truncated because it exceeded ${maxCommentLength.toLocaleString('en-US')} characters. See the [workflow artifact](${workflowRun.html_url}) for the full report._`, + ].join('\n'); + body = `${body.slice(0, maxCommentLength - footer.length)}${footer}`; + } + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number, + per_page: 100, + }); + const previous = comments.find((comment) => + comment.user?.type === 'Bot' && comment.body?.includes(marker)); + + if (previous) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: previous.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } diff --git a/.github/workflows/frontend-bundle-visualizer.yml b/.github/workflows/frontend-bundle-visualizer.yml new file mode 100644 index 0000000000..86b6d960f4 --- /dev/null +++ b/.github/workflows/frontend-bundle-visualizer.yml @@ -0,0 +1,88 @@ +name: Frontend bundle visualizer + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + paths: + - packages/frontend/** + - packages/frontend-shared/** + - packages/frontend-builder/** + - packages/i18n/** + - packages/icons-subsetter/** + - packages/misskey-js/** + - packages/misskey-reversi/** + - packages/misskey-bubble-game/** + - package.json + - pnpm-lock.yaml + - pnpm-workspace.yaml + - .node-version + - .github/workflows/frontend-bundle-visualizer.yml + - .github/workflows/frontend-bundle-visualizer-comment.yml + +permissions: + contents: read + pull-requests: read + +concurrency: + group: frontend-bundle-visualizer-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + visualize: + name: Build frontend bundle visualizer + runs-on: ubuntu-latest + steps: + - name: Checkout pull request + uses: actions/checkout@v6.0.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + submodules: true + + - name: Setup pnpm + uses: pnpm/action-setup@v6.0.3 + + - name: Setup Node.js + uses: actions/setup-node@v6.4.0 + with: + node-version-file: .node-version + cache: pnpm + + - name: Install dependencies + run: pnpm i --frozen-lockfile + + - name: Build frontend dependencies + run: pnpm --filter "frontend^..." run build + + - name: Prepare visualizer output + run: mkdir -p "$RUNNER_TEMP/frontend-bundle-visualizer" + + - name: Build frontend visualizer + env: + FRONTEND_BUNDLE_VISUALIZER: 'true' + FRONTEND_BUNDLE_VISUALIZER_TEMPLATE: markdown + FRONTEND_BUNDLE_VISUALIZER_FILE: ${{ runner.temp }}/frontend-bundle-visualizer/report.md + run: pnpm --filter frontend run build + + - name: Check visualizer report + run: | + test -s "$RUNNER_TEMP/frontend-bundle-visualizer/report.md" + cat "$RUNNER_TEMP/frontend-bundle-visualizer/report.md" >> "$GITHUB_STEP_SUMMARY" + + - name: Write report metadata + run: | + printf '%s\n' "${{ github.event.pull_request.number }}" > "$RUNNER_TEMP/frontend-bundle-visualizer/pr-number.txt" + printf '%s\n' "${{ github.event.pull_request.head.sha }}" > "$RUNNER_TEMP/frontend-bundle-visualizer/head-sha.txt" + printf '%s\n' "${{ github.event.pull_request.html_url }}" > "$RUNNER_TEMP/frontend-bundle-visualizer/pr-url.txt" + + - name: Upload visualizer report + uses: actions/upload-artifact@v7 + with: + name: frontend-bundle-visualizer-report + path: ${{ runner.temp }}/frontend-bundle-visualizer/ + if-no-files-found: error + retention-days: 7 diff --git a/packages/frontend/package.json b/packages/frontend/package.json index a105ce467e..a50771e962 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -129,6 +129,7 @@ "react": "19.2.7", "react-dom": "19.2.7", "rolldown": "1.1.0", + "rollup-plugin-visualizer": "7.0.1", "sass-embedded": "1.100.0", "seedrandom": "3.0.5", "start-server-and-test": "3.0.9", diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index bb1a327cd2..6bacc563f5 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -2,7 +2,8 @@ import path from 'path'; import pluginVue from '@vitejs/plugin-vue'; import pluginGlsl from 'vite-plugin-glsl'; import { replacePlugin } from 'rolldown/plugins'; -import type { UserConfig } from 'vite'; +import { visualizer } from 'rollup-plugin-visualizer'; +import type { PluginOption, UserConfig } from 'vite'; import { defineConfig } from 'vite'; import * as yaml from 'js-yaml'; import { promises as fsp } from 'fs'; @@ -23,6 +24,26 @@ const host = url ? (new URL(url)).hostname : undefined; const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; +function getBundleVisualizerPlugin(): PluginOption[] { + if (process.env.FRONTEND_BUNDLE_VISUALIZER !== 'true') return []; + + const template = process.env.FRONTEND_BUNDLE_VISUALIZER_TEMPLATE === 'markdown' ? 'markdown' : 'treemap'; + const defaultFilename = template === 'markdown' + ? path.resolve(__dirname, '../../built/_frontend_bundle_visualizer_/report.md') + : path.resolve(__dirname, '../../built/_frontend_bundle_visualizer_/stats.html'); + + return [ + visualizer({ + filename: process.env.FRONTEND_BUNDLE_VISUALIZER_FILE ?? defaultFilename, + title: 'Misskey frontend bundle visualizer', + template, + gzipSize: true, + brotliSize: true, + projectRoot: path.resolve(__dirname, '../..'), + }) as PluginOption, + ]; +} + /** * 検索インデックスの生成設定 */ @@ -129,6 +150,7 @@ export function getConfig(): UserConfig { }), ] : [], + ...getBundleVisualizerPlugin(), ], resolve: {