@tailwindcss-obfuscator/core 1.0.1 → 1.0.3

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.
Files changed (60) hide show
  1. package/dist/1-class-strings-finder.d.ts +12 -0
  2. package/dist/1-class-strings-finder.d.ts.map +1 -0
  3. package/dist/1-class-strings-finder.js +96 -0
  4. package/dist/2-obfuscator.d.ts +14 -0
  5. package/dist/2-obfuscator.d.ts.map +1 -0
  6. package/dist/2-obfuscator.js +45 -0
  7. package/dist/2-rename-class-strings.d.ts +14 -0
  8. package/dist/2-rename-class-strings.d.ts.map +1 -0
  9. package/dist/2-rename-class-strings.js +45 -0
  10. package/dist/3-apply-class-names.d.ts +18 -0
  11. package/dist/3-apply-class-names.d.ts.map +1 -0
  12. package/dist/3-apply-class-names.js +227 -0
  13. package/dist/3-rewrite-source.d.ts +17 -0
  14. package/dist/3-rewrite-source.d.ts.map +1 -0
  15. package/dist/3-rewrite-source.js +199 -0
  16. package/dist/4-rewrite-css.d.ts +8 -0
  17. package/dist/4-rewrite-css.d.ts.map +1 -0
  18. package/dist/4-rewrite-css.js +42 -0
  19. package/dist/apply-class-names.d.ts +18 -0
  20. package/dist/apply-class-names.d.ts.map +1 -0
  21. package/dist/apply-class-names.js +227 -0
  22. package/dist/class-strings-finder.d.ts +12 -0
  23. package/dist/class-strings-finder.d.ts.map +1 -0
  24. package/dist/class-strings-finder.js +96 -0
  25. package/dist/css/4-rewrite-css.d.ts +8 -0
  26. package/dist/css/4-rewrite-css.d.ts.map +1 -0
  27. package/dist/css/4-rewrite-css.js +42 -0
  28. package/dist/css/css-handler.d.ts +8 -0
  29. package/dist/css/css-handler.d.ts.map +1 -0
  30. package/dist/css/css-handler.js +42 -0
  31. package/dist/css/parse-class-selectors.d.ts +7 -0
  32. package/dist/css/parse-class-selectors.d.ts.map +1 -0
  33. package/dist/css/parse-class-selectors.js +65 -0
  34. package/dist/css/rewrite-css.d.ts +8 -0
  35. package/dist/css/rewrite-css.d.ts.map +1 -0
  36. package/dist/css/rewrite-css.js +42 -0
  37. package/dist/css-handler.d.ts.map +1 -1
  38. package/dist/css-handler.js +5 -0
  39. package/dist/find-class-strings.d.ts +12 -0
  40. package/dist/find-class-strings.d.ts.map +1 -0
  41. package/dist/find-class-strings.js +96 -0
  42. package/dist/generate-class-names.d.ts +14 -0
  43. package/dist/generate-class-names.d.ts.map +1 -0
  44. package/dist/generate-class-names.js +45 -0
  45. package/dist/generator.d.ts +2 -0
  46. package/dist/generator.d.ts.map +1 -1
  47. package/dist/generator.js +5 -3
  48. package/dist/obfuscator.d.ts +14 -0
  49. package/dist/obfuscator.d.ts.map +1 -0
  50. package/dist/obfuscator.js +45 -0
  51. package/dist/parse-class-selectors.d.ts +7 -0
  52. package/dist/parse-class-selectors.d.ts.map +1 -0
  53. package/dist/parse-class-selectors.js +65 -0
  54. package/dist/rewrite-source.d.ts +17 -0
  55. package/dist/rewrite-source.d.ts.map +1 -0
  56. package/dist/rewrite-source.js +199 -0
  57. package/dist/transformer.d.ts +7 -3
  58. package/dist/transformer.d.ts.map +1 -1
  59. package/dist/transformer.js +51 -40
  60. package/package.json +6 -22
