new-react-cli
Version:
A simple React CLI to generate components instantly and more.
391 lines (308 loc) • 12.8 kB
JavaScript
const chalk = require('chalk');
const path = require('path');
const replace = require('replace');
const { camelCase } = require('lodash');
const { existsSync, outputFileSync, readFileSync } = require('fs-extra');
const componentJsTemplate = require('../templates/component/componentJsTemplate');
const componentTsTemplate = require('../templates/component/componentTsTemplate');
const componentCssTemplate = require('../templates/component/componentCssTemplate');
const componentLazyTemplate = require('../templates/component/componentLazyTemplate');
const componentTsLazyTemplate = require('../templates/component/componentTsLazyTemplate');
const componentStoryTemplate = require('../templates/component/componentStoryTemplate');
const componentTestEnzymeTemplate = require('../templates/component/componentTestEnzymeTemplate');
const componentTestDefaultTemplate = require('../templates/component/componentTestDefaultTemplate');
const componentTestTestingLibraryTemplate = require('../templates/component/componentTestTestingLibraryTemplate');
function getComponentByType(args, cliConfigFile) {
const hasComponentTypeOption = args.find((arg) => arg.includes('--type'));
// Check for component type option.
if (hasComponentTypeOption) {
const componentType = hasComponentTypeOption.split('=')[1]; // get the component type value
const selectedComponentType = cliConfigFile.component[componentType];
// If the selected component type does not exists in the cliConfigFile under `component` throw an error
if (!selectedComponentType) {
console.error(
chalk.red(
`
ERROR: Please make sure the component type you're trying to use exists in the
${chalk.bold('generate-react-cli.json')} config file under the ${chalk.bold('component')} object.
`
)
);
process.exit(1);
}
// Otherwise return it.
return selectedComponentType;
}
// Otherwise return the default component type.
return cliConfigFile.component.default;
}
function getCorrespondingComponentFileTypes(component) {
return Object.keys(component).filter((key) => key.split('with').length > 1);
}
function getCustomTemplate(componentName, templatePath) {
// --- Try loading custom template
try {
const template = readFileSync(templatePath, 'utf8');
const filename = path.basename(templatePath).replace('TemplateName', componentName);
return { template, filename };
} catch (e) {
console.error(
chalk.red(
`
ERROR: The custom template path of "${templatePath}" does not exist.
Please make sure you're pointing to the right custom template path in your generate-react-cli.json config file.
`
)
);
return process.exit(1);
}
}
function componentTemplateGenerator({ cmd, componentName, cliConfigFile }) {
const { cssPreprocessor, testLibrary, usesCssModule, usesTypeScript } = cliConfigFile;
const { customTemplates } = cliConfigFile.component[cmd.type];
let template = null;
let filename = null;
// Check for a custom component template.
if (customTemplates && customTemplates.component) {
// --- Load and use the custom component template
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
componentName,
customTemplates.component
);
template = customTemplate;
filename = customTemplateFilename;
} else {
// --- Else use GRC built-in component template
template = usesTypeScript ? componentTsTemplate : componentJsTemplate;
filename = usesTypeScript ? `${componentName}.tsx` : `${componentName}.js`;
// --- If test library is not Testing Library or if withTest is false. Remove data-testid from template
if (testLibrary !== 'Testing Library' || !cmd.withTest) {
template = template.replace(` data-testid="TemplateName"`, '');
}
// --- If it has a corresponding stylesheet
if (cmd.withStyle) {
const module = usesCssModule ? '.module' : '';
const cssPath = `${componentName}${module}.${cssPreprocessor}`;
// --- If the css module is true make sure to update the template accordingly
if (module.length) {
template = template.replace(`'./TemplateName.module.css'`, `'./${cssPath}'`);
} else {
template = template.replace(`{styles.TemplateName}`, `"${componentName}"`);
template = template.replace(`styles from './TemplateName.module.css'`, `'./${cssPath}'`);
}
} else {
// --- If no stylesheet, remove className attribute and style import from jsTemplate
template = template.replace(` className={styles.TemplateName}`, '');
template = template.replace(`import styles from './TemplateName.module.css';`, '');
}
}
return {
componentPath: `${cmd.path}/${componentName}/${filename}`,
filename,
template,
};
}
function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName }) {
const { customTemplates } = cliConfigFile.component[cmd.type];
let template = null;
let filename = null;
// Check for a custom style template.
if (customTemplates && customTemplates.style) {
// --- Load and use the custom style template
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
componentName,
customTemplates.style
);
template = customTemplate;
filename = customTemplateFilename;
} else {
const { cssPreprocessor, usesCssModule } = cliConfigFile;
const module = usesCssModule ? '.module' : '';
const cssFilename = `${componentName}${module}.${cssPreprocessor}`;
// --- Else use GRC built-in style template
template = componentCssTemplate;
filename = cssFilename;
}
return {
componentPath: `${cmd.path}/${componentName}/${filename}`,
filename,
template,
};
}
function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName }) {
const { customTemplates } = cliConfigFile.component[cmd.type];
const { testLibrary, usesTypeScript } = cliConfigFile;
let template = null;
let filename = null;
// Check for a custom test template.
if (customTemplates && customTemplates.test) {
// --- Load and use the custom test template
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
componentName,
customTemplates.test
);
template = customTemplate;
filename = customTemplateFilename;
} else {
filename = usesTypeScript ? `${componentName}.test.tsx` : `${componentName}.test.js`;
if (testLibrary === 'Enzyme') {
// --- Else use GRC built-in test template based on test library type
template = componentTestEnzymeTemplate;
} else if (testLibrary === 'Testing Library') {
template = componentTestTestingLibraryTemplate;
} else {
template = componentTestDefaultTemplate;
}
}
return {
componentPath: `${cmd.path}/${componentName}/${filename}`,
filename,
template,
};
}
function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName }) {
const { usesTypeScript } = cliConfigFile;
const { customTemplates } = cliConfigFile.component[cmd.type];
let template = null;
let filename = null;
// Check for a custom story template.
if (customTemplates && customTemplates.story) {
// --- Load and use the custom story template
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
componentName,
customTemplates.story
);
template = customTemplate;
filename = customTemplateFilename;
} else {
// --- Else use GRC built-in story template
template = componentStoryTemplate;
filename = usesTypeScript ? `${componentName}.stories.tsx` : `${componentName}.stories.js`;
}
return {
componentPath: `${cmd.path}/${componentName}/${filename}`,
filename,
template,
};
}
function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile }) {
const { usesTypeScript } = cliConfigFile;
const { customTemplates } = cliConfigFile.component[cmd.type];
let template = null;
let filename = null;
// Check for a custom lazy template.
if (customTemplates && customTemplates.lazy) {
// --- Load and use the custom lazy template
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
componentName,
customTemplates.lazy
);
template = customTemplate;
filename = customTemplateFilename;
} else {
// --- Else use GRC built-in lazy template
template = usesTypeScript ? componentTsLazyTemplate : componentLazyTemplate;
filename = usesTypeScript ? `${componentName}.lazy.tsx` : `${componentName}.lazy.js`;
}
return {
componentPath: `${cmd.path}/${componentName}/${filename}`,
filename,
template,
};
}
function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, componentFileType }) {
const { customTemplates } = cliConfigFile.component[cmd.type];
const fileType = camelCase(componentFileType.split('with')[1]);
let filename = null;
let template = null;
// Check for a valid custom template for the corresponding custom component file.
if (!customTemplates || !customTemplates[fileType]) {
console.error(
chalk.red(
`
ERROR: Custom component files require a valid custom template.
Please make sure you're pointing to the right custom template path in your generate-react-cli.json config file.
`
)
);
return process.exit(1);
}
// --- Load and use the custom component template.
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
componentName,
customTemplates[fileType]
);
template = customTemplate;
filename = customTemplateFilename;
return {
componentPath: `${cmd.path}/${componentName}/${filename}`,
filename,
template,
};
}
// --- Built in component file types
const buildInComponentFileTypes = {
COMPONENT: 'component',
STYLE: 'withStyle',
TEST: 'withTest',
STORY: 'withStory',
LAZY: 'withLazy',
};
// --- Generate component template map
const componentTemplateGeneratorMap = {
[buildInComponentFileTypes.COMPONENT]: componentTemplateGenerator,
[buildInComponentFileTypes.STYLE]: componentStyleTemplateGenerator,
[buildInComponentFileTypes.TEST]: componentTestTemplateGenerator,
[buildInComponentFileTypes.STORY]: componentStoryTemplateGenerator,
[buildInComponentFileTypes.LAZY]: componentLazyTemplateGenerator,
};
function generateComponent(componentName, cmd, cliConfigFile) {
const componentFileTypes = ['component', ...getCorrespondingComponentFileTypes(cmd)];
componentFileTypes.forEach((componentFileType) => {
// --- Generate templates only if the component options (withStyle, withTest, etc..) are true,
// or if the template type is "component"
if (
(cmd[componentFileType] && cmd[componentFileType].toString() === 'true') ||
componentFileType === buildInComponentFileTypes.COMPONENT
) {
const generateTemplate = componentTemplateGeneratorMap[componentFileType] || customFileTemplateGenerator;
const { componentPath, filename, template } = generateTemplate({
cmd,
componentName,
cliConfigFile,
componentFileType,
});
// --- Make sure the component does not already exist in the path directory.
if (existsSync(componentPath)) {
console.error(chalk.red(`${filename} already exists in this path "${componentPath}".`));
} else {
try {
outputFileSync(componentPath, template);
replace({
regex: 'TemplateName',
replacement: componentName,
paths: [componentPath],
recursive: false,
silent: true,
});
replace({
regex: 'templateName',
replacement: camelCase(componentName),
paths: [componentPath],
recursive: false,
silent: true,
});
console.log(chalk.green(`${filename} was successfully created at ${componentPath}`));
} catch (error) {
console.error(chalk.red(`${filename} failed and was not created.`));
console.error(error);
}
}
}
});
}
module.exports = {
generateComponent,
getComponentByType,
getCorrespondingComponentFileTypes,
};