Skip to content

Commit 1a49142

Browse files
committedDec 29, 2023
feat: add clsx/lite module
1 parent 5cac14c commit 1a49142

File tree

7 files changed

+194
-25
lines changed

7 files changed

+194
-25
lines changed
 

‎bench/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const classnames = require('classnames');
33
const classcat = require('classcat');
44
const clsx = require('../dist/clsx');
55
const old = require('clsx');
6+
const lite = require('../dist/lite');
67

78
function bench(name, ...args) {
89
console.log(`\n# ${name}`);
@@ -11,6 +12,7 @@ function bench(name, ...args) {
1112
.add('classnames ', () => classnames.apply(classnames, args))
1213
.add('clsx (prev) ', () => old.apply(old, args))
1314
.add('clsx ', () => clsx.apply(clsx, args))
15+
.add('clsx (lite) ', () => lite.apply(lite, args))
1416
.on('cycle', e => console.log(' ' + e.target))
1517
.run();
1618
}

‎bench/readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ These are the results while running this directory's benchmark suite in Node v20
1313
classcat ≠ x 9,613,381 ops/sec ±0.16% (94 runs sampled)
1414
classnames x 6,540,072 ops/sec ±0.11% (101 runs sampled)
1515
clsx x 12,924,662 ops/sec ±0.15% (102 runs sampled)
16+
clsx/lite x 13,122,004 ops/sec ±0.40% (99 runs sampled)
1617
1718
# Objects
1819
classcat ≠ x 8,936,903 ops/sec ±0.12% (100 runs sampled)

‎bin/index.js

+49-16
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ const zlib = require('zlib');
44
const { minify } = require('terser');
55
const pkg = require('../package.json');
66

7-
if (!fs.existsSync('dist')) fs.mkdirSync('dist');
8-
97
/**
108
* @param {string} file
119
* @param {string} source
@@ -17,22 +15,57 @@ function write(file, source) {
1715
compress: true,
1816
});
1917

20-
fs.writeFileSync(file, result.code);
21-
console.log('~> "%s" (%d b)', file, zlib.gzipSync(result.code).byteLength);
18+
if (result.code) {
19+
fs.writeFileSync(file, result.code);
20+
let size = zlib.gzipSync(result.code).byteLength;
21+
console.log('~> "%s" (%d b)', file, size);
22+
} else {
23+
console.error('!! "%s" ::', file, result.error);
24+
}
2225
}
2326

24-
let input = fs.readFileSync('src/index.js', 'utf8');
27+
/**
28+
* @typedef Export
29+
* @property {Condition} import
30+
* @property {Condition} default
31+
*/
2532

26-
// copy for ESM
27-
write(pkg.module, input);
33+
/**
34+
* @typedef Condition
35+
* @property {string} types
36+
* @property {string} default
37+
*/
2838

29-
// transform ESM -> CJS exports
30-
write(pkg.main, input.replace('export function', 'function').replace(
31-
'export default clsx;',
32-
'module.exports = clsx;\n'
33-
+ 'module.exports.clsx = clsx;'
34-
));
39+
/**
40+
* @param {string} file
41+
* @param {"." | "./lite"} entry
42+
*/
43+
function bundle(file, entry) {
44+
fs.existsSync('dist') || fs.mkdirSync('dist');
45+
46+
/**
47+
* @type {Export}
48+
*/
49+
let output = pkg.exports[entry];
50+
let input = fs.readFileSync(file, 'utf8');
51+
52+
// copy for ESM file
53+
write(output.import.default, input);
54+
55+
// transform ESM -> CJS exports
56+
write(output.default.default, input.replace('export function', 'function').replace(
57+
'export default clsx;',
58+
'module.exports = clsx;\n'
59+
+ 'module.exports.clsx = clsx;'
60+
));
61+
62+
if (entry === '.') {
63+
// transform ESM -> UMD exports
64+
input = input.replace('export function', 'function').replace('export default clsx;', 'return clsx.clsx=clsx, clsx;');
65+
write(pkg.unpkg, '!function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):global.clsx=factory()}(this,function(){' + input + '});');
66+
}
67+
}
3568

36-
// transform ESM -> UMD exports
37-
input = input.replace('export function', 'function').replace('export default clsx;', 'return clsx.clsx=clsx, clsx;');
38-
write(pkg.unpkg, '!function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):global.clsx=factory()}(this,function(){' + input + '});');
69+
bundle('src/index.js', '.');
70+
console.log('---');
71+
bundle('src/lite.js', './lite');

‎package.json

+20-8
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,30 @@
66
"module": "dist/clsx.mjs",
77
"unpkg": "dist/clsx.min.js",
88
"main": "dist/clsx.js",
9+
"types": "clsx.d.ts",
10+
"license": "MIT",
911
"exports": {
10-
"import": {
11-
"types": "./clsx.d.mts",
12-
"default": "./dist/clsx.mjs"
12+
".": {
13+
"import": {
14+
"types": "./clsx.d.mts",
15+
"default": "./dist/clsx.mjs"
16+
},
17+
"default": {
18+
"types": "./clsx.d.ts",
19+
"default": "./dist/clsx.js"
20+
}
1321
},
14-
"default": {
15-
"types": "./clsx.d.ts",
16-
"default": "./dist/clsx.js"
22+
"./lite": {
23+
"import": {
24+
"types": "./clsx.d.mts",
25+
"default": "./dist/lite.mjs"
26+
},
27+
"default": {
28+
"types": "./clsx.d.ts",
29+
"default": "./dist/lite.js"
30+
}
1731
}
1832
},
19-
"types": "clsx.d.ts",
20-
"license": "MIT",
2133
"author": {
2234
"name": "Luke Edwards",
2335
"email": "luke.edwards05@gmail.com",

‎readme.md

+46-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,45 @@ clsx(true, false, '', null, undefined, 0, NaN);
6666
//=> ''
6767
```
6868

69+
## Modes
70+
71+
There are multiple "versions" of `clsx` available, which allows you to bring only the functionality you need!
72+
73+
#### `clsx`
74+
> **Size (gzip):** 239 bytes<br>
75+
> **Availability:** CommonJS, ES Module, UMD
76+
77+
The default `clsx` module; see [API](#API) for info.
78+
79+
```js
80+
import { clsx } from 'clsx';
81+
// or
82+
import clsx from 'clsx';
83+
```
84+
85+
#### `clsx/lite`
86+
> **Size (gzip):** 140 bytes<br>
87+
> **Availability:** CommonJS, ES Module<br>
88+
> **CAUTION:** Accepts **ONLY** string arguments!
89+
90+
Ideal for applications that ***only*** use the string-builder pattern.
91+
92+
Any non-string arguments are ignored!
93+
94+
```js
95+
import { clsx } from 'clsx/lite';
96+
// or
97+
import clsx from 'clsx/lite';
98+
99+
// string
100+
clsx('hello', true && 'foo', false && 'bar');
101+
// => "hello foo"
102+
103+
// NOTE: Any non-string input(s) ignored
104+
clsx({ foo: true });
105+
//=> ""
106+
```
107+
69108
## Benchmarks
70109

71110
For snapshots of cross-browser results, check out the [`bench`](bench) directory~!
@@ -81,8 +120,8 @@ All browsers that support [`Array.isArray`](https://developer.mozilla.org/en-US/
81120
## Tailwind Support
82121

83122
Here some additional (optional) steps to enable classes autocompletion using `clsx` with Tailwind CSS.
84-
<details>
85123

124+
<details>
86125
<summary>
87126
Visual Studio Code
88127
</summary>
@@ -100,6 +139,12 @@ Here some additional (optional) steps to enable classes autocompletion using `cl
100139
```
101140
</details>
102141

142+
You may find the [`clsx/lite`](#clsxlite) module useful within Tailwind contexts. This is especially true if/when your application **only** composes classes in this pattern:
143+
144+
```js
145+
clsx('text-base', props.active && 'text-primary', props.className);
146+
```
147+
103148
## Related
104149

105150
- [obj-str](https://github.com/lukeed/obj-str) - A smaller (96B) and similiar utility that only works with Objects.

‎src/lite.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function clsx() {
2+
var i=0, tmp, str='', len=arguments.length;
3+
for (; i < len; i++) {
4+
if (tmp = arguments[i]) {
5+
if (typeof tmp === 'string') {
6+
str += (str && ' ') + tmp;
7+
}
8+
}
9+
}
10+
return str;
11+
}
12+
13+
export default clsx;

‎test/lite.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// @ts-check
2+
import { test } from 'uvu';
3+
import * as assert from 'uvu/assert';
4+
import * as mod from '../src/lite';
5+
6+
const fn = mod.default;
7+
8+
test('exports', () => {
9+
assert.type(mod.default, 'function', 'exports default function');
10+
assert.type(mod.clsx, 'function', 'exports named function');
11+
assert.is(mod.default, mod.clsx, 'exports are equal');
12+
13+
assert.type(mod.default(), 'string', '~> returns string output');
14+
assert.type(mod.clsx(), 'string', '~> returns string output');
15+
});
16+
17+
test('strings', () => {
18+
assert.is(fn(''), '');
19+
assert.is(fn('foo'), 'foo');
20+
assert.is(fn(true && 'foo'), 'foo');
21+
assert.is(fn(false && 'foo'), '');
22+
});
23+
24+
test('strings (variadic)', () => {
25+
assert.is(fn(''), '');
26+
assert.is(fn('foo', 'bar'), 'foo bar');
27+
assert.is(fn(true && 'foo', false && 'bar', 'baz'), 'foo baz');
28+
assert.is(fn(false && 'foo', 'bar', 'baz', ''), 'bar baz');
29+
});
30+
31+
test('emptys', () => {
32+
assert.is(fn(''), '');
33+
assert.is(fn(undefined), '');
34+
assert.is(fn(null), '');
35+
assert.is(fn(0), '');
36+
});
37+
38+
// lite ignores all non-strings
39+
test('non-strings', () => {
40+
// number
41+
assert.is(fn(1), '');
42+
assert.is(fn(1, 2), '');
43+
assert.is(fn(Infinity), '');
44+
assert.is(fn(NaN), '');
45+
assert.is(fn(0), '');
46+
47+
// objects
48+
assert.is(fn({}), '');
49+
assert.is(fn(null), '');
50+
assert.is(fn({ a:1 }), '');
51+
assert.is(fn({ a:1 }, { b:2 }), '');
52+
53+
// arrays
54+
assert.is(fn([]), '');
55+
assert.is(fn(['foo']), '');
56+
assert.is(fn(['foo', 'bar']), '');
57+
58+
// functions
59+
assert.is(fn(fn), '');
60+
assert.is(fn(fn, fn), '');
61+
});
62+
63+
test.run();

0 commit comments

Comments
 (0)
Please sign in to comment.