タグアーカイブ ビルド高速化

VS Code、TypeScript 7移行で型チェック7倍高速化 段階的アプローチの全貌

VS Code、TypeScript 7移行で型チェック7倍高速化 段階的アプローチの全貌

VS Codeチームは2026年2月、TypeScript 7をデフォルトの型チェッカーおよび言語サービスとして採用した。この移行により、VS Code本体の型チェック時間は36秒から5秒へと7倍以上高速化した。全ファイルのビルド時間も80秒から20秒に短縮され、開発者1人あたりの待ち時間が1日に数分単位で削減された。

この劇的な改善は、約6ヶ月にわたる段階的な導入プロセスによって実現した。一気に切り替えるのではなく、低リスクな領域から少しずつTypeScript 7の利用範囲を広げていくことで、バグの早期発見とTypeScriptチームへの継続的なフィードバックが可能になった。以下では、その具体的な戦略と得られた数値、TypeScriptチームとの協業の詳細を解説する。

段階的移行の全体像とメリット

段階的移行の全体像とメリット

リスクを最小化しながら早期フィードバックを得る

VS Codeチームは大規模な変更を行う際、常にインクリメンタル(段階的)なアプローチを選ぶ。その理由は主に2つある。1つはリスクの低減だ。各ステップが小さいため、何か問題が起きても原因の特定と差し戻しが非常に容易になる。2つ目は早期のフィードバックである。TypeScript 7がまだ開発中の段階から、実際の大規模コードベースでテストを始めることで、見過ごされがちなバグや改善点をTypeScriptチームに直接届けられた。

小さな改善を積み重ねるエンジニアリング文化

VS Codeチームは以前にも、コードベース全体にわたるstrict nullチェックの有効化や、リモート開発サポートの追加といった大規模な取り組みを、同じ段階的手法で成功させてきた。今回のTypeScript 7移行もその延長線上にある。一度に大きな変更を加えず、小さな改善をメインブランチに繰り返しマージしていくことで、気づけば一見不可能に思えた課題を克服している。この文化が、Goで書き直された高速なTypeScript 7の恩恵を早期に引き出す原動力となった。

6段階の移行フェーズ詳細

6段階の移行フェーズ詳細
VS CodeにおけるTypeScript 7導入ステップ
STEP 1 探索:プレビュー版で小規模テストとバグ報告
STEP 2 TypeScript 6導入:移行前の互換性確保と改善
STEP 3 TS 6と7の並行稼働:CI上で両方の型チェックを必須化
STEP 4 拡張機能の個別移行とビルドツールの簡素化
STEP 5 TS 7をデフォルト化:全開発者が日常的に利用

上図は約6ヶ月にわたる移行の大まかな流れだ。各ステップが小さく、問題が起きてもすぐに原因を特定できる設計だった。

探索フェーズ(2025年夏〜秋)

TypeScript 7は2025年3月に公開され、夏頃には初期テストが可能な状態にあった。この時点では型チェック機能の方がJavaScript生成(emit)よりも進んでいたため、VS Codeチームはまず --noEmit オプションを使って小規模な拡張機能の型チェックを手動でテストした。問題が見つかり次第、日次で更新されるプレビューパッケージを使って素早く修正を確認するというサイクルが回り始めた。

TypeScript 6による架け橋(2025年秋)

TypeScriptチームは、ユーザーが一足飛びにTS 7へ移行する負荷を軽減するため、TypeScript 6を「橋渡しバージョン」としてリリースした。TS 6では、それまでデフォルトでなかったstrict nullチェックの有効化や、ターゲットのESバージョン引き上げなど、TS 7への適合を容易にする変更が行われた。VS Codeにとっては、完全に書き直されたTS 7への移行に比べるとはるかに小さな一歩であり、わずかなコード修正で対応できた。このステップが、コードベースの健全性を高め、TS 7本番導入への自信を深める役割を果たした。

TS 6と7の並行稼働(2025年秋)

