├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── generators ├── app │ ├── index.js │ ├── postcss.js │ └── prompts.js ├── component │ ├── index.js │ └── templates │ │ ├── 3 │ │ ├── components │ │ │ ├── StatefulNoStyles.js │ │ │ ├── StatefulWithStyles.js │ │ │ ├── StatelessNoStyles.js │ │ │ └── StatelessWithStyles.js │ │ ├── styles │ │ │ ├── Component.css │ │ │ ├── Component.less │ │ │ ├── Component.sass │ │ │ ├── Component.scss │ │ │ └── Component.styl │ │ └── tests │ │ │ └── Base.js │ │ └── 4 │ │ ├── components │ │ ├── StatefulCssModules.js │ │ ├── StatefulNoStyles.js │ │ ├── StatefulWithStyles.js │ │ ├── StatelessCssModules.js │ │ ├── StatelessNoStyles.js │ │ └── StatelessWithStyles.js │ │ ├── styles │ │ ├── Component.css │ │ ├── Component.less │ │ ├── Component.sass │ │ ├── Component.scss │ │ └── Component.styl │ │ └── tests │ │ └── Base.js └── setup-env │ ├── constants.js │ ├── index.js │ ├── templates │ └── 4 │ │ ├── Env.js │ │ └── runtimeConfig.js │ └── utils.js ├── jsconfig.json ├── package.json ├── test ├── generators │ ├── app │ │ └── indexTest.js │ ├── component │ │ └── indexTest.js │ └── setup-env │ │ ├── assets │ │ └── moduleIndex.js │ │ └── setupEnvTest.js ├── mocha.opts └── utils │ ├── configTest.js │ └── yeomanTest.js └── utils ├── all.js ├── config.js ├── configopts.json ├── constants.js └── yeoman.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "amd": false, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "rules": { 10 | "comma-dangle": 1, 11 | "quotes": [ 1, "single" ], 12 | "no-undef": 1, 13 | "global-strict": 0, 14 | "no-extra-semi": 1, 15 | "no-underscore-dangle": 0, 16 | "no-console": 0, 17 | "no-unused-vars": 1, 18 | "no-trailing-spaces": [1, { "skipBlankLines": true }], 19 | "no-unreachable": 1, 20 | "no-alert": 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-workspace 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | Thumbs.db 10 | ehthumbs.db 11 | Desktop.ini 12 | $RECYCLE.BIN/ 13 | node_modules/ 14 | npm-debug.log 15 | .idea/ 16 | test/temp-test 17 | coverage/ 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '4' 5 | - '6' 6 | before_install: 7 | - currentfolder=${PWD##*/} 8 | - if [ "$currentfolder" != 'generator-react-webpack' ]; then cd .. && eval "mv $currentfolder generator-react-webpack" && cd generator-react-webpack; fi 9 | after_success: 'npm run coverage' 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # generator-react-webpack - Changelog 2 | 3 | ## 3.3.2 4 | 5 | 1. Emergency fix: Reverted 3.3.1 6 | 7 | ## 3.3.1 8 | 9 | 1. Added new version of yeoman-generator, adjusted unit tests 10 | 11 | ## 3.3.0 12 | 13 | 1. Added new yeoman key "generatedWithVersion". Will be used for backwards compatibility of new major releases. 14 | 15 | ## 3.2.4 16 | 17 | 1. Added whitespaces to generated unit tests (made problems when using it with various eslint rules) 18 | 19 | ## 3.2.3 20 | 21 | 1. Adjusted postcss to work with react-webpack-template 1.4 upwards 22 | 2. Improved styling output (provided by [thewtex](https://github.com/thewtex)) 23 | 24 | ## 3.2.2 25 | 26 | 1. Cleaned up formatting of unit tests 27 | 28 | ## 3.2.1 29 | 30 | 1. Updated tests for new version of react-webpack-template 31 | 32 | ## 3.2.0 33 | 34 | 1. Updated yeoman-generator package to new version 0.22.1 (some methods like generator.NamedBase are now deprecated!) 35 | 36 | ## 3.1.1 37 | 38 | 1. Added bugfix for https://github.com/newtriks/generator-react-webpack/issues/170 39 | 40 | ## 3.1.0 41 | 42 | 1. Added support for postcss (Patch provided by [stylesuxx](https://github.com/stylesuxx)) 43 | 44 | ## 3.0.1 45 | 46 | 1. Unneeded files (License, .npmignore) are not copied anymore 47 | 2. Existence of .babelrc is now checked in unit tests 48 | 49 | ## 3.0.0 50 | 51 | 1. Updated react-webpack-template to 1.0.0 to include support for babel 6. 52 | 53 | ## 2.2.7 54 | 55 | 1. Updated yeoman to 0.21 56 | 2. Added some badges for the readme 57 | 58 | ## 2.2.6 59 | 60 | 1. Added new version of ```react-webpack-template``` (provides new features for continuous testing and better dist build support) 61 | 62 | ## 2.2.5 63 | 64 | 1. Added ability to create stateless components 65 | 2. Updated README with new installation instructions (need to install globally) 66 | 3. Added new tests for components (should be easier to handle in the future) 67 | 68 | ## 2.2.4 69 | 70 | 1. Added automatic generation of components displayName property. This makes it easier to keep track of components that reside in deep subfolders (like src/components/my/example/components/indexComponent) would become "index" as a displayName, but should instead be MyExampleComponentsIndexComponent instead. 71 | 72 | ## 2.2.3 73 | 74 | 1. Fixed .gitignore renaming (Patch provided by [VovanR](https://github.com/VovanR)) 75 | 76 | ## 2.2.0: 77 | 78 | 1. Added new version of react-webpack-template, including support for React 0.14. 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Simon Bailey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-react-webpack 2 | 3 | [![Coverage Status](https://coveralls.io/repos/github/react-webpack-generators/generator-react-webpack/badge.svg?branch=master)](https://coveralls.io/github/react-webpack-generators/generator-react-webpack?branch=master) [![Join the chat at https://gitter.im/newtriks/generator-react-webpack](https://badges.gitter.im/newtriks/generator-react-webpack.svg)](https://gitter.im/newtriks/generator-react-webpack?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://secure.travis-ci.org/react-webpack-generators/generator-react-webpack.png?branch=master)](https://travis-ci.org/react-webpack-generators/generator-react-webpack) ![Amount of Downloads per month](https://img.shields.io/npm/dm/generator-react-webpack.svg "Amount of Downloads") ![Dependency Tracker](https://img.shields.io/david/react-webpack-generators/generator-react-webpack.svg "Dependency Tracker") ![Dependency Tracker](https://img.shields.io/david/dev/react-webpack-generators/generator-react-webpack.svg "Dependency Tracker") ![Node Version](https://img.shields.io/node/v/generator-react-webpack.svg "Node Version") 4 | 5 | > Yeoman generator for [ReactJS](http://facebook.github.io/react/) - lets you quickly set up a project including karma test runner and [Webpack](http://webpack.github.io/) module system. 6 | 7 | # About 8 | Generator-React-Webpack will help you build new React projects using modern technologies. 9 | 10 | Out of the box it comes with support for: 11 | - Webpack 12 | - ES2015 via Babel-Loader 13 | - Different supported style languages (sass, scss, less, stylus) 14 | - Style transformations via PostCSS 15 | - Automatic code linting via esLint 16 | - Ability to unit test components via Karma and Mocha/Chai 17 | 18 | ## Changes since version 2.0 19 | This generator is written in ES2015. This means it is ___not compatible with node.js versions before 4.0___. 20 | 21 | It also does __NOT__ include support for Flux-Frameworks anymore. Instead, we will use it as a base for other generators to build upon. This will make the base generator easier to use and update. 22 | 23 | If you are interested, feel free to write your own generator and use generator-react-webpack as a base (via composition). 24 | 25 | If you have built a generator using generator-react-webpack, tell us and we will add a link to our README. 26 | 27 | ## Generators that extend generator-react-webpack 28 | - [Generator-React-Webpack-Alt](https://github.com/weblogixx/generator-react-webpack-alt) (Adds ability to create actions, stores and sources for [alt.js](http://alt.js.org)) 29 | - [Generator-React-Webpack-Redux](https://github.com/stylesuxx/generator-react-webpack-redux) (Adds ability to create actions and reducers for [Redux](https://github.com/rackt/redux)) 30 | 31 | --- 32 | 33 | ## Installation 34 | ```bash 35 | # Make sure both is installed globally 36 | npm install -g yo 37 | npm install -g generator-react-webpack 38 | ``` 39 | 40 | ## Setting up projects 41 | ```bash 42 | # Create a new directory, and `cd` into it: 43 | mkdir my-new-project && cd my-new-project 44 | 45 | # Run the generator 46 | yo react-webpack 47 | ``` 48 | 49 | Please make sure to edit your newly generated `package.json` file to set description, author information and the like. 50 | 51 | ## Generating new components 52 | ```bash 53 | # After setup of course :) 54 | # cd my-new-project 55 | yo react-webpack:component my/namespaced/components/name 56 | ``` 57 | 58 | The above command will create a new component, as well as its stylesheet and a basic testcase. 59 | 60 | ## Generating new stateless functional components 61 | ``` 62 | yo react-webpack:component my/namespaced/components/name --stateless 63 | ``` 64 | 65 | Stateless functional components where introduced in React v0.14. They have a much shorter syntax than regular ones and no state or lifecycle methods at all. Please read the [React 0.14 release notes](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html) to get more information about those components. 66 | 67 | ___Note___: You will still be able to set properties for stateless components! 68 | 69 | ## Adding PostCSS plugins 70 | 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*. 71 | 72 | Example for autoprefixer: 73 | ```bash 74 | cd my-new-project 75 | npm install autoprefixer 76 | ``` 77 | Require in *cfg/base.js* 78 | ```JavaScript 79 | ... 80 | postcss: function () { 81 | return [ 82 | require('autoprefixer')({ 83 | browsers: ['last 2 versions', 'ie >= 8'] 84 | }) 85 | ]; 86 | } 87 | ... 88 | ``` 89 | 90 | ## Usage 91 | The following commands are available in your project: 92 | ```bash 93 | # Start for development 94 | npm start # or 95 | npm run serve 96 | 97 | # Start the dev-server with the dist version 98 | npm run serve:dist 99 | 100 | # Just build the dist version and copy static files 101 | npm run dist 102 | 103 | # Run unit tests 104 | npm test 105 | 106 | # Auto-run unit tests on file changes 107 | npm run test:watch 108 | 109 | # Lint all files in src (also automatically done AFTER tests are run) 110 | npm run lint 111 | 112 | # Clean up the dist directory 113 | npm run clean 114 | 115 | # Just copy the static assets 116 | npm run copy 117 | ``` 118 | 119 | ### Naming Components 120 | We have opted to follow [@floydophone](https://twitter.com/floydophone) convention of uppercase for component file naming e.g. [Component.js](https://github.com/petehunt/ReactHack/tree/master/src/components). I am open to suggestions if there is a general objection to this decision. 121 | 122 | ### Modules 123 | Each component is a module and can be required using the [Webpack](http://webpack.github.io/) module system. [Webpack](http://webpack.github.io/) uses [Loaders](http://webpack.github.io/docs/loaders.html) which means you can also require CSS and a host of other file types. Read the [Webpack documentation](http://webpack.github.io/docs/home.html) to find out more. 124 | 125 | ## Props 126 | Thanks to [Edd Hannay](https://github.com/eddhannay) for his Webpack optimisations, my local merge and testing meant his additions lost his signature (my fault, sorry). So, big thanks Edd. 127 | 128 | ## Contribute 129 | Contributions are welcomed. When submitting a bugfix, write a test that exposes the bug and fails before applying your fix. Submit the test alongside the fix. 130 | 131 | ### Running Tests 132 | `npm test` or `node node_modules/.bin/mocha` 133 | 134 | ## License 135 | [MIT license](http://opensource.org/licenses/mit-license.php) 136 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generators = require('yeoman-generator'); 3 | const utils = require('../../utils/all'); 4 | const prompts = require('./prompts'); 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | const packageInfo = require('../../package.json'); 8 | 9 | // Set the base root directory for our files. Make sure to always use the node_modules 10 | // base path instead of the require call only. This is needed because require.resolve 11 | // also includes the path set in package.json main keys! 12 | const baseRootPath = path.join(path.dirname(require.resolve('react-webpack-template')), '..'); 13 | 14 | /** 15 | * Base generator. Will copy all required files from react-webpack-template 16 | */ 17 | class AppGenerator extends Generators.Base { 18 | 19 | constructor(args, options) { 20 | 21 | super(args, options); 22 | 23 | // Make options available 24 | this.option('skip-welcome-message', { 25 | desc: 'Skip the welcome message', 26 | type: Boolean, 27 | defaults: false 28 | }); 29 | this.option('skip-install'); 30 | 31 | // Use our plain template as source 32 | this.sourceRoot(baseRootPath); 33 | 34 | this.config.save(); 35 | } 36 | 37 | initializing() { 38 | 39 | if(!this.options['skip-welcome-message']) { 40 | this.log(require('yeoman-welcome')); 41 | this.log('Out of the box I include Webpack and some default React components.\n'); 42 | } 43 | } 44 | 45 | prompting() { 46 | 47 | return this.prompt(prompts).then((answers) => { 48 | 49 | // Make sure to get the correct app name if it is not the default 50 | if(answers.appName !== utils.yeoman.getAppName()) { 51 | answers.appName = utils.yeoman.getAppName(answers.appName); 52 | } 53 | 54 | // Set needed global vars for yo 55 | this.appName = answers.appName; 56 | this.style = answers.style; 57 | this.cssmodules = answers.cssmodules; 58 | this.postcss = answers.postcss; 59 | this.generatedWithVersion = parseInt(packageInfo.version.split('.').shift(), 10); 60 | 61 | // Set needed keys into config 62 | this.config.set('appName', this.appName); 63 | this.config.set('appPath', this.appPath); 64 | this.config.set('style', this.style); 65 | this.config.set('cssmodules', this.cssmodules); 66 | this.config.set('postcss', this.postcss); 67 | this.config.set('generatedWithVersion', this.generatedWithVersion); 68 | }); 69 | } 70 | 71 | configuring() { 72 | 73 | // Generate our package.json. Make sure to also include the required dependencies for styles 74 | let defaultSettings = this.fs.readJSON(`${baseRootPath}/package.json`); 75 | let packageSettings = { 76 | name: this.appName, 77 | private: true, 78 | version: '0.0.1', 79 | description: `${this.appName} - Generated by generator-react-webpack`, 80 | main: 'src/index.js', 81 | scripts: defaultSettings.scripts, 82 | repository: '', 83 | keywords: [], 84 | author: 'Your name here', 85 | devDependencies: defaultSettings.devDependencies, 86 | dependencies: defaultSettings.dependencies 87 | }; 88 | 89 | // Add needed loaders if we have special styles 90 | let styleConfig = utils.config.getChoiceByKey('style', this.style); 91 | if(styleConfig && styleConfig.packages) { 92 | 93 | for(let dependency of styleConfig.packages) { 94 | packageSettings.devDependencies[dependency.name] = dependency.version; 95 | } 96 | } 97 | 98 | // Add postcss module if enabled 99 | let postcssConfig = utils.config.getChoiceByKey('postcss', 'postcss'); 100 | if(this.postcss && postcssConfig && postcssConfig.packages) { 101 | 102 | for(let dependency of postcssConfig.packages) { 103 | packageSettings.devDependencies[dependency.name] = dependency.version; 104 | } 105 | } 106 | 107 | // Add cssmodules if enabled 108 | const cssmoduleConfig = utils.config.getChoiceByKey('cssmodules', 'cssmodules'); 109 | if(this.cssmodules && cssmoduleConfig && cssmoduleConfig.packages) { 110 | for(let dependency of cssmoduleConfig.packages) { 111 | packageSettings.dependencies[dependency.name] = dependency.version; 112 | } 113 | } 114 | 115 | this.fs.writeJSON(this.destinationPath('package.json'), packageSettings); 116 | } 117 | 118 | writing() { 119 | 120 | const excludeList = [ 121 | 'LICENSE', 122 | 'README.md', 123 | 'CHANGELOG.md', 124 | 'node_modules', 125 | 'package.json', 126 | '.istanbul.yml', 127 | '.travis.yml' 128 | ]; 129 | 130 | // Get all files in our repo and copy the ones we should 131 | fs.readdir(this.sourceRoot(), (err, items) => { 132 | 133 | for(let item of items) { 134 | 135 | // Skip the item if it is in our exclude list 136 | if(excludeList.indexOf(item) !== -1) { 137 | continue; 138 | } 139 | 140 | // Copy all items to our root 141 | let fullPath = path.join(baseRootPath, item); 142 | if(fs.lstatSync(fullPath).isDirectory()) { 143 | this.bulkDirectory(item, item); 144 | } else { 145 | if (item === '.npmignore') { 146 | this.copy(item, '.gitignore'); 147 | } else { 148 | this.copy(item, item); 149 | } 150 | } 151 | } 152 | }); 153 | } 154 | 155 | install() { 156 | 157 | // Currently buggy! 158 | if(this.postcss) { 159 | const postcss = require('./postcss'); 160 | postcss.write(path.join(this.destinationRoot(), 'conf/webpack/Base.js')); 161 | } 162 | 163 | if(!this.options['skip-install']) { 164 | this.installDependencies({ bower: false }); 165 | } 166 | } 167 | } 168 | 169 | module.exports = AppGenerator; 170 | -------------------------------------------------------------------------------- /generators/app/postcss.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const esprima = require('esprima'); 4 | const walk = require('esprima-walk'); 5 | const escodegen = require('escodegen'); 6 | 7 | module.exports = { 8 | 9 | /** 10 | * Add postcss support to the given webpack base configuration object 11 | * @param {String} path The config file name 12 | */ 13 | write: function(path) { 14 | 15 | const data = fs.readFileSync(path, 'utf8'); 16 | const ast = esprima.parse(data); 17 | 18 | // List of css dialects we want to add postCSS for 19 | // On regular css, we can add the loader to the end 20 | // of the chain. If we have a preprocessor, we will add 21 | // it before the initial loader 22 | const cssDialects = [ 23 | '\\.cssmodule\\.css$', 24 | '^.((?!cssmodule).)*\\.css$' 25 | ]; 26 | 27 | const preprocessorDialects = [ 28 | '\\.cssmodule\\.(sass|scss)$', 29 | '^.((?!cssmodule).)*\\.(sass|scss)$', 30 | '\\.cssmodule\\.less$', 31 | '^.((?!cssmodule).)*\\.less$', 32 | '\\.cssmodule\\.styl$', 33 | '^.((?!cssmodule).)*\\.styl$' 34 | ]; 35 | 36 | // Prepare postCSS statement for inclusion 37 | const postcssFunction = 'var postcss = { postcss: function() { return []; } }'; 38 | const postcssAst = esprima.parse(postcssFunction); 39 | const postcss = postcssAst.body[0].declarations[0].init.properties[0]; 40 | 41 | // The postcss loader item to add 42 | const postcssLoaderObject = 'var postcss = [{ loader: \'postcss-loader\'}]'; 43 | const postcssLoaderAst = esprima.parse(postcssLoaderObject); 44 | const postcssLoader = postcssLoaderAst.body[0].declarations[0].init.elements[0]; 45 | 46 | 47 | // Add postcss to the loaders array 48 | walk.walkAddParent(ast, (node) => { 49 | 50 | 51 | // Add the postcss key to the global configuration 52 | 53 | if( 54 | node.type === 'MethodDefinition' && 55 | node.key.name === 'defaultSettings' 56 | ) { 57 | const returnStatement = node.value.body.body[1]; 58 | returnStatement.argument.properties.push(postcss); 59 | } 60 | 61 | // Parse all property nodes that use a regex. 62 | // This should only be available under module.(pre)loaders 63 | if( 64 | node.type === 'Property' && 65 | node.key.type === 'Identifier' && 66 | node.key.name === 'test' && 67 | typeof node.value.regex !== 'undefined' 68 | ) { 69 | 70 | // Regular css usage 71 | if(cssDialects.indexOf(node.value.regex.pattern) !== -1) { 72 | const loaderData = node.parent.properties[1]; 73 | loaderData.value.elements.push(postcssLoader); 74 | } 75 | 76 | 77 | if(preprocessorDialects.indexOf(node.value.regex.pattern) !== -1) { 78 | const loaderData = node.parent.properties[1]; 79 | const lastElm = loaderData.value.elements.pop(); 80 | loaderData.value.elements.push(postcssLoader); 81 | loaderData.value.elements.push(lastElm); 82 | } 83 | } 84 | }); 85 | 86 | // Prepare the final code and write it back 87 | const finalCode = escodegen.generate(ast, { 88 | format: { 89 | indent: { 90 | adjustMultilineComment: true, 91 | style: ' ' 92 | } 93 | }, 94 | comment: true 95 | }); 96 | 97 | fs.writeFileSync(path, finalCode, 'utf8'); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /generators/app/prompts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const utils = require('../../utils/all'); 3 | 4 | module.exports = [ 5 | { 6 | type: 'input', 7 | name: 'appName', 8 | message: 'Please choose your application name', 9 | default: utils.yeoman.getAppName() 10 | }, 11 | { 12 | type: 'list', 13 | name: 'style', 14 | message: 'Which style language do you want to use?', 15 | choices: utils.config.getChoices('style'), 16 | default: utils.config.getDefaultChoice('style') 17 | }, 18 | { 19 | type: 'confirm', 20 | name: 'cssmodules', 21 | message: 'Enable css module support? See https://github.com/gajus/react-css-modules for further info', 22 | default: true 23 | }, 24 | { 25 | type: 'confirm', 26 | name: 'postcss', 27 | message: 'Enable postcss?', 28 | default: false 29 | } 30 | ]; 31 | -------------------------------------------------------------------------------- /generators/component/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generators = require('yeoman-generator'); 3 | const utils = require('../../utils/all'); 4 | const C = utils.constants; 5 | const getAllSettingsFromComponentName = utils.yeoman.getAllSettingsFromComponentName; 6 | 7 | 8 | class ComponentGenerator extends Generators.Base { 9 | 10 | constructor(args, options) { 11 | super(args, options); 12 | 13 | /** 14 | * Flag indicating whether the component should be created with associated style files. 15 | * @type {boolean} 16 | */ 17 | this.useStyles = false; 18 | 19 | /** 20 | * Flag indicating whether the component should make use of css modules. 21 | * @type {boolean} 22 | */ 23 | this.useCssModules = false; 24 | 25 | /** 26 | * A string to prepended to the `className` attribute of generated components. 27 | * @type {string} 28 | */ 29 | this._cssClsPrefix = ''; 30 | 31 | /** 32 | * Flag indicating if stateful components should extends from React.PureComponent 33 | * @type {boolean} 34 | */ 35 | this.usePureComponent = false; 36 | 37 | /** 38 | * Filename of the template that will be used to create the component. 39 | * @type {?string} 40 | */ 41 | this.componentTemplateName = null; 42 | 43 | /** 44 | * Generator and template version to create the component from. 45 | * @type {?number} 46 | */ 47 | this.generatorVersion = null; 48 | 49 | this.argument('name', { type: String, required: true }); 50 | 51 | this.option('stateless', { 52 | desc: 'Create a stateless component instead of a full one', 53 | defaults: false 54 | }); 55 | 56 | this.option('nostyle', { 57 | desc: 'Create a component without creating an associated style', 58 | defaults: false 59 | }); 60 | 61 | this.option('pure', { 62 | desc: 'Create a pure component instead of a "regular" one. Will use React.PureComponent as a base instead of React.Component', 63 | defaults: false 64 | }); 65 | } 66 | 67 | get cssClsPrefix() { 68 | return this._cssClsPrefix; 69 | } 70 | 71 | set cssClsPrefix(val) { 72 | this._cssClsPrefix = (val === '') ? '' : `${val}-`; 73 | } 74 | 75 | configuring() { 76 | // Read the requested major version or default it to the latest stable 77 | this.generatorVersion = this.config.get('generatedWithVersion') || 3; 78 | 79 | if (!C.SUPPORTED_GEN_VERSIONS.some(x => x === this.generatorVersion)) { 80 | this.env.error('Unsupported generator version'); 81 | } 82 | 83 | this.useStyles = !this.options.nostyle; 84 | this.useCssModules = this.config.get('cssmodules') || false; 85 | this.cssClsPrefix = this.config.get('cssClsPrefix') || ''; 86 | 87 | // Make sure we don't try to use template v3 with cssmodules 88 | if (this.generatorVersion < 4 && this.useStyles && this.useCssModules) { 89 | this.env.error('Creating components with cssmodules is only supported in generator versions 4+'); 90 | } 91 | 92 | // Get the filename of the component template to be copied during this run 93 | this.componentTemplateName = 94 | utils.yeoman.getComponentTemplateName(this.options.stateless, this.useStyles, this.useCssModules); 95 | } 96 | 97 | writing() { 98 | const settings = 99 | getAllSettingsFromComponentName( 100 | this.name, 101 | this.config.get('style'), 102 | this.useCssModules, 103 | this.options.pure, 104 | this.generatorVersion, 105 | this.cssClsPrefix 106 | ); 107 | 108 | // Create the style template. Skipped if nostyle is set as command line flag 109 | if(this.useStyles) { 110 | this.fs.copyTpl( 111 | this.templatePath(`${this.generatorVersion}/styles/Component${settings.style.suffix}`), 112 | this.destinationPath(settings.style.path + settings.style.fileName), 113 | settings 114 | ); 115 | } 116 | 117 | // Create the component 118 | this.fs.copyTpl( 119 | this.templatePath(`${this.generatorVersion}/components/${this.componentTemplateName}`), 120 | this.destinationPath(settings.component.path + settings.component.fileName), 121 | settings 122 | ); 123 | 124 | // Create the unit test 125 | this.fs.copyTpl( 126 | this.templatePath(`${this.generatorVersion}/tests/Base.js`), 127 | this.destinationPath(settings.test.path + settings.test.fileName), 128 | settings 129 | ); 130 | } 131 | } 132 | 133 | module.exports = ComponentGenerator; 134 | -------------------------------------------------------------------------------- /generators/component/templates/3/components/StatefulNoStyles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | class <%= component.className %> extends React.Component { 6 | render() { 7 | return ( 8 |
9 | Please edit <%= component.path %>/<%= component.fileName %> to update this component! 10 |
11 | ); 12 | } 13 | } 14 | 15 | <%= component.className %>.displayName = '<%= component.displayName %>'; 16 | 17 | // Uncomment properties you need 18 | // <%= component.className %>.propTypes = {}; 19 | // <%= component.className %>.defaultProps = {}; 20 | 21 | export default <%= component.className %>; 22 | -------------------------------------------------------------------------------- /generators/component/templates/3/components/StatefulWithStyles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | require('<%= style.webpackPath %>'); 6 | 7 | class <%= component.className %> extends React.Component { 8 | render() { 9 | return ( 10 |
11 | Please edit <%= component.path %>/<%= component.fileName %> to update this component! 12 |
13 | ); 14 | } 15 | } 16 | 17 | <%= component.className %>.displayName = '<%= component.displayName %>'; 18 | 19 | // Uncomment properties you need 20 | // <%= component.className %>.propTypes = {}; 21 | // <%= component.className %>.defaultProps = {}; 22 | 23 | export default <%= component.className %>; 24 | -------------------------------------------------------------------------------- /generators/component/templates/3/components/StatelessNoStyles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | let <%= component.className %> = () => ( 6 |
7 | Please edit <%= component.path %>/<%= component.fileName %> to update this component! 8 |
9 | ); 10 | 11 | <%= component.className %>.displayName = '<%= component.displayName %>'; 12 | 13 | // Uncomment properties you need 14 | // <%= component.className %>.propTypes = {}; 15 | // <%= component.className %>.defaultProps = {}; 16 | 17 | export default <%= component.className %>; 18 | -------------------------------------------------------------------------------- /generators/component/templates/3/components/StatelessWithStyles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | require('<%= style.webpackPath %>'); 6 | 7 | let <%= component.className %> = () => ( 8 |
9 | Please edit <%= component.path %>/<%= component.fileName %> to update this component! 10 |
11 | ); 12 | 13 | <%= component.className %>.displayName = '<%= component.displayName %>'; 14 | 15 | // Uncomment properties you need 16 | // <%= component.className %>.propTypes = {}; 17 | // <%= component.className %>.defaultProps = {}; 18 | 19 | export default <%= component.className %>; 20 | -------------------------------------------------------------------------------- /generators/component/templates/3/styles/Component.css: -------------------------------------------------------------------------------- 1 | .<%= style.className %> { 2 | border: 1px dashed #f00; 3 | } 4 | -------------------------------------------------------------------------------- /generators/component/templates/3/styles/Component.less: -------------------------------------------------------------------------------- 1 | .<%= style.className %> { 2 | border: 1px dashed #f00; 3 | } 4 | -------------------------------------------------------------------------------- /generators/component/templates/3/styles/Component.sass: -------------------------------------------------------------------------------- 1 | .<%= style.className %> 2 | border: 1px dashed #f00 3 | -------------------------------------------------------------------------------- /generators/component/templates/3/styles/Component.scss: -------------------------------------------------------------------------------- 1 | .<%= style.className %> { 2 | border: 1px dashed #f00; 3 | } 4 | -------------------------------------------------------------------------------- /generators/component/templates/3/styles/Component.styl: -------------------------------------------------------------------------------- 1 | .<%= style.className %> 2 | border 1px dashed #f00 3 | -------------------------------------------------------------------------------- /generators/component/templates/3/tests/Base.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | /* global expect */ 3 | /* eslint no-console: 0 */ 4 | 'use strict'; 5 | 6 | // Uncomment the following lines to use the react test utilities 7 | // import TestUtils from 'react-addons-test-utils'; 8 | import createComponent from 'helpers/shallowRenderHelper'; 9 | 10 | import <%= component.className %> from '<%= component.webpackPath %>'; 11 | 12 | describe('<%= component.className %>', function () { 13 | let component; 14 | 15 | beforeEach(function () { 16 | component = createComponent(<%= component.className %>); 17 | }); 18 | 19 | it('should have its component name as default className', function () { 20 | expect(component.props.className).to.equal('<%= style.className %>'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /generators/component/templates/4/components/StatefulCssModules.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssmodules from 'react-css-modules'; 3 | import styles from '<%= style.webpackPath %>'; 4 | 5 | class <%= component.className %> extends <%= component.classBase %> { 6 | 7 | render() { 8 | return ( 9 |
10 | Please edit <%= component.path %><%= component.fileName %> to update this component! 11 |
12 | ); 13 | } 14 | } 15 | 16 | <%= component.className %>.displayName = '<%= component.displayName %>'; 17 | <%= component.className %>.propTypes = {}; 18 | <%= component.className %>.defaultProps = {}; 19 | 20 | export default cssmodules(<%= component.className %>, styles); 21 | -------------------------------------------------------------------------------- /generators/component/templates/4/components/StatefulNoStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class <%= component.className %> extends <%= component.classBase %> { 4 | 5 | render() { 6 | return ( 7 |
8 | Please edit <%= component.path %><%= component.fileName %> to update this component! 9 |
10 | ); 11 | } 12 | } 13 | 14 | <%= component.className %>.displayName = '<%= component.displayName %>'; 15 | <%= component.className %>.propTypes = {}; 16 | <%= component.className %>.defaultProps = {}; 17 | 18 | export default <%= component.className %>; 19 | -------------------------------------------------------------------------------- /generators/component/templates/4/components/StatefulWithStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '<%= style.webpackPath %>'; 3 | 4 | class <%= component.className %> extends <%= component.classBase %> { 5 | 6 | render() { 7 | return ( 8 |
9 | Please edit <%= component.path %><%= component.fileName %> to update this component! 10 |
11 | ); 12 | } 13 | } 14 | 15 | <%= component.className %>.displayName = '<%= component.displayName %>'; 16 | <%= component.className %>.propTypes = {}; 17 | <%= component.className %>.defaultProps = {}; 18 | 19 | export default <%= component.className %>; 20 | -------------------------------------------------------------------------------- /generators/component/templates/4/components/StatelessCssModules.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssmodules from 'react-css-modules'; 3 | import styles from '<%= style.webpackPath %>'; 4 | 5 | const <%= component.className %> = () => ( 6 |
7 | Please edit <%= component.path %><%= component.fileName %> to update this component! 8 |
9 | ); 10 | 11 | <%= component.className %>.displayName = '<%= component.displayName %>'; 12 | <%= component.className %>.propTypes = {}; 13 | <%= component.className %>.defaultProps = {}; 14 | 15 | export default cssmodules(<%= component.className %>, styles); 16 | -------------------------------------------------------------------------------- /generators/component/templates/4/components/StatelessNoStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const <%= component.className %> = () => ( 4 |
5 | Please edit <%= component.path %><%= component.fileName %> to update this component! 6 |
7 | ); 8 | 9 | <%= component.className %>.displayName = '<%= component.displayName %>'; 10 | <%= component.className %>.propTypes = {}; 11 | <%= component.className %>.defaultProps = {}; 12 | 13 | export default <%= component.className %>; 14 | -------------------------------------------------------------------------------- /generators/component/templates/4/components/StatelessWithStyles.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '<%= style.webpackPath %>'; 3 | 4 | const <%= component.className %> = () => ( 5 |
6 | Please edit <%= component.path %><%= component.fileName %> to update this component! 7 |
8 | ); 9 | 10 | <%= component.className %>.displayName = '<%= component.displayName %>'; 11 | <%= component.className %>.propTypes = {}; 12 | <%= component.className %>.defaultProps = {}; 13 | 14 | export default <%= component.className %>; 15 | -------------------------------------------------------------------------------- /generators/component/templates/4/styles/Component.css: -------------------------------------------------------------------------------- 1 | .<%= style.className %> { 2 | border: 1px dashed #f00; 3 | } 4 | -------------------------------------------------------------------------------- /generators/component/templates/4/styles/Component.less: -------------------------------------------------------------------------------- 1 | .<%= style.className %> { 2 | border: 1px dashed #f00; 3 | } 4 | -------------------------------------------------------------------------------- /generators/component/templates/4/styles/Component.sass: -------------------------------------------------------------------------------- 1 | .<%= style.className %> 2 | border: 1px dashed #f00 3 | -------------------------------------------------------------------------------- /generators/component/templates/4/styles/Component.scss: -------------------------------------------------------------------------------- 1 | .<%= style.className %> { 2 | border: 1px dashed #f00; 3 | } 4 | -------------------------------------------------------------------------------- /generators/component/templates/4/styles/Component.styl: -------------------------------------------------------------------------------- 1 | .<%= style.className %> 2 | border 1px dashed #f00 3 | -------------------------------------------------------------------------------- /generators/component/templates/4/tests/Base.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import <%= component.className %> from '<%= component.webpackPath %>'; 4 | 5 | describe('<<%= component.className %> />', function () { 6 | 7 | let component; 8 | beforeEach(function () { 9 | component = shallow(<<%= component.className %> />); 10 | }); 11 | 12 | describe('when rendering the component', function () { 13 | 14 | it('should have a className of "<%= style.className %>"', function () { 15 | expect(component.hasClass('<%= style.className %>')).to.equal(true); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /generators/setup-env/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const esDefaultOpts = require('esformatter/lib/preset/default.json'); 4 | 5 | const esOpts = Object.assign({}, esDefaultOpts, { 6 | 'lineBreak': { 7 | 'before': { 8 | 'AssignmentExpression': '>=2', 9 | 'ClassDeclaration': 2, 10 | 'EndOfFile': 1 11 | }, 12 | 'after': { 13 | 'ClassClosingBrace': 2, 14 | 'FunctionDeclaration': '>=2', 15 | 'BlockStatementClosingBrace': '>=2' 16 | } 17 | } 18 | }); 19 | 20 | module.exports = { 21 | esOpts 22 | }; 23 | -------------------------------------------------------------------------------- /generators/setup-env/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Generators = require('yeoman-generator'); 4 | const classify = require('underscore.string').classify; 5 | const underscored = require('underscore.string').underscored; 6 | 7 | const formatCode = require('./utils').formatCode; 8 | const getModifiedConfigModuleIndex = require('./utils').getModifiedConfigModuleIndex; 9 | 10 | 11 | class EnvGenerator extends Generators.Base { 12 | 13 | constructor(args, options) { 14 | super(args, options); 15 | 16 | this.argument('envName', { type: String, required: true }); 17 | } 18 | 19 | configuring() { 20 | 21 | /** 22 | * Currently used major version of the generator (defaults to latest stable). 23 | * @type {number} 24 | */ 25 | this.generatorVersion = this.config.get('generatedWithVersion') || 3; 26 | 27 | // Make sure we don't try to use this subgen on V3 or lower. 28 | if (this.generatorVersion < 4) { 29 | this.env.error('Setting up new envs is only supported in generator versions 4+'); 30 | } 31 | } 32 | 33 | writing() { 34 | const classedEnv = classify(this.envName); 35 | const snakedEnv = underscored(this.envName.toLowerCase()); 36 | 37 | // Write conf/webpack/.js 38 | this.fs.copyTpl( 39 | this.templatePath(`${this.generatorVersion}/Env.js`), 40 | this.destinationPath(`conf/webpack/${classedEnv}.js`), 41 | { envName: snakedEnv } 42 | ); 43 | 44 | // Write src/config/.js 45 | this.fs.copyTpl( 46 | this.templatePath(`${this.generatorVersion}/runtimeConfig.js`), 47 | this.destinationPath(`src/config/${snakedEnv}.js`), 48 | { envName: snakedEnv } 49 | ); 50 | 51 | // Write conf/webpack/index.js 52 | const moduleIndexPath = this.destinationPath('conf/webpack/index.js'); 53 | const updatedModuleIndex = formatCode( 54 | getModifiedConfigModuleIndex(this.fs.read(moduleIndexPath), snakedEnv, classedEnv) 55 | ); 56 | this.fs.write(this.destinationPath('conf/webpack/index.js'), formatCode(updatedModuleIndex)); 57 | } 58 | 59 | } 60 | 61 | module.exports = EnvGenerator; 62 | -------------------------------------------------------------------------------- /generators/setup-env/templates/4/Env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Default dev server configuration. 5 | */ 6 | const webpack = require('webpack'); 7 | const WebpackBaseConfig = require('./Base'); 8 | 9 | class WebpackDevConfig extends WebpackBaseConfig { 10 | 11 | constructor() { 12 | super(); 13 | this.config = { 14 | // Update your env-specific configuration here! 15 | // To start, look at ./Dev.js or ./Dist.js for two example configurations 16 | // targeted at production or development builds. 17 | }; 18 | } 19 | 20 | /** 21 | * Get the environment name 22 | * @return {String} The current environment 23 | */ 24 | get env() { 25 | return '<%= envName %>'; 26 | } 27 | } 28 | 29 | module.exports = WebpackDevConfig; 30 | -------------------------------------------------------------------------------- /generators/setup-env/templates/4/runtimeConfig.js: -------------------------------------------------------------------------------- 1 | import baseConfig from './base'; 2 | 3 | const config = { 4 | appEnv: '<%= envName %>', 5 | }; 6 | 7 | export default Object.freeze(Object.assign({}, baseConfig, config)); 8 | -------------------------------------------------------------------------------- /generators/setup-env/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const acorn = require('acorn'); 4 | const escodegen = require('escodegen'); 5 | const esformatter = require('esformatter'); 6 | const jp = require('jsonpath'); 7 | 8 | const esOpts = require('./constants').esOpts; 9 | 10 | 11 | /** 12 | * Returns an AST Node for a {@code Property} in the {@code module.exports} object. 13 | * 14 | * @param {string} envName 15 | * @return {Object} 16 | */ 17 | function createExportNode(envName) { 18 | return { 19 | 'type': 'Property', 20 | 'method': false, 21 | 'shorthand': true, 22 | 'computed': false, 23 | 'key': { 24 | 'type': 'Identifier', 25 | 'name': envName 26 | }, 27 | 'kind': 'init', 28 | 'value': { 29 | 'type': 'Identifier', 30 | 'name': envName 31 | } 32 | } 33 | } 34 | 35 | 36 | /** 37 | * Returns updated index module requiring and exporting the newly created environment. 38 | * 39 | * @param {string} fileStr 40 | * @param {string} snakedEnv 41 | * @param {string} classedEnv 42 | * @return {string} file contents of updated conf/webpack/index.js 43 | */ 44 | function getModifiedConfigModuleIndex(fileStr, snakedEnv, classedEnv) { 45 | // TODO [sthzg] we might want to rewrite the AST-mods in this function using a walker. 46 | 47 | const moduleFileAst = acorn.parse(fileStr, { module: true }); 48 | 49 | // if required env was already created, just return the original string 50 | if (jp.paths(moduleFileAst, `$..[?(@.value=="./${classedEnv}" && @.type=="Literal")]`).length > 0) { 51 | return fileStr; 52 | } 53 | 54 | // insert require call for the new env 55 | const envImportAst = acorn.parse(`const ${snakedEnv} = require('./${classedEnv}');`); 56 | const insertAt = jp.paths(moduleFileAst, '$..[?(@.name=="require")]').pop()[2] + 1; 57 | moduleFileAst.body.splice(insertAt, 0, envImportAst); 58 | 59 | // add new env to module.exports 60 | const exportsAt = jp.paths(moduleFileAst, '$..[?(@.name=="exports")]').pop()[2]; 61 | moduleFileAst.body[exportsAt].expression.right.properties.push(createExportNode(snakedEnv)); 62 | 63 | return escodegen.generate(moduleFileAst, { format: { indent: { style: ' ' } } }); 64 | } 65 | 66 | 67 | /** 68 | * Returns a beautified representation of {@code fileStr}. 69 | * 70 | * @param {string} fileStr 71 | * @return {string} 72 | */ 73 | function formatCode(fileStr) { 74 | return esformatter.format(fileStr, esOpts); 75 | } 76 | 77 | 78 | module.exports = { 79 | createExportNode, 80 | formatCode, 81 | getModifiedConfigModuleIndex 82 | }; 83 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | //Support for Visual Studio Code https://code.visualstudio.com/docs/languages/javascript 2 | { 3 | "compilerOptions": { 4 | "target": "ES6", 5 | "module": "commonjs" 6 | } 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-react-webpack", 3 | "version": "4.0.1-1", 4 | "description": "Yeoman generator for using React with Webpack via Babel", 5 | "keywords": [ 6 | "yeoman-generator", 7 | "react", 8 | "reactjs", 9 | "webpack", 10 | "scaffold", 11 | "framework", 12 | "component", 13 | "frontend", 14 | "front-end", 15 | "app" 16 | ], 17 | "homepage": "https://github.com/react-webpack-generators/generator-react-webpack#readme", 18 | "bugs": "https://github.com/react-webpack-generators/generator-react-webpack/issues", 19 | "author": { 20 | "name": "Simon Bailey", 21 | "email": "simon@newtriks.com", 22 | "url": "https://github.com/newtriks" 23 | }, 24 | "contributors": [ 25 | { 26 | "name": "Christian Schilling", 27 | "email": "cs@weblogixx.de", 28 | "url": "https://github.com/weblogixx" 29 | } 30 | ], 31 | "main": "generators/app/index.js", 32 | "files": [ 33 | "generators/app", 34 | "generators/component", 35 | "utils" 36 | ], 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/react-webpack-generators/generator-react-webpack.git" 40 | }, 41 | "scripts": { 42 | "test": "istanbul cover _mocha", 43 | "test:watch": "mocha -w", 44 | "coverage": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls", 45 | "release:major": "npm version prerelease && git push --follow-tags && npm publish --tag beta", 46 | "release:minor": "npm version prerelease && git push --follow-tags && npm publish --tag beta", 47 | "release:patch": "npm version prerelease && git push --follow-tags && npm publish --tag beta" 48 | }, 49 | "dependencies": { 50 | "acorn": "^4.0.3", 51 | "escodegen": "^1.8.1", 52 | "esformatter": "^0.9.6", 53 | "esprima": "^3.1.1", 54 | "esprima-walk": "^0.1.0", 55 | "jsonpath": "^0.2.7", 56 | "react-webpack-template": "^2.0.1-6", 57 | "underscore.string": "^3.2.2", 58 | "yeoman-generator": "^0.24.0", 59 | "yeoman-welcome": "^1.0.1" 60 | }, 61 | "devDependencies": { 62 | "chai": "^3.2.0", 63 | "coveralls": "^2.11.12", 64 | "fs-extra": "^0.30.0", 65 | "istanbul": "^0.4.5", 66 | "mocha": "^3.0.0", 67 | "yeoman-assert": "^2.1.1", 68 | "yeoman-test": "^1.0.0" 69 | }, 70 | "engines": { 71 | "node": ">=4.0.0", 72 | "iojs": ">=1.1.0" 73 | }, 74 | "license": "MIT" 75 | } 76 | -------------------------------------------------------------------------------- /test/generators/app/indexTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let path = require('path'); 3 | let expect = require('chai').expect; 4 | let assert = require('yeoman-assert'); 5 | let helpers = require('yeoman-test'); 6 | 7 | // Default globals, used in all tests 8 | const defaultPrompts = require('../../../generators/app/prompts.js'); 9 | const generatorBase = path.join(__dirname, '../../../generators/app'); 10 | 11 | let generator; 12 | 13 | /** 14 | * Global before load function. Run in the before callbacks 15 | * @param {Object} prompts List of prompts to use 16 | * @return {Promise} 17 | */ 18 | const beforeLoad = (prompts) => { 19 | 20 | return helpers.run(generatorBase) 21 | .inTmpDir() 22 | .withOptions({ 23 | 'skip-welcome-message': true, 24 | 'skip-install': true 25 | }) 26 | .withPrompts(prompts) 27 | .on('ready', function(instance) { 28 | generator = instance; 29 | }) 30 | .toPromise(); 31 | }; 32 | 33 | describe('react-webpack:app', () => { 34 | 35 | let prompts = {}; 36 | for(let p of defaultPrompts) { 37 | prompts[p.name] = p.default; 38 | } 39 | 40 | before(() => { 41 | return beforeLoad(prompts); 42 | }); 43 | 44 | describe('#config', () => { 45 | 46 | it('should set the generatedWith key to the current generator major version', () => { 47 | expect(generator.config.get('generatedWithVersion')).to.equal(4); 48 | }); 49 | 50 | it('should use "css" as default style language', () => { 51 | expect(generator.config.get('style')).to.equal('css'); 52 | }); 53 | 54 | it('should use "css modules" per default', () => { 55 | expect(generator.config.get('cssmodules')).to.be.true; 56 | }); 57 | 58 | it('should not enable "PostCSS" by default', () => { 59 | expect(generator.config.get('postcss')).to.be.false; 60 | }); 61 | }); 62 | 63 | describe('configuring', () => { 64 | it('should add css module support', () => { 65 | assert.fileContent('package.json', 'react-css-modules'); 66 | }); 67 | }); 68 | 69 | describe('#createFiles', () => { 70 | 71 | it('should generate dot files', () => { 72 | 73 | assert.file([ 74 | '.babelrc', 75 | '.editorconfig', 76 | '.eslintrc', 77 | '.gitignore', 78 | '.yo-rc.json' 79 | ]); 80 | }); 81 | 82 | it('should generate project configuration files', () => { 83 | 84 | assert.file([ 85 | 'package.json' 86 | ]); 87 | }); 88 | 89 | it('should generate the webpack configuration', () => { 90 | 91 | assert.file([ 92 | 'conf/webpack/Base.js', 93 | 'conf/webpack/Dev.js', 94 | 'conf/webpack/Dist.js', 95 | 'conf/webpack/Test.js', 96 | 'webpack.config.js' 97 | ]); 98 | }); 99 | 100 | it('should generate required source files', () => { 101 | 102 | assert.file([ 103 | 'src/actions/README.md', 104 | 'src/client.js', 105 | 'src/components/App.js', 106 | 'src/components/app.css', 107 | 'src/static/favicon.ico', 108 | 'src/images/yeoman.png', 109 | 'src/index.html', 110 | 'src/sources/README.md', 111 | 'src/stores/README.md' 112 | ]); 113 | }); 114 | 115 | it('should generate test configuration and basic tests', () => { 116 | 117 | assert.file([ 118 | 'karma.conf.js', 119 | 'test/components/AppTest.js', 120 | 'test/config/ConfigTest.js', 121 | 'test/loadtests.js', 122 | 'test/.eslintrc' 123 | ]); 124 | }); 125 | }); 126 | }); 127 | 128 | describe('react-webpack:app without cssmodules support', () => { 129 | 130 | let prompts = {}; 131 | for(let p of defaultPrompts) { 132 | prompts[p.name] = p.default; 133 | } 134 | prompts.cssmodules = false; 135 | 136 | before(() => { 137 | return beforeLoad(prompts); 138 | }); 139 | 140 | describe('#config', () => { 141 | 142 | it('should set the generatedWith key to the current generator major version', () => { 143 | expect(generator.config.get('generatedWithVersion')).to.equal(4); 144 | }); 145 | 146 | it('should use "css" as default style language', () => { 147 | expect(generator.config.get('style')).to.equal('css'); 148 | }); 149 | 150 | it('should not use "css modules"', () => { 151 | expect(generator.config.get('cssmodules')).to.be.false; 152 | }); 153 | 154 | it('should not enable "PostCSS" by default', () => { 155 | expect(generator.config.get('postcss')).to.be.false; 156 | }); 157 | }); 158 | 159 | describe('configuring', () => { 160 | it('should add no cssmodule support', () => { 161 | assert.noFileContent('package.json', 'react-css-modules'); 162 | }); 163 | }); 164 | }); 165 | 166 | describe('react-webpack:app with PostCSS support', () => { 167 | 168 | let prompts = {}; 169 | for(let p of defaultPrompts) { 170 | prompts[p.name] = p.default; 171 | } 172 | prompts.postcss = true; 173 | 174 | before(() => { 175 | return beforeLoad(prompts); 176 | }); 177 | 178 | describe('#config', () => { 179 | 180 | it('should set the generatedWith key to the current generator major version', () => { 181 | expect(generator.config.get('generatedWithVersion')).to.equal(4); 182 | }); 183 | 184 | it('should use "css" as default style language', () => { 185 | expect(generator.config.get('style')).to.equal('css'); 186 | }); 187 | 188 | it('should use "css modules" per default', () => { 189 | expect(generator.config.get('cssmodules')).to.be.true; 190 | }); 191 | 192 | it('should enable "PostCSS"', () => { 193 | expect(generator.config.get('postcss')).to.equal(true); 194 | }); 195 | }); 196 | 197 | describe('#createFiles', () => { 198 | 199 | it('should generate dot files', () => { 200 | 201 | assert.file([ 202 | '.babelrc', 203 | '.editorconfig', 204 | '.eslintrc', 205 | '.gitignore', 206 | '.yo-rc.json' 207 | ]); 208 | }); 209 | 210 | it('should generate project configuration files', () => { 211 | 212 | assert.file([ 213 | 'package.json' 214 | ]); 215 | }); 216 | 217 | it('should generate the webpack configuration', () => { 218 | 219 | assert.file([ 220 | 'conf/webpack/Base.js', 221 | 'conf/webpack/Dev.js', 222 | 'conf/webpack/Dist.js', 223 | 'conf/webpack/Test.js', 224 | 'webpack.config.js' 225 | ]); 226 | }); 227 | 228 | it('should insert the postcss loader into the style pipes', () => { 229 | assert.fileContent('conf/webpack/Base.js', `{ 230 | loader: 'css-loader', 231 | query: cssModulesQuery 232 | }, 233 | { loader: 'postcss-loader' }`); 234 | assert.fileContent('conf/webpack/Base.js', `{ loader: 'css-loader' }, 235 | { loader: 'postcss-loader' }`); 236 | assert.fileContent('conf/webpack/Base.js', `{ 237 | loader: 'css-loader', 238 | query: cssModulesQuery 239 | }, 240 | { loader: 'postcss-loader' }, 241 | { loader: 'sass-loader' }`); 242 | assert.fileContent('conf/webpack/Base.js', `{ loader: 'css-loader' }, 243 | { loader: 'postcss-loader' }, 244 | { loader: 'sass-loader' }`); 245 | assert.fileContent('conf/webpack/Base.js', `{ 246 | loader: 'css-loader', 247 | query: cssModulesQuery 248 | }, 249 | { loader: 'postcss-loader' }, 250 | { loader: 'less-loader' }`); 251 | assert.fileContent('conf/webpack/Base.js', `{ loader: 'css-loader' }, 252 | { loader: 'postcss-loader' }, 253 | { loader: 'less-loader' }`); 254 | assert.fileContent('conf/webpack/Base.js', `{ 255 | loader: 'css-loader', 256 | query: cssModulesQuery 257 | }, 258 | { loader: 'postcss-loader' }, 259 | { loader: 'stylus-loader' }`); 260 | assert.fileContent('conf/webpack/Base.js', `{ loader: 'css-loader' }, 261 | { loader: 'postcss-loader' }, 262 | { loader: 'stylus-loader' }`); 263 | }); 264 | 265 | it('should append the postcss function to the base config', () => { 266 | 267 | assert.fileContent('conf/webpack/Base.js', 'postcss: function () {'); 268 | }); 269 | 270 | it('should generate required source files', () => { 271 | 272 | assert.file([ 273 | 'src/actions/README.md', 274 | 'src/client.js', 275 | 'src/components/App.js', 276 | 'src/components/app.css', 277 | 'src/static/favicon.ico', 278 | 'src/images/yeoman.png', 279 | 'src/index.html', 280 | 'src/sources/README.md', 281 | 'src/stores/README.md' 282 | ]); 283 | }); 284 | 285 | it('should generate test configuration and basic tests', () => { 286 | 287 | assert.file([ 288 | 'karma.conf.js', 289 | 'test/components/AppTest.js', 290 | 'test/config/ConfigTest.js', 291 | 'test/loadtests.js', 292 | 'test/.eslintrc' 293 | ]); 294 | }); 295 | }); 296 | }); 297 | -------------------------------------------------------------------------------- /test/generators/component/indexTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let path = require('path'); 3 | let assert = require('yeoman-assert'); 4 | let helpers = require('yeoman-test'); 5 | 6 | 7 | describe('react-webpack:component', () => { 8 | 9 | const generatorComponent = path.join(__dirname, '../../../generators/component'); 10 | 11 | describe('when using version 3 of the generator', () => { 12 | 13 | // List of available style types. Please add a line that says 14 | // testComponentWithStyle(styleTypes.KEY); to the bottom of the file 15 | // to run all unit tests for this filetype. 16 | const styleTypes = { 17 | css: { 18 | type: 'css', 19 | fileName: 'src/styles/Mycomponent.css', 20 | expandedFileName: 'src/styles/my/littleSpecial/Test.css', 21 | assertions: { 22 | componentImport: 'require(\'styles/Mycomponent.css\');', 23 | styleContent: '.mycomponent-component' 24 | } 25 | }, 26 | sass: { 27 | type: 'sass', 28 | fileName: 'src/styles/Mycomponent.sass', 29 | expandedFileName: 'src/styles/my/littleSpecial/Test.sass', 30 | assertions: { 31 | componentImport: 'require(\'styles/Mycomponent.sass\');', 32 | styleContent: '.mycomponent-component' 33 | } 34 | }, 35 | scss: { 36 | type: 'scss', 37 | fileName: 'src/styles/Mycomponent.scss', 38 | expandedFileName: 'src/styles/my/littleSpecial/Test.scss', 39 | assertions: { 40 | componentImport: 'require(\'styles/Mycomponent.scss\');', 41 | styleContent: '.mycomponent-component' 42 | } 43 | }, 44 | less: { 45 | type: 'less', 46 | fileName: 'src/styles/Mycomponent.less', 47 | expandedFileName: 'src/styles/my/littleSpecial/Test.less', 48 | assertions: { 49 | componentImport: 'require(\'styles/Mycomponent.less\');', 50 | styleContent: '.mycomponent-component' 51 | } 52 | }, 53 | stylus: { 54 | type: 'stylus', 55 | fileName: 'src/styles/Mycomponent.styl', 56 | expandedFileName: 'src/styles/my/littleSpecial/Test.styl', 57 | assertions: { 58 | componentImport: 'require(\'styles/Mycomponent.styl\');', 59 | styleContent: '.mycomponent-component' 60 | } 61 | } 62 | }; 63 | 64 | /** 65 | * Return a newly generated component with given name and style 66 | * @param {String} name Name of the component 67 | * @param {String} styleType Styling language to use 68 | * @param {Object} options Options to use for the generator 69 | * @param {Function} callback Test callback to run 70 | */ 71 | function createGeneratedComponent(name, styleType, options, callback) { 72 | helpers.run(generatorComponent) 73 | .withArguments([name]) 74 | .withOptions(options) 75 | .on('ready', (instance) => { 76 | instance.config.set('style', styleType); 77 | instance.config.set('generatedWithVersion', 3); 78 | }) 79 | .on('end', callback); 80 | } 81 | 82 | /** 83 | * Test a component with styling applied 84 | * @param {Object} style The style to apply (see styleTypes above) 85 | * @param {Object} options Options to use [optional] 86 | */ 87 | function testComponentWithStyle(style, options) { 88 | 89 | // Make sure we always have options 90 | if(!options) { 91 | options = {}; 92 | } 93 | 94 | describe(`when using style type "${style.type}"`, () => { 95 | 96 | describe('when writing is called', () => { 97 | 98 | it(`should create the react component, its ${style.type}-stylesheet and test file`, (done) => { 99 | createGeneratedComponent('mycomponent', style.type, options, () => { 100 | 101 | assert.file([ 102 | 'src/components/MycomponentComponent.js', 103 | style.fileName, 104 | 'test/components/MycomponentComponentTest.js' 105 | ]); 106 | done(); 107 | }); 108 | }); 109 | }); 110 | 111 | describe('when creating a component', () => { 112 | 113 | it('should always import REACT', (done) => { 114 | createGeneratedComponent('mycomponent', style.type, options, () => { 115 | assert.fileContent('src/components/MycomponentComponent.js', 'import React from \'react\';'); 116 | done(); 117 | }); 118 | }); 119 | 120 | it(`should require the created ${style.type} file`, (done) => { 121 | createGeneratedComponent('mycomponent', style.type, options, () => { 122 | assert.fileContent('src/components/MycomponentComponent.js', style.assertions.componentImport); 123 | done(); 124 | }); 125 | }); 126 | 127 | it('should have its displayName set per default', (done) => { 128 | createGeneratedComponent('mycomponent', style.type, options, () => { 129 | assert.fileContent('src/components/MycomponentComponent.js', 'displayName = \'MycomponentComponent\';'); 130 | done(); 131 | }); 132 | }); 133 | 134 | it('should export the created component', (done) => { 135 | createGeneratedComponent('mycomponent', style.type, options, () => { 136 | assert.fileContent('src/components/MycomponentComponent.js', 'export default MycomponentComponent'); 137 | done(); 138 | }); 139 | }); 140 | 141 | it('should be possible to create components in a subfolder', (done) => { 142 | createGeneratedComponent('my/little !special/test', style.type, options, () => { 143 | 144 | assert.file([ 145 | 'src/components/my/littleSpecial/TestComponent.js', 146 | style.expandedFileName, 147 | 'test/components/my/littleSpecial/TestComponentTest.js' 148 | ]); 149 | done(); 150 | }); 151 | }); 152 | 153 | it(`should add the components ${style.type} class to the created stylesheet`, (done) => { 154 | createGeneratedComponent('mycomponent', style.type, options, () => { 155 | assert.fileContent(style.fileName, style.assertions.styleContent); 156 | done(); 157 | }); 158 | }); 159 | 160 | it('should create a unit test that imports the generated component', (done) => { 161 | createGeneratedComponent('mycomponent', style.type, options, () => { 162 | assert.fileContent('test/components/MycomponentComponentTest.js', 'import MycomponentComponent from \'components/MycomponentComponent.js\';'); 163 | done(); 164 | }); 165 | }); 166 | }); 167 | }); 168 | } 169 | 170 | // Run all tests for all available style types. 171 | // Stateless components will also be tested! 172 | for(const style in styleTypes) { 173 | testComponentWithStyle(styleTypes[style]); 174 | testComponentWithStyle(styleTypes[style], { stateless: true }); 175 | } 176 | }); 177 | 178 | describe('when using version 4 of the generator', () => { 179 | 180 | const cssModSuffix = (useCssModules) => useCssModules ? '.cssmodule' : ''; 181 | const importAssertion = (useCssModules, ext) => useCssModules 182 | ? `import styles from './mycomponent.cssmodule.${ext}';` 183 | : `import './mycomponent.${ext}';` 184 | ; 185 | 186 | // List of available style types. Please add a line that says 187 | // testComponentWithStyle(styleTypes.KEY); to the bottom of the file 188 | // to run all unit tests for this filetype. 189 | const styleTypes = (useCssModules) => ({ 190 | css: { 191 | type: 'css', 192 | fileName: `src/components/mycomponent${cssModSuffix(useCssModules)}.css`, 193 | expandedFileName: `src/components/my/littleSpecial/test${cssModSuffix(useCssModules)}.css`, 194 | assertions: { 195 | componentImport: importAssertion(useCssModules, 'css'), 196 | styleContent: '.mycomponent-component' 197 | } 198 | }, 199 | sass: { 200 | type: 'sass', 201 | fileName: `src/components/mycomponent${cssModSuffix(useCssModules)}.sass`, 202 | expandedFileName: `src/components/my/littleSpecial/test${cssModSuffix(useCssModules)}.sass`, 203 | assertions: { 204 | componentImport: importAssertion(useCssModules, 'sass'), 205 | styleContent: '.mycomponent-component' 206 | } 207 | }, 208 | scss: { 209 | type: 'scss', 210 | fileName: `src/components/mycomponent${cssModSuffix(useCssModules)}.scss`, 211 | expandedFileName: `src/components/my/littleSpecial/test${cssModSuffix(useCssModules)}.scss`, 212 | assertions: { 213 | componentImport: importAssertion(useCssModules, 'scss'), 214 | styleContent: '.mycomponent-component' 215 | } 216 | }, 217 | less: { 218 | type: 'less', 219 | fileName: `src/components/mycomponent${cssModSuffix(useCssModules)}.less`, 220 | expandedFileName: `src/components/my/littleSpecial/test${cssModSuffix(useCssModules)}.less`, 221 | assertions: { 222 | componentImport: importAssertion(useCssModules, 'less'), 223 | styleContent: '.mycomponent-component' 224 | } 225 | }, 226 | stylus: { 227 | type: 'stylus', 228 | fileName: `src/components/mycomponent${cssModSuffix(useCssModules)}.styl`, 229 | expandedFileName: `src/components/my/littleSpecial/test${cssModSuffix(useCssModules)}.styl`, 230 | assertions: { 231 | componentImport: importAssertion(useCssModules, 'styl'), 232 | styleContent: '.mycomponent-component' 233 | } 234 | } 235 | }); 236 | 237 | /** 238 | * Return a newly generated component with given name and style 239 | * @param {String} name Name of the component 240 | * @param {String} styleType Styling language to use 241 | * @param {Object} options Options to use for the generator 242 | * @param {boolean} useCssModules useCssModules indicate whether to test with cssmodules enabled 243 | * @param {Function} callback Test callback to run 244 | */ 245 | function createGeneratedComponent(name, styleType, options, useCssModules, callback) { 246 | helpers.run(generatorComponent) 247 | .withArguments([name]) 248 | .withOptions(options) 249 | .on('ready', (instance) => { 250 | instance.config.set('style', styleType); 251 | instance.config.set('cssmodules', useCssModules); 252 | instance.config.set('generatedWithVersion', 4); 253 | }) 254 | .on('end', callback); 255 | } 256 | 257 | /** 258 | * Test a component with styling applied 259 | * @param {Object} style The style to apply (see styleTypes above) 260 | * @param {Object} options Options to use [optional] 261 | * @param {boolean} useCssModules indicate whether to test with cssmodules enabled 262 | */ 263 | function testComponentWithStyle(style, options, useCssModules) { 264 | 265 | // Make sure we always have options 266 | if(!options) { 267 | options = {}; 268 | } 269 | 270 | const isStateless = options.stateless || false; 271 | const isPure = options.pure || false; 272 | const componentBase = isPure ? 'React.PureComponent' : 'React.Component'; 273 | 274 | describe(`when using style type "${style.type}" with nostyle = false, pure rendering = ${isPure} and cssmodules = ${useCssModules}`, () => { 275 | 276 | describe('when writing is called', () => { 277 | 278 | it(`should create the react component, its ${style.type}-stylesheet and test file`, (done) => { 279 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 280 | 281 | assert.file([ 282 | 'src/components/Mycomponent.js', 283 | style.fileName, 284 | 'test/components/MycomponentTest.js' 285 | ]); 286 | done(); 287 | }); 288 | }); 289 | }); 290 | 291 | describe('when creating a component', () => { 292 | 293 | it('should always import REACT', (done) => { 294 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 295 | assert.fileContent('src/components/Mycomponent.js', 'import React from \'react\';'); 296 | done(); 297 | }); 298 | }); 299 | 300 | it(`should require the created ${style.type} file`, (done) => { 301 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 302 | assert.fileContent('src/components/Mycomponent.js', style.assertions.componentImport); 303 | done(); 304 | }); 305 | }); 306 | 307 | // Only run this if we are not in stateless mode 308 | if(!isStateless) { 309 | it(`should extend ${componentBase}`, (done) => { 310 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 311 | assert.fileContent( 312 | 'src/components/Mycomponent.js', 313 | `class Mycomponent extends ${componentBase}` 314 | ); 315 | done(); 316 | }); 317 | }); 318 | } 319 | 320 | it('should have its displayName set per default', (done) => { 321 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 322 | assert.fileContent('src/components/Mycomponent.js', 'Mycomponent.displayName = \'Mycomponent\';'); 323 | done(); 324 | }); 325 | }); 326 | 327 | it('should export the created component', (done) => { 328 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 329 | 330 | let exportAssertion; 331 | if(useCssModules) { 332 | exportAssertion = 'export default cssmodules(Mycomponent, styles);'; 333 | } else { 334 | exportAssertion = 'export default Mycomponent;'; 335 | } 336 | assert.fileContent('src/components/Mycomponent.js', exportAssertion); 337 | done(); 338 | }); 339 | }); 340 | 341 | it('should be possible to create components in a subfolder', (done) => { 342 | createGeneratedComponent('my/little !special/test', style.type, options, useCssModules, () => { 343 | 344 | assert.file([ 345 | 'src/components/my/littleSpecial/Test.js', 346 | style.expandedFileName, 347 | 'test/components/my/littleSpecial/TestTest.js' 348 | ]); 349 | done(); 350 | }); 351 | }); 352 | 353 | it(`should add the components ${style.type} class to the created stylesheet`, (done) => { 354 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 355 | assert.fileContent(style.fileName, style.assertions.styleContent); 356 | done(); 357 | }); 358 | }); 359 | 360 | it('should create a unit test that imports the generated component', (done) => { 361 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 362 | assert.fileContent('test/components/MycomponentTest.js', 'import Mycomponent from \'components/Mycomponent.js\';'); 363 | done(); 364 | }); 365 | }); 366 | }); 367 | }); 368 | } 369 | 370 | /** 371 | * Test a component with styling applied 372 | * @param {Object} style The style to apply (see styleTypes above) 373 | * @param {Object} options Options to use [optional] 374 | * @param {boolean} useCssModules indicate whether to test with cssmodules enabled 375 | */ 376 | function testComponentWithoutStyle(style, options, useCssModules) { 377 | 378 | // Make sure we always have options 379 | if(!options) { 380 | options = {}; 381 | } 382 | 383 | describe(`when using style type "${style.type}" with nostyle = true and cssmodules = ${useCssModules}`, () => { 384 | 385 | describe('when writing is called', () => { 386 | 387 | it('should create the react component and test file', (done) => { 388 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 389 | 390 | assert.file([ 391 | 'src/components/Mycomponent.js', 392 | 'test/components/MycomponentTest.js' 393 | ]); 394 | done(); 395 | }); 396 | }); 397 | }); 398 | 399 | describe('when creating a component', () => { 400 | 401 | it('should always import REACT', (done) => { 402 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 403 | assert.fileContent('src/components/Mycomponent.js', 'import React from \'react\';'); 404 | done(); 405 | }); 406 | }); 407 | 408 | it('should have its displayName set per default', (done) => { 409 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 410 | assert.fileContent('src/components/Mycomponent.js', 'Mycomponent.displayName = \'Mycomponent\';'); 411 | done(); 412 | }); 413 | }); 414 | 415 | it('should export the created component', (done) => { 416 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 417 | assert.fileContent('src/components/Mycomponent.js', 'export default Mycomponent'); 418 | done(); 419 | }); 420 | }); 421 | 422 | it('should be possible to create components in a subfolder', (done) => { 423 | createGeneratedComponent('my/little !special/test', style.type, options, useCssModules, () => { 424 | 425 | assert.file([ 426 | 'src/components/my/littleSpecial/Test.js', 427 | 'test/components/my/littleSpecial/TestTest.js' 428 | ]); 429 | done(); 430 | }); 431 | }); 432 | 433 | it('should create a unit test that imports the generated component', (done) => { 434 | createGeneratedComponent('mycomponent', style.type, options, useCssModules, () => { 435 | assert.fileContent('test/components/MycomponentTest.js', 'import Mycomponent from \'components/Mycomponent.js\';'); 436 | done(); 437 | }); 438 | }); 439 | }); 440 | }); 441 | } 442 | 443 | // Run all tests for all available style types. 444 | // Stateless components will also be tested! 445 | for(const style in styleTypes(true)) { 446 | testComponentWithStyle(styleTypes(true)[style], {}, true); 447 | testComponentWithStyle(styleTypes(true)[style], { pure: true }, true); 448 | testComponentWithStyle(styleTypes(true)[style], { pure: false }, true); 449 | testComponentWithStyle(styleTypes(true)[style], { stateless: true }, true); 450 | testComponentWithoutStyle(styleTypes(true)[style], { nostyle: true }, true); 451 | } 452 | for(const style in styleTypes(false)) { 453 | testComponentWithStyle(styleTypes(false)[style], {}, false); 454 | testComponentWithStyle(styleTypes(false)[style], { pure: true }, false); 455 | testComponentWithStyle(styleTypes(false)[style], { pure: false }, false); 456 | testComponentWithStyle(styleTypes(false)[style], { stateless: true }, false); 457 | testComponentWithoutStyle(styleTypes(false)[style], { nostyle: true }, false); 458 | } 459 | }); 460 | }); 461 | -------------------------------------------------------------------------------- /test/generators/setup-env/assets/moduleIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const foo = require('path'); 4 | 5 | module.exports = { 6 | foo 7 | }; 8 | -------------------------------------------------------------------------------- /test/generators/setup-env/setupEnvTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const acorn = require('acorn'); 4 | const assert = require('yeoman-assert'); 5 | const fs = require('fs-extra'); 6 | const helpers = require('yeoman-test'); 7 | const path = require('path'); 8 | const walk = require('acorn/dist/walk'); 9 | 10 | 11 | /** 12 | * Returns absolute path to (sub-)generator with {@code name}. 13 | * @param {string} name 14 | */ 15 | const genpath = (name) => 16 | path.join(__dirname, '../../../generators', name); 17 | 18 | /** 19 | * A mocked generator config object. 20 | * @type {{appName: string, style: string, cssmodules: boolean, postcss: boolean, generatedWithVersion: number}} 21 | */ 22 | const cfg = { 23 | appName: 'testCfg', 24 | style: 'css', 25 | cssmodules: false, 26 | postcss: false, 27 | generatedWithVersion: 4 28 | }; 29 | 30 | 31 | describe('react-webpack:setup-env', function () { 32 | 33 | describe('react-webpack:setup-env foobar', function () { 34 | before(function () { 35 | return helpers 36 | .run(genpath('setup-env')) 37 | .withArguments(['foobar']) 38 | .withLocalConfig(cfg) 39 | .inTmpDir(function (dir) { 40 | fs.copySync( 41 | path.join(__dirname, 'assets/moduleIndex.js'), 42 | path.join(dir, 'conf/webpack/index.js') 43 | ); 44 | }) 45 | .toPromise(); 46 | }); 47 | 48 | it('creates env files', function () { 49 | assert.file(['conf/webpack/Foobar.js']); 50 | assert.file(['src/config/foobar.js']); 51 | }); 52 | 53 | it('requires the new env in conf/webpack/index.js', function () { 54 | assert.fileContent( 55 | 'conf/webpack/index.js', 56 | /const foobar = require\('\.\/Foobar'\)/ 57 | ); 58 | }); 59 | 60 | it('exports the new env from conf/webpack/index.js', function () { 61 | const fileStr = fs.readFileSync('conf/webpack/index.js').toString(); 62 | const ast = acorn.parse(fileStr); 63 | 64 | let found = false; 65 | walk.simple(ast, { 66 | 'Property': (node) => { 67 | if (node.key.name === 'foobar' && node.value.name === 'foobar') { 68 | found = true; 69 | } 70 | } 71 | }); 72 | 73 | assert(found, 'Did not find a key and value of `foobar` on the module.exports AST node'); 74 | }); 75 | }); 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --recursive 3 | -------------------------------------------------------------------------------- /test/utils/configTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let expect = require('chai').expect; 3 | 4 | let utils = require('../../utils/config'); 5 | let originalSettings = require('../../utils/configopts.json'); 6 | 7 | describe('Utilities:Config', () => { 8 | 9 | describe('#getSetting', () => { 10 | 11 | it('should return "null" if the key could not be found', () => { 12 | expect(utils.getSetting('bogus')).to.be.null; 13 | }); 14 | 15 | it('should return a settings object if it exists', () => { 16 | 17 | let result = utils.getSetting('style'); 18 | expect(result).to.be.an.object; 19 | expect(result).to.deep.equal(originalSettings.style); 20 | }); 21 | }); 22 | 23 | describe('#getChoices', () => { 24 | 25 | it('should return "null" if the key could not be found', () => { 26 | expect(utils.getChoices('bogus')).to.be.null; 27 | }); 28 | 29 | it('should return an array of choices when queried correctly', () => { 30 | 31 | let result = utils.getChoices('style'); 32 | expect(result).to.be.an.array; 33 | expect(result).to.deep.equal(originalSettings.style.options); 34 | }); 35 | }); 36 | 37 | describe('#getChoiceByKey', () => { 38 | 39 | it('should return "null" if the key or the setting could not be found', () => { 40 | 41 | expect(utils.getChoiceByKey('bogus', 'unknown')).to.be.null; 42 | expect(utils.getChoiceByKey('style', 'unknown')).to.be.null; 43 | }); 44 | 45 | it('should return the configured object when it can be found', () => { 46 | 47 | expect(utils.getChoiceByKey('style', 'css')).to.equal(originalSettings.style.options[0]); 48 | expect(utils.getChoiceByKey('style', 'less')).to.equal(originalSettings.style.options[3]); 49 | }); 50 | }); 51 | 52 | describe('#getDefaultChoice', () => { 53 | 54 | it('should return "null" if the key could not be found', () => { 55 | expect(utils.getDefaultChoice('bogus')).to.be.null; 56 | }); 57 | 58 | it('should return the default choice when queried correctly', () => { 59 | 60 | let result = utils.getDefaultChoice('style'); 61 | expect(result).to.equal(originalSettings.style.default); 62 | }); 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /test/utils/yeomanTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let expect = require('chai').expect; 3 | let path = require('path'); 4 | 5 | let utils = require('../../utils/yeoman'); 6 | let baseDir = path.basename(process.cwd()); 7 | 8 | describe('Utilities:Yeoman', () => { 9 | 10 | describe('#getBaseDir', () => { 11 | 12 | it('should return the current run directory', () => { 13 | expect(utils.getBaseDir()).to.equal(baseDir); 14 | }); 15 | }); 16 | 17 | describe('#getCleanedPathName', () => { 18 | 19 | it('should return normalized paths', () => { 20 | 21 | let tests = { 22 | 'my/full œ!/path!': 'my/full/path', 23 | 'Test': 'test', 24 | 'I am a Test Component!': 'iAmATestComponent', 25 | 'A very\^specialChary!@componentName with !!!': 'aVerySpecialCharyComponentNameWith' 26 | }; 27 | 28 | for(let test in tests) { 29 | expect(utils.getCleanedPathName(test)).to.be.equal(tests[test]); 30 | expect(utils.getCleanedPathName(test, 'suffix')).to.be.equal(tests[test] + 'Suffix'); 31 | } 32 | }); 33 | }); 34 | 35 | describe('#getAppName', () => { 36 | 37 | it('should return a js friendly application name', () => { 38 | let result = utils.getAppName('this is a test using % special / chars!'); 39 | expect(result).to.be.equal('this-is-a-test-using-special-chars'); 40 | }); 41 | 42 | it('should use the current path for creating the appName if the argument is omitted', () => { 43 | 44 | let resultWithoutArgs = utils.getAppName(); 45 | let resultWithArgs = utils.getAppName(baseDir); 46 | 47 | expect(resultWithoutArgs).to.be.equal(resultWithArgs); 48 | }); 49 | }); 50 | 51 | describe('#getComponentStyleName', () => { 52 | 53 | it('should return a components css className', () => { 54 | 55 | let tests = { 56 | 'my/full œ!/path!': 'path-component', 57 | 'Test': 'test-component', 58 | 'I am a Test Component!': 'i-am-a-test-component-component', 59 | 'A very\^specialChary!@componentName with !!!': 'a-very-specialchary-componentname-with-component' 60 | }; 61 | 62 | for(let test in tests) { 63 | expect(utils.getComponentStyleName(test)).to.be.equal(tests[test]); 64 | } 65 | }); 66 | }); 67 | 68 | describe('#getAllSettingsFromComponentName', () => { 69 | 70 | describe('when the generator version is set to 4', () => { 71 | 72 | const expectionNamespaced = { 73 | style: { 74 | webpackPath: './test.cssmodule.css', 75 | path: 'src/components/my/component/', 76 | fileName: 'test.cssmodule.css', 77 | className: 'test-component', 78 | suffix: '.css' 79 | }, 80 | component: { 81 | webpackPath: 'components/my/component/Test.js', 82 | path: 'src/components/my/component/', 83 | fileName: 'Test.js', 84 | className: 'Test', 85 | classBase: 'React.Component', 86 | displayName: 'MyComponentTest', 87 | suffix: '.js' 88 | }, 89 | test: { 90 | path: 'test/components/my/component/', 91 | fileName: 'TestTest.js' 92 | } 93 | }; 94 | 95 | const expectionRoot = { 96 | style: { 97 | webpackPath: './test.cssmodule.css', 98 | path: 'src/components/', 99 | fileName: 'test.cssmodule.css', 100 | className: 'test-component', 101 | suffix: '.css' 102 | }, 103 | component: { 104 | webpackPath: 'components/Test.js', 105 | path: 'src/components/', 106 | fileName: 'Test.js', 107 | className: 'Test', 108 | classBase: 'React.Component', 109 | displayName: 'Test', 110 | suffix: '.js' 111 | }, 112 | test: { 113 | path: 'test/components/', 114 | fileName: 'TestTest.js' 115 | } 116 | }; 117 | 118 | it('should get all required information for component creation from the components name', () => { 119 | expect(utils.getAllSettingsFromComponentName('my/component/test', 'css', true, false, 4, '')).to.deep.equal(expectionNamespaced); 120 | }); 121 | 122 | it('should prepend a prefix to the style.className attribute', () => { 123 | const expectation = Object.assign({}, expectionNamespaced); 124 | expectation.style.className = 'myapp-test-component'; 125 | expect(utils.getAllSettingsFromComponentName('my/component/test', 'css', true, false, 4, 'myapp-')).to.deep.equal(expectation); 126 | }); 127 | 128 | it('should build path information wo/ two slashes when dealing with a non-namespaced component', () => { 129 | expect(utils.getAllSettingsFromComponentName('test', 'css', true, false, 4, '')).to.deep.equal(expectionRoot); 130 | }); 131 | }); 132 | 133 | describe('when the generator version is set to 3 (or not set at all)', () => { 134 | 135 | const expection = { 136 | style: { 137 | webpackPath: 'styles/my/component/Test.css', 138 | path: 'src/styles/my/component/', 139 | fileName: 'Test.css', 140 | className: 'test-component', 141 | suffix: '.css' 142 | }, 143 | component: { 144 | webpackPath: 'components/my/component/TestComponent.js', 145 | path: 'src/components/my/component/', 146 | fileName: 'TestComponent.js', 147 | className: 'TestComponent', 148 | displayName: 'MyComponentTestComponent', 149 | suffix: '.js' 150 | }, 151 | test: { 152 | path: 'test/components/my/component/', 153 | fileName: 'TestComponentTest.js' 154 | } 155 | }; 156 | 157 | it('should get all required information for component creation from the components name', () => { 158 | expect(utils.getAllSettingsFromComponentName('my/component/test')).to.deep.equal(expection); 159 | expect(utils.getAllSettingsFromComponentName('my/component/test', 'css', false, 3)).to.deep.equal(expection); 160 | }); 161 | }); 162 | }); 163 | 164 | describe('#getDestinationPath', () => { 165 | 166 | it('should return the correct filesystem path for all components', () => { 167 | 168 | expect(utils.getDestinationPath('test', 'action', 'Actions')).to.equal('src/actions/TestActions.js'); 169 | expect(utils.getDestinationPath('subfolder/test', 'action', 'Actions')).to.equal('src/actions/subfolder/TestActions.js'); 170 | 171 | expect(utils.getDestinationPath('test', 'source', 'Source')).to.equal('src/sources/TestSource.js'); 172 | expect(utils.getDestinationPath('subfolder/test', 'source', 'Source')).to.equal('src/sources/subfolder/TestSource.js'); 173 | 174 | expect(utils.getDestinationPath('test', 'store', 'Store')).to.equal('src/stores/TestStore.js'); 175 | expect(utils.getDestinationPath('subfolder/test', 'store', 'Store')).to.equal('src/stores/subfolder/TestStore.js'); 176 | }); 177 | }); 178 | 179 | describe('#getDestinationClassName', () => { 180 | 181 | it('should return the correct javascript class name for the given paths', () => { 182 | expect(utils.getDestinationClassName('test', 'action', 'Actions')).to.equal('TestActions'); 183 | expect(utils.getDestinationClassName('test', 'source', 'Source')).to.equal('TestSource'); 184 | expect(utils.getDestinationClassName('test', 'store', 'Store')).to.equal('TestStore'); 185 | }); 186 | }); 187 | 188 | describe('#getComponentTemplateName', () => { 189 | 190 | it('should return a stateless component with styles if stateless is set to true and useStyles set to true', () => { 191 | expect(utils.getComponentTemplateName(true, true)).to.equal('StatelessWithStyles.js'); 192 | }); 193 | 194 | it('should return a stateless component without styles if stateless is set to true and useStyles set to false', () => { 195 | expect(utils.getComponentTemplateName(true, false)).to.equal('StatelessNoStyles.js'); 196 | }); 197 | 198 | it('should return a statefull component with styles if stateless is set to false and useStyles set to true', () => { 199 | expect(utils.getComponentTemplateName(false, true)).to.equal('StatefulWithStyles.js'); 200 | }); 201 | 202 | it('should return a statefull component without styles if stateless is set to false and useStyles set to false', () => { 203 | expect(utils.getComponentTemplateName(false, false)).to.equal('StatefulNoStyles.js'); 204 | }); 205 | 206 | it('should return a statefull component with styles and cssmodules if stateless is set to false and useCssModules set to true', () => { 207 | expect(utils.getComponentTemplateName(false, true, true)).to.equal('StatefulCssModules.js'); 208 | }); 209 | }); 210 | }); 211 | -------------------------------------------------------------------------------- /utils/all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('./config'); 4 | const constants = require('./constants'); 5 | const yeoman = require('./yeoman'); 6 | 7 | module.exports = { 8 | config, 9 | constants, 10 | yeoman 11 | }; 12 | -------------------------------------------------------------------------------- /utils/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let opts = require('./configopts.json'); 3 | 4 | /** 5 | * Get a setting 6 | * @param {String} setting 7 | * @return {Mixed} setting or null if not found 8 | */ 9 | let getSetting = (setting) => { 10 | return opts[setting] !== undefined ? opts[setting] : null; 11 | } 12 | 13 | /** 14 | * Get choices for a given setting 15 | * @param {String} setting 16 | * @return {Mixed} Result or null if nothing was found 17 | */ 18 | let getChoices = function getChoices(setting) { 19 | 20 | let config = getSetting(setting); 21 | return config && Array.isArray(config.options) ? config.options : null; 22 | } 23 | 24 | /** 25 | * Get the wanted choice by key 26 | * @param {String} setting 27 | * @param {String} key 28 | * @return {Object} 29 | */ 30 | let getChoiceByKey = (setting, key) => { 31 | 32 | let choices = getChoices(setting); 33 | if(!choices) { 34 | return null; 35 | } 36 | 37 | let result = null; 38 | 39 | for(let choice of choices) { 40 | 41 | if(choice.name === key) { 42 | result = choice; 43 | break; 44 | } 45 | } 46 | 47 | return result; 48 | } 49 | 50 | /** 51 | * Get the default choice for a config setting 52 | * @param {String} setting 53 | * @return {Mixed} 54 | */ 55 | let getDefaultChoice = (setting) => { 56 | let config = getSetting(setting); 57 | return config && config.default !== undefined && config.default.length > 0 ? config.default : null; 58 | } 59 | 60 | module.exports = { 61 | getSetting, 62 | getChoices, 63 | getChoiceByKey, 64 | getDefaultChoice 65 | }; 66 | -------------------------------------------------------------------------------- /utils/configopts.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": { 3 | "options": [ 4 | { 5 | "name": "base", 6 | "path": "src" 7 | }, 8 | { 9 | "name": "action", 10 | "path": "src/actions" 11 | }, 12 | { 13 | "name": "component", 14 | "path": "src/components" 15 | }, 16 | { 17 | "name": "image", 18 | "path": "src/images" 19 | }, 20 | { 21 | "name": "source", 22 | "path": "src/sources" 23 | }, 24 | { 25 | "name": "store", 26 | "path": "src/stores" 27 | }, 28 | { 29 | "name": "style", 30 | "path": "src/styles" 31 | }, 32 | { 33 | "name": "test", 34 | "path": "test" 35 | }, 36 | { 37 | "name": "dist", 38 | "path": "dist" 39 | } 40 | ] 41 | }, 42 | "postcss": { 43 | "options": [ 44 | { 45 | "name": "postcss", 46 | "value": "postcss", 47 | "packages": [ 48 | { "name": "postcss", "version": "^5.0.21" }, 49 | { "name": "postcss-loader", "version": "^0.9.1" } 50 | ] 51 | } 52 | ] 53 | }, 54 | "cssmodules": { 55 | "options": [ 56 | { 57 | "name": "cssmodules", 58 | "value": "cssmodules", 59 | "packages": [ 60 | { "name": "react-css-modules", "version": "^3.7.10" } 61 | ] 62 | } 63 | ] 64 | }, 65 | "style": { 66 | "options": [ 67 | { 68 | "name": "css", 69 | "value": "css", 70 | "suffix": ".css" 71 | }, 72 | { 73 | "name": "sass", 74 | "value": "sass", 75 | "suffix": ".sass", 76 | "packages": [ 77 | { "name": "sass-loader", "version": "^3.2.0" }, 78 | { "name": "node-sass", "version": "^3.7.0" } 79 | ] 80 | }, 81 | { 82 | "name": "scss", 83 | "value": "scss", 84 | "suffix": ".scss", 85 | "packages": [ 86 | { "name": "sass-loader", "version": "^3.2.0" }, 87 | { "name": "node-sass", "version": "^3.7.0" } 88 | ] 89 | }, 90 | { 91 | "name": "less", 92 | "value": "less", 93 | "suffix": ".less", 94 | "packages": [ 95 | { "name": "less-loader", "version": "^2.2.3" }, 96 | { "name": "less", "version": "^2.7.1" } 97 | ] 98 | }, 99 | { 100 | "name": "stylus", 101 | "value": "stylus", 102 | "suffix": ".styl", 103 | "packages": [ 104 | { "name": "stylus-loader", "version": "^2.1.0" }, 105 | { "name": "stylus", "version": "^0.54.5" }, 106 | { "name": "nib", "version": "~1.1.0" } 107 | ] 108 | } 109 | ], 110 | "default": "css" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /utils/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * List of supported generator versions. 5 | * @type {number[]} 6 | */ 7 | const SUPPORTED_GEN_VERSIONS = [3, 4]; 8 | 9 | 10 | /** 11 | * ENUM of supported component types. 12 | * @type {{STATEFUL: string, STATELESS: string}} 13 | */ 14 | const COMP_TYPES = { 15 | STATEFUL: 'Stateful', 16 | STATELESS: 'Stateless' 17 | }; 18 | 19 | 20 | /** 21 | * ENUM of supported style types. 22 | * @type {{WITH_STYLES: string, WITH_CSSMODULES: string, NO_STYLES: string}} 23 | */ 24 | const STYLE_TYPES = { 25 | WITH_STYLES: 'WithStyles', 26 | WITH_CSSMODULES: 'CssModules', 27 | NO_STYLES: 'NoStyles' 28 | }; 29 | 30 | 31 | module.exports = { 32 | SUPPORTED_GEN_VERSIONS, 33 | COMP_TYPES, 34 | STYLE_TYPES 35 | }; 36 | -------------------------------------------------------------------------------- /utils/yeoman.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const configUtils = require('./config'); 5 | const _ = require('underscore.string'); 6 | const C = require('./constants'); 7 | 8 | // Needed directory paths 9 | const baseName = path.basename(process.cwd()); 10 | 11 | /** 12 | * Get the base directory 13 | * @return {String} 14 | */ 15 | let getBaseDir = () => { 16 | return baseName; 17 | }; 18 | 19 | /** 20 | * Get all settings (paths and the like) from components name 21 | * @param {String} componentName The components name 22 | * @param {String} style Style language to use [optional] 23 | * @param {boolean} useCssModules Flag indicating whether to use css modules 24 | * @param {Boolean} isPure Use a pure component? [optional] 25 | * @param {String|Number} generatorVersion The version of the generator [optional] 26 | * @param {String} cssClsPrefix Prefix to prepended to the component class name 27 | * @return {Object} Component settings 28 | */ 29 | let getAllSettingsFromComponentName = (componentName, style, useCssModules, isPure, generatorVersion, cssClsPrefix) => { 30 | 31 | // Use css per default 32 | if(!style) { 33 | style = 'css'; 34 | } 35 | 36 | // Use version 3 fallback as default for projects 37 | if(!generatorVersion) { 38 | generatorVersion = 3; 39 | } 40 | 41 | // Clean up the path and pull it to parts 42 | let cleanedPaths = getCleanedPathName(componentName); 43 | let componentParts = cleanedPaths.split('/'); 44 | let componentBaseName = _.capitalize(componentParts.pop()); 45 | let componentPartPath = componentParts.join('/'); 46 | 47 | // Get the components displayName property 48 | let componentFullName = _.classify(_.replaceAll(componentName, '/', '_')); 49 | 50 | // Configure Styles 51 | let stylePaths = configUtils.getChoiceByKey('path', 'style'); 52 | let styleSettings = configUtils.getChoiceByKey('style', style); 53 | 54 | // Configure components 55 | let componentPath = configUtils.getChoiceByKey('path', 'component'); 56 | 57 | // Configure tests 58 | let testPath = configUtils.getChoiceByKey('path', 'test'); 59 | 60 | let settings; 61 | 62 | switch(generatorVersion) { 63 | 64 | case 4: 65 | settings = { 66 | style: { 67 | webpackPath: `./${componentBaseName.toLowerCase()}${useCssModules ? '.cssmodule' : ''}${styleSettings.suffix}`, 68 | path: path.normalize(`${componentPath.path}/${componentPartPath}/`), 69 | fileName: `${componentBaseName.toLowerCase()}${useCssModules ? '.cssmodule' : ''}${styleSettings.suffix}`, 70 | className: getComponentStyleName(cssClsPrefix + componentBaseName), 71 | suffix: styleSettings.suffix 72 | }, 73 | component: { 74 | webpackPath: path.normalize(`components/${componentPartPath}/${componentBaseName}.js`), 75 | path: path.normalize(`${componentPath.path}/${componentPartPath}/`), 76 | fileName: `${componentBaseName}.js`, 77 | className: `${componentBaseName}`, 78 | classBase: isPure ? 'React.PureComponent' : 'React.Component', 79 | displayName: `${componentFullName}`, 80 | suffix: '.js' 81 | }, 82 | test: { 83 | path: path.normalize(`${testPath.path}/components/${componentPartPath}/`), 84 | fileName: `${componentBaseName}Test.js` 85 | } 86 | }; 87 | break; 88 | 89 | // Use version 3 style for the defaults and fallback 90 | // @deprecated 91 | case 3: 92 | default: 93 | settings = { 94 | style: { 95 | webpackPath: path.normalize(`styles/${componentPartPath}/${componentBaseName}${styleSettings.suffix}`), 96 | path: path.normalize(`${stylePaths.path}/${componentPartPath}/`), 97 | fileName: `${componentBaseName}${styleSettings.suffix}`, 98 | className: getComponentStyleName(componentBaseName), 99 | suffix: styleSettings.suffix 100 | }, 101 | component: { 102 | webpackPath: path.normalize(`components/${componentPartPath}/${componentBaseName}Component.js`), 103 | path: path.normalize(`${componentPath.path}/${componentPartPath}/`), 104 | fileName: `${componentBaseName}Component.js`, 105 | className: `${componentBaseName}Component`, 106 | displayName: `${componentFullName}Component`, 107 | suffix: '.js' 108 | }, 109 | test: { 110 | path: path.normalize(`${testPath.path}/components/${componentPartPath}/`), 111 | fileName: `${componentBaseName}ComponentTest.js` 112 | } 113 | }; 114 | break; 115 | } 116 | 117 | return settings; 118 | }; 119 | 120 | /** 121 | * Get a cleaned path name for a given path 122 | * @param {String} path 123 | * @param {String} suffix [optional] 124 | * @return {String} 125 | */ 126 | let getCleanedPathName = (path, suffix) => { 127 | 128 | if(!suffix) { 129 | suffix = ''; 130 | } 131 | 132 | // If we have filesystem separators, use them to build the full path 133 | let pathArray = path.split('/'); 134 | 135 | // Build the full components name 136 | return pathArray.map((path) => { 137 | return _.camelize(_.slugify(_.humanize(path))); 138 | }).join('/') + _.capitalize(suffix); 139 | }; 140 | 141 | /** 142 | * Get the css/less/whatever style name to use 143 | * @param {String} path 144 | * @return {String} 145 | */ 146 | let getComponentStyleName = (path) => { 147 | let fileName = path.split('/').pop().toLowerCase(); 148 | return _.slugify(_.humanize(fileName)) + '-component'; 149 | }; 150 | 151 | /** 152 | * Get a js friendly application name 153 | * @param {String} appName The input application name [optional] 154 | * @return {String} 155 | */ 156 | let getAppName = (appName) => { 157 | 158 | // If appName is not given, use the current directory 159 | if(appName === undefined) { 160 | appName = getBaseDir(); 161 | } 162 | 163 | return _.slugify(_.humanize(appName)); 164 | }; 165 | 166 | /** 167 | * Get the wanted destination path 168 | * @param {String} name Name of the file 169 | * @param {String} type The type to use (e.g. action, store, ...) 170 | * @param {Suffix} suffix The suffix to use for the file (e.g. Store, Actions, ...) 171 | * @return {String} Final path 172 | */ 173 | let getDestinationPath = (name, type, suffix) => { 174 | 175 | let cleanedPaths = getCleanedPathName(name, suffix); 176 | let fsParts = cleanedPaths.split('/'); 177 | let actionBaseName = _.capitalize(fsParts.pop()); 178 | let partPath = fsParts.join('/'); 179 | 180 | let fsPath = configUtils.getChoiceByKey('path', type).path; 181 | 182 | let parts = [ fsPath ]; 183 | if(partPath.length > 0) { 184 | parts.push(partPath); 185 | } 186 | parts.push(actionBaseName); 187 | let fullPath = parts.join('/'); 188 | 189 | return `${fullPath}.js`; 190 | }; 191 | 192 | /** 193 | * Get the destinations class name 194 | * @param {String} name Name of the file 195 | * @param {String} type The type to use (e.g. action, store, ...) 196 | * @param {Suffix} suffix The suffix to use for the file (e.g. Store, Actions, ...) 197 | * @return {String} The javascript class name to use 198 | */ 199 | let getDestinationClassName = (name, type, suffix) => { 200 | 201 | let fixedName = getDestinationPath(name, type, suffix); 202 | return _.capitalize(fixedName.split('/').pop().split('.js')[0]); 203 | }; 204 | 205 | /** 206 | * Get the filename of the component template to copy. 207 | * @param {boolean} isStateless 208 | * @param {boolean} useStyles 209 | * @param {boolean} useCssModules 210 | * @return {string} The template filename including the .js suffix 211 | */ 212 | let getComponentTemplateName = (isStateless, useStyles, useCssModules) => { 213 | const componentTypeFrag = isStateless ? C.COMP_TYPES.STATELESS : C.COMP_TYPES.STATEFUL; 214 | const styleTypeFrag = !useStyles 215 | ? C.STYLE_TYPES.NO_STYLES 216 | : useCssModules 217 | ? C.STYLE_TYPES.WITH_CSSMODULES 218 | : C.STYLE_TYPES.WITH_STYLES 219 | ; 220 | 221 | return `${componentTypeFrag}${styleTypeFrag}.js`; 222 | }; 223 | 224 | module.exports = { 225 | getBaseDir, 226 | getAllSettingsFromComponentName, 227 | getAppName, 228 | getCleanedPathName, 229 | getComponentStyleName, 230 | getComponentTemplateName, 231 | getDestinationPath, 232 | getDestinationClassName 233 | }; 234 | --------------------------------------------------------------------------------