Skip to content

Add Yarn PnP support - #1966

Closed
Guyllian Gomez (GGomez99) wants to merge 56 commits into
microsoft:mainfrom
GGomez99:guyllian.gomez/yarn-pnp
Closed

Add Yarn PnP support#1966
Guyllian Gomez (GGomez99) wants to merge 56 commits into
microsoft:mainfrom
GGomez99:guyllian.gomez/yarn-pnp

Conversation

@GGomez99

@GGomez99 Guyllian Gomez (GGomez99) commented Oct 28, 2025

Copy link
Copy Markdown

⚠️ [20th Aug 2026] PR Update ⚠️

Due to the migration of typescript-go repo to the original Typescript repo, this PR has been recreated here:
microsoft/TypeScript#63919


Closes microsoft/TypeScript#63769

Co-authored with gun-yu (@gun-yu) as a result of their changes (#1876) being merged in this PR

Motivation

This PR adds Plug'n'Play support natively to Typescript Go, following this issue: microsoft/TypeScript#63769
It has been reviewed and supported by Maël Nison (@arcanis), the lead maintainer of Yarn, and the original author of Yarn PnP.

Datadog has a frontend monorepo using yarn with over 6k packages, and seeing how TS Strada struggles with our current scaling, we decided to invest time in adding a native Yarn PnP support for Typescript Go.
This PnP implementation has been actively used in the IDE of more than 230 engineers at Datadog, and we're committed to fixing all issues reported to us.

Challenges

We did not integrate it in our CI yet as we still have several packages failing on build mode (most errors seem to be reported in the issues section of TS Corsa). Because the TS Corsa API is not available yet, we also couldn't integrate it properly with a fast lage setup unlike with the TS Strada API.

Changes

It's based on the main changes from the original yarn patch (microsoft/TypeScript@99f3e13) that the community has been maintaining for years throughout Typescript Strada updates, except that we implemented the official PnP specification so it doesn't depend on third-party code.

Implemented features:

  • PnP VFS that handles virtual folders and zip files seamlessly, with caching and fallback to the original vfs if pnp is not available
  • Add PnP API and manifest handling, following the yarn PnP specification
  • Initialize the PnP API every time a Host is initialized for both build and LSP modes
  • Add PnP support when resolving modules in internal/module/resolver.go
  • Add PnP support for auto-imports and completion at internal/modulespecifiers/specifiers.go
  • Add PnP support for root types at internal/core/compileroptions.go
  • Handle zip paths when going to implementation with the LSP
  • Update the baseline testing framework to handle PnP when needed
  • PnP manifest auto-refresh by watching .pnp.cjs changes

Tests

  • Basic PnP setup
  • Types from transitive dependencies
  • Root types loading from PnP dependencies
  • Completion and autoimports

@GGomez99

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Datadog"

@GGomez99

Copy link
Copy Markdown
Author

I see my tests are not passing in CI, looking into it 🙇

Comment thread internal/pnp/pnp.go Outdated
Comment on lines +12 to +30
var (
isPnpApiInitialized atomic.Uint32
cachedPnpApi *PnpApi
pnpMu sync.Mutex
// testPnpCache stores per-goroutine PnP APIs for test isolation
// Key is goroutine ID (as int)
testPnpCache sync.Map // map[int]*PnpApi
)

// getGoroutineID returns the current goroutine ID
// It is usually not recommended to work with goroutine IDs, but it is the most non-intrusive way to setup a parallel testing environment for PnP API
func getGoroutineID() int {
var buf [64]byte
n := runtime.Stack(buf[:], false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
id, _ := strconv.Atoi(idField)
return id
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this global / goroutine local storage is something that won't work. If we are storing something, it needs to be attached to a Program, Project, a Host, etc, not global. Parsing out the goroutine ID is definitely a bad idea, especially given our LSP can handle multiple requests at the same time from multiple goroutines and so on.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jake Bailey (@jakebailey) Thank you for having a first look at this PR!
Since your comments, we changed how we initialize the PnpApi by attaching it to the Host directly on initialization. The host can then provide it through host.PnpApi() which will return nil or the pnpapi instance if in a yarn project

Comment thread internal/pnp/pnp.go

pnpApi := &PnpApi{fs: fs, url: filePath}

manifestData, err := pnpApi.findClosestPnpManifest()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand this init. Surely one needs to be able to load multiple projects with differing pnp info at the same time? All of this info really does need to be handled differently.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the last changes mentioned in this comment, we initialize the PnP api with the Host. This means we assume that we have one yarn PnP project per host

I don't think we could initialize it on a smaller scope (like Project or Program) from looking at the changes we had to do to support PnP, but let me know what you think!

@GGomez99
Guyllian Gomez (GGomez99) force-pushed the guyllian.gomez/yarn-pnp branch 2 times, most recently from e92efeb to e4a3c4b Compare October 31, 2025 12:23
* add pnpapi test

* remove cycle dependency

* add error handling

* add error message

* apply error message

* add error bubbling

* code clean up

* remove useless test

* add empty findBrokenPeerDependencies

* early return

* change function name

* apply code review

* merge duplicate logic

* apply collect usage

* apply codereview

* apply fromConfig
Greg Hurrell (wincent) added a commit to wincent/wincent that referenced this pull request Jun 19, 2026
See:

- https://devblogs.microsoft.com/typescript/announcing-typescript-7-0-rc/

Ok, so this is a bit fiddly, so let me explain what's going on:

- We used to have `tsc` from `typescript` (v6), written in JS, and
  `tsgo` from `@typescript/native-preview`, so they could both be
  installed at the same time.
- With v7, the RC package takes over the `tsc` executable; it's now the
  Go implementation and there is no separate `tsgo`; so...
- We change our global install to `7.0-rc`, and ditto for the in-project
  install. We no longer have a v6 install on disk.
- Rename (the project-local) `bin/tsgo` to `bin/tsc`, and make it run
  `tsc` instead of `tsgo`.
- We update our Neovim config to assume `tsc` is now the Go version
  instead of the JS version; but note that we still have to use the
  "tsgo" config from nvim-lspconfig, because that hasn't been renamed
  yet, it's just that we configure it to use the `tsc` executable now
  instead of `tsgo`.
- The Go implementation still doesn't support Yarn PnP and the
  corresponding PR (microsoft/typescript-go#1966)
  is still open, so we maintain our hack wherein we use the `ts_ls`
  config from nvim-lspconfig to use typescript-language-server with a
  wired-in path to a Yarn-managed shim. This looks fragile, and it
  probably is, but it has been working for me ever since I added it a
  couple of months ago (in 011cee5, "feat(nvim): point ts_ls at Yarn
  PnP tsserver shim when available", 2026-05-12).
- The Neovim config will use a repo-local `tsc` if one exists, but note
  that it assumes `tsc` is the Go implementation. So if you use it in a
  repo with v6 or older, it won't work, because v6 doesn't support
  `--lsp --stdio`. I'm not worried about this; I have only a few such
  repos, and I will update them next time I have to work in them.
- Update references to `tsgo` in docs to reference `tsc` instead.
- Note: the `_tsc` completions in the third-party zsh-completions repo
  still match the JS implementation instead of the Go one; presumably
  they will eventually update. I am not sure exactly where the command
  line options diverge, other than having seen that while v7 supports
  LSP via `tsc --lsp --stdio`, v6 does not.

Finally, and most importantly, merely updating my dotfiles on other
machines won't make all this magically start working: I will need to
manually uninstall the old `typescript` package and install the new one:

```
npm uninstall -g typescript @typescript/native-preview
npm install -g typescript@rc --min-release-age=0
```

Change-Id: rmoqwxkqwoywkxrlwspqqmwwmktwqrvz
Maël Nison (arcanis) added a commit to yarnpkg/berry that referenced this pull request Jul 1, 2026
## Summary

- Bound the generated TypeScript compatibility patch to TypeScript
versions before 7.0.0, and committed the matching generated patch
artifact so the compat patch generator stays reproducible.
- Fixed optional patch handling so `optional!` patches fall back when a
package no longer ships a target file (for example `lib/_tsc.js` in
TypeScript 7 / TypeScript compatibility shims), instead of failing the
install with `ENOENT`.
- Added regression coverage for both direct `typescript@7.0.1-rc`
installs and the TypeScript 7 side-by-side recommendation where the
`typescript` ident aliases to `npm:@typescript/typescript6@^6.0.0`.
- Stabilized the scoped `plugin-typescript` acceptance test by moving it
from `@babel/traverse` (now reported by npm search metadata as having
included types) to a fixture package that still exercises
DefinitelyTyped scoped package insertion.
- Added deferred version metadata for the changed workspaces.

## Test plan

- `yarn workspace @yarnpkg/plugin-compat test:plugin-compat`
- `yarn test:unit packages/plugin-patch`
- `node ./scripts/run-yarn.js test:integration
packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-typescript.test.ts`
- `yarn version check`

## Notes

TypeScript 7 ships a native compiler package layout and no longer has
the legacy JS compiler files patched by the existing PnP compatibility
diff, such as `lib/_tsc.js`. Bounding the TypeScript compat patch makes
this intent explicit, while the optional patch fallback fixes the
broader bug that `optional!` patch failures caused by missing target
files were still fatal.

TypeScript 7 support should ship with microsoft/typescript-go#1966.

Made with Cursor

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: John Doe <you@example.com>
Co-authored-by: Maël Nison <mael.nison@mistral.ai>
@proyectoramirez

Jose Ramirez Fuentes (proyectoramirez) commented Jul 7, 2026

Copy link
Copy Markdown

What is a good way to try this PR in a yarn project?

@GGomez99

Copy link
Copy Markdown
Author

What is a good way to try this PR in a yarn project?

Jose Ramirez Fuentes (@proyectoramirez) You can clone the repo and follow the How to build and run section of the contributing doc

Tim Keir (ReDrUm) pushed a commit to ReDrUm/yarn-berry that referenced this pull request Jul 10, 2026
## Summary

- Bound the generated TypeScript compatibility patch to TypeScript
versions before 7.0.0, and committed the matching generated patch
artifact so the compat patch generator stays reproducible.
- Fixed optional patch handling so `optional!` patches fall back when a
package no longer ships a target file (for example `lib/_tsc.js` in
TypeScript 7 / TypeScript compatibility shims), instead of failing the
install with `ENOENT`.
- Added regression coverage for both direct `typescript@7.0.1-rc`
installs and the TypeScript 7 side-by-side recommendation where the
`typescript` ident aliases to `npm:@typescript/typescript6@^6.0.0`.
- Stabilized the scoped `plugin-typescript` acceptance test by moving it
from `@babel/traverse` (now reported by npm search metadata as having
included types) to a fixture package that still exercises
DefinitelyTyped scoped package insertion.
- Added deferred version metadata for the changed workspaces.

## Test plan

- `yarn workspace @yarnpkg/plugin-compat test:plugin-compat`
- `yarn test:unit packages/plugin-patch`
- `node ./scripts/run-yarn.js test:integration
packages/acceptance-tests/pkg-tests-specs/sources/plugins/plugin-typescript.test.ts`
- `yarn version check`

## Notes

TypeScript 7 ships a native compiler package layout and no longer has
the legacy JS compiler files patched by the existing PnP compatibility
diff, such as `lib/_tsc.js`. Bounding the TypeScript compat patch makes
this intent explicit, while the optional patch fallback fixes the
broader bug that `optional!` patch failures caused by missing target
files were still fatal.

TypeScript 7 support should ship with microsoft/typescript-go#1966.

Made with Cursor

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: John Doe <you@example.com>
Co-authored-by: Maël Nison <mael.nison@mistral.ai>
@RyanCavanaugh

Copy link
Copy Markdown
Member

Thank you for contributing to the TypeScript native port!

Development has moved from this repository back to the main microsoft/TypeScript repository. GitHub does not have PR transfer functionality, so we're closing this PR here.

If this change is still relevant, please reopen it as a new pull request in microsoft/TypeScript. We'd appreciate your help carrying the contribution over, and we apologize for the extra work.

See microsoft/typescript-go#4918 for more information about the move.

@GGomez99

Copy link
Copy Markdown
Author

PR has been moved to: microsoft/TypeScript#63919 🙇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

No linked issue This PR doesn't say what bug it fixes Unmigrated PR This PR was open at the time of the repo move back to TypeScript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Yarn PnP