import { URL } from 'url'; import * as PropertySymbol from '../PropertySymbol.js'; import WindowBrowserContext from '../window/WindowBrowserContext.js'; import Path from 'path'; import FS from 'fs'; const MAIN_FIELDS = ['module', 'main']; const EXTENSIONS = ['.js', '.mjs']; /** * Module URL utility. */ export default class ModuleURLUtility { static nodeModuleResolveCache = new Map(); static packageJsonCache = new Map(); /** * Returns module URL based on parent URL and the import map. * * @param window Window. * @param parentURL Parent URL. * @param url Module URL. */ static getURL(window, parentURL, url) { const parentURLString = typeof parentURL === 'string' ? parentURL : parentURL.href; const importMap = window[PropertySymbol.moduleImportMap]; const resolved = this.resolveURL(window, parentURLString, url); if (!importMap) { return new URL(resolved, parentURLString); } if (importMap.scopes.length) { for (const scope of importMap.scopes) { if (parentURLString.includes(scope.scope)) { for (const rule of scope.rules) { if (resolved.startsWith(rule.from)) { return new URL(rule.to + resolved.replace(rule.from, ''), parentURLString); } } } } } if (importMap.imports.length) { for (const rule of importMap.imports) { if (resolved.startsWith(rule.from)) { return new URL(rule.to + resolved.replace(rule.from, ''), parentURLString); } } } return new URL(resolved, parentURLString); } /** * Clears the internal caches. */ static clearCache() { this.nodeModuleResolveCache.clear(); this.packageJsonCache.clear(); } /** * Resolves the module URL using the settings. * * @param window Window. * @param parentURL Parent URL. * @param url Module URL. */ static resolveURL(window, parentURL, url) { const settings = new WindowBrowserContext(window).getSettings(); if (!settings) { return url; } let resolvedURL = url; if (settings.module.resolveNodeModules) { resolvedURL = this.resolveNodeModuleURL(settings.module.resolveNodeModules, url); } if (settings.module.urlResolver) { resolvedURL = settings.module.urlResolver({ url: resolvedURL, parentURL, window }); } return resolvedURL; } /** * Resolves node module URL. * * @param resolveNodeModules Settings for resolving node modules. * @param url Module URL. */ static resolveNodeModuleURL(resolveNodeModules, url) { if (url[0] === '.' || url[0] === '/' || url.startsWith('http://') || url.startsWith('https://')) { return url; } const slash = resolveNodeModules.url[resolveNodeModules.url.length - 1] === '/' ? '' : '/'; const baseURL = `${resolveNodeModules.url}${slash}`; const nodeModulesDirectory = Path.resolve(resolveNodeModules.directory); const parts = url.split('/'); const cached = this.nodeModuleResolveCache.get(url); if (cached) { return cached; } const packageName = url[0] === '@' ? `${parts[0]}/${parts[1]}` : parts[0]; let packageJson = this.packageJsonCache.get(packageName); if (!packageJson) { try { packageJson = JSON.parse(FS.readFileSync(Path.join(nodeModulesDirectory, packageName, 'package.json'), 'utf-8')); this.packageJsonCache.set(packageName, packageJson); } catch { return url; } } if ((url[0] === '@' && parts.length === 2) || parts.length === 1) { if (packageJson.exports?.['.']?.import) { const resolvedURL = `${baseURL}${packageName}/${packageJson.exports['.'].import.replace('./', '')}`; this.nodeModuleResolveCache.set(url, resolvedURL); return resolvedURL; } const mainFields = resolveNodeModules.mainFields || MAIN_FIELDS; for (const field of mainFields) { if (packageJson[field] && FS.existsSync(Path.join(nodeModulesDirectory, packageName, packageJson[field]))) { const resolvedURL = `${baseURL}${url}/${packageJson[field]}`; this.nodeModuleResolveCache.set(url, resolvedURL); return resolvedURL; } } return url; } const subPath = parts.slice(url[0] === '@' ? 2 : 1).join('/'); if (packageJson.exports) { for (const key of Object.keys(packageJson.exports)) { const importEntry = Array.isArray(packageJson.exports[key].import) ? packageJson.exports[key].import[0] : packageJson.exports[key].import; if (importEntry) { const regExp = new RegExp(`^${key.replace('./', '').replace(/\./g, '\\.').replace(/\*/g, '(.*)')}$`); const match = subPath.match(regExp); if (match) { const resolvedSubPath = importEntry.replace('./', '').replace('*', match[1]); const resolvedURL = `${baseURL}${packageName}/${resolvedSubPath}`; this.nodeModuleResolveCache.set(url, resolvedURL); return resolvedURL; } } } } if (!url.endsWith('.js') && !url.endsWith('.mjs')) { for (const ext of EXTENSIONS) { if (FS.existsSync(Path.join(nodeModulesDirectory, `${url}${ext}`))) { const resolvedURL = `${baseURL}${url}${ext}`; this.nodeModuleResolveCache.set(url, resolvedURL); return resolvedURL; } } } else { if (FS.existsSync(Path.join(nodeModulesDirectory, url))) { const resolvedURL = `${baseURL}${url}`; this.nodeModuleResolveCache.set(url, resolvedURL); return resolvedURL; } } return url; } } //# sourceMappingURL=ModuleURLUtility.js.map