diff options
author | Kristóf Marussy <kristof@marussy.com> | 2023-04-09 00:53:53 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2023-04-09 00:53:53 +0200 |
commit | c3fcc1ae3d2f680a973e66c138d9be7ae22eee26 (patch) | |
tree | 6a30b2478a30747cd7a2522acdff941394dea4e7 /subprojects | |
parent | build: always prefer slf4j instead of log4j 1.x (diff) | |
download | refinery-c3fcc1ae3d2f680a973e66c138d9be7ae22eee26.tar.gz refinery-c3fcc1ae3d2f680a973e66c138d9be7ae22eee26.tar.zst refinery-c3fcc1ae3d2f680a973e66c138d9be7ae22eee26.zip |
build: refactor frontend build
* Always write ESLint output to a file in addition to the console to make the
lintFrontend task cacheable in Gradle (according to the output file).
* Make sure frontend task inputs are declared properly for caching.
* Make sure Typescript type checking is incremental.
* Do not use @tsconfig, because both Vite and SonarScanner have problems with
extending tsconfig files from Yarn PnP modules.
Diffstat (limited to 'subprojects')
-rw-r--r-- | subprojects/frontend/.eslintrc.cjs | 3 | ||||
-rw-r--r-- | subprojects/frontend/build.gradle.kts | 75 | ||||
-rw-r--r-- | subprojects/frontend/config/eslintReport.cjs | 52 | ||||
-rw-r--r-- | subprojects/frontend/package.json | 4 | ||||
-rw-r--r-- | subprojects/frontend/tsconfig.base.json | 24 | ||||
-rw-r--r-- | subprojects/frontend/tsconfig.json | 1 | ||||
-rw-r--r-- | subprojects/frontend/tsconfig.node.json | 1 | ||||
-rw-r--r-- | subprojects/frontend/tsconfig.shared.json | 2 |
8 files changed, 119 insertions, 43 deletions
diff --git a/subprojects/frontend/.eslintrc.cjs b/subprojects/frontend/.eslintrc.cjs index 8a7b474a..dc03d721 100644 --- a/subprojects/frontend/.eslintrc.cjs +++ b/subprojects/frontend/.eslintrc.cjs | |||
@@ -90,6 +90,7 @@ module.exports = { | |||
90 | files: [ | 90 | files: [ |
91 | '.eslintrc.cjs', | 91 | '.eslintrc.cjs', |
92 | 'config/*.ts', | 92 | 'config/*.ts', |
93 | 'config/*.cjs', | ||
93 | 'prettier.config.cjs', | 94 | 'prettier.config.cjs', |
94 | 'vite.config.ts', | 95 | 'vite.config.ts', |
95 | ], | 96 | ], |
@@ -103,6 +104,8 @@ module.exports = { | |||
103 | 'error', | 104 | 'error', |
104 | { devDependencies: true }, | 105 | { devDependencies: true }, |
105 | ], | 106 | ], |
107 | // Allow writing to the console in ad-hoc scripts. | ||
108 | 'no-console': 'off', | ||
106 | // Access to the environment in configuration files. | 109 | // Access to the environment in configuration files. |
107 | 'no-process-env': 'off', | 110 | 'no-process-env': 'off', |
108 | }, | 111 | }, |
diff --git a/subprojects/frontend/build.gradle.kts b/subprojects/frontend/build.gradle.kts index 4a51c74e..8d2a8631 100644 --- a/subprojects/frontend/build.gradle.kts +++ b/subprojects/frontend/build.gradle.kts | |||
@@ -18,15 +18,45 @@ val productionAssets: Configuration by configurations.creating { | |||
18 | isCanBeResolved = false | 18 | isCanBeResolved = false |
19 | } | 19 | } |
20 | 20 | ||
21 | val sourcesWithoutTypeGen = fileTree("src") { | 21 | val sourcesWithoutTypes = fileTree("src") { |
22 | exclude("**/*.typegen.ts") | 22 | exclude("**/*.typegen.ts") |
23 | } | 23 | } |
24 | 24 | ||
25 | val sourcesWithTypes = fileTree("src") + fileTree("types") | ||
26 | |||
27 | val buildScripts = fileTree("config") + files( | ||
28 | ".eslintrc.cjs", | ||
29 | "prettier.config.cjs", | ||
30 | "vite.config.ts", | ||
31 | ) | ||
32 | |||
33 | val installationState = files( | ||
34 | rootProject.file("yarn.lock"), | ||
35 | rootProject.file("package.json"), | ||
36 | "package.json", | ||
37 | ) | ||
38 | |||
39 | val sharedConfigFiles = installationState + files( | ||
40 | "tsconfig.json", | ||
41 | "tsconfig.base.json", | ||
42 | "tsconfig.node.json", | ||
43 | "tsconfig.shared.json", | ||
44 | ) | ||
45 | |||
46 | val assembleConfigFiles = sharedConfigFiles + file("vite.config.ts") + fileTree("config") { | ||
47 | include("**/*.ts") | ||
48 | } | ||
49 | |||
50 | val assembleSources = sourcesWithTypes + fileTree("public") + file("index.html") | ||
51 | |||
52 | val assembleFiles = assembleSources + assembleConfigFiles | ||
53 | |||
54 | val lintingFiles = sourcesWithTypes + buildScripts + sharedConfigFiles | ||
55 | |||
25 | val generateXStateTypes by tasks.registering(RunYarn::class) { | 56 | val generateXStateTypes by tasks.registering(RunYarn::class) { |
26 | dependsOn(tasks.installFrontend) | 57 | dependsOn(tasks.installFrontend) |
27 | inputs.files(sourcesWithoutTypeGen) | 58 | inputs.files(sourcesWithoutTypes) |
28 | inputs.file("package.json") | 59 | inputs.files(installationState) |
29 | inputs.file(rootProject.file("yarn.lock")) | ||
30 | outputs.dir("src") | 60 | outputs.dir("src") |
31 | script.set("run typegen") | 61 | script.set("run typegen") |
32 | description = "Generate TypeScript typings for XState state machines." | 62 | description = "Generate TypeScript typings for XState state machines." |
@@ -34,11 +64,7 @@ val generateXStateTypes by tasks.registering(RunYarn::class) { | |||
34 | 64 | ||
35 | tasks.assembleFrontend { | 65 | tasks.assembleFrontend { |
36 | dependsOn(generateXStateTypes) | 66 | dependsOn(generateXStateTypes) |
37 | inputs.dir("public") | 67 | inputs.files(assembleFiles) |
38 | inputs.files(sourcesWithoutTypeGen) | ||
39 | inputs.file("index.html") | ||
40 | inputs.files("package.json", "tsconfig.json", "tsconfig.base.json", "vite.config.ts") | ||
41 | inputs.file(rootProject.file("yarn.lock")) | ||
42 | outputs.dir(productionResources) | 68 | outputs.dir(productionResources) |
43 | } | 69 | } |
44 | 70 | ||
@@ -51,10 +77,7 @@ artifacts { | |||
51 | val typeCheckFrontend by tasks.registering(RunYarn::class) { | 77 | val typeCheckFrontend by tasks.registering(RunYarn::class) { |
52 | dependsOn(tasks.installFrontend) | 78 | dependsOn(tasks.installFrontend) |
53 | dependsOn(generateXStateTypes) | 79 | dependsOn(generateXStateTypes) |
54 | inputs.dir("src") | 80 | inputs.files(lintingFiles) |
55 | inputs.dir("types") | ||
56 | inputs.files("package.json", "tsconfig.json", "tsconfig.base.json", "tsconfig.node.json") | ||
57 | inputs.file(rootProject.file("yarn.lock")) | ||
58 | outputs.dir("$buildDir/typescript") | 81 | outputs.dir("$buildDir/typescript") |
59 | script.set("run typecheck") | 82 | script.set("run typecheck") |
60 | group = "verification" | 83 | group = "verification" |
@@ -65,17 +88,9 @@ val lintFrontend by tasks.registering(RunYarn::class) { | |||
65 | dependsOn(tasks.installFrontend) | 88 | dependsOn(tasks.installFrontend) |
66 | dependsOn(generateXStateTypes) | 89 | dependsOn(generateXStateTypes) |
67 | dependsOn(typeCheckFrontend) | 90 | dependsOn(typeCheckFrontend) |
68 | inputs.dir("src") | 91 | inputs.files(lintingFiles) |
69 | inputs.dir("types") | 92 | outputs.file("$buildDir/eslint.json") |
70 | inputs.files(".eslintrc.cjs", "prettier.config.cjs") | 93 | script.set("run lint") |
71 | inputs.files("package.json", "tsconfig.json", "tsconfig.base.json", "tsconfig.node.json") | ||
72 | inputs.file(rootProject.file("yarn.lock")) | ||
73 | if (project.hasProperty("ci")) { | ||
74 | outputs.file("$buildDir/eslint.json") | ||
75 | script.set("run lint:ci") | ||
76 | } else { | ||
77 | script.set("run lint") | ||
78 | } | ||
79 | group = "verification" | 94 | group = "verification" |
80 | description = "Check for TypeScript lint errors and warnings." | 95 | description = "Check for TypeScript lint errors and warnings." |
81 | } | 96 | } |
@@ -84,11 +99,7 @@ val fixFrontend by tasks.registering(RunYarn::class) { | |||
84 | dependsOn(tasks.installFrontend) | 99 | dependsOn(tasks.installFrontend) |
85 | dependsOn(generateXStateTypes) | 100 | dependsOn(generateXStateTypes) |
86 | dependsOn(typeCheckFrontend) | 101 | dependsOn(typeCheckFrontend) |
87 | inputs.dir("src") | 102 | inputs.files(lintingFiles) |
88 | inputs.dir("types") | ||
89 | inputs.files(".eslintrc.cjs", "prettier.config.cjs") | ||
90 | inputs.files("package.json", "tsconfig.json", "tsconfig.base.json", "tsconfig.node.json") | ||
91 | inputs.file(rootProject.file("yarn.lock")) | ||
92 | script.set("run lint:fix") | 103 | script.set("run lint:fix") |
93 | group = "verification" | 104 | group = "verification" |
94 | description = "Fix TypeScript lint errors and warnings." | 105 | description = "Fix TypeScript lint errors and warnings." |
@@ -102,11 +113,7 @@ tasks.check { | |||
102 | tasks.register("serveFrontend", RunYarn::class) { | 113 | tasks.register("serveFrontend", RunYarn::class) { |
103 | dependsOn(tasks.installFrontend) | 114 | dependsOn(tasks.installFrontend) |
104 | dependsOn(generateXStateTypes) | 115 | dependsOn(generateXStateTypes) |
105 | inputs.dir("public") | 116 | inputs.files(assembleFiles) |
106 | inputs.files(sourcesWithoutTypeGen) | ||
107 | inputs.file("index.html") | ||
108 | inputs.files("package.json", "tsconfig.json", "tsconfig.base.json", "vite.config.ts") | ||
109 | inputs.file(rootProject.file("yarn.lock")) | ||
110 | outputs.dir("$viteOutputDir/development") | 117 | outputs.dir("$viteOutputDir/development") |
111 | script.set("run serve") | 118 | script.set("run serve") |
112 | group = "run" | 119 | group = "run" |
diff --git a/subprojects/frontend/config/eslintReport.cjs b/subprojects/frontend/config/eslintReport.cjs new file mode 100644 index 00000000..5bf6a041 --- /dev/null +++ b/subprojects/frontend/config/eslintReport.cjs | |||
@@ -0,0 +1,52 @@ | |||
1 | const { writeFile } = require('node:fs/promises'); | ||
2 | const path = require('node:path'); | ||
3 | const { Readable } = require('node:stream'); | ||
4 | const { pipeline } = require('node:stream/promises'); | ||
5 | |||
6 | const { ESLint } = require('eslint'); | ||
7 | |||
8 | const rootDir = path.join(__dirname, '..'); | ||
9 | |||
10 | /** | ||
11 | * Write ESLint report to console. | ||
12 | * | ||
13 | * @param cli {import('eslint').ESLint} The ESLint CLI. | ||
14 | * @param report {import('eslint').ESLint.LintResult[]} The ESLint report. | ||
15 | * @return {Promise<void>} A promise that resolves when the report is finished. | ||
16 | */ | ||
17 | async function reportToConsole(cli, report) { | ||
18 | const stylishFormatter = await cli.loadFormatter('stylish'); | ||
19 | const output = new Readable(); | ||
20 | output.push(await stylishFormatter.format(report)); | ||
21 | output.push(null); | ||
22 | return pipeline(output, process.stdout); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Write ESLint report to the <code>build</code> directory. | ||
27 | * | ||
28 | * @param cli {import('eslint').ESLint} The ESLint CLI. | ||
29 | * @param report {import('eslint').ESLint.LintResult[]} The ESLint report. | ||
30 | * @return {Promise<void>} A promise that resolves when the report is finished. | ||
31 | */ | ||
32 | async function reportToJson(cli, report) { | ||
33 | const jsonFormatter = await cli.loadFormatter('json'); | ||
34 | const json = await jsonFormatter.format(report); | ||
35 | const reportPath = path.join(rootDir, 'build', 'eslint.json'); | ||
36 | return writeFile(reportPath, json, 'utf-8'); | ||
37 | } | ||
38 | |||
39 | async function createReport() { | ||
40 | const cli = new ESLint({ | ||
41 | useEslintrc: true, | ||
42 | cwd: rootDir, | ||
43 | }); | ||
44 | const report = await cli.lintFiles('.'); | ||
45 | await Promise.all([reportToConsole(cli, report), reportToJson(cli, report)]); | ||
46 | |||
47 | if (report.some((entry) => entry.errorCount > 0)) { | ||
48 | process.exitCode = 1; | ||
49 | } | ||
50 | } | ||
51 | |||
52 | createReport().catch(console.error); | ||
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json index 8b4a3f71..e6bcc89e 100644 --- a/subprojects/frontend/package.json +++ b/subprojects/frontend/package.json | |||
@@ -9,8 +9,7 @@ | |||
9 | "serve": "cross-env MODE=development vite serve", | 9 | "serve": "cross-env MODE=development vite serve", |
10 | "typegen": "xstate typegen \"src/**/*.ts?(x)\"", | 10 | "typegen": "xstate typegen \"src/**/*.ts?(x)\"", |
11 | "typecheck": "tsc -p tsconfig.shared.json && tsc -p tsconfig.node.json && tsc -p tsconfig.json", | 11 | "typecheck": "tsc -p tsconfig.shared.json && tsc -p tsconfig.node.json && tsc -p tsconfig.json", |
12 | "lint": "eslint .", | 12 | "lint": "node config/eslintReport.cjs", |
13 | "lint:ci": "eslint -f json -o build/eslint.json .", | ||
14 | "lint:fix": "yarn run lint --fix" | 13 | "lint:fix": "yarn run lint --fix" |
15 | }, | 14 | }, |
16 | "repository": { | 15 | "repository": { |
@@ -60,7 +59,6 @@ | |||
60 | }, | 59 | }, |
61 | "devDependencies": { | 60 | "devDependencies": { |
62 | "@lezer/generator": "^1.2.2", | 61 | "@lezer/generator": "^1.2.2", |
63 | "@tsconfig/strictest": "^2.0.0", | ||
64 | "@types/eslint": "^8.37.0", | 62 | "@types/eslint": "^8.37.0", |
65 | "@types/html-minifier-terser": "^7.0.0", | 63 | "@types/html-minifier-terser": "^7.0.0", |
66 | "@types/lodash-es": "^4.17.7", | 64 | "@types/lodash-es": "^4.17.7", |
diff --git a/subprojects/frontend/tsconfig.base.json b/subprojects/frontend/tsconfig.base.json index b960e93c..30e707ae 100644 --- a/subprojects/frontend/tsconfig.base.json +++ b/subprojects/frontend/tsconfig.base.json | |||
@@ -1,10 +1,28 @@ | |||
1 | { | 1 | { |
2 | "extends": "@tsconfig/strictest", | ||
3 | "compilerOptions": { | 2 | "compilerOptions": { |
4 | "useDefineForClassFields": true, | 3 | "strict": true, |
4 | "allowUnusedLabels": false, | ||
5 | "allowUnreachableCode": false, | ||
6 | "exactOptionalPropertyTypes": true, | ||
7 | "noFallthroughCasesInSwitch": true, | ||
8 | "noImplicitOverride": true, | ||
9 | "noImplicitReturns": true, | ||
10 | "noPropertyAccessFromIndexSignature": true, | ||
11 | "noUncheckedIndexedAccess": true, | ||
12 | "noUnusedLocals": true, | ||
13 | "noUnusedParameters": true, | ||
5 | "verbatimModuleSyntax": false, | 14 | "verbatimModuleSyntax": false, |
6 | "isolatedModules": true, | 15 | "isolatedModules": true, |
16 | "checkJs": true, | ||
17 | "esModuleInterop": true, | ||
18 | "skipLibCheck": true, | ||
19 | "forceConsistentCasingInFileNames": true, | ||
20 | "useDefineForClassFields": true, | ||
7 | "module": "es2022", | 21 | "module": "es2022", |
8 | "moduleResolution": "node" | 22 | "moduleResolution": "node", |
23 | "incremental": true, | ||
24 | "declaration": true, | ||
25 | "emitDeclarationOnly": true, | ||
26 | "outDir": "build/typescript" | ||
9 | } | 27 | } |
10 | } | 28 | } |
diff --git a/subprojects/frontend/tsconfig.json b/subprojects/frontend/tsconfig.json index 35abd789..35d0d164 100644 --- a/subprojects/frontend/tsconfig.json +++ b/subprojects/frontend/tsconfig.json | |||
@@ -2,7 +2,6 @@ | |||
2 | "extends": "./tsconfig.base.json", | 2 | "extends": "./tsconfig.base.json", |
3 | "compilerOptions": { | 3 | "compilerOptions": { |
4 | "jsx": "react-jsx", | 4 | "jsx": "react-jsx", |
5 | "noEmit": true, | ||
6 | "lib": ["DOM", "DOM.Iterable", "ES2022"], | 5 | "lib": ["DOM", "DOM.Iterable", "ES2022"], |
7 | "types": ["vite/client", "vite-plugin-pwa/client"] | 6 | "types": ["vite/client", "vite-plugin-pwa/client"] |
8 | }, | 7 | }, |
diff --git a/subprojects/frontend/tsconfig.node.json b/subprojects/frontend/tsconfig.node.json index f4908bcb..cfa2da13 100644 --- a/subprojects/frontend/tsconfig.node.json +++ b/subprojects/frontend/tsconfig.node.json | |||
@@ -10,6 +10,7 @@ | |||
10 | "include": [ | 10 | "include": [ |
11 | ".eslintrc.cjs", | 11 | ".eslintrc.cjs", |
12 | "config/*.ts", | 12 | "config/*.ts", |
13 | "config/*.cjs", | ||
13 | "prettier.config.cjs", | 14 | "prettier.config.cjs", |
14 | "types/node", | 15 | "types/node", |
15 | "vite.config.ts" | 16 | "vite.config.ts" |
diff --git a/subprojects/frontend/tsconfig.shared.json b/subprojects/frontend/tsconfig.shared.json index b7e1de55..f7b56a1d 100644 --- a/subprojects/frontend/tsconfig.shared.json +++ b/subprojects/frontend/tsconfig.shared.json | |||
@@ -4,8 +4,6 @@ | |||
4 | "composite": true, | 4 | "composite": true, |
5 | "lib": ["ES2022"], | 5 | "lib": ["ES2022"], |
6 | "types": [], | 6 | "types": [], |
7 | "emitDeclarationOnly": true, | ||
8 | "outDir": "build/typescript" | ||
9 | }, | 7 | }, |
10 | "include": [ | 8 | "include": [ |
11 | "src/xtext/BackendConfig.ts", | 9 | "src/xtext/BackendConfig.ts", |