Code Monkey home page Code Monkey logo

react-pacomo's Introduction

react-pacomo

React-pacomo transforms your component className props by prefixing them with a pacomo, or packageName-ComponentName- namespace. As a result, your component's CSS will be effectively locally scoped -- just like with CSS Modules, but without requiring a build step. React-pacomo also takes care of other common tasks like selecting classes and handling props.className.

React-pacomo's output is predicatable. This means that when you do want to override component CSS, you can. This makes it more suited for public libraries than inline style or CSS Modules.

For an example of react-pacomo in action, see the Unicorn Standard Starter Kit.

Installation

npm install react-pacomo --save

A simple example

Say you've got a NavItem component which renders some JSX:

class NavItem extends Component {
  render() {
    return <a
      href="/contacts"
      className={`NavItem ${this.props.active ? 'active' : ''}`}
    >
      <Icon className='icon' type={this.props.type} />
      <span className='label'>{this.props.label}</span>
    </a>
  }
}

While this works, it won't work if you ever import a library which defines other styles for .icon, .NavItem, etc. -- which is why you need to namespace your classes.

If your app is called unicorn, your namespaced component will look something like this:

class NavItem extends Component {
  render() {
    return <a
      href="/contacts"
      className={`unicorn-NavItem ${this.props.active ? 'unicorn-NavItem-active' : ''}`}
    >
      <Icon className='unicorn-NavItem-icon' type={this.props.type} />
      <span className='unicorn-NavItem-label'>{this.props.label}</span>
    </a>
  }
}

But while your styles are now safe from interference, repeatedly typing long strings isn't fun. So let's apply react-pacomo's higher order component. By using pacomoDecorator, the following component will emit exactly the same HTML and className props as the above snippet:

@pacomoDecorator
class NavItem extends Component {
  render() {
    return <a
      href="/contacts"
      className={{active: this.props.active}}
    >
      <Icon className='icon' type={this.props.type} />
      <span className='label'>{this.props.label}</span>
    </a>
  }
}

And just like that, you'll never have to manually write namespaces again!

Adding react-pacomo to your project

There are two methods for applying automatic namespacing to your components; a decorator function for component classes, and a transformer function used with stateless function components.

Neither of these methods are directly accessible. Instead, react-pacomo exports a withPackageName function which returns an object with decorator and transformer functions scoped to your package:

import { withPackageName } from 'react-pacomo'
const { decorator, transformer } = withPackageName('unicorn')

decorator(ComponentClass)

This function will return a new component which automatically namespaces className props within the wrapped class's render method.

Use it as a wrapper function, or as an ES7 decorator:

// As an ES7 decorator
@decorator
class MyComponent extends React.Component {
  ...
}

// As a wrapper function
var MyComponent = decorator(React.createClass({
  ...
}))

transformer(ComponentFunction)

This function will return a new stateless component function which automatically namespaces className props within the wrapped stateless component function.

const MyComponent = props => { ... }

const WrappedComponent = transformer(MyComponent)

Transformation Details

react-pacomo works by applying a transformation to the ReactElement which your component renders. The rules involved are simple:

Your root element receives the namespace itself as a class

The pacomo guidelines specify that your component's root element must have a CSS class following the format packageName-ComponentName.

For example:

let Wrapper = props => <div {...props}>{props.children}</div>
Wrapper = transformer(Wrapper)

Rendering <Wrapper /> will automatically apply an app-Wrapper CSS class to the rendered <div>.

className is run through the classnames package, then namespaced

This means that you use classnames objects within your className props!

For example:

@decorator
class NavItem extends Component {
  render() {
    return <a
      href={this.props.href}
      className={{active: this.props.active}}
    >
  }
}

If this.props.active is true, your element will receive the class app-NavItem-active. If it is false, it won't.

If your component's props contain a className, it is appended as-is

Since any className you to add your returned ReactElement will be automatically namespaced, you can't manually handle props.className. Instead, react-pacomo will automatically append it to your root element's className.

For example, if we used the NavItem component from above within a SideNav component, we could still pass a className to it:

@decorator
class SideNav extends Component {
  render() {
    return <div>
      <NavItem className='contacts' href='/contacts' active={true}>
        Contacts
      </NavItem>
      <NavItem className='projects' href='/projects'>
        Projects
      </NavItem>
    </div>
  }
}

The resulting HTML will look like this:

<div class='app-SideNav'>
  <a className='app-NavItem app-NavItem-active app-SideNav-contacts' href='/contacts'>
    Contacts
  </a>
  <a className='app-NavItem app-SideNav-projects' href='/projects'>
    Projects
  </a>
</div>

Child elements are recursively transformed

The upshot of this is that you can still use className on children. But keep in mind that huge component trees will take time to transform - whether you they need transforming or not.

While it is good practice to keep your components small and to the point anyway, it is especially important if you're using react-pacomo.

Elements found in props of custom components are recursively transformed

Not all elements you create will be appear under another element's children. For example, take this snippet:

<OneOrTwoColumnLayout
  left={<DocumentList className='contact-list' {...props} />}
  right={children}
/>

If we only scanned our rendered element's children, we'd miss the className on the <DocumentList> which we passed to left.

To take care of this, react-pacomo will also recursively transform elements on the props of custom React components. However, it will not look within arrays or objects.

Tips

You only need to call withPackageName once

As you'll likely be using the decorator or transformer functions across most of your components, you can make your life easier by exporting them from a file in your utils directory.

For an example, see utils/pacomo.js in the unicorn-standard-boilerplate project:

import { withPackageName } from 'react-pacomo'

export const {
  decorator: pacomoDecorator,
  transformer: pacomoTransformer,
} = withPackageName('app')

While decorator and transformer are easily understood in the context of an object returned by withPackageName, you'll probably want to rename them for your exports. My convention is to call them pacomoDecorator and pacomoTransformer.

Define displayName on your components

While react-pacomo can detect component names from their component's function or class name property, most minifiers mangle these names by default. This can cause inconsistent behavior between your development and production builds.

The solution to this is to make sure you specify a displayName property on your components. Your displayName is given priority over your function or class name, and it is good practice to define it anyway.

But if you insist that setting displayName is too much effort, make sure to tell your minifier to keep your function names intact. The method varies wildly based on your build system, but on the odd chance you're using UglifyJsPlugin with Wepback, the required configuration will look something like this:

new webpack.optimize.UglifyJsPlugin({
  compressor: {screw_ie8: true, keep_fnames: true, warnings: false},
  mangle: {screw_ie8: true, keep_fnames: true}
})

Use the LESS/SCSS parent selector (&)

While react-pacomo will prevent repetition in your JavaScript, it can't do anything about your CSS.

Luckily, since you're probably already using LESS or SCSS, the solution is simple: begin your selectors with &-.

In practice, this looks something like this:

.app-Paper {
  //...

  &-rounded {
    //...
  }

  &-inner {
    //...
  }

  &-rounded &-inner {
    //...
  }
}

Following the Pacomo CSS Guidelines

Namespacing your classes is the first step to taming your CSS, but it isn't the only one. The Pacomo System provides a number of other guidelines. Read them and use them.

Comparisons with other solutions

CSS Modules

Like react-pacomo, CSS Modules** automatically namespace your CSS classes. However, instead of runtime prefixing with React, it relies on your build system to do the prefixing.

Use CSS Modules instead when performance counts, you don't mind being a little more verbose, and you're not writing a library (where being able to monkey patch is important).

Pros

  • No runtime transformation (and thus possibly faster)
  • CSS class names can be minified (meaning less data over the wire)
  • Does not require a displayName when minifed

Cons

  • Depends on a build system
  • Does not handle props.className automatically
  • Does not append a root class automatically
  • Does not handle classnames objects
  • You don't know your class names ahead of time, meaning no monkey-patching

Inline Style

Inline Style takes a completely different approach, assigning your styles directly to the element's style property instead of using CSS.

While this may be useful when sharing code between react-native and react-dom, it has a number of drawbacks. Unless you're writing an exclusively native app, I recommend using react-pacomo or CSS Modules for your web app styles instead.

Pros

  • Styles can be re-used with react-native apps
  • Styles can be processed with JavaScript

Cons

  • Cannot re-use existing CSS code or tooling
  • Inline Style has the highest priority, preventing monkey patching
  • Media queries and pseudo selectors are complicated or impossible

FAQ

Isn't this just BEM?

No.

BEM goes further than react-pacomo by distinguishing between modifiers and elements.

For a BEM-based project, use something like react-bem.

Why should I use react-pacomo over BEM-based modules like react-bem?

Given most React components are very small and have limited scope, BEM is probably overkill. If you find your components are getting big enough that you think it might make sense to distinguish between elements and modifiers (like BEM), you probably should instead focus on re-factoring your app into smaller components.

Related Projects

react-pacomo's People

Contributors

andersonba avatar jamesknelson avatar pdc avatar roryokane 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  avatar  avatar  avatar  avatar  avatar

react-pacomo's Issues

BEM support?

This seems like a very well done project. But it would be nice if it did directly support some notion of BEM. Realistically, some components just have complexity from an element perspective. And I'd argue that fragmenting an entire design into tiny components isn't necessarily a win, especially if the pieces aren't truly atomic. One of the philosophical wins of BEM was that it identified that some elements (in their parlance) don't really stand alone.

Rather than recursively chaining things, the element concept keeps the namespace under blocks flat, which is nice because it makes refactoring a block more or less independent of where its elements are internally.

It doesn't appear to me that much is being saved by not having an element concept. You still have to assign a name to the pieces of each component.

React Warning needs to pass props

When using react 15.1.0 and passing props like

export class ReactBaseComponent extends React.Component { constructor(props) { super(props); } }

I get a warning:

Warning: DecoratedComponent(...): When calling super() in DecoratedComponent, make sure to pass up the same props that your component's constructor was passed.

CSS Module cons

Very interesting project with a lot of inspiring ideas. However, and with all due respect, the cons about CSS Modules strike me as mostly unfair.

  • Depends on a build system

Sure, but who is building any serious React app without one? In fact, you say in your article about inline styles, "But now that we all have access to wonderful tools like Webpack, this benefit isn’t limited to Inline Style anymore." If you have Webpack, you have CSS Modules. It's probably more accurate to say that it depends on Webpack, but if you're also encouraging Webpack use, I'm not sure who you're hoping to satisfy with this one.

  • Does not handle props.className automatically
  • Does not append a root class automatically
  • Does not handle classnames objects

Okay, but that's apples and oranges isn't it? You cannot really compare vanilla CSS Modules (really just the implementation of a spec; it's not itself claiming to be a full-fledged React library) to something like this that gets all of those features from a decorator. Pacomo spec CSS files would no more bring these features to your app if you merely imported them either. And nothing really prevents similar decorators from being written for CSS Modules, and in fact those do exist, e.g. react-css-modules. Comparing react-pacomo to those would seem like a better comparison, and hopefully good ideas would become infectious and improve everyone's respective decorator implementations.

  • You don't know your class names ahead of time, meaning no monkey-patching

This one has teeth, I think. I'm still trying to understand what the best practices are in the CSS Module world for overriding styles, but I imagine you could do a couple of things:

  • Offer an API on your component that takes inline styles and use them for overrides (since they will have higher specificity
  • Offer an API on your component that takes a CSS module and Object.assign it to the internal one.

I hope you will take my comments in a helpful light and consider revising or discussing more some of these things so users will have a fair comparison when deciding what to pick for their next project.

Bug with nested pacomized elements

For two nested elements using a pacomo decorator, example:

<Gutter>
<Paper>
content
</Paper>
</Gutter>

The transform will not be applied to the inner element.

Transformer not working for stateless functions

It seems that using a stateless function, as defined in React 0.14 allows for a syntax like this:

import React from 'react'
import {pacomoTransformer} from '../utils/pacomo.js'

const Input = ({onChangeHandler, placeHolderValue}) => {
  <div>
    <textarea
      className={`content`}
      onChange={onChangeHandler}
      placeHolder={placeHolder}
    />
  </div>
}

Input.displayName = 'App'

export default pacomoTransformer(Input);

However I get an error saying pacomo can't find a render function? Is this something that can be changed as I'd really like to use this syntax.

Error when using transformer on a pure component

The error is 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context.

Here's my module:

import React, { PropTypes } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import { pacomoTransformer } from '../../utils/pacomo';

import './index.less';

const Step1 = (props) => (
  <div>
    <h1 className="header">Sign in</h1>
    <span className="label">Sign in with:</span>
    <RaisedButton label="Google" onTouchTap={props.handleGoogleSignin} />
    <RaisedButton label="Facebook" />
  </div>
);

Step1.propTypes = {
  handleGoogleSignin: PropTypes.func,
};

export default pacomoTransformer(Step1);

only add global prefix, not displayName

Hi, this lib you made really looks like what I need for my app except that I only need to prefix classnames with a global name.
When you also add the displayName in the prefix it makes working on my .less files a pain, even with the "&" trick, because of the sometimes necessary nesting.
My objective is to be able to prefix all classNames with "foo-" and use "postcss-class-prefix" plugin to automatically prefix all my generated CSS.

Also, is there a way to make pacomo work on third party components, like react-bootstrap components ? because their css will get prefixed by postcss but not by pacomo :(

External classes

How can i assign bootstrap classes to my component.

I want to simple add bg-warning, but it prints app-ComponentName-bg-warning
and style does not works.

Is there a way to fix this?

Global CSS

I love the idea of this package. It is sweet and simple, but there is one crucial piece of functionality that is missing. It would be great if there was a way to specify that a class should not be autoprefixed. This way we could use css classes that are defined globally. E.g. utility classes such as clearfix, Bootstrap classes.

One way I could think of doing this is prepending a class with some token that marks it as global:

<Navbar className="*navbar *navbar-default mynav" />

Which is output as:

<div className="navbar navbar-default app-Component-mynav">
   ...
</div>

TypeError: Class constructor MyComponent cannot be invoked without 'new'

I am getting this error only when I try to render server-side for an isomorphic app. Everything works fine client-side. I believe this is because I am using the following Babel config on my server:

            "babel-preset-env",
            {
                "targets": {
                    "node": "current"
                }
            }

In other words - because I am targetting the latest node version, Babel is not transpiling 'extends' statements, it is using the native version of 'extends', so Pacomo fails when it tries to instance my component, because when Babel has transpiled a class extension it is possible to instance the class in a non-standards way. All I am doing to decorate my component is the normal:

@pacomo
class MyComponent extends React.Component {
    render() {
         // etc
    }
}

There is some further discussion of apparently the same issue over here:

https://github.com/gajus/react-css-modules/issues/196

I hope there is a fix for this, as I would really like to use Pacomo, but this is a major blocker for me.

className should only contain owner displayName

class Test extends Component {
  render() {
    return (
      <div>
        <CustomLayout>
           <div className="item"></div>
        </CustomLayout>
     </div>
    );
  }
}

The class name on the item div ends up being CustomLayout-Test-item when expecting Test-item

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.