UNPKG

telefunc

Version:

Remote functions. Instead of API.

208 lines (207 loc) 8.92 kB
export { requireResolveOptional }; export { requireResolveOptionalDir }; export { requireResolveNpmPackage }; export { requireResolveDistFile }; export { getPackageNodeModulesDirectory }; import { assert } from './assert.js'; import { assertIsNotBrowser } from './assertIsNotBrowser.js'; import { assertPosixPath, toPosixPath } from './path.js'; import { scriptFileExtensionList } from './isScriptFile.js'; import { createRequire } from 'node:module'; import path from 'node:path'; import { assertIsImportPathNpmPackage, isImportPathNpmPackageOrPathAlias } from './parseNpmPackage.js'; import { isNotNullish } from './isNullish.js'; import { createDebugger } from './debug.js'; // @ts-ignore import.meta.url is shimmed at dist/cjs by dist-cjs-fixup.js. const importMetaUrl = import.meta.url; assertPosixPath(importMetaUrl); assertIsNotBrowser(); const debug = createDebugger('telefunc:resolve'); // - We still can't use import.meta.resolve() as of 23.1.0 (November 2024) because `parent` argument requires an experimental flag. // - https://stackoverflow.com/questions/54977743/do-require-resolve-for-es-modules#comment139581675_62272600 // - Passing context to createRequire(context) isn't equivalent to passing it to the `paths` argument of require.resolve() // - https://github.com/brillout/require-test // - In practice, I guess it doesn't make a difference? It just seems to be small Node.js weirdness. // - The argument createRequire(argument) seems to be overridden by the `paths` argument require.resolve() // - For example, passing an empty array to `paths` kills the argument passed to `createRequire()` // - Thus, when `paths` is defined, then the context needs to be passed to both createRequire() as well as the `paths` argument of require.resolve() function requireResolve_(importPath, importerFilePath, userRootDir, doNotHandleFileExtension = false) { assertPosixPath(importPath); const contexts = importerFilePath ? [importerFilePath] : [userRootDir ? getFakeImporterFile(userRootDir) : importMetaUrl]; addExtraContextForNpmPackageImport(contexts, importPath, userRootDir); let importPathResolvedFilePath; let failure; for (const context of contexts) { assertPosixPath(context); const contextNode = makeNodeFriendly(ensureFilePrefix(context)); let importPathNode = makeNodeFriendly(importPath); const require_ = createRequire(contextNode); if (!doNotHandleFileExtension) { addFileExtensionsToRequireResolve(require_); importPathNode = removeFileExtension(importPathNode); } try { importPathResolvedFilePath = require_.resolve(importPathNode); } catch (err) { if (debug.isActivated) { const stack = new Error().stack; debug('ERROR', { err, importPath, context }, stack); } failure !== null && failure !== void 0 ? failure : (failure = { err }); } if (importPathResolvedFilePath) break; } if (!importPathResolvedFilePath) { assert(failure); if (debug.isActivated) { debug('FAILURE', { importPath, importerFilePath, userRootDir, doNotHandleFileExtension, importMetaUrl, contexts, }); } return { importPathResolvedFilePath: undefined, err: failure.err, hasFailed: true }; } else { if (failure && debug.isActivated) { debug('SUCCESS', { importPath, contexts, }); } assert(importPathResolvedFilePath); importPathResolvedFilePath = toPosixPath(importPathResolvedFilePath); return { importPathResolvedFilePath, err: undefined, hasFailed: false }; } } function requireResolveOptional({ importPath, importerFilePath, userRootDir, }) { const res = requireResolve_(importPath, importerFilePath, userRootDir); if (res.hasFailed) return null; return res.importPathResolvedFilePath; } function requireResolveOptionalDir({ importPath, importerDir, userRootDir, }) { const importerFilePath = getFakeImporterFile(importerDir); const res = requireResolve_(importPath, importerFilePath, userRootDir); if (res.hasFailed) return null; return res.importPathResolvedFilePath; } function requireResolveNpmPackage({ importPathNpmPackage, userRootDir, }) { assertIsImportPathNpmPackage(importPathNpmPackage); const importerFilePath = getFakeImporterFile(userRootDir); const res = requireResolve_(importPathNpmPackage, importerFilePath, userRootDir); if (res.hasFailed) throw res.err; return res.importPathResolvedFilePath; } function requireResolveDistFile(distFile) { const packageNodeModulesDirectory = getPackageNodeModulesDirectory(); assertPosixPath(packageNodeModulesDirectory); assertPosixPath(distFile); const importPathResolvedFilePath = makeNodeFriendly(path.posix.join(packageNodeModulesDirectory, distFile)); // Double check { const res = requireResolve_(importPathResolvedFilePath, // No context needed: importPathResolvedFilePath is already resolved and absolute null, null, true); if (res.hasFailed) throw res.err; assert(res.importPathResolvedFilePath === importPathResolvedFilePath); } return importPathResolvedFilePath; } function addExtraContextForNpmPackageImport(contexts, importPath, userRootDir) { // We should add extra context only for npm packages, but unfortunately we cannot always disambiguate between npm package imports and path aliases. if (!isImportPathNpmPackageOrPathAlias(importPath)) return; const userRootDirFakeFile = userRootDir && getFakeImporterFile(userRootDir); [ // Workaround for monorepo resolve issue: https://github.com/vikejs/vike-react/pull/161/commits/dbaa6643e78015ac2797c237552800fef29b72a7 userRootDirFakeFile, // I can't think of a use case where this would be needed, but let's add one extra last chance to successfully resolve some complex monorepo setups importMetaUrl, ] .filter(isNotNullish) .forEach((context) => { const alreadyHasContext = contexts.includes(context) || contexts.includes(ensureFilePrefix(context)); if (alreadyHasContext) return; contexts.push(context); }); } function removeFileExtension(importPath) { // Skip for Bun: https://github.com/vikejs/vike/issues/2204 //@ts-ignore if (typeof Bun !== 'undefined') { // https://bun.sh/guides/util/detect-bun assert(process.versions.bun); return importPath; } for (const ext of scriptFileExtensionList) { const suffix = `.${ext}`; if (importPath.endsWith(suffix)) { return importPath.slice(0, -1 * suffix.length); } } return importPath; } function addFileExtensionsToRequireResolve(require_) { const added = []; scriptFileExtensionList.forEach((ext) => { assert(!ext.includes('.')); ext = `.${ext}`; if (!require_.extensions[ext]) { require_.extensions[ext] = require_.extensions['.js']; added.push(ext); } }); } function getPackageNodeModulesDirectory() { // [RELATIVE_PATH_FROM_DIST] Current file: node_modules/${packageName}/dist/utils/requireResolve.js assert(importMetaUrl.includes('/dist/utils/')); const packageNodeModulesDirectory = path.posix.join(removeFilePrefix(path.dirname(importMetaUrl)), '../../'); // Return `node_modules/${packageName}/` return packageNodeModulesDirectory; } function getFakeImporterFile(dirPath) { assertPosixPath(dirPath); assert(!dirPath.startsWith('file')); // The file:// prefix is bogus when used with path.posix.join() const importerFilePath = path.posix.join(dirPath, 'fakeFileForNodeResolve.js'); return importerFilePath; } function ensureFilePrefix(filePath) { assertPosixPath(filePath); const filePrefix = getFilePrefix(); if (!filePath.startsWith(filePrefix)) { assert(!filePath.startsWith('file')); filePath = filePrefix + filePath; } assert(filePath.startsWith(filePrefix)); return filePath; } function removeFilePrefix(filePath) { const filePrefix = getFilePrefix(); if (filePath.startsWith(filePrefix)) { filePath = filePath.slice(filePrefix.length); } assert(!filePath.startsWith('file')); return filePath; } function getFilePrefix() { let prefix = 'file://'; if (process.platform === 'win32') prefix += '/'; return prefix; } function makeNodeFriendly(filePath) { // https://github.com/vikejs/vike/issues/2436#issuecomment-2849145340 return decodeURIComponent(filePath); }