次の段階では、最もリスクの低い領域である「組み込み拡張機能の型チェック」にTypeScript 7の利用を開始した。同時に、CI(継続的インテグレーション)の設定を変更し、TS 6とTS 7の両方でビルドが成功することを必須化した。この並行稼働によって、両バージョンの型チェック結果の微妙な差異を検出し、TypeScriptチームへ報告することができた。

拡張機能の段階的切り替え(2026年1〜2月)

2026年初頭には、TypeScript 7の型チェックの信頼性が十分に高まり、emit機能も完成した。VS Codeチームは内蔵の拡張機能を1つずつTS 7へ移行し始めた。同時に、バンドルツールをwebpackからesbuildに切り替え、ビルド構成を簡素化した。この変更により、バンドル生成の時間も大幅に短縮された。移行は単純な拡張機能から始め、徐々に複雑なものへと広げていった。すでにTS 7でのテスト実績が豊富だったため、問題はほとんど起きなかった。

TS 7のデフォルト化(2026年2月)

最終段階として、通常の開発タスクで実行するウォッチャーやエディタ内で使用する言語サービスをTypeScript 7に切り替えた。コード変更自体は非常に軽微だった。VS Codeリポジトリでは今も旧バージョンへの切り戻しオプションが残されているが、実際に使われることは稀だ。ほとんどの開発者は、TS 7の圧倒的なパフォーマンスの前に戻る理由がない。

数値で見る劇的なパフォーマンス向上

数値で見る劇的なパフォーマンス向上
従来のTypeScript 6 (Before)
36秒 フル型チェック (tsc --noEmit)
VS Codeメインコードベースの型チェックに必要だった時間
TypeScript 7 (After)
5秒 フル型チェック (tsgo --noEmit)
約7倍の高速化を達成。瞬時に近いフィードバックが可能に

上記の比較は、同一のファイル群に対して同じ厳密さで型チェックを実行した結果だ。Goによるネイティブ再実装がこれほど大きな差を生み出した。

型チェック速度の比較

VS Codeのメインコードベースにおける型チェック時間は、TS 6では約36秒だった。TS 7に切り替えることで、同じ処理が5秒で完了する。実に7倍以上の高速化だ。この処理は開発中に何度も実行されるため、待ち時間の累積短縮効果は非常に大きい。

ビルド時間全体の短縮

npm run watch コマンドによるフルビルドと型チェックでは、TS 6利用時に約80秒かかっていた。TS 7移行後は約20秒にまで短縮され、約4分の1の時間で完了する。1回の再起動ごとに約1分が節約され、エージェント支援開発のイテレーション速度も大幅に向上した。

エディタ内言語サポートの起動時間

エディタでTypeScriptの補完やエラー表示を行うには、背後でプロジェクト全体の読み込みが必要になる。VS Codeのメインプロジェクトでは、TS 6時代に約1分を要していたこの処理が、TS 7では10秒ほどで完了する。開発者はエディタの再読み込みを1日に何度も行うため、この50秒の短縮が日々の生産性に直結する。

TypeScriptチームとの協業がもたらした相乗効果

TypeScriptチームとの協業がもたらした相乗効果

大規模コードベースが生きたテスト環境に

VS Codeの巨大で複雑なコードベースは、TypeScript 7の実地テスト環境として非常に優秀だった。新バージョンの開発中から実際の利用に近い形でテストを行い、バグを発見し、エディタツールの完成度を高めることに貢献した。VS Codeチームの開発者たちは、少しでも動作に違和感があれば旧バージョンに切り替え、その都度TypeScriptチームが修正の優先度を判断した。

フォーマット不一致が早期修正を促進

開発者が旧バージョンに戻る最も意外な理由は「コードフォーマットの不一致」だった。補完提案や定義ジャンプの不整合はある程度許容できても、フォーマットの差はPRのコミット前チェックやCIの検証を失敗させる。そのため、わずかな空白の違いまでもが高い優先度で修正された。このフィードバックループが、結果としてTS 7の言事語サポート全体の品質を引き上げた。

