From 312c88b0bce5418c667d44143f815f52351ec9ae Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Wed, 18 Nov 2015 03:11:04 +0100 Subject: [PATCH 01/11] Added dependencies for postcss --- generators/app/index.js | 13 +++++++++++++ generators/app/prompts.js | 6 ++++++ utils/configopts.json | 12 ++++++++++++ 3 files changed, 31 insertions(+) diff --git a/generators/app/index.js b/generators/app/index.js index 0da3e55..eefa018 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -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); }, diff --git a/generators/app/prompts.js b/generators/app/prompts.js index 5c447ea..77a90fe 100644 --- a/generators/app/prompts.js +++ b/generators/app/prompts.js @@ -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 } ]; diff --git a/utils/configopts.json b/utils/configopts.json index 2a157ba..8012f56 100644 --- a/utils/configopts.json +++ b/utils/configopts.json @@ -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": [ { From f70a1eed8fd11a2ac6af2e9e462cc30b6109d82d Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Wed, 18 Nov 2015 04:33:58 +0100 Subject: [PATCH 02/11] Parse base config and add postcss related stuff --- generators/app/index.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/generators/app/index.js b/generators/app/index.js index eefa018..350e66a 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -134,6 +134,21 @@ module.exports = generator.Base.extend({ }, install: function() { + // Update base cfg if postcss is enabled + if(this.postcss) { + var baseConfigPath = path.join(this.destinationRoot(), 'cfg/base.js'); + var data = fs.readFileSync(baseConfigPath, 'utf8'); + var result = data.replace(/(style-loader.*)'/g, + function(match, $1) { + return $1 + '!postcss-loader\''; + } + ); + result = result.replace(/style!css/, 'style-loader!css-loader!postcss-loader'); + result = result.replace(/\s\s\}\n};/, ' },\n postcss: function () {\n return [\n\n ];\n }\n};'); + + fs.writeFileSync(baseConfigPath, result, 'utf8'); + } + if(!this.options['skip-install']) { this.installDependencies({ bower: false }); } From 9f427d9332dc8a76f2c7e938ae698019580ef4ab Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Wed, 18 Nov 2015 18:46:39 +0100 Subject: [PATCH 03/11] Refactored regex in favor of traversing via AST --- generators/app/index.js | 56 ++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/generators/app/index.js b/generators/app/index.js index 350e66a..fbb98a7 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -25,6 +25,49 @@ module.exports = generator.Base.extend({ this.sourceRoot(baseRootPath); this.config.save(); + + this.postcssConfig = function() { + let esprima = require('esprima'); + let walk = require('esprima-walk'); + let escodegen = require('escodegen'); + + let baseConfigPath = path.join(this.destinationRoot(), 'cfg/base.js'); + + let css = '/\\.css$/'; + let cssDialects = ['/\\.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; + current += '!postcss-loader'; + + node.parent.properties[1].value.value = current; + } + + if(node.value.raw === css) { + let current = 'style-loader!css-loader!postcss-loader'; + + node.parent.properties[1].value.value = current; + } + } + }); + + let options = { format: { indent: { style: ' ' } } }; + let code = escodegen.generate(parsed, options); + fs.writeFileSync(baseConfigPath, code, 'utf8'); + } }, initializing: function() { @@ -134,19 +177,8 @@ module.exports = generator.Base.extend({ }, install: function() { - // Update base cfg if postcss is enabled if(this.postcss) { - var baseConfigPath = path.join(this.destinationRoot(), 'cfg/base.js'); - var data = fs.readFileSync(baseConfigPath, 'utf8'); - var result = data.replace(/(style-loader.*)'/g, - function(match, $1) { - return $1 + '!postcss-loader\''; - } - ); - result = result.replace(/style!css/, 'style-loader!css-loader!postcss-loader'); - result = result.replace(/\s\s\}\n};/, ' },\n postcss: function () {\n return [\n\n ];\n }\n};'); - - fs.writeFileSync(baseConfigPath, result, 'utf8'); + this.postcssConfig(); } if(!this.options['skip-install']) { From ac6eb05171e212d73db3f35f65270f5fb4ea4a8f Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Wed, 18 Nov 2015 18:47:05 +0100 Subject: [PATCH 04/11] Added dependencies for reading, traversing and converting AST --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 19c31d9..9c1dd0e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,10 @@ "react-webpack-template": "^0.3.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", From 255d7b94d8f373d0fe80f7c5ab0a8327fdd5e31e Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Sat, 21 Nov 2015 15:59:39 +0100 Subject: [PATCH 05/11] Refactored postcss functionality in it's own file - #163 --- generators/app/index.js | 46 ++------------------------------------ generators/app/postcss.js | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 44 deletions(-) create mode 100644 generators/app/postcss.js diff --git a/generators/app/index.js b/generators/app/index.js index fbb98a7..c6abcf2 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -25,49 +25,6 @@ module.exports = generator.Base.extend({ this.sourceRoot(baseRootPath); this.config.save(); - - this.postcssConfig = function() { - let esprima = require('esprima'); - let walk = require('esprima-walk'); - let escodegen = require('escodegen'); - - let baseConfigPath = path.join(this.destinationRoot(), 'cfg/base.js'); - - let css = '/\\.css$/'; - let cssDialects = ['/\\.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; - current += '!postcss-loader'; - - node.parent.properties[1].value.value = current; - } - - if(node.value.raw === css) { - let current = 'style-loader!css-loader!postcss-loader'; - - node.parent.properties[1].value.value = current; - } - } - }); - - let options = { format: { indent: { style: ' ' } } }; - let code = escodegen.generate(parsed, options); - fs.writeFileSync(baseConfigPath, code, 'utf8'); - } }, initializing: function() { @@ -178,7 +135,8 @@ module.exports = generator.Base.extend({ install: function() { if(this.postcss) { - this.postcssConfig(); + let postcss = require('./postcss'); + postcss.write(path.join(this.destinationRoot(), 'cfg/base.js')); } if(!this.options['skip-install']) { diff --git a/generators/app/postcss.js b/generators/app/postcss.js new file mode 100644 index 0000000..b56ed73 --- /dev/null +++ b/generators/app/postcss.js @@ -0,0 +1,47 @@ +'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 css = '/\\.css$/'; + let cssDialects = ['/\\.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; + current += '!postcss-loader'; + + node.parent.properties[1].value.value = current; + } + + if(node.value.raw === css) { + let current = 'style-loader!css-loader!postcss-loader'; + + node.parent.properties[1].value.value = current; + } + } + }); + + let options = { format: { indent: { style: ' ' } } }; + let code = escodegen.generate(parsed, options); + fs.writeFileSync(baseConfigPath, code, 'utf8'); + + } +} From 3a10311c4793789be2f38b64f28d7cf95db92977 Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Sat, 21 Nov 2015 16:06:14 +0100 Subject: [PATCH 06/11] css is a dilakt itself - got rid of one condition --- generators/app/postcss.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/generators/app/postcss.js b/generators/app/postcss.js index b56ed73..e83f531 100644 --- a/generators/app/postcss.js +++ b/generators/app/postcss.js @@ -7,9 +7,7 @@ let escodegen = require('escodegen'); module.exports = { write: function(path) { let baseConfigPath = path; - - let css = '/\\.css$/'; - let cssDialects = ['/\\.sass/', '/\\.scss/', '/\\.less/', '/\\.styl/']; + let cssDialects = ['/\\.css$/', '/\\.sass/', '/\\.scss/', '/\\.less/', '/\\.styl/']; let postcss = 'var postcss = { postcss: function() { return []; } }'; postcss = esprima.parse(postcss); @@ -30,18 +28,11 @@ module.exports = { node.parent.properties[1].value.value = current; } - - if(node.value.raw === css) { - let current = 'style-loader!css-loader!postcss-loader'; - - node.parent.properties[1].value.value = current; - } } }); let options = { format: { indent: { style: ' ' } } }; let code = escodegen.generate(parsed, options); fs.writeFileSync(baseConfigPath, code, 'utf8'); - } } From 629ce71db5037664a17be10c67b6d6fa3649b845 Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Sat, 21 Nov 2015 16:30:54 +0100 Subject: [PATCH 07/11] Added some information about the usage of the postcss functionality - #163 --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index e57f452..e403c84 100644 --- a/README.md +++ b/README.md @@ -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 From 98ffadd612f1b511a286c4d56b0f1526c4403193 Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Sat, 21 Nov 2015 16:49:03 +0100 Subject: [PATCH 08/11] Test: Do not enable PostCSS by default - #163. --- test/generators/app/indexTest.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/generators/app/indexTest.js b/test/generators/app/indexTest.js index c8aa927..2547dc5 100644 --- a/test/generators/app/indexTest.js +++ b/test/generators/app/indexTest.js @@ -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', () => { From 0e3b55485559fa0b6c7b518cd005df74279849f2 Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Sun, 22 Nov 2015 03:31:05 +0100 Subject: [PATCH 09/11] PostCSS loader has to be the third item in the pipe --- generators/app/postcss.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generators/app/postcss.js b/generators/app/postcss.js index e83f531..9bf2377 100644 --- a/generators/app/postcss.js +++ b/generators/app/postcss.js @@ -24,7 +24,8 @@ module.exports = { if(node.type === 'Property' && node.key.name === 'test') { if(cssDialects.indexOf(node.value.raw) > -1) { let current = node.parent.properties[1].value.value; - current += '!postcss-loader'; + 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; } From 4596462f295193815b189d3a50ecea17a14639ab Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Sun, 22 Nov 2015 03:45:27 +0100 Subject: [PATCH 10/11] Test: Check that the base config is rewritten if PostCSS is enabled - #163 --- test/generators/app/indexTest.js | 110 +++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/test/generators/app/indexTest.js b/test/generators/app/indexTest.js index 2547dc5..923bbf9 100644 --- a/test/generators/app/indexTest.js +++ b/test/generators/app/indexTest.js @@ -100,3 +100,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 append the postcss loader to 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' + ]); + }); + }); +}); From b214930f8e1ce4da8f5a5cb7163627b58ba5430e Mon Sep 17 00:00:00 2001 From: stylesuxx Date: Sun, 22 Nov 2015 03:48:47 +0100 Subject: [PATCH 11/11] Updated wording --- test/generators/app/indexTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/generators/app/indexTest.js b/test/generators/app/indexTest.js index 923bbf9..83247b5 100644 --- a/test/generators/app/indexTest.js +++ b/test/generators/app/indexTest.js @@ -169,7 +169,7 @@ describe('react-webpack:app non-default-prompts', () => { ]); }); - it('should append the postcss loader to the style pipes', () => { + 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\'');