Skip to content

Commit

Permalink
Merge pull request #4 from Ticketfly-UI/towards-v0.0.1
Browse files Browse the repository at this point in the history
@compose inside CSS.
  • Loading branch information
spencer516 authored Jan 12, 2017
2 parents 06e7e91 + 402d836 commit 13cd9ba
Show file tree
Hide file tree
Showing 20 changed files with 242 additions and 31 deletions.
47 changes: 37 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default Component.extend({
```

```css
/* app/styles/app.css-compose */
/* app/styles/app.css */
@composer {
@a-list-component {
composes: 'my-list', 'background-g3', 'font-size-big';
Expand Down Expand Up @@ -82,15 +82,42 @@ export default Component.extend({
{{/each}}
```

## The Pieces
### Dynamically Generated Class Names

1. A Broccoli Plugin for finding the config files and processing in order from addon up to app.
- Should create an in memory file for converting a class name into a list of class names
2. The `classNamesMacro` for converting `classNameBindings` that have truthy/falsey values.
3. Babel Plugin to transpile component files looking for string definitions beginning with `@`
- Imports file from step 1
4. HTMLBars Plugin to transpile template files looking for `class="@"`
- Imports file from step 2
In the event that you need to dynamically generate class names, you can directly import the `classify`
utility to convert a key into a list of class names. For example:

```css
/* app/styles/app.css */
@composer {
@myComponent-red {
composes: 'red';
}

@myComponent-blue {
composes: 'blue';
}

}
```

```js
import Component from 'ember-component';
import computed from 'ember-computed';
import get from 'ember-metal/get';
import { classify } from 'ember-css-composer';

export default Component.extend({
classNameBindings: ['colorClass'],
color: 'red',

colorClass: computed('color', {
get() {
return classify(`myComponent-${get(this, 'color')}`);
}
})
});
```

## Installation

Expand All @@ -113,4 +140,4 @@ export default Component.extend({

For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).

![Amadeus](http://24.media.tumblr.com/tumblr_mcbz1pZFKN1qllovxo1_500.gif)
![Amadeus](http://24.media.tumblr.com/tumblr_mcbz1pZFKN1qllovxo1_500.gif)
10 changes: 10 additions & 0 deletions addon/[css-classes-json.js]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
This is a placeholder file. At build time, the application will generate a
real file that exports a POJO that maps from lookup names to output names.

Something like:
*/

export default {
'component-a': ['class-1', 'class-2', 'class-3']
};
5 changes: 5 additions & 0 deletions addon/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import classify from './classify';

export {
classify
};
14 changes: 12 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* jshint node: true */
'use strict';

const LOOKUP_OUTPUT = 'css-classes-json.js';
const CSStoJSON = require('./lib/css-plugin/index');
const CleanCSSCompose = require('./lib/css-plugin/clean-composer');
const BabelPlugin = require('./lib/babel-plugin/index');
const BabelTranspiler = require('broccoli-babel-transpiler');
const MergeTrees = require('broccoli-merge-trees');
Expand All @@ -16,6 +18,14 @@ module.exports = {
return;
}

registry.add('css', {
name: 'ember-css-composer-clean-css',
toTree: function(inputTree) {
return new CleanCSSCompose(inputTree);
}
});


registry.add('js', {
name: 'ember-css-composer-babel',
toTree: function(inputTree) {
Expand All @@ -39,11 +49,11 @@ module.exports = {
var addonTree = this._super.treeForAddon.call(this, tree);

let cssComposeTree = new Funnel(this.app.trees.app, {
include: ['**/*.css-compose']
include: ['**/*.css']
});

let configFileTree = CSStoJSON(cssComposeTree, {
outputFile: 'modules/ember-css-composer/css-classes-json.js'
outputFile: `modules/ember-css-composer/${LOOKUP_OUTPUT}`
});

return new MergeTrees([addonTree, configFileTree]);
Expand Down
33 changes: 33 additions & 0 deletions lib/css-plugin/clean-composer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const postcss = require('postcss');
const Filter = require('broccoli-persistent-filter');

CSSStripCompose.prototype = Object.create(Filter.prototype);
CSSStripCompose.prototype.constructor = CSSStripCompose;
CSSStripCompose.prototype.extensions = ['css'];
CSSStripCompose.prototype.targetExtension = 'css';

function CSSStripCompose(inputTree, options = {}) {
if (!(this instanceof CSSStripCompose)) {
return new CSSStripCompose(inputTree, options);
}

Filter.call(this, inputTree, options);
}

CSSStripCompose.prototype.processString = function(content) {
return removeComposesDecls(content);
};

function removeComposesDecls(content) {
let ast = postcss.parse(content);

ast.walkAtRules((node) => {
if (node.name === 'composer') {
node.remove();
}
});

return ast.toResult().css;
}

module.exports = CSSStripCompose;
17 changes: 17 additions & 0 deletions node-test/fixtures/css-clean-plugin/test-1/input/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@composer {
@test-1 {
composes: 'class-a', 'class-b', 'class-c';
}

@test-2 {
composes: 'class-d','class-e';
}
}

.class-a {
background: red;
}

.class-b {
background: green;
}
7 changes: 7 additions & 0 deletions node-test/fixtures/css-clean-plugin/test-1/output/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.class-a {
background: red;
}

.class-b {
background: green;
}
13 changes: 13 additions & 0 deletions node-test/fixtures/css-clean-plugin/test-2/input/app-2.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.class-e {
font-size: 12px;
}

@composer {
@test-2 {
composes: 'class-d','class-e';
}
}

.other-styles {
color: blue;
}
13 changes: 13 additions & 0 deletions node-test/fixtures/css-clean-plugin/test-2/input/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@composer {
@test-1 {
composes: 'class-a', 'class-b', 'class-c';
}
}

.class-a {
background: red;
}

.class-b {
background: green;
}
7 changes: 7 additions & 0 deletions node-test/fixtures/css-clean-plugin/test-2/output/app-2.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.class-e {
font-size: 12px;
}

.other-styles {
color: blue;
}
7 changes: 7 additions & 0 deletions node-test/fixtures/css-clean-plugin/test-2/output/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.class-a {
background: red;
}

.class-b {
background: green;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
@test-2 {
composes: 'class-d','class-e';
}
}
}
28 changes: 28 additions & 0 deletions node-test/helpers/clean-compose-css-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const broccoli = require('broccoli');
const plugin = require('../../lib/css-plugin/clean-composer');
const fs = require('fs');
const path = require('path');
const filesPath = 'node-test/fixtures/css-clean-plugin';
const outputFile = 'some/file/tmp.js';

module.exports = {
transform(treePath) {
let tree = plugin(`${filesPath}/${treePath}/input`, { outputFile });
let builder = new broccoli.Builder(tree);

return builder.build().then(() => {
return filesMap(builder.outputPath);
});
},
getExpectedResult(path) {
let fullPath = `${filesPath}/${path}/output`;
return filesMap(fullPath);
}
}

function filesMap(folder) {
return fs.readdirSync(folder).reduce((hash, file) => {
hash[file] = fs.readFileSync(path.join(folder, file), 'utf8');
return hash;
}, {});
}
33 changes: 33 additions & 0 deletions node-test/lib/css-plugin/clean-composer-nodetest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { transform, getExpectedResult } = require('../../helpers/clean-compose-css-file');
const should = require('should');

describe('css plugin', function() {
it('works with a single file', function() {
// Make sure it strips out any @compose declarations.
return transform('test-1').then((result) => {
let expected = getExpectedResult('test-1');
assertKeys(expected, result);
});
});

it('works with multiple files', function() {
// Make sure it strips out any @compose declarations.
return transform('test-2').then((result) => {
let expected = getExpectedResult('test-2');
assertKeys(expected, result);
});
});
});

function assertKeys(expectedHash, resultHash) {
let expectedKeys = Object.keys(expectedHash);
let resultKeys = Object.keys(resultHash);

// Assert the same keys
expectedKeys.should.deepEqual(resultKeys);

// Assert each of the values are the same
expectedKeys.forEach((key) => {
expectedHash[key].should.equal(resultHash[key]);
});
}
2 changes: 1 addition & 1 deletion node-test/lib/css-plugin/index-nodetest.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ describe('css plugin', function() {
result.should.equal(expected);
});
});
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"postcss": "^5.2.8"
},
"devDependencies": {
"babel-core": "^5.8.38",
"babel-preset-es2015": "^6.18.0",
"babelify": "^7.3.0",
"broccoli-asset-rev": "^2.4.5",
Expand Down
17 changes: 17 additions & 0 deletions tests/dummy/app/styles/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@composer {
@testing-basic {
composes: 'tb-1', 'tb-2';
}

@testing-basic__open {
composes: 'tb-3', 'tb-4';
}

@testing-basic__closed {
composes: 'tb-5';
}

@testing-basic__inner {
composes: 'tb-6', 'tb-7', 'tb-8';
}
}
17 changes: 0 additions & 17 deletions tests/dummy/app/styles/config.css-compose

This file was deleted.

0 comments on commit 13cd9ba

Please sign in to comment.