Skip to content

Commit

Permalink
feat: add clsx/lite module
Browse files Browse the repository at this point in the history
  • Loading branch information
lukeed committed Dec 29, 2023
1 parent 5cac14c commit 1a49142
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 25 deletions.
2 changes: 2 additions & 0 deletions bench/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const classnames = require('classnames');
const classcat = require('classcat');
const clsx = require('../dist/clsx');
const old = require('clsx');
const lite = require('../dist/lite');

function bench(name, ...args) {
console.log(`\n# ${name}`);
Expand All @@ -11,6 +12,7 @@ function bench(name, ...args) {
.add('classnames ', () => classnames.apply(classnames, args))
.add('clsx (prev) ', () => old.apply(old, args))
.add('clsx ', () => clsx.apply(clsx, args))
.add('clsx (lite) ', () => lite.apply(lite, args))
.on('cycle', e => console.log(' ' + e.target))
.run();
}
Expand Down
1 change: 1 addition & 0 deletions bench/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ These are the results while running this directory's benchmark suite in Node v20
classcat ≠ x 9,613,381 ops/sec ±0.16% (94 runs sampled)
classnames x 6,540,072 ops/sec ±0.11% (101 runs sampled)
clsx x 12,924,662 ops/sec ±0.15% (102 runs sampled)
clsx/lite x 13,122,004 ops/sec ±0.40% (99 runs sampled)
# Objects
classcat ≠ x 8,936,903 ops/sec ±0.12% (100 runs sampled)
Expand Down
65 changes: 49 additions & 16 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ const zlib = require('zlib');
const { minify } = require('terser');
const pkg = require('../package.json');

if (!fs.existsSync('dist')) fs.mkdirSync('dist');

/**
* @param {string} file
* @param {string} source
Expand All @@ -17,22 +15,57 @@ function write(file, source) {
compress: true,
});

fs.writeFileSync(file, result.code);
console.log('~> "%s" (%d b)', file, zlib.gzipSync(result.code).byteLength);
if (result.code) {
fs.writeFileSync(file, result.code);
let size = zlib.gzipSync(result.code).byteLength;
console.log('~> "%s" (%d b)', file, size);
} else {
console.error('!! "%s" ::', file, result.error);
}
}

let input = fs.readFileSync('src/index.js', 'utf8');
/**
* @typedef Export
* @property {Condition} import
* @property {Condition} default
*/

// copy for ESM
write(pkg.module, input);
/**
* @typedef Condition
* @property {string} types
* @property {string} default
*/

// transform ESM -> CJS exports
write(pkg.main, input.replace('export function', 'function').replace(
'export default clsx;',
'module.exports = clsx;\n'
+ 'module.exports.clsx = clsx;'
));
/**
* @param {string} file
* @param {"." | "./lite"} entry
*/
function bundle(file, entry) {
fs.existsSync('dist') || fs.mkdirSync('dist');

/**
* @type {Export}
*/
let output = pkg.exports[entry];
let input = fs.readFileSync(file, 'utf8');

// copy for ESM file
write(output.import.default, input);

// transform ESM -> CJS exports
write(output.default.default, input.replace('export function', 'function').replace(
'export default clsx;',
'module.exports = clsx;\n'
+ 'module.exports.clsx = clsx;'
));

if (entry === '.') {
// transform ESM -> UMD exports
input = input.replace('export function', 'function').replace('export default clsx;', 'return clsx.clsx=clsx, clsx;');
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 + '});');
}
}

