Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: react module now depends on typescript module #10914

Merged
merged 6 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import static tech.jhipster.lite.module.domain.JHipsterModule.*;
import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.COMMON;
import static tech.jhipster.lite.module.domain.npm.JHLiteNpmVersionSource.REACT;
import static tech.jhipster.lite.module.domain.packagejson.NodeModuleFormat.MODULE;

import java.util.function.Consumer;
import tech.jhipster.lite.module.domain.Indentation;
import tech.jhipster.lite.module.domain.JHipsterModule;
import tech.jhipster.lite.module.domain.file.JHipsterDestination;
import tech.jhipster.lite.module.domain.file.JHipsterSource;
import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
import tech.jhipster.lite.module.domain.replacement.MandatoryReplacer;

public class ReactCoreModulesFactory {

Expand All @@ -27,56 +29,38 @@ public class ReactCoreModulesFactory {
private static final JHipsterDestination PRIMARY_APP_DESTINATION = APP_DESTINATION.append(PRIMARY_APP);

private static final String TEST_PRIMARY = "src/test/webapp/unit/common/primary/app";
private static final String DEFAULT_TSCONFIG_PATH = "\"@/*\": [\"src/main/webapp/app/*\"]";

public JHipsterModule buildModule(JHipsterModuleProperties properties) {
//@formatter:off
return moduleBuilder(properties)
.preCommitActions(stagedFilesFilter("{src/**/,}*.{ts,tsx}"), preCommitCommands("eslint --fix", "prettier --write"))
.packageJson()
.type(MODULE)
.removeDevDependency(packageName("@tsconfig/recommended"))
.addDevDependency(packageName("@testing-library/dom"), REACT)
.addDevDependency(packageName("@testing-library/react"), REACT)
.addDevDependency(packageName("@types/node"), COMMON)
.addDevDependency(packageName("@types/react"), REACT)
.addDevDependency(packageName("@types/react-dom"), REACT)
.addDevDependency(packageName("@tsconfig/vite-react"), REACT)
.addDevDependency(packageName("@typescript-eslint/eslint-plugin"), COMMON)
.addDevDependency(packageName("@vitejs/plugin-react"), REACT)
.addDevDependency(packageName("@vitest/coverage-istanbul"), COMMON)
.addDevDependency(packageName("typescript-eslint"), COMMON)
.addDevDependency(packageName("globals"), COMMON)
.addDevDependency(packageName("eslint"), COMMON)
.addDevDependency(packageName("eslint-plugin-react"), REACT)
.addDevDependency(packageName("jsdom"), COMMON)
.addDevDependency(packageName("typescript"), COMMON)
.addDevDependency(packageName("ts-node"), REACT)
.addDevDependency(packageName("vite"), COMMON)
.addDevDependency(packageName("vite-tsconfig-paths"), COMMON)
.addDevDependency(packageName("vitest"), COMMON)
.addDevDependency(packageName("vitest-sonar-reporter"), COMMON)
.addDependency(packageName("react"), REACT)
.addDependency(packageName("react-dom"), REACT)
.addDevDependency(packageName("npm-run-all2"), COMMON)
.addScript(scriptKey("dev"), scriptCommand("npm-run-all dev:*"))
.addScript(scriptKey("dev:vite"), scriptCommand("vite"))
.addScript(scriptKey("build"), scriptCommand("npm-run-all build:*"))
.addScript(scriptKey("build:tsc"), scriptCommand("tsc"))
.addScript(scriptKey("build:vite"), scriptCommand("vite build --emptyOutDir"))
.addScript(scriptKey("preview"), scriptCommand("vite preview"))
.addScript(scriptKey("start"), scriptCommand("vite"))
.addScript(scriptKey("lint"), scriptCommand("eslint ."))
.addScript(scriptKey("watch"), scriptCommand("npm-run-all --parallel watch:*"))
.addScript(scriptKey("watch:tsc"), scriptCommand("tsc --noEmit --watch"))
.addScript(scriptKey("test"), scriptCommand("npm run watch:test"))
.addScript(scriptKey("test:coverage"), scriptCommand("vitest run --coverage"))
.addScript(scriptKey("watch:test"), scriptCommand("vitest --"))
.and()
.files()
.batch(SOURCE, to("."))
.addFile("tsconfig.json")
.addTemplate("vite.config.ts")
.addTemplate("vitest.config.ts")
.addTemplate("eslint.config.js")
.and()
.batch(APP_SOURCE, APP_DESTINATION)
.addTemplate("index.css")
Expand All @@ -92,7 +76,93 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) {
.addFile("ReactLogo.png")
.and()
.and()
.apply(patchEslintConfig(properties))
.apply(patchTsConfig(properties))
.apply(patchVitestConfig(properties))
.build();
//@formatter:on
}

private Consumer<JHipsterModuleBuilder> patchEslintConfig(JHipsterModuleProperties properties) {
String reactConfig =
"""
\t\tfiles: ['src/main/webapp/**/*.{ts,tsx}', 'src/main/webapp/**/*.spec.ts'],
\t\textends: [...typescript.configs.recommendedTypeChecked, react],
\t\tsettings: {
\t\t\treact: {
\t\t\t\tversion: 'detect',
\t\t\t},
\t\t},\
""".replace("\t", properties.indentation().spaces());
//@formatter:off
return moduleBuilder -> moduleBuilder
.mandatoryReplacements()
.in(path("eslint.config.js"))
.add(lineAfterRegex("from 'typescript-eslint'"), "import react from 'eslint-plugin-react/configs/recommended.js';")
.add(regex("\\s+\\.\\.\\.typescript\\.configs\\.recommended.*"), "")
.add(regex("[ \\t]+files: \\['src/\\*/webapp/\\*\\*\\/\\*\\.ts'],"), reactConfig)
.add(
lineAfterRegex("globals: \\{ \\.\\.\\.globals\\.browser },"),
"""
\t\t\tparserOptions: {
\t\t\t\tproject: ['./tsconfig.json'],
\t\t\t},\
""".replace("\t", properties.indentation().spaces())
)
.add(
regex("[ \\t]+quotes: \\['error', 'single', \\{ avoidEscape: true }],"),
"""
\t\t\t'react/react-in-jsx-scope': 'off',
\t\t\t'@typescript-eslint/no-misused-promises': [
\t\t\t\t'error',
\t\t\t\t{
\t\t\t\t\tchecksVoidReturn: false,
\t\t\t\t}
\t\t\t],\
""".replace("\t", properties.indentation().spaces())
)
.and()
.and();
//@formatter:on
}

private Consumer<JHipsterModuleBuilder> patchTsConfig(JHipsterModuleProperties properties) {
String pathsReplacement =
DEFAULT_TSCONFIG_PATH + "," + LINE_BREAK + properties.indentation().times(3) + "\"@assets/*\": [\"src/main/webapp/assets/*\"]";
//@formatter:off
return moduleBuilder -> moduleBuilder
.mandatoryReplacements()
.in(path("tsconfig.json"))
.add(text("@tsconfig/recommended/tsconfig.json"), "@tsconfig/vite-react/tsconfig.json")
.add(tsConfigCompilerOption("composite", false, properties.indentation()))
.add(tsConfigCompilerOption("forceConsistentCasingInFileNames", true, properties.indentation()))
.add(tsConfigCompilerOption("allowSyntheticDefaultImports", true, properties.indentation()))
.add(text(DEFAULT_TSCONFIG_PATH), pathsReplacement)
.and()
.and();
//@formatter:on
}

private static MandatoryReplacer tsConfigCompilerOption(String optionName, boolean optionValue, Indentation indentation) {
String compilerOption = indentation.times(2) + "\"%s\": %s,".formatted(optionName, optionValue);
return new MandatoryReplacer(lineAfterRegex("\"compilerOptions\":"), compilerOption);
}

private Consumer<JHipsterModuleBuilder> patchVitestConfig(JHipsterModuleProperties properties) {
//@formatter:off
return moduleBuilder -> moduleBuilder
.mandatoryReplacements()
.in(path("vitest.config.ts"))
.add(lineAfterRegex("from 'vitest/config';"), "import react from '@vitejs/plugin-react';")
.add(text( "plugins: ["), "plugins: [react(), ")
.add(text("environment: 'node',"), "environment: 'jsdom',")
.add(vitestCoverageExclusion(properties,"src/main/webapp/app/index.tsx"))
.and();
//@formatter:on
}

private static MandatoryReplacer vitestCoverageExclusion(JHipsterModuleProperties properties, String filePattern) {
Indentation indentation = properties.indentation();
return new MandatoryReplacer(lineAfterRegex("configDefaults.coverage.exclude"), indentation.times(4) + "'" + filePattern + "',");
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package tech.jhipster.lite.generator.client.react.core.infrastructure.primary;

import static tech.jhipster.lite.generator.slug.domain.JHLiteFeatureSlug.*;
import static tech.jhipster.lite.generator.slug.domain.JHLiteFeatureSlug.CLIENT_CORE;
import static tech.jhipster.lite.generator.slug.domain.JHLiteModuleSlug.*;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tech.jhipster.lite.generator.client.react.core.application.ReactCoreApplicationService;
import tech.jhipster.lite.module.domain.resource.JHipsterModuleOrganization;
import tech.jhipster.lite.module.domain.resource.JHipsterModulePropertiesDefinition;
import tech.jhipster.lite.module.domain.resource.JHipsterModuleResource;
import tech.jhipster.lite.module.domain.resource.*;

@Configuration
class ReactCoreModulesConfiguration {
Expand All @@ -21,7 +19,7 @@ JHipsterModuleResource reactCoreModule(ReactCoreApplicationService react) {
.slug(REACT_CORE)
.propertiesDefinition(properties())
.apiDoc("Frontend - React", "Add React+Vite with minimal CSS")
.organization(JHipsterModuleOrganization.builder().feature(CLIENT_CORE).addDependency(INIT).addDependency(PRETTIER).build())
.organization(JHipsterModuleOrganization.builder().feature(CLIENT_CORE).addDependency(TYPESCRIPT).addDependency(PRETTIER).build())
.tags("client", REACT)
.factory(react::buildModule);
}
Expand Down
1 change: 1 addition & 0 deletions src/test/features/client/react-i18n.feature
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Feature: React i18n
Scenario: Should apply react i18n module to react
When I apply modules to default project
| init |
| typescript |
| react-core |
| react-i18next |
Then I should have files in "src/main/webapp/app"
Expand Down
1 change: 1 addition & 0 deletions src/test/features/client/react-jwt.feature
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Feature: React JWT
Scenario: Should apply react jwt module to react
When I apply modules to default project
| init |
| typescript |
| react-core |
| react-jwt |
Then I should have files in "src/main/webapp/app/common/services"
Expand Down
1 change: 1 addition & 0 deletions src/test/features/client/react.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Feature: React modules
When I apply modules to default project
| init |
| prettier |
| typescript |
| react-core |
Then I should have files in "src/main/webapp/app/common/primary/app"
| App.tsx |
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,29 @@ void shouldBuildModuleWithStyle() {
JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).projectBaseName("jhipster").build()
);

assertThatModuleWithFiles(module, packageJsonFile(), lintStagedConfigFile())
assertThatModuleWithFiles(module, packageJsonFile(), lintStagedConfigFile(), eslintConfigFile(), tsConfigFile(), vitestConfigFile())
.hasFile("package.json")
.containing("\"type\": \"module\"")
.notContaining(nodeDependency("@tsconfig/recommended"))
.containing(nodeDependency("@testing-library/dom"))
.containing(nodeDependency("@testing-library/react"))
.containing(nodeDependency("@types/node"))
.containing(nodeDependency("@types/react"))
.containing(nodeDependency("@types/react-dom"))
.containing(nodeDependency("@tsconfig/vite-react"))
.containing(nodeDependency("@typescript-eslint/eslint-plugin"))
.containing(nodeDependency("@vitejs/plugin-react"))
.containing(nodeDependency("@vitest/coverage-istanbul"))
.containing(nodeDependency("typescript-eslint"))
.containing(nodeDependency("globals"))
.containing(nodeDependency("eslint"))
.containing(nodeDependency("eslint-plugin-react"))
.containing(nodeDependency("jsdom"))
.containing(nodeDependency("typescript"))
.containing(nodeDependency("ts-node"))
.containing(nodeDependency("vite"))
.containing(nodeDependency("vite-tsconfig-paths"))
.containing(nodeDependency("vitest"))
.containing(nodeDependency("vitest-sonar-reporter"))
.containing(nodeDependency("react"))
.containing(nodeDependency("npm-run-all2"))
.containing(nodeDependency("react-dom"))
.containing(nodeScript("dev", "npm-run-all dev:*"))
.containing(nodeScript("dev:vite", "vite"))
.containing(nodeScript("build", "npm-run-all build:*"))
.containing(nodeScript("build:tsc", "tsc"))
.containing(nodeScript("build:vite", "vite build --emptyOutDir"))
.containing(nodeScript("watch", "npm-run-all --parallel watch:*"))
.containing(nodeScript("watch:tsc", "tsc --noEmit --watch"))
.containing(nodeScript("preview", "vite preview"))
.containing(nodeScript("start", "vite"))
.containing(nodeScript("lint", "eslint ."))
.containing(nodeScript("test", "npm run watch:test"))
.containing(nodeScript("test:coverage", "vitest run --coverage"))
.containing(nodeScript("watch:test", "vitest --"))
.and()
.hasFile(".lintstagedrc.cjs")
.containing(
Expand All @@ -69,7 +53,16 @@ void shouldBuildModuleWithStyle() {
"""
)
.and()
.hasFiles("tsconfig.json", "vite.config.ts", "vitest.config.ts", "eslint.config.js")
.hasFile("eslint.config.js")
.matchingSavedSnapshot()
.and()
.hasFile("tsconfig.json")
.matchingSavedSnapshot()
.and()
.hasFile("vitest.config.ts")
.matchingSavedSnapshot()
.and()
.hasFiles("vite.config.ts")
.hasFiles("src/main/webapp/index.html")
.hasPrefixedFiles("src/main/webapp/app", "index.css", "index.tsx", "vite-env.d.ts")
.hasFiles("src/test/webapp/unit/common/primary/app/App.spec.tsx")
Expand All @@ -86,7 +79,7 @@ void shouldViteConfigBeUpdatedWhenServerPortPropertyNotDefault() {
JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).projectBaseName("jhipster").put("serverPort", 8081).build()
);

assertThatModuleWithFiles(module, packageJsonFile())
assertThatModuleWithFiles(module, packageJsonFile(), eslintConfigFile(), tsConfigFile(), vitestConfigFile())
.hasFile("vite.config.ts")
.containing("localhost:8081")
.notContaining("localhost:8080");
Expand All @@ -98,7 +91,9 @@ void shouldViteConfigBeDefaultWhenServerPortPropertyMissing() {
JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).projectBaseName("jhipster").build()
);

assertThatModuleWithFiles(module, packageJsonFile()).hasFile("vite.config.ts").containing("localhost:8080");
assertThatModuleWithFiles(module, packageJsonFile(), eslintConfigFile(), tsConfigFile(), vitestConfigFile())
.hasFile("vite.config.ts")
.containing("localhost:8080");
}

private String nodeScript(String key, String value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import js from '@eslint/js';
import globals from 'globals';
import typescript from 'typescript-eslint';
import eslint from '@eslint/js';
import react from 'eslint-plugin-react/configs/recommended.js';

export default typescript.config(
Expand All @@ -12,9 +12,9 @@ export default typescript.config(
},
},
{
ignores: ['{{projectBuildDirectory}}/'],
ignores: ['target/'],
},
eslint.configs.recommended,
js.configs.recommended,
{
files: ['src/main/webapp/**/*.{ts,tsx}', 'src/main/webapp/**/*.spec.ts'],
extends: [...typescript.configs.recommendedTypeChecked, react],
Expand All @@ -24,9 +24,7 @@ export default typescript.config(
},
},
languageOptions: {
globals: {
...globals.browser,
},
globals: { ...globals.browser },
parserOptions: {
project: ['./tsconfig.json'],
},
Expand All @@ -36,7 +34,7 @@ export default typescript.config(
'@typescript-eslint/no-misused-promises': [
'error',
{
'checksVoidReturn': false
checksVoidReturn: false,
}
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"composite": false,
"baseUrl": ".",
"types": ["vitest/globals"],
"baseUrl": ".",
"paths": {
"@/*": ["src/main/webapp/app/*"],
"@assets/*": ["src/main/webapp/assets/*"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default defineConfig({
test: {
reporters: ['verbose', 'vitest-sonar-reporter'],
outputFile: {
'vitest-sonar-reporter': '{{projectBuildDirectory}}/test-results/TESTS-results-sonar.xml',
'vitest-sonar-reporter': 'target/test-results/TESTS-results-sonar.xml',
},
globals: true,
logHeapUsage: true,
Expand All @@ -34,7 +34,7 @@ export default defineConfig({
'src/main/webapp/app/index.tsx',
],
provider: 'istanbul',
reportsDirectory: '{{projectBuildDirectory}}/test-results/',
reportsDirectory: 'target/test-results/',
reporter: ['html', 'json-summary', 'text', 'text-summary', 'lcov', 'clover'],
watermarks: {
statements: [100, 100],
Expand Down
1 change: 1 addition & 0 deletions tests-ci/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ elif [[ $application == 'reactapp' ]]; then
frontend_server_plugin
applyModules \
"prettier" \
"typescript" \
"react-core" \
"cypress-component-tests"

Expand Down
Loading