Merge pull request #168 from stylesuxx/feature/postcss

Add basic support for PostCSS (provided by @stylesuxx)
This commit is contained in:
Chris
2015-11-23 08:33:32 +01:00
7 changed files with 215 additions and 1 deletions
+22
View File
@@ -11,6 +11,7 @@ Out of the box it comes with support for:
- Webpack
- ES2015 via Babel-Loader
- Different supported style languages (sass, scss, less, stylus)
- Style transformations via PostCSS
- Automatic code linting via esLint
- Ability to unit test components via Karma and Mocha/Chai
@@ -64,6 +65,27 @@ Stateless functional components where introduced in React v0.14. They have a muc
___Note___: You will still be able to set properties for stateless components!
## Adding PostCSS plugins
If you have enabled [PostCSS](https://github.com/postcss/postcss) at generation time, install your PostCSS plugins via npm and *require* it in **postcss** function in *cfg/base.js*.
Example for autoprefixer:
```bash
cd my-new-project
npm install autoprefixer
```
Require in *cfg/base.js*
```JavaScript
...
postcss: function () {
return [
require('autoprefixer')({
browsers: ['last 2 versions', 'ie >= 8']
})
];
}
...
```
## Usage
The following commands are available in your project:
```bash
+18
View File
@@ -46,11 +46,15 @@ module.exports = generator.Base.extend({
// Set needed global vars for yo
this.appName = props.appName;
this.style = props.style;
this.postcss = props.postcss
// Set needed keys into config
this.config.set('appName', this.appName);
this.config.set('appPath', this.appPath);
this.config.set('style', this.style);
this.config.set('postcss', this.postcss);
this.config.save();
done();
}.bind(this));
@@ -83,6 +87,15 @@ module.exports = generator.Base.extend({
}
}
// Add postcss module if enabled
let postcssConfig = utils.config.getChoiceByKey('postcss', 'postcss');
if(this.postcss && postcssConfig && postcssConfig.packages) {
for(let dependency of postcssConfig.packages) {
packageSettings.dependencies[dependency.name] = dependency.version;
}
}
this.fs.writeJSON(this.destinationPath('package.json'), packageSettings);
},
@@ -123,6 +136,11 @@ module.exports = generator.Base.extend({
},
install: function() {
if(this.postcss) {
let postcss = require('./postcss');
postcss.write(path.join(this.destinationRoot(), 'cfg/base.js'));
}
if(!this.options['skip-install']) {
this.installDependencies({ bower: false });
}
+39
View File
@@ -0,0 +1,39 @@
'use strict';
let fs = require('fs');
let esprima = require('esprima');
let walk = require('esprima-walk');
let escodegen = require('escodegen');
module.exports = {
write: function(path) {
let baseConfigPath = path;
let cssDialects = ['/\\.css$/', '/\\.sass/', '/\\.scss/', '/\\.less/', '/\\.styl/'];
let postcss = 'var postcss = { postcss: function() { return []; } }';
postcss = esprima.parse(postcss);
postcss = postcss.body[0].declarations[0].init.properties[0];
let data = fs.readFileSync(baseConfigPath, 'utf8');
let parsed = esprima.parse(data);
walk.walkAddParent(parsed, function(node) {
if(node.type === 'AssignmentExpression' && node.left.object.name === 'module') {
node.right.properties.push(postcss);
}
if(node.type === 'Property' && node.key.name === 'test') {
if(cssDialects.indexOf(node.value.raw) > -1) {
let current = node.parent.properties[1].value.value;
let position = current.split('!', 2).join('!').length;
current = [current.slice(0, position), '!postcss-loader', current.slice(position)].join('');
node.parent.properties[1].value.value = current;
}
}
});
let options = { format: { indent: { style: ' ' } } };
let code = escodegen.generate(parsed, options);
fs.writeFileSync(baseConfigPath, code, 'utf8');
}
}
+6
View File
@@ -14,5 +14,11 @@ module.exports = [
message: 'Which styles language you want to use?',
choices: utils.config.getChoices('style'),
default: utils.config.getDefaultChoice('style')
},
{
type: 'confirm',
name: 'postcss',
message: 'Enable postcss?',
default: false
}
];
+4 -1
View File
@@ -43,7 +43,10 @@
"react-webpack-template": "^1.0.0",
"underscore.string": "^3.2.2",
"yeoman-generator": "^0.21.0",
"yeoman-welcome": "^1.0.1"
"yeoman-welcome": "^1.0.1",
"esprima": "^2.7.0" ,
"esprima-walk": "^0.1.0" ,
"escodegen": "^1.7.0"
},
"devDependencies": {
"chai": "^3.2.0",
+114
View File
@@ -35,6 +35,10 @@ describe('react-webpack:app', () => {
it('should use "css" as default style language', () => {
expect(generator.config.get('style')).to.equal('css');
});
it('should not enable "PostCSS" by default', () => {
expect(generator.config.get('postcss')).to.equal(false);
});
});
describe('#createFiles', () => {
@@ -95,3 +99,113 @@ describe('react-webpack:app', () => {
});
});
});
describe('react-webpack:app non-default-prompts', () => {
let defaultPrompts = require('../../../generators/app/prompts.js');
let prompts = {};
for(let p of defaultPrompts) {
prompts[p.name] = p.default;
}
prompts.postcss = true;
let generator;
let generatorBase = path.join(__dirname, '../../../generators/app');
before((done) => {
helpers.run(generatorBase)
.inTmpDir()
.withOptions({
'skip-welcome-message': true,
'skip-install': true
})
.withPrompts(prompts)
.on('ready', (instance) => {
generator = instance;
})
.on('end', done);
});
describe('#config', () => {
it('should use "css" as default style language', () => {
expect(generator.config.get('style')).to.equal('css');
});
});
describe('#createFiles', () => {
it('should generate dot files', () => {
assert.file([
'.editorconfig',
'.eslintrc',
'.gitignore',
'.npmignore',
'.yo-rc.json'
]);
});
it('should generate project configuration files', () => {
assert.file([
'LICENSE',
'package.json'
]);
});
it('should generate the webpack configuration', () => {
assert.file([
'cfg/base.js',
'cfg/dev.js',
'cfg/dist.js',
'cfg/test.js',
'server.js',
'webpack.config.js'
]);
});
it('should insert the postcss loader into the style pipes', () => {
assert.fileContent('cfg/base.js', 'loader: \'style!css!postcss-loader\'');
//assert.fileContent('cfg/base.js', 'loader: \'style-loader!css-loader!postcss-loader\'');
assert.fileContent('cfg/base.js', 'loader: \'style-loader!css-loader!postcss-loader!sass-loader?outputStyle=expanded&indentedSyntax\'');
assert.fileContent('cfg/base.js', 'loader: \'style-loader!css-loader!postcss-loader!sass-loader?outputStyle=expanded\'');
assert.fileContent('cfg/base.js', 'loader: \'style-loader!css-loader!postcss-loader!less-loader\'');
assert.fileContent('cfg/base.js', 'loader: \'style-loader!css-loader!postcss-loader!stylus-loader\'');
});
it('should append the postcss function to the base config', () => {
assert.fileContent('cfg/base.js', ',\n postcss: function () {\n return [];\n }');
});
it('should generate required source files', () => {
assert.file([
'src/actions/README.md',
'src/components/Main.js',
'src/components/run.js',
'src/favicon.ico',
'src/images/yeoman.png',
'src/index.html',
'src/sources/README.md',
'src/stores/README.md',
'src/styles/App.css'
]);
});
it('should generate test configuration and basic tests', () => {
assert.file([
'karma.conf.js',
'test/components/MainTest.js',
'test/helpers/shallowRenderHelper.js',
'test/loadtests.js'
]);
});
});
});
+12
View File
@@ -39,6 +39,18 @@
}
]
},
"postcss": {
"options": [
{
"name": "postcss",
"value": "postcss",
"packages": [
{ "name": "postcss", "version": "^5.0.11" },
{ "name": "postcss-loader", "version": "^0.8.0" }
]
}
]
},
"style": {
"options": [
{