Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Guillaume Tournier committed Sep 18, 2019
0 parents commit 314abfb
Show file tree
Hide file tree
Showing 16 changed files with 4,464 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "standard",
"parserOptions": { "ecmaVersion": 5 }
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
npm-debug.log*
.npm
node_modules
.nyc_output
coverage
11 changes: 11 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
examples
test
coverage
.babelrc
.eslint*
.idea
.npmignore
.travis.yml
.history
.nyc_output
console.d.ts
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
language: node_js
node_js:
- '8'
script:
- npm run check:src
after_success:
- npm run coveralls
branches:
only:
- master
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 Guillaume Tournier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
133 changes: 133 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# babel-plugin-transform-jsx-abem

[![Build Status](https://travis-ci.org/gtournie/babel-plugin-transform-jsx-abem.svg?branch=master)](https://travis-ci.org/gtournie/babel-plugin-transform-jsx-abem)
[![Coverage Status](https://coveralls.io/repos/github/gtournie/babel-plugin-transform-jsx-abem/badge.svg?branch=master)](https://coveralls.io/github/gtournie/babel-plugin-transform-jsx-abem?branch=master)
[![Standard - JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
[![npm downloads](https://img.shields.io/npm/dm/babel-plugin-transform-jsx-abem.svg?style=flat-square)](https://www.npmjs.com/package/babel-plugin-transform-jsx-abem)

[Babel](https://babeljs.io/) plugin for [ABEM](https://css-tricks.com/abem-useful-adaptation-bem/) class names generation in [JSX](https://facebook.github.io/react/docs/introducing-jsx.html).

## Install

Require it as any other babel plugin and add the `abem` package to your `dependencies` (so Webpack/Rollbar/etc. can use it in the frontend).

```bash
$ npm install babel-plugin-transform-jsx-abem --save-dev
$ npm install abem --save
```

```js
{
plugins: ['transform-jsx-abem']
}
```

## Usage

Add ABEM properties in your tags and the plugin will generate the `className` for you.

The plugin will try to resolve the className during the compilation (`className="block"`) and fallback to runtime if not possible (`className={_abem("block")}` - helper will be included automatically)

**If the tag already have a className, then it'll be skipped.**

### Properties

| name | type |
| :---- | :--------------------------------------- |
| block | string |
| elem | string |
| mods | string \| array \| object \| expression* |
| mix | string \| array \| object \| expression* |

\* must return a string, an array or an object

### Scopes

The `block` property creates a scope and should only be used at the top-level of the JSX tag. It will be automatically generated if it's inside a class or a named function (if the class/function name is prefixed by a A, O or M, then it'll add the prefix 'a-', 'o-', or 'm-')

### Examples

**Input**
```js
<div block="m-main" mix="panel" mods={{ warning: true }}>
<div elem="header" mods="header">Title</div>
<div elem="body">Text</div>
</div>
```
**Output**
```js
<div className="m-main -warning panel">
<div className="m-main__header -header">Title</div>
<div className="m-main__body">Text</div>
</div>
```

**Input**
```js
const Message = ({ title, text}) => {
return <div>
<div elem="header">{ title }</div>
<div elem="body">{ text }</div>
</div>
}
```
**Output**
```js
const Message = ({ title, text }) => {
return <div className="message">
<div className="message__header">{title}</div>
<div className="message__body">{text}</div>
</div>;
};
```

**Input**
```js
class OMessage extends Component {
...
render() {
...
return <div mods={this.getMods()}>
<div elem="header">{ title }</div>
<div elem="body">{ text }</div>
</div>
}
}
```
**Output**
```js
class OMessage extends Component {
...
render() {
...
return <div className={_abem("o-message", null, this.getMods())}>
<div className="o-message__header">{title}</div>
<div className="o-message__body">{text}</div>
</div>;
}
}
```

## Options

### properties

Set custom naming properties. Default values:
```js
properties: {
block: 'block',
element: 'elem',
modifiers: 'mods',
mixin: 'mix'
}
```

### separators

Set custom abem separators. Default values:
```js
separators: {
element: '__',
modifier: '-',
}
```
5 changes: 5 additions & 0 deletions __tests__/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"mocha": true
}
}
53 changes: 53 additions & 0 deletions __tests__/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict'

const c = require('./common')
const assert = require('assert')

describe('base', function () {
it('should generate the className', function () {
assert.strictEqual(c.getCode('<div />;'), '<div />;')
assert.strictEqual(c.getCode('<div block="block" />;'), '<div className="block" />;')
assert.strictEqual(c.getCode('<div block="block" className="name" />;'), '<div block="block" className="name" />;')
assert.strictEqual(c.getCode('<div block="block" elem="element" />;'), '<div className="block__element" />;')
assert.strictEqual(
c.getCode('<div block="block" elem="element" mods="mod" />;'),
'<div className="block__element -mod" />;'
)
assert.strictEqual(
c.getBody('<div block="block" elem="element" mods={ cbmod() } />;'),
'<div className={_abem("block", "element", cbmod())} />;'
)
assert.strictEqual(
c.getCode('<div block="block" elem="element" mods={{ foo: true, bar: false }} />;'),
'<div className="block__element -foo" />;'
)
assert.strictEqual(
c.getCode('<div block="block" elem="element" {...{ mods: "mod", foo: "bar" }} />;'),
'<div {...{ foo: "bar" }} className="block__element -mod" />;'
)
assert.strictEqual(
c.getCode('<div block="block" elem="element" {...{ mods: "mod", className: "bar" }} />;'),
'<div block="block" elem="element" {...{ mods: "mod", className: "bar" }} />;'
)
assert.strictEqual(
c.getBody('<div block="block" elem="element" {...{ mods: cbmod(), foo: "bar" }} />;'),
'<div {...{ foo: "bar" }} className={_abem("block", "element", cbmod())} />;'
)
assert.strictEqual(
c.getBody('<div block="block" elem="element" {...{ mods: cbmod(), foo: "bar" }} {...{ mods: cbmod2() }} />;'),
'<div {...{ foo: "bar" }} {...{}} className={_abem("block", "element", cbmod2())} />;'
)
assert.strictEqual(
c.getCode('<div block="block" elem="element" mix="mix" />;'),
'<div className="block__element mix" />;'
)
assert.strictEqual(
c.getCode('<div block="block" mix={ ["mix1", "mix2"] } />;'),
'<div className="block mix1 mix2" />;'
)
assert.strictEqual(
c.getBody('<div block="block" mix={ ["mix1", cbmod()] } />;'),
'<div className={_abem("block", null, null, ["mix1", cbmod()])} />;'
)
})
})
17 changes: 17 additions & 0 deletions __tests__/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict'

const path = require('path')
const babel = require('babel-core')

const transform = (code, opts) =>
babel
.transform(code, {
plugins: [[path.join(__dirname, '/..'), opts]]
})
.code.replace('\n\n', '\n')

module.exports = {
getCode: (source, opts) => transform(source, opts),
getBody: (source, opts) => transform(source, opts).split('\n\n')[1],
getHeader: opts => transform('<div block="block" />', opts).split('\n\n')[0]
}
13 changes: 13 additions & 0 deletions __tests__/exceptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'

const c = require('./common')
const assert = require('assert')

describe('exceptions', function () {
it('should throw an exception', function () {
assert.throws(() => c.getCode('<div block={ block } />'), SyntaxError)
assert.throws(() => c.getCode('<div elem="toto" />'), SyntaxError)
assert.throws(() => c.getCode('<div block="block"><div mods="mod" /></div>'), SyntaxError)
assert.throws(() => c.getCode('<div block="block"><div block="element"></div></div>'), SyntaxError)
})
})
34 changes: 34 additions & 0 deletions __tests__/nested.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict'

const c = require('./common')
const assert = require('assert')

describe('nested', function () {
it('should generate the className in nested blocks', function () {
assert.strictEqual(
c.getBody(
`<div block="message">
<h1 elem="title">{ title }</h1>
<div elem="message" mods={{ error }}>{ message }</div>
</div>;`
),
`<div className="message">
<h1 className="message__title">{title}</h1>
<div className={_abem("message", "message", { error })}>{message}</div>
</div>;`
)

assert.strictEqual(
c.getCode(
`<div block="time">
<span></span>
{ time }
</div>;`
),
`<div className="time">
<span></span>
{time}
</div>;`
)
})
})
30 changes: 30 additions & 0 deletions __tests__/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict'

const c = require('./common')
const assert = require('assert')

describe('options', function () {
it('should generate the className with custom separators', function () {
assert.strictEqual(
c.getCode('<div block="block" elem="element" mods="mod" />;', {
separators: { element: '___' }
}),
'<div className="block___element -mod" />;'
)
assert.strictEqual(
c.getBody('<div block="block" elem="element" mods={ mod() } />;', {
separators: { element: '___' }
}),
'<div className={_abem("block", "element", mod())} />;'
)
})

it('should target custom property names', function () {
assert.strictEqual(
c.getCode('<div block="block" element="element" modifiers="mod" />;', {
properties: { element: 'element', modifiers: 'modifiers' }
}),
'<div className="block__element -mod" />;'
)
})
})

0 comments on commit 314abfb

Please sign in to comment.