Added new directory options for generated components (styles are now also generated in the components folder)

Added directory options fallbacks for version 3 and below
Prepared for inclusion of css modules
This commit is contained in:
Chris 2016-06-15 15:34:26 +02:00
parent ed4686edd0
commit 892f771899
11 changed files with 171 additions and 91 deletions

View File

@ -54,6 +54,7 @@ class AppGenerator extends Generators.Base {
// Set needed global vars for yo // Set needed global vars for yo
this.appName = answers.appName; this.appName = answers.appName;
this.style = answers.style; this.style = answers.style;
this.cssmodules = answers.cssmodules;
this.postcss = answers.postcss; this.postcss = answers.postcss;
this.generatedWithVersion = parseInt(packageInfo.version.split('.').shift(), 10); this.generatedWithVersion = parseInt(packageInfo.version.split('.').shift(), 10);
@ -61,6 +62,7 @@ class AppGenerator extends Generators.Base {
this.config.set('appName', this.appName); this.config.set('appName', this.appName);
this.config.set('appPath', this.appPath); this.config.set('appPath', this.appPath);
this.config.set('style', this.style); this.config.set('style', this.style);
this.config.set('cssmodules', this.cssmodules);
this.config.set('postcss', this.postcss); this.config.set('postcss', this.postcss);
this.config.set('generatedWithVersion', this.generatedWithVersion); this.config.set('generatedWithVersion', this.generatedWithVersion);
}); });

View File