フィードバックループの構築

VS CodeチームはTypeScript 7のプレビュー版を試しやすい環境を整え、問題があればエディタから直接報告できる仕組みを作った。報告のハードルを下げることで、小さな違和感も即座にフィードバックとして蓄積された。こうした緊密な連携が、本番運用に耐えうる安定版の早期完成を支えた。

大規模移行プロジェクトから得られた教訓

大規模移行プロジェクトから得られた教訓

TypeScript 7への移行は、VS Codeチームにとって単なるツールのバージョンアップ以上の意味を持つ。段階的に取り組む文化、早期から本番に近い環境でテストする姿勢、そしてツール開発チームとの緊密なコラボレーションが、巨大なコードベースを迅速かつ安全にモダナイズする鍵だった。

VS Codeチームは、この経験が他のプロジェクトにおける大規模なエンジニアリング課題への取り組み方にも応用できると期待している。小さな一歩を積み重ね、フィードバックループを短く保ち、協業を恐れないこと。これらの価値観が、最終的にはより良いプロダクトをより早く届ける力になる。

この記事のポイント

  • VS Codeは約6ヶ月の段階的移行でTypeScript 7を導入。リスクを抑えつつ早期フィードバックを得られた
  • メインコードベースの型チェックが36秒→5秒に高速化。ビルド全体も80秒→20秒に短縮
  • エディタの言語サポート起動が約1分→10秒に短縮され、日々の開発効率が大幅に向上
  • 大規模コードベースがTypeScript 7の実地テスト環境として機能し、協業が相乗効果を生んだ
  • 段階的アプローチと密なフィードバックループが、大規模移行をスムーズに進める鍵となる
Astro 7.0リリース、Rustコンパイラでビルド時間を最大61%短縮

Astro 7.0リリース、Rustコンパイラでビルド時間を最大61%短縮

Astro 7.0が6月22日に正式リリースされた。今回のメジャーアップデートは「速度」にフォーカスしており、.astroファイルのコンパイラをRustで書き直した点が最大の変更点だ。

ベンチマークによると、ビルド時間は前バージョンと比較して15〜61%短縮される。Astro公式ブログが公開したテスト結果では、13,275ページを持つaspire.devのビルドが半分以下になった事例も報告されている。Rust化された基盤、Vite 8との統合、新しいアドバンストルーティング機能が主要な柱だ。

本記事ではAstro 7.0の全変更点を、実務者の視点から詳しく解説する。

Vite 8によるバンドル基盤の刷新

Vite 8によるバンドル基盤の刷新

Astro 7.0のビルド高速化を支える土台が、Vite 8へのアップグレードだ。Vite 8は、JavaScriptツールチェインの世界で最も注目されているリリースのひとつである。最大の変更点は、Rustベースのバンドラ「Rolldown」が標準搭載されたことだ。

Rolldownとは何か

Rolldownは、従来のesbuildとRollupを単一のバンドラで置き換えるツールである。バンドラとは、複数のJavaScriptファイルやコンポーネントを本番用の少数のファイルにまとめる役割を持つ。Rolldownのベンチマークでは、Rollupと比較して10〜30倍高速という結果が出ている。速度だけでなく、既存のRollupプラグインAPIとの互換性も維持している点が実務上の大きな利点だ。

Astroユーザーにとって重要なのは、ほとんどのプロジェクトで設定変更が不要なことだ。Vite 8には既存のesbuild設定やrollupOptions設定を自動的にRolldown用に変換する互換レイヤーが組み込まれている。カスタムViteプラグインを使っている場合も、RolldownがRollupと同じプラグインAPIをサポートするため、そのまま動作する可能性が高い。

Rust化がもたらすビルド性能の飛躍的向上

Rust化がもたらすビルド性能の飛躍的向上

