mirror of
https://github.com/react-webpack-generators/generator-react-webpack.git
synced 2025-12-08 18:01:59 +00:00
This subgen creates a new Webpack config environment by - creating conf/webpack/<EnvName>.js - creating src/config/<env_name>.js - requiring and exporting the new env in conf/webpack/index.js The commit introduces a basic config template that is supposed to be populated by the generator's users. Various more fine grained subgen options can be added at a later time (e.g. prompting the user if new run scripts should be added to package.json). The subgen's basic functionality is backed up by unit tests that check - if files are created - if conf/webpack/index.js contains correct import/export
This commit is contained in:
parent
a2bd75a83b
commit
01855bd2ed
22
generators/setup-env/constants.js
Normal file
22
generators/setup-env/constants.js
Normal file
@ -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
generators/setup-env/index.js
Normal file
61
generators/setup-env/index.js
Normal file
@ -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
generators/setup-env/templates/4/Env.js
Normal file
29
generators/setup-env/templates/4/Env.js
Normal file
@ -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
generators/setup-env/templates/4/runtimeConfig.js
Normal file
7
generators/setup-env/templates/4/runtimeConfig.js
Normal file
@ -0,0 +1,7 @@
|
||||
import baseConfig from './base';
|
||||
|
||||
const config = {
|
||||
appEnv: '<%= envName %>',
|
||||
};
|
||||
|
||||
export default Object.freeze(Object.assign({}, baseConfig, config));
|
||||
82
generators/setup-env/utils.js
Normal file
82
generators/setup-env/utils.js
Normal file
@ -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
|
||||
};
|
||||
@ -47,9 +47,12 @@
|
||||
"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",
|
||||
"jsonpath": "^0.2.7",
|
||||
"react-webpack-template": "^2.0.1-5",
|
||||
"underscore.string": "^3.2.2",
|
||||
"yeoman-generator": "^0.24.0",
|
||||
@ -58,6 +61,7 @@
|
||||
"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",
|
||||
|
||||
7
test/generators/setup-env/assets/moduleIndex.js
Normal file
7
test/generators/setup-env/assets/moduleIndex.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const foo = require('path');
|
||||
|
||||
module.exports = {
|
||||
foo
|
||||
};
|
||||
77
test/generators/setup-env/setupEnvTest.js
Normal file
77
test/generators/setup-env/setupEnvTest.js
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user