// transform ESM -> UMD exports
input = input.replace('export function', 'function').replace('export default clsx;', 'return clsx.clsx=clsx, clsx;');
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 + '});');
bundle('src/index.js', '.');
console.log('---');
bundle('src/lite.js', './lite');
28 changes: 20 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,30 @@
"module": "dist/clsx.mjs",
"unpkg": "dist/clsx.min.js",
"main": "dist/clsx.js",
"types": "clsx.d.ts",
"license": "MIT",
"exports": {
"import": {
"types": "./clsx.d.mts",
"default": "./dist/clsx.mjs"
".": {
"import": {
"types": "./clsx.d.mts",
"default": "./dist/clsx.mjs"
},
"default": {
"types": "./clsx.d.ts",
"default": "./dist/clsx.js"
}
},
"default": {
"types": "./clsx.d.ts",
"default": "./dist/clsx.js"
"./lite": {
"import": {
"types": "./clsx.d.mts",
"default": "./dist/lite.mjs"
},
"default": {
"types": "./clsx.d.ts",
"default": "./dist/lite.js"
}
}
},
"types": "clsx.d.ts",
"license": "MIT",
"author": {
"name": "Luke Edwards",
"email": "[email protected]",
Expand Down
47 changes: 46 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,45 @@ clsx(true, false, '', null, undefined, 0, NaN);
//=> ''
```

## Modes

There are multiple "versions" of `clsx` available, which allows you to bring only the functionality you need!

#### `clsx`
> **Size (gzip):** 239 bytes<br>
> **Availability:** CommonJS, ES Module, UMD
The default `clsx` module; see [API](#API) for info.

```js
import { clsx } from 'clsx';
// or
import clsx from 'clsx';
```

#### `clsx/lite`
> **Size (gzip):** 140 bytes<br>
> **Availability:** CommonJS, ES Module<br>
> **CAUTION:** Accepts **ONLY** string arguments!
Ideal for applications that ***only*** use the string-builder pattern.

Any non-string arguments are ignored!

```js
import { clsx } from 'clsx/lite';
// or
import clsx from 'clsx/lite';

// string
clsx('hello', true && 'foo', false && 'bar');
// => "hello foo"

// NOTE: Any non-string input(s) ignored
clsx({ foo: true });
//=> ""
```

## Benchmarks

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

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

<details>
<summary>
Visual Studio Code
</summary>
Expand All @@ -100,6 +139,12 @@ Here some additional (optional) steps to enable classes autocompletion using `cl
```
</details>

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:

```js
clsx('text-base', props.active && 'text-primary', props.className);
```

## Related

- [obj-str](https://github.com/lukeed/obj-str) - A smaller (96B) and similiar utility that only works with Objects.
Expand Down
13 changes: 13 additions & 0 deletions src/lite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function clsx() {
var i=0, tmp, str='', len=arguments.length;
for (; i < len; i++) {
if (tmp = arguments[i]) {
if (typeof tmp === 'string') {
str += (str && ' ') + tmp;
}
}
}
return str;
}

export default clsx;
63 changes: 63 additions & 0 deletions test/lite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// @ts-check
import { test } from 'uvu';
import * as assert from 'uvu/assert';
import * as mod from '../src/lite';

const fn = mod.default;

test('exports', () => {
assert.type(mod.default, 'function', 'exports default function');
assert.type(mod.clsx, 'function', 'exports named function');
assert.is(mod.default, mod.clsx, 'exports are equal');

assert.type(mod.default(), 'string', '~> returns string output');
assert.type(mod.clsx(), 'string', '~> returns string output');
});

test('strings', () => {
assert.is(fn(''), '');
assert.is(fn('foo'), 'foo');
assert.is(fn(true && 'foo'), 'foo');
assert.is(fn(false && 'foo'), '');
});

test('strings (variadic)', () => {
assert.is(fn(''), '');
assert.is(fn('foo', 'bar'), 'foo bar');
assert.is(fn(true && 'foo', false && 'bar', 'baz'), 'foo baz');
assert.is(fn(false && 'foo', 'bar', 'baz', ''), 'bar baz');
});

test('emptys', () => {
assert.is(fn(''), '');
assert.is(fn(undefined), '');
assert.is(fn(null), '');
assert.is(fn(0), '');
});

// lite ignores all non-strings
test('non-strings', () => {
// number
assert.is(fn(1), '');
assert.is(fn(1, 2), '');
assert.is(fn(Infinity), '');
assert.is(fn(NaN), '');
assert.is(fn(0), '');

// objects
assert.is(fn({}), '');
assert.is(fn(null), '');
assert.is(fn({ a:1 }), '');
assert.is(fn({ a:1 }, { b:2 }), '');

// arrays
assert.is(fn([]), '');
assert.is(fn(['foo']), '');
assert.is(fn(['foo', 'bar']), '');

// functions
assert.is(fn(fn), '');
assert.is(fn(fn, fn), '');
});

test.run();

0 comments on commit 1a49142

Please sign in to comment.