"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveNodeModuleFile = exports.resolveNodeModule = exports.resolveOptimizedModule = exports.resolveBareModuleRequest = exports.jsSrcRE = exports.createResolver = exports.mainFields = exports.supportedExts = void 0;
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const slash_1 = __importDefault(require("slash"));
const utils_1 = require("./utils");
const serverPluginModuleResolve_1 = require("./server/serverPluginModuleResolve");
const optimizer_1 = require("./optimizer");
const serverPluginClient_1 = require("./server/serverPluginClient");
const cssUtils_1 = require("./utils/cssUtils");
const pathUtils_1 = require("./utils/pathUtils");
const chalk_1 = __importDefault(require("chalk"));
const debug = require('debug')('vite:resolve');
const isWin = require('os').platform() === 'win32';
const pathSeparator = isWin ? '\\' : '/';
exports.supportedExts = ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'];
exports.mainFields = ['module', 'jsnext', 'jsnext:main', 'browser', 'main'];
const defaultRequestToFile = (publicPath, root) => {
    if (serverPluginModuleResolve_1.moduleRE.test(publicPath)) {
        const id = publicPath.replace(serverPluginModuleResolve_1.moduleRE, '');
        const cachedNodeModule = serverPluginModuleResolve_1.moduleIdToFileMap.get(id);
        if (cachedNodeModule) {
            return cachedNodeModule;
        }
        // try to resolve from optimized modules
        const optimizedModule = resolveOptimizedModule(root, id);
        if (optimizedModule) {
            return optimizedModule;
        }
        // try to resolve from normal node_modules
        const nodeModule = resolveNodeModuleFile(root, id);
        if (nodeModule) {
            serverPluginModuleResolve_1.moduleIdToFileMap.set(id, nodeModule);
            return nodeModule;
        }
    }
    const publicDirPath = path_1.default.join(root, 'public', publicPath.slice(1));
    if (fs_extra_1.default.existsSync(publicDirPath)) {
        return publicDirPath;
    }
    return path_1.default.join(root, publicPath.slice(1));
};
const defaultFileToRequest = (filePath, root) => serverPluginModuleResolve_1.moduleFileToIdMap.get(filePath) ||
    '/' + slash_1.default(path_1.default.relative(root, filePath)).replace(/^public\//, '');
const isFile = (file) => {
    try {
        return fs_extra_1.default.statSync(file).isFile();
    }
    catch (e) {
        return false;
    }
};
/**
 * this function resolve fuzzy file path. examples:
 * /path/file is a fuzzy file path for /path/file.tsx
 * /path/dir is a fuzzy file path for /path/dir/index.js
 *
 * returning undefined indicates the filePath is not fuzzy:
 * it is already an exact file path, or it can't match any file
 */
const resolveFilePathPostfix = (filePath) => {
    const cleanPath = utils_1.cleanUrl(filePath);
    if (!isFile(cleanPath)) {
        let postfix = '';
        for (const ext of exports.supportedExts) {
            if (isFile(cleanPath + ext)) {
                postfix = ext;
                break;
            }
            const defaultFilePath = `/index${ext}`;
            if (isFile(path_1.default.join(cleanPath, defaultFilePath))) {
                postfix = defaultFilePath;
                break;
            }
        }
        const queryMatch = filePath.match(/\?.*$/);
        const query = queryMatch ? queryMatch[0] : '';
        const resolved = cleanPath + postfix + query;
        if (resolved !== filePath) {
            debug(`(postfix) ${filePath} -> ${resolved}`);
            return postfix;
        }
    }
};
const isDir = (p) => fs_extra_1.default.existsSync(p) && fs_extra_1.default.statSync(p).isDirectory();
function createResolver(root, resolvers = [], userAlias = {}, assetsInclude) {
    resolvers = [...resolvers];
    const literalAlias = {};
    const literalDirAlias = {};
    const resolveAlias = (alias) => {
        for (const key in alias) {
            let target = alias[key];
            // aliasing a directory
            if (key.startsWith('/') && key.endsWith('/') && path_1.default.isAbsolute(target)) {
                // check first if this is aliasing to a path from root
                const fromRoot = path_1.default.join(root, target);
                if (isDir(fromRoot)) {
                    target = fromRoot;
                }
                else if (!isDir(target)) {
                    continue;
                }
                resolvers.push({
                    requestToFile(publicPath) {
                        if (publicPath.startsWith(key)) {
                            return path_1.default.join(target, publicPath.slice(key.length));
                        }
                    },
                    fileToRequest(filePath) {
                        if (filePath.startsWith(target + pathSeparator)) {
                            return slash_1.default(key + path_1.default.relative(target, filePath));
                        }
                    }
                });
                literalDirAlias[key] = target;
            }
            else {
                literalAlias[key] = target;
            }
        }
    };
    resolvers.forEach(({ alias }) => {
        if (alias && typeof alias === 'object') {
            resolveAlias(alias);
        }
    });
    resolveAlias(userAlias);
    const requestToFileCache = new Map();
    const fileToRequestCache = new Map();
    const resolver = {
        requestToFile(publicPath) {
            publicPath = decodeURIComponent(publicPath);
            if (requestToFileCache.has(publicPath)) {
                return requestToFileCache.get(publicPath);
            }
            let resolved;
            for (const r of resolvers) {
                const filepath = r.requestToFile && r.requestToFile(publicPath, root);
                if (filepath) {
                    resolved = filepath;
                    break;
                }
            }
            if (!resolved) {
                resolved = defaultRequestToFile(publicPath, root);
            }
            const postfix = resolveFilePathPostfix(resolved);
            if (postfix) {
                if (postfix[0] === '/') {
                    resolved = path_1.default.join(resolved, postfix);
                }
                else {
                    resolved += postfix;
                }
            }
            requestToFileCache.set(publicPath, resolved);
            return resolved;
        },
        fileToRequest(filePath) {
            if (fileToRequestCache.has(filePath)) {
                return fileToRequestCache.get(filePath);
            }
            for (const r of resolvers) {
                const request = r.fileToRequest && r.fileToRequest(filePath, root);
                if (request)
                    return request;
            }
            const res = defaultFileToRequest(filePath, root);
            fileToRequestCache.set(filePath, res);
            return res;
        },
        /**
         * Given a fuzzy public path, resolve missing extensions and /index.xxx
         */
        normalizePublicPath(publicPath) {
            if (publicPath === serverPluginClient_1.clientPublicPath) {
                return publicPath;
            }
            // preserve query
            const queryMatch = publicPath.match(/\?.*$/);
            const query = queryMatch ? queryMatch[0] : '';
            const cleanPublicPath = utils_1.cleanUrl(publicPath);
            const finalize = (result) => {
                result += query;
                if (resolver.requestToFile(result) !== resolver.requestToFile(publicPath)) {
                    throw new Error(`[vite] normalizePublicPath check fail. please report to vite.`);
                }
                return result;
            };
            if (!serverPluginModuleResolve_1.moduleRE.test(cleanPublicPath)) {
                return finalize(resolver.fileToRequest(resolver.requestToFile(cleanPublicPath)));
            }
            const filePath = resolver.requestToFile(cleanPublicPath);
            const cacheDir = optimizer_1.resolveOptimizedCacheDir(root);
            if (cacheDir) {
                const relative = path_1.default.relative(cacheDir, filePath);
                if (!relative.startsWith('..')) {
                    return finalize(path_1.default.posix.join('/@modules/', slash_1.default(relative)));
                }
            }
            // fileToRequest doesn't work with files in node_modules
            // because of edge cases like symlinks or yarn-aliased-install
            // or even aliased-symlinks
            // example id: "@babel/runtime/helpers/esm/slicedToArray"
            // see the test case: /playground/TestNormalizePublicPath.vue
            const id = cleanPublicPath.replace(serverPluginModuleResolve_1.moduleRE, '');
            const { scope, name, inPkgPath } = utils_1.parseNodeModuleId(id);
            if (!inPkgPath)
                return publicPath;
            let filePathPostFix = '';
            let findPkgFrom = filePath;
            while (!filePathPostFix.startsWith(inPkgPath)) {
                // some package contains multi package.json...
                // for example: @babel/runtime@7.10.2/helpers/esm/package.json
                const pkgPath = utils_1.lookupFile(findPkgFrom, ['package.json'], true);
                if (!pkgPath) {
                    throw new Error(`[vite] can't find package.json for a node_module file: ` +
                        `"${publicPath}". something is wrong.`);
                }
                filePathPostFix = slash_1.default(path_1.default.relative(path_1.default.dirname(pkgPath), filePath));
                findPkgFrom = path_1.default.join(path_1.default.dirname(pkgPath), '../');
            }
            return finalize(['/@modules', scope, name, filePathPostFix].filter(Boolean).join('/'));
        },
        alias(id) {
            let aliased = literalAlias[id];
            if (aliased) {
                return aliased;
            }
            for (const { alias } of resolvers) {
                aliased = alias && typeof alias === 'function' ? alias(id) : undefined;
                if (aliased) {
                    return aliased;
                }
            }
        },
        resolveRelativeRequest(importer, importee) {
            const queryMatch = importee.match(utils_1.queryRE);
            let resolved = importee;
            if (importee.startsWith('.')) {
                resolved = path_1.default.posix.resolve(path_1.default.posix.dirname(importer), importee);
                for (const alias in literalDirAlias) {
                    if (importer.startsWith(alias)) {
                        if (!resolved.startsWith(alias)) {
                            // resolved path is outside of alias directory, we need to use
                            // its full path instead
                            const importerFilePath = resolver.requestToFile(importer);
                            const importeeFilePath = path_1.default.resolve(path_1.default.dirname(importerFilePath), importee);
                            resolved = resolver.fileToRequest(importeeFilePath);
                        }
                        break;
                    }
                }
            }
            return {
                pathname: utils_1.cleanUrl(resolved) +
                    // path resolve strips ending / which should be preserved
                    (importee.endsWith('/') && !resolved.endsWith('/') ? '/' : ''),
                query: queryMatch ? queryMatch[0] : ''
            };
        },
        isPublicRequest(publicPath) {
            return resolver
                .requestToFile(publicPath)
                .startsWith(path_1.default.resolve(root, 'public'));
        },
        isAssetRequest(filePath) {
            return ((assetsInclude && assetsInclude(filePath)) || pathUtils_1.isStaticAsset(filePath));
        }
    };
    return resolver;
}
exports.createResolver = createResolver;
exports.jsSrcRE = /\.(?:(?:j|t)sx?|vue)$|\.mjs$/;
const deepImportRE = /^([^@][^/]*)\/|^(@[^/]+\/[^/]+)\//;
/**
 * Redirects a bare module request to a full path under /@modules/
 * It resolves a bare node module id to its full entry path so that relative
 * imports from the entry can be correctly resolved.
 * e.g.:
 * - `import 'foo'` -> `import '/@modules/foo/dist/index.js'`
 * - `import 'foo/bar/baz'` -> `import '/@modules/foo/bar/baz.js'`
 */
function resolveBareModuleRequest(root, id, importer, resolver) {
    const optimized = resolveOptimizedModule(root, id);
    if (optimized) {
        // ensure optimized module requests always ends with `.js` - this is because
        // optimized deps may import one another and in the built bundle their
        // relative import paths ends with `.js`. If we don't append `.js` during
        // rewrites, it may result in duplicated copies of the same dep.
        return path_1.default.extname(id) === '.js' ? id : id + '.js';
    }
    let isEntry = false;
    const basedir = path_1.default.dirname(resolver.requestToFile(importer));
    const pkgInfo = resolveNodeModule(basedir, id, resolver);
    if (pkgInfo) {
        if (!pkgInfo.entry) {
            console.error(chalk_1.default.yellow(`[vite] dependency ${id} does not have default entry defined in package.json.`));
        }
        else {
            isEntry = true;
            id = pkgInfo.entry;
        }
    }
    if (!isEntry) {
        const deepMatch = !isEntry && id.match(deepImportRE);
        if (deepMatch) {
            // deep import
            const depId = deepMatch[1] || deepMatch[2];
            // check if this is a deep import to an optimized dep.
            if (resolveOptimizedModule(root, depId)) {
                if (resolver.alias(depId) === id) {
                    // this is a deep import but aliased from a bare module id.
                    // redirect it the optimized copy.
                    return resolveBareModuleRequest(root, depId, importer, resolver);
                }
                if (!cssUtils_1.isCSSRequest(id) && !resolver.isAssetRequest(id)) {
                    // warn against deep imports to optimized dep
                    console.error(chalk_1.default.yellow(`\n[vite] Avoid deep import "${id}" (imported by ${importer})\n` +
                        `because "${depId}" has been pre-optimized by vite into a single file.\n` +
                        `Prefer importing directly from the module entry:\n` +
                        chalk_1.default.cyan(`\n  import { ... } from "${depId}" \n\n`) +
                        `If the dependency requires deep import to function properly, \n` +
                        `add the deep path to ${chalk_1.default.cyan(`optimizeDeps.include`)} in vite.config.js.\n`));
                }
            }
            // resolve ext for deepImport
            const filePath = resolveNodeModuleFile(root, id);
            if (filePath) {
                const deepPath = id.replace(deepImportRE, '');
                const normalizedFilePath = slash_1.default(filePath);
                const postfix = normalizedFilePath.slice(normalizedFilePath.lastIndexOf(deepPath) + deepPath.length);
                id += postfix;
            }
        }
    }
    // check and warn deep imports on optimized modules
    const ext = path_1.default.extname(id);
    if (!exports.jsSrcRE.test(ext)) {
        // append import query for non-js deep imports
        return id + (utils_1.queryRE.test(id) ? '&import' : '?import');
    }
    else {
        return id;
    }
}
exports.resolveBareModuleRequest = resolveBareModuleRequest;
const viteOptimizedMap = new Map();
function resolveOptimizedModule(root, id) {
    const cacheKey = `${root}#${id}`;
    const cached = viteOptimizedMap.get(cacheKey);
    if (cached) {
        return cached;
    }
    const cacheDir = optimizer_1.resolveOptimizedCacheDir(root);
    if (!cacheDir)
        return;
    const tryResolve = (file) => {
        file = path_1.default.join(cacheDir, file);
        if (fs_extra_1.default.existsSync(file) && fs_extra_1.default.statSync(file).isFile()) {
            viteOptimizedMap.set(cacheKey, file);
            return file;
        }
    };
    return tryResolve(id) || tryResolve(id + '.js');
}
exports.resolveOptimizedModule = resolveOptimizedModule;
const nodeModulesInfoMap = new Map();
const nodeModulesFileMap = new Map();
function resolveNodeModule(root, id, resolver) {
    const cacheKey = `${root}#${id}`;
    const cached = nodeModulesInfoMap.get(cacheKey);
    if (cached) {
        return cached;
    }
    let pkgPath;
    try {
        // see if the id is a valid package name
        pkgPath = utils_1.resolveFrom(root, `${id}/package.json`);
    }
    catch (e) {
        debug(`failed to resolve package.json for ${id}`);
    }
    if (pkgPath) {
        // if yes, this is a entry import. resolve entry file
        let pkg;
        try {
            pkg = fs_extra_1.default.readJSONSync(pkgPath);
        }
        catch (e) {
            return;
        }
        let entryPoint;
        // TODO properly support conditional exports
        // https://nodejs.org/api/esm.html#esm_conditional_exports
        // Note: this would require @rollup/plugin-node-resolve to support it too
        // or we will have to implement that logic in vite's own resolve plugin.
        if (!entryPoint) {
            for (const field of exports.mainFields) {
                if (typeof pkg[field] === 'string') {
                    entryPoint = pkg[field];
                    break;
                }
            }
        }
        if (!entryPoint) {
            entryPoint = 'index.js';
        }
        // resolve object browser field in package.json
        // https://github.com/defunctzombie/package-browser-field-spec
        const { browser: browserField } = pkg;
        if (entryPoint && browserField && typeof browserField === 'object') {
            entryPoint = mapWithBrowserField(entryPoint, browserField);
        }
        debug(`(node_module entry) ${id} -> ${entryPoint}`);
        // save resolved entry file path using the deep import path as key
        // e.g. foo/dist/foo.js
        // this is the path raw imports will be rewritten to, and is what will
        // be passed to resolveNodeModuleFile().
        let entryFilePath;
        // respect user manual alias
        const aliased = resolver.alias(id);
        if (aliased && aliased !== id) {
            entryFilePath = resolveNodeModuleFile(root, aliased);
        }
        if (!entryFilePath && entryPoint) {
            // #284 some packages specify entry without extension...
            entryFilePath = path_1.default.join(path_1.default.dirname(pkgPath), entryPoint);
            const postfix = resolveFilePathPostfix(entryFilePath);
            if (postfix) {
                entryPoint += postfix;
                entryFilePath += postfix;
            }
            entryPoint = path_1.default.posix.join(id, entryPoint);
            // save the resolved file path now so we don't need to do it again in
            // resolveNodeModuleFile()
            nodeModulesFileMap.set(entryPoint, entryFilePath);
        }
        const result = {
            entry: entryPoint,
            entryFilePath,
            pkg
        };
        nodeModulesInfoMap.set(cacheKey, result);
        return result;
    }
}
exports.resolveNodeModule = resolveNodeModule;
function resolveNodeModuleFile(root, id) {
    const cacheKey = `${root}#${id}`;
    const cached = nodeModulesFileMap.get(cacheKey);
    if (cached) {
        return cached;
    }
    try {
        const resolved = utils_1.resolveFrom(root, id);
        nodeModulesFileMap.set(cacheKey, resolved);
        return resolved;
    }
    catch (e) {
        // error will be reported downstream
    }
}
exports.resolveNodeModuleFile = resolveNodeModuleFile;
const normalize = path_1.default.posix.normalize;
/**
 * 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 normalized;
    }
    const [, to] = foundEntry;
    return normalize(to);
}
//# sourceMappingURL=resolver.js.map