cls hot path: clsx vs cls-v1 vs cls-v2

Benchmark publishedon

Description

Compares clsx src/index.js against local cls helpers under string, object, array, nested array, and optional-value inputs. Numeric class names are not included because the local helpers intentionally do not support them.

Setup

const CLS_RUNS = 1000;

const CLASS_OBJECT_PRIMARY = {
    active: true,
    disabled: false,
    loading: false,
};

const CLASS_OBJECT_STATUS = {
    focused: true,
    invalid: false,
    compact: true,
};

const CLASS_OBJECT_VARIANT = {
    success: true,
    danger: false,
    muted: true,
};

const CLASS_LIST_PRIMARY = ['panel', ['corners_rad2', false, 'elevated']];
const CLASS_LIST_STATUS = ['input_field', [null, 'with_icon', ['large']]];
const CLASS_LIST_VARIANT = ['tag', ['filled', undefined, ['interactive']]];

let clsSink = 0;

// clsx src/index.js, adapted for jsPerf by removing ESM exports.
// Source: https://raw.githubusercontent.com/lukeed/clsx/refs/heads/master/src/index.js
function toVal(mix) {

    var key, nested, output = '';

    if (typeof mix === 'string' || typeof mix === 'number') {
        output += mix;
    } else if (typeof mix === 'object') {
        if (Array.isArray(mix)) {
            var length = mix.length;

            for (key = 0; key < length; key++) {
                if (mix[key]) {
                    if (nested = toVal(mix[key])) {
                        output && (output += ' ');
                        output += nested;
                    }
                }
            }
        } else {
            for (nested in mix) {
                if (mix[nested]) {
                    output && (output += ' ');
                    output += nested;
                }
            }
        }
    }

    return output;
}

function clsxIndex() {

    var index = 0, value, resolved, output = '', length = arguments.length;

    for (; index < length; index++) {
        if (value = arguments[index]) {
            if (resolved = toVal(value)) {
                output && (output += ' ');
                output += resolved;
            }
        }
    }

    return output;
}

function clsV1() {

    let output = "";

    for (let index = 0; index < arguments.length; index += 1) {
        const resolved = clsV1Resolver(arguments[index]);

        if (resolved) {
            if (output) output += " ";
            output += resolved;
        }
    }

    return output;
}

function clsV1Resolver(value) {

    if (typeof value === "string") return value;
    if (!value || typeof value !== "object") return "";

    let output = "";

    if (Array.isArray(value)) {
        for (let index = 0; index < value.length; index += 1) {
            const resolved = clsV1Resolver(value[index]);

            if (resolved) {
                if (output) output += " ";
                output += resolved;
            }
        }

        return output;
    }

    for (const key in value) {
        if (value[key]) {
            if (output) output += " ";
            output += key;
        }
    }

    return output;
}

function clsV2() {

    let output = "";
    let value;

    for (let index = 0, length = arguments.length; index < length; index += 1) {
        value = arguments[index];

        if (!value) continue;

        if (typeof value === "string") {
            output = output ? output + " " + value : value;
            continue;
        }

        if (typeof value === "object") {
            output = appendClsV2Value(output, value);
        }
    }

    return output;
}

function appendClsV2Value(output, value) {

    if (!value) return output;

    if (typeof value === "string") {
        return output ? output + " " + value : value;
    }

    if (typeof value !== "object") return output;

    let key;

    if (Array.isArray(value)) {
        for (let index = 0, length = value.length; index < length; index += 1) {
            output = appendClsV2Value(output, value[index]);
        }

        return output;
    }

    for (key in value) {
        if (value[key]) {
            output = output ? output + " " + key : key;
        }
    }

    return output;
}

function runClsxIndex(current) {

    for (let index = 0; index < CLS_RUNS; index += 1) {
        current += clsxIndex('button', 'primary', 'compact').length;
        current += clsxIndex('button', CLASS_OBJECT_PRIMARY, 'corners_rad1').length;
        current += clsxIndex(CLASS_LIST_PRIMARY, CLASS_OBJECT_STATUS, 'visible').length;
        current += clsxIndex(false, 'segment_button', CLASS_LIST_STATUS, CLASS_OBJECT_VARIANT).length;
        current += clsxIndex(true, null, undefined, 'notification', CLASS_LIST_VARIANT).length;
    }

    return current;
}

function runClsV1(current) {

    for (let index = 0; index < CLS_RUNS; index += 1) {
        current += clsV1('button', 'primary', 'compact').length;
        current += clsV1('button', CLASS_OBJECT_PRIMARY, 'corners_rad1').length;
        current += clsV1(CLASS_LIST_PRIMARY, CLASS_OBJECT_STATUS, 'visible').length;
        current += clsV1(false, 'segment_button', CLASS_LIST_STATUS, CLASS_OBJECT_VARIANT).length;
        current += clsV1(true, null, undefined, 'notification', CLASS_LIST_VARIANT).length;
    }

    return current;
}

function runClsV2(current) {

    for (let index = 0; index < CLS_RUNS; index += 1) {
        current += clsV2('button', 'primary', 'compact').length;
        current += clsV2('button', CLASS_OBJECT_PRIMARY, 'corners_rad1').length;
        current += clsV2(CLASS_LIST_PRIMARY, CLASS_OBJECT_STATUS, 'visible').length;
        current += clsV2(false, 'segment_button', CLASS_LIST_STATUS, CLASS_OBJECT_VARIANT).length;
        current += clsV2(true, null, undefined, 'notification', CLASS_LIST_VARIANT).length;
    }

    return current;
}

const EXPECTED_RESULT = 'button primary compact';
const EXPECTED_MIXED_RESULT = 'panel corners_rad2 elevated focused compact visible';

if (clsxIndex('button', 'primary', 'compact') !== EXPECTED_RESULT) throw new Error('clsxIndex string result mismatch');
if (clsV1('button', 'primary', 'compact') !== EXPECTED_RESULT) throw new Error('clsV1 string result mismatch');
if (clsV2('button', 'primary', 'compact') !== EXPECTED_RESULT) throw new Error('clsV2 string result mismatch');
if (clsxIndex(CLASS_LIST_PRIMARY, CLASS_OBJECT_STATUS, 'visible') !== EXPECTED_MIXED_RESULT) throw new Error('clsxIndex mixed result mismatch');
if (clsV1(CLASS_LIST_PRIMARY, CLASS_OBJECT_STATUS, 'visible') !== EXPECTED_MIXED_RESULT) throw new Error('clsV1 mixed result mismatch');
if (clsV2(CLASS_LIST_PRIMARY, CLASS_OBJECT_STATUS, 'visible') !== EXPECTED_MIXED_RESULT) throw new Error('clsV2 mixed result mismatch');

Test Runner

Initializing...

Testing in
Test CaseOps/sec
clsx src/index.js
clsSink = runClsxIndex(clsSink);
ready
cls-v1.js
clsSink = runClsV1(clsSink);
ready
cls-v2.js
clsSink = runClsV2(clsSink);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

Revision 1
publishedon