Skip to content

Commit

Permalink
Merged with upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
stylesuxx committed Nov 25, 2016
2 parents 954c3d1 + 9c68254 commit 0f89280
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 7 deletions.
23 changes: 22 additions & 1 deletion generators/component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class ComponentGenerator extends Generators.Base {
*/
this.useCssModules = false;

/**
* A string to prepended to the `className` attribute of generated components.
* @type {string}
*/
this._cssClsPrefix = '';

/**
* Flag indicating if stateful components should extends from React.PureComponent
* @type {boolean}
Expand Down Expand Up @@ -58,6 +64,13 @@ class ComponentGenerator extends Generators.Base {
});
}

get cssClsPrefix() {
return this._cssClsPrefix;
}

set cssClsPrefix(val) {
this._cssClsPrefix = (val === '') ? '' : `${val}-`;
}

configuring() {
// Read the requested major version or default it to the latest stable
Expand All @@ -69,6 +82,7 @@ class ComponentGenerator extends Generators.Base {

this.useStyles = !this.options.nostyle;
this.useCssModules = this.config.get('cssmodules') || false;
this.cssClsPrefix = this.config.get('cssClsPrefix') || '';

// Make sure we don't try to use template v3 with cssmodules
if (this.generatorVersion < 4 && this.useStyles && this.useCssModules) {
Expand All @@ -82,7 +96,14 @@ class ComponentGenerator extends Generators.Base {

writing() {
const settings =
getAllSettingsFromComponentName(this.name, this.config.get('style'), this.useCssModules, this.options.pure, this.generatorVersion);
getAllSettingsFromComponentName(
this.name,
this.config.get('style'),
this.useCssModules,
this.options.pure,
this.generatorVersion,
this.cssClsPrefix
);

// Create the style template. Skipped if nostyle is set as command line flag
if(this.useStyles) {
Expand Down
22 changes: 22 additions & 0 deletions generators/setup-env/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

const esDefaultOpts = require('esformatter/lib/preset/default.json');

const esOpts = Object.assign({}, esDefaultOpts, {
'lineBreak': {
'before': {
'AssignmentExpression': '>=2',
'ClassDeclaration': 2,
'EndOfFile': 1
},
'after': {
'ClassClosingBrace': 2,
'FunctionDeclaration': '>=2',
'BlockStatementClosingBrace': '>=2'
}
}
});

module.exports = {
esOpts
};
61 changes: 61 additions & 0 deletions generators/setup-env/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

const Generators = require('yeoman-generator');
const classify = require('underscore.string').classify;
const underscored = require('underscore.string').underscored;

const formatCode = require('./utils').formatCode;
const getModifiedConfigModuleIndex = require('./utils').getModifiedConfigModuleIndex;


class EnvGenerator extends Generators.Base {

constructor(args, options) {
super(args, options);

this.argument('envName', { type: String, required: true });
}

configuring() {

/**
* Currently used major version of the generator (defaults to latest stable).
* @type {number}
*/
this.generatorVersion = this.config.get('generatedWithVersion') || 3;

// Make sure we don't try to use this subgen on V3 or lower.
if (this.generatorVersion < 4) {
this.env.error('Setting up new envs is only supported in generator versions 4+');
}
}

writing() {
const classedEnv = classify(this.envName);
const snakedEnv = underscored(this.envName.toLowerCase());

// Write conf/webpack/<EnvName>.js
this.fs.copyTpl(
this.templatePath(`${this.generatorVersion}/Env.js`),
this.destinationPath(`conf/webpack/${classedEnv}.js`),
{ envName: snakedEnv }
);

// Write src/config/<env_name>.js
this.fs.copyTpl(
this.templatePath(`${this.generatorVersion}/runtimeConfig.js`),
this.destinationPath(`src/config/${snakedEnv}.js`),
{ envName: snakedEnv }
);

// Write conf/webpack/index.js
const moduleIndexPath = this.destinationPath('conf/webpack/index.js');
const updatedModuleIndex = formatCode(
getModifiedConfigModuleIndex(this.fs.read(moduleIndexPath), snakedEnv, classedEnv)
);
this.fs.write(this.destinationPath('conf/webpack/index.js'), formatCode(updatedModuleIndex));
}

}

module.exports = EnvGenerator;
29 changes: 29 additions & 0 deletions generators/setup-env/templates/4/Env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

/**
* Default dev server configuration.
*/
const webpack = require('webpack');
const WebpackBaseConfig = require('./Base');

class WebpackDevConfig extends WebpackBaseConfig {

constructor() {
super();
this.config = {
// Update your env-specific configuration here!
// To start, look at ./Dev.js or ./Dist.js for two example configurations
// targeted at production or development builds.
};
}

/**
* Get the environment name
* @return {String} The current environment
*/
get env() {
return '<%= envName %>';
}
}

module.exports = WebpackDevConfig;
7 changes: 7 additions & 0 deletions generators/setup-env/templates/4/runtimeConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import baseConfig from './base';

const config = {
appEnv: '<%= envName %>',
};

export default Object.freeze(Object.assign({}, baseConfig, config));
82 changes: 82 additions & 0 deletions generators/setup-env/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';

const acorn = require('acorn');
const escodegen = require('escodegen');
const esformatter = require('esformatter');
const jp = require('jsonpath');

const esOpts = require('./constants').esOpts;


/**
* Returns an AST Node for a {@code Property} in the {@code module.exports} object.
*
* @param {string} envName
* @return {Object}
*/
function createExportNode(envName) {
return {
'type': 'Property',
'method': false,
'shorthand': true,
'computed': false,
'key': {
'type': 'Identifier',
'name': envName
},
'kind': 'init',
'value': {
'type': 'Identifier',
'name': envName
}
}
}