Astroのビルドプロセスは、大きく2つの段階に分かれる。1つ目はサイトのページやコンテンツ、クライアントコンポーネントをJavaScriptにバンドルする段階。2つ目は、バンドルされたコードを「小さなサーバー」として実行し、プリレンダリング対象の全ページにリクエストを送ってHTMLを生成する段階だ。

Astro 7.0は両方の段階を改善しているが、とくに1つ目のバンドル段階に注力している。ビルド時間のボトルネックになりやすい処理をRustで書かれたネイティブコードに移行することで、大幅な高速化を実現した。

Astro 6.x のビルドフロー
.astro ファイル Go製 コンパイラ unified (Markdown処理) Recursive レンダリング
Markdown処理はJavaScriptベースで数千ページ規模だと大きなボトルネックに
Astro 7.0 のビルドフロー
.astro ファイル Rust製 コンパイラ (oxc/Lightning CSS) Sätteri (Rust製Markdown処理) キュー型レンダリング
Rust化+Vite 8 (Rolldown) の組み合わせで15〜61%のビルド時間短縮

上図の通り、ビルドフローの主要な構成要素がRustベースに置き換えられている。.astroファイルのコンパイル、Markdown/MDXの処理、レンダリングエンジンのすべてが刷新された。以下では各要素を詳しく見ていく。

.astroコンパイラのRust化

Astro 7.0では、.astroコンポーネント形式の新しいコンパイラがRustで構築された。このコンパイラは、以前のGoベースのコンパイラをフルリライトしたものだ。内部的には、oxc(高速なJavaScript/TypeScriptパーサ)を解析に、Lightning CSSをCSSスコープ処理に使っている。

単体ではビルド時間の約6%改善にとどまるが、数千ページ規模の大規模サイトでは、他の改善と相乗効果を発揮する。以下の3点は後方互換性に関わる変更として注意が必要だ。

  • HTML自動修正の廃止。旧コンパイラは「正しいHTML」にしようと要素の並べ替えやタグの自動クローズを行っていたが、新コンパイラではマークアップをそのまま扱う。予期せぬ挙動の原因だった自動修正がなくなり、意図した通りに出力されるようになった。
  • JSX形式の厳格化<div>Helloのような閉じタグ欠落や、<div class="Hello >のような属性の未終端は、自動修正されずエラーになる。旧コンパイラがブラウザの挙動を真似て黙って修正していた部分だ。
  • JSXホワイトスペース処理。インライン要素間の改行が可視スペースを生成しなくなる。たとえば、<span>Hello</span><span>World</span>は「HelloWorld」と表示される。スペースが必要な場合は{' '}を明示的に挿入する。

Markdown/MDX処理のSätteri移行

Astro 7.0では、デフォルトのMarkdownとMDXの処理パイプラインが、Rust製プロセッサ「Sätteri」に置き換えられた。SätteriはAstroコアチームメンバーが開発したツールで、内部的にはpulldown-cmark(CommonMark解析)とoxc(MDX式解析)を使用している。

従来のAstroは、JavaScriptベースのunified(remark/rehypeとそのプラグイン群)でMarkdownを処理していた。数千ページのサイトでは、このパイプラインがビルドの最も遅い段階になることが多かった。Astro公式ブログによれば、AstroドキュメントサイトとCloudflareドキュメントサイトでSätteriに切り替えたところ、ビルド時間が1分以上短縮されたという。

Sätteriには、これまで別途プラグインが必要だったMarkdown機能の多くがビルトインで含まれている。GFM(テーブル、脚注、取り消し線、タスクリスト)、スマートパンクチュエーション(カーリークォート)、見出しID、コンテナディレクティブ、数式、フロントマター(YAML/TOML)、上付き・下付き文字、Wikilinksなどだ。これらはfeaturesオプションで有効化できる。

既存のremark/rehypeプラグインに依存しているプロジェクトは、@astrojs/markdown-remarkを使って従来のunifiedベースのパイプラインを引き続き利用できる。

