**パッケージマネージャにおけるワークスペースとモノレポ**

2026/01/20 18:33

**パッケージマネージャにおけるワークスペースとモノレポ**

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

要約

ワークスペースは、開発中にローカルパッケージを相互にリンクできるため、多くのパッケージマネージャで普及した機能です。これにより、変更を公開したりシンボリックリンクを作成する必要がなくなり、依存関係の更新が関連プロジェクト全体で即座に反映され、調整コストが削減されます。

この記事では各エコシステムがワークスペースをどのように実装しているかを説明しています:

  • npm(v7以降) – ルート
    package.json
    "workspaces": ["packages/*"]
    を宣言し、シンボリックリンクを作成します。依存関係はルートへホイストされますが、ワークスペース対応の公開機能はありません。
  • pnpm – 依存関係をホイストせず、コンテンツアドレス可能なストアを使用し、
    "workspace:*"
    プロトコルでローカル解決を強制します。公開時には実際のバージョンに置き換えられます。
  • Cargo – メンバー間で単一の
    Cargo.lock
    を共有し、一つのターゲットディレクトリへビルド、機能はグローバルに解決します。ワークスペースメンバーを依存関係順に公開できます(
    cargo publish
    )。
  • Go(1.18以降)
    go.work
    ファイルでローカルで解決するモジュールを列挙します。通常はバージョン管理から除外され、公開モジュールの一部ではなくローカル開発の便宜として機能します。
  • その他のエコシステム(Bundler, Composer, SwiftPM, Dart pub, Elixir Mix, NuGet)ではパスベースのローカル依存関係が提供されますが、正式なワークスペースサポートや公開認識はなく、リリース前に手動で調整する必要があります。

ワークスペースは、JavaScriptマネージャ(Yarn, npm, pnpm, Bun)、Rust の Cargo、Go 1.18+、およびその他のエコシステムによって独立して採用されました。これはローカル変更を公開し複数パッケージを調整するコストが高いためです。

ワークスペースに共通する問題としては、ホイストによる仮想依存関係、開発中に無視されるバージョン不一致、ローカルと新規インストール間のCI差分、ビルドオーケストレーションのギャップ、および組み込み公開調整機能の欠如があります。

Turborepo, Nx, Changesets, Lerna, および Cargo の publish コマンドなどのツールは、ワークスペースパッケージ間でビルド順序、キャッシュ、およびバージョンアップを調整しますが、コアワークスペース機能セットとは別物です。

著者は自身がワークスペースを必要とした経験がないことを指摘し、この機能は調整摩擦を減らすために生まれたものであると述べています。読者には、ワークスペースを使用する動機やメリットが追加の複雑さを上回るかどうかについて経験を共有してほしいと呼びかけています。

本文

私はワークスペースを使ったことがありません。モノレポも使ったことがないですし、巨大なチームで働いた経験もありません。
私が手掛けるプロジェクトは小規模なので、リポジトリごとに1つのパッケージで十分ですし、複数パッケージ間で変更を調整する必要がある場合でも、公開作業はそれほど面倒ではありません。

ところが、現在主流のパッケージマネージャーならほぼ必ずワークスペース(または同等の仕組み)を持っています。JavaScript で言えば Yarn・npm・pnpm・Bun。Rust の Cargo、Python の uv、Go の go.work、PHP の Composer、Dart の pub、Elixir の Mix といった他エコシステムでもほぼ同じ形になっており、Bundler や NuGet もワークスペースに近い手段を提供しています。すべてのエコシステムが独自に同じ構造に至るということは、何か根本的なものが働いているとしか思えません。そこで、その理由を探ってみたいと思います。


基本的な問題

リポジトリ内に 2 つのパッケージがあり、片方がもう一方に依存しているケースを想像してください。ワークスペースがない場合、依存先を変更するたびにそのパッケージを公開し直さなければならず、あるいは手動でシンボリックリンクを張って管理する必要があります。しかし、システム全体に見えないリンクが残り、微妙に壊れたり、公開されたパッケージと挙動が異なるという問題が発生します。

