Skip to content

Commit 9d2ff32

Browse files
committed
fix(pnp) esm - support loaders importing named exports from commonjs
1 parent 75ff474 commit 9d2ff32

File tree

9 files changed

+229
-84
lines changed

9 files changed

+229
-84
lines changed

.pnp.cjs

Lines changed: 25 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.pnp.loader.mjs

Lines changed: 63 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.yarn/versions/3fde6039.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
releases:
2+
"@yarnpkg/cli": patch
3+
"@yarnpkg/plugin-pnp": patch
4+
"@yarnpkg/pnp": patch
5+
6+
declined:
7+
- "@yarnpkg/plugin-compat"
8+
- "@yarnpkg/plugin-constraints"
9+
- "@yarnpkg/plugin-dlx"
10+
- "@yarnpkg/plugin-essentials"
11+
- "@yarnpkg/plugin-init"
12+
- "@yarnpkg/plugin-interactive-tools"
13+
- "@yarnpkg/plugin-nm"
14+
- "@yarnpkg/plugin-npm-cli"
15+
- "@yarnpkg/plugin-pack"
16+
- "@yarnpkg/plugin-patch"
17+
- "@yarnpkg/plugin-pnpm"
18+
- "@yarnpkg/plugin-stage"
19+
- "@yarnpkg/plugin-typescript"
20+
- "@yarnpkg/plugin-version"
21+
- "@yarnpkg/plugin-workspace-tools"
22+
- "@yarnpkg/builder"
23+
- "@yarnpkg/core"
24+
- "@yarnpkg/doctor"
25+
- "@yarnpkg/nm"
26+
- "@yarnpkg/pnpify"
27+
- "@yarnpkg/sdks"

packages/acceptance-tests/pkg-tests-specs/sources/pnp-esm.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,34 @@ describe(`Plug'n'Play - ESM`, () => {
777777
),
778778
);
779779

