sverweij / dependency-cruiser

Validate and visualize dependencies. Your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
https://npmjs.com/dependency-cruiser
MIT License
5.27k stars 250 forks source link

Feature request: consider symlinked workspaces #913

Closed swachter closed 7 months ago

swachter commented 8 months ago

When workspaces are used, NPM symlinks workspace folders into the node_modules folder. If corresponding dependencies are defined in package.json of the root project then files can be imported using these "workspace dependencies".

When a mono repo is analyzed e.g. by depcruise src packages/*/src then files in workspaces that are imported using such "workspace dependencies" are reported as orphans.

Expected Behavior

Dependency cruiser should recognize that imports from "workspace dependencies" target files in workspaces.

Current Behavior

Files in workspaces that are imported in the main sources by "workspace dependencies" only, count as orphans.

sverweij commented 8 months ago

Hi @swachter thanks for reaching out! I would expect this to work out of the box, so I'm curious what's going on exactly. Do you have an example (doesn't have to be the complete repository, a small reproduction sample would already be helpful).

swachter commented 8 months ago

Hi @sverweij many thanks for looking into this!

I prepared a repo with a minimized reproduction sample.

sverweij commented 8 months ago

Thanks @swachter that was very helpful!

It took a bit to wrap my head around the setup, but once it had and I viewed the graph understanding dawned:

dependency-graph

src/index.mts imports the transpiled constants.mjs from the @monorepo/utils/resources package and not the typescript file it originates from. As a consequence the TypeScript source will indeed never be imported, which technically makes it an orphan, even though conceptually it isn't - it was used to generate out/constants.mjs.

Why this is so - module resolution

Expected?

I guess that in the symlinked workspaces setup it is an expected pattern to only export the compiled-to-javascript dist files of packages and not the original sources?

Does it make sense, knowing the above, that index.mts depends on the compiled constants.mjs?

A workaround

I do understand that for a conceptual overview you still might want to see links/ dependencies to the original TypeScript. If so:

diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs
index 1696d4e..30c6d62 100644
--- a/.dependency-cruiser.cjs
+++ b/.dependency-cruiser.cjs
@@ -316,7 +316,7 @@ module.exports = {
       /* List of conditions to check for in the exports field.
          Only works when the 'exportsFields' array is non-empty.
       */
-      conditionNames: ["import", "require", "node", "default", "types"],
+      conditionNames: ["source-import", "import", "require", "node", "default", "types"],
       /*
          The extensions, by default are the same as the ones dependency-cruiser
          can access (run `npx depcruise --info` to see which ones that are in
diff --git a/packages/utils/package.json b/packages/utils/package.json
index 6f78013..a1d1ae1 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -14,6 +14,7 @@
   "type": "module",
   "exports": {
     "./*": {
+      "source-import": "./src/*.mts",
       "import": "./out/*.mjs",
       "types": "./out/*.d.mts"
     }

Another workaround, for completeness sake only

If you'd want to export the TypeScript source instead of its compiled version (which I don't expect you do ...) you can update the exports clause in packages/utils/ to references the src files.

diff --git a/packages/utils/package.json b/packages/utils/package.json
index 6f78013..a1d1ae1 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -14,6 +14,7 @@
   "type": "module",
   "exports": {
     "./*": {
-      "import": "./out/*.mjs",
+      "import": "./src/*.mts",
       "types": "./out/*.d.mts"
     }
swachter commented 8 months ago

Many thanks for the analysis! I understand that a general mechanism for detecting such indirect dependencies would be complicated. I assume it would be necessary to consider what output directories are configured for tsc and how these outputs are exported. Could source maps come to the rescue here? However, for now I will proceed with your suggested workaround with the extra export condition.

github-actions[bot] commented 7 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.