From b1308accc392370fcac131c3d4d1a862c0c9170f Mon Sep 17 00:00:00 2001 From: Noah Date: Wed, 10 Aug 2022 12:19:41 -0400 Subject: [PATCH] fix: pass yarn PnP experimental loader to worker if it exists (#103) Co-authored-by: JounQin --- .changeset/weak-yaks-remain.md | 5 + src/index.ts | 13 ++ test/fixtures/yarn-pnp/.pnp.loader.mjs | 273 +++++++++++++++++++++++++ test/fixtures/yarn-pnp/.yarnrc.yml | 2 +- test/utils.spec.ts | 11 + 5 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 .changeset/weak-yaks-remain.md create mode 100644 test/fixtures/yarn-pnp/.pnp.loader.mjs create mode 100644 test/utils.spec.ts diff --git a/.changeset/weak-yaks-remain.md b/.changeset/weak-yaks-remain.md new file mode 100644 index 000000000..3ee4e855e --- /dev/null +++ b/.changeset/weak-yaks-remain.md @@ -0,0 +1,5 @@ +--- +"synckit": patch +--- + +fix: pass yarn PnP experimental loader to worker if it exists diff --git a/src/index.ts b/src/index.ts index a13c57c37..db98e2a4c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import fs from 'node:fs' import { createRequire } from 'node:module' import path from 'node:path' import { pathToFileURL } from 'node:url' @@ -127,6 +128,14 @@ const cjsRequire = const dataUrl = (code: string) => new URL(`data:text/javascript,${encodeURIComponent(code)}`) +export const isFile = (path: string) => { + try { + return fs.statSync(path).isFile() + } catch { + return false + } +} + const setupTsRunner = ( workerPath: string, { execArgv, tsRunner }: { execArgv: string[]; tsRunner: TsRunner }, // eslint-disable-next-line sonarjs/cognitive-complexity @@ -223,6 +232,10 @@ const setupTsRunner = ( !execArgv.includes(pnpApiPath) ) { execArgv = ['-r', pnpApiPath, ...execArgv] + const pnpLoaderPath = path.resolve(pnpApiPath, '../.pnp.loader.mjs') + if (isFile(pnpLoaderPath)) { + execArgv = ['--experimental-loader', pnpLoaderPath, ...execArgv] + } } } diff --git a/test/fixtures/yarn-pnp/.pnp.loader.mjs b/test/fixtures/yarn-pnp/.pnp.loader.mjs new file mode 100644 index 000000000..26efb9746 --- /dev/null +++ b/test/fixtures/yarn-pnp/.pnp.loader.mjs @@ -0,0 +1,273 @@ +import { URL, fileURLToPath, pathToFileURL } from 'url'; +import fs from 'fs'; +import path from 'path'; +import moduleExports, { Module } from 'module'; + +var PathType; +(function(PathType2) { + PathType2[PathType2["File"] = 0] = "File"; + PathType2[PathType2["Portable"] = 1] = "Portable"; + PathType2[PathType2["Native"] = 2] = "Native"; +})(PathType || (PathType = {})); +const npath = Object.create(path); +const ppath = Object.create(path.posix); +npath.cwd = () => process.cwd(); +ppath.cwd = () => toPortablePath(process.cwd()); +ppath.resolve = (...segments) => { + if (segments.length > 0 && ppath.isAbsolute(segments[0])) { + return path.posix.resolve(...segments); + } else { + return path.posix.resolve(ppath.cwd(), ...segments); + } +}; +const contains = function(pathUtils, from, to) { + from = pathUtils.normalize(from); + to = pathUtils.normalize(to); + if (from === to) + return `.`; + if (!from.endsWith(pathUtils.sep)) + from = from + pathUtils.sep; + if (to.startsWith(from)) { + return to.slice(from.length); + } else { + return null; + } +}; +npath.fromPortablePath = fromPortablePath; +npath.toPortablePath = toPortablePath; +npath.contains = (from, to) => contains(npath, from, to); +ppath.contains = (from, to) => contains(ppath, from, to); +const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/; +const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/; +const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/; +const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/; +function fromPortablePath(p) { + if (process.platform !== `win32`) + return p; + let portablePathMatch, uncPortablePathMatch; + if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP)) + p = portablePathMatch[1]; + else if (uncPortablePathMatch = p.match(UNC_PORTABLE_PATH_REGEXP)) + p = `\\\\${uncPortablePathMatch[1] ? `.\\` : ``}${uncPortablePathMatch[2]}`; + else + return p; + return p.replace(/\//g, `\\`); +} +function toPortablePath(p) { + if (process.platform !== `win32`) + return p; + p = p.replace(/\\/g, `/`); + let windowsPathMatch, uncWindowsPathMatch; + if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP)) + p = `/${windowsPathMatch[1]}`; + else if (uncWindowsPathMatch = p.match(UNC_WINDOWS_PATH_REGEXP)) + p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`; + return p; +} + +const builtinModules = new Set(Module.builtinModules || Object.keys(process.binding(`natives`))); +const isBuiltinModule = (request) => request.startsWith(`node:`) || builtinModules.has(request); +function readPackageScope(checkPath) { + const rootSeparatorIndex = checkPath.indexOf(npath.sep); + let separatorIndex; + do { + separatorIndex = checkPath.lastIndexOf(npath.sep); + checkPath = checkPath.slice(0, separatorIndex); + if (checkPath.endsWith(`${npath.sep}node_modules`)) + return false; + const pjson = readPackage(checkPath + npath.sep); + if (pjson) { + return { + data: pjson, + path: checkPath + }; + } + } while (separatorIndex > rootSeparatorIndex); + return false; +} +function readPackage(requestPath) { + const jsonPath = npath.resolve(requestPath, `package.json`); + if (!fs.existsSync(jsonPath)) + return null; + return JSON.parse(fs.readFileSync(jsonPath, `utf8`)); +} + +async function tryReadFile(path2) { + try { + return await fs.promises.readFile(path2, `utf8`); + } catch (error) { + if (error.code === `ENOENT`) + return null; + throw error; + } +} +function tryParseURL(str, base) { + try { + return new URL(str, base); + } catch { + return null; + } +} +let entrypointPath = null; +function setEntrypointPath(file) { + entrypointPath = file; +} +function getFileFormat(filepath) { + var _a, _b; + const ext = path.extname(filepath); + switch (ext) { + case `.mjs`: { + return `module`; + } + case `.cjs`: { + return `commonjs`; + } + case `.wasm`: { + throw new Error(`Unknown file extension ".wasm" for ${filepath}`); + } + case `.json`: { + throw new Error(`Unknown file extension ".json" for ${filepath}`); + } + case `.js`: { + const pkg = readPackageScope(filepath); + if (!pkg) + return `commonjs`; + return (_a = pkg.data.type) != null ? _a : `commonjs`; + } + default: { + if (entrypointPath !== filepath) + return null; + const pkg = readPackageScope(filepath); + if (!pkg) + return `commonjs`; + if (pkg.data.type === `module`) + return null; + return (_b = pkg.data.type) != null ? _b : `commonjs`; + } + } +} + +async function getFormat$1(resolved, context, defaultGetFormat) { + const url = tryParseURL(resolved); + if ((url == null ? void 0 : url.protocol) !== `file:`) + return defaultGetFormat(resolved, context, defaultGetFormat); + const format = getFileFormat(fileURLToPath(url)); + if (format) { + return { + format + }; + } + return defaultGetFormat(resolved, context, defaultGetFormat); +} + +async function getSource$1(urlString, context, defaultGetSource) { + const url = tryParseURL(urlString); + if ((url == null ? void 0 : url.protocol) !== `file:`) + return defaultGetSource(urlString, context, defaultGetSource); + return { + source: await fs.promises.readFile(fileURLToPath(url), `utf8`) + }; +} + +async function load$1(urlString, context, nextLoad) { + const url = tryParseURL(urlString); + if ((url == null ? void 0 : url.protocol) !== `file:`) + return nextLoad(urlString, context, nextLoad); + const filePath = fileURLToPath(url); + const format = getFileFormat(filePath); + if (!format) + return nextLoad(urlString, context, nextLoad); + return { + format, + source: await fs.promises.readFile(filePath, `utf8`), + shortCircuit: true + }; +} + +const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:node:)?(?:@[^/]+\/)?[^/]+)\/*(.*|)$/; +const isRelativeRegexp = /^\.{0,2}\//; +async function resolve$1(originalSpecifier, context, nextResolve) { + var _a; + const {findPnpApi} = moduleExports; + if (!findPnpApi || isBuiltinModule(originalSpecifier)) + return nextResolve(originalSpecifier, context, nextResolve); + let specifier = originalSpecifier; + const url = tryParseURL(specifier, isRelativeRegexp.test(specifier) ? context.parentURL : void 0); + if (url) { + if (url.protocol !== `file:`) + return nextResolve(originalSpecifier, context, nextResolve); + specifier = fileURLToPath(url); + } + const {parentURL, conditions = []} = context; + const issuer = parentURL ? fileURLToPath(parentURL) : process.cwd(); + const pnpapi = (_a = findPnpApi(issuer)) != null ? _a : url ? findPnpApi(specifier) : null; + if (!pnpapi) + return nextResolve(originalSpecifier, context, nextResolve); + const dependencyNameMatch = specifier.match(pathRegExp); + let allowLegacyResolve = false; + if (dependencyNameMatch) { + const [, dependencyName, subPath] = dependencyNameMatch; + if (subPath === ``) { + const resolved = pnpapi.resolveToUnqualified(`${dependencyName}/package.json`, issuer); + if (resolved) { + const content = await tryReadFile(resolved); + if (content) { + const pkg = JSON.parse(content); + allowLegacyResolve = pkg.exports == null; + } + } + } + } + const result = pnpapi.resolveRequest(specifier, issuer, { + conditions: new Set(conditions), + extensions: allowLegacyResolve ? void 0 : [] + }); + if (!result) + throw new Error(`Resolving '${specifier}' from '${issuer}' failed`); + const resultURL = pathToFileURL(result); + if (url) { + resultURL.search = url.search; + resultURL.hash = url.hash; + } + if (!parentURL) + setEntrypointPath(fileURLToPath(resultURL)); + return { + url: resultURL.href, + shortCircuit: true + }; +} + +const binding = process.binding(`fs`); +const originalfstat = binding.fstat; +const ZIP_FD = 2147483648; +binding.fstat = function(...args) { + const [fd, useBigint, req] = args; + if ((fd & ZIP_FD) !== 0 && useBigint === false && req === void 0) { + try { + const stats = fs.fstatSync(fd); + return new Float64Array([ + stats.dev, + stats.mode, + stats.nlink, + stats.uid, + stats.gid, + stats.rdev, + stats.blksize, + stats.ino, + stats.size, + stats.blocks + ]); + } catch { + } + } + return originalfstat.apply(this, args); +}; + +const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10)); +const hasConsolidatedHooks = major > 16 || major === 16 && minor >= 12; +const resolve = resolve$1; +const getFormat = hasConsolidatedHooks ? void 0 : getFormat$1; +const getSource = hasConsolidatedHooks ? void 0 : getSource$1; +const load = hasConsolidatedHooks ? load$1 : void 0; + +export { getFormat, getSource, load, resolve }; diff --git a/test/fixtures/yarn-pnp/.yarnrc.yml b/test/fixtures/yarn-pnp/.yarnrc.yml index 2d9d8af74..b9ede862b 100644 --- a/test/fixtures/yarn-pnp/.yarnrc.yml +++ b/test/fixtures/yarn-pnp/.yarnrc.yml @@ -1,3 +1,3 @@ nodeLinker: pnp - +pnpEnableEsmLoader: true yarnPath: .yarn/releases/yarn-3.2.2.cjs diff --git a/test/utils.spec.ts b/test/utils.spec.ts new file mode 100644 index 000000000..c75afba95 --- /dev/null +++ b/test/utils.spec.ts @@ -0,0 +1,11 @@ +import { fileURLToPath } from 'node:url' + +import { _dirname } from './helpers' + +import { isFile } from 'synckit' + +test('utils', () => { + expect(isFile(_dirname)).toBe(false) + expect(isFile('non-existed')).toBe(false) + expect(isFile(fileURLToPath(import.meta.url))).toBe(true) +})