Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(inspector): analyzer #2762

Merged
merged 25 commits into from
Aug 9, 2023
Merged

feat(inspector): analyzer #2762

merged 25 commits into from
Aug 9, 2023

Conversation

enkot
Copy link
Contributor

@enkot enkot commented Jun 13, 2023

Implements #2682

This PR adds utilities usage info to UnoCSS Inspector (under additional Analyzer tab), the same way it was done for WindiCSS.
Screenshot 2023-06-14 at 00 18 26
Screenshot 2023-06-14 at 00 12 26
Screenshot 2023-06-14 at 00 12 08

Tradeoffs:

  • Because UnoCSS uses Set as main data structure to add/store tokens (in the core, presets, extractors etc.), new CountableSet data structure was introduced. The main idea is that besides actual value it stores count number linked to the value, which increments each time the same value is added to the CountableSet.
    It is based on the Map and has the same API as Set, but with additional getCount and setCount methods. Because of performance reasons CountableSet is opt-in and utilised only for Inspector needs.
    ☝️ But currently there is a problem that extractors should also "know" about what data structure to use: Set or CountableSet, which looks like breaking change.
    Alternative solution I see is to implement separate extractors and generate method only for Analyzer needs. But it feels like an overkill.

Problems:

  • Because Analyzer uses parseColor util from @unocss/preset-mini/utils - Inspector package should build after preset-mini package, which currently is not the case. The fast workaround was to do this:
"build": "rimraf packages/*/dist && esno scripts/copy-files.ts && pnpm -r --filter=./packages/* --filter=!./packages/inspector run build && nr build:inspector && pnpm -r run build-post",

I know it doesn't look good and for sure there is more correct solution 🙈
Also the design/icons is topic to discuss :)

I would be grateful for a review!

@enkot enkot requested a review from antfu as a code owner June 13, 2023 22:14
@netlify
Copy link

netlify bot commented Jun 13, 2023

Deploy Preview for unocss ready!

Name Link
🔨 Latest commit 91f6944
🔍 Latest deploy log https://app.netlify.com/sites/unocss/deploys/64d3908fcc7c24000804a3a4
😎 Deploy Preview https://deploy-preview-2762--unocss.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@@ -0,0 +1,66 @@
export class CountableSet<K> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about extending Set, override the add function to count, and introduce an additional function to get the counts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like this, right?
E.g:

export class CountableSet<K> extends Set {
  _map = new Map<K, number>()

  add(key: K) {
    super.add(key)
    this._map.set(key, (this._map.get(key) ?? 0) + 1)
    return this
  }

  getCount(key: K) {
    return this._map.get(key) ?? 0
  }
}

But in this case Set's values with be duplicated as Map's keys. Not sure if it is a problem 🤔

Copy link
Contributor Author

@enkot enkot Jun 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and we also need delete method:

delete(key: K) {
  super.delete(key)
  return this._map.delete(key)
}

as well as clear() :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :)

@enkot
Copy link
Contributor Author

enkot commented Jun 17, 2023

@antfu Reimplemented CountableSet to extend Set, but there is weird error on build-post

Sometimes this._map is undefined. It feels like something tries to replace context for Set method calls, not sure.
Trying to investigate that, but will be grateful for your help :sleuth_or_spy:

There is interesting problem with extending Set. When our constructor calls super(...args), Set's constructor calls this.add under the hood. But because _map is not initialised yet and our add method tries to use it, we get:

Uncaught TypeError: Cannot read properties of undefined (reading 'set')

Reproduction:

class CountableSet extends Set {
  constructor(...args) {
    super(...args);
    this._map = new Map()
  }

  add(key) {
    this._map.set(key, 1)
  }
}

const mySet = new CountableSet(['foo', 'bar']);

// > Uncaught TypeError: Cannot read properties of undefined (reading 'set')

Unfortunately super() can't be called after accessing 'this'.
The solve this we need to check if this._map exists, and if no - create it:

class CountableSet extends Set {
  constructor(...args) {
    super(...args);
  }

  add(key) {
    if (!this._map) this._map = new Map()
    this._map.set(key, 1)
  }
}

But it is dirty workaround as for me.

@enkot
Copy link
Contributor Author

enkot commented Jun 19, 2023

@antfu Maybe lets stick with the initial implementation of CountableSet?
We can use implements to ensure that CountableSet behaves the same way:

export class CountableSet<K> implements Set<K> {
// ...

@enkot
Copy link
Contributor Author

enkot commented Jun 23, 2023

@antfu @zyyv Could you please share your thoughts on extending Set? - #2762 (comment)

@antfu
Copy link
Member

antfu commented Jun 23, 2023

The design looks neat! Thanks. I will need some more time to review the changes to the core and how things are categorized

@enkot
Copy link
Contributor Author

enkot commented Jun 23, 2023

Thanks!
Currently categories should be specified explicitly. The solution I like more - is adding another category field to the RuleMeta object.

@zyyv
Copy link
Member

zyyv commented Jul 15, 2023

Thanks! Currently categories should be specified explicitly. The solution I like more - is adding another category field to the RuleMeta object.

Agree, this is a simple but complicated work, (such as autoComplete), we can use file management temporarily, and then we can send pr separately for update.

@antfu
Copy link
Member

antfu commented Jul 23, 2023

Screenshot 2023-07-23 at 17 49 57

It seems there is something wrong since the latest pushes, the usage does not show the correct count I think. Would be appreciated if you can have a look.

In addition, I think it could be nice to list which modules are contributing those utilities by providing a popup instead of directly going to the interactive search, similar to this:

image

@enkot
Copy link
Contributor Author

enkot commented Aug 3, 2023

@antfu Done :) Added the popup which shows the list of modules where target utility/color/shortcut is used.
It also allows to copy the name or go to the docs.

Screenshot 2023-08-03 at 22 10 15 Screenshot 2023-08-03 at 22 10 31 Screenshot 2023-08-03 at 22 12 34

@@ -176,7 +176,7 @@ function openEditor(id: string) {
<sup op50 ml-0.5>{{ item.count }}</sup>
</span>
<template #popper>
<ReuseDropdown :matched="item" :show-docs="false" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linter converts showDocs to show-docs, but it doesn't work with ReusableTemplate, so renamed it to docs as fast fix.

@antfu
Copy link
Member

antfu commented Aug 9, 2023

Thanks a lot, great work! Sorry for taking so long

@antfu antfu enabled auto-merge August 9, 2023 13:16
@antfu antfu added this pull request to the merge queue Aug 9, 2023
Merged via the queue into unocss:main with commit ef6a3bc Aug 9, 2023
10 checks passed
@enkot
Copy link
Contributor Author

enkot commented Aug 9, 2023

Thanks! Great news 🥳
But aren't we planning to have suggested shortcuts?

@@ -4,6 +4,8 @@ export class CountableSet<K> extends Set<K> {
constructor(values?: Iterable<K>) {
super(values)
this._map ??= new Map()
for (const value of values ?? [])
Copy link
Contributor Author

@enkot enkot Aug 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antfu Why do we need this loop? add() method will be called twice for each value, because super() already does in the parent constructor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because super does not add to _map

Copy link
Contributor Author

@enkot enkot Aug 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it calls our add() method which does this, isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add some test and remove it if not necessary

@antfu
Copy link
Member

antfu commented Aug 14, 2023

I feel suggested shortcuts might not be as useful as I originally implemented in Windi, while adding a bunch of complexity. I guess it's better to define those shortcuts from a Design System rather that from the usages as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants