├── .npmignore ├── .gitignore ├── .babelrc ├── package.json ├── src └── pacomo.js ├── test.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-pacomo", 3 | "version": "0.5.3", 4 | "description": "Provide structure to your component's CSS.", 5 | "main": "lib/pacomo.js", 6 | "scripts": { 7 | "compile": "babel --presets es2015,stage-0 -d lib/ src/", 8 | "prepublish": "npm run compile", 9 | "test": "npm run compile && mocha --compilers js:babel-core/register" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/jamesknelson/react-pacomo.git" 14 | }, 15 | "keywords": [ 16 | "react", 17 | "component", 18 | "decorator", 19 | "decorators", 20 | "classes", 21 | "props", 22 | "pacomo" 23 | ], 24 | "author": "James K Nelson ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/jamesknelson/react-pacomo/issues" 28 | }, 29 | "homepage": "https://github.com/jamesknelson/react-pacomo#readme", 30 | "peerDependencies": { 31 | "react": "^0.14.0 || ^15.0.0" 32 | }, 33 | "dependencies": { 34 | "babel-polyfill": "^6.1.18", 35 | "classnames": "^2.1.3" 36 | }, 37 | "devDependencies": { 38 | "babel-cli": "^6.1.18", 39 | "babel-core": "^6.1.18", 40 | "babel-preset-es2015": "^6.1.18", 41 | "babel-preset-react": "^6.1.18", 42 | "babel-preset-stage-0": "^6.1.18", 43 | "mocha": "^2.3.0", 44 | "react": "^0.14.0", 45 | "react-addons-test-utils": "^0.14.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/pacomo.js: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import { isValidElement, cloneElement, Children, PropTypes } from 'react' 3 | 4 | 5 | export function prefixedClassNames(prefix, ...args) { 6 | return ( 7 | classNames(...args) 8 | .split(/\s+/) 9 | .filter(name => name !== "") 10 | .map(name => `${prefix}-${name}`) 11 | .join(' ') 12 | ) 13 | } 14 | 15 | 16 | const ignoredFunctionStatics = 17 | Object.getOwnPropertyNames(function(){}).concat(['displayName', 'propTypes', 'name']) 18 | 19 | 20 | function hoistFunctionStatics(source, target) { 21 | Object 22 | .getOwnPropertyNames(source) 23 | .forEach(key => { 24 | if(ignoredFunctionStatics.indexOf(key) < 0) { 25 | target[key] = source[key] 26 | } 27 | }) 28 | return target 29 | } 30 | 31 | 32 | // Applies `fn` to all passed in props which are react elements, with 33 | // *updated* elements being returned as a new `props`-like object. 34 | // 35 | // Optionally, `childrenOnly` can be set to true to ignore non-children 36 | // props. This is useful for DOM elements which are guaranteed to have 37 | // no element children anywhere except `props.children`. 38 | function transformElementProps(props, fn, childrenOnly) { 39 | const changes = {} 40 | 41 | if (typeof props.children === 'object') { 42 | const children = Children.toArray(props.children) 43 | const transformedChildren = children.map(fn) 44 | 45 | if (transformedChildren.some((transformed, i) => transformed != children[i])) { 46 | changes.children = transformedChildren 47 | } 48 | } 49 | 50 | if (!childrenOnly) { 51 | for (let key of Object.keys(props)) { 52 | if (key == 'children') continue 53 | const value = props[key] 54 | if (isValidElement(value)) { 55 | const transformed = fn(value) 56 | if (transformed !== value) { 57 | changes[key] = transformed 58 | } 59 | } 60 | } 61 | } 62 | 63 | return changes 64 | } 65 | 66 | 67 | function cloneElementWithSkip(element) { 68 | return cloneElement(element, {'data-pacomoSkip': true}) 69 | } 70 | 71 | 72 | // Add the `__pacomoSkip` prop to any elements in the passed in `props` object 73 | function skipPropElements(props) { 74 | return Object.assign({}, props, transformElementProps(props, cloneElementWithSkip)) 75 | } 76 | 77 | 78 | export function transformWithPrefix(prefix) { 79 | const childTransform = element => transform(element) 80 | 81 | // Prefix all `className` props on the passed in ReactElement object, its 82 | // children, and elements on `props`. 83 | // 84 | // Optionally prefix with a `rootClass` and postfix with `suffixClass`. 85 | function transform(element, rootClass, suffixClasses='') { 86 | if (typeof element !== 'object' || element.props['data-pacomoSkip']) return element 87 | 88 | const changes = transformElementProps( 89 | element.props, 90 | childTransform, 91 | typeof element.type !== 'function' 92 | ) 93 | 94 | if (element.props.className) { 95 | changes.className = `${rootClass || ''} ${prefixedClassNames(prefix, element.props.className)} ${suffixClasses}`.trim() 96 | } 97 | else if (rootClass) { 98 | changes.className = `${rootClass} ${suffixClasses}`.trim() 99 | } 100 | 101 | return ( 102 | Object.keys(changes).length === 0 103 | ? element 104 | : cloneElement(element, changes, changes.children || element.props.children) 105 | ) 106 | } 107 | 108 | return transform 109 | } 110 | 111 | 112 | export function withPackageName(packageName) { 113 | return { 114 | // Transform a stateless function component 115 | transformer(componentFunction) { 116 | const componentName = componentFunction.displayName || componentFunction.name 117 | const prefix = `${packageName}-${componentName}` 118 | const transform = transformWithPrefix(prefix) 119 | 120 | const transformedComponent = (props, ...args) => 121 | transform( 122 | componentFunction(skipPropElements(props), ...args), 123 | prefix, 124 | props.className 125 | ) 126 | 127 | // Add `className` propType, if none exists 128 | transformedComponent.propTypes = { className: PropTypes.string, ...componentFunction.propTypes } 129 | 130 | return hoistFunctionStatics(componentFunction, transformedComponent) 131 | }, 132 | 133 | 134 | // Transform a React.Component class 135 | decorator(componentClass) { 136 | const componentName = componentClass.displayName || componentClass.name 137 | const prefix = `${packageName}-${componentName}` 138 | const transform = transformWithPrefix(prefix) 139 | 140 | const DecoratedComponent = class DecoratedComponent extends componentClass { 141 | render() { 142 | const rawProps = this.props 143 | this.props = skipPropElements(this.props) 144 | const transformed = transform(super.render(), prefix, this.props.className) 145 | this.props = rawProps 146 | return transformed 147 | } 148 | } 149 | 150 | // Add `className` propType, if none exists 151 | DecoratedComponent.propTypes = { className: PropTypes.string, ...componentClass.propTypes } 152 | 153 | return DecoratedComponent 154 | }, 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import {prefixedClassNames, withPackageName, transformWithPrefix} from './lib/pacomo' 3 | import React, {Component, PropTypes} from 'react' 4 | import TestUtils from 'react-addons-test-utils' 5 | import assert from 'assert' 6 | 7 | 8 | const testTransform = transformWithPrefix('test') 9 | const { transformer, decorator } = withPackageName('prefix') 10 | 11 | 12 | /* 13 | * Util 14 | */ 15 | 16 | function shallowRenderElement(element) { 17 | const shallowRenderer = TestUtils.createRenderer() 18 | shallowRenderer.render(element) 19 | return shallowRenderer.getRenderOutput() 20 | } 21 | 22 | function shallowRenderComponent(Component, props) { 23 | return shallowRenderElement() 24 | } 25 | 26 | 27 | /* 28 | * Fixtures 29 | */ 30 | 31 | class FullComponent extends Component { 32 | render() { 33 | return
34 | } 35 | } 36 | FullComponent.propTypes = { 37 | className: PropTypes.string.isRequired, 38 | active: PropTypes.bool, 39 | } 40 | FullComponent.defaultProps = { 41 | className: "defaultClassName", 42 | active: true, 43 | } 44 | 45 | 46 | class BareComponent extends Component { 47 | render() { 48 | return
49 | } 50 | } 51 | 52 | 53 | class ContainerComponent extends Component { 54 | render() { 55 | return
{this.props.children}
56 | } 57 | } 58 | 59 | 60 | function FullStatelessComponent(props) { 61 | return
62 | } 63 | FullStatelessComponent.propTypes = { 64 | className: PropTypes.string.isRequired, 65 | active: PropTypes.bool, 66 | } 67 | FullStatelessComponent.defaultProps = { 68 | className: "defaultClassName", 69 | active: true, 70 | } 71 | 72 | 73 | function BareStatelessComponent(props) { 74 | return
75 | } 76 | 77 | 78 | function ContainerStatelessComponent({children}) { 79 | return
{children}
80 | } 81 | 82 | 83 | function nestedElement({active}) { 84 | return ( 85 | 105 | ) 106 | } 107 | 108 | 109 | /* 110 | * Tests 111 | */ 112 | 113 | 114 | describe('decorator', () => { 115 | it("should pass through propTypes from the original class", () => { 116 | const decoratedClass = decorator(FullComponent) 117 | 118 | assert.equal( 119 | decoratedClass.propTypes.className, 120 | PropTypes.string.isRequired, 121 | "className propType from origina class are passed through" 122 | ) 123 | 124 | assert.equal( 125 | decoratedClass.propTypes.active, 126 | PropTypes.bool, 127 | "other propTypes from original class is passed through" 128 | ) 129 | }) 130 | 131 | it("should add a className propType if one doesn't already exist", () => { 132 | const decoratedClass = decorator(BareComponent) 133 | 134 | assert.equal( 135 | decoratedClass.propTypes.className, 136 | PropTypes.string, 137 | "className propType exists" 138 | ) 139 | }) 140 | }) 141 | 142 | 143 | describe('transformer', () => { 144 | it("should pass through propTypes from the original function", () => { 145 | const transformedFunction = transformer(FullStatelessComponent) 146 | 147 | assert.equal( 148 | transformedFunction.propTypes.className, 149 | PropTypes.string.isRequired, 150 | "className propType from origina class are passed through" 151 | ) 152 | 153 | assert.equal( 154 | transformedFunction.propTypes.active, 155 | PropTypes.bool, 156 | "other propTypes from original class is passed through" 157 | ) 158 | }) 159 | 160 | it("should add a className propType if one doesn't already exist", () => { 161 | const transformedFunction = transformer(BareStatelessComponent) 162 | 163 | assert.equal( 164 | transformedFunction.propTypes.className, 165 | PropTypes.string, 166 | "className propType exists" 167 | ) 168 | }) 169 | }) 170 | 171 | 172 | describe('#render', () => { 173 | it("respects the values of passed in `props`", () => { 174 | const rendered = shallowRenderComponent( 175 | decorator(FullComponent), 176 | {className: 'passedIn', active: false} 177 | ) 178 | 179 | assert.equal( 180 | rendered.props.className, 181 | 'prefix-FullComponent passedIn' 182 | ) 183 | }) 184 | 185 | it("repects the value of the original class's `defaultProps`", () => { 186 | const rendered = shallowRenderComponent(decorator(FullComponent)) 187 | 188 | assert.equal( 189 | rendered.props.className, 190 | 'prefix-FullComponent prefix-FullComponent-active defaultClassName' 191 | ) 192 | }) 193 | 194 | it("works correctly when `super.render` does not return a `className`", () => { 195 | const rendered = shallowRenderComponent(decorator(BareComponent)) 196 | 197 | assert.equal( 198 | rendered.props.className, 199 | 'prefix-BareComponent' 200 | ) 201 | }) 202 | 203 | it("adds `__pacomoSkip` to the any `children`", () => { 204 | const rendered = shallowRenderComponent( 205 | decorator(ContainerComponent), 206 | { children:
} 207 | ) 208 | 209 | assert.equal( 210 | rendered.props.children[0].props.__pacomoSkip, 211 | true 212 | ) 213 | }) 214 | }) 215 | 216 | 217 | describe('stateless render', () => { 218 | it("respects the values of passed in `props`", () => { 219 | const rendered = shallowRenderComponent( 220 | transformer(FullStatelessComponent), 221 | {className: 'passedIn', active: false} 222 | ) 223 | 224 | assert.equal( 225 | rendered.props.className, 226 | 'prefix-FullStatelessComponent passedIn' 227 | ) 228 | }) 229 | 230 | it("repects the value of the original class's `defaultProps`", () => { 231 | const rendered = shallowRenderComponent(transformer(FullStatelessComponent)) 232 | 233 | assert.equal( 234 | rendered.props.className, 235 | 'prefix-FullStatelessComponent prefix-FullStatelessComponent-active defaultClassName' 236 | ) 237 | }) 238 | 239 | it("works correctly when `super.render` does not return a `className`", () => { 240 | const rendered = shallowRenderComponent(transformer(BareStatelessComponent)) 241 | 242 | assert.equal( 243 | rendered.props.className, 244 | 'prefix-BareStatelessComponent' 245 | ) 246 | }) 247 | 248 | it("adds `__pacomoSkip` to the any `children`", () => { 249 | const rendered = shallowRenderComponent( 250 | transformer(ContainerStatelessComponent), 251 | { children:
} 252 | ) 253 | 254 | assert.equal( 255 | rendered.props.children[0].props.__pacomoSkip, 256 | true 257 | ) 258 | }) 259 | }) 260 | 261 | 262 | describe('prefixedClassNames', () => { 263 | it("accepts an object, producing the correct result", () => { 264 | assert.equal( 265 | prefixedClassNames('test-prefix', { 266 | active: true, 267 | inactive: false, 268 | }), 269 | 'test-prefix-active', 270 | "`classNames` produces the correct string when an object is passed in" 271 | ) 272 | }) 273 | 274 | it("accepts a string, producing the correct result", () => { 275 | assert.equal( 276 | prefixedClassNames('test-prefix', 'test1', 'test2'), 277 | 'test-prefix-test1 test-prefix-test2', 278 | "`classNames` produces the correct string when a string is passed in" 279 | ) 280 | }) 281 | }) 282 | 283 | 284 | describe('transformWithPrefix', () => { 285 | it("does not transform elements with the __pacomoSkip prop", () => { 286 | const originalElement =
287 | const transformedElement = testTransform(originalElement) 288 | 289 | assert.equal( 290 | originalElement, 291 | transformedElement 292 | ) 293 | }) 294 | 295 | it("transforms the passed in element", () => { 296 | const transformed = testTransform(nestedElement({active: 'link1'})) 297 | 298 | assert.equal( 299 | transformed.props.className, 300 | 'test-nav' 301 | ) 302 | }) 303 | 304 | it("transforms passed in child elements", () => { 305 | const transformed = testTransform(nestedElement({active: 'link1'})) 306 | 307 | assert.equal( 308 | transformed.props.children[0].props.className, 309 | 'test-link1 test-active' 310 | ) 311 | assert.equal( 312 | transformed.props.children[1].props.className, 313 | 'test-link2' 314 | ) 315 | }) 316 | 317 | it("transforms element props on custom components", () => { 318 | const transformed = testTransform(nestedElement({active: 'link1'})) 319 | 320 | assert.equal( 321 | transformed.props.children[0].props.focusable.props.className, 322 | 'test-Link' 323 | ) 324 | }) 325 | 326 | it("does not transform elements with nothing to transform", () => { 327 | const originalElement = nestedElement({active: 'link1'}) 328 | const transformedElement = testTransform(originalElement) 329 | 330 | assert.equal( 331 | transformedElement.props.children[0].props.children[0], 332 | originalElement.props.children[0].props.children[0] 333 | ) 334 | assert.equal( 335 | transformedElement.props.children[0].props.children[0].className, 336 | originalElement.props.children[0].props.children[0].className 337 | ) 338 | }) 339 | 340 | it("does not transform arrays of element props on custom components", () => { 341 | const originalElement = nestedElement({active: 'link1'}) 342 | const transformedElement = testTransform(originalElement) 343 | 344 | assert.equal( 345 | transformedElement.props.untransformed, 346 | originalElement.props.untransformed 347 | ) 348 | }) 349 | 350 | it("does not transform element props on DOM elements", () => { 351 | const originalElement = nestedElement({active: 'link1'}) 352 | const transformedElement = testTransform(originalElement) 353 | 354 | assert.equal( 355 | transformedElement.props.children[0].props.untransformed, 356 | originalElement.props.children[0].props.untransformed 357 | ) 358 | }) 359 | }) 360 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-pacomo 2 | 3 | React-pacomo transforms your component `className` props by prefixing them with a [pacomo](http://unicornstandard.com/packages/pacomo.html), or `packageName-ComponentName-` namespace. As a result, **your component's CSS will be effectively locally scoped** -- just like with [CSS Modules](https://github.com/css-modules/css-modules), but *without requiring a build step*. React-pacomo also takes care of other common tasks like selecting classes and handling `props.className`. 4 | 5 | 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. 6 | 7 | For an example of react-pacomo in action, see the [Unicorn Standard Starter Kit](https://github.com/unicorn-standard/starter-kit). 8 | 9 | ## Installation 10 | 11 | ``` 12 | npm install react-pacomo --save 13 | ``` 14 | 15 | ## A simple example 16 | 17 | Say you've got a `NavItem` component which renders some JSX: 18 | 19 | ```jsx 20 | class NavItem extends Component { 21 | render() { 22 | return 26 | 27 | {this.props.label} 28 | 29 | } 30 | } 31 | ``` 32 | 33 | 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. 34 | 35 | If your app is called `unicorn`, your namespaced component will look something like this: 36 | 37 | ```jsx 38 | class NavItem extends Component { 39 | render() { 40 | return 44 | 45 | {this.props.label} 46 | 47 | } 48 | } 49 | ``` 50 | 51 | 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](http://jamesknelson.com/structuring-react-applications-higher-order-components/). By using `pacomoDecorator`, the following component will emit *exactly* the same HTML and `className` props as the above snippet: 52 | 53 | ```jsx 54 | @pacomoDecorator 55 | class NavItem extends Component { 56 | render() { 57 | return 61 | 62 | {this.props.label} 63 | 64 | } 65 | } 66 | ``` 67 | 68 | And just like that, you'll never have to manually write namespaces again! 69 | 70 | ## Adding react-pacomo to your project 71 | 72 | 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. 73 | 74 | 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: 75 | 76 | ```javascript 77 | import { withPackageName } from 'react-pacomo' 78 | const { decorator, transformer } = withPackageName('unicorn') 79 | ``` 80 | 81 | ### `decorator(ComponentClass)` 82 | 83 | This function will return a new component which automatically namespaces `className` props within the wrapped class's `render` method. 84 | 85 | Use it as a wrapper function, or as an ES7 decorator: 86 | 87 | ```javascript 88 | // As an ES7 decorator 89 | @decorator 90 | class MyComponent extends React.Component { 91 | ... 92 | } 93 | 94 | // As a wrapper function 95 | var MyComponent = decorator(React.createClass({ 96 | ... 97 | })) 98 | ``` 99 | 100 | ### `transformer(ComponentFunction)` 101 | 102 | This function will return a new stateless component function which automatically namespaces `className` props within the wrapped stateless component function. 103 | 104 | ```javascript 105 | const MyComponent = props => { ... } 106 | 107 | const WrappedComponent = transformer(MyComponent) 108 | ``` 109 | 110 | ## Transformation Details 111 | 112 | **react-pacomo** works by applying a transformation to the `ReactElement` which your component renders. The rules involved are simple: 113 | 114 | ### Your root element receives the namespace itself as a class 115 | 116 | The pacomo guidelines specify that your component's root element *must* have a CSS class following the format `packageName-ComponentName`. 117 | 118 | For example: 119 | 120 | ```jsx 121 | let Wrapper = props =>
{props.children}
122 | Wrapper = transformer(Wrapper) 123 | ``` 124 | 125 | Rendering `` will automatically apply an `app-Wrapper` CSS class to the rendered `
`. 126 | 127 | ### `className` is run through the classnames package, then namespaced 128 | 129 | This means that you use [classnames](https://www.npmjs.com/package/classnames) objects within your `className` props! 130 | 131 | For example: 132 | 133 | ```jsx 134 | @decorator 135 | class NavItem extends Component { 136 | render() { 137 | return 141 | } 142 | } 143 | ``` 144 | 145 | If `this.props.active` is true, your element will receive the class `app-NavItem-active`. If it is false, it won't. 146 | 147 | ### If your component's `props` contain a `className`, it is appended as-is 148 | 149 | 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`. 150 | 151 | For example, if we used the NavItem component from above within a SideNav component, we could still pass a `className` to it: 152 | 153 | ```jsx 154 | @decorator 155 | class SideNav extends Component { 156 | render() { 157 | return
158 | 159 | Contacts 160 | 161 | 162 | Projects 163 | 164 |
165 | } 166 | } 167 | ``` 168 | 169 | The resulting HTML will look like this: 170 | 171 | ```html 172 |
180 | ``` 181 | 182 | ### Child elements are recursively transformed 183 | 184 | 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. 185 | 186 | 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. 187 | 188 | ### Elements found in props of custom components are recursively transformed 189 | 190 | Not all elements you create will be appear under another element's children. For example, take this snippet: 191 | 192 | ```jsx 193 | } 195 | right={children} 196 | /> 197 | ``` 198 | 199 | If we only scanned our rendered element's `children`, we'd miss the `className` on the `` which we passed to `left`. 200 | 201 | 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. 202 | 203 | ## Tips 204 | 205 | ### You only need to call `withPackageName` once 206 | 207 | 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. 208 | 209 | For an example, see `utils/pacomo.js` in the [unicorn-standard-boilerplate](http://unicornstandard.com/packages/boilerplate.html) project: 210 | 211 | ```javascript 212 | import { withPackageName } from 'react-pacomo' 213 | 214 | export const { 215 | decorator: pacomoDecorator, 216 | transformer: pacomoTransformer, 217 | } = withPackageName('app') 218 | ``` 219 | 220 | 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`. 221 | 222 | ### Define `displayName` on your components 223 | 224 | 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. 225 | 226 | 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*. 227 | 228 | 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: 229 | 230 | ```javascript 231 | new webpack.optimize.UglifyJsPlugin({ 232 | compressor: {screw_ie8: true, keep_fnames: true, warnings: false}, 233 | mangle: {screw_ie8: true, keep_fnames: true} 234 | }) 235 | ``` 236 | 237 | ### Use the LESS/SCSS parent selector (`&`) 238 | 239 | While **react-pacomo** will prevent repetition in your *JavaScript*, it can't do anything about your *CSS*. 240 | 241 | Luckily, since you're probably already using LESS or SCSS, the solution is simple: begin your selectors with `&-`. 242 | 243 | In practice, this looks something like this: 244 | 245 | ```less 246 | .app-Paper { 247 | //... 248 | 249 | &-rounded { 250 | //... 251 | } 252 | 253 | &-inner { 254 | //... 255 | } 256 | 257 | &-rounded &-inner { 258 | //... 259 | } 260 | } 261 | ``` 262 | 263 | ### Following the Pacomo CSS Guidelines 264 | 265 | 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](http://unicornstandard.com/packages/pacomo.html). Read them and use them. 266 | 267 | ## Comparisons with other solutions 268 | 269 | ### CSS Modules 270 | 271 | Like react-pacomo, [CSS Modules](https://github.com/css-modules/css-modules)** automatically namespace your CSS classes. However, instead of runtime prefixing with React, it relies on your build system to do the prefixing. 272 | 273 | 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). 274 | 275 | #### Pros 276 | 277 | - No runtime transformation (and thus possibly faster) 278 | - CSS class names can be minified (meaning less data over the wire) 279 | - Does not require a `displayName` when minifed 280 | 281 | #### Cons 282 | 283 | - Depends on a build system 284 | - Does not handle `props.className` automatically 285 | - Does not append a root class automatically 286 | - Does not handle [classnames](npmjs.com/package/classnames) objects 287 | - You don't know your class names ahead of time, meaning no monkey-patching 288 | 289 | ### Inline Style 290 | 291 | Inline Style takes a completely different approach, assigning your styles directly to the element's `style` property instead of using CSS. 292 | 293 | While this *may* be useful when sharing code between react-native and react-dom, [it has a number of drawbacks](http://jamesknelson.com/why-you-shouldnt-style-with-javascript/). Unless you're writing an exclusively native app, I recommend using react-pacomo or CSS Modules for your web app styles instead. 294 | 295 | #### Pros 296 | 297 | - Styles can be re-used with react-native apps 298 | - Styles can be processed with JavaScript 299 | 300 | #### Cons 301 | 302 | - **Cannot re-use existing CSS code or tooling** 303 | - Inline Style has the highest priority, preventing monkey patching 304 | - Media queries and pseudo selectors are complicated or impossible 305 | 306 | ## FAQ 307 | 308 | ### Isn't this just BEM? 309 | 310 | No. 311 | 312 | BEM goes further than react-pacomo by distinguishing between *modifiers* and *elements*. 313 | 314 | For a BEM-based project, use something like [react-bem](https://github.com/cuzzo/react-bem). 315 | 316 | ### Why should I use react-pacomo over BEM-based modules like react-bem? 317 | 318 | 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. 319 | 320 | ## Related Projects 321 | 322 | - [unicorn-standard-boilerplate](http://unicornstandard.com/packages/boilerplate.html) is a great example of how to use pacomo, as well as a number of other technologies from the Unicorn Standard project. 323 | - [The Pacomo Specification](http://unicornstandard.com/packages/pacomo.html) contains the guidelines which this project follows. 324 | --------------------------------------------------------------------------------