2021-03-01 15:06:11 +08:00

484 lines
20 KiB
JavaScript

"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