From 22f49db21f9952ad2ccfded6387470bb1c50a3c4 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 6 Oct 2025 00:01:01 -0400 Subject: [PATCH] modernize frontend to target the same ES and TS standards as the rest of the app --- packages/frontend/.storybook/fake-utils.ts | 8 +- packages/frontend/.storybook/fakes.ts | 20 ++--- packages/frontend/.storybook/generate.tsx | 8 +- packages/frontend/.storybook/mocks.ts | 4 +- .../frontend/.storybook/preload-locale.ts | 2 +- packages/frontend/.storybook/preload-theme.ts | 2 +- packages/frontend/.storybook/preview.ts | 4 +- .../frontend/.storybook/tsconfig.gen.json | 17 ++++ packages/frontend/.storybook/tsconfig.json | 34 ++------ .../frontend/.storybook/tsconfig.vue.json | 19 +++++ packages/frontend/eslint.config.js | 84 ++++++++++++++++--- ...lup-plugin-unwind-css-module-class-name.ts | 4 +- .../lib/vite-plugin-create-search-index.ts | 14 ++-- packages/frontend/package.json | 11 ++- packages/frontend/src/workers/tsconfig.json | 2 +- packages/frontend/test/tsconfig.json | 33 +------- packages/frontend/tsconfig.json | 65 ++------------ packages/frontend/tsconfig.scripts.json | 26 ++++++ packages/frontend/tsconfig.vue.json | 35 ++++++++ packages/frontend/vite.config.ts | 23 +++-- packages/frontend/vite.json5.ts | 4 +- 21 files changed, 249 insertions(+), 170 deletions(-) create mode 100644 packages/frontend/.storybook/tsconfig.gen.json create mode 100644 packages/frontend/.storybook/tsconfig.vue.json create mode 100644 packages/frontend/tsconfig.scripts.json create mode 100644 packages/frontend/tsconfig.vue.json diff --git a/packages/frontend/.storybook/fake-utils.ts b/packages/frontend/.storybook/fake-utils.ts index 44e2263ca0..29ceb311f9 100644 --- a/packages/frontend/.storybook/fake-utils.ts +++ b/packages/frontend/.storybook/fake-utils.ts @@ -12,7 +12,7 @@ export const firstNameDict = [ 'Ethan', 'Olivia', 'Jackson', 'Emma', 'Liam', 'Ava', 'Aiden', 'Sophia', 'Mason', 'Isabella', 'Noah', 'Mia', 'Lucas', 'Harper', 'Caleb', 'Abigail', 'Samuel', 'Emily', 'Logan', 'Madison', 'Benjamin', 'Chloe', 'Elijah', 'Grace', 'Alexander', 'Scarlett', 'William', 'Zoey', 'James', 'Lily', -] +]; /** * AIで生成した無作為なラストネーム @@ -21,7 +21,7 @@ export const lastNameDict = [ 'Anderson', 'Johnson', 'Thompson', 'Davis', 'Rodriguez', 'Smith', 'Patel', 'Williams', 'Lee', 'Brown', 'Garcia', 'Jackson', 'Martinez', 'Taylor', 'Harris', 'Nguyen', 'Miller', 'Jones', 'Wilson', 'White', 'Thomas', 'Garcia', 'Martinez', 'Robinson', 'Turner', 'Lewis', 'Hall', 'King', 'Baker', 'Cooper', -] +]; /** * AIで生成した無作為な国名 @@ -30,7 +30,7 @@ export const countryDict = [ 'Japan', 'Canada', 'Brazil', 'Australia', 'Italy', 'SouthAfrica', 'Mexico', 'Sweden', 'Russia', 'India', 'Germany', 'Argentina', 'South Korea', 'France', 'Nigeria', 'Turkey', 'Spain', 'Egypt', 'Thailand', 'Vietnam', 'Kenya', 'Saudi Arabia', 'Netherlands', 'Colombia', 'Poland', 'Chile', 'Malaysia', 'Ukraine', 'New Zealand', 'Peru', -] +]; export function text(length: number = 10, seed?: string): string { let result = ""; @@ -140,7 +140,7 @@ export function imageDataUrl(options?: { throw new Error('Failed to get 2d context'); } - ctx.beginPath() + ctx.beginPath(); const red = options?.color?.red ?? integer(0, 255, seed); const green = options?.color?.green ?? integer(0, 255, seed); diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index 7288dabb60..3105f148d7 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -4,7 +4,7 @@ */ import { AISCRIPT_VERSION } from '@syuilo/aiscript'; -import type { entities } from 'misskey-js' +import type { entities } from 'misskey-js'; import { date, imageDataUrl, text } from "./fake-utils.js"; export function abuseUserReport() { @@ -124,7 +124,7 @@ export function galleryPost(isSensitive = false) { isSensitive, likedCount: 0, isLiked: false, - } + }; } export function file(isSensitive = false) { @@ -318,13 +318,13 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host: enti export function inviteCode(isUsed = false, hasExpiration = false, isExpired = false, isCreatedBySystem = false) { const date = new Date(); const createdAt = new Date(); - createdAt.setDate(date.getDate() - 1) + createdAt.setDate(date.getDate() - 1); const expiresAt = new Date(); if (isExpired) { - expiresAt.setHours(date.getHours() - 1) + expiresAt.setHours(date.getHours() - 1); } else { - expiresAt.setHours(date.getHours() + 1) + expiresAt.setHours(date.getHours() + 1); } return { @@ -336,7 +336,7 @@ export function inviteCode(isUsed = false, hasExpiration = false, isExpired = fa usedBy: isUsed ? userDetailed('3i3r2znx1v') : null, usedAt: isUsed ? date.toISOString() : null, used: isUsed, - } + }; } export function role(params: { @@ -385,7 +385,7 @@ export function role(params: { values: [] }, policies: {}, - } + }; } export function emoji(params?: { @@ -401,7 +401,7 @@ export function emoji(params?: { license?: string, isSensitive?: boolean, localOnly?: boolean, - roleIdsThatCanBeUsedThisEmojiAsReaction?: {id:string, name:string}[], + roleIdsThatCanBeUsedThisEmojiAsReaction?: { id: string, name: string }[], updatedAt?: string, }, seed?: string): entities.EmojiDetailedAdmin { const _seed = seed ?? (params?.id ?? "DEFAULT_SEED"); @@ -409,7 +409,7 @@ export function emoji(params?: { const name = params?.name ?? text(8, _seed); const updatedAt = params?.updatedAt ?? date({}, _seed).toISOString(); - const image = imageDataUrl({}, _seed) + const image = imageDataUrl({}, _seed); return { id: id, @@ -426,5 +426,5 @@ export function emoji(params?: { localOnly: params?.localOnly ?? false, roleIdsThatCanBeUsedThisEmojiAsReaction: params?.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [], updatedAt: updatedAt, - } + }; } diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index 89d4214141..00eed832cf 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -72,7 +72,7 @@ const generator = { break; } default: { - // @ts-ignore + // @ts-expect-error ??? this[node.expression.type](node.expression, state); break; } @@ -94,7 +94,7 @@ type SplitCamel< : SplitCamel : YN; -// @ts-ignore +// @ts-expect-error ??? type SplitKebab = T extends `${infer XH}-${infer XR}` ? [XH, ...SplitKebab] : [T]; @@ -110,7 +110,7 @@ type ToKebab = T extends readonly [ ? `${XH}${XR extends readonly string[] ? `-${ToKebab}` : ''}` : ''; -// @ts-ignore +// @ts-expect-error ??? type ToPascal = T extends readonly [ infer XH extends string, ...infer XR extends readonly string[] @@ -474,5 +474,5 @@ function toStories(component: string): Promise { await Promise.all(components.map(async (component) => { const stories = component.replace(/\.vue$/, '.stories.ts'); await writeFile(stories, await toStories(component)); - })) + })); })(); diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts index 29cb112ccb..870c364059 100644 --- a/packages/frontend/.storybook/mocks.ts +++ b/packages/frontend/.storybook/mocks.ts @@ -8,9 +8,9 @@ import { type SharedOptions, http, HttpResponse } from 'msw'; export const onUnhandledRequest = ((req, print) => { const url = new URL(req.url); if (url.hostname !== 'localhost' || /^\/(?:client-assets\/|fluent-emojis?\/|iframe.html$|node_modules\/|src\/|sb-|static-assets\/|vite\/)/.test(url.pathname)) { - return + return; } - print.warning() + print.warning(); }) satisfies SharedOptions['onUnhandledRequest']; export const commonHandlers = [ diff --git a/packages/frontend/.storybook/preload-locale.ts b/packages/frontend/.storybook/preload-locale.ts index c823ff9bee..b9b092262d 100644 --- a/packages/frontend/.storybook/preload-locale.ts +++ b/packages/frontend/.storybook/preload-locale.ts @@ -10,4 +10,4 @@ await writeFile( new URL('locale.ts', import.meta.url), `export default ${JSON.stringify(locales['ja-JP'], undefined, 2)} as const;`, 'utf8', -) +); diff --git a/packages/frontend/.storybook/preload-theme.ts b/packages/frontend/.storybook/preload-theme.ts index 1b6a605a6e..1f9969bcb6 100644 --- a/packages/frontend/.storybook/preload-theme.ts +++ b/packages/frontend/.storybook/preload-theme.ts @@ -30,7 +30,7 @@ const keys = [ 'd-u0', 'rosepine', 'rosepine-dawn', -] +]; await Promise.all(keys.map((key) => readFile(new URL(`../../frontend-shared/themes/${key}.json5`, import.meta.url), 'utf8'))).then((sources) => { writeFile( diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts index fb855c1410..593287dbdf 100644 --- a/packages/frontend/.storybook/preview.ts +++ b/packages/frontend/.storybook/preview.ts @@ -99,13 +99,13 @@ const preview = { const channel = addons.getChannel(); const resetIndexedDBPromise = globalThis.indexedDB?.databases ? indexedDB.databases().then((r) => { - for (var i = 0; i < r.length; i++) { + for (let i = 0; i < r.length; i++) { indexedDB.deleteDatabase(r[i].name!); } }).catch(() => {}) : Promise.resolve(); const resetDefaultStorePromise = import('../src/store').then(({ store }) => { - // @ts-expect-error + // @ts-expect-error ??? store.init(); }).catch(() => {}); Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => { diff --git a/packages/frontend/.storybook/tsconfig.gen.json b/packages/frontend/.storybook/tsconfig.gen.json new file mode 100644 index 0000000000..c85994ecdc --- /dev/null +++ b/packages/frontend/.storybook/tsconfig.gen.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../tsconfig.vue.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "jsxFactory": "h" + }, + "files": [ + "./changes.ts", + "./generate.tsx", + "./preload-locale.ts", + "./preload-theme.ts" + ] +} diff --git a/packages/frontend/.storybook/tsconfig.json b/packages/frontend/.storybook/tsconfig.json index 18baf516ba..0a79681e84 100644 --- a/packages/frontend/.storybook/tsconfig.json +++ b/packages/frontend/.storybook/tsconfig.json @@ -1,31 +1,9 @@ { - "compilerOptions": { - "target": "es2022", - "module": "Node16", - "strict": true, - "allowUnusedLabels": false, - "allowUnreachableCode": false, - "exactOptionalPropertyTypes": true, - "noEmitOnError": false, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": true, - "noUncheckedIndexedAccess": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "checkJs": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "incremental": true, - "jsx": "react", - "jsxFactory": "h" - }, - "files": [ - "./changes.ts", - "./generate.tsx", - "./preload-locale.ts", - "./preload-theme.ts" + "$schema": "https://json.schemastore.org/tsconfig", + "files": [], + // WebStorm only reads one tsconfig per directory, so this tricks it into loading both. + "references": [ + { "path": "./tsconfig.gen.json" }, + { "path": "./tsconfig.vue.json" } ] } diff --git a/packages/frontend/.storybook/tsconfig.vue.json b/packages/frontend/.storybook/tsconfig.vue.json new file mode 100644 index 0000000000..e6e6df85b0 --- /dev/null +++ b/packages/frontend/.storybook/tsconfig.vue.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../tsconfig.vue.json", + "compilerOptions": { + "jsx": "preserve" + }, + "include": [ + "**/*.js", + "**/*.jsx", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "./changes.ts", + "./generate.tsx", + "./preload-locale.ts", + "./preload-theme.ts" + ] +} diff --git a/packages/frontend/eslint.config.js b/packages/frontend/eslint.config.js index 0a844ddf55..fab16f8061 100644 --- a/packages/frontend/eslint.config.js +++ b/packages/frontend/eslint.config.js @@ -16,6 +16,9 @@ export default [ ...pluginVue.configs['flat/recommended'], { files: ['{src,test,js,@types}/**/*.{ts,vue}'], + ignores: [ + '*.*', + ], plugins: { sharkey: { rules: { locale: localeRule } } }, languageOptions: { globals: { @@ -43,7 +46,7 @@ export default [ parserOptions: { extraFileExtensions: ['.vue'], parser: tsParser, - project: ['./tsconfig.json'], + project: ['./tsconfig.vue.json'], sourceType: 'module', tsconfigRootDir: import.meta.dirname, }, @@ -168,18 +171,77 @@ export default [ files: ['src/**/*.stories.ts'], rules: { 'no-restricted-globals': 'off', - } + }, + }, + { + files: [ + ".storybook/changes.ts", + ".storybook/generate.tsx", + ".storybook/preload-locale.ts", + ".storybook/preload-theme.ts" + ], + languageOptions: { + parserOptions: { + parser: tsParser, + project: ['.storybook/tsconfig.gen.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + files: ['.storybook/**/*.ts', '.storybook/**/*.tsx', '.storybook/**/*.js', '.storybook/**/*.jsx'], + ignores: [ + ".storybook/changes.ts", + ".storybook/generate.tsx", + ".storybook/preload-locale.ts", + ".storybook/preload-theme.ts" + ], + languageOptions: { + parserOptions: { + parser: tsParser, + project: ['.storybook/tsconfig.vue.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + files: ['test/**/*.ts', 'test/**/*.js'], + languageOptions: { + parserOptions: { + parser: tsParser, + project: ['test/tsconfig.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + files: ['*.js', '*.ts', 'lib/**/*.ts', 'lib/**/*.js'], + languageOptions: { + parserOptions: { + parser: tsParser, + project: ['./tsconfig.scripts.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + 'import/no-default-export': 'off', + }, }, { ignores: [ - "**/lib/", - "**/temp/", - "**/built/", - "**/coverage/", - "**/node_modules/", - "**/libopenmpt/", - "**/storybook-static/", - "*.*", - ] + '**/lib/', + '**/temp/', + '**/built/', + '**/coverage/', + '**/node_modules/', + '**/libopenmpt/', + '**/storybook-static/', + 'vue-shims.d.ts', + 'assets/' + ], }, ]; diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts index 7ecb1e9179..5302f35e39 100644 --- a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts +++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts @@ -8,6 +8,7 @@ import { walk } from '../node_modules/estree-walker/src/index.js'; import type * as estree from 'estree'; import type * as estreeWalker from 'estree-walker'; import type { Plugin } from 'vite'; +import type { Identifier } from 'estree'; function isFalsyIdentifier(identifier: estree.Identifier): boolean { return identifier.name === 'undefined' || identifier.name === 'NaN'; @@ -382,7 +383,7 @@ export function unwindCssModuleClassName(ast: estree.Node): void { if (childNode.name !== ident) return; this.replace({ type: 'Identifier', - name: node.declarations[0].id.name, + name: (node.declarations[0].id as Identifier).name, }); }, }); @@ -432,6 +433,7 @@ export function unwindCssModuleClassName(ast: estree.Node): void { type: 'ArrayExpression', elements: node.declarations[0].init.arguments[1].elements.slice(0, __cssModulesIndex).concat(node.declarations[0].init.arguments[1].elements.slice(__cssModulesIndex + 1)), }], + optional: false, }, }], kind: 'const', diff --git a/packages/frontend/lib/vite-plugin-create-search-index.ts b/packages/frontend/lib/vite-plugin-create-search-index.ts index 97f4e589a3..d19cd24620 100644 --- a/packages/frontend/lib/vite-plugin-create-search-index.ts +++ b/packages/frontend/lib/vite-plugin-create-search-index.ts @@ -3,12 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -/// - import { parse as vueSfcParse } from 'vue/compiler-sfc'; import { createLogger, - EnvironmentModuleGraph, + type EnvironmentModuleGraph, type LogErrorOptions, type LogOptions, normalizePath, @@ -20,7 +18,7 @@ import { glob } from 'glob'; import JSON5 from 'json5'; import MagicString, { SourceMap } from 'magic-string'; import path from 'node:path' -import { hash, toBase62 } from '../vite.config'; +import { hash, toBase62 } from '../vite.config.js'; import { minimatch } from 'minimatch'; import { type AttributeNode, @@ -63,7 +61,7 @@ interface MarkerRelation { let logger = { info: (msg: string, options?: LogOptions) => { }, warn: (msg: string, options?: LogOptions) => { }, - error: (msg: string, options?: LogErrorOptions | unknown) => { }, + error: (msg: string, options?: LogErrorOptions) => { }, }; let loggerInitialized = false; @@ -470,7 +468,11 @@ export function collectFileMarkers(id: string, code: string): SearchIndexItem[] return extractUsageInfoFromTemplateAst(descriptor.template?.ast, id); } catch (error) { - logger.error(`Error analyzing file ${id}:`, error); + logger.error(`Error analyzing file ${id}:`, { + error: error instanceof Error + ? error + : new Error(`Unknown error of type ${typeof(error)}`, { cause: error }), + }); } return []; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 2f2fd62574..b61314186b 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -11,8 +11,12 @@ "chromatic": "chromatic", "test": "vitest --run --globals", "test-and-coverage": "vitest --run --coverage --globals", - "typecheck": "vue-tsc --noEmit", - "eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache", + "typecheck-all": "pnpm run --no-bail typecheck:vue && pnpm run --no-bail typecheck:test && pnpm run --no-bail typecheck:scripts", + "typecheck": "pnpm run typecheck:vue && pnpm run typecheck:test && pnpm run typecheck:scripts", + "typecheck:vue": "vue-tsc -p tsconfig.vue.json --noEmit", + "typecheck:test": "vue-tsc -p test/tsconfig.json --noEmit", + "typecheck:scripts": "tsc -p tsconfig.scripts.json --noEmit", + "eslint": "eslint --quiet --cache -c eslint.config.js .", "lint": "pnpm typecheck && pnpm eslint" }, "dependencies": { @@ -58,7 +62,6 @@ "textarea-caret": "3.1.0", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", - "typescript": "5.9.2", "uuid": "13.0.0", "v-code-diff": "1.13.1", "vue": "3.5.21", @@ -69,6 +72,7 @@ "cypress": "15.3.0" }, "devDependencies": { + "@misskey-dev/eslint-plugin": "2.1.0", "@misskey-dev/summaly": "npm:@transfem-org/summaly@5.2.3", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.2", @@ -138,6 +142,7 @@ "three": "0.180.0", "tsc-alias": "1.8.16", "tsconfig-paths": "4.2.0", + "typescript": "5.9.2", "vite": "7.1.7", "vite-plugin-turbosnap": "1.0.3", "vitest": "3.2.4", diff --git a/packages/frontend/src/workers/tsconfig.json b/packages/frontend/src/workers/tsconfig.json index 39ba45ddbb..19d498d537 100644 --- a/packages/frontend/src/workers/tsconfig.json +++ b/packages/frontend/src/workers/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "lib": ["esnext", "webworker"], + "lib": ["ES2022", "WebWorker", "Webworker.Iterable"], "incremental": true } } diff --git a/packages/frontend/test/tsconfig.json b/packages/frontend/test/tsconfig.json index f8a73b8e21..360277e3a6 100644 --- a/packages/frontend/test/tsconfig.json +++ b/packages/frontend/test/tsconfig.json @@ -1,29 +1,9 @@ { + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../tsconfig.vue.json", "compilerOptions": { - "allowJs": true, - "noEmitOnError": false, - "noImplicitAny": true, - "noImplicitReturns": true, - "noUnusedParameters": false, - "noUnusedLocals": true, - "noFallthroughCasesInSwitch": true, - "declaration": false, - "sourceMap": true, - "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "allowSyntheticDefaultImports": true, - "removeComments": false, - "noLib": false, - "strict": true, - "strictNullChecks": true, - "strictPropertyInitialization": false, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - "isolatedModules": true, - "skipLibCheck": true, - "incremental": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", "baseUrl": "./", "paths": { "@/*": ["../src/*"] @@ -31,13 +11,8 @@ "typeRoots": [ "../node_modules/@types" ], - "lib": [ - "esnext", - "dom" - ], "types": ["node"] }, - "compileOnSave": false, "include": [ "./**/*.ts", "../src/**/*.vue" diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index 5e7483f247..77e8ba1be6 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -1,62 +1,9 @@ { - "compilerOptions": { - "allowJs": true, - "noEmitOnError": false, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedParameters": false, - "noUnusedLocals": false, - "noFallthroughCasesInSwitch": true, - "declaration": false, - "sourceMap": false, - "target": "ES2022", - "module": "ES2022", - "moduleResolution": "Bundler", - "removeComments": false, - "noLib": false, - "strict": true, - "strictNullChecks": true, - "experimentalDecorators": true, - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true, - "isolatedModules": true, - "useDefineForClassFields": true, - "verbatimModuleSyntax": true, - "skipLibCheck": true, - "incremental": true, - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"], - "@@/*": ["../frontend-shared/*"] - }, - "typeRoots": [ - "./@types", - "./node_modules/@types", - "./node_modules/@vue-macros", - "./node_modules" - ], - "types": [ - "vite/client", - "vitest/importMeta", - ], - "lib": [ - "esnext", - "dom", - "dom.iterable" - ], - "jsx": "preserve" - }, - "compileOnSave": false, - "include": [ - "./lib/**/*.ts", - "./src/**/*.ts", - "./src/**/*.vue", - "./test/**/*.ts", - "./test/**/*.vue", - "./@types/**/*.ts" - ], - "exclude": [ - "node_modules", - ".storybook/**/*" + "$schema": "https://json.schemastore.org/tsconfig", + "files": [], + // WebStorm only reads one tsconfig per directory, so this tricks it into loading both. + "references": [ + { "path": "./tsconfig.scripts.json" }, + { "path": "./tsconfig.vue.json" } ] } diff --git a/packages/frontend/tsconfig.scripts.json b/packages/frontend/tsconfig.scripts.json new file mode 100644 index 0000000000..bc36fadbd5 --- /dev/null +++ b/packages/frontend/tsconfig.scripts.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../shared/tsconfig.node.json", + "compilerOptions": { + "noImplicitAny": false, + "typeRoots": [ + "./node_modules/@types", + "./node_modules" + ], + "types": [ + "vite/client", + "vitest/importMeta", + ] + }, + "include": [ + "*.js", + "*.ts", + "lib/**/*.ts", + "lib/**/*.js" + ], + "exclude": [ + "node_modules", + ".storybook/**/*", + "vue-shims.d.ts" + ] +} diff --git a/packages/frontend/tsconfig.vue.json b/packages/frontend/tsconfig.vue.json new file mode 100644 index 0000000000..28c99bdb61 --- /dev/null +++ b/packages/frontend/tsconfig.vue.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../shared/tsconfig.web.json", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"], + "@@/*": ["../frontend-shared/*"] + }, + "typeRoots": [ + "./@types", + "./node_modules/@types", + "./node_modules/@vue-macros", + "./node_modules" + ], + "types": [ + "vite/client", + "vitest/importMeta" + ], + "jsx": "preserve" + }, + "include": [ + "./lib/**/*.ts", + "./src/**/*.ts", + "./src/**/*.vue", + "./test/**/*.ts", + "./test/**/*.vue", + "./@types/**/*.ts", + "./vue-shims.d.ts" + ], + "exclude": [ + "node_modules", + ".storybook/**/*", + "*.*" + ] +} diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 255efce80f..2b269f53da 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -2,20 +2,24 @@ import path from 'path'; import pluginReplace from '@rollup/plugin-replace'; import pluginVue from '@vitejs/plugin-vue'; import { defineConfig } from 'vite'; -import type { UserConfig } from 'vite'; - +import { pluginReplaceIcons } from 'frontend-shared/util/vite.replaceIcons.js'; import locales from '../../locales/index.js'; import { localesVersion } from '../../locales/version.js'; -import meta from '../../package.json'; +import meta from '../../package.json' with { type: 'json' }; import packageInfo from './package.json' with { type: 'json' }; +import tsconfigVue from './tsconfig.vue.json' with { type: 'json' }; import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js'; import pluginJson5 from './vite.json5.js'; import pluginCreateSearchIndex from './lib/vite-plugin-create-search-index.js'; +import type { TsconfigRaw } from 'esbuild'; +import type { UserConfig } from 'vite'; import type { Options as SearchIndexOptions } from './lib/vite-plugin-create-search-index.js'; -import { pluginReplaceIcons } from './vite.replaceIcons.js'; const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue', '.wasm']; +// https://github.com/rollup/plugins/issues/1541#issuecomment-3114729017 +const fix = (f: { default: T }): T => f as unknown as T; + /** * 検索インデックスの生成設定 */ @@ -110,7 +114,7 @@ export function getConfig(): UserConfig { ...pluginReplaceIcons(), ...process.env.NODE_ENV === 'production' ? [ - pluginReplace({ + fix(pluginReplace)({ preventAssignment: true, values: { 'isChromatic()': JSON.stringify(false), @@ -143,6 +147,7 @@ export function getConfig(): UserConfig { }, preprocessorOptions: { scss: { + // @ts-expect-error This produces an error, but all example code has it api: 'modern-compiler', }, }, @@ -160,7 +165,13 @@ export function getConfig(): UserConfig { _DATA_TRANSFER_DECK_COLUMN_: JSON.stringify('mk_deck_column'), __VUE_OPTIONS_API__: true, __VUE_PROD_DEVTOOLS__: false, - _RUFFLE_VERSION_: JSON.stringify(packageInfo.dependencies['@ruffle-rs/ruffle']) + _RUFFLE_VERSION_: JSON.stringify(packageInfo.dependencies['@ruffle-rs/ruffle']), + }, + + esbuild: { + // https://github.com/vitejs/vite/discussions/8483#discussioncomment-14485974 + // https://esbuild.github.io/api/#tsconfig-raw + tsconfigRaw: tsconfigVue as TsconfigRaw, }, build: { diff --git a/packages/frontend/vite.json5.ts b/packages/frontend/vite.json5.ts index 87b67c2142..7c4f7f4cc0 100644 --- a/packages/frontend/vite.json5.ts +++ b/packages/frontend/vite.json5.ts @@ -1,9 +1,9 @@ // Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json import JSON5 from 'json5'; -import { Plugin } from 'rollup'; +import type { Plugin } from 'rollup'; import { createFilter, dataToEsm } from '@rollup/pluginutils'; -import { RollupJsonOptions } from '@rollup/plugin-json'; +import type { RollupJsonOptions } from '@rollup/plugin-json'; // json5 extends SyntaxError with additional fields (without subclassing) // https://github.com/json5/json5/blob/de344f0619bda1465a6e25c76f1c0c3dda8108d9/lib/parse.js#L1111-L1112