キュー型レンダリングの安定化

Astro 6.0で実験的機能として導入されたキュー型レンダリングが、Astro 7.0で安定版となりデフォルトのレンダリングエンジンになった。これは、式が密集したページで約2.4倍高速という結果が出ている。

従来のレンダリングは再帰的アプローチを取っていた。親コンポーネントが子コンポーネントを呼び出し、さらにその子が孫を呼び出すという入れ子構造でレンダリングが進む。これに対し、新しいエンジンはキュー(またはスタック)と単一のループを使う。キューに子ノードを正しい順序で追加し、キューが空になるまでループでレンダリングを続ける仕組みだ。

初回の実装では「全コンポーネントの順序付きリストを作成→リストをループしてレンダリング」という2パス方式だったが、最終版ではリスト作成とレンダリングを同時に行う方式に改善された。この方式は再帰的アプローチと比較してメモリ使用量も少ない。

アドバンストルーティングでリクエストパイプラインを完全制御

アドバンストルーティングでリクエストパイプラインを完全制御

Astroは静的サイトジェネレーターとしてスタートし、ファイルベースのルーティングを基本としてきた。しかし、ミドルウェア、リダイレクト、リライト、Actions、セッション、i18nといった機能が追加されるにつれ、リクエストのライフサイクル制御が複雑化していた。

認証をActionsより先に実行したい、ログ出力をページレンダリングだけに限定したい、APIリクエストをAstroの外で先に処理したい、といったニーズに応えるため、Astro 7.0ではsrc/fetch.tsファイルを追加することでリクエストパイプラインを完全制御できるようになった。

このパターンは、Cloudflare WorkersやDeno、Bunが採用している標準的なfetchハンドラ形式に準拠している。

import { astro, FetchState } from 'astro/fetch';

export default {
  fetch(request: Request) {
    const state = new FetchState(request);

    // APIリクエストをバックエンドサービスに転送
    if (state.url.pathname.startsWith('/api')) {
      const url = new URL(
        state.url.pathname + state.url.search,
        'https://backend-api.example.com'
      );
      return fetch(new Request(url, request));
    }

    // それ以外はAstroのページやエンドポイントにフォールバック
    return astro(state);
  }
}

Honoとの統合

アドバンストルーティングAPIはHonoとも互換性がある。Honoは軽量なWebフレームワークで、豊富なミドルウェアエコシステムを持つ。以下のようにBasic認証をAstroアプリケーションに組み込める。

import { astro } from 'astro/hono';
import { Hono } from 'hono';
import { basicAuth } from 'hono/basic-auth';

const app = new Hono();
app.use(basicAuth({ username: 'admin', password: 'secret' }));
app.use(astro());

export default app;
従来のミドルウェア(Before)
i18n Actions ミドルウェア ページ
認証チェックがActionsの後になるため、未認証のアクション呼び出しが発生しうる。レスポンスタイミングのログ出力も個別にラップする必要があった
アドバンストルーティング(After)
i18n 認証 Actions ミドルウェア タイミングログ ページ
認証がActionsより先に実行され、タイミングログはページレンダリングのみをラップする。コードを必要な場所に正確に配置可能

より高度な使い方として、個別のAstro機能を別々のミドルウェアとして構成できる。認証、Actions、ミドルウェア、i18n、ページの各レイヤーを任意の順序で積み重ねられるため、認証チェックをActionsより手前に置くといった制御がシンプルに実現できる。このsrc/fetch.tsファイルを追加しなければ、Astroの動作は従来通りだ。

ルートキャッシングとCDNプロバイダ連携

ルートキャッシングとCDNプロバイダ連携

オンデマンドレンダリング応答のキャッシュ制御は、ホスティングサービスごとに異なる仕組みで実装されてきた。Astro 7.0で安定版となったルートキャッシングは、デプロイ先を問わない単一のキャッシングAPIを提供する。

