npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gulp-css-morfemo

v1.1.12

Published

Gulp плагин расширяет возможности именования CSS-классов добавляя функционал к приставкам и суффиксам имен классов + фича с CSS-переменными

Readme

gulp-css-morfemo

Extend CSS classes like words in a language: root + prefixes + suffixes.

📌 Introduction

In real-world projects, you often have to duplicate styles for parent and child elements, write separate classes for each layer (color, layout, animation), and repeat CSS variable values in properties. This bloats the code and makes maintenance harder.

Solution: the gulp-css-morfemo plugin allows you to use word-formation when naming classes. You define a root (the base class name), and prefixes and suffixes automatically extend its behavior.

Everything is configurable, and all features can be turned off individually. The plugin analyzes your HTML and inserts the needed selectors directly into your CSS.

🚀 Features

  • Suffixes — control nesting and sibling relationships through class names.
  • Prefixes — separate CSS into layers (one letter + hyphen).
  • Special --*-self variables — automatically transform into properties.
  • Flexible configuration — each feature can be enabled/disabled separately.
  • Custom suffix config — redefine rules for your own project.
  • Auto HTML analysis — the plugin finds classes in HTML automatically.
  • Selector merging — generated selectors are merged with the original rule (can be disabled).

📦 Installation

npm install gulp-css-morfemo --save-dev

🛠 Использование

Минимальный пример gulpfile

const gulp = require('gulp');
const postcss = require('gulp-postcss');
const cssMorfemo = require('gulp-css-morfemo');

function cssTask() {
    return gulp.src('src/main.css')
        .pipe(postcss([
            cssMorfemo({
                htmlPath: 'src/index.html',   // analyze classes from HTML
                verbose: true                  // see what's happening
            })
        ]))
        .pipe(gulp.dest('dist/'));
}

exports.default = cssTask;

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | htmlPath | string | null | Path to HTML file for class analysis | | suffixConfigPath | string | null | Path to custom suffix config | | enableVariables | boolean | true | Process self variables | | enablePrefixes | boolean | true | Process prefixes (layers) | | enableSuffixes | boolean | true | Process suffixes | | keepSelf | boolean | false | Keep original --*-self variables | | combineSelectors | boolean | true | Merge generated selectors with the original rule (comma-separated). When false, creates separate rules | | verbose | boolean | false | Show detailed console output |

🔍 Detailed Functionality

1. Suffixes

Control nesting and siblings directly through class names:

<div class="test-">
    <div>Direct child</div>
</div>

<div class="test__">
    <div>All descendants (any depth)</div>
</div>

<div class="test_">
    <div>Next sibling</div>
</div>
.test { color: red; }

Result (default, combineSelectors: true):

.test, .test- > *, .test__ *, .test_ + * { color: red; }

If you want separate rules, set combineSelectors: false.


🧮 Numeric suffixes (nth-child)

Work with element sequences without extra classes in HTML:

<div class="someclass-3n2">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
</div>
.someclass { background: lightgray; }

Result:

.someclass, .someclass-3n2 > *:nth-child(3n + 2) { background: lightgray; }

The style applies to the 2nd, 5th, 8th… elements. The numbers 3 and 2 are just examples; you can use any numbers.


🔤 Aliases (syntactic sugar suffixes)

For common scenarios, there are handy aliases:

| Suffix | Selector | |--------|----------| | -even | > *:nth-child(even) | | -odd | > *:nth-child(odd) | | -first | > *:first-child | | -last | > *:last-child | | -not-first | > *:not(:first-child) | | -not-empty | > *:not(:empty) |

<div class="list-even">
    <div>1</div>
    <div>2</div>
    <div>3</div>
</div>
.list { background: lightblue; }

Result:

.list, .list-even > *:nth-child(even) { background: lightblue; }

📋 Default suffix config

Here's the full list of suffixes available out of the box:

