|
| 1 | +# Bundle Size Optimization Plan |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Reduce `@actions/languageservice` package size from **7.9 MB** to **~1.5 MB** (80% reduction). |
| 6 | + |
| 7 | +## Summary |
| 8 | + |
| 9 | +| Phase | Change | Savings | Effort | |
| 10 | +|-------|--------|---------|--------| |
| 11 | +| 1a | Minify all JSON | 60% | Low | |
| 12 | +| 1b | Strip unused fields | 10% | Low | |
| 13 | +| 1c | Drop unused events | 19% | Low | |
| 14 | +| 2 | Lazy-load webhooks.json (optional) | Faster initial load | Medium | |
| 15 | + |
| 16 | +## Phase 1: Optimize JSON files |
| 17 | + |
| 18 | +### What each JSON file is used for |
| 19 | + |
| 20 | +| File | Package | Purpose | |
| 21 | +|------|---------|---------| |
| 22 | +| `webhooks.json` | languageservice | Autocomplete/validation for `github.event.*` expressions. Contains event payload schemas from GitHub's REST API. | |
| 23 | +| `objects.json` | languageservice | Deduplicated parameter definitions shared across webhooks (reduces duplication in webhooks.json). | |
| 24 | +| `workflow-v1.0.json` | workflow-parser | Workflow schema defining valid YAML structure (`jobs`, `steps`, `runs-on`, event triggers, etc.). | |
| 25 | +| `descriptions.json` | languageservice | Hover descriptions for contexts (`github`, `env`, `secrets`) and built-in functions (`format`, `contains`, etc.). | |
| 26 | +| `schedule.json` | languageservice | Sample `github.event` payload for `on: schedule` trigger (not a real webhook, manually authored). | |
| 27 | +| `workflow_call.json` | languageservice | Sample `github.event` payload for `on: workflow_call` trigger (not a real webhook, manually authored). | |
| 28 | + |
| 29 | +### Impact table |
| 30 | + |
| 31 | +| File | Original | Strip | Drop | Minify | Gzip | All (no Gzip) | All (w/ Gzip) | |
| 32 | +|------|----------|-------|------|--------|------|---------------|---------------| |
| 33 | +| `webhooks.json` | 6.2 MB | 5.6 MB | 5.0 MB | 2.4 MB | 188 KB | **1.0 MB** | **50 KB** | |
| 34 | +| `objects.json` | 948 KB | N/A | 770 KB | 460 KB | 36 KB | **180 KB** | **18 KB** | |
| 35 | +| `workflow-v1.0.json` | 112 KB | N/A | N/A | 70 KB | 13 KB | **70 KB** | **12 KB** | |
| 36 | +| `descriptions.json` | 18 KB | N/A | N/A | 17 KB | 3 KB | **17 KB** | **3 KB** | |
| 37 | +| `schedule.json` | 5.7 KB | N/A | N/A | 5.1 KB | 1 KB | **5.1 KB** | **1 KB** | |
| 38 | +| `workflow_call.json` | 7.3 KB | N/A | N/A | 6.5 KB | 1 KB | **6.5 KB** | **1 KB** | |
| 39 | +| **Total** | **7.3 MB** | | | | **~240 KB** | **~1.3 MB** | **~85 KB** | |
| 40 | + |
| 41 | +- **Strip** = Remove unused fields (`summary`, `availability`, `category`, `action`) |
| 42 | +- **Drop** = Remove 31 non-trigger events (`installation`, `star`, `team`, etc.) |
| 43 | +- **Minify** = Remove whitespace (`JSON.stringify(data)` instead of `JSON.stringify(data, null, 2)`) |
| 44 | +- **Gzip** = Network transfer size (free - handled automatically by browser/server) |
| 45 | + |
| 46 | +### 1a. Minify all JSON files |
| 47 | + |
| 48 | +**Generated files** (`webhooks.json`, `objects.json`): |
| 49 | +- Update `languageservice/script/webhooks/index.ts` |
| 50 | +- These are generated via `npm run update-webhooks` from GitHub's REST API spec |
| 51 | +- Use `JSON.stringify(data)` instead of `JSON.stringify(data, null, 2)` |
| 52 | + |
| 53 | +**Hand-authored files** (`workflow-v1.0.json`, `descriptions.json`, `schedule.json`, `workflow_call.json`): |
| 54 | +- Add minification step to build scripts |
| 55 | + |
| 56 | +### 1b. Strip unused fields from webhooks.json |
| 57 | + |
| 58 | +Remove before writing: |
| 59 | +- `summary` |
| 60 | +- `availability` |
| 61 | +- `category` |
| 62 | +- `action` |
| 63 | + |
| 64 | +### 1c. Drop non-trigger events from webhooks.json |
| 65 | + |
| 66 | +Keep only events that can trigger workflows ([docs](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)). Drop 31 events: |
| 67 | + |
| 68 | +``` |
| 69 | +code_scanning_alert, commit_comment, dependabot_alert, deploy_key, |
| 70 | +github_app_authorization, installation, installation_repositories, |
| 71 | +installation_target, marketplace_purchase, member, membership, meta, |
| 72 | +org_block, organization, package, ping, projects_v2, projects_v2_item, |
| 73 | +pull_request_review_thread, repository, repository_import, |
| 74 | +repository_vulnerability_alert, secret_scanning_alert, |
| 75 | +secret_scanning_alert_location, security_advisory, security_and_analysis, |
| 76 | +sponsorship, star, team, team_add, workflow_job |
| 77 | +``` |
| 78 | + |
| 79 | +**Expected result:** Total JSON 7.3 MB → ~1.3 MB (82% reduction) |
| 80 | + |
| 81 | +--- |
| 82 | + |
| 83 | +## Phase 2: Lazy loading (optional) |
| 84 | + |
| 85 | +Refactor `eventPayloads.ts` to load JSON on first use: |
| 86 | + |
| 87 | +```typescript |
| 88 | +let webhooksData: Webhooks | null = null; |
| 89 | + |
| 90 | +async function getWebhooks() { |
| 91 | + if (!webhooksData) { |
| 92 | + const { default: data } = await import("./webhooks.json"); |
| 93 | + webhooksData = hydrate(data); |
| 94 | + } |
| 95 | + return webhooksData; |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +**Benefit:** Faster initial load when `github.event.*` isn't used. |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## Current github-ui architecture |
| 104 | + |
| 105 | +github-ui lazy-loads the language service via dynamic import: |
| 106 | + |
| 107 | +```typescript |
| 108 | +// workflow-editor-next.ts |
| 109 | +let languageServicePromise: Promise<typeof import('./workflow-editor-language-service')> | null = null |
| 110 | + |
| 111 | +async function getLanguageServiceModule() { |
| 112 | + if (!languageServicePromise) { |
| 113 | + languageServicePromise = import('./workflow-editor-language-service') |
| 114 | + } |
| 115 | + return languageServicePromise |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +**What this means:** |
| 120 | +- The language service is only loaded when the workflow editor needs autocomplete/hover/validation |
| 121 | +- Webpack code-splits it into a separate chunk |
| 122 | +- The ~7.9 MB package is NOT loaded on initial page load |
| 123 | + |
| 124 | +**Why Phase 1 is the priority:** |
| 125 | +- When the language service chunk IS loaded, it still loads all 7.3 MB of JSON |
| 126 | +- Reducing JSON to ~1.3 MB directly reduces this chunk size |
| 127 | +- No changes needed in github-ui - the benefit is automatic |
| 128 | + |
| 129 | +--- |
| 130 | + |
| 131 | +## Not doing |
| 132 | + |
| 133 | +- **Tree-shaking / `sideEffects`** - github-ui imports `complete`, `hover`, and `validate` together, and all three depend on the same webhook JSON. Tree-shaking can't eliminate any of it. |
| 134 | +- **Replacing dependencies** - `yaml` and `cronstrue` are appropriately sized |
| 135 | +- **Multi-pass validation API** - Too complex for the benefit |
| 136 | +- **Further deduplication** - Current object deduplication is sufficient |
| 137 | + |
| 138 | +--- |
| 139 | + |
| 140 | +## Future considerations |
| 141 | + |
| 142 | +- **`workflow_call.json` may be incorrect** - For `on: workflow_call`, `github.event` is inherited from the calling workflow (could be push, pull_request, etc.). The current file shows generic properties which may be misleading for autocomplete. Consider returning `Null` for all modes or removing the file entirely. |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## Success metrics |
| 147 | + |
| 148 | +| Metric | Before | After | |
| 149 | +|--------|--------|-------| |
| 150 | +| `webhooks.json` | 6.2 MB | ~1.2 MB | |
| 151 | +| `objects.json` | 948 KB | ~225 KB | |
| 152 | +| Total package (disk) | 7.9 MB | ~1.5 MB | |
| 153 | +| npm tarball (gzipped) | 368 KB | ~80 KB | |
0 commit comments