rangle / radius-tracker

Measure adoption of your design system
MIT License
57 stars 3 forks source link

Does detectHomebrew support styled-components? #35

Closed beamery-tomht closed 1 year ago

beamery-tomht commented 2 years ago

Apologies if this is the wrong place to ask the question and also really great effort writing a static analyser for detecting design system adoption - this is very valuable.

We know a lot of our DS consumers will be using styled-components for their "homebrew" components, as you call them, will radius-tracker be able to work with this? I saw that in your talk it will be focusing on lower-case elements to determine "homebrew".

Another question along a similar line: what about when components are built with other tooling that is not the design system you are measuring adoption for?

smoogly commented 2 years ago

Will radius-tracker be able to work with [styled-components]?

Good question. At the moment, styled components won't be detected. You can see a range of things detected as homebrew in tests: https://github.com/rangle/radius-tracker/blob/fb8287c17011c1f10849e8c44252b23e6c197870/src/lib/detectHomebrew/detectHomebrew.spec.ts

CSS-in-JS libs tend to create components via function calls or tagged literals — those are easy enough to find for specific css-in-js lib, the problem is with number varieties of those libs.

If you have a project using styled components or similar that you want to analyze right now, your best bet is using the tool programmatically, instead of CLI. That way you can use filterImports from resolveDependencies to find imports from the styled components, and then track usages of those imports.

Long term, a plugin system could work for CSS-in-JS libs.

what about when components are built with other tooling that is not the design system you are measuring adoption for?

I'm not sure I understand the question. Could you elaborate?

beamery-tomht commented 2 years ago

Thanks the answer! The tests file makes your current coverage very clear ^_^

That way you can use filterImports from resolveDependencies to find imports from the styled components, and then track usages of those imports.

Okay, thanks for the headsup on this approach!

I'm not sure I understand the question. Could you elaborate?

Sure! However you already answered the question with the link to tests.

But for clarity - I was wondering that if a file has the following code, would it be able to determine these homebrew cases:

import { Button } from 'my-design-system' // all good
import { Select } from 'legacy-internal-component-library' // can this be detected as "homebrew"
import { Filter } from 'chakra-or-something' // also this

As these would also be rendered as capitalised custom elements

Long term, a plugin system could work for CSS-in-JS libs.

I think a plugin system would be beneficial for many use-cases of determining both homebrew and adoption

smoogly commented 2 years ago

To expand on the approach to detecting styled components, here's what you can do right now:

    const project = new Project({ useInMemoryFileSystem: true, compilerOptions: { baseUrl: ".", allowJs: true } });
    project.createSourceFile("tst.jsx", `
        import styled from "styled-components";

        const Button = styled.button\`color: red;\`;        
        const UsesButton = () => <Button/>;

        const Img = styled.img\`width: 200px\`;
        const UsesImg = () => <Img/>;
    `);

    const resolveModule = jest.fn<ReturnType<ResolveModule>, Parameters<ResolveModule>>().mockImplementation(target => project.getSourceFiles().find(f => f.getFilePath() === target) ?? null);
    const dependencies = resolveDependencies(project, resolveModule);

    const findUsages = setupFindUsages(dependencies);

    const importsFromStyledComponents = dependencies.filterImports(imp => imp.moduleSpecifier === "styled-components");

    const styledUsages = importsFromStyledComponents
        .map(getImportNode)
        .map(findUsages)
        .map(({ usages }) => usages)
        .flat();

    expect(styledUsages).toHaveLength(2);
    expect(styledUsages.map(({ use }) => use.print())).toEqual(["Button", "Img"]);

By finding usages of the import node you will already get the components. This loses specificity somewhat, it's not as straightforward to know usage of which component you found. All you know is that it's a usage of a component created from styled components — probably enough for aggregate statistics, e.g. calculating the DS share %.

Each usage also contains a trace array of how that usage was found. Somewhere in there would be an assignment that creates the component itself, but it's not exactly easy to figure out which trace defines a component.


import { Select } from 'legacy-internal-component-library' // can this be detected as "homebrew"

Can't detect homebrew if there's no access to code! Homebrew definition is a component created directly in product codebase.

You could count those imports towards "competition" if you find them directly. Use dependencies.filterImports(imp => imp.moduleSpecifier === "legacy-internal-component-library") to find those imports, and findUsages on those imports.

smoogly commented 1 year ago

It's only been a year!

There's now support for styled components for the purpose of homebrew detection. E.g. in the code below Component is now detected as homebrew, because it directly outputs a DOM element.

import styled from "styled-components"
const Div = styled.div`background: red`
const Component = () => <Div>Hello, World!</Div>

On top of that, tracker supports detection of multiple targets for a while, so imports from multiple component libraries can be tracked separately. See "Multi-target configuration" in the config docs: https://rangle.github.io/radius-tracker/configuration_file#multi-target-configuration

This covers the use case you brought up earlier:

I was wondering that if a file has the following code, would it be able to determine these homebrew cases:

import { Button } from 'my-design-system' // all good
import { Select } from 'legacy-internal-component-library' // can this be detected as "homebrew"
import { Filter } from 'chakra-or-something' // also this

Let me know if you'd like help setting up the tracker, I'm happy to jump on a call and guide you through configuration steps.

BerenikaKotelko commented 1 year ago

Hiya! Just as a follow-up to @beamery-tomht, what we'd like to do is check multiple repos with the report generation script, and we wondered about a way to convert the SQLite data into JSON, if possible. Any tips/advice on that?

BerenikaKotelko commented 1 year ago

Additional context: we already have a JS script that iterates over every repo of interest and downloads them. The idea is to have it perform radius-tracker on each of them, we're just not sure how to deal with the data output, how to combine it into one, for example.

smoogly commented 1 year ago

@BerenikaKotelko, Tracker already implements functionality to download and parse multiple repos, and for combining the results to generate a report. I feel that I might be able to help you set it up over a call, we can probably do it in under an hour, and I'm happy to do it for free. Let me know if you would like that.

Below are some details about configuration.

Please take a look at the config file documentation for running tracker over multiple repos: https://rangle.github.io/radius-tracker/configuration_file And the config backing the sample report data (covering 300 repos): https://github.com/rangle/radius-tracker/blob/17da736e27f325ec3fa7c920b85fd645a0a81a0a/src/lib/cli/test/grafana_samples.ts#L23-L437

Apart from taking care of multiple repos in parallel, running tracker using a config also handles cache, and looks through history to generate the historical stats. You can see the result here: https://observablehq.com/@smoogly/design-system-metrics

The sample report I linked above is also generated using Tracker. You can see how to do it here: https://rangle.github.io/radius-tracker/analysis And I recommend generating this report in CI on a weekly schedule, here's some recommendations how to do that: https://rangle.github.io/radius-tracker/ci_integration

I don't recommend outputting data into a JSON. While absolutely possible to do, the data tends to be huge. Normalized & compressed database backing a sample report has about a million records and is 12mb. JSONs would take enormous amount of space.

BerenikaKotelko commented 1 year ago

@smoogly we've chatted about it and have a few concerns - I think I haven't communicated our needs well enough. It'd be easier over a call I think, if you're still willing to do that - which timezone are you based in?

smoogly commented 1 year ago

I am still happy to help! I've reached out on LinkedIn, but also feel free to drop me a message at arseny.smoogly@rangle.io — let's find a good time for a call.