ワークスペースは、インストール時にローカル依存関係を自動で結び付けます。1 つのパッケージを編集すると、もう 1 つが即座に変更を反映します。公開する際には通常通りバージョン解決が行われます。


よくあるユースケース

人はワークスペースとモノレポを同一視しがちですが、大規模コードベースでなくてもメリットがあります。代表的な例は次の通りです。

  • ライブラリとそのプラグイン
  • 公開されないローカルユーティリティを持つアプリ
  • テスト用に例示アプリと共に動作確認するパッケージ
  • デバッグ目的で依存先をローカルクローンして利用

ワークスペースは「これらのパッケージが同時開発されている」ことを解決し、モノレポは「すべてのコードが 1 か所にある」ことを解決します。両者は重なる部分がありますが、別物です。複数リポジトリ間で変更を調整するのは面倒(PR が分離、CI が個別、リリーススケジュールも異なる)ため、モノレポが魅力的になったわけです。ワークスペースは依存関係の結び付けを自動化して、モノレポを実用的にします。


実際の仕組み

npm (v7 以降)

{
  "workspaces": ["packages/*"]
}

npm install
を実行すると
node_modules
に各ワークスペースパッケージへのシンボリックリンクが作られます。もし package-bpackage-a を依存として宣言していれば、npm はレジストリからではなくローカルコピーへリンクします。可能な限り依存はルートの
node_modules
に hoist(昇格)されますが、これは後述する「幻影依存」問題を引き起こす原因になります。npm にはワークスペース専用の公開機能はありません。手動リンクの代替策として
npm link
が利用できます。

Yarn

Yarn は最初からワークスペースをサポートしていました。Yarn 1 がこのパターンを広め、Yarn Berry(v2 以降)では内部構造が変わりましたが同じ設定を保持しています。npm と同様に依存は hoist され、公開時のワークスペース感知機能はありません。

pnpm

  1. hoisting を行わない
    各パッケージに独自の

    node_modules
    が作られ、pnpm のコンテンツアドレス可能ストアへのシンボリックリンクのみが配置されます。これにより、パッケージは明示的に宣言した依存だけをインポートできます。

  2. workspace:
    プロトコル

    {
      "dependencies": {
        "sibling-package": "workspace:*"
      }
    }
    

    これにより pnpm は常にワークスペース内で解決し、レジストリからは取得しません。公開時には

    workspace:*
    が実際のバージョン番号へ置き換えられます。npm と Yarn にはこの機能がないため、ローカルパスを参照したまま公開してしまうケースがあります。厳格な分離は依存バグを早期に発見できる一方で、npm/Yarn からの移行時に多少の摩擦があります。

Bun

Bun は npm と Yarn と同じ

workspaces
フィールドを使用し、シンボリックリンクを作成します。Bun の高速性はワークスペースインストールにも適用されます。

Cargo (Rust)

[workspace]
members = ["crates/*"]

すべてのメンバーが単一の

Cargo.lock
を共有し、ビルドは同じターゲットディレクトリに行われます。
path = "../other"
のように宣言した依存は Cargo がリンクします。共有ロックファイルはワークスペース全体で整合性を保ちます。また、機能(feature)解決もワークスペース単位で統一されるため、同じ依存の異なるバージョンが重複してビルドされることはありません。
cargo publish
はワークスペース関係を理解し、依存順にメンバーを公開できます。

Go

従来は

replace
ディレクティブを使っていました。

replace example.com/mylib => ../mylib

これはコンパイラに対してそのインポートをローカルパスから解決するよう指示します。ディレクティブは

go.mod
に記述され、明確な意図が伝わります。

Go 1.18 で導入された

go.work
は複数モジュールワークスペースをサポートします。各モジュールの
go.mod
に個別に
replace
を追加する代わりに、リポジトリルートに
go.work
ファイルを作成します。

go 1.18

use (
    ./app
    ./lib
)

これで Go は指定されたモジュール間のインポートをローカルで解決します。主な違いは、

