@tailwindcss-mangle/core 2.1.0 → 2.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.mjs CHANGED
@@ -1,12 +1,19 @@
1
+ import fs from 'node:fs';
2
+ import { resolve, isAbsolute } from 'node:path';
3
+ import { ClassGenerator, defaultMangleClassFilter, splitCode, makeRegex } from '@tailwindcss-mangle/shared';
4
+ export { ClassGenerator } from '@tailwindcss-mangle/shared';
5
+ import { getConfig } from '@tailwindcss-mangle/config';
6
+ import { sort } from 'fast-sort';
7
+ import micromatch from 'micromatch';
1
8
  import postcss from 'postcss';
2
9
  import parser from 'postcss-selector-parser';
3
10
  import { html, defaultTreeAdapter, parse, serialize } from 'parse5';
4
- import { splitCode, makeRegex } from '@tailwindcss-mangle/shared';
5
- export { ClassGenerator } from '@tailwindcss-mangle/shared';
6
- import babel, { transformSync } from '@babel/core';
11
+ import babel, { transformSync as transformSync$1 } from '@babel/core';
7
12
  import { declare } from '@babel/helper-plugin-utils';
8
13
  import MagicString from 'magic-string';
9
14
  import { jsStringEscape } from '@ast-core/escape';
15
+ import { parse as parse$1 } from '@babel/parser';
16
+ import traverse$1 from '@babel/traverse';
10
17
 
11
18
 
12
19
 
@@ -59,7 +66,126 @@ function createDefu(merger) {
59
66
  }
60
67
  const defu = createDefu();
61
68
 
69
+ const { isMatch } = micromatch;
70
+ function escapeStringRegexp(str) {
71
+ if (typeof str !== "string") {
72
+ throw new TypeError("Expected a string");
73
+ }
74
+ return str.replaceAll(/[$()*+.?[\\\]^{|}]/g, "\\$&").replaceAll("-", "\\x2d");
75
+ }
76
+ function createGlobMatcher(pattern, fallbackValue = false) {
77
+ if (pattern === void 0) {
78
+ return function() {
79
+ return fallbackValue;
80
+ };
81
+ }
82
+ return function(file) {
83
+ return isMatch(file, pattern);
84
+ };
85
+ }
86
+
87
+ var __defProp = Object.defineProperty;
88
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
89
+ var __publicField = (obj, key, value) => {
90
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
91
+ return value;
92
+ };
93
+ class Context {
94
+ constructor() {
95
+ __publicField(this, "options");
96
+ __publicField(this, "includeMatcher");
97
+ __publicField(this, "excludeMatcher");
98
+ __publicField(this, "replaceMap");
99
+ __publicField(this, "classSet");
100
+ __publicField(this, "classGenerator");
101
+ __publicField(this, "preserveFunctionSet");
102
+ __publicField(this, "preserveClassNamesSet");
103
+ __publicField(this, "preserveFunctionRegexs");
104
+ this.options = {};
105
+ this.classSet = /* @__PURE__ */ new Set();
106
+ this.replaceMap = /* @__PURE__ */ new Map();
107
+ this.includeMatcher = () => true;
108
+ this.excludeMatcher = () => false;
109
+ this.classGenerator = new ClassGenerator();
110
+ this.preserveFunctionSet = /* @__PURE__ */ new Set();
111
+ this.preserveClassNamesSet = /* @__PURE__ */ new Set();
112
+ this.preserveFunctionRegexs = [];
113
+ }
114
+ isPreserveClass(className) {
115
+ return this.preserveClassNamesSet.has(className);
116
+ }
117
+ addPreserveClass(className) {
118
+ return this.preserveClassNamesSet.add(className);
119
+ }
120
+ isPreserveFunction(calleeName) {
121
+ return this.preserveFunctionSet.has(calleeName);
122
+ }
123
+ mergeOptions(...opts) {
124
+ this.options = defu(this.options, ...opts);
125
+ this.includeMatcher = createGlobMatcher(this.options.include, true);
126
+ this.excludeMatcher = createGlobMatcher(this.options.exclude, false);
127
+ this.classGenerator = new ClassGenerator(this.options.classGenerator);
128
+ this.preserveFunctionSet = new Set(this.options?.preserveFunction ?? []);
129
+ this.preserveFunctionRegexs = [...this.preserveFunctionSet.values()].map((x) => {
130
+ return new RegExp(escapeStringRegexp(x) + "\\(([^)]*)\\)", "g");
131
+ });
132
+ }
133
+ isInclude(file) {
134
+ return this.includeMatcher(file) && !this.excludeMatcher(file);
135
+ }
136
+ currentMangleClassFilter(className) {
137
+ return (this.options.mangleClassFilter ?? defaultMangleClassFilter)(className);
138
+ }
139
+ getClassSet() {
140
+ return this.classSet;
141
+ }
142
+ getReplaceMap() {
143
+ return this.replaceMap;
144
+ }
145
+ addToUsedBy(key, file) {
146
+ const hit = this.classGenerator.newClassMap[key];
147
+ if (hit) {
148
+ hit.usedBy.add(file);
149
+ }
150
+ }
151
+ loadClassSet(classList) {
152
+ const list = sort(classList).desc((c) => c.length);
153
+ for (const className of list) {
154
+ if (this.currentMangleClassFilter(className)) {
155
+ this.classSet.add(className);
156
+ }
157
+ }
158
+ }
159
+ async initConfig(opts = {}) {
160
+ const { cwd, classList: _classList, mangleOptions } = opts;
161
+ const { config, cwd: configCwd } = await getConfig(cwd);
162
+ this.mergeOptions(mangleOptions, config?.mangle);
163
+ if (_classList) {
164
+ this.loadClassSet(_classList);
165
+ } else {
166
+ let jsonPath = this.options.classListPath ?? resolve(process.cwd(), config?.patch?.output?.filename);
167
+ if (!isAbsolute(jsonPath)) {
168
+ jsonPath = resolve(configCwd ?? process.cwd(), jsonPath);
169
+ }
170
+ if (jsonPath && fs.existsSync(jsonPath)) {
171
+ const rawClassList = fs.readFileSync(jsonPath, "utf8");
172
+ const list = JSON.parse(rawClassList);
173
+ this.loadClassSet(list);
174
+ }
175
+ }
176
+ for (const cls of this.classSet) {
177
+ this.classGenerator.generateClassName(cls);
178
+ }
179
+ for (const x of Object.entries(this.classGenerator.newClassMap)) {
180
+ this.replaceMap.set(x[0], x[1].name);
181
+ }
182
+ return config;
183
+ }
184
+ // ["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
185
+ }
186
+
62
187
  const postcssPlugin = "postcss-mangle-tailwindcss-plugin";
