@purgeon/analyzer-css 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -17,6 +17,7 @@ interface CssVarDeclaration {
17
17
  }
18
18
  interface CssRule {
19
19
  classes: string[];
20
+ dataAttrs: string[];
20
21
  declarations: string[];
21
22
  declaredVars: string[];
22
23
  referencedVars: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/class-graph.ts","../src/css-node.ts","../src/var-graph.ts","../src/css-analyzer.ts","../src/purge-ir.ts","../src/index.ts"],"mappings":";;cAAa;mBACM;UAET;EASR,SAAS;EAIT,gBAAgB,WAAW;EAM3B,UAAU,oBAAoB;EAI9B,WAAW;EAIX,YAAY;;;;UC5BG;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA,iBAAiB;;UAGF,qBAAqB;EACpC,OAAO;;UAGQ;EACf;;;;cCpBW;mBACM;mBACA;mBACA;mBACA;UAET;EASR,QAAQ,iBAAiB;EAIzB,UAAU,iBAAiB,kBAAkB;EAS7C,gBAAgB,kBAAkB;EAIlC,mBAAmB,kBAAkB;EAIrC,eAAe,kBAAkB;EAIjC,aAAa,kBAAkB;EAI/B;IACE,YAAY;IACZ,cAAc;IACd,WAAW;;;;;cC5BF,oBAAoB,eAAe;mBAMjB;WALpB,MAAM;mBAEE;mBACA;cAEY,UAAS;EAItC,iBAAiB;EAIjB,eAAe;YAIL,kBAAkB;YAIlB,YAAY,cAAc,aAAa,MAAM;YAI7C,WAAW,aAAa;YAIxB,cAAc,YAAY,eAAe,cAAc,MAAM;YAW7D,cAAc,QAAQ;UAWxB;;;;iBCnEM,aAAa,OAAO,YAAY;;;cCKnC,cAAe,UAAU,uBAAkB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/class-graph.ts","../src/css-node.ts","../src/var-graph.ts","../src/css-analyzer.ts","../src/purge-ir.ts","../src/index.ts"],"mappings":";;cAAa;mBACM;UAET;EASR,SAAS;EAIT,gBAAgB,WAAW;EAM3B,UAAU,oBAAoB;EAI9B,WAAW;EAIX,YAAY;;;;UC5BG;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA,iBAAiB;;UAGF,qBAAqB;EACpC,OAAO;;UAGQ;EACf;;;;cCrBW;mBACM;mBACA;mBACA;mBACA;UAET;EASR,QAAQ,iBAAiB;EAIzB,UAAU,iBAAiB,kBAAkB;EAS7C,gBAAgB,kBAAkB;EAIlC,mBAAmB,kBAAkB;EAIrC,eAAe,kBAAkB;EAIjC,aAAa,kBAAkB;EAI/B;IACE,YAAY;IACZ,cAAc;IACd,WAAW;;;;;cC3BF,oBAAoB,eAAe;mBAMjB;WALpB,MAAM;mBAEE;mBACA;cAEY,UAAS;EAItC,iBAAiB;EAIjB,eAAe;YAIL,kBAAkB;YAIlB,YAAY,cAAc,aAAa,MAAM;YAI7C,WAAW,aAAa;YAIxB,cAAc,YAAY,eAAe,cAAc,MAAM;YAW7D,cAAc,QAAQ;UAWxB;;;;iBCpEM,aAAa,OAAO,YAAY;;;cCKnC,cAAe,UAAU,uBAAkB"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { AnalyzerPlugin, classesOf, pluginName, splitCompoundSelectors } from "@purgeon/core";
1
+ import { AnalyzerPlugin, classesOf, dataAttrsOf, pluginName, splitCompoundSelectors } from "@purgeon/core";
2
2
  import { transform } from "lightningcss";
3
3
  //#region src/class-graph.ts
4
4
  var ClassGraph = class {
@@ -140,14 +140,17 @@ var CssAnalyzer = class extends AnalyzerPlugin {
140
140
  visitor: {
141
141
  Rule: { style: (rule) => {
142
142
  const classes = /* @__PURE__ */ new Set();
143
+ const dataAttrs = /* @__PURE__ */ new Set();
143
144
  for (const selector of rule.value.selectors) for (const group of splitCompoundSelectors(selector)) {
144
145
  const groupClasses = classesOf(group);
145
146
  for (const className of groupClasses) classes.add(className);
146
147
  for (let i = 0; i < groupClasses.length; i++) for (let j = i + 1; j < groupClasses.length; j++) this.classGraph.addCoOccurrence(groupClasses[i], groupClasses[j]);
148
+ for (const attrKey of dataAttrsOf(group)) dataAttrs.add(attrKey);
147
149
  }
148
150
  for (const className of classes) this.classGraph.addClass(className);
149
151
  currentRule = {
150
152
  classes: [...classes],
153
+ dataAttrs: [...dataAttrs],
151
154
  declarations: (rule.value.declarations?.declarations ?? []).map((declaration) => declaration.property),
152
155
  declaredVars: [],
153
156
  referencedVars: [],
@@ -189,6 +192,7 @@ var CssAnalyzer = class extends AnalyzerPlugin {
189
192
  function toCssPurgeIR(rules) {
190
193
  return rules.map((rule) => ({
191
194
  classes: rule.classes,
195
+ dataAttrs: rule.dataAttrs,
192
196
  declaredVars: rule.declaredVars,
193
197
  referencedVars: rule.referencedVars,
194
198
  varDeclarations: rule.varDeclarations.map((decl) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/class-graph.ts","../src/var-graph.ts","../src/css-analyzer.ts","../src/purge-ir.ts","../src/index.ts"],"sourcesContent":["export class ClassGraph {\n private readonly edges = new Map<string, Set<string>>();\n\n private ensure(className: string): Set<string> {\n let neighbors = this.edges.get(className);\n if (!neighbors) {\n neighbors = new Set();\n this.edges.set(className, neighbors);\n }\n return neighbors;\n }\n\n addClass(className: string): void {\n this.ensure(className);\n }\n\n addCoOccurrence(a: string, b: string): void {\n if (a === b) return;\n this.ensure(a).add(b);\n this.ensure(b).add(a);\n }\n\n neighbors(className: string): Set<string> | undefined {\n return this.edges.get(className);\n }\n\n classes(): IterableIterator<string> {\n return this.edges.keys();\n }\n\n toObject(): Record<string, string[]> {\n return Object.fromEntries([...this.edges].map(([className, neighbors]) => [className, [...neighbors]]));\n }\n}\n","export class VarGraph {\n private readonly declaredIn = new Map<string, Set<string>>();\n private readonly referencedIn = new Map<string, Set<string>>();\n private readonly dependsOn = new Map<string, Set<string>>();\n private readonly usedBy = new Map<string, Set<string>>();\n\n private ensure(map: Map<string, Set<string>>, key: string): Set<string> {\n let values = map.get(key);\n if (!values) {\n values = new Set();\n map.set(key, values);\n }\n return values;\n }\n\n declare(varName: string, moduleId: string): void {\n this.ensure(this.declaredIn, varName).add(moduleId);\n }\n\n reference(varName: string, moduleId: string, declaringVar?: string): void {\n this.ensure(this.referencedIn, varName).add(moduleId);\n\n if (declaringVar && declaringVar !== varName) {\n this.ensure(this.dependsOn, declaringVar).add(varName);\n this.ensure(this.usedBy, varName).add(declaringVar);\n }\n }\n\n declaredModules(varName: string): Set<string> | undefined {\n return this.declaredIn.get(varName);\n }\n\n referencingModules(varName: string): Set<string> | undefined {\n return this.referencedIn.get(varName);\n }\n\n dependenciesOf(varName: string): Set<string> | undefined {\n return this.dependsOn.get(varName);\n }\n\n dependentsOf(varName: string): Set<string> | undefined {\n return this.usedBy.get(varName);\n }\n\n toObject(): {\n declaredIn: Record<string, string[]>;\n referencedIn: Record<string, string[]>;\n dependsOn: Record<string, string[]>;\n } {\n return {\n declaredIn: toRecord(this.declaredIn),\n referencedIn: toRecord(this.referencedIn),\n dependsOn: toRecord(this.dependsOn),\n };\n }\n}\n\nfunction toRecord(map: Map<string, Set<string>>): Record<string, string[]> {\n return Object.fromEntries([...map].map(([key, values]) => [key, [...values]]));\n}\n","import {\n AnalyzerPlugin,\n classesOf,\n pluginName,\n splitCompoundSelectors,\n type OutputBundle,\n type ParseContext,\n type PluginName,\n} from \"@purgeon/core\";\nimport { transform } from \"lightningcss\";\n\nimport { ClassGraph } from \"./class-graph\";\nimport type { CssAnalyzerOptions, CssGraphNode, CssRule, CssVarDeclaration } from \"./css-node\";\nimport { VarGraph } from \"./var-graph\";\n\nfunction normalizeModuleId(id: string): string {\n return id.replace(/\\?.*$/, \"\").replace(/\\\\/g, \"/\");\n}\n\nexport class CssAnalyzer extends AnalyzerPlugin<CssGraphNode> {\n readonly name: PluginName = pluginName(\"css-graph\");\n\n private readonly classGraph = new ClassGraph();\n private readonly varGraph = new VarGraph();\n\n constructor(private readonly options: CssAnalyzerOptions = {}) {\n super();\n }\n\n getClassGraph(): ClassGraph {\n return this.classGraph;\n }\n\n getVarGraph(): VarGraph {\n return this.varGraph;\n }\n\n protected isModuleSupported(id: string): boolean {\n return id.endsWith(\".module.css\");\n }\n\n protected parseModule(code: string, _id: string, _ctx: ParseContext): unknown {\n return code;\n }\n\n protected createNode(id: string): CssGraphNode {\n return { id, imports: [], importers: [], rules: [] };\n }\n\n protected analyzeModule(id: string, _ast: unknown, code: string, node: CssGraphNode): void {\n if (node.rules.length > 0) return;\n\n const rawRules = this.parseCss(code, id);\n const normalizedId = normalizeModuleId(id);\n node.rules = rawRules.map((rule) => ({\n ...rule,\n classes: rule.classes.map((cls) => `${normalizedId}::${cls}`),\n }));\n }\n\n protected analyzeBundle(bundle: OutputBundle): void {\n for (const fileName in bundle) {\n const output = bundle[fileName]!;\n if (output.type !== \"asset\" || !fileName.endsWith(\".css\")) continue;\n\n const source = typeof output.source === \"string\" ? output.source : Buffer.from(output.source).toString(\"utf-8\");\n const node = this.graph.ensureNode(fileName, (id) => this.createNode(id));\n node.rules = this.parseCss(source, fileName);\n }\n }\n\n private parseCss(code: string, id: string): CssRule[] {\n const rules: CssRule[] = [];\n\n let currentRule: CssRule | null = null;\n let currentDeclaredVar: string | null = null;\n let currentVarDeclaration: CssVarDeclaration | null = null;\n\n transform({\n filename: this.options.filename ?? id,\n code: Buffer.from(code),\n visitor: {\n Rule: {\n style: (rule) => {\n const classes = new Set<string>();\n\n for (const selector of rule.value.selectors) {\n for (const group of splitCompoundSelectors(selector)) {\n const groupClasses = classesOf(group);\n for (const className of groupClasses) classes.add(className);\n for (let i = 0; i < groupClasses.length; i++) {\n for (let j = i + 1; j < groupClasses.length; j++) {\n this.classGraph.addCoOccurrence(groupClasses[i]!, groupClasses[j]!);\n }\n }\n }\n }\n\n for (const className of classes) this.classGraph.addClass(className);\n\n currentRule = {\n classes: [...classes],\n declarations: (rule.value.declarations?.declarations ?? []).map((declaration: { property: string }) => declaration.property),\n declaredVars: [],\n referencedVars: [],\n varDeclarations: [],\n };\n rules.push(currentRule);\n },\n },\n RuleExit: {\n style: () => {\n currentRule = null;\n },\n },\n Declaration: {\n custom: (declaration) => {\n const varName = declaration.name;\n currentDeclaredVar = varName;\n currentRule?.declaredVars.push(varName);\n this.varGraph.declare(varName, id);\n\n currentVarDeclaration = { name: varName, referencedVars: [] };\n currentRule?.varDeclarations.push(currentVarDeclaration);\n },\n },\n DeclarationExit: {\n custom: () => {\n currentDeclaredVar = null;\n currentVarDeclaration = null;\n },\n },\n Variable: (variable) => {\n const varName = variable.name.ident;\n currentRule?.referencedVars.push(varName);\n currentVarDeclaration?.referencedVars.push(varName);\n this.varGraph.reference(varName, id, currentDeclaredVar ?? undefined);\n },\n },\n });\n\n return rules;\n }\n}\n","import type { CssPurgeIRRule, CssPurgeIRVarDeclaration } from \"@purgeon/core\";\n\nimport type { CssRule, CssVarDeclaration } from \"./css-node\";\n\nexport function toCssPurgeIR(rules: CssRule[]): CssPurgeIRRule[] {\n return rules.map(\n (rule: CssRule): CssPurgeIRRule => ({\n classes: rule.classes,\n declaredVars: rule.declaredVars,\n referencedVars: rule.referencedVars,\n varDeclarations: rule.varDeclarations.map(\n (decl: CssVarDeclaration): CssPurgeIRVarDeclaration => ({\n name: decl.name,\n referencedVars: decl.referencedVars,\n }),\n ),\n }),\n );\n}\n","import { CssAnalyzer } from \"./css-analyzer\";\nimport type { CssAnalyzerOptions } from \"./css-node\";\n\nexport * from \"./class-graph\";\nexport * from \"./css-analyzer\";\nexport * from \"./css-node\";\nexport * from \"./purge-ir\";\nexport * from \"./var-graph\";\n\nexport const cssAnalyzer = (options?: CssAnalyzerOptions) => new CssAnalyzer(options);\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/class-graph.ts","../src/var-graph.ts","../src/css-analyzer.ts","../src/purge-ir.ts","../src/index.ts"],"sourcesContent":["export class ClassGraph {\n private readonly edges = new Map<string, Set<string>>();\n\n private ensure(className: string): Set<string> {\n let neighbors = this.edges.get(className);\n if (!neighbors) {\n neighbors = new Set();\n this.edges.set(className, neighbors);\n }\n return neighbors;\n }\n\n addClass(className: string): void {\n this.ensure(className);\n }\n\n addCoOccurrence(a: string, b: string): void {\n if (a === b) return;\n this.ensure(a).add(b);\n this.ensure(b).add(a);\n }\n\n neighbors(className: string): Set<string> | undefined {\n return this.edges.get(className);\n }\n\n classes(): IterableIterator<string> {\n return this.edges.keys();\n }\n\n toObject(): Record<string, string[]> {\n return Object.fromEntries([...this.edges].map(([className, neighbors]) => [className, [...neighbors]]));\n }\n}\n","export class VarGraph {\n private readonly declaredIn = new Map<string, Set<string>>();\n private readonly referencedIn = new Map<string, Set<string>>();\n private readonly dependsOn = new Map<string, Set<string>>();\n private readonly usedBy = new Map<string, Set<string>>();\n\n private ensure(map: Map<string, Set<string>>, key: string): Set<string> {\n let values = map.get(key);\n if (!values) {\n values = new Set();\n map.set(key, values);\n }\n return values;\n }\n\n declare(varName: string, moduleId: string): void {\n this.ensure(this.declaredIn, varName).add(moduleId);\n }\n\n reference(varName: string, moduleId: string, declaringVar?: string): void {\n this.ensure(this.referencedIn, varName).add(moduleId);\n\n if (declaringVar && declaringVar !== varName) {\n this.ensure(this.dependsOn, declaringVar).add(varName);\n this.ensure(this.usedBy, varName).add(declaringVar);\n }\n }\n\n declaredModules(varName: string): Set<string> | undefined {\n return this.declaredIn.get(varName);\n }\n\n referencingModules(varName: string): Set<string> | undefined {\n return this.referencedIn.get(varName);\n }\n\n dependenciesOf(varName: string): Set<string> | undefined {\n return this.dependsOn.get(varName);\n }\n\n dependentsOf(varName: string): Set<string> | undefined {\n return this.usedBy.get(varName);\n }\n\n toObject(): {\n declaredIn: Record<string, string[]>;\n referencedIn: Record<string, string[]>;\n dependsOn: Record<string, string[]>;\n } {\n return {\n declaredIn: toRecord(this.declaredIn),\n referencedIn: toRecord(this.referencedIn),\n dependsOn: toRecord(this.dependsOn),\n };\n }\n}\n\nfunction toRecord(map: Map<string, Set<string>>): Record<string, string[]> {\n return Object.fromEntries([...map].map(([key, values]) => [key, [...values]]));\n}\n","import {\n AnalyzerPlugin,\n classesOf,\n dataAttrsOf,\n pluginName,\n splitCompoundSelectors,\n type OutputBundle,\n type ParseContext,\n type PluginName,\n} from \"@purgeon/core\";\nimport { transform } from \"lightningcss\";\n\nimport { ClassGraph } from \"./class-graph\";\nimport type { CssAnalyzerOptions, CssGraphNode, CssRule, CssVarDeclaration } from \"./css-node\";\nimport { VarGraph } from \"./var-graph\";\n\nfunction normalizeModuleId(id: string): string {\n return id.replace(/\\?.*$/, \"\").replace(/\\\\/g, \"/\");\n}\n\nexport class CssAnalyzer extends AnalyzerPlugin<CssGraphNode> {\n readonly name: PluginName = pluginName(\"css-graph\");\n\n private readonly classGraph = new ClassGraph();\n private readonly varGraph = new VarGraph();\n\n constructor(private readonly options: CssAnalyzerOptions = {}) {\n super();\n }\n\n getClassGraph(): ClassGraph {\n return this.classGraph;\n }\n\n getVarGraph(): VarGraph {\n return this.varGraph;\n }\n\n protected isModuleSupported(id: string): boolean {\n return id.endsWith(\".module.css\");\n }\n\n protected parseModule(code: string, _id: string, _ctx: ParseContext): unknown {\n return code;\n }\n\n protected createNode(id: string): CssGraphNode {\n return { id, imports: [], importers: [], rules: [] };\n }\n\n protected analyzeModule(id: string, _ast: unknown, code: string, node: CssGraphNode): void {\n if (node.rules.length > 0) return;\n\n const rawRules = this.parseCss(code, id);\n const normalizedId = normalizeModuleId(id);\n node.rules = rawRules.map((rule) => ({\n ...rule,\n classes: rule.classes.map((cls) => `${normalizedId}::${cls}`),\n }));\n }\n\n protected analyzeBundle(bundle: OutputBundle): void {\n for (const fileName in bundle) {\n const output = bundle[fileName]!;\n if (output.type !== \"asset\" || !fileName.endsWith(\".css\")) continue;\n\n const source = typeof output.source === \"string\" ? output.source : Buffer.from(output.source).toString(\"utf-8\");\n const node = this.graph.ensureNode(fileName, (id) => this.createNode(id));\n node.rules = this.parseCss(source, fileName);\n }\n }\n\n private parseCss(code: string, id: string): CssRule[] {\n const rules: CssRule[] = [];\n\n let currentRule: CssRule | null = null;\n let currentDeclaredVar: string | null = null;\n let currentVarDeclaration: CssVarDeclaration | null = null;\n\n transform({\n filename: this.options.filename ?? id,\n code: Buffer.from(code),\n visitor: {\n Rule: {\n style: (rule) => {\n const classes = new Set<string>();\n const dataAttrs = new Set<string>();\n\n for (const selector of rule.value.selectors) {\n for (const group of splitCompoundSelectors(selector)) {\n const groupClasses = classesOf(group);\n for (const className of groupClasses) classes.add(className);\n for (let i = 0; i < groupClasses.length; i++) {\n for (let j = i + 1; j < groupClasses.length; j++) {\n this.classGraph.addCoOccurrence(groupClasses[i]!, groupClasses[j]!);\n }\n }\n\n for (const attrKey of dataAttrsOf(group)) dataAttrs.add(attrKey);\n }\n }\n\n for (const className of classes) this.classGraph.addClass(className);\n\n currentRule = {\n classes: [...classes],\n dataAttrs: [...dataAttrs],\n declarations: (rule.value.declarations?.declarations ?? []).map((declaration: { property: string }) => declaration.property),\n declaredVars: [],\n referencedVars: [],\n varDeclarations: [],\n };\n rules.push(currentRule);\n },\n },\n RuleExit: {\n style: () => {\n currentRule = null;\n },\n },\n Declaration: {\n custom: (declaration) => {\n const varName = declaration.name;\n currentDeclaredVar = varName;\n currentRule?.declaredVars.push(varName);\n this.varGraph.declare(varName, id);\n\n currentVarDeclaration = { name: varName, referencedVars: [] };\n currentRule?.varDeclarations.push(currentVarDeclaration);\n },\n },\n DeclarationExit: {\n custom: () => {\n currentDeclaredVar = null;\n currentVarDeclaration = null;\n },\n },\n Variable: (variable) => {\n const varName = variable.name.ident;\n currentRule?.referencedVars.push(varName);\n currentVarDeclaration?.referencedVars.push(varName);\n this.varGraph.reference(varName, id, currentDeclaredVar ?? undefined);\n },\n },\n });\n\n return rules;\n }\n}\n","import type { CssPurgeIRRule, CssPurgeIRVarDeclaration } from \"@purgeon/core\";\n\nimport type { CssRule, CssVarDeclaration } from \"./css-node\";\n\nexport function toCssPurgeIR(rules: CssRule[]): CssPurgeIRRule[] {\n return rules.map(\n (rule: CssRule): CssPurgeIRRule => ({\n classes: rule.classes,\n dataAttrs: rule.dataAttrs,\n declaredVars: rule.declaredVars,\n referencedVars: rule.referencedVars,\n varDeclarations: rule.varDeclarations.map(\n (decl: CssVarDeclaration): CssPurgeIRVarDeclaration => ({\n name: decl.name,\n referencedVars: decl.referencedVars,\n }),\n ),\n }),\n );\n}\n","import { CssAnalyzer } from \"./css-analyzer\";\nimport type { CssAnalyzerOptions } from \"./css-node\";\n\nexport * from \"./class-graph\";\nexport * from \"./css-analyzer\";\nexport * from \"./css-node\";\nexport * from \"./purge-ir\";\nexport * from \"./var-graph\";\n\nexport const cssAnalyzer = (options?: CssAnalyzerOptions) => new CssAnalyzer(options);\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@purgeon/analyzer-css",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Walks bundled CSS via lightningcss visitor, builds class co-occurrence and custom-property graphs for purgeon",
5
5
  "keywords": [
6
6
  "css",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "lightningcss": "^1.32.0",
35
- "@purgeon/core": "0.1.0"
35
+ "@purgeon/core": "0.2.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsdown"