[{"data":1,"prerenderedAt":888},["ShallowReactive",2],{"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns":3,"\u002Fideas\u002Fworkspaces-and-monorepos-in-package-managers":8},["Island",4],{"key":5,"result":6},"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns",{"head":7},{},{"id":9,"title":10,"authors":11,"body":13,"canonicalUrl":875,"canonicalWebsiteName":876,"category":877,"date":878,"description":879,"extension":880,"featured":881,"fullWidthLayout":881,"image":882,"imageAlt":882,"location":882,"meta":883,"metaImage":882,"navigation":623,"path":884,"seo":885,"stem":886,"venue":882,"venueUrl":882,"__hash__":887},"ideas\u002Fideas\u002Fworkspaces-and-monorepos-in-package-managers.md","Workspaces and Monorepos in Package Managers",[12],"andrew",{"type":14,"value":15,"toc":869},"minimark",[16,20,86,89,92,97,100,116,119,123,136,177,195,227,245,284,291,302,314,331,342,354,362,365,379,385,397,407,418,429,437,489,492,502,513,516,532,572,585,597,666,678,688,715,722,726,746,756,762,768,786,818,821,824,832,865],[17,18,19],"p",{},"I've never needed workspaces. Never used a monorepo either. I've also never worked in a massive team. The projects I work on are small enough that a single package per repo works fine, and when I need to coordinate changes across packages, publishing isn't that painful.",[17,21,22,23,30,31,30,36,30,41,46,47,52,53,58,59,67,68,73,74,79,80,85],{},"But every major package manager now has workspaces or something like them. In JavaScript: ",[24,25,29],"a",{"href":26,"rel":27},"https:\u002F\u002Fyarnpkg.com\u002Ffeatures\u002Fworkspaces",[28],"nofollow","Yarn",", ",[24,32,35],{"href":33,"rel":34},"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fusing-npm\u002Fworkspaces",[28],"npm",[24,37,40],{"href":38,"rel":39},"https:\u002F\u002Fpnpm.io\u002Fworkspaces",[28],"pnpm",[24,42,45],{"href":43,"rel":44},"https:\u002F\u002Fbun.sh\u002Fdocs\u002Finstall\u002Fworkspaces",[28],"Bun",". In other ecosystems: ",[24,48,51],{"href":49,"rel":50},"https:\u002F\u002Fdoc.rust-lang.org\u002Fcargo\u002Freference\u002Fworkspaces.html",[28],"Cargo"," (Rust), ",[24,54,57],{"href":55,"rel":56},"https:\u002F\u002Fdocs.astral.sh\u002Fuv\u002Fconcepts\u002Fworkspaces\u002F",[28],"uv"," (Python), ",[24,60,63],{"href":61,"rel":62},"https:\u002F\u002Fgo.dev\u002Fdoc\u002Ftutorial\u002Fworkspaces",[28],[64,65,66],"code",{},"go.work"," (Go), ",[24,69,72],{"href":70,"rel":71},"https:\u002F\u002Fgetcomposer.org\u002Fdoc\u002F05-repositories.md#path",[28],"Composer"," (PHP), ",[24,75,78],{"href":76,"rel":77},"https:\u002F\u002Fdart.dev\u002Ftools\u002Fpub\u002Fworkspaces",[28],"pub"," (Dart), ",[24,81,84],{"href":82,"rel":83},"https:\u002F\u002Fhexdocs.pm\u002Fmix\u002FMix.Tasks.New.html#module-umbrella-projects",[28],"Mix"," (Elixir). Even Bundler and NuGet have workarounds. When every ecosystem independently arrives at the same shape, something structural is going on. So I wanted to understand why.",[17,87,88],{},"The basic problem: you have two packages in your repo, and one depends on the other. Without workspaces, you'd have to publish the dependency every time you change it, or manually symlink it and deal with links that persist invisibly across your system, break in subtle ways, and behave differently than published packages.",[17,90,91],{},"Workspaces let the package manager wire up local dependencies automatically during install. You edit one package, the other sees the changes immediately. When you publish, normal version resolution takes over.",[93,94,96],"h3",{"id":95},"common-use-cases","Common use cases",[17,98,99],{},"People often associate workspaces with monorepos, but you don't need a massive codebase to benefit. Common cases:",[101,102,103,107,110,113],"ul",{},[104,105,106],"li",{},"A library and its plugins",[104,108,109],{},"An app with local utilities that won't be published separately",[104,111,112],{},"A package tested against an example app",[104,114,115],{},"Cloning a dependency locally to debug an issue",[17,117,118],{},"Workspaces solve \"these packages are developed together.\" Monorepos solve \"all our code lives in one place.\" They overlap but aren't the same thing. Coordinating changes across multiple repos is painful (separate PRs, separate CI, separate release schedules), which is why monorepos became attractive. Workspaces make monorepos practical by handling the dependency wiring.",[93,120,122],{"id":121},"how-they-work-in-practice","How they work in practice",[17,124,125,131,132,135],{},[126,127,128],"strong",{},[24,129,35],{"href":33,"rel":130},[28]," (v7+) uses a ",[64,133,134],{},"workspaces"," field in the root package.json:",[137,138,143],"pre",{"className":139,"code":140,"language":141,"meta":142,"style":142},"language-json shiki shiki-themes github-light github-dark","{\n  \"workspaces\": [\"packages\u002F*\"]\n}\n","json","",[64,144,145,154,171],{"__ignoreMap":142},[146,147,150],"span",{"class":148,"line":149},"line",1,[146,151,153],{"class":152},"sVt8B","{\n",[146,155,157,161,164,168],{"class":148,"line":156},2,[146,158,160],{"class":159},"sj4cs","  \"workspaces\"",[146,162,163],{"class":152},": [",[146,165,167],{"class":166},"sZZnC","\"packages\u002F*\"",[146,169,170],{"class":152},"]\n",[146,172,174],{"class":148,"line":173},3,[146,175,176],{"class":152},"}\n",[17,178,179,180,183,184,187,188,190,191,194],{},"Running ",[64,181,182],{},"npm install"," creates symlinks from ",[64,185,186],{},"node_modules"," to each workspace package. If package-b lists package-a as a dependency, npm links to the local copy instead of fetching from the registry. Dependencies get hoisted to the root ",[64,189,186],{}," where possible, which can cause phantom dependency issues. npm has no special publish support for workspaces. The escape hatch for manual linking is ",[64,192,193],{},"npm link",".",[17,196,197,202,203,208,209,214,215,217,218,226],{},[126,198,199],{},[24,200,29],{"href":26,"rel":201},[28]," works similarly but had workspaces from the start. ",[24,204,207],{"href":205,"rel":206},"https:\u002F\u002Fclassic.yarnpkg.com\u002Fblog\u002F2017\u002F08\u002F02\u002Fintroducing-workspaces\u002F",[28],"Yarn 1 popularized the pattern",". Yarn Berry (v2+) changed the internals but kept the same configuration. Yarn 1 hoists like npm, but Yarn Berry's ",[24,210,213],{"href":211,"rel":212},"https:\u002F\u002Fyarnpkg.com\u002Ffeatures\u002Fpnp",[28],"PnP mode"," eliminates ",[64,216,186],{}," entirely and enforces strict dependency resolution, preventing phantom dependencies. Yarn also supports the ",[24,219,222,225],{"href":220,"rel":221},"https:\u002F\u002Fyarnpkg.com\u002Ffeatures\u002Fworkspaces#workspace-ranges-workspace",[28],[64,223,224],{},"workspace:"," protocol"," like pnpm.",[17,228,229,234,235,237,238,244],{},[126,230,231],{},[24,232,40],{"href":38,"rel":233},[28]," doesn't hoist dependencies to the root. Each package gets its own ",[64,236,186],{}," with symlinks into pnpm's content-addressable store. This means packages can only import what they explicitly declare. pnpm and Yarn Berry both support the ",[24,239,242,225],{"href":240,"rel":241},"https:\u002F\u002Fpnpm.io\u002Fworkspaces#workspace-protocol-workspace",[28],[64,243,224],{},":",[137,246,248],{"className":139,"code":247,"language":141,"meta":142,"style":142},"{\n  \"dependencies\": {\n    \"sibling-package\": \"workspace:*\"\n  }\n}\n",[64,249,250,254,262,273,279],{"__ignoreMap":142},[146,251,252],{"class":148,"line":149},[146,253,153],{"class":152},[146,255,256,259],{"class":148,"line":156},[146,257,258],{"class":159},"  \"dependencies\"",[146,260,261],{"class":152},": {\n",[146,263,264,267,270],{"class":148,"line":173},[146,265,266],{"class":159},"    \"sibling-package\"",[146,268,269],{"class":152},": ",[146,271,272],{"class":166},"\"workspace:*\"\n",[146,274,276],{"class":148,"line":275},4,[146,277,278],{"class":152},"  }\n",[146,280,282],{"class":148,"line":281},5,[146,283,176],{"class":152},[17,285,286,287,290],{},"This tells pnpm to always resolve from the workspace, never the registry. When you publish, pnpm replaces ",[64,288,289],{},"workspace:*"," with the actual version number. Yarn Berry supports this protocol too. npm doesn't, so with npm it's easier to accidentally publish a package that references a local path.",[17,292,293,298,299,301],{},[126,294,295],{},[24,296,45],{"href":43,"rel":297},[28]," supports workspaces with the same configuration as npm and Yarn. It uses the ",[64,300,134],{}," field in package.json and creates symlinks like the others. Bun's speed advantage applies to workspace installs too.",[17,303,304,309,310,313],{},[126,305,306],{},[24,307,51],{"href":49,"rel":308},[28]," uses a ",[64,311,312],{},"[workspace]"," section in Cargo.toml:",[137,315,319],{"className":316,"code":317,"language":318,"meta":142,"style":142},"language-toml shiki shiki-themes github-light github-dark","[workspace]\nmembers = [\"crates\u002F*\"]\n","toml",[64,320,321,326],{"__ignoreMap":142},[146,322,323],{"class":148,"line":149},[146,324,325],{},"[workspace]\n",[146,327,328],{"class":148,"line":156},[146,329,330],{},"members = [\"crates\u002F*\"]\n",[17,332,333,334,337,338,341],{},"All workspace members share a single Cargo.lock and build into a single target directory. When one crate depends on another via ",[64,335,336],{},"path = \"..\u002Fother\"",", Cargo handles linking. The shared lockfile provides consistency across the workspace. Cargo also unifies feature resolution: if two crates enable different features of the same dependency, Cargo resolves them across the whole workspace rather than duplicating the dependency. ",[64,339,340],{},"cargo publish"," understands workspace relationships and can publish members in dependency order, making it one of the more complete implementations.",[17,343,344,350,351,353],{},[126,345,346],{},[24,347,349],{"href":61,"rel":348},[28],"Go"," took a different approach. Before ",[64,352,66],{}," existed, you'd use replace directives:",[137,355,360],{"className":356,"code":358,"language":359},[357],"language-text","replace example.com\u002Fmylib => ..\u002Fmylib\n","text",[64,361,358],{"__ignoreMap":142},[17,363,364],{},"This tells the compiler to resolve that import from a local path instead of fetching it. The directive lives in go.mod and is explicit about what it's doing.",[17,366,367,372,373,375,376,378],{},[24,368,371],{"href":369,"rel":370},"https:\u002F\u002Fgo.dev\u002Fblog\u002Fgo1.18",[28],"Go 1.18"," added ",[64,374,66],{}," files for multi-module workspaces. Instead of adding replace directives to each module's go.mod, you create a ",[64,377,66],{}," file at the repo root:",[137,380,383],{"className":381,"code":382,"language":359},[357],"go 1.18\n\nuse (\n    .\u002Fapp\n    .\u002Flib\n)\n",[64,384,382],{"__ignoreMap":142},[17,386,387,388,390,391,396],{},"This tells Go to resolve imports across these modules locally. The key difference: ",[64,389,66],{}," is typically kept out of version control. It's a local development convenience, not part of the published module. For ecosystems like Go (and Swift, which also fetches packages from git), workspaces are partly about short-circuiting the network: without them, you'd have to push a commit just to see if things compile together. Go has no registry to publish to (modules are fetched from version control via proxies like ",[24,392,395],{"href":393,"rel":394},"https:\u002F\u002Fproxy.golang.org",[28],"proxy.golang.org","), so the publishing coordination problem doesn't arise in the same way.",[17,398,399,406],{},[126,400,401],{},[24,402,405],{"href":403,"rel":404},"https:\u002F\u002Fbundler.io\u002Fman\u002Fgemfile.5.html",[28],"Bundler"," has no formal workspace support. You use path dependencies in the Gemfile:",[137,408,412],{"className":409,"code":410,"language":411,"meta":142,"style":142},"language-ruby shiki shiki-themes github-light github-dark","gem 'my_gem', path: '..\u002Fmy_gem'\n","ruby",[64,413,414],{"__ignoreMap":142},[146,415,416],{"class":148,"line":149},[146,417,410],{},[17,419,420,421,428],{},"This works for development but doesn't compose with publishing. You'd need to change the Gemfile before releasing. There's no isolation between gems and no publish support. ",[24,422,425],{"href":423,"rel":424},"https:\u002F\u002Fbundler.io\u002Fman\u002Fbundle-config.1.html#LOCAL-GIT-REPOS",[28],[64,426,427],{},"bundle config local"," lets you redirect a git dependency to a local path without editing the Gemfile, which is cleaner but still a workaround.",[17,430,431,436],{},[126,432,433],{},[24,434,72],{"href":70,"rel":435},[28]," (PHP) supports path repositories. You add a repository entry pointing to a local directory:",[137,438,440],{"className":139,"code":439,"language":141,"meta":142,"style":142},"{\n  \"repositories\": [\n    { \"type\": \"path\", \"url\": \"..\u002Fmy-package\" }\n  ]\n}\n",[64,441,442,446,454,480,485],{"__ignoreMap":142},[146,443,444],{"class":148,"line":149},[146,445,153],{"class":152},[146,447,448,451],{"class":148,"line":156},[146,449,450],{"class":159},"  \"repositories\"",[146,452,453],{"class":152},": [\n",[146,455,456,459,462,464,467,469,472,474,477],{"class":148,"line":173},[146,457,458],{"class":152},"    { ",[146,460,461],{"class":159},"\"type\"",[146,463,269],{"class":152},[146,465,466],{"class":166},"\"path\"",[146,468,30],{"class":152},[146,470,471],{"class":159},"\"url\"",[146,473,269],{"class":152},[146,475,476],{"class":166},"\"..\u002Fmy-package\"",[146,478,479],{"class":152}," }\n",[146,481,482],{"class":148,"line":275},[146,483,484],{"class":152},"  ]\n",[146,486,487],{"class":148,"line":281},[146,488,176],{"class":152},[17,490,491],{},"Composer symlinks the local package. Like Bundler, this is a development convenience without workspace-aware publishing. You'd need to remove the path repository before releasing.",[17,493,494,501],{},[126,495,496],{},[24,497,500],{"href":498,"rel":499},"https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Fxcode\u002Fediting-a-package-dependency-as-a-local-package",[28],"Swift Package Manager"," handles local development through Xcode's UI or by editing Package.swift to use a local path:",[137,503,507],{"className":504,"code":505,"language":506,"meta":142,"style":142},"language-swift shiki shiki-themes github-light github-dark",".package(path: \"..\u002FMyLibrary\")\n","swift",[64,508,509],{"__ignoreMap":142},[146,510,511],{"class":148,"line":149},[146,512,505],{},[17,514,515],{},"SPM doesn't have a central registry (packages are fetched from git), so the publishing coordination problem is similar to Go's.",[17,517,518,523,524,527,528,531],{},[126,519,520],{},[24,521,78],{"href":76,"rel":522},[28]," (Dart\u002FFlutter) added workspace support. You define a ",[64,525,526],{},"pubspec.yaml"," at the root with a ",[64,529,530],{},"workspace"," field:",[137,533,537],{"className":534,"code":535,"language":536,"meta":142,"style":142},"language-yaml shiki shiki-themes github-light github-dark","name: my_workspace\nworkspace:\n  - packages\u002Fapp\n  - packages\u002Fshared\n","yaml",[64,538,539,550,557,565],{"__ignoreMap":142},[146,540,541,545,547],{"class":148,"line":149},[146,542,544],{"class":543},"s9eBZ","name",[146,546,269],{"class":152},[146,548,549],{"class":166},"my_workspace\n",[146,551,552,554],{"class":148,"line":156},[146,553,530],{"class":543},[146,555,556],{"class":152},":\n",[146,558,559,562],{"class":148,"line":173},[146,560,561],{"class":152},"  - ",[146,563,564],{"class":166},"packages\u002Fapp\n",[146,566,567,569],{"class":148,"line":275},[146,568,561],{"class":152},[146,570,571],{"class":166},"packages\u002Fshared\n",[17,573,574,575,578,579,584],{},"Members share a resolution, and ",[64,576,577],{},"pub get"," links them together. Dart packages are published to ",[24,580,583],{"href":581,"rel":582},"https:\u002F\u002Fpub.dev",[28],"pub.dev"," individually.",[17,586,587,592,593,596],{},[126,588,589],{},[24,590,84],{"href":82,"rel":591},[28]," (Elixir) has umbrella projects. You create a parent project with child apps in an ",[64,594,595],{},"apps\u002F"," directory:",[137,598,602],{"className":599,"code":600,"language":601,"meta":142,"style":142},"language-elixir shiki shiki-themes github-light github-dark","# mix.exs at root\ndefmodule MyUmbrella.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      apps_path: \"apps\",\n      deps: deps()\n    ]\n  end\nend\n","elixir",[64,603,604,609,614,619,625,630,636,642,648,654,660],{"__ignoreMap":142},[146,605,606],{"class":148,"line":149},[146,607,608],{},"# mix.exs at root\n",[146,610,611],{"class":148,"line":156},[146,612,613],{},"defmodule MyUmbrella.MixProject do\n",[146,615,616],{"class":148,"line":173},[146,617,618],{},"  use Mix.Project\n",[146,620,621],{"class":148,"line":275},[146,622,624],{"emptyLinePlaceholder":623},true,"\n",[146,626,627],{"class":148,"line":281},[146,628,629],{},"  def project do\n",[146,631,633],{"class":148,"line":632},6,[146,634,635],{},"    [\n",[146,637,639],{"class":148,"line":638},7,[146,640,641],{},"      apps_path: \"apps\",\n",[146,643,645],{"class":148,"line":644},8,[146,646,647],{},"      deps: deps()\n",[146,649,651],{"class":148,"line":650},9,[146,652,653],{},"    ]\n",[146,655,657],{"class":148,"line":656},10,[146,658,659],{},"  end\n",[146,661,663],{"class":148,"line":662},11,[146,664,665],{},"end\n",[17,667,668,669,672,673,584],{},"Each app has its own ",[64,670,671],{},"mix.exs"," but they share dependencies and can reference each other. Umbrella apps can be published to ",[24,674,677],{"href":675,"rel":676},"https:\u002F\u002Fhex.pm\u002F",[28],"Hex",[17,679,680,687],{},[126,681,682],{},[24,683,686],{"href":684,"rel":685},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fnuget\u002Fconsume-packages\u002Fcentral-package-management",[28],"NuGet"," (.NET) uses project references for local dependencies. In a solution, projects reference each other directly:",[137,689,693],{"className":690,"code":691,"language":692,"meta":142,"style":142},"language-xml shiki shiki-themes github-light github-dark","\u003C!-- In MyApp.csproj -->\n\u003CItemGroup>\n  \u003CProjectReference Include=\"..\\MyLibrary\\MyLibrary.csproj\" \u002F>\n\u003C\u002FItemGroup>\n","xml",[64,694,695,700,705,710],{"__ignoreMap":142},[146,696,697],{"class":148,"line":149},[146,698,699],{},"\u003C!-- In MyApp.csproj -->\n",[146,701,702],{"class":148,"line":156},[146,703,704],{},"\u003CItemGroup>\n",[146,706,707],{"class":148,"line":173},[146,708,709],{},"  \u003CProjectReference Include=\"..\\MyLibrary\\MyLibrary.csproj\" \u002F>\n",[146,711,712],{"class":148,"line":275},[146,713,714],{},"\u003C\u002FItemGroup>\n",[17,716,717,718,721],{},"For centralized dependency management, NuGet supports ",[64,719,720],{},"Directory.Packages.props"," to share versions across projects. Publishing to nuget.org is per-package.",[93,723,725],{"id":724},"common-problems","Common problems",[17,727,728,731,732,734,735,745],{},[126,729,730],{},"Phantom dependencies."," npm and Yarn 1 hoist dependencies to the root ",[64,733,186],{},". A package can import something it doesn't declare, as long as a sibling declared it and it got hoisted. This works in the workspace but breaks when you publish the package and a consumer installs it standalone. pnpm avoids this by not hoisting.",[736,737,738],"sup",{},[24,739,744],{"href":740,"ariaDescribedBy":741,"dataFootnoteRef":142,"id":743},"#user-content-fn-1",[742],"footnote-label","user-content-fnref-1","1"," Yarn Berry's PnP mode also prevents this by enforcing strict dependency resolution.",[17,747,748,751,752,755],{},[126,749,750],{},"Version mismatches."," In a workspace, ",[64,753,754],{},"\"sibling\": \"^1.0.0\""," resolves to whatever version is on disk, even if the local package.json says version 2.0.0. The version constraint is ignored during development. You only find out there's a mismatch after publishing.",[17,757,758,761],{},[126,759,760],{},"Tooling assumptions."," Jest, TypeScript, ESLint, and other tools need configuration to understand workspace layouts. Some follow symlinks correctly; some don't. You end up with config files that exist solely to make tools aware of the structure.",[17,763,764,767],{},[126,765,766],{},"CI divergence."," The workspace graph during local development can differ from what CI or consumers see. A dependency that got hoisted locally might resolve differently in a fresh install.",[17,769,770,773,774,779,780,785],{},[126,771,772],{},"Build orchestration."," Workspaces solve where code lives, not how it gets built. If package A is TypeScript and package B imports it, you need to compile A before B can see the types. Workspaces handle linking; build order is a separate problem. This is why tools like ",[24,775,778],{"href":776,"rel":777},"https:\u002F\u002Fturbo.build\u002F",[28],"Turborepo"," and ",[24,781,784],{"href":782,"rel":783},"https:\u002F\u002Fnx.dev\u002F",[28],"Nx"," exist on top of workspaces: they understand the dependency graph and run builds, tests, and lints in the right order, with caching.",[17,787,788,791,792,797,798,803,804,807,808,810,811,30,814,817],{},[126,789,790],{},"Publishing coordination."," Workspaces wire up development, but publishing is a separate problem. If you update two packages together, you probably want to release them together with matching versions. Workspaces have no opinion on this. Tools like ",[24,793,796],{"href":794,"rel":795},"https:\u002F\u002Fgithub.com\u002Fchangesets\u002Fchangesets",[28],"Changesets"," (JavaScript-only) track changes across workspace packages and coordinate version bumps. ",[24,799,802],{"href":800,"rel":801},"https:\u002F\u002Flerna.js.org\u002F",[28],"Lerna's"," ",[64,805,806],{},"lerna publish"," does something similar. Cargo's ",[64,809,340],{}," can publish workspace members in dependency order, but you still manage versioning manually. npm has scoped packages (",[64,812,813],{},"@babel\u002Fcore",[64,815,816],{},"@myorg\u002Futils",") but scopes are just namespacing for ownership. The registry has no concept of \"these packages form a coherent unit.\" You publish each package individually and hope consumers update them in sync.",[819,820],"hr",{},[17,822,823],{},"Looking at all this, my sense is that ecosystems made package creation cheap but left coordination expensive. People created lots of small packages, then needed workspaces to manage the friction that created.",[17,825,826,827,194],{},"I've never needed workspaces myself. If you use them regularly, I'd be curious to hear what pushed you there and whether they've been worth the complexity. What's worked? What's bitten you? ",[24,828,831],{"href":829,"rel":830},"https:\u002F\u002Fmastodon.social\u002F@andrewnez",[28],"Let me know on Mastodon",[833,834,837,843],"section",{"className":835,"dataFootnotes":142},[836],"footnotes",[838,839,842],"h2",{"className":840,"id":742},[841],"sr-only","Footnotes",[844,845,846],"ol",{},[104,847,849,854,855,857,858],{"id":848},"user-content-fn-1",[24,850,853],{"href":851,"rel":852},"https:\u002F\u002Fpnpm.io\u002Fmotivation",[28],"pnpm's motivation"," explains their non-flat ",[64,856,186],{}," structure and why it prevents phantom dependencies. ",[24,859,864],{"href":860,"ariaLabel":861,"className":862,"dataFootnoteBackref":142},"#user-content-fnref-1","Back to reference 1",[863],"data-footnote-backref","↩",[866,867,868],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":142,"searchDepth":156,"depth":156,"links":870},[871,872,873,874],{"id":95,"depth":173,"text":96},{"id":121,"depth":173,"text":122},{"id":724,"depth":173,"text":725},{"id":742,"depth":156,"text":842},"https:\u002F\u002Fnesbitt.io\u002F2026\u002F01\u002F18\u002Fworkspaces-and-monorepos-in-package-managers","nesbitt.io","package-management","2026-01-18","How various package managers implement workspaces and their relationship with monorepos.","md",false,null,{},"\u002Fideas\u002Fworkspaces-and-monorepos-in-package-managers",{"title":10,"description":879},"ideas\u002Fworkspaces-and-monorepos-in-package-managers","6nllMca2Wqt2pe8GMpmJr4zYdPlKQy5cy2kyjtRUOOk",1780596104554]