188
+ const clonedKey = "__tw_mangle_cloned__";
63
189
  function isVueScoped(s) {
64
190
  if (s.parent) {
65
191
  const index = s.parent.nodes.indexOf(s);
@@ -73,12 +199,15 @@ function isVueScoped(s) {
73
199
  return false;
74
200
  }
75
201
  const transformSelectorPostcssPlugin = function(options) {
76
- const { ignoreVueScoped, replaceMap } = defu(options, {
202
+ const { ignoreVueScoped, replaceMap, ctx } = defu(options, {
77
203
  ignoreVueScoped: true
78
204
  });
79
205
  return {
80
206
  postcssPlugin,
81
207
  async Rule(rule) {
208
+ if (rule[clonedKey]) {
209
+ return;
210
+ }
82
211
  await parser((selectors) => {
83
212
  selectors.walkClasses((s) => {
84
213
  if (s.value && replaceMap && replaceMap.has(s.value)) {
@@ -87,6 +216,10 @@ const transformSelectorPostcssPlugin = function(options) {
87
216
  }
88
217
  const v = replaceMap.get(s.value);
89
218
  if (v) {
219
+ if (ctx.isPreserveClass(s.value)) {
220
+ const r = rule.cloneBefore();
221
+ r[clonedKey] = true;
222
+ }
90
223
  s.value = v;
91
224
  }
92
225
  }
@@ -203,7 +336,7 @@ function traverse(node, visitor, parent) {
203
336
  }
204
337
 
205
338
  function htmlHandler(rawSource, options) {
206
- const { replaceMap, classGenerator } = options;
339
+ const { replaceMap, ctx } = options;
207
340
  const fragment = parse(rawSource);
208
341
  traverse(fragment, {
209
342
  element(node) {
@@ -214,7 +347,7 @@ function htmlHandler(rawSource, options) {
214
347
  });
215
348
  for (const v of array) {
216
349
  if (replaceMap.has(v)) {
217
- attribute.value = attribute.value.replace(makeRegex(v), classGenerator.generateClassName(v).name);
350
+ attribute.value = attribute.value.replace(makeRegex(v), ctx.classGenerator.generateClassName(v).name);
218
351
  }
219
352
  }
220
353
  }
@@ -225,136 +358,46 @@ function htmlHandler(rawSource, options) {
225
358
 
226
359
  const isProd = () => process.env.NODE_ENV === "production";
227
360
 
228
- // >>> INTERFACES <<<
229
- // >>> HELPERS <<<
230
- var castComparer = function (comparer) { return function (a, b, order) { return comparer(a, b, order) * order; }; };
231
- var throwInvalidConfigErrorIfTrue = function (condition, context) {
232
- if (condition)
233
- throw Error("Invalid sort config: " + context);
234
- };
235
- var unpackObjectSorter = function (sortByObj) {
236
- var _a = sortByObj || {}, asc = _a.asc, desc = _a.desc;
237
- var order = asc ? 1 : -1;
238
- var sortBy = (asc || desc);
239
- // Validate object config
240
- throwInvalidConfigErrorIfTrue(!sortBy, 'Expected `asc` or `desc` property');
241
- throwInvalidConfigErrorIfTrue(asc && desc, 'Ambiguous object with `asc` and `desc` config properties');
242
- var comparer = sortByObj.comparer && castComparer(sortByObj.comparer);
243
- return { order: order, sortBy: sortBy, comparer: comparer };
244
- };
245
- // >>> SORTERS <<<
246
- var multiPropertySorterProvider = function (defaultComparer) {
247
- return function multiPropertySorter(sortBy, sortByArr, depth, order, comparer, a, b) {
248
- var valA;
249
- var valB;
250
- if (typeof sortBy === 'string') {
251
- valA = a[sortBy];
252
- valB = b[sortBy];
253
- }
254
- else if (typeof sortBy === 'function') {
255
- valA = sortBy(a);
256
- valB = sortBy(b);
257
- }
258
- else {
259
- var objectSorterConfig = unpackObjectSorter(sortBy);
260
- return multiPropertySorter(objectSorterConfig.sortBy, sortByArr, depth, objectSorterConfig.order, objectSorterConfig.comparer || defaultComparer, a, b);
261
- }
262
- var equality = comparer(valA, valB, order);
263
- if ((equality === 0 || (valA == null && valB == null)) &&
264
- sortByArr.length > depth) {
265
- return multiPropertySorter(sortByArr[depth], sortByArr, depth + 1, order, comparer, a, b);
266
- }
267
- return equality;
268
- };
269
- };
270
- function getSortStrategy(sortBy, comparer, order) {
271
- // Flat array sorter
272
- if (sortBy === undefined || sortBy === true) {
273
- return function (a, b) { return comparer(a, b, order); };
274
- }
275
- // Sort list of objects by single object key
276
- if (typeof sortBy === 'string') {
277
- throwInvalidConfigErrorIfTrue(sortBy.includes('.'), 'String syntax not allowed for nested properties.');
278
- return function (a, b) { return comparer(a[sortBy], b[sortBy], order); };
279
- }
280
- // Sort list of objects by single function sorter
281
- if (typeof sortBy === 'function') {
282
- return function (a, b) { return comparer(sortBy(a), sortBy(b), order); };
283
- }
284
- // Sort by multiple properties
285
- if (Array.isArray(sortBy)) {
286
- var multiPropSorter_1 = multiPropertySorterProvider(comparer);
287
- return function (a, b) { return multiPropSorter_1(sortBy[0], sortBy, 1, order, comparer, a, b); };
288
- }
289
- // Unpack object config to get actual sorter strategy
290
- var objectSorterConfig = unpackObjectSorter(sortBy);
291
- return getSortStrategy(objectSorterConfig.sortBy, objectSorterConfig.comparer || comparer, objectSorterConfig.order);
292
- }
293
- var sortArray = function (order, ctx, sortBy, comparer) {
294
- var _a;
295
- if (!Array.isArray(ctx)) {
296
- return ctx;
297
- }
298
- // Unwrap sortBy if array with only 1 value to get faster sort strategy
299
- if (Array.isArray(sortBy) && sortBy.length < 2) {
300
- _a = sortBy, sortBy = _a[0];
301
- }
302
- return ctx.sort(getSortStrategy(sortBy, comparer, order));
303
- };
304
- function createNewSortInstance(opts) {
305
- var comparer = castComparer(opts.comparer);
306
- return function (arrayToSort) {
307
- var ctx = Array.isArray(arrayToSort) && !opts.inPlaceSorting
308
- ? arrayToSort.slice()
309
- : arrayToSort;
310
- return {
311
- asc: function (sortBy) {
312
- return sortArray(1, ctx, sortBy, comparer);
313
- },
314
- desc: function (sortBy) {
315
- return sortArray(-1, ctx, sortBy, comparer);
316
- },
317
- by: function (sortBy) {
318
- return sortArray(1, ctx, sortBy, comparer);
319
- },
320
- };
321
- };
322
- }
323
- var defaultComparer = function (a, b, order) {
324
- if (a == null)
325
- return order;
326
- if (b == null)
327
- return -order;
328
- if (typeof a !== typeof b) {
329
- return typeof a < typeof b ? -1 : 1;
330
- }
331
- if (a < b)
332
- return -1;
333
- if (a > b)
334
- return 1;
335
- return 0;
336
- };
337
- var sort = createNewSortInstance({
338
- comparer: defaultComparer,
339
- });
340
- createNewSortInstance({
341
- comparer: defaultComparer,
342
- inPlaceSorting: true,
343
- });
361
+ function getStringLiteralCalleeName(path) {
362
+ if (path.parentPath.isCallExpression()) {
363
+ const callee = path.parentPath.get("callee");
364
+ if (callee.isIdentifier()) {
365
+ return callee.node.name;
366
+ }
367
+ }
368
+ }
369
+ function getTemplateElementCalleeName(path) {
370
+ if (path.parentPath.isTemplateLiteral()) {
371
+ const pp = path.parentPath;
372
+ if (pp.parentPath.isCallExpression()) {
373
+ const callee = pp.parentPath.get("callee");
374
+ if (callee.isIdentifier()) {
375
+ return callee.node.name;
376
+ }
377
+ }
378
+ }
379
+ }
344
380
 
345
381
  function handleValue$1(options) {
346
- const { addToUsedBy, id, magicString, node, raw, replaceMap, offset = 0, escape = false } = options;
382
+ const { ctx, id, path, magicString, raw, replaceMap, offset = 0, escape = false, preserve = false } = options;
383
+ const node = path.node;
347
384
  let value = raw;
348
385
  const arr = sort(splitCode(value)).desc((x) => x.length);
349
386
  for (const str of arr) {
350
387
  if (replaceMap.has(str)) {
351
- addToUsedBy(str, id);
388
+ ctx.addToUsedBy(str, id);
389
+ if (preserve) {
390
+ ctx.addPreserveClass(str);
391
+ }
352
392
  const v = replaceMap.get(str);
353
393
  if (v) {
354
394
  value = value.replaceAll(str, v);
355
395
  }
356
396
  }
357
397
  }
398
+ if (preserve) {
399
+ return;
400
+ }
358
401
  if (typeof node.start === "number" && typeof node.end === "number" && value) {
359
402
  const start = node.start + offset;
360
403
  const end = node.end - offset;
@@ -363,48 +406,56 @@ function handleValue$1(options) {
363
406
  }
364
407
  }
365
408
  }
366
- const plugin = declare((api, options) => {
409
+ const JsPlugin = declare((api, options) => {
367
410
  api.assertVersion(7);
368
- const { magicString, replaceMap, id, addToUsedBy } = options;
411
+ const { magicString, replaceMap, id, ctx } = options;
369
412
  return {
370
413
  visitor: {
371
414
  StringLiteral: {
372
415
  enter(p) {
373
- const node = p.node;
374
- handleValue$1({
375
- addToUsedBy,
416
+ const opts = {
417
+ ctx,
376
418
  id,
377
419
  magicString,
378
- node,
379
- raw: node.value,
420
+ path: p,
421
+ raw: p.node.value,
380
422
  replaceMap,
381
423
  offset: 1,
382
- escape: true
383
- });
424
+ escape: true,
425
+ preserve: false
426
+ };
427
+ const calleeName = getStringLiteralCalleeName(p);
428
+ if (calleeName && ctx.isPreserveFunction(calleeName)) {
429
+ opts.preserve = true;
430
+ }
431
+ handleValue$1(opts);
384
432
  }
385
433
  },
386
434
  TemplateElement: {
387
435
  enter(p) {
388
- const node = p.node;
389
- handleValue$1({
390
- addToUsedBy,
436
+ const opts = {
437
+ ctx,
391
438
  id,
392
439
  magicString,
393
- node,
394
- raw: node.value.raw,
440
+ path: p,
441
+ raw: p.node.value.raw,
395
442
  replaceMap,
396
443
  offset: 0,
397
- escape: false
398
- });
444
+ escape: false,
445
+ preserve: false
446
+ };
447
+ const calleeName = getTemplateElementCalleeName(p);
448
+ if (calleeName && ctx.isPreserveFunction(calleeName)) {
449
+ opts.preserve = true;
450
+ }
451
+ handleValue$1(opts);
399
452
  }
400
453
  }
401
454
  }
402
455
  };
403
456
  });
404
- function preProcessJs(options) {
405
- const { code, replaceMap, id, addToUsedBy } = options;
406
- const magicString = typeof code === "string" ? new MagicString(code) : code;
407
- babel.transformSync(magicString.original, {
457
+ function transformSync(code, plugins, filename) {
458
+ babel.transformSync(code, {
408
459
  presets: [
409
460
  // ['@babel/preset-react', {}],
410
461
  [
@@ -415,24 +466,99 @@ function preProcessJs(options) {
415
466
  }
416
467
  ]
417
468
  ],
418
- plugins: [
469
+ plugins,
470
+ filename
471
+ });
472
+ }
473
+ function preProcessJs(options) {
474
+ const { code, replaceMap, id, ctx } = options;
475
+ const magicString = typeof code === "string" ? new MagicString(code) : code;
476
+ transformSync(
477
+ magicString.original,
478
+ [
419
479
  [
420
- plugin,
480
+ JsPlugin,
421
481
  {
422
482
  magicString,
423
483
  replaceMap,
424
484
  id,
425
- addToUsedBy
485
+ ctx
426
486
  }
427
487
  ]
428
488
  ],
429
- filename: id
430
- });
489
+ id
490
+ );
491
+ return magicString.toString();
492
+ }
493
+ function preProcessRawCode(options) {
494
+ const { code, replaceMap, ctx } = options;
495
+ const magicString = typeof code === "string" ? new MagicString(code) : code;
496
+ const markArr = [];
497
+ for (const regex of ctx.preserveFunctionRegexs) {
498
+ const allArr = [];
499
+ let arr = null;
500
+ while ((arr = regex.exec(magicString.original)) !== null) {
501
+ allArr.push(arr);
502
+ markArr.push([arr.index, arr.index + arr[0].length]);
503
+ }
504
+ for (const regExpMatch of allArr) {
505
+ let ast;
506
+ try {
507
+ ast = parse$1(regExpMatch[0], {
508
+ sourceType: "unambiguous"
509
+ });
510
+ traverse$1(ast, {
511
+ StringLiteral: {
512
+ enter(p) {
513
+ const arr2 = sort(splitCode(p.node.value)).desc((x) => x.length);
514
+ for (const v of arr2) {
515
+ if (replaceMap.has(v)) {
516
+ ctx.addPreserveClass(v);
517
+ }
518
+ }
519
+ }
520
+ },
521
+ TemplateElement: {
522
+ enter(p) {
523
+ const arr2 = sort(splitCode(p.node.value.raw)).desc((x) => x.length);
524
+ for (const v of arr2) {
525
+ if (replaceMap.has(v)) {
526
+ ctx.addPreserveClass(v);
527
+ }
528
+ }
529
+ }
530
+ }
531
+ });
532
+ } catch {
533
+ continue;
534
+ }
535
+ }
536
+ }
537
+ for (const [key, value] of replaceMap) {
538
+ const regex = new RegExp(escapeStringRegexp(key), "g");
539
+ let arr = null;
540
+ while ((arr = regex.exec(magicString.original)) !== null) {
541
+ const start = arr.index;
542
+ const end = arr.index + arr[0].length;
543
+ let shouldUpdate = true;
544
+ for (const [ps, pe] of markArr) {
545
+ if (start > ps && start < pe || end < pe && end > ps) {
546
+ shouldUpdate = false;
547
+ break;
548
+ }
549
+ }
550
+ if (shouldUpdate) {
551
+ magicString.update(start, end, value);
552
+ markArr.push([start, end]);
553
+ }
554
+ }
555
+ }
431
556
  return magicString.toString();
432
557
  }
433
558
 
434
559
  function handleValue(raw, node, options) {
435
- const { replaceMap, classGenerator: clsGen, splitQuote = true } = options;
560
+ const { replaceMap, ctx, splitQuote = true } = options;
561
+ const clsGen = ctx.classGenerator;
436
562
  const array = splitCode(raw, {
437
563
  splitQuote
438
564
  });
@@ -451,7 +577,7 @@ function handleValue(raw, node, options) {
451
577
  return rawString;
452
578
  }
453
579
  function jsHandler(rawSource, options) {
454
- const result = transformSync(rawSource, {
580
+ const result = transformSync$1(rawSource, {
455
581
  babelrc: false,
456
582
  ast: true,
457
583
  plugins: [
@@ -499,4 +625,4 @@ function jsHandler(rawSource, options) {
499
625
  return result;
500
626
  }
501
627
 
502
- export { cssHandler, handleValue, htmlHandler, jsHandler, preProcessJs };
628
+ export { Context, cssHandler, handleValue, htmlHandler, jsHandler, preProcessJs, preProcessRawCode };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailwindcss-mangle/core",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "The core of tailwindcss-mangle",
5
5
  "exports": {
6
6
  ".": {
@@ -36,27 +36,43 @@
36
36
  "registry": "https://registry.npmjs.org/"
37
37
  },
38
38
  "dependencies": {
39
- "@ast-core/escape": "^1.0.0",
40
- "@babel/core": "^7.22.10",
39
+ "@ast-core/escape": "^1.0.1",
40
+ "@babel/core": "^7.22.19",
41
41
  "@babel/helper-plugin-utils": "^7.22.5",
42
- "@babel/preset-typescript": "^7.22.5",
43
- "@babel/types": "^7.22.10",
44
- "magic-string": "^0.30.2",
42
+ "@babel/parser": "^7.22.16",
43
+ "@babel/preset-typescript": "^7.22.15",
44
+ "@babel/traverse": "^7.22.19",
45
+ "@babel/types": "^7.22.19",
46
+ "fast-sort": "^3.4.0",
47
+ "magic-string": "^0.30.3",
48
+ "micromatch": "^4.0.5",
45
49
  "parse5": "^7.1.2",
46
- "postcss": "^8.4.27",
50
+ "postcss": "^8.4.29",
47
51
  "postcss-selector-parser": "^6.0.13",
48
- "@tailwindcss-mangle/shared": "^2.1.0"
52
+ "@tailwindcss-mangle/config": "^2.2.0",
53
+ "@tailwindcss-mangle/shared": "^2.2.0"
49
54
  },
50
55
  "devDependencies": {
51
- "@parse5/tools": "^0.2.0",
52
- "@types/babel__core": "^7.20.1"
56
+ "@parse5/tools": "^0.3.0",
57
+ "@types/babel__core": "^7.20.1",
58
+ "@types/babel__traverse": "^7.20.1",
59
+ "@types/micromatch": "^4.0.2",
60
+ "@vue/compiler-core": "^3.3.4",
61
+ "@vue/compiler-sfc": "^3.3.4"
53
62
  },
54
63
  "homepage": "https://github.com/sonofmagic/tailwindcss-mangle",
55
64
  "repository": {
56
65
  "type": "git",
57
66
  "url": "git+https://github.com/sonofmagic/tailwindcss-mangle.git"
58
67
  },
68
+ "bugs": {
69
+ "url": "https://github.com/sonofmagic/tailwindcss-mangle/issues"
70
+ },
71
+ "directories": {
72
+ "test": "test"
73
+ },
59
74
  "scripts": {
75
+ "dev": "unbuild --sourcemap",
60
76
  "build": "unbuild",
61
77
  "test": "vitest run --coverage.enabled",
62
78
  "test:dev": "vitest",