780+
// Tests /packages/yarnpkg-pnp/sources/esm-loader/fspatch.ts
781+
it(
782+
`should support loaders importing named exports from commonjs files`,
783+
makeTemporaryEnv(
784+
{
785+
dependencies: {
786+
'no-deps-exports': `1.0.0`,
787+
},
788+
type: `module`,
789+
},
790+
async ({path, run, source}) => {
791+
await xfs.writeFilePromise(ppath.join(path, `loader.mjs`), `
792+
import {foo} from 'no-deps-exports';
793+
console.log(foo);
794+
`);
795+
await xfs.writeFilePromise(ppath.join(path, `index.js`), ``);
796+
797+
await expect(run(`install`)).resolves.toMatchObject({code: 0});
798+
799+
await expect(run(`node`, `--loader`, `./loader.mjs`, `./index.js`)).resolves.toMatchObject({
800+
code: 0,
801+
stdout: `42\n`,
802+
stderr: ``,
803+
});
804+
},
805+
),
806+
);
807+
780808
describe(`private import mappings`, () => {
781809
test(
782810
`it should support private import mappings`,

packages/yarnpkg-core/sources/worker-zip/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/yarnpkg-pnp/sources/esm-loader/built-loader.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/yarnpkg-pnp/sources/esm-loader/fspatch.ts

Lines changed: 80 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,61 +4,91 @@ import {HAS_LAZY_LOADED_TRANSLATORS} from './loaderFlags';
44

55
//#region ESM to CJS support
66
if (!HAS_LAZY_LOADED_TRANSLATORS) {
7-
/*
8-
In order to import CJS files from ESM Node does some translating
9-
internally[1]. This translator calls an unpatched `readFileSync`[2]
10-
which itself calls an internal `tryStatSync`[3] which calls
11-
`binding.fstat`[4]. A PR[5] has been made to use the monkey-patchable
12-
`fs.readFileSync` but assuming that wont be merged this region of code
13-
patches that final `binding.fstat` call.
14-
15-
1: https://github.com/nodejs/node/blob/d872aaf1cf20d5b6f56a699e2e3a64300e034269/lib/internal/modules/esm/translators.js#L177-L277
16-
2: https://github.com/nodejs/node/blob/d872aaf1cf20d5b6f56a699e2e3a64300e034269/lib/internal/modules/esm/translators.js#L240
17-
3: https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/fs.js#L452
18-
4: https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/fs.js#L403
19-
5: https://github.com/nodejs/node/pull/39513
20-
*/
21-
227
const binding = (process as any).binding(`fs`) as {
238
fstat: (fd: number, useBigint: false, req: any, ctx: object) => Float64Array;
9+
/**
10+
* Added in https://github.com/nodejs/node/pull/48658 / v20.5.0
11+
* Renamed in https://github.com/nodejs/node/pull/49593 / v20.8.0
12+
*/
13+
readFileSync?: (path: string, flag: number) => string;
14+
/**
15+
* Added in https://github.com/nodejs/node/pull/49593
16+
*/
17+
readFileUtf8?: (path: string, flag: number) => string;
2418
};
25-
const originalfstat = binding.fstat;
26-
27-
// Those values must be synced with packages/yarnpkg-fslib/sources/ZipOpenFS.ts
28-
const ZIP_MASK = 0xff000000;
29-
const ZIP_MAGIC = 0x2a000000;
3019

31-
binding.fstat = function(...args) {
32-
const [fd, useBigint, req] = args;
33-
if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === undefined) {
20+
const originalReadFile = binding.readFileUtf8 || binding.readFileSync;
21+
if (originalReadFile) {
22+
binding.readFileSync = binding.readFileUtf8 = function (...args) {
3423
try {
35-
const stats = fs.fstatSync(fd);
36-
// The reverse of this internal util
37-
// https://github.com/nodejs/node/blob/8886b63cf66c29d453fdc1ece2e489dace97ae9d/lib/internal/fs/utils.js#L542-L551
38-
return new Float64Array([
39-
stats.dev,
40-
stats.mode,
41-
stats.nlink,
42-
stats.uid,
43-
stats.gid,
44-
stats.rdev,
45-
stats.blksize,
46-
stats.ino,
47-
stats.size,
48-
stats.blocks,
49-
// atime sec
50-
// atime ns
51-
// mtime sec
52-
// mtime ns
53-
// ctime sec
54-
// ctime ns
55-
// birthtime sec
56-
// birthtime ns
57-
]);
58-
} catch {}
59-
}
24+
return fs.readFileSync(args[0], {
25+
encoding: `utf8`,
26+
// @ts-expect-error - The docs says it needs to be a string but
27+
// links to https://nodejs.org/dist/latest-v20.x/docs/api/fs.html#file-system-flags
28+
// which says it can be a number which matches the implementation.
29+
flag: args[1],
30+
});
31+
} catch { }
6032

61-
return originalfstat.apply(this, args);
62-
};
33+
return originalReadFile.apply(this, args);
34+
};
35+
} else {
36+
/*
37+
In order to import CJS files from ESM Node does some translating
38+
internally[1]. This translator calls an unpatched `readFileSync`[2]
39+
which itself calls an internal `tryStatSync`[3] which calls
40+
`binding.fstat`[4]. A PR[5] has been made to use the monkey-patchable
41+
`fs.readFileSync` but assuming that wont be merged this region of code
42+
patches that final `binding.fstat` call.
43+
44+
1: https://github.com/nodejs/node/blob/d872aaf1cf20d5b6f56a699e2e3a64300e034269/lib/internal/modules/esm/translators.js#L177-L277
45+
2: https://github.com/nodejs/node/blob/d872aaf1cf20d5b6f56a699e2e3a64300e034269/lib/internal/modules/esm/translators.js#L240
46+
3: https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/fs.js#L452
47+
4: https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/fs.js#L403
48+
5: https://github.com/nodejs/node/pull/39513
49+
*/
50+
51+
const binding = (process as any).binding(`fs`) as {
52+
fstat: (fd: number, useBigint: false, req: any, ctx: object) => Float64Array;
53+
};
54+
const originalfstat = binding.fstat;
55+
56+
// Those values must be synced with packages/yarnpkg-fslib/sources/ZipOpenFS.ts
57+
const ZIP_MASK = 0xff000000;
58+
const ZIP_MAGIC = 0x2a000000;
59+
60+
binding.fstat = function (...args) {
61+
const [fd, useBigint, req] = args;
62+
if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === undefined) {
63+
try {
64+
const stats = fs.fstatSync(fd);
65+
// The reverse of this internal util
66+
// https://github.com/nodejs/node/blob/8886b63cf66c29d453fdc1ece2e489dace97ae9d/lib/internal/fs/utils.js#L542-L551
67+
return new Float64Array([
68+
stats.dev,
69+
stats.mode,
70+
stats.nlink,
71+
stats.uid,
72+
stats.gid,
73+
stats.rdev,
74+
stats.blksize,
75+
stats.ino,
76+
stats.size,
77+
stats.blocks,
78+
// atime sec
79+
// atime ns
80+
// mtime sec
81+
// mtime ns
82+
// ctime sec
83+
// ctime ns
84+
// birthtime sec
85+
// birthtime ns
86+
]);
87+
} catch { }
88+
}
89+
90+
return originalfstat.apply(this, args);
91+
};
92+
}
6393
}
6494
//#endregion

packages/yarnpkg-pnp/sources/esm-loader/loaderFlags.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const [major, minor] = process.versions.node.split(`.`).map(value => parseInt(va
33
// The message switched to using an array in https://github.com/nodejs/node/pull/45348
44
export const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || (major === 19 && minor >= 2) || (major === 18 && minor >= 13);
55

6-
// https://github.com/nodejs/node/pull/45659 changed the internal translators to be lazy loaded
6+
// https://github.com/nodejs/node/pull/45659 changed the internal translators to be lazy loaded so they use our patch.
7+
// https://github.com/nodejs/node/pull/48842 changed it so that our patch is loaded after the internal translators.
78
// TODO: Update the version range if https://github.com/nodejs/node/pull/46425 lands.
8-
export const HAS_LAZY_LOADED_TRANSLATORS = major > 19 || (major === 19 && minor >= 3);
9+
export const HAS_LAZY_LOADED_TRANSLATORS = (major === 20 && minor < 6) || (major === 19 && minor >= 3);

packages/yarnpkg-pnp/sources/hook.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)