gabotechs/dep-tree

Machine-Parsable Render Mode?

belong-bryce opened this issue · 7 comments

Hello Gabriel. I want to let you know that discovering this package on /r/javascript made my day today.

What a terrific (and fast!) tool that I wish I discovered a week ago!

I'm in the middle of trying to write a change detector for a CI/CD pipeline and the functionality I'm looking for is very close to what dep-tree offers.

Was wondering if you would consider adding another command -- similar to render -- that could output the dependency tree to JSON format (or even YAML)?

Feature Request Summary

For example, given the following output from render:

index.tsx
│
├▷../../../../libs/providers/products/products.ts
└▷│../../../../libs/utils/analytics/commonEvents.ts
  ││
  └│▷../../../../libs/providers/_factory.tsx
   ├▷../../../../libs/utils/analytics/constants.ts
   └▷../../../../libs/utils/url.ts

It would be cool to have an option to output something like this to stdout:

{
   /* parent dep, has list of child deps */
   "index.tsx": [
      /* item plain string means no further child deps */
      "../../../../libs/providers/products/products.ts",
      /* object means further child deps follow */
      {
         /* process continues, recursively */
         "../../../../libs/utils/analytics/commonEvents.ts": [
            /* leaf nodes -- no further sub-deps */
            "../../../../libs/providers/_factory.tsx",
            "../../../../libs/utils/analytics/constants.ts",
            "../../../../libs/utils/url.ts"
         ]
      }
   ]
}

(This syntax is just a suggestion, not a requirement)

Suggested Interface

This could either be:

  1. A new command (eg dep-tree parse ...)
  2. An output/format flag on the existing render command (eg dep-tree render ... --output=json)

If we go with option 2, it could either be:

  1. --output=<format> like kubectl
  2. --json like Yarn workspace tools

Prior Art

I've evaluated some other options but none of them have the ease-of-operation nor the speed of your package. Here's what I've tried:

@vercel/nft

Node File Trace comes close but:

👎 It has no CLI
👎 It's much slower
👎 It has a strange bug/behaviour where the current working directory changes the output

dependency-cruiser

Dependency Cruiser is very powerful but:

👎 Is much more verbose in output
👎 Harder to use (not as targeted to the exact use-case of dep-tree
👎 Appears unable to cross the package.json boundaries

dependency-tree

Dependency Tree has more of the same issues:

👎 Is slower
👎 Requires more onerous configuration
👎 inherits bugs from the underlying precinct module


The reason I ask this request of you is because your package already has the following advantages:

✅ speed
✅ correctness
✅ appropriateness to my use-case
✅ punches through package boundaries without issue

And would be perfect if I could get a machine-readable output! 😁

Please let me know if this is something you'd consider adding.

Thanks
Bryce

Also, thank you for considering this request and for your time contributing to Open Source! 😁

Hi Bryce! really glad you liked it.

Your request makes total sense, I can imagine myself also using a feature like this, so I am down to give it a try.

But first, there are some challenges that need some thought in order to print in structured formats:

Circular dependencies

Imagine a render that looks like this

index.tsx
│
├▷../../../../libs/providers/products/products.ts
└▷│../../../../libs/utils/analytics/commonEvents.ts◁─┐
  ││                                                 │
  └│▷../../../../libs/providers/_factory.tsx         │
   ├▷../../../../libs/utils/analytics/constants.ts ──┘
   └▷../../../../libs/utils/url.ts

Expanding it in a structured format would result in an endless recursive expanding of the nodes:

{
   "index.tsx": [
      "../../../../libs/providers/products/products.ts",
      {
         "../../../../libs/utils/analytics/commonEvents.ts": [
            "../../../../libs/providers/_factory.tsx",
            {
                "../../../../libs/utils/analytics/constants.ts": [
                  {
                     "../../../../libs/utils/analytics/commonEvents.ts": [
                        "../../../../libs/providers/_factory.tsx",
                        {
                            "../../../../libs/utils/analytics/constants.ts": [
                            /* recursively expanding */
                            ],
                        },
                        "../../../../libs/utils/url.ts"
                     ]
                  }
                ],
            },
            "../../../../libs/utils/url.ts"
         ]
      }
   ]
}

I think this could be avoided either by ommiting the render of circular dependencies or just throwing a runtime error whenever a circular dep is detected while rendering in strucutred format.
Do you have any opinions about this? I think throwing a runtime error is the most restrictive and safe thing to do for a first iteration.

A child pointed by two or more nodes

index.tsx
│
├▷../../../../libs/providers/products/products.ts
│ │
└─├▷../../../../libs/utils/analytics/commonEvents.ts
  │ │
  └─├▷../../../../libs/providers/_factory.tsx
    ├▷../../../../libs/utils/analytics/constants.ts
    └▷../../../../libs/utils/url.ts

In this example commonEvents.ts is pointed by index.ts and products.ts

The structured output of this would duplicate entries for the three last leaf nodes:

{
   "index.tsx": [
      {
        "../../../../libs/providers/products/products.ts": [
          "../../../../libs/providers/_factory.tsx",
          {
             "../../../../libs/utils/analytics/commonEvents.ts": [
                "../../../../libs/providers/_factory.tsx",
                "../../../../libs/utils/analytics/constants.ts",
                "../../../../libs/utils/url.ts"
             ]
          }
        ],
      },
      {
         "../../../../libs/utils/analytics/commonEvents.ts": [
            "../../../../libs/providers/_factory.tsx",
            "../../../../libs/utils/analytics/constants.ts",
            "../../../../libs/utils/url.ts"
         ]
      }
   ]
}

The output would be very verbose, but that might be fine? would this fit your use-case?

I think this could be avoided either by omitting the render of circular dependencies or just throwing a runtime error whenever a circular dep is detected while rendering in structured format.

This is a valid edge case. I'd prefer an output that doesn't result in an error, because if the process throws then the consumer probably can't use the util until "someone" in the org refactors the codebase first... we all know how often that gets prioritised 😂 .

Your suggestion of simply omitting the render of circular dependencies works for me 👍.

For bonus points, maybe some metadata/report at the end of "detectedCirculars" would be useful to someone, but not needed for my use-case.

In this example commonEvents.ts is pointed by both index.ts and products.ts
The structured output of this would duplicate entries for the three last leaf nodes...
The output would be very verbose, but that might be fine? would this fit your use-case?

That would be fine to me, it would let me do whatever analysis I'd need to do on both files, or ignore if not relevant / already dealt-with.

Plus, how can you do data engineering unless your data is massive? 😹

Thanks mate
Bryce

For bonus points, maybe some metadata/report at the end of "detectedCirculars" would be useful to someone, but not needed for my use-case.

that sounds fine, I think it will actually be easy to do, I imagine something like:

{
  "tree": {
    ...the actual tree
  },
  "circularDependencies": {
    "from-a": "to-b",
    ...more
  }
}

That would be fine to me, it would let me do whatever analysis I'd need to do on both files, or ignore if not relevant / already dealt-with.

👍

I proposed a slightly different approach for rendering the dependency tree as a json output: #7, feel free to give some comments about the format

released in 0.9.0