Skip to content

Commit ef6a3bc

Browse files
enkotantfu
andauthored
feat(inspector): analyzer (#2762)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
1 parent 382c832 commit ef6a3bc

File tree

36 files changed

+2495
-733
lines changed

36 files changed

+2495
-733
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"esno": "^0.17.0",
8989
"execa": "^7.2.0",
9090
"fast-glob": "^3.3.1",
91+
"floating-vue": "2.0.0-beta.24",
9192
"fs-extra": "^11.1.1",
9293
"gzip-size": "^6.0.0",
9394
"jsdom": "^22.1.0",

packages/core/src/extractors/split.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { Extractor } from '../types'
33
export const defaultSplitRE = /[\\:]?[\s'"`;{}]+/g
44
export const splitWithVariantGroupRE = /([\\:]?[\s"'`;<>]|:\(|\)"|\)\s)/g
55

6-
export function splitCode(code: string) {
7-
return [...new Set(code.split(defaultSplitRE))]
6+
export function splitCode(code: string): string[] {
7+
return code.split(defaultSplitRE)
88
}
99

1010
export const extractorSplit: Extractor = {

packages/core/src/generator/index.ts

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createNanoEvents } from '../utils/events'
2-
import type { CSSEntries, CSSObject, DynamicRule, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, RuleContext, RuleMeta, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantHandlerContext, VariantMatchedResult } from '../types'
2+
import type { CSSEntries, CSSObject, DynamicRule, ExtendedTokenInfo, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, RuleContext, RuleMeta, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantHandlerContext, VariantMatchedResult } from '../types'
33
import { resolveConfig } from '../config'
4-
import { CONTROL_SHORTCUT_NO_MERGE, TwoKeyMap, e, entriesToCss, expandVariantGroup, isRawUtil, isStaticShortcut, isString, noop, normalizeCSSEntries, normalizeCSSValues, notNull, toArray, uniq, warnOnce } from '../utils'
4+
import { CONTROL_SHORTCUT_NO_MERGE, CountableSet, TwoKeyMap, e, entriesToCss, expandVariantGroup, isCountableSet, isRawUtil, isStaticShortcut, isString, noop, normalizeCSSEntries, normalizeCSSValues, notNull, toArray, uniq, warnOnce } from '../utils'
55
import { version } from '../../package.json'
66
import { LAYER_DEFAULT, LAYER_PREFLIGHTS } from '../constants'
77

@@ -39,21 +39,40 @@ export class UnoGenerator<Theme extends object = object> {
3939
this.events.emit('config', this.config)
4040
}
4141

42+
applyExtractors(
43+
code: string,
44+
id?: string,
45+
extracted?: Set<string>,
46+
): Promise<Set<string>>
47+
applyExtractors(
48+
code: string,
49+
id?: string,
50+
extracted?: CountableSet<string>,
51+
): Promise<CountableSet<string>>
4252
async applyExtractors(
4353
code: string,
4454
id?: string,
45-
extracted = new Set<string>(),
46-
): Promise<Set<string>> {
55+
extracted: Set<string> | CountableSet<string> = new Set<string>(),
56+
): Promise<Set<string> | CountableSet<string>> {
4757
const context: ExtractorContext = {
4858
original: code,
4959
code,
5060
id,
5161
extracted,
62+
envMode: this.config.envMode,
5263
}
5364

5465
for (const extractor of this.config.extractors) {
5566
const result = await extractor.extract?.(context)
56-
if (result) {
67+
68+
if (!result)
69+
continue
70+
71+
if (isCountableSet(result) && isCountableSet(extracted)) {
72+
for (const token of result)
73+
extracted.setCount(token, extracted.getCount(token) + result.getCount(token))
74+
}
75+
else {
5776
for (const token of result)
5877
extracted.add(token)
5978
}
@@ -127,31 +146,54 @@ export class UnoGenerator<Theme extends object = object> {
127146
this._cache.set(cacheKey, null)
128147
}
129148

149+
generate(
150+
input: string | Set<string> | CountableSet<string> | string[],
151+
options?: GenerateOptions<false>
152+
): Promise<GenerateResult<Set<string>>>
153+
generate(
154+
input: string | Set<string> | CountableSet<string> | string[],
155+
options?: GenerateOptions<true>
156+
): Promise<GenerateResult<Map<string, ExtendedTokenInfo<Theme>>>>
130157
async generate(
131-
input: string | Set<string> | string[],
132-
options: GenerateOptions = {},
133-
): Promise<GenerateResult> {
158+
input: string | Set<string> | CountableSet<string> | string[],
159+
options: GenerateOptions<boolean> = {},
160+
): Promise<GenerateResult<unknown>> {
134161
const {
135162
id,
136163
scope,
137164
preflights = true,
138165
safelist = true,
139166
minify = false,
167+
extendedInfo = false,
140168
} = options
141169

142-
const tokens: Readonly<Set<string>> = isString(input)
143-
? await this.applyExtractors(input, id)
170+
const tokens: Readonly<Set<string> | CountableSet<string>> = isString(input)
171+
? await this.applyExtractors(
172+
input,
173+
id,
174+
extendedInfo
175+
? new CountableSet<string>()
176+
: new Set<string>(),
177+
)
144178
: Array.isArray(input)
145-
? new Set(input)
179+
? new Set<string>(input)
146180
: input
147181

148-
if (safelist)
149-
this.config.safelist.forEach(s => tokens.add(s))
182+
if (safelist) {
183+
this.config.safelist.forEach((s) => {
184+
// We don't want to increment count if token is already in the set
185+
if (!tokens.has(s))
186+
tokens.add(s)
187+
})
188+
}
150189

151190
const nl = minify ? '' : '\n'
152191

153192
const layerSet = new Set<string>([LAYER_DEFAULT])
154-
const matched = new Set<string>()
193+
const matched = extendedInfo
194+
? new Map<string, ExtendedTokenInfo<Theme>>()
195+
: new Set<string>()
196+
155197
const sheet = new Map<string, StringifiedUtil<Theme>[]>()
156198
let preflightsMap: Record<string, string> = {}
157199

@@ -163,7 +205,15 @@ export class UnoGenerator<Theme extends object = object> {
163205
if (payload == null)
164206
return
165207

166-
matched.add(raw)
208+
if (matched instanceof Map) {
209+
matched.set(raw, {
210+
data: payload,
211+
count: isCountableSet(tokens) ? tokens.getCount(raw) : -1,
212+
})
213+
}
214+
else {
215+
matched.add(raw)
216+
}
167217

168218
for (const item of payload) {
169219
const parent = item[3] || ''

packages/core/src/types.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { LoadConfigResult } from 'unconfig'
22
import type MagicString from 'magic-string'
33
import type { UnoGenerator } from './generator'
4-
import type { BetterMap } from './utils'
4+
import type { BetterMap, CountableSet } from './utils'
55

66
export type Awaitable<T> = T | Promise<T>
77
export type Arrayable<T> = T | T[]
@@ -116,7 +116,8 @@ export interface ExtractorContext {
116116
readonly original: string
117117
code: string
118118
id?: string
119-
extracted: Set<string>
119+
extracted: Set<string> | CountableSet<string>
120+
envMode?: 'dev' | 'build'
120121
}
121122

122123
export interface PreflightContext<Theme extends object = object> {
@@ -138,7 +139,7 @@ export interface Extractor {
138139
*
139140
* Return `undefined` to skip this extractor.
140141
*/
141-
extract?(ctx: ExtractorContext): Awaitable<Set<string> | string[] | undefined | void>
142+
extract?(ctx: ExtractorContext): Awaitable<Set<string> | CountableSet<string> | string[] | undefined | void>
142143
}
143144

144145
export interface RuleMeta {
@@ -781,12 +782,12 @@ RequiredByKey<UserConfig<Theme>, 'mergeSelectors' | 'theme' | 'rules' | 'variant
781782
separators: string[]
782783
}
783784

784-
export interface GenerateResult {
785+
export interface GenerateResult<T = Set<string>> {
785786
css: string
786787
layers: string[]
787788
getLayer(name?: string): string | undefined
788789
getLayers(includes?: string[], excludes?: string[]): string
789-
matched: Set<string>
790+
matched: T
790791
}
791792

792793
export type VariantMatchedResult<Theme extends object = object> = readonly [
@@ -840,7 +841,21 @@ export interface UtilObject {
840841
noMerge: boolean | undefined
841842
}
842843

843-
export interface GenerateOptions {
844+
/**
845+
* Returned from `uno.generate()` when `extendedInfo` option is enabled.
846+
*/
847+
export interface ExtendedTokenInfo<Theme extends object = object> {
848+
/**
849+
* Stringified util data
850+
*/
851+
data: StringifiedUtil<Theme>[]
852+
/**
853+
* Return -1 if the data structure is not countable
854+
*/
855+
count: number
856+
}
857+
858+
export interface GenerateOptions<T extends boolean> {
844859
/**
845860
* Filepath of the file being processed.
846861
*/
@@ -868,4 +883,9 @@ export interface GenerateOptions {
868883
* @experimental
869884
*/
870885
scope?: string
886+
887+
/**
888+
* If return extended "matched" with payload and count
889+
*/
890+
extendedInfo?: T
871891
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export class CountableSet<K> extends Set<K> {
2+
_map: Map<K, number>
3+
4+
constructor(values?: Iterable<K>) {
5+
super(values)
6+
this._map ??= new Map()
7+
for (const value of values ?? [])
8+
this.add(value)
9+
}
10+
11+
add(key: K) {
12+
this._map ??= new Map()
13+
this._map.set(key, (this._map.get(key) ?? 0) + 1)
14+
return super.add(key)
15+
}
16+
17+
delete(key: K) {
18+
this._map.delete(key)
19+
return super.delete(key)
20+
}
21+
22+
clear() {
23+
this._map.clear()
24+
super.clear()
25+
}
26+
27+
getCount(key: K) {
28+
return this._map.get(key) ?? 0
29+
}
30+
31+
setCount(key: K, count: number) {
32+
this._map.set(key, count)
33+
return super.add(key)
34+
}
35+
}
36+
37+
export function isCountableSet<T = string>(value: any): value is CountableSet<T> {
38+
return value instanceof CountableSet
39+
}

packages/core/src/utils/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ export * from './object'
33
export * from './basic'
44
export * from './helpers'
55
export * from './map'
6+
export * from './countable-set'
67
export * from './layer'
7-
export * from './variantGroup'
8+
export * from './variant-group'
89
export * from './warn'
910
export * from './handlers'

packages/extractor-arbitrary-variants/src/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,27 @@ export const quotedArbitraryValuesRE = /(?:[\w&:[\]-]|\[\S+=\S+\])+\[\\?['"]?\S+
55
export const arbitraryPropertyRE = /\[(\\\W|[\w-])+:[^\s:]*?("\S+?"|'\S+?'|`\S+?`|[^\s:]+?)[^\s:]*?\)?\]/g
66
const arbitraryPropertyCandidateRE = /^\[(\\\W|[\w-])+:['"]?\S+?['"]?\]$/
77

8-
export function splitCodeWithArbitraryVariants(code: string) {
9-
const result = new Set<string>()
8+
export function splitCodeWithArbitraryVariants(code: string): string[] {
9+
const result: string[] = []
1010

1111
for (const match of code.matchAll(arbitraryPropertyRE)) {
1212
if (!code[match.index! - 1]?.match(/^[\s'"`]/))
1313
continue
1414

15-
result.add(match[0])
15+
result.push(match[0])
1616
}
1717

1818
for (const match of code.matchAll(quotedArbitraryValuesRE))
19-
result.add(match[0])
19+
result.push(match[0])
2020

2121
code
2222
.split(defaultSplitRE)
2323
.forEach((match) => {
2424
if (isValidSelector(match) && !arbitraryPropertyCandidateRE.test(match))
25-
result.add(match)
25+
result.push(match)
2626
})
2727

28-
return [...result]
28+
return result
2929
}
3030

3131
export const extractorArbitraryVariants: Extractor = {

packages/inspector/client/components.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,23 @@ export {}
77

88
declare module 'vue' {
99
export interface GlobalComponents {
10+
Analyzer: typeof import('./components/Analyzer.vue')['default']
11+
AnalyzerItem: typeof import('./components/AnalyzerItem.vue')['default']
1012
CodeMirror: typeof import('./components/CodeMirror.vue')['default']
13+
Copy: typeof import('./components/Copy.vue')['default']
1114
FileIcon: typeof import('./components/FileIcon.vue')['default']
1215
ModuleId: typeof import('./components/ModuleId.vue')['default']
1316
ModuleInfo: typeof import('./components/ModuleInfo.vue')['default']
1417
ModuleTreeNode: typeof import('./components/ModuleTreeNode.vue')['default']
1518
NarBar: typeof import('./components/NarBar.vue')['default']
1619
Overview: typeof import('./components/Overview.vue')['default']
20+
OverviewTabs: typeof import('./components/OverviewTabs.vue')['default']
1721
ReplPlayground: typeof import('./components/ReplPlayground.vue')['default']
1822
RouterLink: typeof import('vue-router')['RouterLink']
1923
RouterView: typeof import('vue-router')['RouterView']
2024
Sidebar: typeof import('./components/Sidebar.vue')['default']
2125
StatusBar: typeof import('./components/StatusBar.vue')['default']
26+
Tabs: typeof import('./components/Tabs.vue')['default']
2227
TitleBar: typeof import('./components/TitleBar.vue')['default']
2328
}
2429
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy