jigsass-tools-selectors
Version:
Mixins to generate maintainable and scalable css selectors with zero bloat
388 lines (315 loc) • 14 kB
Markdown
# JigSass Tools Selectors
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url]
> Create predictable, manageable and composable CSS with zero bloat and scalability in mind
## Installation
Using npm:
```sh
npm i -S jigsass-tools-selectors
```
## Usage
```scss
'path/to/jigsass-tools-selectors';
```
Managing CSS at scale is hard, even very hard. Code bases easily grow out of control,
and dead code elimination is an onerous task.
JigSass borrows much of its architecture from [inuit](https://github.com/inuitcss) and ideas
on CSS structure expressed elsewhere by Harry Roberts, and as such is, at its core, based on
two main pillars:
- **Objects:** Abstract design pattern, such as the
[media object](http://www.stubbornella.org/content/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/) meant for reuse in many unrelated contexts. Can be extended, with modifer classes, but directly
mutating an object will have rippling effects. Should consist of minimal styling, to later
be expanded with modifiers, utiliies and within components themselves.
- **Components:** An implementation-specific piece of UI. Should not be reused outside the context
of the specific component. Will often consist mostly of including objects and utils within the
ruleset.
- **Utilities:** Low-level, single-purpose immutable units of style, often only declaring a
single declaration. These are not bound to any spcific ui or design abstruction, and can
be used to change, adjust or augment
JigSass Selectors is an attempt to provide tools for better tackling these tasks,
and assisting with defining and generating clean and easy to maintain CSS based on
responsive-enabled, reusable object and utility classes.
In order to keep a CSS footprint to a minimum, not even a single line of the defined CSS
is generated unless it is explicitly included for use.
##### Objects
CSS rulsets of objects and object modifiers are generated based on settings in its
configuration map (passed as a parameter to the `jigsass-object` mixin), where classes
must be explicitly enabled in the associated configuration map (passed
to the mixin as the `$config` param) _BEFORE_ the object is
``d.
By default, CSS rulsets of objects and object modifiers are not automatically
generated generated by including the `jigsass-object` mixin. For any output to
be generated, classes must be explicitly enabled in the Object's associated
configuration map (passed to the mixin as the `$config` param) _BEFORE_ the
object is ``d.
The structure of configuration maps is:
```scss
// _objects.foo.scss
$foo-map: (
no-breakpoint: (
no-modifier: true, // Enables generation of the `.o-foo`
// class outside of any media query.
bar: true, // Enables generation of the `.o-foo--bar`
// modifier class outside of any media query.
),
from-<bp-name>: (
no-modifier: true, // Enables generation of the `.o-foo--from-<bp-name>`
// class inside a min-width media query
// defined ins `$jigsass-breakpoints.length`.
bar: true, // Enables generation of the `.o-foo--bar--from-<bp-name>`
// class inside a min-width media query
// defined ins `$jigsass-breakpoints.length`.
),
until-<bp-name>: (
no-modifier: true, // Enables generation of the `.o-foo--until-<bp-name>`
// class inside a max-width media query
// defined ins `$jigsass-breakpoints.length`.
bar: true, // Enables generation of the `.o-foo--bar--until-<bp-name>`
// class inside a max-width media query
// defined ins `$jigsass-breakpoints.length`.
),
when-<bp-name>: (
no-modifier: true, // Enables generation of the `.o-foo--when-<bp-name>`
// class inside a misc media query
// defined ins `$jigsass-breakpoints.features`.
bar: true, // Enables generation of the `.o-foo--bar--when-<bp-name>`
// class inside a misc media query
// defined ins `$jigsass-breakpoints.features`.
),
from-<bp-name>-until-<bp-name>: (...);
from-<bp-name>-when-<bp-name>: (...);
until-<bp-name>-when-<bp-name>: (...);
from-<bp-name>-until-<bp-name>-when-<bp-name>: (...);
);
```
##### Utilities
Instead of using configuration maps or variables, which eventually become
difficult to maintain and keep track of for utility classes, JigSass offers the
`jigsass-define-util` and `jigsass-util` mixins, used to define utils and dynamically
CSS output for them without creating duplication or changing the cascade.
**Let's take a look at a simplified example:**
```scss
/* _object.media.scss */
$media-conf: (
from-large: (
no-modifier: true,
middle: true,
),
);
$media-item-conf: (
from-large: (
bottom: true,
),
);
-object(o-media, $media-conf) {
jigsass-classname {
display: flex;
}
jigsass-classname($modifer: middle) {
align-items: center;
}
jigsass-classname($modifer: reverse) {
flex-direction: row-reverse;
}
}
-object(o-media__item, $media-item-conf) {
jigsass-classname($modifier: bottom) {
align-self: flex-end;
}
}
/* _component.foo.scss */
.c-foo {
// Styles unique for `.c-foo`:
mix-blend-mode: multiply;
}
.c-foo__fig {
jigsass-util(u-ml, $from: large, $modifier: 1);
jigsass-util(u-mr, $from: large, $modifier: 1);
// Styles unique for `.c-foo__fig`:
filter: blur(3px);
}
// `.foo__fig--bottom` will not actually be generated,
// as it has no styles of its own.
.c-foo__fig--bottom {
jigsass-util(u-ml, $from: large, $modifier: 1);
}
// `.foo__body` will not actually be generated,
// as it has no styles of its own.
.c-foo__body {
jigsass-util(u-mr, $from: large, $modifier: 1);
}
/* _util.margin.scss */
jigsass-define-util(u-mr) {
$modifier: $jigsass-util-modifier or 0;
margin-right: $jigsass-util-modifier * 12px;
}
jigsass-define-util(u-ml) {
$modifier: $jigsass-util-modifier or 0;
margin-left: $jigsass-util-modifier * 12px;
}
// style.scss
'object.media'; // `.o-media` will be generated here
'component.foo'; // `.c-foo` and `.c-foo__fig` will be generated here.
'util.margin'; // `.u-ml` and `.u-mr` will be generated here.
```
Will generate:
```css
/* style.css */
(min-width: 65em) {
.o-media--from-large {
display: flex;
}
.o-media--middle--from-large {
align-items: center;
}
}
(min-width: 65em) {
.o-media__item--bottom--from-large {
align-self: bottom;
}
}
.c-foo {
mix-blend-mode: multiply;
}
.c-foo__fig {
filter: blur(3px);
}
(min-width: 65em) {
.u-mr--1--from-large {
margin-right: 12px;
}
}
(min-width: 65em) {
.u-ml--1--from-large {
margin-left: 12px;
}
}
```
Notice how only the styles we actually used ended up in our CSS? How they were generated
where they were defined, and only once?
When managing the generation of css utility classes through config files, it eventually
becomes damn near impossible to keep track of where each class is used, and when it is safe to
remove, often leaving us with codebases that are larger than they need to be.
In contrast, generating the styles by simply including them where they are being used is a lot
easier to grasp and manage, while still keeping our CSS stringent and modular.
Now, with the above CSS and following html:
```scss
<article class="[ o-media--from-large o-media--middle--from-large ] c-foo">
<figure class="c-foo__fig u-mr--1--from-large">
<!-- img1 here -->
</figure>
<div class="foo__body">
<!-- content here -->
</div>
<figure class="o-media__item--bottom--from-large c-foo__fig u-ml--1--from-large">
<!-- img2 here -->
</figure>
</articel>
```
We can get something like this:
```scss
┌─────────────╥╥─────────────────────────────╥╥─────────────┐
│############ ║║CONTENT HEAD ║║ │
│############ ║║ ║║ │
│############ ║║ minus saepe sequi velit a, ║║ ############│
│## IMG1 ## ║║ sit veniam quia quibusdam ║║ ############│
│############ ║║ odio itaque non! Dolores ║║ ############│
│############ ║║ deserunt atque repudiandae ║║ ## IMG2 ##│
│############ ║║ asperiores rerum velit ║║ ############│
│ ║║ magnam deleniti deleniti ║║ ############│
│ ║║ sed aspernatur commodi? ║║ ############│
└─────────────╨╨─────────────────────────────╨╨─────────────┘
```
The `jigsass-object` and `jigsass-util` mixins will generate selectors according to the following
logic:
```scss
.class-name[--modifier][-[-from-{breakpoint-name}][-until-{breakpoint-name}][-misc-{breakpoint-name}]]
```
Please check the full [documentation](https://TxHawks.github.io/jigsass-tools-selectors) for
a better understanding of how to use `jigsass-tools-selectors`.
## Development
It is a best practice for JigSass modules to *not* automatically generate css on ``, but
rather have to user explicitly enable the generation of specific styles from the module.
Contributions in the form of pull-requests, issues, bug reports, etc. are welcome.
Please feel free to fork, hack or modify JigSass Tools Selectors in any way you see fit.
#### Writing documentation
Good documentation is crucial for scalability and maintainability. When contributing,
please do make sure that all Sass functionality (functions, mixins,
variables and placeholder selectors) is well documented.
Documentation is auto-generated using [SassDoc](http://sassdoc.com/)
#### Running tests
`gulp lint` will, well, lint the contents scss files in the `scss` directory.
`gulp test` with run module's test using Mocha and Sassaby.
`gulp tdd` will watch both the Sass files and the test specs for changes, and will
run tests automatically upon them.
#### Writing tests
JigSass Tools Selectors tests are written using [Sassaby](https://github.com/ryanbahniuk/sassaby)
and Mocha. Spec files are located in the `test` directory.
Mocha allows us to place a call to `before()` in the root of any test file and it
will be run once, before all the other tests in every `test_*.js` file.
We can also `require()` files and assign them to the global object to make them
available to all `test_*.js` files.
jigsass-tools-selectors uses a file called `helper.js` can be used to set up mocha
globals requires and `before()`.
In addition to Sassaby's testing functions, jigsass-tools-selectors makes a few Sass
functions available to the test suite, for use inside Sassaby tests:
<dl>
<dt>jig-var-equals($value, $var) -> {boolean}<dt>
<dd>
Check if a variable equals a value.<br />
<strong>$value</strong> {*}: A value to compare the value of $var to.<br />
<strong>$var</strong> {*}: The variable to test<br />
</dd>
<dt>jig-var-type-is($type, $var) -> {boolean}<dt>
<dd>
Check if a variable is of a certain type.<br />
<strong>$type</strong> {string}: A type to compare with the type of $var.<br />
<strong>$var</strong> {*}: The variable to test<br />
</dd>
<dt>jig-map-key-equals($value, $map, $keys...) -> {boolean}<dt>
<dd>
Check if a map's key is assigned a cerain value.<br />
<strong>$value</strong> {*}: A value to compare the value of a key in $map with.<br />
<strong>$map</strong> {map}: The map to test.<br />
<strong>$keys... </strong> {arglist}: A recursive chain of keys.<br />
</dd>
<dt>jig-map-key-type-is($type, $map, keys...) -> {boolean}<dt>
<dd>
Check if a map's key is of a certain type<br />
<strong>$type</strong> {string}: A type to compare with the type of $var.<br />
<strong>$map</strong> {map}: The map to test.<br />
<strong>$keys... </strong> {arglist}: A recursive chain of keys.<br />
</dd>
</dl>
## File structure
```bash
┬ ./
│
├─┬ scss/
│ └─ index.scss # The module's importable file.
│
├── sassdoc/ # Generated documentation
│ # of the module's sass features
│
└─┬─ test/
│
├─┬ helpers/
│ │
│ ├── importer.scss # Used for easilty importing tested scss files
│ │
│ └── _test_helpers.scss # JigSass's assertion helpers,
│ # for use inside Sassaby tests.
│
├── helper.js # Used for defining global `before()`
│ # functions and requiring modules.
│
└── test_jigsass-tools-selectors # Specs. Mocha will automatically
# run all javascript files located
# in the `test` directory.
```
**License:** MIT
[npm-image]: https://badge.fury.io/js/jigsass-tools-selectors.svg
[npm-url]: https://npmjs.org/package/jigsass-tools-selectors
[travis-image]: https://travis-ci.org/TxHawks/jigsass-tools-selectors.svg?branch=master
[travis-url]: https://travis-ci.org/TxHawks/jigsass-tools-selectors
[daviddm-image]: https://david-dm.org/TxHawks/jigsass-tools-selectors.svg?theme=shields.io
[daviddm-url]: https://david-dm.org/TxHawks/jigsass-tools-selectors