Skip to content

Commit

Permalink
feature(main|extract): adds builtInModules option to tweak what to co…
Browse files Browse the repository at this point in the history
…nsider built-in (/ core) modules (#847)

## Description

- adds a `builtInModules` option to tweak what to consider built-in (/ core) modules

## Motivation and Context

By default dependency-cruiser considers nodejs built-in modules as core modules. In other contexts this might be not (entirely) right:
- when targeting the browser, the core modules are either not available or you're using a shim (e.g. https://www.npmjs.com/package/path). In these cases dependency-cruiser should respectively flag the included core module as unresolvable or resolve it to node_modules/path
- when targeting a platform built on top of nodejs, like electron, you might want to specify the packages built into that platform as.

This PR adds options for both of these. See the documentation included in this PR for details

Fixes #845 

## How Has This Been Tested?

- [x] green ci
- [x] additional automated tests

## Types of changes


- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Documentation only change
- [ ] Refactor (non-breaking change which fixes an issue without changing functionality)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
  • Loading branch information
sverweij committed Sep 30, 2023
1 parent 32cad15 commit ddd2ee6
Show file tree
Hide file tree
Showing 16 changed files with 377 additions and 56 deletions.
40 changes: 40 additions & 0 deletions doc/options-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- [mono repo behaviour - combinedDependencies](#mono-repo-behaviour---combinedDependencies)
- [exotic ways to require modules - exoticRequireStrings](#exotic-ways-to-require-modules---exoticrequirestrings)
- [extraExtensionsToScan](#extraextensionstoscan)
- [`builtInModules`: influencing what to consider built-in (/ core) modules](#builtinmodules-influencing-what-to-consider-built-in--core-modules)
- [enhancedResolveOptions](#enhancedresolveoptions)
- [forceDeriveDependents](#forcederivedependents)
- [parser](#parser)
Expand Down Expand Up @@ -1450,6 +1451,45 @@ their extensions in an `extraExtensionsToScan` array, like so:
> time. This also means that if you put an extension in the extra extensions
> to scan dependency-cruiser _could_ have had parsed it won't.
### `builtInModules`: influencing what to consider built-in (/ core) modules
By default dependency-cruiser considers nodejs built-in modules as core modules.
In contexts that are not nodejs this might be not (entirely) right:
- when targeting the browser, the core modules are either not available or you're
using a shim (e.g. the 1:1 copy of `path` on [npmjs](https://npmjs.com/package/path)).
In those cases dependency-cruiser should respectively flag the included core
module as unresolvable or resolve it to `node_modules/path`
- when targeting a platform built on top of nodejs, like electron, you might
want to specify the packages built into that platform as built-in/ core
as well.
To override the default behaviour you can pass a `builtInModules` object in
the options section of your dependency-cruiser configuration. The object has two
attributes, that can be used on their own or combined:
- `override`: an array of strings with the names of the modules you want to
use as core modules instead of the default ones.
I.e. for a browser context you could use the empty array:
```javascript
options: {
// ...
builtInModules: {
override: [];
}
}
```
- `add`: an array of strings with the names of the modules you want to add
to the list of core modules. I.e. for electron you could use:
```javascript
options: {
// ...
builtInModules: {
add: ["electron"];
}
}
```
### enhancedResolveOptions
Under the hood dependency-cruiser uses webpack's
Expand Down
18 changes: 9 additions & 9 deletions src/config-utl/extract-depcruise-config/merge-configs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function extendNamedRule(pExtendedRule, pForbiddenArrayBase) {
...pBaseRule,
...pAll,
}),
pExtendedRule
pExtendedRule,
);
}

Expand All @@ -23,7 +23,7 @@ function extendNamedRule(pExtendedRule, pForbiddenArrayBase) {
* rules get merged, where individual attributes of the named rules
* in pForbiddenArrayExtended win)
*
* @param {*} pRuleArrayExtended - array of 'fobidden' rules that extend the ...
* @param {*} pRuleArrayExtended - array of 'forbidden' rules that extend the ...
* @param {*} pRuleArrayBase - array of 'forbidden' rules to extend
*
* @return {Array} - the merged array
Expand All @@ -32,7 +32,7 @@ function mergeRules(pRuleArrayExtended, pRuleArrayBase) {
// merge anonymous on 100% equality
let lAnonymousRules = uniqWith(
pRuleArrayExtended.concat(pRuleArrayBase).filter(({ name }) => !name),
isDeepStrictEqual
isDeepStrictEqual,
);

let lNamedRules = pRuleArrayExtended
Expand All @@ -48,7 +48,7 @@ function mergeRules(pRuleArrayExtended, pRuleArrayBase) {
// the other concats (anonymous, allowed) don't need it
// but have it to be consistent with this
lNamedRules.concat(pRuleArrayBase).filter(({ name }) => name),
({ name }) => name
({ name }) => name,
);

return lNamedRules.concat(lAnonymousRules);
Expand All @@ -67,7 +67,7 @@ function mergeRules(pRuleArrayExtended, pRuleArrayBase) {
function mergeAllowedRules(pAllowedArrayExtended, pAllowedArrayBase) {
return uniqWith(
pAllowedArrayExtended.concat(pAllowedArrayBase),
isDeepStrictEqual
isDeepStrictEqual,
);
}

Expand Down Expand Up @@ -109,15 +109,15 @@ function mergeAllowedSeverities(pConfigExtended, pConfigBase) {
export default (pConfigExtended, pConfigBase) => {
const lForbidden = mergeRules(
pConfigExtended?.forbidden ?? [],
pConfigBase?.forbidden ?? []
pConfigBase?.forbidden ?? [],
);
const lRequired = mergeRules(
pConfigExtended?.required ?? [],
pConfigBase?.required ?? []
pConfigBase?.required ?? [],
);
const lAllowed = mergeAllowedRules(
pConfigExtended?.allowed ?? [],
pConfigBase?.allowed ?? []
pConfigBase?.allowed ?? [],
);

return {
Expand All @@ -131,7 +131,7 @@ export default (pConfigExtended, pConfigBase) => {
: {}),
options: mergeOptions(
pConfigExtended?.options ?? {},
pConfigBase?.options ?? {}
pConfigBase?.options ?? {},
),
};
};
37 changes: 19 additions & 18 deletions src/extract/resolve/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function resolveModule(
pModule,
pBaseDirectory,
pFileDirectory,
pResolveOptions
pResolveOptions,
) {
let lReturnValue = null;

Expand All @@ -34,13 +34,14 @@ function resolveModule(
lStrippedModuleName,
pBaseDirectory,
pFileDirectory,
pResolveOptions
pResolveOptions,
);
} else {
lReturnValue = resolveAMD(
lStrippedModuleName,
pBaseDirectory,
pFileDirectory
pFileDirectory,
pResolveOptions,
);
}
return lReturnValue;
Expand Down Expand Up @@ -89,13 +90,13 @@ function resolveWithRetry(
pModule,
pBaseDirectory,
pFileDirectory,
pResolveOptions
pResolveOptions,
) {
let lReturnValue = resolveModule(
pModule,
pBaseDirectory,
pFileDirectory,
pResolveOptions
pResolveOptions,
);
const lStrippedModuleName = stripToModuleName(pModule.module);

Expand Down Expand Up @@ -123,17 +124,17 @@ function resolveWithRetry(
) {
const lModuleWithoutExtension = lStrippedModuleName.replace(
/\.(js|jsx|cjs|mjs)$/g,
""
"",
);
const lExtensionsToTry = getTypeScriptExtensionsToTry(
extname(lStrippedModuleName)
extname(lStrippedModuleName),
);

const lReturnValueCandidate = resolveModule(
{ ...pModule, module: lModuleWithoutExtension },
pBaseDirectory,
pFileDirectory,
{ ...pResolveOptions, extensions: lExtensionsToTry }
{ ...pResolveOptions, extensions: lExtensionsToTry },
);

if (isTypeScriptIshExtension(lReturnValueCandidate.resolved)) {
Expand All @@ -160,13 +161,13 @@ export default function resolve(
pDependency,
pBaseDirectory,
pFileDirectory,
pResolveOptions
pResolveOptions,
) {
let lResolvedDependency = resolveWithRetry(
pDependency,
pBaseDirectory,
pFileDirectory,
pResolveOptions
pResolveOptions,
);
const lStrippedModuleName = stripToModuleName(pDependency.module);

Expand All @@ -176,19 +177,19 @@ export default function resolve(
lStrippedModuleName,
lResolvedDependency.resolved,
{ baseDirectory: pBaseDirectory, fileDirectory: pFileDirectory },
pResolveOptions
pResolveOptions,
),
dependencyTypes: determineDependencyTypes(
{ ...pDependency, ...lResolvedDependency },
lStrippedModuleName,
getManifest(
pFileDirectory,
pBaseDirectory,
pResolveOptions.combinedDependencies
pResolveOptions.combinedDependencies,
),
pFileDirectory,
pResolveOptions,
pBaseDirectory
pBaseDirectory,
),
};

Expand All @@ -210,11 +211,11 @@ export default function resolve(
again corresponds with a real file on disk
*/
// eslint-disable-next-line no-control-regex
lResolvedDependency.resolved.replace(/\u0000#/g, "#")
)
)
)
)
lResolvedDependency.resolved.replace(/\u0000#/g, "#"),
),
),
),
),
);
} catch (pError) {
lResolvedDependency.couldNotResolve = true;
Expand Down
28 changes: 28 additions & 0 deletions src/extract/resolve/is-built-in.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { builtinModules } from "node:module";

function getBuiltIns(pResolveOptions) {
// builtinModules does not expose all builtin modules for #reasons -
// see https://github.com/nodejs/node/issues/42785. In stead we could use
// isBuiltin, but that is not available in node 16.14, the lowest version
// of node dependency-cruiser currently supports. So we add the missing
// modules here.
let lReturnValue = builtinModules.concat(["test", "node:test"]);

if (pResolveOptions?.builtInModules?.override) {
lReturnValue = pResolveOptions?.builtInModules?.override;
}
if (pResolveOptions?.builtInModules?.add) {
lReturnValue = lReturnValue.concat(pResolveOptions.builtInModules.add);
}
return lReturnValue;
}

/**
*
* @param {string} pModuleName - the unresolved module name
* @param {*} pResolveOptions
* @returns {boolean} - true if the module is a built-in module
*/
export function isBuiltin(pModuleName, pResolveOptions) {
return getBuiltIns(pResolveOptions).includes(pModuleName);
}
15 changes: 4 additions & 11 deletions src/extract/resolve/resolve-amd.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import { accessSync, R_OK } from "node:fs";
import { relative, join } from "node:path";
import { builtinModules } from "node:module";
import memoize from "lodash/memoize.js";
import pathToPosix from "../../utl/path-to-posix.mjs";

// builtinModules does not expose all builtin modules for #reasons -
// see https://github.com/nodejs/node/issues/42785. In stead we could use
// isBuiltin, but that is not available in node 16.14, the lowest version
// of node dependency-cruiser currently supports. So we add the missing
// modules here.
// b.t.w. code is duplicated in resolve-cjs.mjs
const REALLY_BUILTIN_MODULES = builtinModules.concat(["test", "node:test"]);
import { isBuiltin } from "./is-built-in.mjs";

const fileExists = memoize((pFile) => {
try {
Expand Down Expand Up @@ -46,6 +38,7 @@ export function resolveAMD(
pStrippedModuleName,
pBaseDirectory,
pFileDirectory,
pResolveOptions,
) {
// lookups:
// - [x] could be relative in the end (implemented)
Expand All @@ -61,10 +54,10 @@ export function resolveAMD(

return {
resolved: lResolvedPath,
coreModule: REALLY_BUILTIN_MODULES.includes(pStrippedModuleName),
coreModule: isBuiltin(pStrippedModuleName, pResolveOptions),
followable: fileExists(lResolvedPath) && lResolvedPath.endsWith(".js"),
couldNotResolve:
!REALLY_BUILTIN_MODULES.includes(pStrippedModuleName) &&
!isBuiltin(pStrippedModuleName, pResolveOptions) &&
!fileExists(lResolvedPath),
};
}
Expand Down
12 changes: 2 additions & 10 deletions src/extract/resolve/resolve-cjs.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import { relative } from "node:path";
import { builtinModules } from "node:module";
import pathToPosix from "../../utl/path-to-posix.mjs";
import { isFollowable } from "./module-classifiers.mjs";
import { resolve } from "./resolve.mjs";

// builtinModules does not expose all builtin modules for #reasons -
// see https://github.com/nodejs/node/issues/42785. In stead we could use
// isBuiltin, but that is not available in node 16.14, the lowest version
// of node dependency-cruiser currently supports. So we add the missing
// modules here.
// b.t.w. code is duplicated in resolve-amd.mjs
const REALLY_BUILTIN_MODULES = builtinModules.concat(["test", "node:test"]);
import { isBuiltin } from "./is-built-in.mjs";

function addResolutionAttributes(
pBaseDirectory,
Expand All @@ -20,7 +12,7 @@ function addResolutionAttributes(
) {
let lReturnValue = {};

if (REALLY_BUILTIN_MODULES.includes(pModuleName)) {
if (isBuiltin(pModuleName, pResolveOptions)) {
lReturnValue.coreModule = true;
} else {
try {
Expand Down
14 changes: 9 additions & 5 deletions src/main/resolve-options/normalize.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function pushPlugin(pPlugins, pPluginToPush) {
async function compileResolveOptions(
pResolveOptions,
pTSConfig,
pResolveOptionsFromDCConfig
pResolveOptionsFromDCConfig,
) {
let lResolveOptions = {};

Expand Down Expand Up @@ -113,7 +113,7 @@ async function compileResolveOptions(
// or with the supported ones.
extensions:
pResolveOptions.extensions || DEFAULT_RESOLVE_OPTIONS.extensions,
})
}),
);
}

Expand All @@ -124,7 +124,7 @@ async function compileResolveOptions(
...pResolveOptions,
...getNonOverridableResolveOptions(
pResolveOptionsFromDCConfig?.cachedInputFileSystem?.cacheDuration ??
DEFAULT_CACHE_DURATION
DEFAULT_CACHE_DURATION,
),
};
}
Expand All @@ -139,7 +139,7 @@ async function compileResolveOptions(
export default async function normalizeResolveOptions(
pResolveOptions,
pOptions,
pTSConfig
pTSConfig,
) {
const lRuleSet = pOptions?.ruleSet ?? {};
// eslint-disable-next-line no-return-await
Expand All @@ -160,11 +160,15 @@ export default async function normalizeResolveOptions(
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 ?? {}
pOptions?.enhancedResolveOptions ?? {},
);
}

0 comments on commit ddd2ee6

Please sign in to comment.