@gjsify/resolve-npm 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/pnp-relay.d.ts +32 -0
- package/lib/pnp-relay.mjs +181 -0
- package/package.json +9 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Plugin, PluginBuild, OnLoadResult } from 'esbuild';
|
|
2
|
+
|
|
3
|
+
export type TransformContents = (
|
|
4
|
+
args: { path: string; namespace?: string },
|
|
5
|
+
contents: string,
|
|
6
|
+
) => Promise<OnLoadResult | undefined> | OnLoadResult | undefined;
|
|
7
|
+
|
|
8
|
+
export interface GetPnpPluginOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Called once per esbuild `setup(build)` invocation. Returns the actual
|
|
11
|
+
* per-file transformer that runs after the file is read but before
|
|
12
|
+
* returning to esbuild. The factory pattern gives the transformer access
|
|
13
|
+
* to `build.initialOptions` (e.g. for computing bundle-relative paths)
|
|
14
|
+
* which isn't available at plugin-construction time.
|
|
15
|
+
*/
|
|
16
|
+
transformContentsFactory?: (build: PluginBuild) => TransformContents;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `import.meta.url` of the caller. Used to anchor relay issuer resolution
|
|
20
|
+
* on the caller's installation rather than `@gjsify/resolve-npm`'s.
|
|
21
|
+
* Defaults to this module's own URL.
|
|
22
|
+
*/
|
|
23
|
+
issuerUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build the gjsify-flavoured PnP plugin.
|
|
28
|
+
*
|
|
29
|
+
* Returns null when not running under Yarn PnP (no `.pnp.cjs` ancestor of cwd
|
|
30
|
+
* or `@yarnpkg/esbuild-plugin-pnp` not installed).
|
|
31
|
+
*/
|
|
32
|
+
export function getPnpPlugin(opts?: GetPnpPluginOptions): Promise<Plugin | null>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// PnP relay for @gjsify/* polyfills.
|
|
2
|
+
//
|
|
3
|
+
// Yarn PnP throws `UNDECLARED_DEPENDENCY` when esbuild's resolver asks for a
|
|
4
|
+
// package that exists in the install graph but isn't a direct dep of the
|
|
5
|
+
// importer. For external consumers of @gjsify/cli (e.g. ts-for-gir) this means
|
|
6
|
+
// every transitive `@gjsify/<polyfill>` reached via the rewriter would have to
|
|
7
|
+
// be declared as a direct devDep — defeating the point of having
|
|
8
|
+
// `@gjsify/{node,web}-polyfills` as meta-packages.
|
|
9
|
+
//
|
|
10
|
+
// `getPnpPlugin()` returns a Plugin that catches `UNDECLARED_DEPENDENCY` errors
|
|
11
|
+
// and re-resolves them through two relay issuers:
|
|
12
|
+
// 1. The caller's own location (covers `@gjsify/*` that are direct deps of
|
|
13
|
+
// cli's own deps — fast first-try)
|
|
14
|
+
// 2. @gjsify/{node,web}-polyfills' package.json paths (covers everything
|
|
15
|
+
// else, since those meta-packages depend on every individual polyfill)
|
|
16
|
+
//
|
|
17
|
+
// The plugin also accepts a `transformContents` factory: given the
|
|
18
|
+
// `PluginBuild` it returns a per-build transformer that runs after the file
|
|
19
|
+
// is read. esbuild stops at the first matching onLoad, so the rewriter MUST
|
|
20
|
+
// run from inside this onLoad — not be registered as a separate onLoad
|
|
21
|
+
// afterwards (that's the F5 bug).
|
|
22
|
+
|
|
23
|
+
import { createRequire } from 'node:module';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import { dirname, join } from 'node:path';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
27
|
+
import { readFile } from 'node:fs/promises';
|
|
28
|
+
|
|
29
|
+
/** Walk up from dir until .pnp.cjs is found; return its directory or null. */
|
|
30
|
+
function findPnpRoot(dir) {
|
|
31
|
+
let current = dir;
|
|
32
|
+
// eslint-disable-next-line no-constant-condition
|
|
33
|
+
while (true) {
|
|
34
|
+
if (existsSync(join(current, '.pnp.cjs'))) return current;
|
|
35
|
+
const parent = dirname(current);
|
|
36
|
+
if (parent === current) return null;
|
|
37
|
+
current = parent;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {(args: { path: string, namespace?: string }, contents: string) =>
|
|
43
|
+
* Promise<{contents: string, loader?: string, resolveDir?: string} | undefined>
|
|
44
|
+
* | {contents: string, loader?: string, resolveDir?: string} | undefined} TransformContents
|
|
45
|
+
*
|
|
46
|
+
* @typedef {object} GetPnpPluginOptions
|
|
47
|
+
* @property {(build: import('esbuild').PluginBuild) => TransformContents} [transformContentsFactory]
|
|
48
|
+
* Optional: called once per esbuild `setup(build)` invocation. Returns the
|
|
49
|
+
* actual per-file transformer that runs after the file is read but before
|
|
50
|
+
* returning to esbuild. The factory pattern gives the transformer access to
|
|
51
|
+
* `build.initialOptions` (e.g. for computing bundle-relative paths) which
|
|
52
|
+
* isn't available at plugin-construction time.
|
|
53
|
+
*
|
|
54
|
+
* @property {string} [issuerUrl]
|
|
55
|
+
* `import.meta.url` of the caller. Used to anchor relay issuer resolution
|
|
56
|
+
* on the caller's installation rather than `@gjsify/resolve-npm`'s.
|
|
57
|
+
* Defaults to this module's own URL.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build the gjsify-flavoured PnP plugin.
|
|
62
|
+
*
|
|
63
|
+
* Returns null when not running under Yarn PnP (no `.pnp.cjs` ancestor of cwd
|
|
64
|
+
* or `@yarnpkg/esbuild-plugin-pnp` not installed).
|
|
65
|
+
*
|
|
66
|
+
* @param {GetPnpPluginOptions} [opts]
|
|
67
|
+
* @returns {Promise<import('esbuild').Plugin | null>}
|
|
68
|
+
*/
|
|
69
|
+
export async function getPnpPlugin(opts = {}) {
|
|
70
|
+
const { transformContentsFactory, issuerUrl = import.meta.url } = opts;
|
|
71
|
+
if (!findPnpRoot(process.cwd())) return null;
|
|
72
|
+
|
|
73
|
+
let pnpPlugin;
|
|
74
|
+
try {
|
|
75
|
+
({ pnpPlugin } = await import('@yarnpkg/esbuild-plugin-pnp'));
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Anchor relay-issuer resolution on whoever called us. For external
|
|
81
|
+
// consumers this is @gjsify/cli's own file path; node-polyfills + web-polyfills
|
|
82
|
+
// are direct deps of @gjsify/cli, so requireFromIssuer.resolve() reaches them.
|
|
83
|
+
const issuerPath = fileURLToPath(issuerUrl);
|
|
84
|
+
const requireFromIssuer = createRequire(issuerPath);
|
|
85
|
+
const relayIssuers = [];
|
|
86
|
+
for (const pkg of ['@gjsify/node-polyfills', '@gjsify/web-polyfills']) {
|
|
87
|
+
try {
|
|
88
|
+
relayIssuers.push(requireFromIssuer.resolve(`${pkg}/package.json`));
|
|
89
|
+
} catch {
|
|
90
|
+
// polyfills package not in dep tree — relay won't cover it
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// pnpapi is a virtual CJS module injected by Yarn PnP. `await import()` of a
|
|
95
|
+
// CJS module yields the ESM namespace `{ default, "module.exports" }`, NOT
|
|
96
|
+
// the exports object — so unconditional `.resolveRequest` access is
|
|
97
|
+
// `undefined`. Unwrap `.default` (the CJS exports) before use.
|
|
98
|
+
let pnpApi = null;
|
|
99
|
+
try {
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
101
|
+
// @ts-expect-error pnpapi has no npm package
|
|
102
|
+
const mod = await import('pnpapi');
|
|
103
|
+
pnpApi = mod.default ?? mod;
|
|
104
|
+
} catch {
|
|
105
|
+
// Not in a PnP runtime (shouldn't happen since findPnpRoot passed).
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
name: '@gjsify/pnp-relay',
|
|
110
|
+
async setup(build) {
|
|
111
|
+
const transformContents = transformContentsFactory
|
|
112
|
+
? transformContentsFactory(build)
|
|
113
|
+
: null;
|
|
114
|
+
|
|
115
|
+
const inner = pnpPlugin({
|
|
116
|
+
onResolve: async (args, { resolvedPath, error, watchFiles }) => {
|
|
117
|
+
if (resolvedPath !== null) {
|
|
118
|
+
return { namespace: 'pnp', path: resolvedPath, watchFiles };
|
|
119
|
+
}
|
|
120
|
+
if (error?.pnpCode === 'UNDECLARED_DEPENDENCY') {
|
|
121
|
+
if (pnpApi !== null) {
|
|
122
|
+
// Fast first-try: caller's own context.
|
|
123
|
+
try {
|
|
124
|
+
const rp = pnpApi.resolveRequest(args.path, issuerPath);
|
|
125
|
+
if (rp !== null) return { namespace: 'pnp', path: rp, watchFiles };
|
|
126
|
+
} catch { /* fall through to relay */ }
|
|
127
|
+
// Two-hop relay: resolve from {node,web}-polyfills context.
|
|
128
|
+
for (const relayIssuer of relayIssuers) {
|
|
129
|
+
try {
|
|
130
|
+
const rp = pnpApi.resolveRequest(args.path, relayIssuer);
|
|
131
|
+
if (rp !== null) return { namespace: 'pnp', path: rp, watchFiles };
|
|
132
|
+
} catch { /* try next issuer */ }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Fall through — bare aliases (`abort-controller`, `fetch/register/*`)
|
|
136
|
+
// are handled by the gjsify alias plugin after this returns null;
|
|
137
|
+
// the re-resolved `@gjsify/*` path then comes back through this hook.
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
external: true,
|
|
142
|
+
errors: error ? [{ text: error.message }] : [],
|
|
143
|
+
watchFiles,
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
onLoad: async (args) => {
|
|
147
|
+
// @yarnpkg/esbuild-plugin-pnp invokes the user-provided onLoad
|
|
148
|
+
// with just `args` (same shape as esbuild's standard onLoad —
|
|
149
|
+
// not the (args, ctx) tuple of its onResolve). args.path is
|
|
150
|
+
// already a resolved fs path; we only need to read it.
|
|
151
|
+
let contents;
|
|
152
|
+
try {
|
|
153
|
+
contents = await readFile(args.path, 'utf8');
|
|
154
|
+
} catch (readErr) {
|
|
155
|
+
return {
|
|
156
|
+
errors: [{ text: `gjsify pnp-relay: failed to read ${args.path}: ${readErr.message}` }],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (transformContents) {
|
|
161
|
+
const transformed = await transformContents(
|
|
162
|
+
{ path: args.path, namespace: 'pnp' },
|
|
163
|
+
contents,
|
|
164
|
+
);
|
|
165
|
+
if (transformed !== undefined) {
|
|
166
|
+
return transformed;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// `loader: 'default'` lets esbuild infer from the path extension.
|
|
171
|
+
return {
|
|
172
|
+
contents,
|
|
173
|
+
loader: 'default',
|
|
174
|
+
resolveDir: dirname(args.path),
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
await inner.setup(build);
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/resolve-npm",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Resolve NPM package aliases",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.mjs",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"default": "./lib/index.mjs"
|
|
13
13
|
},
|
|
14
14
|
"./globals-map": "./lib/globals-map.mjs",
|
|
15
|
+
"./pnp-relay": {
|
|
16
|
+
"types": "./lib/pnp-relay.d.ts",
|
|
17
|
+
"default": "./lib/pnp-relay.mjs"
|
|
18
|
+
},
|
|
15
19
|
"./package.json": "./package.json"
|
|
16
20
|
},
|
|
17
21
|
"scripts": {
|
|
@@ -25,5 +29,8 @@
|
|
|
25
29
|
"node",
|
|
26
30
|
"npm",
|
|
27
31
|
"alias"
|
|
28
|
-
]
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.15"
|
|
35
|
+
}
|
|
29
36
|
}
|