go.work
が通常バージョン管理から除外される点です。開発時の便利機能であり、公開モジュールには含まれません。Go はレジストリを持たず(モジュールは
proxy.golang.org
など経由で Git から取得)、ワークスペースはネットワーク呼び出しを短縮する役割も果たします。

Bundler

Bundler に公式のワークスペース機能はありません。Gemfile でパス依存を指定します。

gem 'my_gem', path: '../my_gem'

開発時には便利ですが、公開時に Gemfile を変更する必要があります。

bundle config local
を使えば Gemfile を編集せずにローカルパスへリダイレクトできますが、やはりワークスペース感知の出版機能はありません。

Composer (PHP)

Composer は path リポジトリをサポートします。

{
  "repositories": [
    { "type": "path", "url": "../my-package" }
  ]
}

これによりローカルパッケージがシンボリックリンクで結び付けられます。Bundler 同様、開発時の便宜であり、ワークスペース感知の公開機能はありません。

Swift Package Manager

Xcode UI か

Package.swift
を編集してローカルパッケージを指定します。

.package(path: "../MyLibrary")

Swift は Git からパッケージを取得するため、Go と同様にレジストリが存在せず、ワークスペースはネットワーク呼び出しの短縮を目的としています。

pub (Dart/Flutter)

pub もワークスペースをサポートします。ルートに

pubspec.yaml
を作り、
workspace
フィールドでメンバーを列挙します。

name: my_workspace
workspace:
  - packages/app
  - packages/shared

メンバーは共通の解決戦略を共有し、

pub get
がリンクします。Dart パッケージは個別に pub.dev に公開されます。

Mix (Elixir)

エルビラプログラムで「アンブレラプロジェクト」を作成します。親プロジェクトの

mix.exs
で子アプリを
apps/
ディレクトリに配置します。

# mix.exs at root
defmodule MyUmbrella.MixProject do
  use Mix.Project

  def project do
    [
      apps_path: "apps",
      deps: deps()
    ]
  end
end

各アプリは独自の

mix.exs
を持ちつつ、依存関係を共有し相互参照できます。アンブレラプロジェクトは Hex に個別に公開可能です。

NuGet (.NET)

ローカル依存は「プロジェクト参照」で実現します。ソリューション内でプロジェクト同士が直接参照します。

<!-- In MyApp.csproj -->
<ItemGroup>
  <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
</ItemGroup>

集中管理のために

Directory.Packages.props
を使ってバージョンを共有できます。NuGet.org への公開はパッケージ単位です。


よくある問題点

問題説明
幻影依存(Phantom dependencies)npm と Yarn は可能な限り依存をルート
node_modules
に hoist します。これにより、隣接パッケージが宣言した依存を自動的にインポートできるようになります。しかしワークスペース外で公開されたパッケージはその依存を持たないため、別環境で失敗します。pnpm は hoist しないのでこの問題を回避できます。
バージョン不一致ワークスペース内では
"sibling": "^1.0.0"
のような制約はローカルディスク上の実際のパッケージに対して無視されます。公開時に初めてバージョン不整合が露呈します。
ツールの仮定Jest、TypeScript、ESLint など多くの開発ツールはワークスペース構成を認識できるよう設定が必要です。シンボリックリンクを正しく扱うものとそうでないものがあります。結果として、ツール専用の設定ファイルが増えます。
CI とローカルの差異ローカル開発時に hoist された依存は CI 環境では別解決になる可能性があります。新規インストールでは同じ結果にならないケースがあります。
ビルドオーケストレーションワークスペースはコードの場所を統一するだけで、ビルド順序は管理しません。TypeScript などのコンパイルが必要なパッケージ間では、依存先を先にビルドしておかないと型情報が取れません。そのため Turborepo や Nx のようなツールが追加で必要になることがあります。
公開調整ワークスペースは開発時のリンクのみを扱い、パッケージ公開は別問題です。同じ変更セットで複数パッケージを同時にリリースしたい場合、バージョン番号を手動で合わせる必要があります。Changesets(JavaScript 専用)や Lerna の
lerna publish
などがこの調整を補助します。Cargo は依存順に公開できますが、バージョニングは手作業です。
レジストリの制約npm/Yarn ではスコープ(例:
@babel/core
)で名前空間化できますが、レジストリ側には「これらパッケージは一緒に管理される」という概念はありません。個別に公開し、消費者側で同時更新を保証する必要があります。

結論

すべてのエコシステムはパッケージ作成を極めて簡単にしましたが、その結果として多数の小さなパッケージが増え、調整コストが高くなりました。ワークスペースは「同時開発」する複数パッケージ間のリンクと依存解決を自動化し、この摩擦を軽減します。

私自身はワークスペースを使った経験がないため、実際に導入した方から聞きたいことがあります。どんなケースでワークスペースに乗り換えましたか?メリットとデメリットは何でしたか?ぜひ Mastodon などで共有してください。

同じ日のほかのニュース

一覧に戻る →

2026/01/24 10:00

**27ブランドから325車種へ対応したオープンソース自動運転**

## Japanese Translation: 提供された要約は正確で網羅的かつ明瞭であるため、変更の必要はありません。 --- **元の要約:** Comma Four は、オープンパイロットプラットフォームを利用した高度なドライバーアシスタンス機能を車両に提供する AI 主導型アップグレードです。トヨタ・ヒュンダイ・フォードなど 27 社の 325 台以上の車に追加でき、広範なモデルでテストされ、人間の入力を最小限に抑えて数時間動作可能であることが示されています。オープンパイロットの実証済み自律走行機能を基盤とし、Comma Four は自動運転技術の業界全体への普及へ向けた一歩を表します。同社はユーザーにコミュニティ参加と将来の自律システム形成への貢献を呼びかけつつ、プロダクト開発・自律工学・運用部門での採用も積極的に行っています。広く展開されれば、このアップグレードは多ブランドのドライバーアシスト機能の導入を加速し、自動車技術分野で新たなキャリアパスを創出する可能性があります。

2026/01/19 17:03

**現代のプログラミングで実践しているC++習慣** 1. **説明的な変数名を使う** - 単一文字の識別子は避け、意図が伝わる名前を選ぶ。 2. **不変データには `const` と `static const` を優先する** - 変更されないことを保証し、コンパイラ最適化を促進する。 3. **RAII(Resource Acquisition Is Initialization)パターンを採用する** - リソースの取得と解放をオブジェクトに閉じ込めることでリークを防止。 4. **必要に応じてヘッダーオンリ―ライブラリを利用する** - コンパイル依存性が減り、ビルド時間が短縮される。 5. **型推論には `auto` を使う** - 複雑なイテレータ型を簡潔にしつつ可読性を保つ。 6. **モダンなコンテナ初期化子を活用する** - `std::vector<int> v{1, 2, 3};` は手動の `push_back` よりも明確で簡潔。 7. **Catch2 や Google Test のようなフレームワークで単体テストを書く** - コードの正しさを保証し、リファクタリングを容易にする。 8. **関数は短く、目的を絞る** - 単一責任原則を目指すことで保守性が向上する。 9. **Doxygen コメントでドキュメント化する** - クリーンで検索可能な API ドキュメントを自動生成できる。 10. **最適化はプロファイル後に行う** - ホットスポットを測定し、実際のパフォーマンスボトルネックに対処する。

## Japanese Translation: 作者は主にC#とPythonで作業していますが、バインディングやニッチなタスクのために依然としてC(またはC++)を使用します。これは細粒度の制御を提供するからです。Cには公式のスタイルガイドラインがないため、ブログ、Rust、および完璧主義的マインドセットから引き出した個人的な習慣を構築しています。 新しいプロジェクトでは、GCC/Clang/MSVCサポート付き**C23**を好み、`#if CHAR_BIT != 8 #error` を強制して8ビットの `char` を保証します。彼らは簡潔な typedef のセット(`u8`, `i8`, `i16`, `u16`, `i32`, `u32`, `u64`, `f32`, `f64`, `uptr`, `isize`, `usize`)を採用し、<stdbool.h> からの C23 の `bool` をブール値に使用します。 ヌル終端文字列を避けるために、彼らは **長さ+データ構造**(`String` に `u8 *data; isize len`)を使用します。「parse, don’t validate」に触発されてオープックタイプと信頼できるコンストラクタ(Lelenthran のブログ参照)を作成しています。C23 のタグ互換性により、マクロ (`Tuple2(T1,T2)`) を使って単純なタプルを定義できますが、名前付き構造体は必要です。 エラーハンドリングは **sum types** でモデル化されています:列挙型とそれに伴う構造体(`ErrorCode`, `SafeBuffer`, `MaybeBuffer`)が戻り値に成功または失敗を符号化します。作者は純粋な C では動的メモリ割り当てを意図的に避け、ヒープ重視のコードには Rust または C# を好みます;アレーナアロケータも言及されますが使用されません。 標準ライブラリの使用は最小限です。文字列関数はほとんど使わず、代わりに生の `mem*` 呼び出しを優先します。また、OS API はエルゴノミクスが悪いため再実装されることがよくあります。作者は外部関数のドキュメントを注意深く読むことを強調し、将来的により安全なメモリ取り扱いのために **「slice」タイプ** を追加することを検討しています。 全体として、この記事は読者が自分自身の C スタイルガイドラインを作成するよう奨励しつつ、言語の強みと挫折の両方を認めています。

2026/01/20 0:06

**Go言語が1万5000行を削減** --- ### 概要 Goプログラミング言語は、最近の更新で約 **150万行(LOC)** のコードを削除し、コードベースの大幅な縮小を実現しました。これはコミュニティが言語をシンプルに保ち、保守性を向上させるために継続的に取り組んでいる結果です。 ### 主なポイント - **削減規模** - コアパッケージとツール全体で約1,500,000行が削除されました。 - **動機** - 現在の使用状況に合わなくなった重複コードやレガシーコードを排除する。 - 保守性を簡素化し、コンパイル時間を短縮し、可読性を向上させる。 - **開発者への影響** - 廃止予定の機能に対してわずかなAPI変更が加えられました。 - よりシンプルになったコードベースを反映したドキュメントが更新されました。 - **今後の展望** - ミニマリズムとパフォーマンスへの継続的な注力。 - 言語をさらに洗練させるため、コミュニティからの貢献を奨励しています。 ### 結論 Goプロジェクトが半百万行に及ぶ削減を意図的に実施したことは、世界中の開発者に対して明瞭性・効率性・長期的持続可能性へのコミットメントを示すものです。

## Japanese Translation: ``` ## Summary 著者はQuaminaにUnicode文字プロパティ正規表現の堅牢なサポートを構築し、`[~p{L}~p{Zs}~p{Nd}]`という構文を使用しました。 Goの標準ライブラリが最新のUnicodeバージョン(15.0対17.0)に追いついていないため、Quaminaは独自のデータを維持する必要がありました。著者は `UnicodeData.txt` を取得し、フィールド1と3を解析してすべての37カテゴリとそれらの補集合の範囲をリスト化したコードを生成しました—結果として従来の775K行アプローチに比べ5,122行のGoコードのみで済みました。 初期は、すべてのオートマタを事前計算しコードへ直列化すると約12Mのデータが生成され、起動時に長時間停止したりIDEがクラッシュする問題が発生しました。実行時キャッシュ戦略に切り替えることで、Quaminaは初回使用時にUnicodeプロパティオートマタを計算し保持できるようになりました。この変更で追加速度が135/秒から4,330/秒へ(30倍)向上しました。マッチング性能も高いままであり、UTF‑8の短さと浅いオートマタのおかげで数十万〜百万メッセージ/秒を処理できます。 著者は日常的な作業にGenAIツールを使用することを検討しましたが、ツール不足・時間制約・そのようなサービスのビジネス実現性への懐疑心から控えています。次の主要機能は数値量指定子サポート(例:`a{2-5}`)であり、これによりQuaminaの正規表現機能が完結します。この成功を受けてQuamina 2.0の安定リリースが計画されています。生活上の誘惑が勢いを鈍らせましたが、不確実性があるものの今後の開発は奨励されます。 ```