@@ -0,0 +1,199 @@
1
+ // src/class-strings-finder.ts
2
+ var CN_FUNCTION_NAMES = ["cn", "clsx", "classNames", "twMerge", "cva"];
3
+ function skipQuotedString(code, i) {
4
+ const quote = code[i];
5
+ let pos = i + 1;
6
+ while (pos < code.length && code[pos] !== quote) {
7
+ if (code[pos] === "\\") {
8
+ pos += 1;
9
+ }
10
+ pos += 1;
11
+ }
12
+ return pos + 1;
13
+ }
14
+ function skipToMatchingParen(code, fromIndex) {
15
+ let depth = 1;
16
+ let i = fromIndex;
17
+ while (i < code.length && depth > 0) {
18
+ if (code[i] === "(") {
19
+ depth += 1;
20
+ i += 1;
21
+ } else if (code[i] === ")") {
22
+ depth -= 1;
23
+ i += 1;
24
+ } else if (code[i] === '"' || code[i] === "'" || code[i] === "`") {
25
+ i = skipQuotedString(code, i);
26
+ } else {
27
+ i += 1;
28
+ }
29
+ }
30
+ return i;
31
+ }
32
+ function findCnCalls(code) {
33
+ const calls = [];
34
+ for (const fnName of CN_FUNCTION_NAMES) {
35
+ const regex = new RegExp(`\\b${fnName}\\s*\\(`, "g");
36
+ regex.lastIndex = 0;
37
+ let match = regex.exec(code);
38
+ while (match !== null) {
39
+ const start = match.index;
40
+ const i = skipToMatchingParen(code, start + match[0].length);
41
+ calls.push({ content: code.slice(start, i), end: i, start });
42
+ match = regex.exec(code);
43
+ }
44
+ }
45
+ return calls;
46
+ }
47
+ function findClassStrings(code) {
48
+ const matches = [];
49
+ const stringRegex = /(['"`])(.*?)\1/g;
50
+ for (const call of findCnCalls(code)) {
51
+ stringRegex.lastIndex = 0;
52
+ let match = stringRegex.exec(call.content);
53
+ while (match !== null) {
54
+ const [fullMatch, , classString] = match;
55
+ matches.push({
56
+ classString,
57
+ end: call.start + match.index + fullMatch.length,
58
+ fullMatch,
59
+ start: call.start + match.index
60
+ });
61
+ match = stringRegex.exec(call.content);
62
+ }
63
+ }
64
+ const jsxClassNameRegex = /className\s*=\s*(?:\{[^}]*["'`]([^"'`]+)["'`][^}]*\}|["'`]([^"'`]+)["'`])/g;
65
+ const compiledClassNameRegex = /className\s*:\s*(["'`])([^"'`]+)\1/g;
66
+ for (const { regex, getClassString } of [
67
+ {
68
+ getClassString: (m) => m[1] || m[2],
69
+ regex: jsxClassNameRegex
70
+ },
71
+ {
72
+ getClassString: (m) => m[2],
73
+ regex: compiledClassNameRegex
74
+ }
75
+ ]) {
76
+ regex.lastIndex = 0;
77
+ let classMatch = regex.exec(code);
78
+ while (classMatch !== null) {
79
+ const [fullMatch] = classMatch;
80
+ const classString = getClassString(classMatch);
81
+ if (classString && !CN_FUNCTION_NAMES.some((fn) => fullMatch.includes(`${fn}(`))) {
82
+ matches.push({
83
+ classString,
84
+ end: classMatch.index + fullMatch.length,
85
+ fullMatch,
86
+ start: classMatch.index
87
+ });
88
+ }
89
+ classMatch = regex.exec(code);
90
+ }
91
+ }
92
+ return matches;
93
+ }
94
+
95
+ // src/obfuscator.ts
96
+ class Obfuscator {
97
+ counter = 0;
98
+ map = new Map;
99
+ prefix;
100
+ alphabet = "abcdefghijklmnopqrstuvwxyz";
101
+ constructor(prefix = "az") {
102
+ this.prefix = prefix;
103
+ }
104
+ numberToLetters(num) {
105
+ let result = "";
106
+ let n = num;
107
+ const base = this.alphabet.length;
108
+ do {
109
+ result = this.alphabet[n % base] + result;
110
+ n = Math.floor(n / base);
111
+ } while (n > 0);
112
+ return result;
113
+ }
114
+ generateCode() {
115
+ const letters = this.numberToLetters(this.counter);
116
+ const code = letters.padStart(2, "a");
117
+ this.counter += 1;
118
+ return this.prefix + code;
119
+ }
120
+ getObfuscatedClass(originalClass) {
121
+ const existing = this.map.get(originalClass);
122
+ if (existing !== undefined) {
123
+ return existing;
124
+ }
125
+ const obfuscated = this.generateCode();
126
+ this.map.set(originalClass, obfuscated);
127
+ return obfuscated;
128
+ }
129
+ getMap() {
130
+ return new Map(this.map);
131
+ }
132
+ reset() {
133
+ this.counter = 0;
134
+ this.map.clear();
135
+ }
136
+ }
137
+
138
+ // src/rewrite-source.ts
139
+ var RE_WHITESPACE = /\s+/;
140
+ function stripEscapeSequence(str) {
141
+ return str.replaceAll("\\", "");
142
+ }
143
+
144
+ class RewriteSource {
145
+ allowlist = new Set;
146
+ obfuscator;
147
+ constructor(prefix = "az") {
148
+ this.obfuscator = new Obfuscator(prefix);
149
+ }
150
+ setAllowlist(allowlist) {
151
+ this.allowlist = allowlist;
152
+ }
153
+ normalizeClass(cls) {
154
+ return stripEscapeSequence(cls);
155
+ }
156
+ isInAllowlist(normalized) {
157
+ return this.allowlist.has(normalized);
158
+ }
159
+ transformClassString(classString) {
160
+ const classes = classString.split(RE_WHITESPACE).filter(Boolean);
161
+ const transformed = classes.map((cls) => {
162
+ const normalized = this.normalizeClass(cls);
163
+ if (!this.isInAllowlist(normalized)) {
164
+ return cls;
165
+ }
166
+ return this.obfuscator.getObfuscatedClass(normalized);
167
+ });
168
+ return transformed.join(" ");
169
+ }
170
+ transform(code, _id) {
171
+ const matches = findClassStrings(code);
172
+ const replacements = [];
173
+ for (const { start, end, classString, fullMatch } of matches) {
174
+ const hasAllowlistClass = classString.split(RE_WHITESPACE).some((c) => this.isInAllowlist(this.normalizeClass(c)));
175
+ if (!hasAllowlistClass) {
176
+ continue;
177
+ }
178
+ const transformed = this.transformClassString(classString);
179
+ const newContent = fullMatch.replace(classString, transformed);
180
+ replacements.push({ end, newContent, start });
181
+ }
182
+ replacements.sort((a, b) => b.start - a.start);
183
+ let result = code;
184
+ for (const { start, end, newContent } of replacements) {
185
+ result = result.slice(0, start) + newContent + result.slice(end);
186
+ }
187
+ return { code: result, map: this.obfuscator.getMap() };
188
+ }
189
+ getMap() {
190
+ return this.obfuscator.getMap();
191
+ }
192
+ reset() {
193
+ this.allowlist = new Set;
194
+ this.obfuscator.reset();
195
+ }
196
+ }
197
+ export {
198
+ RewriteSource
199
+ };
@@ -1,12 +1,16 @@
1
1
  import type { ClassMap } from "./types.ts";
2
2
  export declare class SourceTransformer {
3
- private readonly generator;
3
+ private allowlist;
4
+ private readonly obfuscator;
5
+ constructor(prefix?: string);
6
+ setAllowlist(allowlist: Set<string>): void;
4
7
  private normalizeClass;
8
+ private isTailwindClass;
5
9
  private transformClassString;
6
10
  private skipToMatchingParen;
7
11
  private findCnCalls;
8
- private processCnCalls;
9
- private processClassNameAttrs;
12
+ private collectCnCallReplacements;
13
+ private collectClassNameAttrReplacements;
10
14
  transform(code: string, _id: string): {
11
15
  code: string;
12
16
  map: ClassMap;
@@ -1 +1 @@
1
- {"version":3,"file":"transformer.d.ts","sourceRoot":"","sources":["../src/transformer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA6C3C,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuB;IAEjD,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,qBAAqB;IAoC7B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE;IAOrE,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAI7B,KAAK,IAAI,IAAI;CAGb"}
1
+ {"version":3,"file":"transformer.d.ts","sourceRoot":"","sources":["../src/transformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA4B3C,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;gBAE5B,MAAM,SAAO;IAIzB,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI;IAI1C,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,yBAAyB;IA4BjC,OAAO,CAAC,gCAAgC;IA0CxC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE;IAarE,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAI7B,KAAK,IAAI,IAAI;CAIb"}
@@ -1,10 +1,12 @@
1
- // src/generator.ts
2
- var PREFIX = "az";
3
-
4
- class CodeGenerator {
1
+ // src/obfuscator.ts
2
+ class Obfuscator {
5
3
  counter = 0;
6
4
  map = new Map;
5
+ prefix;
7
6
  alphabet = "abcdefghijklmnopqrstuvwxyz";
7
+ constructor(prefix = "az") {
8
+ this.prefix = prefix;
9
+ }
8
10
  numberToLetters(num) {
9
11
  let result = "";
10
12
  let n = num;
@@ -19,7 +21,7 @@ class CodeGenerator {
19
21
  const letters = this.numberToLetters(this.counter);
20
22
  const code = letters.padStart(2, "a");
21
23
  this.counter += 1;
22
- return PREFIX + code;
24
+ return this.prefix + code;
23
25
  }
24
26
  getObfuscatedClass(originalClass) {
25
27
  const existing = this.map.get(originalClass);
@@ -40,30 +42,11 @@ class CodeGenerator {
40
42
  }
41
43
 
42
44
  // src/transformer.ts
43
- import MagicString from "magic-string";
44
- var CN_FUNCTION_NAMES = ["cn", "clsx", "classNames", "twMerge"];
45
+ var CN_FUNCTION_NAMES = ["cn", "clsx", "classNames", "twMerge", "cva"];
45
46
  var RE_WHITESPACE = /\s+/;
46
- var TAILWIND_CLASS_TEST = /[:-]/;
47
- var KNOWN_TAILWIND_UTILITIES = new Set([
48
- "flex",
49
- "grid",
50
- "block",
51
- "inline",
52
- "hidden",
53
- "static",
54
- "relative",
55
- "absolute",
56
- "fixed",
57
- "sticky",
58
- "table",
59
- "contents"
60
- ]);
61
47
  function stripEscapeSequence(str) {
62
48
  return str.replaceAll("\\", "");
63
49
  }
64
- function isTailwindClass(className) {
65
- return TAILWIND_CLASS_TEST.test(className) || KNOWN_TAILWIND_UTILITIES.has(className);
66
- }
67
50
  function skipQuotedString(code, i) {
68
51
  const quote = code[i];
69
52
  let pos = i + 1;
@@ -77,18 +60,28 @@ function skipQuotedString(code, i) {
77
60
  }
78
61
 
79
62
  class SourceTransformer {
80
- generator = new CodeGenerator;
63
+ allowlist = new Set;
64
+ obfuscator;
65
+ constructor(prefix = "az") {
66
+ this.obfuscator = new Obfuscator(prefix);
67
+ }
68
+ setAllowlist(allowlist) {
69
+ this.allowlist = allowlist;
70
+ }
81
71
  normalizeClass(cls) {
82
72
  return stripEscapeSequence(cls);
83
73
  }
74
+ isTailwindClass(normalized) {
75
+ return this.allowlist.has(normalized);
76
+ }
84
77
  transformClassString(classString) {
85
78
  const classes = classString.split(RE_WHITESPACE).filter(Boolean);
86
79
  const transformed = classes.map((cls) => {
87
80
  const normalized = this.normalizeClass(cls);
88
- if (!isTailwindClass(normalized)) {
81
+ if (!this.isTailwindClass(normalized)) {
89
82
  return cls;
90
83
  }
91
- return this.generator.getObfuscatedClass(normalized);
84
+ return this.obfuscator.getObfuscatedClass(normalized);
92
85
  });
93
86
  return transformed.join(" ");
94
87
  }
@@ -125,7 +118,8 @@ class SourceTransformer {
125
118
  }
126
119
  return calls;
127
120
  }
128
- processCnCalls(s, code) {
121
+ collectCnCallReplacements(code) {
122
+ const replacements = [];
129
123
  const cnCalls = this.findCnCalls(code);
130
124
  const stringRegex = /(['"`])(.*?)\1/g;
131
125
  for (const call of cnCalls) {
@@ -133,16 +127,22 @@ class SourceTransformer {
133
127
  let match = stringRegex.exec(call.content);
134
128
  while (match !== null) {
135
129
  const [fullMatch, quote, classString] = match;
136
- if (classString.split(RE_WHITESPACE).some((c) => isTailwindClass(this.normalizeClass(c)))) {
130
+ if (classString.split(RE_WHITESPACE).some((c) => this.isTailwindClass(this.normalizeClass(c)))) {
137
131
  const transformed = this.transformClassString(classString);
138
132
  const startInFile = call.start + match.index;
139
- s.overwrite(startInFile, startInFile + fullMatch.length, `${quote}${transformed}${quote}`);
133
+ replacements.push({
134
+ end: startInFile + fullMatch.length,
135
+ newContent: `${quote}${transformed}${quote}`,
136
+ start: startInFile
137
+ });
140
138
  }
141
139
  match = stringRegex.exec(call.content);
142
140
  }
143
141
  }
142
+ return replacements;
144
143
  }
145
- processClassNameAttrs(s, code) {
144
+ collectClassNameAttrReplacements(code) {
145
+ const replacements = [];
146
146
  const jsxClassNameRegex = /className\s*=\s*(?:\{[^}]*["'`]([^"'`]+)["'`][^}]*\}|["'`]([^"'`]+)["'`])/g;
147
147
  const compiledClassNameRegex = /className\s*:\s*(["'`])([^"'`]+)\1/g;
148
148
  for (const { regex, getClassString } of [
@@ -160,27 +160,38 @@ class SourceTransformer {
160
160
  while (classMatch !== null) {
161
161
  const [fullMatch] = classMatch;
162
162
  const classString = getClassString(classMatch);
163
- if (classString && !CN_FUNCTION_NAMES.some((fn) => fullMatch.includes(`${fn}(`)) && classString.split(RE_WHITESPACE).some((c) => isTailwindClass(this.normalizeClass(c)))) {
163
+ if (classString && !CN_FUNCTION_NAMES.some((fn) => fullMatch.includes(`${fn}(`)) && classString.split(RE_WHITESPACE).some((c) => this.isTailwindClass(this.normalizeClass(c)))) {
164
164
  const transformed = this.transformClassString(classString);
165
165
  const startInFile = classMatch.index;
166
166
  const newValue = fullMatch.replace(classString, transformed);
167
- s.overwrite(startInFile, startInFile + fullMatch.length, newValue);
167
+ replacements.push({
168
+ end: startInFile + fullMatch.length,
169
+ newContent: newValue,
170
+ start: startInFile
171
+ });
168
172
  }
169
173
  classMatch = regex.exec(code);
170
174
  }
171
175
  }
176
+ return replacements;
172
177
  }
173
178
  transform(code, _id) {
174
- const s = new MagicString(code);
175
- this.processCnCalls(s, code);
176
- this.processClassNameAttrs(s, code);
177
- return { code: s.toString(), map: this.generator.getMap() };
179
+ const replacements = [
180
+ ...this.collectCnCallReplacements(code),
181
+ ...this.collectClassNameAttrReplacements(code)
182
+ ].sort((a, b) => b.start - a.start);
183
+ let result = code;
184
+ for (const { start, end, newContent } of replacements) {
185
+ result = result.slice(0, start) + newContent + result.slice(end);
186
+ }
187
+ return { code: result, map: this.obfuscator.getMap() };
178
188
  }
179
189
  getMap() {
180
- return this.generator.getMap();
190
+ return this.obfuscator.getMap();
181
191
  }
182
192
  reset() {
183
- this.generator.reset();
193
+ this.allowlist = new Set;
194
+ this.obfuscator.reset();
184
195
  }
185
196
  }
186
197
  export {
package/package.json CHANGED
@@ -1,27 +1,11 @@
1
1
  {
2
- "dependencies": {
3
- "lightningcss": "^1.31.1",
4
- "magic-string": "^0.30.21"
5
- },
6
2
  "devDependencies": {
7
3
  "typescript": "^5.9.3"
8
4
  },
9
5
  "exports": {
10
- "./compressor": {
11
- "import": "./dist/compressor.js",
12
- "types": "./dist/compressor.d.ts"
13
- },
14
- "./css-handler": {
15
- "import": "./dist/css-handler.js",
16
- "types": "./dist/css-handler.d.ts"
17
- },
18
- "./transformer": {
19
- "import": "./dist/transformer.js",
20
- "types": "./dist/transformer.d.ts"
21
- },
22
- "./types": {
23
- "import": "./dist/types.js",
24
- "types": "./dist/types.d.ts"
6
+ "./apply-class-names": {
7
+ "import": "./dist/apply-class-names.js",
8
+ "types": "./dist/apply-class-names.d.ts"
25
9
  }
26
10
  },
27
11
  "files": [
@@ -32,9 +16,9 @@
32
16
  "access": "public"
33
17
  },
34
18
  "scripts": {
35
- "build": "tsc && bun build src/transformer.ts src/css-handler.ts src/compressor.ts src/generator.ts src/types.ts --outdir=dist --format=esm --external=lightningcss --external=magic-string"
19
+ "build": "tsc && bun build src/find-class-strings.ts src/generate-class-names.ts src/apply-class-names.ts --outdir=dist --format=esm"
36
20
  },
37
21
  "type": "module",
38
- "types": "./dist/transformer.d.ts",
39
- "version": "1.0.1"
22
+ "types": "./dist/apply-class-names.d.ts",
23
+ "version": "1.0.3"
40
24
  }