diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 3bf97b9b..2a9cf3ec 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,3 +1,6 @@ +require("@rushstack/eslint-patch/modern-module-resolution.js"); + +/** @type {import("eslint").Linter.Config} */ module.exports = { extends: [ "@kachkaev/eslint-config-react", @@ -5,4 +8,12 @@ module.exports = { "plugin:@next/next/recommended", ], parserOptions: { tsconfigRootDir: __dirname }, + settings: { + "import/resolver": { + typescript: { + alwaysTryTypes: true, + project: `${__dirname}/tsconfig.json`, + }, + }, + }, }; diff --git a/next.config.js b/next.config.js index 0d68024a..27af0e96 100644 --- a/next.config.js +++ b/next.config.js @@ -23,6 +23,21 @@ const nextConfig = { destination: "/api/jump", }, ], + + webpack: (config) => { + return { + ...config, + resolve: { + ...config.resolve, + extensionAlias: { + /* eslint-disable @typescript-eslint/naming-convention -- external API */ + ".js": [".js", ".ts"], + ".jsx": [".jsx", ".tsx"], + /* eslint-enable @typescript-eslint/naming-convention -- external API */ + }, + }, + }; + }, }; export default nextConfig; diff --git a/package.json b/package.json index cb6dd054..91dddbee 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,14 @@ "@kachkaev/eslint-config-react": "0.6.0", "@kachkaev/markdownlint-config": "0.5.0", "@next/eslint-plugin-next": "14.0.2", + "@rushstack/eslint-patch": "1.5.1", "@tsconfig/next": "2.0.1", "@tsconfig/strictest": "2.0.2", "@types/hosted-git-info": "3.0.5", "@types/node": "20.9.0", "@types/react": "18.2.37", "eslint": "8.53.0", + "eslint-import-resolver-typescript": "3.6.1", "husky": "8.0.3", "lint-staged": "15.1.0", "markdownlint-cli": "0.37.0", diff --git a/pages/404.page.tsx b/pages/404.page.tsx index e18a989d..44c732d7 100644 --- a/pages/404.page.tsx +++ b/pages/404.page.tsx @@ -1,8 +1,8 @@ import type { NextPage } from "next"; import * as React from "react"; -import { ErrorPageBody } from "./shared/error-page-body"; -import { PageMetadata } from "./shared/page-metadata"; +import { ErrorPageBody } from "./shared/error-page-body.jsx"; +import { PageMetadata } from "./shared/page-metadata.jsx"; const Page: NextPage = () => { const message = "page not found"; diff --git a/pages/_app.page.tsx b/pages/_app.page.tsx index 63e3f81f..eea5eb43 100644 --- a/pages/_app.page.tsx +++ b/pages/_app.page.tsx @@ -1,9 +1,8 @@ -// eslint-disable-next-line import/no-unresolved -- https://github.com/import-js/eslint-plugin-import/issues/1810 import { Analytics } from "@vercel/analytics/react"; -import type { AppProps } from "next/app"; +import type { AppProps } from "next/app.js"; import * as React from "react"; -import { PageLayout } from "./_app.page/page-layout"; +import { PageLayout } from "./_app.page/page-layout.jsx"; const App: React.FunctionComponent = ({ Component, pageProps }) => { React.useEffect(() => { diff --git a/pages/_app.page/page-layout.tsx b/pages/_app.page/page-layout.tsx index e6a06046..9ba3dbed 100644 --- a/pages/_app.page/page-layout.tsx +++ b/pages/_app.page/page-layout.tsx @@ -1,8 +1,10 @@ import * as React from "react"; -import styled, { createGlobalStyle, css } from "styled-components"; -import normalize from "styled-normalize"; +import _styled, { createGlobalStyle, css } from "styled-components"; +import { normalize } from "styled-normalize"; -import { ExternalLink } from "../shared/external-link"; +import { ExternalLink } from "../shared/external-link.jsx"; + +const styled = _styled as unknown as typeof _styled.default; const base = css` body { diff --git a/pages/_document.page.tsx b/pages/_document.page.tsx index b4ebbd0d..5dc9e933 100644 --- a/pages/_document.page.tsx +++ b/pages/_document.page.tsx @@ -1,8 +1,10 @@ -import type { DocumentContext } from "next/document"; -import Document, { Head, Html, Main, NextScript } from "next/document"; +import type { DocumentContext } from "next/document.js"; +import _Document, { Head, Html, Main, NextScript } from "next/document.js"; import * as React from "react"; import { ServerStyleSheet } from "styled-components"; +const Document = _Document as unknown as typeof _Document.default; + export default class MyDocument extends Document { static override async getInitialProps(ctx: DocumentContext) { const sheet = new ServerStyleSheet(); diff --git a/pages/_error.page.tsx b/pages/_error.page.tsx index 4666590c..8e5f8deb 100644 --- a/pages/_error.page.tsx +++ b/pages/_error.page.tsx @@ -1,8 +1,8 @@ import type { NextPage } from "next"; import * as React from "react"; -import { ErrorPageBody } from "./shared/error-page-body"; -import { PageMetadata } from "./shared/page-metadata"; +import { ErrorPageBody } from "./shared/error-page-body.jsx"; +import { PageMetadata } from "./shared/page-metadata.jsx"; const Page: NextPage<{ statusCode: number }> = ({ statusCode }) => { const message = "unknown error"; diff --git a/pages/api/jump.handler.ts b/pages/api/jump.handler.ts index e572c7f4..46d5e48e 100644 --- a/pages/api/jump.handler.ts +++ b/pages/api/jump.handler.ts @@ -1,6 +1,6 @@ import type { NextApiHandler } from "next"; -import { resolveDestination } from "../shared/destinations"; +import { resolveDestination } from "../shared/destinations.js"; const handler: NextApiHandler = async (req, res) => { let destinationUrl = "/"; diff --git a/pages/index.page.tsx b/pages/index.page.tsx index 07fea4ca..78148d3f 100644 --- a/pages/index.page.tsx +++ b/pages/index.page.tsx @@ -1,11 +1,13 @@ import * as React from "react"; -import styled from "styled-components"; +import _styled from "styled-components"; -import { AvailableDestinations } from "./index.page/available-destinations"; -import { Example } from "./index.page/example"; -import { InputForm } from "./index.page/input-form"; -import { ExternalLink } from "./shared/external-link"; -import { PageMetadata } from "./shared/page-metadata"; +import { AvailableDestinations } from "./index.page/available-destinations.jsx"; +import { Example } from "./index.page/example.jsx"; +import { InputForm } from "./index.page/input-form.jsx"; +import { ExternalLink } from "./shared/external-link.jsx"; +import { PageMetadata } from "./shared/page-metadata.jsx"; + +const styled = _styled as unknown as typeof _styled.default; const H2 = styled.h2` margin-top: 3em; diff --git a/pages/index.page/available-destinations.tsx b/pages/index.page/available-destinations.tsx index d74bdb7e..b4d2c170 100644 --- a/pages/index.page/available-destinations.tsx +++ b/pages/index.page/available-destinations.tsx @@ -1,8 +1,10 @@ import * as React from "react"; -import styled from "styled-components"; +import _styled from "styled-components"; -import { ExternalLink } from "../shared/external-link"; -import { ClickableCode } from "./clickable-code"; +import { ExternalLink } from "../shared/external-link.jsx"; +import { ClickableCode } from "./clickable-code.jsx"; + +const styled = _styled as unknown as typeof _styled.default; const Ul = styled.ul` padding-left: 0; diff --git a/pages/index.page/clickable-code.tsx b/pages/index.page/clickable-code.tsx index a2e113d4..f48eb359 100644 --- a/pages/index.page/clickable-code.tsx +++ b/pages/index.page/clickable-code.tsx @@ -1,4 +1,6 @@ -import styled from "styled-components"; +import _styled from "styled-components"; + +const styled = _styled as unknown as typeof _styled.default; export const ClickableCode = styled.code` border-bottom: 1px dotted transparent; diff --git a/pages/index.page/example.tsx b/pages/index.page/example.tsx index ffbe9d63..ae246226 100644 --- a/pages/index.page/example.tsx +++ b/pages/index.page/example.tsx @@ -1,8 +1,10 @@ import * as React from "react"; -import styled from "styled-components"; +import _styled from "styled-components"; -import { ExternalLink } from "../shared/external-link"; -import { ClickableCode } from "./clickable-code"; +import { ExternalLink } from "../shared/external-link.jsx"; +import { ClickableCode } from "./clickable-code.jsx"; + +const styled = _styled as unknown as typeof _styled.default; const Remark = styled.span` white-space: nowrap; diff --git a/pages/index.page/input-form.tsx b/pages/index.page/input-form.tsx index ef1f822e..c45475cb 100644 --- a/pages/index.page/input-form.tsx +++ b/pages/index.page/input-form.tsx @@ -1,5 +1,7 @@ import * as React from "react"; -import styled from "styled-components"; +import _styled from "styled-components"; + +const styled = _styled as unknown as typeof _styled.default; const verticalFormPadding = 20; diff --git a/pages/shared/destinations.ts b/pages/shared/destinations.ts index 81b392bd..612a3968 100644 --- a/pages/shared/destinations.ts +++ b/pages/shared/destinations.ts @@ -1,7 +1,7 @@ import hostedGitInfo from "hosted-git-info"; import { LRUCache } from "lru-cache"; -import type { JsonObject } from "./json-types"; +import type { JsonObject } from "./json-types.js"; export interface SuccessfullyResolvedDestination { outcome: "success"; diff --git a/pages/shared/error-page-body.tsx b/pages/shared/error-page-body.tsx index f9315368..6a1c6bfd 100644 --- a/pages/shared/error-page-body.tsx +++ b/pages/shared/error-page-body.tsx @@ -1,6 +1,9 @@ -import Link from "next/link"; +import _Link from "next/link.js"; import * as React from "react"; -import styled from "styled-components"; +import _styled from "styled-components"; + +const Link = _Link as unknown as typeof _Link.default; +const styled = _styled as unknown as typeof _styled.default; const Container = styled.div` text-align: center; diff --git a/pages/shared/page-metadata.tsx b/pages/shared/page-metadata.tsx index fa14fbc8..1ad97951 100644 --- a/pages/shared/page-metadata.tsx +++ b/pages/shared/page-metadata.tsx @@ -1,6 +1,8 @@ -import Head from "next/head"; +import _Head from "next/head.js"; import * as React from "react"; +const Head = _Head as unknown as typeof _Head.default; + const getBaseUrl = () => { const hostname = process.env["NEXT_PUBLIC_VERCEL_URL"] ?? "njt.vercel.app"; const protocol = hostname.split(":")[0] === "localhost" ? "http" : "https"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd360fe8..35abb338 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,13 +38,16 @@ importers: devDependencies: '@kachkaev/eslint-config-react': specifier: 0.6.0 - version: 0.6.0(eslint@8.53.0)(typescript@5.2.2) + version: 0.6.0(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0)(typescript@5.2.2) '@kachkaev/markdownlint-config': specifier: 0.5.0 version: 0.5.0(markdownlint-cli@0.37.0) '@next/eslint-plugin-next': specifier: 14.0.2 version: 14.0.2 + '@rushstack/eslint-patch': + specifier: 1.5.1 + version: 1.5.1 '@tsconfig/next': specifier: 2.0.1 version: 2.0.1 @@ -63,6 +66,9 @@ importers: eslint: specifier: 8.53.0 version: 8.53.0 + eslint-import-resolver-typescript: + specifier: 3.6.1 + version: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.0)(eslint@8.53.0) husky: specifier: 8.0.3 version: 8.0.3 @@ -230,7 +236,7 @@ packages: wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: true - /@kachkaev/eslint-config-base@0.6.0(eslint@8.53.0)(typescript@5.2.2): + /@kachkaev/eslint-config-base@0.6.0(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0)(typescript@5.2.2): resolution: {integrity: sha512-taC4vVosaj26duE/hdf/sSc7ACoZwqSJlLVsgaj+uJO1T7CUWUrXtr40I40/pUL1JM9rqpKDo8DDw2bCdDqkCg==} peerDependencies: eslint: '>=8.0.0' @@ -240,7 +246,7 @@ packages: '@typescript-eslint/parser': 5.62.0(eslint@8.53.0)(typescript@5.2.2) eslint: 8.53.0 eslint-config-prettier: 8.10.0(eslint@8.53.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint@8.53.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0) eslint-plugin-jest: 27.6.0(@typescript-eslint/eslint-plugin@5.62.0)(eslint@8.53.0)(typescript@5.2.2) eslint-plugin-json: 3.1.0 eslint-plugin-simple-import-sort: 10.0.0(eslint@8.53.0) @@ -253,12 +259,12 @@ packages: - typescript dev: true - /@kachkaev/eslint-config-react@0.6.0(eslint@8.53.0)(typescript@5.2.2): + /@kachkaev/eslint-config-react@0.6.0(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0)(typescript@5.2.2): resolution: {integrity: sha512-2PkyPC3h6DxfptMbxW2AE5SidaQTemE50HClvat6s+AWIrCgaHxxmO6R3a0LOWKBj8rtzVg5oAavhvI07mCJZg==} peerDependencies: eslint: '>=8.0.0' dependencies: - '@kachkaev/eslint-config-base': 0.6.0(eslint@8.53.0)(typescript@5.2.2) + '@kachkaev/eslint-config-base': 0.6.0(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0)(typescript@5.2.2) eslint: 8.53.0 eslint-plugin-react: 7.33.2(eslint@8.53.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.53.0) @@ -410,6 +416,10 @@ packages: tslib: 2.6.2 dev: true + /@rushstack/eslint-patch@1.5.1: + resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==} + dev: true + /@swc/helpers@0.5.2: resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} dependencies: @@ -1070,6 +1080,14 @@ packages: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true + /enhanced-resolve@5.15.0: + resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + dev: true + /entities@3.0.1: resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} engines: {node: '>=0.12'} @@ -1198,7 +1216,30 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.53.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.0)(eslint@8.53.0): + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + dependencies: + debug: 4.3.4 + enhanced-resolve: 5.15.0 + eslint: 8.53.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0) + fast-glob: 3.3.2 + get-tsconfig: 4.7.2 + is-core-module: 2.13.1 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -1223,11 +1264,12 @@ packages: debug: 3.2.7 eslint: 8.53.0 eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.0)(eslint@8.53.0) transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0)(eslint@8.53.0): + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0): resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} engines: {node: '>=4'} peerDependencies: @@ -1246,7 +1288,7 @@ packages: doctrine: 2.1.0 eslint: 8.53.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.53.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.53.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -1665,6 +1707,12 @@ packages: get-intrinsic: 1.2.2 dev: true + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /git-hooks-list@3.1.0: resolution: {integrity: sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==} dev: true @@ -2922,6 +2970,10 @@ packages: engines: {node: '>=4'} dev: true + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -3360,6 +3412,11 @@ packages: tslib: 2.6.2 dev: true + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true diff --git a/tsconfig.json b/tsconfig.json index 982d9ae6..a08e7828 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,8 +4,8 @@ "@tsconfig/strictest/tsconfig.json" ], "compilerOptions": { - "module": "ESNext", - "target": "ESNext" + "module": "NodeNext", + "moduleResolution": "NodeNext" }, "exclude": [], "include": ["next-env.d.ts", "**/*.cjs", "**/*.ts", "**/*.tsx"]