resolver.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.resolveNodeModuleFile = exports.resolveNodeModule = exports.resolveOptimizedModule = exports.resolveBareModuleRequest = exports.jsSrcRE = exports.createResolver = exports.mainFields = exports.supportedExts = void 0;
  7. const fs_extra_1 = __importDefault(require("fs-extra"));
  8. const path_1 = __importDefault(require("path"));
  9. const slash_1 = __importDefault(require("slash"));
  10. const utils_1 = require("./utils");
  11. const serverPluginModuleResolve_1 = require("./server/serverPluginModuleResolve");
  12. const optimizer_1 = require("./optimizer");
  13. const serverPluginClient_1 = require("./server/serverPluginClient");
  14. const cssUtils_1 = require("./utils/cssUtils");
  15. const pathUtils_1 = require("./utils/pathUtils");
  16. const chalk_1 = __importDefault(require("chalk"));
  17. const debug = require('debug')('vite:resolve');
  18. const isWin = require('os').platform() === 'win32';
  19. const pathSeparator = isWin ? '\\' : '/';
  20. exports.supportedExts = ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'];
  21. exports.mainFields = ['module', 'jsnext', 'jsnext:main', 'browser', 'main'];
  22. const defaultRequestToFile = (publicPath, root) => {
  23. if (serverPluginModuleResolve_1.moduleRE.test(publicPath)) {
  24. const id = publicPath.replace(serverPluginModuleResolve_1.moduleRE, '');
  25. const cachedNodeModule = serverPluginModuleResolve_1.moduleIdToFileMap.get(id);
  26. if (cachedNodeModule) {
  27. return cachedNodeModule;
  28. }
  29. // try to resolve from optimized modules
  30. const optimizedModule = resolveOptimizedModule(root, id);
  31. if (optimizedModule) {
  32. return optimizedModule;
  33. }
  34. // try to resolve from normal node_modules
  35. const nodeModule = resolveNodeModuleFile(root, id);
  36. if (nodeModule) {
  37. serverPluginModuleResolve_1.moduleIdToFileMap.set(id, nodeModule);
  38. return nodeModule;
  39. }
  40. }
  41. const publicDirPath = path_1.default.join(root, 'public', publicPath.slice(1));
  42. if (fs_extra_1.default.existsSync(publicDirPath)) {
  43. return publicDirPath;
  44. }
  45. return path_1.default.join(root, publicPath.slice(1));
  46. };
  47. const defaultFileToRequest = (filePath, root) => serverPluginModuleResolve_1.moduleFileToIdMap.get(filePath) ||
  48. '/' + slash_1.default(path_1.default.relative(root, filePath)).replace(/^public\//, '');
  49. const isFile = (file) => {
  50. try {
  51. return fs_extra_1.default.statSync(file).isFile();
  52. }
  53. catch (e) {
  54. return false;
  55. }
  56. };
  57. /**
  58. * this function resolve fuzzy file path. examples:
  59. * /path/file is a fuzzy file path for /path/file.tsx
  60. * /path/dir is a fuzzy file path for /path/dir/index.js
  61. *
  62. * returning undefined indicates the filePath is not fuzzy:
  63. * it is already an exact file path, or it can't match any file
  64. */
  65. const resolveFilePathPostfix = (filePath) => {
  66. const cleanPath = utils_1.cleanUrl(filePath);
  67. if (!isFile(cleanPath)) {
  68. let postfix = '';
  69. for (const ext of exports.supportedExts) {
  70. if (isFile(cleanPath + ext)) {
  71. postfix = ext;
  72. break;
  73. }
  74. const defaultFilePath = `/index${ext}`;
  75. if (isFile(path_1.default.join(cleanPath, defaultFilePath))) {
  76. postfix = defaultFilePath;
  77. break;
  78. }
  79. }
  80. const queryMatch = filePath.match(/\?.*$/);
  81. const query = queryMatch ? queryMatch[0] : '';
  82. const resolved = cleanPath + postfix + query;
  83. if (resolved !== filePath) {
  84. debug(`(postfix) ${filePath} -> ${resolved}`);
  85. return postfix;
  86. }
  87. }
  88. };
  89. const isDir = (p) => fs_extra_1.default.existsSync(p) && fs_extra_1.default.statSync(p).isDirectory();
  90. function createResolver(root, resolvers = [], userAlias = {}, assetsInclude) {
  91. resolvers = [...resolvers];
  92. const literalAlias = {};
  93. const literalDirAlias = {};
  94. const resolveAlias = (alias) => {
  95. for (const key in alias) {
  96. let target = alias[key];
  97. // aliasing a directory
  98. if (key.startsWith('/') && key.endsWith('/') && path_1.default.isAbsolute(target)) {
  99. // check first if this is aliasing to a path from root
  100. const fromRoot = path_1.default.join(root, target);
  101. if (isDir(fromRoot)) {
  102. target = fromRoot;
  103. }
  104. else if (!isDir(target)) {
  105. continue;
  106. }
  107. resolvers.push({
  108. requestToFile(publicPath) {
  109. if (publicPath.startsWith(key)) {
  110. return path_1.default.join(target, publicPath.slice(key.length));
  111. }
  112. },
  113. fileToRequest(filePath) {
  114. if (filePath.startsWith(target + pathSeparator)) {
  115. return slash_1.default(key + path_1.default.relative(target, filePath));
  116. }
  117. }
  118. });
  119. literalDirAlias[key] = target;
  120. }
  121. else {
  122. literalAlias[key] = target;
  123. }
  124. }
  125. };
  126. resolvers.forEach(({ alias }) => {
  127. if (alias && typeof alias === 'object') {
  128. resolveAlias(alias);
  129. }
  130. });
  131. resolveAlias(userAlias);
  132. const requestToFileCache = new Map();
  133. const fileToRequestCache = new Map();
  134. const resolver = {
  135. requestToFile(publicPath) {
  136. publicPath = decodeURIComponent(publicPath);
  137. if (requestToFileCache.has(publicPath)) {
  138. return requestToFileCache.get(publicPath);
  139. }
  140. let resolved;
  141. for (const r of resolvers) {
  142. const filepath = r.requestToFile && r.requestToFile(publicPath, root);
  143. if (filepath) {
  144. resolved = filepath;
  145. break;
  146. }
  147. }
  148. if (!resolved) {
  149. resolved = defaultRequestToFile(publicPath, root);
  150. }
  151. const postfix = resolveFilePathPostfix(resolved);
  152. if (postfix) {
  153. if (postfix[0] === '/') {
  154. resolved = path_1.default.join(resolved, postfix);
  155. }
  156. else {
  157. resolved += postfix;
  158. }
  159. }
  160. requestToFileCache.set(publicPath, resolved);
  161. return resolved;
  162. },
  163. fileToRequest(filePath) {
  164. if (fileToRequestCache.has(filePath)) {
  165. return fileToRequestCache.get(filePath);
  166. }
  167. for (const r of resolvers) {
  168. const request = r.fileToRequest && r.fileToRequest(filePath, root);
  169. if (request)
  170. return request;
  171. }
  172. const res = defaultFileToRequest(filePath, root);
  173. fileToRequestCache.set(filePath, res);
  174. return res;
  175. },
  176. /**
  177. * Given a fuzzy public path, resolve missing extensions and /index.xxx
  178. */
  179. normalizePublicPath(publicPath) {
  180. if (publicPath === serverPluginClient_1.clientPublicPath) {
  181. return publicPath;
  182. }
  183. // preserve query
  184. const queryMatch = publicPath.match(/\?.*$/);
  185. const query = queryMatch ? queryMatch[0] : '';
  186. const cleanPublicPath = utils_1.cleanUrl(publicPath);
  187. const finalize = (result) => {
  188. result += query;
  189. if (resolver.requestToFile(result) !== resolver.requestToFile(publicPath)) {
  190. throw new Error(`[vite] normalizePublicPath check fail. please report to vite.`);
  191. }
  192. return result;
  193. };
  194. if (!serverPluginModuleResolve_1.moduleRE.test(cleanPublicPath)) {
  195. return finalize(resolver.fileToRequest(resolver.requestToFile(cleanPublicPath)));
  196. }
  197. const filePath = resolver.requestToFile(cleanPublicPath);
  198. const cacheDir = optimizer_1.resolveOptimizedCacheDir(root);
  199. if (cacheDir) {
  200. const relative = path_1.default.relative(cacheDir, filePath);
  201. if (!relative.startsWith('..')) {
  202. return finalize(path_1.default.posix.join('/@modules/', slash_1.default(relative)));
  203. }
  204. }
  205. // fileToRequest doesn't work with files in node_modules
  206. // because of edge cases like symlinks or yarn-aliased-install
  207. // or even aliased-symlinks
  208. // example id: "@babel/runtime/helpers/esm/slicedToArray"
  209. // see the test case: /playground/TestNormalizePublicPath.vue
  210. const id = cleanPublicPath.replace(serverPluginModuleResolve_1.moduleRE, '');
  211. const { scope, name, inPkgPath } = utils_1.parseNodeModuleId(id);
  212. if (!inPkgPath)
  213. return publicPath;
  214. let filePathPostFix = '';
  215. let findPkgFrom = filePath;
  216. while (!filePathPostFix.startsWith(inPkgPath)) {
  217. // some package contains multi package.json...
  218. // for example: @babel/runtime@7.10.2/helpers/esm/package.json
  219. const pkgPath = utils_1.lookupFile(findPkgFrom, ['package.json'], true);
  220. if (!pkgPath) {
  221. throw new Error(`[vite] can't find package.json for a node_module file: ` +
  222. `"${publicPath}". something is wrong.`);
  223. }
  224. filePathPostFix = slash_1.default(path_1.default.relative(path_1.default.dirname(pkgPath), filePath));
  225. findPkgFrom = path_1.default.join(path_1.default.dirname(pkgPath), '../');
  226. }
  227. return finalize(['/@modules', scope, name, filePathPostFix].filter(Boolean).join('/'));
  228. },
  229. alias(id) {
  230. let aliased = literalAlias[id];
  231. if (aliased) {
  232. return aliased;
  233. }
  234. for (const { alias } of resolvers) {
  235. aliased = alias && typeof alias === 'function' ? alias(id) : undefined;
  236. if (aliased) {
  237. return aliased;
  238. }
  239. }
  240. },
  241. resolveRelativeRequest(importer, importee) {
  242. const queryMatch = importee.match(utils_1.queryRE);
  243. let resolved = importee;
  244. if (importee.startsWith('.')) {
  245. resolved = path_1.default.posix.resolve(path_1.default.posix.dirname(importer), importee);
  246. for (const alias in literalDirAlias) {
  247. if (importer.startsWith(alias)) {
  248. if (!resolved.startsWith(alias)) {
  249. // resolved path is outside of alias directory, we need to use
  250. // its full path instead
  251. const importerFilePath = resolver.requestToFile(importer);
  252. const importeeFilePath = path_1.default.resolve(path_1.default.dirname(importerFilePath), importee);
  253. resolved = resolver.fileToRequest(importeeFilePath);
  254. }
  255. break;
  256. }
  257. }
  258. }
  259. return {
  260. pathname: utils_1.cleanUrl(resolved) +
  261. // path resolve strips ending / which should be preserved
  262. (importee.endsWith('/') && !resolved.endsWith('/') ? '/' : ''),
  263. query: queryMatch ? queryMatch[0] : ''
  264. };
  265. },
  266. isPublicRequest(publicPath) {
  267. return resolver
  268. .requestToFile(publicPath)
  269. .startsWith(path_1.default.resolve(root, 'public'));
  270. },
  271. isAssetRequest(filePath) {
  272. return ((assetsInclude && assetsInclude(filePath)) || pathUtils_1.isStaticAsset(filePath));
  273. }
  274. };
  275. return resolver;
  276. }
  277. exports.createResolver = createResolver;
  278. exports.jsSrcRE = /\.(?:(?:j|t)sx?|vue)$|\.mjs$/;
  279. const deepImportRE = /^([^@][^/]*)\/|^(@[^/]+\/[^/]+)\//;
  280. /**
  281. * Redirects a bare module request to a full path under /@modules/
  282. * It resolves a bare node module id to its full entry path so that relative
  283. * imports from the entry can be correctly resolved.
  284. * e.g.:
  285. * - `import 'foo'` -> `import '/@modules/foo/dist/index.js'`
  286. * - `import 'foo/bar/baz'` -> `import '/@modules/foo/bar/baz.js'`
  287. */
  288. function resolveBareModuleRequest(root, id, importer, resolver) {
  289. const optimized = resolveOptimizedModule(root, id);
  290. if (optimized) {
  291. // ensure optimized module requests always ends with `.js` - this is because
  292. // optimized deps may import one another and in the built bundle their
  293. // relative import paths ends with `.js`. If we don't append `.js` during
  294. // rewrites, it may result in duplicated copies of the same dep.
  295. return path_1.default.extname(id) === '.js' ? id : id + '.js';
  296. }
  297. let isEntry = false;
  298. const basedir = path_1.default.dirname(resolver.requestToFile(importer));
  299. const pkgInfo = resolveNodeModule(basedir, id, resolver);
  300. if (pkgInfo) {
  301. if (!pkgInfo.entry) {
  302. console.error(chalk_1.default.yellow(`[vite] dependency ${id} does not have default entry defined in package.json.`));
  303. }
  304. else {
  305. isEntry = true;
  306. id = pkgInfo.entry;
  307. }
  308. }
  309. if (!isEntry) {
  310. const deepMatch = !isEntry && id.match(deepImportRE);
  311. if (deepMatch) {
  312. // deep import
  313. const depId = deepMatch[1] || deepMatch[2];
  314. // check if this is a deep import to an optimized dep.
  315. if (resolveOptimizedModule(root, depId)) {
  316. if (resolver.alias(depId) === id) {
  317. // this is a deep import but aliased from a bare module id.
  318. // redirect it the optimized copy.
  319. return resolveBareModuleRequest(root, depId, importer, resolver);
  320. }
  321. if (!cssUtils_1.isCSSRequest(id) && !resolver.isAssetRequest(id)) {
  322. // warn against deep imports to optimized dep
  323. console.error(chalk_1.default.yellow(`\n[vite] Avoid deep import "${id}" (imported by ${importer})\n` +
  324. `because "${depId}" has been pre-optimized by vite into a single file.\n` +
  325. `Prefer importing directly from the module entry:\n` +
  326. chalk_1.default.cyan(`\n import { ... } from "${depId}" \n\n`) +
  327. `If the dependency requires deep import to function properly, \n` +
  328. `add the deep path to ${chalk_1.default.cyan(`optimizeDeps.include`)} in vite.config.js.\n`));
  329. }
  330. }
  331. // resolve ext for deepImport
  332. const filePath = resolveNodeModuleFile(root, id);
  333. if (filePath) {
  334. const deepPath = id.replace(deepImportRE, '');
  335. const normalizedFilePath = slash_1.default(filePath);
  336. const postfix = normalizedFilePath.slice(normalizedFilePath.lastIndexOf(deepPath) + deepPath.length);
  337. id += postfix;
  338. }
  339. }
  340. }
  341. // check and warn deep imports on optimized modules
  342. const ext = path_1.default.extname(id);
  343. if (!exports.jsSrcRE.test(ext)) {
  344. // append import query for non-js deep imports
  345. return id + (utils_1.queryRE.test(id) ? '&import' : '?import');
  346. }
  347. else {
  348. return id;
  349. }
  350. }
  351. exports.resolveBareModuleRequest = resolveBareModuleRequest;
  352. const viteOptimizedMap = new Map();
  353. function resolveOptimizedModule(root, id) {
  354. const cacheKey = `${root}#${id}`;
  355. const cached = viteOptimizedMap.get(cacheKey);
  356. if (cached) {
  357. return cached;
  358. }
  359. const cacheDir = optimizer_1.resolveOptimizedCacheDir(root);
  360. if (!cacheDir)
  361. return;
  362. const tryResolve = (file) => {
  363. file = path_1.default.join(cacheDir, file);
  364. if (fs_extra_1.default.existsSync(file) && fs_extra_1.default.statSync(file).isFile()) {
  365. viteOptimizedMap.set(cacheKey, file);
  366. return file;
  367. }
  368. };
  369. return tryResolve(id) || tryResolve(id + '.js');
  370. }
  371. exports.resolveOptimizedModule = resolveOptimizedModule;
  372. const nodeModulesInfoMap = new Map();
  373. const nodeModulesFileMap = new Map();
  374. function resolveNodeModule(root, id, resolver) {
  375. const cacheKey = `${root}#${id}`;
  376. const cached = nodeModulesInfoMap.get(cacheKey);
  377. if (cached) {
  378. return cached;
  379. }
  380. let pkgPath;
  381. try {
  382. // see if the id is a valid package name
  383. pkgPath = utils_1.resolveFrom(root, `${id}/package.json`);
  384. }
  385. catch (e) {
  386. debug(`failed to resolve package.json for ${id}`);
  387. }
  388. if (pkgPath) {
  389. // if yes, this is a entry import. resolve entry file
  390. let pkg;
  391. try {
  392. pkg = fs_extra_1.default.readJSONSync(pkgPath);
  393. }
  394. catch (e) {
  395. return;
  396. }
  397. let entryPoint;
  398. // TODO properly support conditional exports
  399. // https://nodejs.org/api/esm.html#esm_conditional_exports
  400. // Note: this would require @rollup/plugin-node-resolve to support it too
  401. // or we will have to implement that logic in vite's own resolve plugin.
  402. if (!entryPoint) {
  403. for (const field of exports.mainFields) {
  404. if (typeof pkg[field] === 'string') {
  405. entryPoint = pkg[field];
  406. break;
  407. }
  408. }
  409. }
  410. if (!entryPoint) {
  411. entryPoint = 'index.js';
  412. }
  413. // resolve object browser field in package.json
  414. // https://github.com/defunctzombie/package-browser-field-spec
  415. const { browser: browserField } = pkg;
  416. if (entryPoint && browserField && typeof browserField === 'object') {
  417. entryPoint = mapWithBrowserField(entryPoint, browserField);
  418. }
  419. debug(`(node_module entry) ${id} -> ${entryPoint}`);
  420. // save resolved entry file path using the deep import path as key
  421. // e.g. foo/dist/foo.js
  422. // this is the path raw imports will be rewritten to, and is what will
  423. // be passed to resolveNodeModuleFile().
  424. let entryFilePath;
  425. // respect user manual alias
  426. const aliased = resolver.alias(id);
  427. if (aliased && aliased !== id) {
  428. entryFilePath = resolveNodeModuleFile(root, aliased);
  429. }
  430. if (!entryFilePath && entryPoint) {
  431. // #284 some packages specify entry without extension...
  432. entryFilePath = path_1.default.join(path_1.default.dirname(pkgPath), entryPoint);
  433. const postfix = resolveFilePathPostfix(entryFilePath);
  434. if (postfix) {
  435. entryPoint += postfix;
  436. entryFilePath += postfix;
  437. }
  438. entryPoint = path_1.default.posix.join(id, entryPoint);
  439. // save the resolved file path now so we don't need to do it again in
  440. // resolveNodeModuleFile()
  441. nodeModulesFileMap.set(entryPoint, entryFilePath);
  442. }
  443. const result = {
  444. entry: entryPoint,
  445. entryFilePath,
  446. pkg
  447. };
  448. nodeModulesInfoMap.set(cacheKey, result);
  449. return result;
  450. }
  451. }
  452. exports.resolveNodeModule = resolveNodeModule;
  453. function resolveNodeModuleFile(root, id) {
  454. const cacheKey = `${root}#${id}`;
  455. const cached = nodeModulesFileMap.get(cacheKey);
  456. if (cached) {
  457. return cached;
  458. }
  459. try {
  460. const resolved = utils_1.resolveFrom(root, id);
  461. nodeModulesFileMap.set(cacheKey, resolved);
  462. return resolved;
  463. }
  464. catch (e) {
  465. // error will be reported downstream
  466. }
  467. }
  468. exports.resolveNodeModuleFile = resolveNodeModuleFile;
  469. const normalize = path_1.default.posix.normalize;
  470. /**
  471. * given a relative path in pkg dir,
  472. * return a relative path in pkg dir,
  473. * mapped with the "map" object
  474. */
  475. function mapWithBrowserField(relativePathInPkgDir, map) {
  476. const normalized = normalize(relativePathInPkgDir);
  477. const foundEntry = Object.entries(map).find(([from]) => normalize(from) === normalized);
  478. if (!foundEntry) {
  479. return normalized;
  480. }
  481. const [, to] = foundEntry;
  482. return normalize(to);
  483. }
  484. //# sourceMappingURL=resolver.js.map