gen-jhipster
Version:
VHipster - Spring Boot + Angular/React/Vue in one handy generator
326 lines (325 loc) • 15.3 kB
JavaScript
/**
* Copyright 2013-2026 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import chalk from 'chalk';
import { isFileStateModified } from 'mem-fs-editor/state';
import { clientFrameworkTypes, fieldTypes } from "../../lib/jhipster/index.js";
import { upperFirstCamelCase } from "../../lib/utils/index.js";
import { createNeedleCallback } from "../base-core/support/index.js";
import { ClientApplicationGenerator } from "../client/generator.js";
import { generateEntityClientEnumImports as getClientEnumImportsFormat, generateEntityClientFields as getHydratedEntityClientFields, generateEntityClientImports as formatEntityClientImports, } from "../client/support/index.js";
import { JAVA_WEBAPP_SOURCES_DIR } from "../index.js";
import { writeEslintClientRootConfigFile } from "../javascript-simple-application/generators/eslint/support/tasks.js";
import cleanupOldFilesTask from "./cleanup.js";
import { cleanupEntitiesFiles, postWriteEntitiesFiles, writeEntitiesFiles } from "./entity-files-react.js";
import { writeFiles } from "./files-react.js";
import { isTranslatedReactFile, translateReactFilesTransform } from "./support/index.js";
const { CommonDBTypes } = fieldTypes;
const TYPE_BOOLEAN = CommonDBTypes.BOOLEAN;
const { REACT } = clientFrameworkTypes;
export default class ReactGenerator extends ClientApplicationGenerator {
async beforeQueue() {
if (!this.fromBlueprint) {
await this.composeWithBlueprints();
}
await this.dependsOnBootstrap('react');
if (!this.delegateToBlueprint) {
await this.dependsOnJHipster('jhipster:client:i18n');
await this.dependsOnJHipster('client');
await this.dependsOnJHipster('languages');
}
}
get composing() {
return this.asComposingTaskGroup({
async composing() {
await this.composeWithJHipster('jhipster:client:common');
if (this.jhipsterConfigWithDefaults.websocket === 'spring-websocket') {
await this.composeWithJHipster('jhipster:client:encode-csrf-token');
}
},
});
}
get [ClientApplicationGenerator.COMPOSING]() {
return this.delegateTasksToBlueprint(() => this.composing);
}
get preparing() {
return this.asPreparingTaskGroup({
loadPackageJson({ application }) {
this.loadNodeDependenciesFromPackageJson(application.nodeDependencies, this.fetchFromInstalledJHipster('react', 'resources', 'package.json'));
},
applicationDefaults({ application, applicationDefaults }) {
application.prettierExtensions.push('html', 'tsx', 'css', 'scss');
if (application.clientBundlerWebpack) {
application.prettierFolders.push('webpack/');
}
if (!application.backendTypeJavaAny && application.clientSrcDir !== JAVA_WEBAPP_SOURCES_DIR) {
// When we have a java backend, 'src/**' is already added by java:bootstrap
application.prettierFolders.push(`${application.clientSrcDir}**/`);
}
applicationDefaults({
__override__: true,
webappEnumerationsDir: app => `${app.clientSrcDir}app/shared/model/enumerations/`,
});
},
async javaNodeBuildPaths({ application }) {
const { clientBundlerWebpack, javaNodeBuildPaths } = application;
javaNodeBuildPaths?.push('.postcss.config.js', 'tsconfig.json');
if (clientBundlerWebpack) {
javaNodeBuildPaths?.push('webpack/');
}
},
prepareForTemplates({ application, source }) {
source.addWebpackConfig = args => {
const webpackPath = `${application.clientRootDir}webpack/webpack.common.js`;
const ignoreNonExisting = this.ignoreNeedlesError && 'Webpack configuration file not found';
this.editFile(webpackPath, { ignoreNonExisting }, createNeedleCallback({
needle: 'jhipster-needle-add-webpack-config',
contentToAdd: `,${args.config}`,
}));
};
source.addClientStyle = ({ style, comment }) => {
comment = comment
? `/* ==========================================================================
${comment}
========================================================================== */
`
: '';
this.editFile(`${application.clientSrcDir}app/app.scss`, createNeedleCallback({
needle: 'scss-add-main',
contentToAdd: `${comment}${style}`,
contentToCheck: style,
}));
};
source.addEntitiesToClient = ({ application, entities }) => {
const entityRoutes = `${application.clientSrcDir}app/entities/routes.tsx`;
const entityReducers = `${application.clientSrcDir}app/entities/reducers.ts`;
const entityMenu = `${application.clientSrcDir}app/entities/menu.tsx`;
for (const entity of entities) {
const { entityAngularName, entityInstance, entityFolderName, entityFileName, entityPage, entityUrl, entityTranslationKeyMenuPath, entityNameHumanized, } = entity;
this.editFile(entityMenu, createNeedleCallback({
needle: 'add-entity-to-menu',
contentToAdd: `<MenuItem icon="asterisk" to="/${entityPage}">
${application.enableTranslation ? `<Translate contentKey="${entityTranslationKeyMenuPath}" />` : `${entityNameHumanized}`}
</MenuItem>`,
}));
this.editFile(entityRoutes, createNeedleCallback({
needle: 'add-route-import',
contentToAdd: `import ${entityAngularName} from './${entityFolderName}';`,
}), createNeedleCallback({
needle: 'add-route-path',
contentToAdd: `<Route path="/${entityUrl}/*" element={<${entityAngularName} />} />`,
}));
this.editFile(entityReducers, createNeedleCallback({
needle: 'add-reducer-import',
contentToAdd: `import ${entityInstance} from 'app/entities/${entityFolderName}/${entityFileName}.reducer';`,
}), createNeedleCallback({
needle: 'add-reducer-combine',
contentToAdd: `${entityInstance},`,
}));
}
};
},
});
}
get [ClientApplicationGenerator.PREPARING]() {
return this.delegateTasksToBlueprint(() => this.preparing);
}
get preparingEachEntity() {
return this.asPreparingEachEntityTaskGroup({
react({ application, entity }) {
entity.entityReactState = application.applicationTypeMonolith
? entity.entityInstance
: `${application.lowercaseBaseName}.${entity.entityInstance}`;
},
});
}
get [ClientApplicationGenerator.PREPARING_EACH_ENTITY]() {
return this.delegateTasksToBlueprint(() => this.preparingEachEntity);
}
get preparingEachEntityField() {
return this.asPreparingEachEntityFieldTaskGroup({
react({ field }) {
field.fieldValidateRulesPatternReact ??= field.fieldValidateRulesPattern?.replace(/'/g, String.raw `\'`);
},
});
}
get [ClientApplicationGenerator.PREPARING_EACH_ENTITY_FIELD]() {
return this.delegateTasksToBlueprint(() => this.preparingEachEntityField);
}
get default() {
return this.asDefaultTaskGroup({
queueTranslateTransform({ application }) {
if (!application.enableTranslation) {
this.queueTransformStream({
name: 'translating react application',
filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslatedReactFile(file),
refresh: false,
}, translateReactFilesTransform(application.getWebappTranslation));
}
},
});
}
get [ClientApplicationGenerator.DEFAULT]() {
return this.delegateTasksToBlueprint(() => this.default);
}
get writing() {
return this.asWritingTaskGroup({
async cleanup({ control }) {
await control.cleanupFiles({
'9.0.0-alpha.0': [
// Try to remove possibles old eslint config files
'eslint.config.js',
'eslint.config.mjs',
'postcss.config.js',
],
});
},
cleanupOldFilesTask,
writeEslintClientRootConfigFile,
writeFiles,
});
}
get [ClientApplicationGenerator.WRITING]() {
return this.delegateTasksToBlueprint(() => this.writing);
}
get writingEntities() {
return {
cleanupEntitiesFiles,
writeEntitiesFiles,
};
}
get [ClientApplicationGenerator.WRITING_ENTITIES]() {
return this.delegateTasksToBlueprint(() => this.writingEntities);
}
get postWriting() {
return this.asPostWritingTaskGroup({
clientBundler({ application, source }) {
source.mergeClientPackageJson({
overrides: {
'react-redux-loading-bar': {
react: '$react',
'react-dom': '$react-dom',
},
},
});
if (application.clientRootDir) {
this.packageJson.merge({
overrides: {
'react-redux-loading-bar': {
react: '$react',
'react-dom': '$react-dom',
},
},
});
}
},
addMicrofrontendDependencies({ application, source }) {
if (!application.microfrontend)
return;
const { applicationTypeGateway } = application;
if (applicationTypeGateway) {
source.mergeClientPackageJson({
devDependencies: { '@module-federation/utilities': null },
});
}
source.mergeClientPackageJson({
devDependencies: { '@module-federation/enhanced': null },
});
},
addWebsocketDependencies({ application, source }) {
const { communicationSpringWebsocket, nodeDependencies } = application;
if (communicationSpringWebsocket) {
source.mergeClientPackageJson({
dependencies: {
rxjs: nodeDependencies.rxjs,
'sockjs-client': nodeDependencies['sockjs-client'],
'webstomp-client': nodeDependencies['webstomp-client'],
},
});
}
},
sonar({ application, source }) {
const { clientI18nDir, clientDistDir, clientSrcDir, temporaryDir } = application;
source.addSonarProperties?.([
{ key: 'sonar.test.inclusions', value: `${clientSrcDir}app/**/*.spec.ts, ${clientSrcDir}app/**/*.spec.tsx`, valueSep: ', ' },
{ key: 'sonar.testExecutionReportPaths', value: `${temporaryDir}test-results/jest/TESTS-results-sonar.xml` },
{ key: 'sonar.javascript.lcov.reportPaths', value: `${temporaryDir}test-results/lcov.info` },
{
key: 'sonar.exclusions',
value: `${clientSrcDir}content/**/*.*, ${clientI18nDir}*.ts, ${clientDistDir}**/*.*`,
valueSep: ', ',
},
]);
},
});
}
get [ClientApplicationGenerator.POST_WRITING]() {
return this.delegateTasksToBlueprint(() => this.postWriting);
}
get postWritingEntities() {
return {
postWriteEntitiesFiles,
};
}
get [ClientApplicationGenerator.POST_WRITING_ENTITIES]() {
return this.delegateTasksToBlueprint(() => this.postWritingEntities);
}
get end() {
return this.asEndTaskGroup({
end({ application }) {
this.log.ok(`React ${application.nodeDependencies.react} application generated successfully.`);
this.log.log(chalk.green(` Start your Webpack development server with:
${chalk.yellow.bold(`${application.nodePackageManager} start`)}
`));
},
});
}
get [ClientApplicationGenerator.END]() {
return this.delegateTasksToBlueprint(() => this.end);
}
/**
* @private
* Generate Entity Client Field Default Values
*/
generateEntityClientFieldDefaultValues(fields) {
const defaultVariablesValues = {};
fields.forEach(field => {
const { fieldType, fieldName } = field;
if (fieldType === TYPE_BOOLEAN) {
defaultVariablesValues[fieldName] = `${fieldName}: false,`;
}
});
return defaultVariablesValues;
}
generateEntityClientFields(primaryKey, fields, relationships, dto, customDateType, embedded = false) {
return getHydratedEntityClientFields(primaryKey, fields, relationships, dto, customDateType, embedded, REACT);
}
generateEntityClientImports(relationships, dto) {
return formatEntityClientImports(relationships, dto, REACT);
}
generateEntityClientEnumImports(fields) {
return getClientEnumImportsFormat(fields, REACT);
}
/**
* get the an upperFirst camelCase value.
* @param {string} value string to convert
*/
upperFirstCamelCase(value) {
return upperFirstCamelCase(value);
}
}