設定の流れはシンプルだ。まずキャッシュプロバイダを一度設定し、あとはページ内でAstro.cache(APIルートではcontext.cache)を使ってレスポンスごとにキャッシュ制御を記述する。標準的なHTTPキャッシングセマンティクスに従うため、特別な知識は不要だ。

import { defineConfig, memoryCache } from 'astro/config';

export default defineConfig({
  cache: {
    provider: memoryCache(),
  },
});
---
Astro.cache.set({
  maxAge: 120,          // 2分間キャッシュ
  swr: 60,              // 再検証中は1分間 stale を返す
  tags: ['products'],   // タグベースの無効化用
});
---

routeRulesを使えば、ルートグループ単位で宣言的にキャッシュルールを設定できる。

export default defineConfig({
  cache: { provider: memoryCache() },
  routeRules: {
    '/blog/[...path]': { maxAge: 300, swr: 60 },
  },
});

キャッシュの無効化はcache.invalidate()でタグ単位またはパス単位で行える。CMSのwebhookエンドポイントをAstroで実装し、コンテンツ更新時に該当キャッシュを破棄するといった使い方が可能だ。

CDNキャッシュプロバイダ

Astro 7.0では、Netlify、Vercel、Cloudflare向けのCDNキャッシュプロバイダが実験的機能として追加された(Cloudflareはプライベートベータ)。これらはレスポンスをメモリではなく、各プラットフォームのエッジネットワークにキャッシュする。キャッシュヒット時はサーバー関数を呼び出さず、CDNから直接応答が返るため、さらに高速なレスポンスを実現できる。

アダプタごとに/cacheエントリポイントからプロバイダをインポートする。

import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';
import { cacheNetlify } from '@astrojs/netlify/cache';

export default defineConfig({
  adapter: netlify(),
  cache: {
    provider: cacheNetlify(),
  },
});

Astro.cacherouteRulescache.invalidate()のAPIは、どのプロバイダでも同じように動作する。各プロバイダが、Astroのキャッシュディレクティブを各プラットフォームのネイティブなキャッシュ制御ヘッダと無効化APIに変換する仕組みだ。

AIエージェント向け開発サーバー機能

AIエージェント向け開発サーバー機能

AIコーディングエージェントの普及に伴い、Astro 7.0はエージェント駆動開発を支援する機能を導入した。AIエージェントは、終了しない長時間実行プロセス(開発サーバー)の扱いが苦手だ。シェルコマンドを実行し、終了を待って出力を読むワークフローに、開発サーバーは適合しない。

バックグラウンド開発サーバー

astro dev --backgroundコマンドを使うと、開発サーバーを管理されたバックグラウンドプロセスとして起動できる。コマンドはサーバーがリクエストを受け付け可能になるまでブロックし、URLとプロセスIDを出力してからデタッチする。ポーリングやスリープ、端末出力の解析は一切不要だ。

AstroはAIエージェント内で実行されていることを自動検出し、バックグラウンドモードを自動的に有効にする。エージェントワークフローでは--backgroundフラグの指定すら不要だ。

ロックファイルによって重複インスタンスが防止される。エージェントが誤って2つ目のサーバーを起動しようとすると、既存インスタンスの詳細が返される。astro dev statusで状態確認、astro dev stopで停止、astro dev logsでバックグラウンドサーバーのログを確認できる。また、全実行中の開発サーバーは/_astro/statusヘルスエンドポイントを公開し、エージェントがサーバーの生存を確認できる。

JSONログ出力

Astroのロガーが完全に設定可能になった。AIエージェント向けには、バックグラウンドモードの自動検出時にJSONログが自動的に有効化される。それ以外の用途でも、CLIまたは設定ファイルで有効化できる。

astro dev --json
import { defineConfig, logHandlers } from "astro/config";

export default defineConfig({
  logger: logHandlers.json()
})

