index.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. import { dirname, resolve, extname, normalize, sep } from 'path';
  2. import builtinList from 'builtin-modules';
  3. import deepMerge from 'deepmerge';
  4. import isModule from 'is-module';
  5. import fs, { realpathSync } from 'fs';
  6. import { promisify } from 'util';
  7. import { createFilter } from '@rollup/pluginutils';
  8. import resolveModule from 'resolve';
  9. const exists = promisify(fs.exists);
  10. const readFile = promisify(fs.readFile);
  11. const realpath = promisify(fs.realpath);
  12. const stat = promisify(fs.stat);
  13. const onError = (error) => {
  14. if (error.code === 'ENOENT') {
  15. return false;
  16. }
  17. throw error;
  18. };
  19. const makeCache = (fn) => {
  20. const cache = new Map();
  21. const wrapped = async (param, done) => {
  22. if (cache.has(param) === false) {
  23. cache.set(
  24. param,
  25. fn(param).catch((err) => {
  26. cache.delete(param);
  27. throw err;
  28. })
  29. );
  30. }
  31. try {
  32. const result = cache.get(param);
  33. const value = await result;
  34. return done(null, value);
  35. } catch (error) {
  36. return done(error);
  37. }
  38. };
  39. wrapped.clear = () => cache.clear();
  40. return wrapped;
  41. };
  42. const isDirCached = makeCache(async (file) => {
  43. try {
  44. const stats = await stat(file);
  45. return stats.isDirectory();
  46. } catch (error) {
  47. return onError(error);
  48. }
  49. });
  50. const isFileCached = makeCache(async (file) => {
  51. try {
  52. const stats = await stat(file);
  53. return stats.isFile();
  54. } catch (error) {
  55. return onError(error);
  56. }
  57. });
  58. const readCachedFile = makeCache(readFile);
  59. const resolveId = promisify(resolveModule);
  60. // returns the imported package name for bare module imports
  61. function getPackageName(id) {
  62. if (id.startsWith('.') || id.startsWith('/')) {
  63. return null;
  64. }
  65. const split = id.split('/');
  66. // @my-scope/my-package/foo.js -> @my-scope/my-package
  67. // @my-scope/my-package -> @my-scope/my-package
  68. if (split[0][0] === '@') {
  69. return `${split[0]}/${split[1]}`;
  70. }
  71. // my-package/foo.js -> my-package
  72. // my-package -> my-package
  73. return split[0];
  74. }
  75. function getMainFields(options) {
  76. let mainFields;
  77. if (options.mainFields) {
  78. ({ mainFields } = options);
  79. } else {
  80. mainFields = ['module', 'main'];
  81. }
  82. if (options.browser && mainFields.indexOf('browser') === -1) {
  83. return ['browser'].concat(mainFields);
  84. }
  85. if (!mainFields.length) {
  86. throw new Error('Please ensure at least one `mainFields` value is specified');
  87. }
  88. return mainFields;
  89. }
  90. function getPackageInfo(options) {
  91. const { cache, extensions, pkg, mainFields, preserveSymlinks, useBrowserOverrides } = options;
  92. let { pkgPath } = options;
  93. if (cache.has(pkgPath)) {
  94. return cache.get(pkgPath);
  95. }
  96. // browserify/resolve doesn't realpath paths returned in its packageFilter callback
  97. if (!preserveSymlinks) {
  98. pkgPath = realpathSync(pkgPath);
  99. }
  100. const pkgRoot = dirname(pkgPath);
  101. const packageInfo = {
  102. // copy as we are about to munge the `main` field of `pkg`.
  103. packageJson: Object.assign({}, pkg),
  104. // path to package.json file
  105. packageJsonPath: pkgPath,
  106. // directory containing the package.json
  107. root: pkgRoot,
  108. // which main field was used during resolution of this module (main, module, or browser)
  109. resolvedMainField: 'main',
  110. // whether the browser map was used to resolve the entry point to this module
  111. browserMappedMain: false,
  112. // the entry point of the module with respect to the selected main field and any
  113. // relevant browser mappings.
  114. resolvedEntryPoint: ''
  115. };
  116. let overriddenMain = false;
  117. for (let i = 0; i < mainFields.length; i++) {
  118. const field = mainFields[i];
  119. if (typeof pkg[field] === 'string') {
  120. pkg.main = pkg[field];
  121. packageInfo.resolvedMainField = field;
  122. overriddenMain = true;
  123. break;
  124. }
  125. }
  126. const internalPackageInfo = {
  127. cachedPkg: pkg,
  128. hasModuleSideEffects: () => null,
  129. hasPackageEntry: overriddenMain !== false || mainFields.indexOf('main') !== -1,
  130. packageBrowserField:
  131. useBrowserOverrides &&
  132. typeof pkg.browser === 'object' &&
  133. Object.keys(pkg.browser).reduce((browser, key) => {
  134. let resolved = pkg.browser[key];
  135. if (resolved && resolved[0] === '.') {
  136. resolved = resolve(pkgRoot, resolved);
  137. }
  138. /* eslint-disable no-param-reassign */
  139. browser[key] = resolved;
  140. if (key[0] === '.') {
  141. const absoluteKey = resolve(pkgRoot, key);
  142. browser[absoluteKey] = resolved;
  143. if (!extname(key)) {
  144. extensions.reduce((subBrowser, ext) => {
  145. subBrowser[absoluteKey + ext] = subBrowser[key];
  146. return subBrowser;
  147. }, browser);
  148. }
  149. }
  150. return browser;
  151. }, {}),
  152. packageInfo
  153. };
  154. const browserMap = internalPackageInfo.packageBrowserField;
  155. if (
  156. useBrowserOverrides &&
  157. typeof pkg.browser === 'object' &&
  158. // eslint-disable-next-line no-prototype-builtins
  159. browserMap.hasOwnProperty(pkg.main)
  160. ) {
  161. packageInfo.resolvedEntryPoint = browserMap[pkg.main];
  162. packageInfo.browserMappedMain = true;
  163. } else {
  164. // index.node is technically a valid default entrypoint as well...
  165. packageInfo.resolvedEntryPoint = resolve(pkgRoot, pkg.main || 'index.js');
  166. packageInfo.browserMappedMain = false;
  167. }
  168. const packageSideEffects = pkg.sideEffects;
  169. if (typeof packageSideEffects === 'boolean') {
  170. internalPackageInfo.hasModuleSideEffects = () => packageSideEffects;
  171. } else if (Array.isArray(packageSideEffects)) {
  172. internalPackageInfo.hasModuleSideEffects = createFilter(packageSideEffects, null, {
  173. resolve: pkgRoot
  174. });
  175. }
  176. cache.set(pkgPath, internalPackageInfo);
  177. return internalPackageInfo;
  178. }
  179. function normalizeInput(input) {
  180. if (Array.isArray(input)) {
  181. return input;
  182. } else if (typeof input === 'object') {
  183. return Object.values(input);
  184. }
  185. // otherwise it's a string
  186. return [input];
  187. }
  188. // Resolve module specifiers in order. Promise resolves to the first module that resolves
  189. // successfully, or the error that resulted from the last attempted module resolution.
  190. function resolveImportSpecifiers(importSpecifierList, resolveOptions) {
  191. let promise = Promise.resolve();
  192. for (let i = 0; i < importSpecifierList.length; i++) {
  193. promise = promise.then((value) => {
  194. // if we've already resolved to something, just return it.
  195. if (value) {
  196. return value;
  197. }
  198. return resolveId(importSpecifierList[i], resolveOptions).then((result) => {
  199. if (!resolveOptions.preserveSymlinks) {
  200. result = realpathSync(result);
  201. }
  202. return result;
  203. });
  204. });
  205. if (i < importSpecifierList.length - 1) {
  206. // swallow MODULE_NOT_FOUND errors from all but the last resolution
  207. promise = promise.catch((error) => {
  208. if (error.code !== 'MODULE_NOT_FOUND') {
  209. throw error;
  210. }
  211. });
  212. }
  213. }
  214. return promise;
  215. }
  216. /* eslint-disable no-param-reassign, no-shadow, no-undefined */
  217. const builtins = new Set(builtinList);
  218. const ES6_BROWSER_EMPTY = '\0node-resolve:empty.js';
  219. const nullFn = () => null;
  220. const deepFreeze = (object) => {
  221. Object.freeze(object);
  222. for (const value of Object.values(object)) {
  223. if (typeof value === 'object' && !Object.isFrozen(value)) {
  224. deepFreeze(value);
  225. }
  226. }
  227. return object;
  228. };
  229. const defaults = {
  230. customResolveOptions: {},
  231. dedupe: [],
  232. // It's important that .mjs is listed before .js so that Rollup will interpret npm modules
  233. // which deploy both ESM .mjs and CommonJS .js files as ESM.
  234. extensions: ['.mjs', '.js', '.json', '.node'],
  235. resolveOnly: []
  236. };
  237. const DEFAULTS = deepFreeze(deepMerge({}, defaults));
  238. function nodeResolve(opts = {}) {
  239. const options = Object.assign({}, defaults, opts);
  240. const { customResolveOptions, extensions, jail } = options;
  241. const warnings = [];
  242. const packageInfoCache = new Map();
  243. const idToPackageInfo = new Map();
  244. const mainFields = getMainFields(options);
  245. const useBrowserOverrides = mainFields.indexOf('browser') !== -1;
  246. const isPreferBuiltinsSet = options.preferBuiltins === true || options.preferBuiltins === false;
  247. const preferBuiltins = isPreferBuiltinsSet ? options.preferBuiltins : true;
  248. const rootDir = options.rootDir || process.cwd();
  249. let { dedupe } = options;
  250. let rollupOptions;
  251. if (options.only) {
  252. warnings.push('node-resolve: The `only` options is deprecated, please use `resolveOnly`');
  253. options.resolveOnly = options.only;
  254. }
  255. if (typeof dedupe !== 'function') {
  256. dedupe = (importee) =>
  257. options.dedupe.includes(importee) || options.dedupe.includes(getPackageName(importee));
  258. }
  259. const resolveOnly = options.resolveOnly.map((pattern) => {
  260. if (pattern instanceof RegExp) {
  261. return pattern;
  262. }
  263. const normalized = pattern.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
  264. return new RegExp(`^${normalized}$`);
  265. });
  266. const browserMapCache = new Map();
  267. let preserveSymlinks;
  268. return {
  269. name: 'node-resolve',
  270. buildStart(options) {
  271. rollupOptions = options;
  272. for (const warning of warnings) {
  273. this.warn(warning);
  274. }
  275. ({ preserveSymlinks } = options);
  276. },
  277. generateBundle() {
  278. readCachedFile.clear();
  279. isFileCached.clear();
  280. isDirCached.clear();
  281. },
  282. async resolveId(importee, importer) {
  283. if (importee === ES6_BROWSER_EMPTY) {
  284. return importee;
  285. }
  286. // ignore IDs with null character, these belong to other plugins
  287. if (/\0/.test(importee)) return null;
  288. // strip hash and query params from import
  289. const [withoutHash, hash] = importee.split('#');
  290. const [importPath, params] = withoutHash.split('?');
  291. const importSuffix = `${params ? `?${params}` : ''}${hash ? `#${hash}` : ''}`;
  292. importee = importPath;
  293. const basedir = !importer || dedupe(importee) ? rootDir : dirname(importer);
  294. // https://github.com/defunctzombie/package-browser-field-spec
  295. const browser = browserMapCache.get(importer);
  296. if (useBrowserOverrides && browser) {
  297. const resolvedImportee = resolve(basedir, importee);
  298. if (browser[importee] === false || browser[resolvedImportee] === false) {
  299. return ES6_BROWSER_EMPTY;
  300. }
  301. const browserImportee =
  302. browser[importee] ||
  303. browser[resolvedImportee] ||
  304. browser[`${resolvedImportee}.js`] ||
  305. browser[`${resolvedImportee}.json`];
  306. if (browserImportee) {
  307. importee = browserImportee;
  308. }
  309. }
  310. const parts = importee.split(/[/\\]/);
  311. let id = parts.shift();
  312. let isRelativeImport = false;
  313. if (id[0] === '@' && parts.length > 0) {
  314. // scoped packages
  315. id += `/${parts.shift()}`;
  316. } else if (id[0] === '.') {
  317. // an import relative to the parent dir of the importer
  318. id = resolve(basedir, importee);
  319. isRelativeImport = true;
  320. }
  321. if (
  322. !isRelativeImport &&
  323. resolveOnly.length &&
  324. !resolveOnly.some((pattern) => pattern.test(id))
  325. ) {
  326. if (normalizeInput(rollupOptions.input).includes(importee)) {
  327. return null;
  328. }
  329. return false;
  330. }
  331. let hasModuleSideEffects = nullFn;
  332. let hasPackageEntry = true;
  333. let packageBrowserField = false;
  334. let packageInfo;
  335. const filter = (pkg, pkgPath) => {
  336. const info = getPackageInfo({
  337. cache: packageInfoCache,
  338. extensions,
  339. pkg,
  340. pkgPath,
  341. mainFields,
  342. preserveSymlinks,
  343. useBrowserOverrides
  344. });
  345. ({ packageInfo, hasModuleSideEffects, hasPackageEntry, packageBrowserField } = info);
  346. return info.cachedPkg;
  347. };
  348. let resolveOptions = {
  349. basedir,
  350. packageFilter: filter,
  351. readFile: readCachedFile,
  352. isFile: isFileCached,
  353. isDirectory: isDirCached,
  354. extensions
  355. };
  356. if (preserveSymlinks !== undefined) {
  357. resolveOptions.preserveSymlinks = preserveSymlinks;
  358. }
  359. const importSpecifierList = [];
  360. if (importer === undefined && !importee[0].match(/^\.?\.?\//)) {
  361. // For module graph roots (i.e. when importer is undefined), we
  362. // need to handle 'path fragments` like `foo/bar` that are commonly
  363. // found in rollup config files. If importee doesn't look like a
  364. // relative or absolute path, we make it relative and attempt to
  365. // resolve it. If we don't find anything, we try resolving it as we
  366. // got it.
  367. importSpecifierList.push(`./${importee}`);
  368. }
  369. const importeeIsBuiltin = builtins.has(importee);
  370. if (importeeIsBuiltin && (!preferBuiltins || !isPreferBuiltinsSet)) {
  371. // The `resolve` library will not resolve packages with the same
  372. // name as a node built-in module. If we're resolving something
  373. // that's a builtin, and we don't prefer to find built-ins, we
  374. // first try to look up a local module with that name. If we don't
  375. // find anything, we resolve the builtin which just returns back
  376. // the built-in's name.
  377. importSpecifierList.push(`${importee}/`);
  378. }
  379. // TypeScript files may import '.js' to refer to either '.ts' or '.tsx'
  380. if (importer && importee.endsWith('.js')) {
  381. for (const ext of ['.ts', '.tsx']) {
  382. if (importer.endsWith(ext) && extensions.includes(ext)) {
  383. importSpecifierList.push(importee.replace(/.js$/, ext));
  384. }
  385. }
  386. }
  387. importSpecifierList.push(importee);
  388. resolveOptions = Object.assign(resolveOptions, customResolveOptions);
  389. try {
  390. let resolved = await resolveImportSpecifiers(importSpecifierList, resolveOptions);
  391. if (resolved && packageBrowserField) {
  392. if (Object.prototype.hasOwnProperty.call(packageBrowserField, resolved)) {
  393. if (!packageBrowserField[resolved]) {
  394. browserMapCache.set(resolved, packageBrowserField);
  395. return ES6_BROWSER_EMPTY;
  396. }
  397. resolved = packageBrowserField[resolved];
  398. }
  399. browserMapCache.set(resolved, packageBrowserField);
  400. }
  401. if (hasPackageEntry && !preserveSymlinks && resolved) {
  402. const fileExists = await exists(resolved);
  403. if (fileExists) {
  404. resolved = await realpath(resolved);
  405. }
  406. }
  407. idToPackageInfo.set(resolved, packageInfo);
  408. if (hasPackageEntry) {
  409. if (builtins.has(resolved) && preferBuiltins && isPreferBuiltinsSet) {
  410. return null;
  411. } else if (importeeIsBuiltin && preferBuiltins) {
  412. if (!isPreferBuiltinsSet) {
  413. this.warn(
  414. `preferring built-in module '${importee}' over local alternative at '${resolved}', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning`
  415. );
  416. }
  417. return null;
  418. } else if (jail && resolved.indexOf(normalize(jail.trim(sep))) !== 0) {
  419. return null;
  420. }
  421. }
  422. if (resolved && options.modulesOnly) {
  423. const code = await readFile(resolved, 'utf-8');
  424. if (isModule(code)) {
  425. return {
  426. id: `${resolved}${importSuffix}`,
  427. moduleSideEffects: hasModuleSideEffects(resolved)
  428. };
  429. }
  430. return null;
  431. }
  432. const result = {
  433. id: `${resolved}${importSuffix}`,
  434. moduleSideEffects: hasModuleSideEffects(resolved)
  435. };
  436. return result;
  437. } catch (error) {
  438. return null;
  439. }
  440. },
  441. load(importee) {
  442. if (importee === ES6_BROWSER_EMPTY) {
  443. return 'export default {};';
  444. }
  445. return null;
  446. },
  447. getPackageInfoForId(id) {
  448. return idToPackageInfo.get(id);
  449. }
  450. };
  451. }
  452. export default nodeResolve;
  453. export { DEFAULTS, nodeResolve };