Giter Site home page Giter Site logo

css-ns's Introduction

css-ns

Build Status Dependency Status devDependency Status

tl;dr

There's no shortage of solutions to the problem of global CSS. The properties that set this one apart:

  • It's very simple, on the order of 100 well-tested lines of JS, with 0 dependencies.
  • Works with all your favorite styling languages, including Sass, PostCSS, Less and Stylus.
  • Doesn't rely on a specific bundler, meaning you can use Browserify, webpack, RequireJS, or any bundler-de-jour.
  • Isn't tied to any UI framework, but has opt-in convenience for use with React.
  • Generates stable and predictable class names for external parties, such as the consumers of your UI library on npm, or your test automation system.

The core API is very straightforward:

var ns = require('css-ns')('MyComponent');

ns('foo') // "MyComponent-foo"

Everything else is just added convenience for working with class names:

// Multiple class names:
ns('foo bar') // "MyComponent-foo MyComponent-bar"

// Dynamic list of class names, filtering falsy ones out:
ns([ 'foo', null, 'bar' ]) // "MyComponent-foo MyComponent-bar"

// Providing class names as object properties:
ns({ foo: true, unwanted: false, bar: true }) // "MyComponent-foo MyComponent-bar"

// Escaping the namespace where needed:
ns('foo =icon-star') // "MyComponent-foo icon-star"

And with the optional React integration:

// Simplest possible integration:
<div className={ns('foo')} /> // <div class="MyComponent-foo">

// Namespacing existing elements:
ns(<div className="foo" />) // <div class="MyComponent-foo">

// Creating a namespace-bound React instance:
var { React } = require('./config/css-ns')('MyComponent');
<div className="foo" /> // <div class="MyComponent-foo">
<div className={{ foo: true }} /> // <div class="MyComponent-foo">

Creating a bound React instance is a powerful way of enforcing a namespace within a file.

Getting started

Install with:

$ npm install --save css-ns

Then, create a namespace function:

var ns = require('css-ns')('MyComponent');

Usage example

It's easy to add css-ns to most frameworks and workflows. For example, if you happen to use React, this is what MyComponent.js might look like:

var ns = require('css-ns')('MyComponent');

module.exports = (props) => (
  <div className={ns('this')}>
    <button className={ns({ submit: true, isActive: props.isActive })} />
  </div>
);

This component produces the following DOM when rendered with truthy props.isActive:

<div class="MyComponent">
  <button class="MyComponent-submit MyComponent-isActive"></button>
</div>

To ensure you won't accidentally forget to wrap a className with ns(), you can use the optional React integration. Here we also use __filename (supported by node, Browserify, webpack and others) to make sure our namespace always matches the file that contains it:

// Instead of requiring React directly, let's require a
// wrapped version that's bound to the current namespace:
var { React } = require('./config/css-ns')(__filename);

// All className props within this file are automatically fed through css-ns. There's really no
// magic here; keep in mind <div /> is just JSX sugar for React.createElement('div', {});
module.exports = (props) => (
  <div className="this">
    <button className={{ submit: true, isActive: props.isActive }} />
  </div>
);

You'll note we also require('./config/css-ns'); this is explained thoroughly in Configuration.

Finally, if we were to style this component using Sass, those styles could be:

.MyComponent {
  background: white;

  &-isActive {
    background: cyan;
  }

  &-submit {
    font-weight: bold;
  }
}

The & reference is a Sass built-in, no plugins needed.

Configuration