構造化ログが必要なユースケースはAIだけではない。SSRで本番運用しているチームは、Kibana、CloudWatch、Grafana/Lokiといったログ集約サービスと統合するために構造化ログを必要としている。従来のAstroのログ出力は、色付き表示や罫線文字、複数行エラーフォーマットなど、人間の可読性に特化しており、機械による解析が困難だった。

compose() APIを使えば、人間向けのコンソール出力と機械向けのJSONログを同時に出力できる。

import { defineConfig, logHandlers } from "astro/config";

export default defineConfig({
  logger: logHandlers.compose(
    logHandlers.console(),
    logHandlers.json()
  )
})

この記事のポイント

  • Astro 7.0は.astroコンパイラとMarkdown/MDX処理をRust化し、ビルド時間を15〜61%短縮した
  • Vite 8のRust製バンドラRolldownが標準搭載され、既存の設定をほぼそのまま使える
  • アドバンストルーティングでリクエストパイプラインを完全制御でき、Honoとの統合も可能
  • ルートキャッシングが安定版となり、Netlify/Vercel/CloudflareのCDNキャッシュプロバイダも追加された
  • AIエージェント向けにバックグラウンド開発サーバーとJSONログ出力が自動有効化される
WooCommerceモノレポのビルドが大幅高速化、コールドビルド60%減。メモリ使用量84%削減

WooCommerceモノレポのビルドが大幅高速化、コールドビルド60%減。メモリ使用量84%削減

WooCommerceのモノレポ(単一リポジトリ)を使った開発において、ビルドにかかる時間とメモリ消費が大幅に改善された。2026年6月5日、Developer WooCommerce Blogで公開された記事によると、コールドビルド時間が60%削減、ウォッチ(ファイル監視)の準備完了時間が75%短縮、開発時ウォッチプロセスのメモリ使用量が84%削減されたという。

計測環境はM4 Maxプロセッサ(48GB RAM、macOS 26)で、ベースラインから顕著な低下を確認している。一連のプルリクエストによってビルドプロセスを再設計し、開発者体験を飛躍的に向上させた内容を解説しよう。

本記事では、実際に実施されたビルド最適化の技術的詳細と、今後のCIスループット向上計画を紹介する。

WooCommerceモノレポのビルドが抱えていた課題

WooCommerceモノレポのビルドが抱えていた課題

WooCommerceのコードベースは多数のパッケージと連携しており、開発時のwatch:buildコマンドは最大128個のプロセスを起動していた。それぞれがESM(ECMAScript Modules)やCJS(CommonJS)、webpackによる監視を分担し、さらにwireitモニターとPNPMのランチャープロセスが重なり、24.4GBものメモリを消費する状態だった。

このままでは開発マシンのリソースを圧迫し、CI(継続的インテグレーション)実行時にもジョブの待機時間が増大する。ビルド速度の遅延はフィードバックサイクルを長びかせ、生産性に悪影響を及ぼしていた。その根本原因を取り除くため、重複作業の排除とツールチェーンの刷新に着手した。

従来のビルド(Before)
コールドビルド時間 96秒
ウォッチ準備完了時間 132秒
ウォッチメモリ使用量 24.4 GB
改善後のビルド(After)
コールドビルド時間 38秒(60%減)
ウォッチ準備完了時間 33秒(75%減)
ウォッチメモリ使用量 3.9 GB(84%減)
※M4 Max / 48GB RAM / macOS 26 上での計測値。改善率はベースライン比較。

上記の数値が示す通り、ビルド時間とメモリ消費の両面で大幅な改善が実現された。この結果をもたらした主要な施策は以下の3段階に分けられる。

重複ビルドの排除とTypeScriptコンパイラからの脱却

重複ビルドの排除とTypeScriptコンパイラからの脱却

最初に取り組まれたのは、不必要なモジュール形式のビルド重複の解消だ。WooCommerceはESMとCJSの両方を配布しているが、内部のバンドラ(webpack)ではESMのみが消費されていた。にもかかわらず、開発フローでは常に両方を生成していたため、無駄なビルドコストがかかっていた。