@ -15,6 +15,12 @@ module.exports = [
choices: utils.config.getChoices('style'), choices: utils.config.getChoices('style'),
default: utils.config.getDefaultChoice('style') default: utils.config.getDefaultChoice('style')
}, },
{
type: 'confirm',
name: 'cssmodules',
message: 'Enable css module support? See https://github.com/gajus/react-css-modules for further info',
default: true
},
{ {
type: 'confirm', type: 'confirm',
name: 'postcss', name: 'postcss',

View File

@ -16,34 +16,34 @@ class ComponentGenerator extends Generators.Base {
writing() { writing() {
let settings = utils.yeoman.getAllSettingsFromComponentName(this.name, this.config.get('style'));
let componentType = this.options.stateless ? 'Stateless' : 'Base';
// Set the template base. If it cannot be guessed, // Set the template base. If it cannot be guessed,
// use files from the default directory. If it cannot be // use files from the default directory. In this case,
// guessed, assume we have something REALLY REALLY old here... // assume we have something REALLY REALLY old here...
let templateBase = this.config.get('generatedWithVersion'); let generatorVersion = this.config.get('generatedWithVersion');
if(!templateBase) { if(!generatorVersion) {
templateBase = 3; generatorVersion = 3;
} }
const settings = utils.yeoman.getAllSettingsFromComponentName(this.name, this.config.get('style'), generatorVersion);
const componentType = this.options.stateless ? 'Stateless' : 'Base';
// Create the style template // Create the style template
this.fs.copyTpl( this.fs.copyTpl(
this.templatePath(`${templateBase}/styles/Component${settings.style.suffix}`), this.templatePath(`${generatorVersion}/styles/Component${settings.style.suffix}`),
this.destinationPath(settings.style.path + settings.style.fileName), this.destinationPath(settings.style.path + settings.style.fileName),
settings settings
); );
// Create the component // Create the component
this.fs.copyTpl( this.fs.copyTpl(
this.templatePath(`${templateBase}/components/${componentType}.js`), this.templatePath(`${generatorVersion}/components/${componentType}.js`),
this.destinationPath(settings.component.path + settings.component.fileName), this.destinationPath(settings.component.path + settings.component.fileName),
settings settings
); );
// Create the unit test // Create the unit test
this.fs.copyTpl( this.fs.copyTpl(
this.templatePath(`${templateBase}/tests/Base.js`), this.templatePath(`${generatorVersion}/tests/Base.js`),
this.destinationPath(settings.test.path + settings.test.fileName), this.destinationPath(settings.test.path + settings.test.fileName),
settings settings
); );

View File

@ -4,6 +4,7 @@ import styles from '<%= style.webpackPath %>';
@cssmodules(styles) @cssmodules(styles)
class <%= component.className %> extends React.Component { class <%= component.className %> extends React.Component {
render() { render() {
return ( return (
<div className="<%= style.className %>" styleName="<%= style.className %>"> <div className="<%= style.className %>" styleName="<%= style.className %>">

View File

@ -5,7 +5,7 @@ import styles from '<%= style.webpackPath %>';
function <%= component.className %>() { function <%= component.className %>() {
return ( return (
<div className="<%= style.className %>"> <div className="<%= style.className %>" styleName="<%= style.className %>">
Please edit <%= component.path %><%= component.fileName %> to update this component! Please edit <%= component.path %><%= component.fileName %> to update this component!
</div> </div>
); );

View File

@ -6,12 +6,12 @@ describe('<<%= component.className %> />', () => {
let component; let component;
beforeEach(() => { beforeEach(() => {
component = shallow(<%= component.className %>); component = shallow(<<%= component.className %> />);
}); });
describe('when rendering the component', () => { describe('when rendering the component', () => {
it('should have a className of "index"', () => { it('should have a className of "<%= style.className %>"', () => {
expect(component.hasClass('<%= style.className %>')).to.equal(true); expect(component.hasClass('<%= style.className %>')).to.equal(true);
}); });
}); });

View File

@ -40,9 +40,9 @@
"scripts": { "scripts": {
"test": "mocha", "test": "mocha",
"test:watch": "mocha -w", "test:watch": "mocha -w",
"release:major": "npm version major && npm publish && git push --follow-tags", "release:major": "npm version major && npm publish && git push --follow-tags --tag beta",
"release:minor": "npm version minor && npm publish && git push --follow-tags", "release:minor": "npm version minor && npm publish && git push --follow-tags --tag beta",
"release:patch": "npm version patch && npm publish && git push --follow-tags" "release:patch": "npm version patch && npm publish && git push --follow-tags --tag beta"
}, },
"dependencies": { "dependencies": {
"escodegen": "^1.7.0", "escodegen": "^1.7.0",

View File

@ -52,8 +52,12 @@ describe('react-webpack:app', () => {
expect(generator.config.get('style')).to.equal('css'); expect(generator.config.get('style')).to.equal('css');
}); });
it('should use "css modules" per default', () => {
expect(generator.config.get('cssmodules')).to.be.true;
});
it('should not enable "PostCSS" by default', () => { it('should not enable "PostCSS" by default', () => {
expect(generator.config.get('postcss')).to.equal(false); expect(generator.config.get('postcss')).to.be.false;
}); });
}); });

View File

@ -173,75 +173,72 @@ describe('react-webpack:component', () => {
} }
// Run all tests for all available style types. // Run all tests for all available style types.
testComponentWithStyle(styleTypes.css); // Stateless components will also be tested!
testComponentWithStyle(styleTypes.sass); for(const style in styleTypes) {
testComponentWithStyle(styleTypes.scss); testComponentWithStyle(styleTypes[style]);
testComponentWithStyle(styleTypes.less); testComponentWithStyle(styleTypes[style], { stateless: true });
testComponentWithStyle(styleTypes.stylus); }
// Test stateless components (should be enough when testing with defaults)
testComponentWithStyle(styleTypes.css, { stateless: true });
}); });
describe('when using version 4 of the generator', () => { describe('when using version 4 of the generator', () => {
/**
* @var {yeoman.generator} generator
* Global generator instance, set by createGeneratedComponent
*/
let generator;
// List of available style types. Please add a line that says // List of available style types. Please add a line that says
// testComponentWithStyle(styleTypes.KEY); to the bottom of the file // testComponentWithStyle(styleTypes.KEY); to the bottom of the file
// to run all unit tests for this filetype. // to run all unit tests for this filetype.
const styleTypes = { const styleTypes = {
css: { css: {
type: 'css', type: 'css',
fileName: 'src/styles/Mycomponent.css', fileName: 'src/components/mycomponent.cssmodule.css',
expandedFileName: 'src/styles/my/littleSpecial/Test.css', expandedFileName: 'src/components/my/littleSpecial/test.cssmodule.css',
assertions: { assertions: {
componentImport: 'import styles from \'styles//Mycomponent.css\';', componentImport: 'import styles from \'./mycomponent.cssmodule.css\';',
styleContent: '.mycomponent-component' styleContent: '.mycomponent-component'
} }
}, },
sass: { sass: {
type: 'sass', type: 'sass',
fileName: 'src/styles/Mycomponent.sass', fileName: 'src/components/Mycomponent.cssmodule.sass',
expandedFileName: 'src/styles/my/littleSpecial/Test.sass', expandedFileName: 'src/components/my/littleSpecial/test.cssmodule.sass',
assertions: { assertions: {
componentImport: 'import styles from \'styles//Mycomponent.sass\';', componentImport: 'import styles from \'./mycomponent.cssmodule.sass\';',
styleContent: '.mycomponent-component' styleContent: '.mycomponent-component'
} }
}, },
scss: { scss: {
type: 'scss', type: 'scss',
fileName: 'src/styles/Mycomponent.scss', fileName: 'src/components/mycomponent.cssmodule.scss',
expandedFileName: 'src/styles/my/littleSpecial/Test.scss', expandedFileName: 'src/components/my/littleSpecial/test.cssmodule.scss',
assertions: { assertions: {
componentImport: 'import styles from \'styles//Mycomponent.scss\';', componentImport: 'import styles from \'./mycomponent.cssmodule.scss\';',
styleContent: '.mycomponent-component' styleContent: '.mycomponent-component'
} }
}, },
less: { less: {
type: 'less', type: 'less',
fileName: 'src/styles/Mycomponent.less', fileName: 'src/components/mycomponent.cssmodule.less',
expandedFileName: 'src/styles/my/littleSpecial/Test.less', expandedFileName: 'src/components/my/littleSpecial/test.cssmodule.less',
assertions: { assertions: {
componentImport: 'import styles from \'styles//Mycomponent.less\';', componentImport: 'import styles from \'./mycomponent.cssmodule.less\';',
styleContent: '.mycomponent-component' styleContent: '.mycomponent-component'
} }
}, },
stylus: { stylus: {
type: 'stylus', type: 'stylus',
fileName: 'src/styles/Mycomponent.styl', fileName: 'src/components/mycomponent.cssmodule.styl',
expandedFileName: 'src/styles/my/littleSpecial/Test.styl', expandedFileName: 'src/components/my/littleSpecial/test.cssmodule.styl',
assertions: { assertions: {
componentImport: 'import styles from \'styles//Mycomponent.styl\';', componentImport: 'import styles from \'./mycomponent.cssmodule.styl\';',
styleContent: '.mycomponent-component' styleContent: '.mycomponent-component'
} }
} }
}; };
/**
* @var {yeoman.generator} generator
* Global generator instance, set by createGeneratedComponent
*/
let generator;
/** /**
* Return a newly generated component with given name and style * Return a newly generated component with given name and style
* @param {String} name Name of the component * @param {String} name Name of the component
@ -281,9 +278,9 @@ describe('react-webpack:component', () => {
createGeneratedComponent('mycomponent', style.type, options, () => { createGeneratedComponent('mycomponent', style.type, options, () => {
assert.file([ assert.file([
'src/components/MycomponentComponent.js', 'src/components/Mycomponent.js',
style.fileName, style.fileName,
'test/components/MycomponentComponentTest.js' 'test/components/MycomponentTest.js'
]); ]);
done(); done();
}); });
@ -294,21 +291,21 @@ describe('react-webpack:component', () => {
it('should always import REACT', (done) => { it('should always import REACT', (done) => {
createGeneratedComponent('mycomponent', style.type, options, () => { createGeneratedComponent('mycomponent', style.type, options, () => {
assert.fileContent('src/components/MycomponentComponent.js', 'import React from \'react\';'); assert.fileContent('src/components/Mycomponent.js', 'import React from \'react\';');
done(); done();
}); });
}); });
it(`should require the created ${style.type} file`, (done) => { it(`should require the created ${style.type} file`, (done) => {
createGeneratedComponent('mycomponent', style.type, options, () => { createGeneratedComponent('mycomponent', style.type, options, () => {
assert.fileContent('src/components/MycomponentComponent.js', style.assertions.componentImport); assert.fileContent('src/components/Mycomponent.js', style.assertions.componentImport);
done(); done();
}); });
}); });
it('should have its displayName set per default', (done) => { it('should have its displayName set per default', (done) => {
createGeneratedComponent('mycomponent', style.type, options, () => { createGeneratedComponent('mycomponent', style.type, options, () => {
assert.fileContent('src/components/MycomponentComponent.js', 'displayName = \'MycomponentComponent\';'); assert.fileContent('src/components/Mycomponent.js', 'Mycomponent.displayName = \'Mycomponent\';');
done(); done();
}); });
}); });
@ -318,11 +315,11 @@ describe('react-webpack:component', () => {
let exportAssertion; let exportAssertion;
if(generator.options.stateless) { if(generator.options.stateless) {
exportAssertion = 'export default cssmodules(MycomponentComponent, styles);'; exportAssertion = 'export default cssmodules(Mycomponent, styles);';
} else { } else {
exportAssertion = 'export default MycomponentComponent'; exportAssertion = 'export default Mycomponent';
} }
assert.fileContent('src/components/MycomponentComponent.js', exportAssertion); assert.fileContent('src/components/Mycomponent.js', exportAssertion);
done(); done();
}); });
}); });
@ -331,9 +328,9 @@ describe('react-webpack:component', () => {
createGeneratedComponent('my/little !special/test', style.type, options, () => { createGeneratedComponent('my/little !special/test', style.type, options, () => {
assert.file([ assert.file([
'src/components/my/littleSpecial/TestComponent.js', 'src/components/my/littleSpecial/Test.js',
style.expandedFileName, style.expandedFileName,
'test/components/my/littleSpecial/TestComponentTest.js' 'test/components/my/littleSpecial/TestTest.js'
]); ]);
done(); done();
}); });
@ -354,7 +351,7 @@ describe('react-webpack:component', () => {
it('should import the react component', (done) => { it('should import the react component', (done) => {
createGeneratedComponent('mycomponent', style.type, options, () => { createGeneratedComponent('mycomponent', style.type, options, () => {
assert.fileContent('test/components/MycomponentComponentTest.js', 'import MycomponentComponent from \'components//MycomponentComponent.js\';'); assert.fileContent('test/components/MycomponentTest.js', 'import Mycomponent from \'components//Mycomponent.js\';');
done(); done();
}); });
}); });
@ -363,14 +360,11 @@ describe('react-webpack:component', () => {
} }
// Run all tests for all available style types. // Run all tests for all available style types.
testComponentWithStyle(styleTypes.css); // Stateless components will also be tested!
testComponentWithStyle(styleTypes.sass); for(const style in styleTypes) {
testComponentWithStyle(styleTypes.scss); testComponentWithStyle(styleTypes[style]);
testComponentWithStyle(styleTypes.less); testComponentWithStyle(styleTypes[style], { stateless: true });
testComponentWithStyle(styleTypes.stylus); }
// Test stateless components (should be enough when testing with defaults)
testComponentWithStyle(styleTypes.css, { stateless: true });
}); });
}); });

View File

@ -67,9 +67,38 @@ describe('Utilities:Yeoman', () => {
describe('#getAllSettingsFromComponentName', () => { describe('#getAllSettingsFromComponentName', () => {
it('should get all required information for component creation from the components name', () => { describe('when the generator version is set to 4', () => {
let expection = { const expection = {
style: {
webpackPath: './test.cssmodule.css',
path: 'src/components/my/component/',
fileName: 'test.cssmodule.css',
className: 'test-component',
suffix: '.css'
},
component: {
webpackPath: 'components/my/component/Test.js',
path: 'src/components/my/component/',
fileName: 'Test.js',
className: 'Test',
displayName: 'MyComponentTest',
suffix: '.js'
},
test: {
path: 'test/components/my/component/',
fileName: 'TestTest.js'
}
};
it('should get all required information for component creation from the components name', () => {
expect(utils.getAllSettingsFromComponentName('my/component/test', 'css', 4)).to.deep.equal(expection);
});
});
describe('when the generator version is set to 3 (or not set at all)', () => {
const expection = {
style: { style: {
webpackPath: 'styles/my/component/Test.css', webpackPath: 'styles/my/component/Test.css',
path: 'src/styles/my/component/', path: 'src/styles/my/component/',
@ -91,7 +120,10 @@ describe('Utilities:Yeoman', () => {
} }
}; };
expect(utils.getAllSettingsFromComponentName('my/component/test')).to.deep.equal(expection); it('should get all required information for component creation from the components name', () => {
expect(utils.getAllSettingsFromComponentName('my/component/test')).to.deep.equal(expection);
expect(utils.getAllSettingsFromComponentName('my/component/test', 'css', 3)).to.deep.equal(expection);
});
}); });
}); });

View File

@ -19,14 +19,21 @@ let getBaseDir = () => {
* Get all settings (paths and the like) from components name * Get all settings (paths and the like) from components name
* @param {String} componentName The components name * @param {String} componentName The components name
* @param {String} style Style language to use [optional] * @param {String} style Style language to use [optional]
* @param {String|Number} generatorVersion The version of the generator [optional]
* @return {Object} Component settings * @return {Object} Component settings
*/ */
let getAllSettingsFromComponentName = (componentName, style) => { let getAllSettingsFromComponentName = (componentName, style, generatorVersion) => {
// Use css per default
if(!style) { if(!style) {
style = 'css'; style = 'css';
} }
// Use version 3 fallback as default for projects
if(!generatorVersion) {
generatorVersion = 3;
}
// Clean up the path and pull it to parts // Clean up the path and pull it to parts
let cleanedPaths = getCleanedPathName(componentName); let cleanedPaths = getCleanedPathName(componentName);
let componentParts = cleanedPaths.split('/'); let componentParts = cleanedPaths.split('/');
@ -46,27 +53,61 @@ let getAllSettingsFromComponentName = (componentName, style) => {
// Configure tests // Configure tests
let testPath = configUtils.getChoiceByKey('path', 'test'); let testPath = configUtils.getChoiceByKey('path', 'test');
let settings = { let settings;
style: {
webpackPath: `styles/${componentPartPath}/${componentBaseName}${styleSettings.suffix}`, switch(generatorVersion) {
path: `${stylePaths.path}/${componentPartPath}/`,
fileName: `${componentBaseName}${styleSettings.suffix}`, case 4:
className: getComponentStyleName(componentBaseName), settings = {
suffix: styleSettings.suffix style: {
}, webpackPath: `./${componentBaseName.toLowerCase()}.cssmodule${styleSettings.suffix}`,
component: { path: `${componentPath.path}/${componentPartPath}/`,
webpackPath: `components/${componentPartPath}/${componentBaseName}Component.js`, fileName: `${componentBaseName.toLowerCase()}.cssmodule${styleSettings.suffix}`,
path: `${componentPath.path}/${componentPartPath}/`, className: getComponentStyleName(componentBaseName),
fileName: `${componentBaseName}Component.js`, suffix: styleSettings.suffix
className: `${componentBaseName}Component`, },
displayName: `${componentFullName}Component`, component: {
suffix: '.js' webpackPath: `components/${componentPartPath}/${componentBaseName}.js`,
}, path: `${componentPath.path}/${componentPartPath}/`,
test: { fileName: `${componentBaseName}.js`,
path: `${testPath.path}/components/${componentPartPath}/`, className: `${componentBaseName}`,
fileName: `${componentBaseName}ComponentTest.js` displayName: `${componentFullName}`,
} suffix: '.js'
}; },
test: {
path: `${testPath.path}/components/${componentPartPath}/`,
fileName: `${componentBaseName}Test.js`
}
};
break;
// Use version 3 style for the defaults and fallback
// @deprecated
case 3:
default:
settings = {
style: {
webpackPath: `styles/${componentPartPath}/${componentBaseName}${styleSettings.suffix}`,
path: `${stylePaths.path}/${componentPartPath}/`,
fileName: `${componentBaseName}${styleSettings.suffix}`,
className: getComponentStyleName(componentBaseName),
suffix: styleSettings.suffix
},
component: {
webpackPath: `components/${componentPartPath}/${componentBaseName}Component.js`,
path: `${componentPath.path}/${componentPartPath}/`,
fileName: `${componentBaseName}Component.js`,
className: `${componentBaseName}Component`,
displayName: `${componentFullName}Component`,
suffix: '.js'
},
test: {
path: `${testPath.path}/components/${componentPartPath}/`,
fileName: `${componentBaseName}ComponentTest.js`
}
};
break;
}
return settings; return settings;
}; };