Skip to content

Conversation

@GGomez99
Copy link

@GGomez99 GGomez99 commented Oct 28, 2025

Co-authored with @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: #460
It has been reviewed and supported by @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

Missing features:

  • 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
Author

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

@GGomez99
Copy link
Author

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

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
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
Author

Choose a reason for hiding this comment

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

@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


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

manifestData, err := pnpApi.findClosestPnpManifest()
Copy link
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
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!

@gun-yu gun-yu mentioned this pull request Oct 29, 2025
@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from d70cc8a to 854be04 Compare October 29, 2025 14:07
@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch 2 times, most recently from e92efeb to e4a3c4b Compare October 31, 2025 12:23
@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from 36c114c to 9aa7f3e Compare October 31, 2025 12:57
* 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
@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from 440f2f8 to 478d4d9 Compare November 3, 2025 10:50
@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from 478d4d9 to f3c6477 Compare November 3, 2025 10:52
@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from a98f2f6 to c343dca Compare November 20, 2025 13:49
@adrian-gierakowski
Copy link

is this kept up to date with master? I would like to try it out on a monorepo at work. Thanks!

@GGomez99
Copy link
Author

GGomez99 commented Dec 5, 2025

is this kept up to date with master? I would like to try it out on a monorepo at work. Thanks!

@adrian-gierakowski Sure! Let me update the PR 🙇
Feel free to reach out if you find any issues!

@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from 4f9181b to 9d4c544 Compare December 5, 2025 14:41
@loynoir
Copy link

loynoir commented Dec 13, 2025

Would be nice to support go install fork, due to

$ GOBIN=/tmp/tsgo-pnp go install github.com/GGomez99/typescript-go/cmd/tsgo@latest
go: github.com/GGomez99/typescript-go/cmd/tsgo@latest: version constraints conflict:
        github.com/GGomez99/typescript-go@v0.0.0-20251211030145-ddf0b72580c2: parsing go.mod:
        module declares its path as: github.com/microsoft/typescript-go
                but was required as: github.com/GGomez99/typescript-go
$ GOBIN=/tmp/tsgo-pnp go install github.com/GGomez99/typescript-go/cmd/tsgo@guyllian.gomez/yarn-pnp
go: github.com/GGomez99/typescript-go/cmd/tsgo@guyllian.gomez/yarn-pnp: \
github.com/GGomez99/typescript-go/cmd/tsgo@guyllian.gomez/yarn-pnp: \
invalid version: version "guyllian.gomez/yarn-pnp" invalid: disallowed version string

vscode TypeScriptTeam.native-preview config

  "typescript.experimental.useTsgo": true,
  "typescript.native-preview.tsdk": "/tmp/tsgo-pnp"

@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch 3 times, most recently from 16d5871 to 3777ffa Compare December 16, 2025 14:30
@GGomez99
Copy link
Author

GGomez99 commented Dec 17, 2025

Would be nice to support go install fork

