"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolvePackageEntry = exports.resolvePackageData = exports.tryNodeResolve = exports.resolvePlugin = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk"));
const constants_1 = require("../constants");
const utils_1 = require("../utils");
const slash_1 = __importDefault(require("slash"));
const pluginutils_1 = require("@rollup/pluginutils");
const isbuiltin_1 = __importDefault(require("isbuiltin"));
const css_1 = require("./css");
const mainFields = ['module', 'jsnext', 'jsnext:main', 'main'];
const isDebug = process.env.DEBUG;
const debug = utils_1.createDebugger('vite:resolve-details', {
    onlyWhenFocused: true
});
function resolvePlugin({ root, isBuild, asSrc, dedupe }) {
    let config;
    let server;
    return {
        name: 'vite:resolve',
        configureServer(_server) {
            server = _server;
        },
        configResolved(_config) {
            config = _config;
        },
        resolveId(id, importer) {
            let res;
            // fast path for commonjs proxy modules
            if (/\?commonjs/.test(id) || id === 'commonjsHelpers.js') {
                return;
            }
            // explicit fs paths that starts with /@fs/*
            if (asSrc && id.startsWith(constants_1.FS_PREFIX)) {
                const fsPath = utils_1.fsPathFromId(id);
                res = tryFsResolve(fsPath, false);
                isDebug && debug(`[@fs] ${chalk_1.default.cyan(id)} -> ${chalk_1.default.dim(res)}`);
                // always return here even if res doesn't exist since /@fs/ is explicit
                // if the file doesn't exist it should be a 404
                return res || fsPath;
            }
            // URL
            // /foo -> /fs-root/foo
            if (asSrc && id.startsWith('/')) {
                const fsPath = path_1.default.resolve(root, id.slice(1));
                if ((res = tryFsResolve(fsPath))) {
                    isDebug && debug(`[url] ${chalk_1.default.cyan(id)} -> ${chalk_1.default.dim(res)}`);
                    return res;
                }
            }
            // relative
            if (id.startsWith('.')) {
                const basedir = importer ? path_1.default.dirname(importer) : process.cwd();
                let fsPath = path_1.default.resolve(basedir, id);
                // handle browser field mapping for relative imports
                const pkg = importer && idToPkgMap.get(importer);
                if (pkg && utils_1.isObject(pkg.data.browser)) {
                    const pkgRealtivePath = './' + slash_1.default(path_1.default.relative(pkg.dir, fsPath));
                    fsPath = path_1.default.resolve(pkg.dir, mapWithBrowserField(pkgRealtivePath, pkg.data.browser));
                }
                if ((res = tryFsResolve(fsPath))) {
                    isDebug && debug(`[relative] ${chalk_1.default.cyan(id)} -> ${chalk_1.default.dim(res)}`);
                    if (pkg) {
                        idToPkgMap.set(res, pkg);
                    }
                    return res;
                }
            }
            // absolute fs paths
            if (path_1.default.isAbsolute(id) && (res = tryFsResolve(id))) {
                isDebug && debug(`[fs] ${chalk_1.default.cyan(id)} -> ${chalk_1.default.dim(res)}`);
                return res;
            }
            // external
            if (utils_1.isExternalUrl(id) || utils_1.isDataUrl(id)) {
                return {
                    id,
                    external: true
                };
            }
            // bare package imports, perform node resolve
            if (utils_1.bareImportRE.test(id)) {
                // externalize node built-ins only when building for ssr
                if (isBuild && config && config.build.ssr && isbuiltin_1.default(id)) {
                    return {
                        id,
                        external: true
                    };
                }
                if (asSrc && server && (res = tryOptimizedResolve(id, server))) {
                    return res;
                }
                if ((res = tryNodeResolve(id, importer ? path_1.default.dirname(importer) : root, isBuild, dedupe, root, server))) {
                    return res;
                }
            }
            isDebug && debug(`[fallthrough] ${chalk_1.default.dim(id)}`);
        }
    };
}
exports.resolvePlugin = resolvePlugin;
function tryFsResolve(fsPath, tryIndex = true) {
    const [file, q] = fsPath.split(`?`, 2);
    const query = q ? `?${q}` : ``;
    let res;
    if ((res = tryResolveFile(file, query, tryIndex))) {
        return res;
    }
    for (const ext of constants_1.SUPPORTED_EXTS) {
        if ((res = tryResolveFile(file + ext, query, tryIndex))) {
            return res;
        }
    }
}
function tryResolveFile(file, query, tryIndex) {
    if (fs_1.default.existsSync(file)) {
        const isDir = fs_1.default.statSync(file).isDirectory();
        if (isDir) {
            if (tryIndex) {
                const index = tryFsResolve(file + '/index', false);
                if (index)
                    return utils_1.normalizePath(index) + query;
            }
            const pkgPath = file + '/package.json';
            if (fs_1.default.existsSync(pkgPath)) {
                // path points to a node package
                const pkg = loadPackageData(pkgPath);
                return resolvePackageEntry(file, pkg);
            }
        }
        else {
            return utils_1.normalizePath(file) + query;
        }
    }
}
const idToPkgMap = new Map();
function tryNodeResolve(id, basedir, isBuild = true, dedupe, dedupeRoot, server) {
    const deepMatch = id.match(utils_1.deepImportRE);
    const pkgId = deepMatch ? deepMatch[1] || deepMatch[2] : id;
    if (dedupe && dedupeRoot && dedupe.includes(pkgId)) {
        basedir = dedupeRoot;
    }
    const pkg = resolvePackageData(pkgId, basedir);
    if (!pkg) {
        return;
    }
    // prevent deep imports to optimized deps.
    if (deepMatch &&
        server &&
        server.optimizeDepsMetadata &&
        pkg.data.name in server.optimizeDepsMetadata.map &&
        !css_1.isCSSRequest(id)) {
        throw new Error(chalk_1.default.yellow(`Deep import "${chalk_1.default.cyan(id)}" should be avoided because dependency "${chalk_1.default.cyan(pkg.data.name)}" has been pre-optimized. Prefer importing directly from the module entry:\n\n` +
            `${chalk_1.default.green(`import { ... } from "${pkg.data.name}"`)}\n\n` +
            `If the used import is not exported from the package's main entry ` +
            `and can only be attained via deep import, you can explicitly add ` +
            `the deep import path to "optimizeDeps.include" in vite.config.js.`));
    }
    let resolved = deepMatch
        ? resolveDeepImport(id, pkg)
        : resolvePackageEntry(id, pkg);
    if (!resolved) {
        return;
    }
    // link id to pkg for browser field mapping check
    idToPkgMap.set(resolved, pkg);
    if (isBuild) {
        // Resolve package side effects for build so that rollup can better
        // perform tree-shaking
        return {
            id: resolved,
            moduleSideEffects: pkg.hasSideEffects(resolved)
        };
    }
    else {
        // During serve, inject a version query to npm deps so that the browser
        // can cache it without revalidation. Make sure to apply this only to
        // files actually inside node_modules so that locally linked packages
        // in monorepos are not cached this way.
        if (resolved.includes('node_modules')) {
            resolved = utils_1.injectQuery(resolved, `v=${pkg.data.version}`);
        }
        return { id: resolved };
    }
}
exports.tryNodeResolve = tryNodeResolve;
function tryOptimizedResolve(rawId, server) {
    const cacheDir = server.config.optimizeCacheDir;
    const depData = server.optimizeDepsMetadata;
    if (cacheDir && depData) {
        const [id, q] = rawId.split(`?`, 2);
        const query = q ? `?${q}` : ``;
        const filePath = depData.map[id];
        if (filePath) {
            return utils_1.normalizePath(path_1.default.resolve(cacheDir, filePath)) + query;
        }
    }
}
const packageCache = new Map();
function resolvePackageData(id, basedir) {
    const cacheKey = id + basedir;
    if (packageCache.has(cacheKey)) {
        return packageCache.get(cacheKey);
    }
    try {
        const pkgPath = utils_1.resolveFrom(`${id}/package.json`, basedir);
        return loadPackageData(pkgPath, cacheKey);
    }
    catch (e) {
        isDebug && debug(`${chalk_1.default.red(`[failed loading package.json]`)} ${id}`);
    }
}
exports.resolvePackageData = resolvePackageData;
function loadPackageData(pkgPath, cacheKey = pkgPath) {
    const data = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
    const pkgDir = path_1.default.dirname(pkgPath);
    const { sideEffects } = data;
    let hasSideEffects;
    if (typeof sideEffects === 'boolean') {
        hasSideEffects = () => sideEffects;
    }
    else if (Array.isArray(sideEffects)) {
        hasSideEffects = pluginutils_1.createFilter(sideEffects, null, { resolve: pkgDir });
    }
    else {
        hasSideEffects = () => true;
    }
    const pkg = {
        dir: pkgDir,
        data,
        hasSideEffects
    };
    packageCache.set(cacheKey, pkg);
    return pkg;
}
function resolvePackageEntry(id, { dir, data }) {
    let entryPoint;
    // check browser field first with highest priority
    const browserEntry = typeof data.browser === 'string'
        ? data.browser
        : utils_1.isObject(data.browser) && data.browser['.'];
    if (browserEntry) {
        entryPoint = browserEntry;
    }
    if (!entryPoint) {
        // resolve exports field
        // https://nodejs.org/api/packages.html#packages_package_entry_points
        const { exports: exportsField } = data;
        if (exportsField) {
            if (typeof exportsField === 'string') {
                entryPoint = exportsField;
            }
            else if (Array.isArray(exportsField)) {
                entryPoint = exportsField[0];
            }
            else if (utils_1.isObject(exportsField)) {
                if ('.' in exportsField) {
                    entryPoint = resolveConditionalExports(exportsField['.']);
                }
                else {
                    entryPoint = resolveConditionalExports(exportsField);
                }
            }
        }
    }
    if (!entryPoint) {
        for (const field of mainFields) {
            if (typeof data[field] === 'string') {
                entryPoint = data[field];
                break;
            }
        }
    }
    entryPoint = entryPoint || 'index.js';
    // resolve object browser field in package.json
    // https://github.com/defunctzombie/package-browser-field-spec
    const { browser: browserField } = data;
    if (utils_1.isObject(browserField)) {
        entryPoint = mapWithBrowserField(entryPoint, browserField);
    }
    entryPoint = path_1.default.resolve(dir, entryPoint);
    const resolvedEntryPont = tryFsResolve(entryPoint);
    if (resolvedEntryPont) {
        isDebug &&
            debug(`[package entry] ${chalk_1.default.cyan(id)} -> ${chalk_1.default.dim(resolvedEntryPont)}`);
        return resolvedEntryPont;
    }
    else {
        throw new Error(`Failed to resolve entry for package "${id}". ` +
            `The package may have incorrect main/module/exports specified in its package.json.`);
    }
}
exports.resolvePackageEntry = resolvePackageEntry;
function resolveDeepImport(id, { dir, data }) {
    let relativeId = '.' + id.slice(data.name.length);
    const { exports: exportsField, browser: browserField } = data;
    // map relative based on exports data
    if (exportsField) {
        if (utils_1.isObject(exportsField) &&
            !Array.isArray(exportsField) &&
            relativeId in exportsField) {
            relativeId = resolveConditionalExports(exportsField[relativeId]);
        }
        else {
            throw new Error(`Package subpath '${relativeId}' is not defined by "exports" in ` +
                `${path_1.default.join(dir, 'package.json')}.`);
        }
    }
    else if (utils_1.isObject(browserField)) {
        relativeId = mapWithBrowserField(relativeId, browserField);
    }
    if (relativeId) {
        const resolved = tryFsResolve(path_1.default.resolve(dir, relativeId), !exportsField);
        if (resolved) {
            isDebug &&
                debug(`[node/deep-import] ${chalk_1.default.cyan(id)} -> ${chalk_1.default.dim(resolved)}`);
            return resolved;
        }
    }
}
function resolveConditionalExports(exp) {
    if (typeof exp === 'string') {
        return exp;
    }
    else if (utils_1.isObject(exp)) {
        if (typeof exp.browser === 'string') {
            return exp.browser;
        }
        else if (typeof exp.import === 'string') {
            return exp.import;
        }
        else if (typeof exp.default === 'string') {
            return exp.default;
        }
    }
    else if (Array.isArray(exp)) {
        for (let i = 0; i < exp.length; i++) {
            const res = resolveConditionalExports(exp[i]);
            if (res)
                return res;
        }
    }
}
/**
 * given a relative path in pkg dir,
 * return a relative path in pkg dir,
 * mapped with the "map" object
 */
function mapWithBrowserField(relativePathInPkgDir, map) {
    const normalized = normalize(relativePathInPkgDir);
    const foundEntry = Object.entries(map).find(([from]) => normalize(from) === normalized);
    if (!foundEntry) {
        return relativePathInPkgDir;
    }
    return foundEntry[1];
}
function normalize(file) {
    return path_1.default.posix.normalize(path_1.default.extname(file) ? file : file + '.js');
}
//# sourceMappingURL=resolve.js.map