Skip to content

Commit

Permalink
Fixes errors in the PnP spec (#4724)
Browse files Browse the repository at this point in the history
* Fixes errors in the PnP spec

* Explicitly suggests using `path.resolve` from the manifest location

* Adds a mention about path formats

* Adds a note about $$virtual
  • Loading branch information
arcanis authored and merceyz committed Feb 1, 2023
1 parent dfbbaed commit a229c63
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 24 deletions.
34 changes: 24 additions & 10 deletions packages/gatsby/content/advanced/pnp-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Extra features can then be designed, but are optional. For example, Yarn leverag

All packages are uniquely referenced by **locators**. A locator is a combination of a **package ident**, which includes its scope if relevant, and a **package reference**, which can be seen as a unique ID used to distinguish different instances (or versions) of a same package. The package references should be treated as an opaque value: it doesn't matter from a resolution algorithm perspective that they start with `workspace:`, `virtual:`, `npm:`, or any other protocol.

For portability reasons, all paths must be relative to the manifest folder (so that they can be the same regardless of the location of the project on disk), and all paths must use the unix path format (`/` as separators).

## Fallback

For improved compatibility with legacy codebases, Plug'n'Play supports a feature we call "fallback". The fallback triggers when a package makes a resolution request to a dependency it doesn't list in its dependencies. In normal circumstances the resolver would throw, but when the fallback is enabled the resolver should first try to find the dependency packages amongst the dependencies of a set of special packages. If it finds it, it then returns it transparently.
Expand Down Expand Up @@ -80,6 +82,8 @@ When this pattern is found, the `__virtual__/<hash>/<n>` part must be removed, t

If writing a JS tool, the [`@yarnpkg/fslib`](https://yarnpkg.com/package/@yarnpkg/fslib) package may be of assistance, providing a virtual-aware filesystem layer called `VirtualFS`.

> **Note:** The `__virtual__` folder name appeared with Yarn 3.0. Earlier releases used `$$virtual`, but we changed it after discovering that this pattern triggered bugs in softwares where paths were used as either regexps or replacement. For example, `$$` found in the second parameter from [`String.prototype.replace`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace) silently turned into `$`.
## Manifest reference

When [`pnpEnableInlining`](/configuration/yarnrc#pnpEnableInlining) is explicitly set to `false`, Yarn will generate an additional `.pnp.data.json` file containing the following fields.
Expand Down Expand Up @@ -117,7 +121,7 @@ import {JsonDoc} from 'react-json-doc';

1. Set `resolved` to `specifier` itself and return it

3. Otherwise, if `specifier` starts with "/", "./", or "../", then
3. Otherwise, if `specifier` is either an absolute path or a path prefixed with "./" or "../", then

1. Set `resolved` to **NM_RESOLVE**(`specifier`, `parentURL`) and return it

Expand Down Expand Up @@ -151,19 +155,23 @@ import {JsonDoc} from 'react-json-doc';

8. Let `referenceOrAlias` be the entry from `parentPkg.packageDependencies` referenced by `ident`

9. If `referenceOrAlias` is **undefined**, then
9. If `referenceOrAlias` is **null** or **undefined**, then

1. If `manifest.enableTopLevelFallback` is **true**, then

1. If `parentLocator` **isn't** in `manifest.fallbackExclusionList`, then

1. Set `referenceOrAlias` to **RESOLVE_VIA_FALLBACK**(`manifest`, `ident`)
1. Let `fallback` be **RESOLVE_VIA_FALLBACK**(`manifest`, `ident`)

2. If `fallback` is neither **null** nor **undefined**

1. Set `referenceOrAlias` to `fallback`

10. If `referenceOrAlias` is still **undefined**, then

1. Throw a resolution error

11. If `referenceOrAlias` is **null**, then
11. If `referenceOrAlias` is still **null**, then

1. Note: It means that `parentPkg` has an unfulfilled peer dependency on `ident`

Expand All @@ -175,15 +183,15 @@ import {JsonDoc} from 'react-json-doc';

2. Let `dependencyPkg` be **GET_PACKAGE**(`manifest`, `alias`)

3. Return `dependencyPkg.packageLocation` concatenated with `modulePath`
3. Return `path.resolve(manifest.dirPath, dependencyPkg.packageLocation, modulePath)`

13. Otherwise,

1. Let `reference` be `referenceOrAlias`

2. Let `dependencyPkg` be **GET_PACKAGE**(`manifest`, {`ident`, `reference`})

3. Return `dependencyPkg.packageLocation` concatenated with `modulePath`
3. Return `path.resolve(manifest.dirPath, dependencyPkg.packageLocation, modulePath)`

### GET_PACKAGE(`manifest`, `locator`)

Expand All @@ -205,13 +213,15 @@ Note: The algorithm described here is quite inefficient. You should make sure to

3. Let `relativeUrl` be the relative path between `manifest` and `moduleUrl`

1. Note: Make sure it always starts with a `./` or `../`
1. Note: The relative path must not start with `./`; trim it if needed

4. If `relativeUrl` matches `manifest.ignorePatternData`, then

1. Return **null**

5. For each `referenceMap` value in `manifest.packageRegistryData`
5. Let `relativeUrlWithDot` be `relativeUrl` prefixed with `./` or `../` as necessary

6. For each `referenceMap` value in `manifest.packageRegistryData`

1. For each `registryPkg` value in `referenceMap`

Expand All @@ -227,7 +237,7 @@ Note: The algorithm described here is quite inefficient. You should make sure to

6. Return `bestLocator`

### RESOLVE_VIA_FALLBACK(`manifest`, `specifier`)
### RESOLVE_VIA_FALLBACK(`manifest`, `ident`)

1. Let `topLevelPkg` be **GET_PACKAGE**(`manifest`, {**null**, **null**})

Expand Down Expand Up @@ -267,7 +277,11 @@ Finding the right PnP manifest to use for a resolution isn't always trivial. The

1. Let `pnpDataPath` be `directoryPath` concatenated with `/.pnp.data.json`

2. Set `manifest` to `JSON.parse(readFile(pnpDataPath))` and return it
2. Set `manifest` to `JSON.parse(readFile(pnpDataPath))`

3. Set `manifest.dirPath` to `directoryPath`

4. Return `manifest`

5. Otherwise, if `directoryPath` is `/`, then

Expand Down
24 changes: 11 additions & 13 deletions packages/gatsby/static/configuration/pnp.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,19 @@
"description": "A map of locators that all packages are allowed to access, regardless whether they list them in their dependencies or not.",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"pattern": "^(?:@([^/]+?)/)?([^/]+?)$",
"examples": ["@app/name"]
},
"reference": {
"type": "string",
"examples": ["workspace:."]
}
}
"type": "array",
"prefixItems": [{
"type": "string",
"pattern": "^(?:@([^/]+?)/)?([^/]+?)$",
"examples": ["@app/name"]
}, {
"type": "string",
"foldStyle": false,
"examples": ["workspace:."]
}]
},
"exampleItems": [
{"name": "@app/monorepo", "reference": "workspace:."}
["@app/monorepo", "workspace:."]
]
},
"fallbackExclusionList": {
Expand Down
2 changes: 1 addition & 1 deletion packages/yarnpkg-pnp/tests/testExpectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
[null, [
[null, {
"packageLocation": "./",
"packageDependencies": [],
"packageDependencies": [["test", "npm:1.0.0"]],
"linkType": "SOFT"
}]
]],
Expand Down

0 comments on commit a229c63

Please sign in to comment.