UNPKG

dependency-cruiser

Version:

Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.

181 lines (169 loc) 6.78 kB
import fs from "node:fs"; import enhancedResolve from "enhanced-resolve"; import { scannableExtensions } from "#extract/transpile/meta.mjs"; import { ruleSetHasDeprecationRule, ruleSetHasLicenseRule, } from "#graph-utl/rule-set.mjs"; /** * @import { IResolveOptions } from "../../../types/resolve-options.mjs"; * @import { ICruiseOptions } from "../../../types/options.mjs"; */ const DEFAULT_CACHE_DURATION = 4000; /** @type {Partial<IResolveOptions>} */ const DEFAULT_RESOLVE_OPTIONS = { symlinks: true, // if a webpack config overrides extensions, there's probably // good cause. The scannableExtensions are an educated guess // anyway.Note: if extract/transpile/index gets an unknown extension // it'll fall back to the javascript parser extensions: scannableExtensions, // for typescript projects that import stuff that's only in // node_modules/@types we need: // - the inclusion of .d.ts to the extensions (see above) // - an explicit inclusion of node_modules/@types to the spots // to look for modules (in addition to "node_modules" which // is the default for enhanced-resolve) modules: ["node_modules", "node_modules/@types"], // this overrides the default exports fields enhanced-resolve uses // (being ["exports"]) to keep backwards compatibility between enhanced-resolve // 4 and 5 (4 didn't heed them at all and the empty array has the same // effect). // Also see https://github.com/sverweij/dependency-cruiser/issues/338 exportsFields: [], }; function omit(pObject, pProperty) { const lObject = structuredClone(pObject); // eslint-disable-next-line security/detect-object-injection delete lObject[pProperty]; return lObject; } /** * * @param {Number} pCacheDuration * @returns {Partial<IResolveOptions>} */ function getNonOverridableResolveOptions(pCacheDuration) { return { // This should cover most of the bases of dependency-cruiser's // uses. Not overridable for now because for other // file systems it's not sure we can use sync system calls // Also: passing a non-cached filesystem makes performance // worse. fileSystem: new enhancedResolve.CachedInputFileSystem(fs, pCacheDuration), // our code depends on sync behavior, so having this // overridden is not an option useSyncFileSystemCalls: true, }; } function pushPlugin(pPlugins, pPluginToPush) { return (pPlugins || []).concat(pPluginToPush); } // eslint-disable-next-line max-lines-per-function async function compileResolveOptions( pResolveOptions, pTSConfig, pResolveOptionsFromDCConfig, ) { let lResolveOptions = {}; // There's a performance impact of ~1 ms per resolve even when there // are 0 paths in the tsconfig, so not loading it when not necessary // will be a win. // Also: requiring the plugin only when it's necessary will save some // startup time (especially on a cold require cache) if (pResolveOptions.tsConfig) { const { default: TsConfigPathsPlugin } = await import("tsconfig-paths-webpack-plugin"); lResolveOptions.plugins = pushPlugin( lResolveOptions.plugins, // @ts-expect-error TS2351 "TsConfPathsPlugin is not constructable" - is unjustified new TsConfigPathsPlugin({ configFile: pResolveOptions.tsConfig, // TsConfigPathsPlugin requires a baseUrl to be present in the tsconfig, // otherwise it prints scary messages that it didn't and read the tsConfig // (potentially making users think it's dependency-cruiser disregarding the // tsconfig). Hence up till version 13.0.4 dependency-cruiser only loaded // TsConfigPathsPlugin when an options.baseUrl existed. However, this // isn't necessary anymore: // - [tsconfig#baseUrl documentation](https://www.typescriptlang.org/tsconfig#baseUrl) // UNrecommends the use of the baseUrl for non-AMD projects // - [tsconfig-paths PR #207](https://github.com/dividab/tsconfig-paths/pull/208) // 'tolerates' undefined baseUrls // // Hence, until // [tpwp issue #99](https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/99) // lands: // - pass a default baseUrl to TsConfigPathsPlugin if the baseUrl isn't available // - pass undefined in all other cases; TsConfigPathsPlugin will read // it from the tsconfig.json in that case. Passing the processed baseUrl // (pTSConfig?.options?.baseUrl) instead would've been more obvious, but // doesn't work, as that is an absolute path and tsconfig-paths(-wpp) // seems to process that again resulting in invalid paths and unresolved // or erroneous dependencies // eslint-disable-next-line no-undefined baseUrl: pTSConfig?.options?.baseUrl ? undefined : "./", // TsConfigPathsPlugin doesn't (can't) read enhanced-resolve's // list of extensions, and the default it uses for extensions // so we do it ourselves - either with the extensions passed // or with the supported ones. extensions: pResolveOptionsFromDCConfig.extensions || pResolveOptions.extensions || DEFAULT_RESOLVE_OPTIONS.extensions, }), ); } return { ...DEFAULT_RESOLVE_OPTIONS, ...lResolveOptions, ...omit(pResolveOptionsFromDCConfig, "cachedInputFileSystem"), ...pResolveOptions, ...getNonOverridableResolveOptions( pResolveOptionsFromDCConfig?.cachedInputFileSystem?.cacheDuration ?? DEFAULT_CACHE_DURATION, ), }; } /** * @param {IResolveOptions} pResolveOptions * @param {ICruiseOptions} pOptions * @param {import("typescript").ParsedTsconfig} pTSConfig * @returns */ // eslint-disable-next-line complexity export default async function normalizeResolveOptions( pResolveOptions, pOptions, pTSConfig, ) { const lRuleSet = pOptions?.ruleSet ?? {}; return await compileResolveOptions( { // EnhancedResolve's symlinks: // - true => symlinks are followed (vv) // node's --preserve-symlinks: // - true => symlinks are NOT followed (vv) // => symlinks = !preserveSymlinks symlinks: !pOptions?.preserveSymlinks, tsConfig: pOptions?.tsConfig?.fileName ?? pOptions?.ruleSet?.options?.tsConfig?.fileName ?? null, /* squirrel the externalModuleResolutionStrategy and combinedDependencies thing into the resolve options - they're not for enhanced resolve, but they are for what we consider resolve options ... */ combinedDependencies: pOptions?.combinedDependencies ?? false, /* Same for the builtInModules override/ add option ...*/ ...(pOptions?.builtInModules?.override || pOptions?.builtInModules?.add ? { builtInModules: pOptions?.builtInModules } : {}), resolveLicenses: ruleSetHasLicenseRule(lRuleSet), resolveDeprecations: ruleSetHasDeprecationRule(lRuleSet), ...pResolveOptions, }, pTSConfig || {}, pOptions?.enhancedResolveOptions ?? {}, ); }