PR #64876では、パブリッシング専用のプリパックビルドコマンドを新設し、普段の開発時にはESMのみを生成するよう分離した。これによりビルドプロセスがスリム化され、コールドビルドの短縮に寄与している。

続いて、TypeScriptのコンパイル基盤をtscからesbuildへ移行するための準備が行われた。型チェックは独立したLintステップに分離し、型定義ファイルの生成はパブリッシュ時のみ実行する方式に切り替えた。こうしてビルド本体からTypeScriptコンパイラを外し、高速なバンドラに置き換える土台が整った。

esbuildへの移行とビルド設定の一元化

esbuildへの移行とビルド設定の一元化

型チェックとビルドの分離が完了したあと、全パッケージのビルドをesbuildへ切り替えた。esbuildはGo言語で実装されており、TypeScriptのトランスパイルにおいてtscやBabelよりもはるかに高速だ。この移行だけでウォームビルドの速度は顕著に向上した。

しかし、急いで移行した結果、各パッケージに似たようなbuild.mjsファイルが散在するという新たな課題が生まれた。これに対処するため、PR #65422ではビルド用の内部パッケージ@woocommerce/internal-buildを新設し、従来の設定パッケージ(internal-ts-configinternal-style-build)を統合した。開発マシン上のスクリプトが整理され、今後の保守性も高まった。

Admin/Blocksによるパッケージビルド統合、メモリ大幅削減の鍵

Admin/Blocksによるパッケージビルド統合、メモリ大幅削減の鍵

一連の改善の集大成となったのが、PR #65254で実施されたAdminおよびBlocks向けのビルド統合だ。それまでwatch:buildコマンドが128プロセスも必要だった最大の要因は、Admin用webpackとBlocks用webpackがトランスパイル済みESMを外部パッケージとして消費していたことにある。

このPRでは、各パッケージのソースを直接AdminおよびBlocksのwebpackビルドに含める方式へと変更した。その結果、128プロセスが大幅に削減され、メモリ使用量が24.4GBから3.9GBへと激減した。ウォッチ準備時間も132秒から33秒へと4分の1に短縮されている。

トレードオフとして、パッケージのトランスパイルがesbuildではなくBabelで行われるため、コールドビルドの速度が一部でわずかに後退した(38秒)。しかし、webpackのファイルシステムキャッシュによって日常的な開発では体感されず、E2E(End-to-End)テストのCIジョブが約1分長くなる程度にとどまった。全体のメモリ削減と開発体験の向上に比べれば、十分に許容できる交換だったと言える。

次のターゲットはCIスループットの改善

次のターゲットはCIスループットの改善

ビルドプロセス自体の最適化が完了した現在、開発チームはCIのスループット向上に注力する方針だ。WooCommerceのCIはジョブをマトリクスシャーディングで分散実行しているが、GitHub Actionsのワーカーが枯渇しやすく、各ジョブの実行時間が短くなってもワーカー獲得に20分以上待たされる局面がある。

この問題を解消するため、同一ワーカー上で複数のタスクを並列実行する方式への移行が計画されている。ワーカー台数に依存しない設計に切り替えることで、CI全体のスループットを大幅に引き上げる狙いだ。さらに、E2Eテストスイートの高速化として、マルチサイト構成などを活用し、単一環境内でテストスイートを並列実行する手法も検討されている。

これらの施策が実装されれば、コードをプッシュしてからCIが完了するまでの時間がさらに短縮され、開発のスピードは一段と加速するだろう。

この記事のポイント

  • WooCommerceモノレポの開発ビルドが全面的に見直され、コールドビルド60%減、メモリ使用量84%減を達成
  • 重複ビルドの排除とesbuild移行により、トランスパイル速度が大幅に向上
  • Admin/Blocksへのパッケージ統合で128プロセスを一掃し、メモリ消費を劇的に低減
  • 今後のCIスループット改善では、ワーカー枯渇問題の解決と並列化が焦点