The simple require('css-ns')(__filename) one-liner might very well be enough for some projects. If you need to set some options, however, it might become tedious to repeat them in every file. Having an .*rc-style configuration file would tie css-ns to environments with a file system (browsers don't have one), so to create a configuration file, just use whatever module system you're already using. Let's say we're using ES6:

// e.g. config/css-ns.js

import { createCssNs } from 'css-ns';

export default namespace => createCssNs({
  namespace,
  exclude: /^fa-/ // exclude Font Awesome classes, as they have their own "fa-" namespace
});

Then, to create a local namespace with the exclude option set:

import createCssNs from './config/css-ns'; // instead of the global 'css-ns'

const ns = createCssNs(__filename);

Or, in the more compact CommonJS form:

var ns = require('./config/css-ns')(__filename);

There's also a complete demo app configured this way.

API

The createCssNs() factory takes either a string or an options object as its single argument:

var createCssNs = require('css-ns');

// This shorthand syntax...
var ns = createCssNs(__filename);

// ...is equivalent to this options object:
var ns = createCssNs({
  namespace: __filename
});

All available options are:

Option Type Default Description
namespace string (none) Mandatory base part for the namespace, e.g. "MyComponent" or __filename. For convenience, a possible file path and suffix are ignored, so that if the provided value is "/path/to/MyComponent.js", the resulting namespace will still be "MyComponent".
prefix string "" All namespaces are prefixed with this string. By default, namespaces aren't prefixed. Class names that begin with this prefix are automatically excluded .
include regex /^[a-z]/ Only class names matching this regex are namespaced. By default, only ones starting in lower-case are. This works out nicely with upper-cased namespace values: it ensures only one namespace can be applied to a class name, and calling ns() multiple times has the same effect as calling it once.
exclude regex /^$/ Class names matching this regex are not namespaced. By default, nothing is excluded (since /^$/ won't ever match a class name). When both include and exclude match, exclude wins.
escape string "=" Class names beginning with this string are not namespaced. By default, = is used for escaping.
self regex /^this$/ Class names matching this regex are replaced with the name of the namespace itself. This allows you to e.g. mark the root of your UI component without any suffixes, just the component name.
glue string "-" This string is used to combine the namespace and the class name.
React object (none) Providing this option enables React integration. When provided, must be an instance of React, e.g. { react: require('react') }. See Use with React for details.

Use with React

By default, css-ns doesn't use or depend on React in any way. This ensures bundlers don't get confused in projects that don't need the React integration. To enable React integration, provide the React option for createCssNs():

// e.g. config/css-ns.js

var createCssNs = require('css-ns');
var React = require('react');

module.exports = function(namespace) {
  return createCssNs({
    namespace,
    React
  });
};

Wrapped React instance

Providing the React option will expose a wrapped React instance on resulting namespace functions:

var ns = require('./config/css-ns')('MyComponent');

ns.React.createElement('div', { className: 'foo' }) // <div class="MyComponent-foo">

Because JSX just sugar for React.createElement() calls, this allows you to:

var { React } = require('./config/css-ns')('MyComponent');

<div className="foo"> // <div class="MyComponent-foo">

The wrapping is in fact extremely thin, and everything except React.createElement() is just inherited from React proper.

Namespacing existing React elements

Providing the React option will also enable support for React elements in the ns() function, so that:

var React = require('react'); // vanilla, non-wrapped React instance

ns(<div className="foo" />) // <div class="MyComponent-foo">

This can be useful in the (rare) cases where your namespace has to be dynamically applied to elements created by some other module. Because the only safe way to do this is to invoke React.cloneElement() under the hood, it can have performance implications if overused.

Use with Sass

Use with Sass requires no special support or plugins. This input:

.MyComponent {
  background: cyan;

  &-row {
    color: red;
  }
}

will be compiled to this CSS:

.MyComponent {
  background: cyan;
}
.MyComponent-row {
  color: red;
}

Use with PostCSS

Use with PostCSS is easiest with the postcss-nested plugin. With it, this input:

.MyComponent {
  background: cyan;

  &-row {
    color: red;
  }
}

will be compiled to this CSS:

.MyComponent {
  background: cyan;
}
.MyComponent-row {
  color: red;
}

Use with Less

Use with Less requires no special support or plugins. This input:

.MyComponent {
  background: cyan;

  &-row {
    color: red;
  }
}

will be compiled to this CSS:

.MyComponent {
  background: cyan;
}
.MyComponent-row {
  color: red;
}

Use with Stylus

Use with Stylus requires no special support or plugins. This input:

.MyComponent
  background: cyan

  &-row
    color: red

will be compiled to this CSS:

.MyComponent {
  background: cyan;
}
.MyComponent-row {
  color: red;
}

Test suite

The web-based test suite is available at http://jrw.fi/css-ns/.

Release

  1. Bump version number in package.json
  2. $ git commit -m "Version bump for release." && git push
  3. $ cp css-ns.{js,d.ts} package.json dist/
  4. $ cd dist && npm publish
  5. Create release on GitHub, e.g. v1.1.3

Licence

MIT

css-ns's People

Contributors

jareware avatar vtainio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

css-ns's Issues

installation with yarn fails

Expected Behavior

yarn add css-ns should successfully install css-ns with latest versions of node

Actual Behavior

error [email protected]: The engine "node" is incompatible with this module. Expected version ">=9.3.0 <10". Got "10.7.0"
error Found incompatible module

Steps to Reproduce the Problem

  1. Initialize npm (npm init -y)
  2. yarn add css-ns while using any version of node < 9.3 or >= 10

Specifications

  • Version: 1.2.1
  • Platform: OSX

Notes

It looks like this commit fixed the issue but downloads from npm still show "node": ">=9.3.0 <10", in package.json. Maybe the latest just didn't get pushed to npm?

  "engines": {
    "node": ">=9.3.0 <10",
    "npm": ">=5.5.1 <6"
  },

Need to add ignore prefix

Great idea for a library! I uses it with big pleasure.
I think it could be improved. On my project we lack for a ignore prefix, which would be replaced by library. Let me illustrate what I mean:
Let we have a React Component:

import R from 'react'
import { createCssNs } from 'css-ns'
const { React } = createCssNs({
   'Example',
    React: R,
    glue: '-',
    globalPrefix: '~'
  })

class Example extends React.Component {
  render () {
    return (
      <div className='this'>
		<div className='title'></div>
                <div className='~global-class-name'
      </div>)
  }
}

And this would transforms to:

<div className='Example'>
        <div className='Example-title'></div>
        <div className='global-class-name'
</div>

It would be very helpful. Now I don't see any workaround except create a prefix for all non-components classes

Do not add className prop to React.Fragment

Hello, great module, thanks for saving me a lot of time !

Issue

Nested React.Fragments receive className as a prop. Fragments at first level are ok but not when they are nested in other elements or in a map() function for exemple. It is not a big issue because it does not cause any error in production build but in development environment the console is filled of warnings.

Warning: Invalid prop `className` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.
    in Fragment (created by Example)
    in Connect(App)
    in Provider

Exemple & Way to reproduce

const { React } = require('./path/to/css-ns')('Exemple');

class Example extends React.Component {
  render () {
   const items = [
       'item-1',
       'item-2',
       'item-3'
    ];

    return (
      <div className='outer'>
           {items.map(item => (

              // This is the Fragment that receive the className prop
              <React.Fragment key={item}> 
                  {item !== 'item-1' && <div className='separator'></div>}
                  <div className='item'>{item}</div>
              </React.Fragment>

            ))}
      </div>
    )
  }
}

Suggestion

Check if the element in createElement is a Symbol or more precisely if
_.toString() !== 'Symbol(react.fragment)'

return Object.create(opt.React, { // inherit everything from standard React
    createElement: { // ...except hijack createElement()
      value: function(_, props) {
        if (props && _.toString() !== 'Symbol(react.fragment)') props.className = ns(props.className);
        
        return opt.React.createElement.apply(opt.React, arguments);
      }
    }
  });

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.