/**
* Returns updated index module requiring and exporting the newly created environment.
*
* @param {string} fileStr
* @param {string} snakedEnv
* @param {string} classedEnv
* @return {string} file contents of updated conf/webpack/index.js
*/
function getModifiedConfigModuleIndex(fileStr, snakedEnv, classedEnv) {
// TODO [sthzg] we might want to rewrite the AST-mods in this function using a walker.

const moduleFileAst = acorn.parse(fileStr, { module: true });

// if required env was already created, just return the original string
if (jp.paths(moduleFileAst, `$..[?(@.value=="./${classedEnv}" && @.type=="Literal")]`).length > 0) {
return fileStr;
}

// insert require call for the new env
const envImportAst = acorn.parse(`const ${snakedEnv} = require('./${classedEnv}');`);
const insertAt = jp.paths(moduleFileAst, '$..[?(@.name=="require")]').pop()[2] + 1;
moduleFileAst.body.splice(insertAt, 0, envImportAst);

// add new env to module.exports
const exportsAt = jp.paths(moduleFileAst, '$..[?(@.name=="exports")]').pop()[2];
moduleFileAst.body[exportsAt].expression.right.properties.push(createExportNode(snakedEnv));

return escodegen.generate(moduleFileAst, { format: { indent: { style: ' ' } } });
}


/**
* Returns a beautified representation of {@code fileStr}.
*
* @param {string} fileStr
* @return {string}
*/
function formatCode(fileStr) {
return esformatter.format(fileStr, esOpts);
}


module.exports = {
createExportNode,
formatCode,
getModifiedConfigModuleIndex
};
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,21 @@
"release:patch": "npm version prerelease && git push --follow-tags && npm publish --tag beta"
},
"dependencies": {
"escodegen": "^1.7.0",
"acorn": "^4.0.3",
"escodegen": "^1.8.1",
"esformatter": "^0.9.6",
"esprima": "^3.1.1",
"esprima-walk": "^0.1.0",
"react-webpack-template": "^2.0.1-5",
"jsonpath": "^0.2.7",
"react-webpack-template": "^2.0.1-6",
"underscore.string": "^3.2.2",
"yeoman-generator": "^0.24.0",
"yeoman-welcome": "^1.0.1"
},
"devDependencies": {
"chai": "^3.2.0",
"coveralls": "^2.11.12",
"fs-extra": "^0.30.0",
"istanbul": "^0.4.5",
"mocha": "^3.0.0",
"yeoman-assert": "^2.1.1",
Expand Down
7 changes: 7 additions & 0 deletions test/generators/setup-env/assets/moduleIndex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

const foo = require('path');

module.exports = {
foo
};
77 changes: 77 additions & 0 deletions test/generators/setup-env/setupEnvTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use strict';

const acorn = require('acorn');
const assert = require('yeoman-assert');
const fs = require('fs-extra');
const helpers = require('yeoman-test');
const path = require('path');
const walk = require('acorn/dist/walk');


/**
* Returns absolute path to (sub-)generator with {@code name}.
* @param {string} name
*/
const genpath = (name) =>
path.join(__dirname, '../../../generators', name);

/**
* A mocked generator config object.
* @type {{appName: string, style: string, cssmodules: boolean, postcss: boolean, generatedWithVersion: number}}
*/
const cfg = {
appName: 'testCfg',
style: 'css',
cssmodules: false,
postcss: false,
generatedWithVersion: 4
};


describe('react-webpack:setup-env', function () {

describe('react-webpack:setup-env foobar', function () {
before(function () {
return helpers
.run(genpath('setup-env'))
.withArguments(['foobar'])
.withLocalConfig(cfg)
.inTmpDir(function (dir) {
fs.copySync(
path.join(__dirname, 'assets/moduleIndex.js'),
path.join(dir, 'conf/webpack/index.js')
);
})
.toPromise();
});

it('creates env files', function () {
assert.file(['conf/webpack/Foobar.js']);
assert.file(['src/config/foobar.js']);
});

it('requires the new env in conf/webpack/index.js', function () {
assert.fileContent(
'conf/webpack/index.js',
/const foobar = require\('\.\/Foobar'\)/
);
});

it('exports the new env from conf/webpack/index.js', function () {
const fileStr = fs.readFileSync('conf/webpack/index.js').toString();
const ast = acorn.parse(fileStr);

let found = false;
walk.simple(ast, {
'Property': (node) => {
if (node.key.name === 'foobar' && node.value.name === 'foobar') {
found = true;
}
}
});

assert(found, 'Did not find a key and value of `foobar` on the module.exports AST node');
});
});

});
10 changes: 8 additions & 2 deletions test/utils/yeomanTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,17 @@ describe('Utilities:Yeoman', () => {
};

it('should get all required information for component creation from the components name', () => {
expect(utils.getAllSettingsFromComponentName('my/component/test', 'css', true, false, 4)).to.deep.equal(expectionNamespaced);
expect(utils.getAllSettingsFromComponentName('my/component/test', 'css', true, false, 4, '')).to.deep.equal(expectionNamespaced);
});

it('should prepend a prefix to the style.className attribute', () => {
const expectation = Object.assign({}, expectionNamespaced);
expectation.style.className = 'myapp-test-component';
expect(utils.getAllSettingsFromComponentName('my/component/test', 'css', true, false, 4, 'myapp-')).to.deep.equal(expectation);
});

it('should build path information wo/ two slashes when dealing with a non-namespaced component', () => {
expect(utils.getAllSettingsFromComponentName('test', 'css', true, false, 4)).to.deep.equal(expectionRoot);
expect(utils.getAllSettingsFromComponentName('test', 'css', true, false, 4, '')).to.deep.equal(expectionRoot);
});
});

Expand Down
Loading

0 comments on commit 0f89280

Please sign in to comment.