@loynoir I checked the go install limitations, and it looks like I would have to maintain a separate branch where I replace all github.com/microsoft/typescript-go imports with github.com/GGomez99/typescript-go, which sounds a bit tedious (and I'm not even sure if it would work?) 😓

Would it be okay for you to build the binary manually?

git clone https://github.com/GGomez99/typescript-go.git --branch guyllian.gomez/yarn-pnp
cd typescript-go
npm run build
cp ./built/local/tsgo /tmp/tsgo-pnp

@adrian-gierakowski
Copy link

adrian-gierakowski commented Dec 17, 2025

Would be nice to support go install fork

@loynoir I checked the go install limitations, and it looks like I would have to maintain a separate branch where I replace all github.com/microsoft/typescript-go imports with github.com/GGomez99/typescript-go, which sounds a bit tedious (and I'm not even sure if it would work?) 😓

Would it be okay for you to build the binary manually?

git clone https://github.com/GGomez99/typescript-go.git --branch guyllian.gomez/yarn-pnp
cd typescript-go
npm run build
cp ./built/local/tsgo /tmp/tsgo-pnp

Best to wrap this with nix to turn this into reliable one liner

It's in nixpkgs, so it should be trivial to override the arc https://github.com/NixOS/nixpkgs/blob/0fc6414b45da07a3f57a64f4ce6426b1fd015c1f/pkgs/by-name/ty/typescript-go/package.nix#L13

@loynoir
Copy link

loynoir commented Dec 17, 2025

Currently, I workaround with below

  // "typescript.experimental.useTsgo": false,
  "typescript.experimental.useTsgo": true,
  "typescript.native-preview.tsdk": ".vscode",
$ chmod +x ./.vscode/tsgo
...
#!/usr/bin/env bash
set -euo pipefail

handle() {
  local TSGO_PNP_HOME="${HOME:?}/.tsgo-pnp"
  local TSGO_PNP_GOPROXY='https://proxy.golang.org,direct'

  local TSGO_PNP_FORK='https://github.com/GGomez99/typescript-go.git'
  local TSGO_PNP_BRANCH='guyllian.gomez/yarn-pnp'
  local TSGO_PNP_MODULE='./cmd/tsgo'

  local TSGO_PNP_CLONE="${TSGO_PNP_HOME:?}/repo"
  local TSGO_PNP_GOPATH="${TSGO_PNP_HOME:?}/gopath"
  local TSGO_PNP_EXE="${TSGO_PNP_HOME:?}/bin/tsgo-pnp"

  if [ ! -e "${TSGO_PNP_EXE:?}" ]; then
    {
      hash git echo env go

      if [ ! -e "${TSGO_PNP_CLONE:?}" ]; then
        git clone \
          --depth 1 \
          "${TSGO_PNP_FORK:?}" \
          -b "${TSGO_PNP_BRANCH:?}" \
          "${TSGO_PNP_CLONE:?}"
      fi

      echo "replace github.com/microsoft/typescript-go => ${TSGO_PNP_CLONE:?}" >>"${TSGO_PNP_CLONE:?}"/go.mod

      env \
        -C "${TSGO_PNP_CLONE:?}" \
        GOPROXY="${TSGO_PNP_GOPROXY:?}" \
        GOPATH="${TSGO_PNP_GOPATH:?}" \
        go build \
        -o "${TSGO_PNP_EXE:?}" \
        -v \
        "${TSGO_PNP_MODULE:?}"

    } >&2
  fi

  exec "${TSGO_PNP_EXE:?}" "$@"
}

handle "$@"

@adrian-gierakowski
Copy link

Would be nice to support go install fork

@loynoir I checked the go install limitations, and it looks like I would have to maintain a separate branch where I replace all github.com/microsoft/typescript-go imports with github.com/GGomez99/typescript-go, which sounds a bit tedious (and I'm not even sure if it would work?) 😓
Would it be okay for you to build the binary manually?

git clone https://github.com/GGomez99/typescript-go.git --branch guyllian.gomez/yarn-pnp
cd typescript-go
npm run build
cp ./built/local/tsgo /tmp/tsgo-pnp

Best to wrap this with nix to turn this into reliable one liner

It's in nixpkgs, so it should be trivial to override the arc https://github.com/NixOS/nixpkgs/blob/0fc6414b45da07a3f57a64f4ce6426b1fd015c1f/pkgs/by-name/ty/typescript-go/package.nix#L13

done here

you can build and run it with:

nix run github:adrian-gierakowski/typescript-go-yarn-pnp -- --version

note that I only tested it on x84_64-linux

@jdpt0
Copy link

jdpt0 commented Jan 15, 2026

@jakebailey Any chance of getting this PR reviewed again? I, and I'm sure many others, would love to see this get merged before the full release. Many thanks 🙏

@jakebailey
Copy link
Member

jakebailey commented Jan 15, 2026

PR review is not the blocker, it's deciding whether or not we should do it, what that means long term, etc. It's on my mind but there are other things that are a bit more pressing at the moment... (And, I am not the only opinion-haver/decider here!)

@wojtekmaj
Copy link

wojtekmaj commented Jan 16, 2026

I’d like to add my perspective as a long-time Yarn user and express support for adding native Plug’n’Play (PnP) support to typescript-go.

For clarity, I’ll refer to modern Yarn (v2, v3, v4) as Yarn, and to the legacy release line as Yarn v1.

Yarn usage is meaningful and growing

Yarn is not a fringe tool in the JS/TS ecosystem. Based on my own research across the top ~1000 JavaScript and ~1000 TypeScript repositories on GitHub, modern Yarn is currently the 3rd most commonly used package manager, after npm and Yarn v1. Yes, it is more popular than pnpm.

While its absolute share is not dominant, usage shows that it is not going away:

This aligns with the level of interest around this PR. It is currently the most +1’d PR in this repository, and the analogous PR in the original TypeScript repository ranks 2nd by reactions, with over 600 +1s. This suggests ongoing demand rather than isolated requests.

Missing PnP support creates recurring maintenance cost

Without native PnP support:

This creates an implicit coupling between Yarn and TypeScript that increases maintenance effort and complicates upgrades. Native support would remove the need for this coordination.

Patch-based solutions are fragile by design

Relying on patches means:

  • delaying the ability to use new TypeScript releases until a new Yarn version is published
  • depending on internal compiler behavior rather than stable integration points
  • higher risk of breakage during refactors
  • limited ability to test resolution behavior as part of the compiler’s own test suite

A native implementation allows better test coverage and earlier detection of regressions, which benefits both maintainers and users.

Active community and maintainer involvement

  • Yarn’s maintainer is involved
  • Both Yarn maintainers and users have a direct incentive to keep PnP support maintained

This reduces the risk of the feature becoming unmaintained or abandoned after merge.

Moreover, Yarn’s maintainer has voiced interest in adopting typescript-go in the Yarn repository once PnP is supported. This would provide mutual benefits: typescript-go would gain more real-world testing coverage ahead of wider adoption, and Yarn would gain, well... PnP support.

Summary

PnP is not universally used, but it is common in modern monorepos and environments where typescript-go is expected to provide value. Native support would reduce ecosystem friction, remove reliance on patches, and improve long-term stability for users who rely on PnP-based workflows.

Disclaimer: This comment was edited with the help of AI for clarity; all opinions expressed are entirely my own.

@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from 3777ffa to f8261ad Compare January 16, 2026 09:18
@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from f8261ad to 01fbd2e Compare January 16, 2026 09:30
@GGomez99 GGomez99 force-pushed the guyllian.gomez/yarn-pnp branch from 01fbd2e to f46e24d Compare January 16, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants