diff --git a/docs/guides/typescript.md b/docs/guides/typescript.md index 949eaec1911..cfd7720efd2 100644 --- a/docs/guides/typescript.md +++ b/docs/guides/typescript.md @@ -9,27 +9,28 @@ Remix seamlessly supports both JavaScript and TypeScript. If you name a file wit The Remix compiler will not do any type checking (it simply removes the types). If you want to do type checking, you'll want to use TypeScript's `tsc` CLI yourself. A common solution is to add a `typecheck` script to your package.json: -```json filename=package.json lines=[9] +```json filename=package.json lines=[10] { "name": "remix-app", "private": true, "sideEffects": false, "scripts": { "build": "remix build", - "dev": "remix dev", - "start": "remix-serve build/index.js", + "dev": "remix dev --manual", + "lint": "eslint --ignore-path .gitignore .", + "start": "remix-serve ./build/index.js", "typecheck": "tsc" }, "dependencies": { "@remix-run/node": "latest", "@remix-run/react": "latest", "@remix-run/serve": "latest", + "isbot": "^3.6.8", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@remix-run/dev": "latest", - "@remix-run/eslint-config": "latest", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "eslint": "^8.23.1", diff --git a/scripts/lint-templates.sh b/scripts/lint-templates.sh new file mode 100755 index 00000000000..3417c7e994f --- /dev/null +++ b/scripts/lint-templates.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Helper script for us to use to validate template lint setups outside of the +# monorepo. This script will, for each template in your local Remix repo clone: +# +# * Install an app via create-remix for the template (into a temp/ directory) +# * Install deps +# * npm run lint +# +# Usage: +# lint-templates.sh [path-to-remix-repo] +# +# So from a sibling folder to the remix repo: +# ../remix/scripts/lint-templates.sh ../remix + +REPO_DIR="${1}" + +if [ "${REPO_DIR}" == "" ]; then + REPO_DIR="../remix" +fi + +if [ ! -e "${REPO_DIR}" ]; then + echo "Invalid repo directory: ${REPO_DIR}"; + exit 1; +fi + +TEMPLATES=$(find ${REPO_DIR}/templates -type d -mindepth 1 -maxdepth 1 | grep -v deno | grep -v tutorial | sort) + +for D in ${TEMPLATES}; do + echo "Linting template: ${D}"; + echo "-------------------------------------------------------"; + rm -rf temp; + npx create-remix@latest --template "${D}" temp -y; + cd temp; + npm run lint; + echo ""; + echo ""; + cd ..; +done diff --git a/templates/arc/.eslintrc.cjs b/templates/arc/.eslintrc.cjs index 2061cd22684..84c2abea6d7 100644 --- a/templates/arc/.eslintrc.cjs +++ b/templates/arc/.eslintrc.cjs @@ -1,4 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.js", "plugin-remix.js"], + env: { + node: true, + }, + }, + ], }; diff --git a/templates/arc/app/entry.server.tsx b/templates/arc/app/entry.server.tsx index 0c7712b0b0e..e2002b0740e 100644 --- a/templates/arc/app/entry.server.tsx +++ b/templates/arc/app/entry.server.tsx @@ -19,6 +19,9 @@ export default function handleRequest( responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars loadContext: AppLoadContext ) { return isbot(request.headers.get("user-agent")) diff --git a/templates/arc/package.json b/templates/arc/package.json index 1c202dea0f7..91d8e08eacb 100644 --- a/templates/arc/package.json +++ b/templates/arc/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "remix build", "dev": "remix dev --manual -c \"arc sandbox -e testing\"", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "cross-env NODE_ENV=production arc sandbox", "typecheck": "tsc" }, @@ -22,11 +23,16 @@ "devDependencies": { "@architect/architect": "^10.12.1", "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@types/source-map-support": "^0.5.6", + "@typescript-eslint/eslint-plugin": "^6.7.4", "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.6" }, "engines": { diff --git a/templates/cloudflare-pages/.eslintrc.cjs b/templates/cloudflare-pages/.eslintrc.cjs index 2061cd22684..edd3094f704 100644 --- a/templates/cloudflare-pages/.eslintrc.cjs +++ b/templates/cloudflare-pages/.eslintrc.cjs @@ -1,4 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.js"], + env: { + node: true, + }, + }, + ], }; diff --git a/templates/cloudflare-pages/app/entry.server.tsx b/templates/cloudflare-pages/app/entry.server.tsx index dd06c61bcfa..3f5b9989333 100644 --- a/templates/cloudflare-pages/app/entry.server.tsx +++ b/templates/cloudflare-pages/app/entry.server.tsx @@ -14,6 +14,9 @@ export default async function handleRequest( responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars loadContext: AppLoadContext ) { const body = await renderToReadableStream( diff --git a/templates/cloudflare-pages/package.json b/templates/cloudflare-pages/package.json index efe545594c5..ee3120f5819 100644 --- a/templates/cloudflare-pages/package.json +++ b/templates/cloudflare-pages/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "remix build", "dev": "remix dev --manual -c \"npm run start\"", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "wrangler pages dev --compatibility-date=2023-06-21 ./public", "typecheck": "tsc" }, @@ -20,10 +21,15 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20230518.0", "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.0", "wrangler": "3.8.0" }, diff --git a/templates/cloudflare-workers/.eslintrc.cjs b/templates/cloudflare-workers/.eslintrc.cjs index 2061cd22684..edd3094f704 100644 --- a/templates/cloudflare-workers/.eslintrc.cjs +++ b/templates/cloudflare-workers/.eslintrc.cjs @@ -1,4 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.js"], + env: { + node: true, + }, + }, + ], }; diff --git a/templates/cloudflare-workers/app/entry.server.tsx b/templates/cloudflare-workers/app/entry.server.tsx index dd06c61bcfa..3f5b9989333 100644 --- a/templates/cloudflare-workers/app/entry.server.tsx +++ b/templates/cloudflare-workers/app/entry.server.tsx @@ -14,6 +14,9 @@ export default async function handleRequest( responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars loadContext: AppLoadContext ) { const body = await renderToReadableStream( diff --git a/templates/cloudflare-workers/package.json b/templates/cloudflare-workers/package.json index 24c570412dc..abab2ac1a74 100644 --- a/templates/cloudflare-workers/package.json +++ b/templates/cloudflare-workers/package.json @@ -6,6 +6,7 @@ "build": "remix build", "deploy": "wrangler deploy", "dev": "remix dev --manual -c \"npm start\"", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "wrangler dev ./build/index.js", "typecheck": "tsc" }, @@ -21,10 +22,16 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20230518.0", "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.6", "wrangler": "3.8.0" }, diff --git a/templates/cloudflare-workers/server.ts b/templates/cloudflare-workers/server.ts index 7408ed39813..4b6e39ef295 100644 --- a/templates/cloudflare-workers/server.ts +++ b/templates/cloudflare-workers/server.ts @@ -2,6 +2,7 @@ import { getAssetFromKV } from "@cloudflare/kv-asset-handler"; import type { AppLoadContext } from "@remix-run/cloudflare"; import { createRequestHandler, logDevReady } from "@remix-run/cloudflare"; import * as build from "@remix-run/dev/server-build"; +// eslint-disable-next-line import/no-unresolved import __STATIC_CONTENT_MANIFEST from "__STATIC_CONTENT_MANIFEST"; const MANIFEST = JSON.parse(__STATIC_CONTENT_MANIFEST); @@ -38,7 +39,9 @@ export default { }, } ); - } catch (error) {} + } catch (error) { + // No-op + } try { const loadContext: AppLoadContext = { diff --git a/templates/deno/app/entry.server.tsx b/templates/deno/app/entry.server.tsx index 03789f45e62..97b0046c16a 100644 --- a/templates/deno/app/entry.server.tsx +++ b/templates/deno/app/entry.server.tsx @@ -14,7 +14,10 @@ export default async function handleRequest( responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, - loadContext: AppLoadContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars + loadContext: AppLoadContext ) { const body = await renderToReadableStream( , @@ -25,7 +28,7 @@ export default async function handleRequest( console.error(error); responseStatusCode = 500; }, - }, + } ); if (isbot(request.headers.get("user-agent"))) { diff --git a/templates/express/.eslintrc.cjs b/templates/express/.eslintrc.cjs index 2061cd22684..e40419a11b5 100644 --- a/templates/express/.eslintrc.cjs +++ b/templates/express/.eslintrc.cjs @@ -1,4 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.js", "server.js"], + env: { + node: true, + }, + }, + ], }; diff --git a/templates/express/app/entry.server.tsx b/templates/express/app/entry.server.tsx index 0c7712b0b0e..e2002b0740e 100644 --- a/templates/express/app/entry.server.tsx +++ b/templates/express/app/entry.server.tsx @@ -19,6 +19,9 @@ export default function handleRequest( responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars loadContext: AppLoadContext ) { return isbot(request.headers.get("user-agent")) diff --git a/templates/express/package.json b/templates/express/package.json index 94cda6955ae..5cb109be617 100644 --- a/templates/express/package.json +++ b/templates/express/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "remix build", "dev": "remix dev --manual -c \"node --watch-path server.js --watch server.js\"", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "cross-env NODE_ENV=production node ./server.js", "typecheck": "tsc" }, @@ -24,15 +25,20 @@ }, "devDependencies": { "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", "@types/compression": "^1.7.2", "@types/express": "^4.17.17", "@types/morgan": "^1.9.4", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@types/source-map-support": "^0.5.6", + "@typescript-eslint/eslint-plugin": "^6.7.4", "chokidar": "^3.5.3", "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.6" }, "engines": { diff --git a/templates/fly/.eslintrc.cjs b/templates/fly/.eslintrc.cjs index 2061cd22684..edd3094f704 100644 --- a/templates/fly/.eslintrc.cjs +++ b/templates/fly/.eslintrc.cjs @@ -1,4 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.js"], + env: { + node: true, + }, + }, + ], }; diff --git a/templates/fly/app/entry.server.tsx b/templates/fly/app/entry.server.tsx index 0c7712b0b0e..e2002b0740e 100644 --- a/templates/fly/app/entry.server.tsx +++ b/templates/fly/app/entry.server.tsx @@ -19,6 +19,9 @@ export default function handleRequest( responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars loadContext: AppLoadContext ) { return isbot(request.headers.get("user-agent")) diff --git a/templates/fly/package.json b/templates/fly/package.json index a536c283941..a4672d381f1 100644 --- a/templates/fly/package.json +++ b/templates/fly/package.json @@ -6,6 +6,7 @@ "build": "remix build", "deploy": "fly deploy --remote-only", "dev": "remix dev", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "remix-serve ./build/index.js", "typecheck": "tsc" }, @@ -20,10 +21,15 @@ }, "devDependencies": { "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.6" }, "engines": { diff --git a/templates/remix-javascript/.eslintrc.cjs b/templates/remix-javascript/.eslintrc.cjs index 2061cd22684..777bae21d87 100644 --- a/templates/remix-javascript/.eslintrc.cjs +++ b/templates/remix-javascript/.eslintrc.cjs @@ -1,4 +1,56 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + { + files: ["**/*.{js,jsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Node + { + files: [".eslintrc.js"], + env: { + node: true, + }, + }, + ], }; diff --git a/templates/remix-javascript/package.json b/templates/remix-javascript/package.json index 2c4c9e0b638..e821a97d2b5 100644 --- a/templates/remix-javascript/package.json +++ b/templates/remix-javascript/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "remix build", "dev": "remix dev --manual", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "remix-serve ./build/index.js" }, "dependencies": { @@ -18,8 +19,11 @@ }, "devDependencies": { "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", - "eslint": "^8.38.0" + "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0" }, "engines": { "node": ">=18.0.0" diff --git a/templates/remix-tutorial/.eslintrc.js b/templates/remix-tutorial/.eslintrc.js index d57eeaf437a..edd3094f704 100644 --- a/templates/remix-tutorial/.eslintrc.js +++ b/templates/remix-tutorial/.eslintrc.js @@ -1,3 +1,9 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + /** @type {import('eslint').Linter.Config} */ module.exports = { root: true, diff --git a/templates/remix-tutorial/package.json b/templates/remix-tutorial/package.json index 2d45011a075..52bfc990bb6 100644 --- a/templates/remix-tutorial/package.json +++ b/templates/remix-tutorial/package.json @@ -4,6 +4,7 @@ "scripts": { "build": "remix build", "dev": "remix dev --manual", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "remix-serve build", "typecheck": "tsc" }, diff --git a/templates/remix/.eslintrc.cjs b/templates/remix/.eslintrc.cjs index 2061cd22684..edd3094f704 100644 --- a/templates/remix/.eslintrc.cjs +++ b/templates/remix/.eslintrc.cjs @@ -1,4 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + /** @type {import('eslint').Linter.Config} */ module.exports = { - extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"], + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.js"], + env: { + node: true, + }, + }, + ], }; diff --git a/templates/remix/app/entry.server.tsx b/templates/remix/app/entry.server.tsx index 0c7712b0b0e..e2002b0740e 100644 --- a/templates/remix/app/entry.server.tsx +++ b/templates/remix/app/entry.server.tsx @@ -19,6 +19,9 @@ export default function handleRequest( responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, + // This is ignored so we can keep it in the template for visibility. Feel + // free to delete this parameter in your app if you're not using it! + // eslint-disable-next-line @typescript-eslint/no-unused-vars loadContext: AppLoadContext ) { return isbot(request.headers.get("user-agent")) diff --git a/templates/remix/package.json b/templates/remix/package.json index 57fc5734a21..9a7d370bf82 100644 --- a/templates/remix/package.json +++ b/templates/remix/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "remix build", "dev": "remix dev --manual", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "remix-serve ./build/index.js", "typecheck": "tsc" }, @@ -19,10 +20,15 @@ }, "devDependencies": { "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.6" }, "engines": { diff --git a/templates/unstable-vite-express/.eslintrc.cjs b/templates/unstable-vite-express/.eslintrc.cjs new file mode 100644 index 00000000000..edd3094f704 --- /dev/null +++ b/templates/unstable-vite-express/.eslintrc.cjs @@ -0,0 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.js"], + env: { + node: true, + }, + }, + ], +}; diff --git a/templates/unstable-vite-express/package.json b/templates/unstable-vite-express/package.json index 40878aaa49e..2cbbeeada14 100644 --- a/templates/unstable-vite-express/package.json +++ b/templates/unstable-vite-express/package.json @@ -3,8 +3,9 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "node ./server.mjs", "build": "vite build && vite build --ssr", + "dev": "node ./server.mjs", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "cross-env NODE_ENV=production node ./server.mjs", "typecheck": "tsc" }, @@ -19,12 +20,18 @@ }, "devDependencies": { "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", "@types/express": "^4.17.20", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", "cross-env": "^7.0.3", "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.6", "vite": "^5.0.0", "vite-tsconfig-paths": "^4.2.1" diff --git a/templates/unstable-vite-express/vite.config.ts b/templates/unstable-vite-express/vite.config.ts index 177dd59b27d..26504208fa5 100644 --- a/templates/unstable-vite-express/vite.config.ts +++ b/templates/unstable-vite-express/vite.config.ts @@ -1,5 +1,8 @@ import { unstable_vitePlugin as remix } from "@remix-run/dev"; import { defineConfig } from "vite"; +// This is only an issue in the Remix monorepo, this should lint properly once +// you've created an app from this template and this comment can be removed +// eslint-disable-next-line import/no-unresolved import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ diff --git a/templates/unstable-vite/.eslintrc.cjs b/templates/unstable-vite/.eslintrc.cjs new file mode 100644 index 00000000000..edd3094f704 --- /dev/null +++ b/templates/unstable-vite/.eslintrc.cjs @@ -0,0 +1,80 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.js"], + env: { + node: true, + }, + }, + ], +}; diff --git a/templates/unstable-vite/package.json b/templates/unstable-vite/package.json index a25a9ffec52..0a6aecf4d8d 100644 --- a/templates/unstable-vite/package.json +++ b/templates/unstable-vite/package.json @@ -3,8 +3,9 @@ "sideEffects": false, "type": "module", "scripts": { - "dev": "vite dev", "build": "vite build && vite build --ssr", + "dev": "vite dev", + "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", "start": "remix-serve ./build/index.js", "typecheck": "tsc" }, @@ -18,10 +19,16 @@ }, "devDependencies": { "@remix-run/dev": "*", - "@remix-run/eslint-config": "*", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.7.4", "eslint": "^8.38.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "typescript": "^5.1.6", "vite": "^5.0.0", "vite-tsconfig-paths": "^4.2.1" diff --git a/templates/unstable-vite/vite.config.ts b/templates/unstable-vite/vite.config.ts index 177dd59b27d..26504208fa5 100644 --- a/templates/unstable-vite/vite.config.ts +++ b/templates/unstable-vite/vite.config.ts @@ -1,5 +1,8 @@ import { unstable_vitePlugin as remix } from "@remix-run/dev"; import { defineConfig } from "vite"; +// This is only an issue in the Remix monorepo, this should lint properly once +// you've created an app from this template and this comment can be removed +// eslint-disable-next-line import/no-unresolved import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({