[
    { "suffix": "---", "match": "---$", "transform": ".${className}, .${className} *" },
    { "suffix": "--even", "match": "--even$", "transform": ".${className} *:nth-child(even)" },
    { "suffix": "--odd", "match": "--odd$", "transform": ".${className} *:nth-child(odd)" },
    { "suffix": "--xny", "match": "--(\\d+)n(\\d+)$", "transform": ".${className} *:nth-child(${1}n + ${2})" },
    { "suffix": "--xn", "match": "--(\\d+)n$", "transform": ".${className} *:nth-child(${1}n)" },
    { "suffix": "--x", "match": "--(\\d+)$", "transform": ".${className} *:nth-child(${1})" },
    { "suffix": "--first", "match": "--first$", "transform": ".${className} *:first-child" },
    { "suffix": "--last", "match": "--last$", "transform": ".${className} *:last-child" },
    { "suffix": "--not-empty", "match": "--not-empty$", "transform": ".${className} *:not(:empty)" },
    { "suffix": "--empty", "match": "--empty$", "transform": ".${className} *:empty" },
    { "suffix": "--", "match": "--$", "transform": ".${className} *" },
    { "suffix": "-even", "match": "-even$", "transform": ".${className} > *:nth-child(even)" },
    { "suffix": "-odd", "match": "-odd$", "transform": ".${className} > *:nth-child(odd)" },
    { "suffix": "-xny", "match": "-(\\d+)n(\\d+)$", "transform": ".${className} > *:nth-child(${1}n + ${2})" },
    { "suffix": "-xn", "match": "-(\\d+)n$", "transform": ".${className} > *:nth-child(${1}n)" },
    { "suffix": "-x", "match": "-(\\d+)$", "transform": ".${className} > *:nth-child(${1})" },
    { "suffix": "-first", "match": "-first$", "transform": ".${className} > *:first-child" },
    { "suffix": "-last", "match": "-last$", "transform": ".${className} > *:last-child" },
    { "suffix": "-not-first", "match": "-not-first$", "transform": ".${className} > *:not(:first-child)" },
    { "suffix": "-not-last", "match": "-not-last$", "transform": ".${className} > *:not(:last-child)" },
    { "suffix": "-not-empty", "match": "-not-empty$", "transform": ".${className} > *:not(:empty)" },
    { "suffix": "-empty", "match": "-empty$", "transform": ".${className} > *:empty" },
    { "suffix": "-", "match": "-$", "transform": ".${className} > *" },
    { "suffix": "__", "match": "__$", "transform": ".${className} ~ *" },
    { "suffix": "_", "match": "_$", "transform": ".${className} + *" }
]

Field explanations:

  • suffix — the suffix itself.
  • match — regular expression to detect the suffix in the class name.
  • transform — CSS selector template where the full class name (${className}) is substituted.
  • The order of elements determines priority (first = highest priority).

Custom suffix config

If the built-in config doesn't suit you, you can create your own JSON file (an array of objects with the same fields) and point to it via the suffixConfigPath option. The structure exactly matches the default config above. The order of elements determines priority (first = highest priority).

A custom config is useful when you want to:

  • Change existing suffixes (e.g., replace - with _child).
  • Add your own suffixes.
  • Remove unnecessary suffixes.

2. Prefixes (layers)

Separate CSS into logical layers: l- (layout), c- (color), a- (animation), t- (typography), s- (state). This helps keep the code organized.

.l-test { display: flex; gap: 10px; }
.c-test { color: red; background: blue; }

Result: the plugin creates a .test class that combines both layers:

.test, .l-test, .c-test {
    display: flex;
    gap: 10px;
    color: red;
    background: blue;
}

Now in HTML you can use .test instead of l-test c-test. If you don't need a particular layer, simply don't write it in CSS. Which letter represents which layer is up to you.

3. self variables

An ergonomic way to pass property values via CSS variables.

.test {
    --color-self: red;
}

Result: the plugin transforms this into:

.test {
    --color: red;
    color: var(--color);
}

The --color-self variable is removed (if keepSelf: false), and the color property gets its value via var(--color). This is handy when you need to inherit a value through the cascade.

📄 Full build example (with watch and browserSync)

const gulp = require('gulp');
const postcss = require('gulp-postcss');
const browserSync = require('browser-sync').create();
const cssMorfemo = require('gulp-css-morfemo');

function cssTask() {
    return gulp.src('src/main.css')
        .pipe(postcss([
            cssMorfemo({
                htmlPath: 'src/index.html',
                keepSelf: false,
                combineSelectors: true,   // can be omitted (default is true)
                verbose: true
            })
        ]))
        .pipe(gulp.dest('dist/'));
}

function htmlTask() {
    return gulp.src('src/index.html')
        .pipe(gulp.dest('dist/'));
}

function watch() {
    browserSync.init({
        server: { baseDir: 'dist/' },
        port: 3000,
        notify: false,
        open: true
    });

    gulp.watch('src/main.css', gulp.series(
        cssTask,
        (done) => { browserSync.reload(); done(); }
    ));

    gulp.watch('src/index.html', gulp.series(
        cssTask,        // rebuild CSS with new classes
        htmlTask,
        (done) => { browserSync.reload(); done(); }
    ));
}

exports.default = gulp.series(
    cssTask,
    htmlTask,
    watch
);

❓ Why series, not parallel?

The plugin analyzes the HTML file (htmlPath) before processing CSS. So it's important to copy/generate the HTML (or update it) first, then run the CSS task. Use gulp.series to guarantee the correct order.

🤝 Support the project

This plugin was born from a personal project and the need for flexible CSS workflows. If you find it useful, please consider supporting its development:

📬 Ideas, bugs, suggestions — send me a personal message on GitHub.

📄 License

MIT © Anarkio