29.配置篇TypeScript和ESLint

目录

使用 create-next-app创建项目的时候,Next.js 会提示是否使用 TypeScript 和 ESLint,本篇会详细介绍 TypeScript 和 ESLint 的配置内容,帮助大家了解默认设置中支持的功能和逻辑。

Next.js 内置了对 TypeScript 的支持,会自动安装所需依赖以及进行合适的配置。

当你运行 create-next-app 创建项目的时候,就会让你选择是否使用 TypeScript,默认是 Yes

npx create-next-app@latest

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/338e1a0cbeae4e6dbed45b8aabcab1db~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=2048\&h=400\&s=1160077\&e=png\&b=090f16

将文件重命名为 .ts或者 .tsx,然后运行 next devnext build,Next.js 会自动安装所需依赖,以及添加一个包含推荐配置项的 tsconfig.json文件。

如果你已经有了一个 jsconfig.json 文件,那就需要你手动拷贝 jsconfig.json中的内容(比如 paths)到 tsconfig.json中,然后删除掉之前的 jsconfig.json文件。

Next.js 包含了一个自定义的 TypeScript 插件和类型检查器。VScode 和其他代码编辑器可以用其实现高级类型检查和自动补写功能。

在 VSCode 中开启:

  1. 打开命令面板(Ctrl/⌘ + Shift + P
  2. 搜索 TypeScript: Select TypeScript Version
  3. 选择“Use Workspace Versiion

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f51c0de853c0406c888bbe530ab425c1~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1600\&h=637\&s=265220\&e=png\&b=212329

现在,当你编辑文件的时候,自定义 TypeScript 插件就会启用。当运行 next build的时候,会执行自定义类型检查器。

插件功能介绍:

  1. 当使用了无效的路由段配置会出现警告:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/92a7d286aa984ea48754705c1772f15f~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=2204\&h=426\&s=137977\&e=png\&b=1f1f1f

  1. 显示上下文文档:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/23bbc37a4282495b9ca8ea4802de1632~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=2192\&h=592\&s=197306\&e=png\&b=252526

  1. 显示可用选项:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/778168b2f93e4ce99b86d1dae56f389c~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1826\&h=394\&s=99703\&e=png\&b=202020

  1. 确保 use client被正确使用:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ac2531413a04757bf683c1655adc604~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1534\&h=554\&s=107023\&e=png\&b=1f1f1f

  1. 确保客户端 hooks(如 useState)只在客户端组件中使用:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45148815980341b38c6b766c60388871~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1256\&h=410\&s=88021\&e=png\&b=202020

Next.js 推荐最少要 v4.5.2 版本以获取如导入名称的 type 修饰符(type Modifiers on Import Names)等功能:

// 可以直接导入类型
import { someFunc, type BaseType } from "./some-module.js";

export class Thing implements BaseType {
  someMethod() {
    someFunc();
  }
}

Next.js 可以静态写入链接,防止使用 next/link 时出现拼写或者其他错误,从而保证页面导航地址的正确性。

要使用此功能,需要在项目使用 TypeScript 的前提下,在 next.config.js 中使用 experimental.typedRoutes选项开启:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    typedRoutes: true,
  },
}
 
module.exports = nextConfig

Next.js 将会在 .next/types 中生成一个包含所有路由信息的链接定义,从而让 TypeScript 据此来进行链接提示。

目前,支持任何字符串文字,包括动态路由。对于非文字字符,目前则需要手工使用 as Route标示 href :

import type { Route } from 'next';
import Link from 'next/link'
 
// 如果 href 是有效路由,则不会出现 TypeScript 错误
<Link href="/about" />
<Link href="/blog/nextjs" />
<Link href={`/blog/${slug}`} />
<Link href={('/blog' + slug) as Route} />
 
// 如果 href 不是有效路由,则会出现 TypeScript 错误,这里出现了拼写错误
<Link href="/aboot" />

如果要在一个使用 next/link 包装的自定义组件中接收 href 属性,使用泛型:

import type { Route } from 'next'
import Link from 'next/link'
 
function Card<T extends string>({ href }: { href: Route<T> | URL }) {
  return (
    <Link href={href}>
      <div>My Card</div>
    </Link>
  )
}

next.config.js 必须是一个 JavaScript 文件,它不会被 TypeScript 或者 Babel 解析,但你可以使用 JSDoc 添加一些类型检查,举个例子:

// @ts-check
 
/**
 * @type {import('next').NextConfig}
 **/
const nextConfig = {
  /* config options here */
}
 
module.exports = nextConfig

当出现 TypeScript 错误的时候,使用 next build 构建生产版本会失败。但如果你希望及时有错误,也要构建出生产代码,那你可以禁用内置的类型检查。打开 next.config.js启用 typescript.ignoreBuildErrors 选项:

// next.config.js
module.exports = {
  typescript: {
    // !! WARN !!
    // 其实是很危险的操作
    // !! WARN !!
    ignoreBuildErrors: true,
  },
}

Next.js 提供了开箱即用的 ESLint 功能(如果你创建项目的时候选择了 ESLint),查看 package.json会看到:

// package.json
{
  "scripts": {
    "lint": "next lint"
  }
}

然后运行 npm run lint 或者yarn lint:

yarn lint

如果应用中并没有 ESLint 相关的配置,则会引导你安装和配置:

? How would you like to configure ESLint?

❯ Strict (recommended)
Base
Cancel

这个时候你会看到三个选项:

  1. Strict

这是首次设置 ESLint 的推荐配置。包括 Next.js 基础 ESLint 配置以及更严格的核心 Web 指标规则集。查看 .eslintrc.json文件:

// .eslintrc.json
{
  "extends": "next/core-web-vitals"
}
  1. Base

只包括 Next.js 基础 ESLint 配置,查看 .eslintrc.json文件:

// .eslintrc.json
{
  "extends": "next"
}

这个时候你可能困惑 Strict 模式下不是包含了基础 ESLint 配置吗?为什么 Strict 下的 extends 没有 "next"呢?这是因为 "next/core-web-vitals" 的规则拓展了 "next",也就是说包含了 "next" ,所以无须再次 extends 了。

  1. Cancel

不包括任何 ESLint 配置。当你需要自定义 ESLint 配置的时候,选择此项。

如果选择了前两个配置项的任何一个,Next.js 都会自动安装 eslinteslint-config-next 作为应用的依赖项,以及在项目的根目录创建一个包含你选择的配置的 .eslintrc.json 文件。

现在你就可以运行 next lint捕获错误。一般 ESLint 设置完成后,它会在每次 next build 的时候自动运行。如果有错误会导致构建失败,警告则不会。

默认配置 eslint-config-next包含以下插件:

初次接触 ESLint 的同学可能对 ESLint 的 configplugin 概念有些困惑。我们简单讲一下区别。

现在假设你是 Next.js 的开发者,为了让用户更好的使用 Next.js,需要根据 Next.js 的用法自定义一些 ESLint 规则,就比如我们在讲 <Script> 组件的时候讲到内联脚本:

<Script id="show-banner">
  {`document.getElementById('banner').classList.remove('hidden')`}
</Script>

Next.js 要求必须为内联脚本分配一个 id,以保证 Next.js 追踪和优化脚本。如果用户没有传入 id,就可以使用 ESLint 提示一个错误。为了实现这个自定义规则,我们就需要编写一个插件,名为 eslint-plugin-next,其中 eslint-plugin-这个前缀是 ESLint 要求的。它的代码结构类似于:

module.exports = {
  rules: {
    'inline-script-id': require('./rules/inline-script-id'),
    // ... 其他规则
  }
}

开发了一堆规则还不够,正如打游戏的时候,游戏难度有简单、适中、困难、地狱,规则集合也是一样,我们也可以用这些规则建立多个分类,比如推荐、严格、宽松等,这个也可以写在插件代码中,代码结构类似于:

module.exports = {
  rules: {
    'inline-script-id': require('./rules/inline-script-id'),
    // ... 其他规则
  },
  configs: {
    recommended: {
      plugins: ['next'],
      rules: {
        // warnings
        'next/inline-script-id': 'warn',
        // errors
        'next/other-rule': 'error',
      },
    },
    strict: {
      plugins: ['next'],
      rules: {
        'next/inline-script-id': 'error',
        'next/other-rule': 'error',
      },
    },
  },
}

在这个示例中,我们为我们的 eslint-plugin-next插件定义了两套配置,一套名为 recommended,一套名为 strict。其中 plugins: ['next'] 表示加载名为 eslint-plugin-next的插件(ESLint 会自动补全名字),其实就是自己,然后 rules设定使用哪些规则和规则的提示,比如同样是 inline-script-id 这条规则,在 recommended 中,我们设定为 warn,但是在 strict 中,既然是严格,我们就设定为 error

有了这个插件还不够,Next.js 是基于 React 的,关于 React 的用法,也有一些 ESLint 规则,React 的开发者对应开发了 eslint-plugin-react 插件。这个插件虽然不错,但是个别规则我不想用,又该怎么办呢?为了帮助开发者提供一个更加“成品”的配置项,你可以定义一个 eslint-config-next配置,其中 eslint-config-这个前缀是 ESLint 要求的。它的代码结构类似于:

module.exports = {
  extends: [
    'plugin:next/recommended',
  ],
  plugins: ['react'],
  rules: {
    'react/no-unknown-property': 'warn',
    'react/jsx-no-target-blank': 'off'
  }
}

在这个例子中,我们使用 extends 表示使用已有的配置,'plugin:next/recommended'表示的就是 eslint-plugin-next插件中的 recommended 配置。plugins 表示加载的插件,但是这只是加载,具体使用哪些规则,你还需要写 rules 自定义。

这个成品的配置,我们可以在 .eslintrc.json中,使用 extends 语法直接继承:

{
  "extends": "next"
}

因为所有的配置包都是以 eslint-config-开头,所以不需要写成 "extends": "eslint-config-next",ESLint 会自动补全。

简而言之,就如它们的名字一样,plugin 侧重于规则的编写,config 侧重于规则的配置。plugin 使用 plugins: [xxx]的形式加载,但还需要编写 rules 才能使用。config 可以使用 extends语法直接继承,无须再额外编写。extends 语法也可以继承 plugin 中编写的配置,语法为 extends: ['plugin:xxxx/xxx']这种形式。

Next.js 提供了一个 ESLint 插件 eslint-plugin-next,已经绑定在基本配置(eslint-config-next)中,完整的 21 条规则可以查看 https://nextjs.org/docs/app/building-your-application/configuring/eslint#eslint-plugin

如果你正在使用 eslint-plugin-next,但是没有在根目录里安装 Next.js(比如 monorepo 项目),你可以使用 .eslintrcsettings 属性告诉 eslint-plugin-next 哪里找到你的 Next.js 应用:

// .eslintrc.json
{
  "extends": "next",
  "settings": {
    "next": {
      "rootDir": "packages/my-app/"
    }
  }
}

rootDir 可以是一个路径(相对地址或者绝对地址),也可以使用通配符,比如 packages/*/,也可以是一个包含路径或使用通配符的地址的数组。

默认情况下,Next.js 会为 pages/app/components/lib/src/ 目录下的所有文件运行 ESLint。不过你可以使用next.config.jseslint.dirs 选项指定检查的目录:

// next.config.js
module.exports = {
  eslint: {
    dirs: ['pages', 'utils'], 
  },
}

同样,你可以使用 next lint--dir--file 指定检查的目录和文件:

next lint --dir pages --dir utils --file bar.js

为了提高性能,Next.js 默认会缓存 ESLint 处理信息,存在.next/cache或者构建目录下 。如果需要禁用缓存,使用 next lint--no-cache

next lint --no-cache

如果你希望修改或者禁用插件(reactreact-hooksnext)中的规则,你可以直接在 .eslintrc 中使用 rules 属性更改它们:

// .eslintrc.json
{
  "extends": "next",
  "rules": {
    "react/no-unescaped-entities": "off",
    "@next/next/no-page-custom-font": "off"
  }
}

前面讲到,首次运行 next lint并选择 Strict 选项,会开启使用 next/core-web-vitals规则集:

{
  "extends": "next/core-web-vitals"
}

通过查看源码,可以得知目前 core-web-vitals 配置相比于默认配置会更加严格:

module.exports = {
  rules: {
    // ...
  },
  configs: {
    recommended: {
      plugins: ['@next/next'],
      rules: {
        // warnings
        '@next/next/no-html-link-for-pages': 'warn',
        '@next/next/no-sync-scripts': 'warn',
         //...
      },
    },
    'core-web-vitals': {
      plugins: ['@next/next'],
      extends: ['plugin:@next/next/recommended'],
      rules: {
        '@next/next/no-html-link-for-pages': 'error',
        '@next/next/no-sync-scripts': 'error',
      },
    },
  },
}

其实目前也就将 2 条规则设定的更严格了一些,一条是 no-html-link-for-pages,也就是不要用 <a> 标签,而是用 Link 组件替代:

function Home() {
  return (
    <div>
      <a href="/about">About Us</a>
    </div>
  )
}

你应该改为使用:

import Link from 'next/link'
 
function Home() {
  return (
    <div>
      <Link href="/about">About Us</Link>
    </div>
  )
}
 
export default Home

另外一条是 no-sync-scripts,不要用同步脚本,也就推荐使用 Script 组件或者为 script 添加 defer 或者 async 属性:

使用 Script 组件:

import Script from 'next/script'
 
function Home() {
  return (
    <div class="container">
      <Script src="https://third-party-script.js"></Script>
      <div>Home Page</div>
    </div>
  )
}
 
export default Home

添加 async 或 defer 属性:

<script src="https://third-party-script.js" async />
<script src="https://third-party-script.js" defer />

ESLint 也包含代码格式化规则,可能会跟已经存在的 Prettier 配置冲突。我们推荐使用 eslint-config-prettier 让 ESLint 和 Prettier 能够协同工作。

首先安装依赖:

npm install --save-dev eslint-config-prettier
 
yarn add --dev eslint-config-prettier
 
pnpm add --save-dev eslint-config-prettier
 
bun add --dev eslint-config-prettier

然后添加 prettier.eslintrc.json中:

// .eslintrc.json
{
  "extends": ["next", "prettier"]
}

开发的时候,你肯定不希望修改了一个文件后,又所有文件都执行一次 ESLint 检查,那你就可以使用 lint-staged,它会在暂存的 git 文件上(git add 的那些文件)执行 ESLint 检查。

为了搭配使用 lint-staged,你需要将以下内容添加到根目录的 .lintstagedrc.js 文件:

// .lintstagedrc.js
const path = require('path')
 
const buildEslintCommand = (filenames) =>
  `next lint --fix --file ${filenames
    .map((f) => path.relative(process.cwd(), f))
    .join(' --file ')}`
 
module.exports = {
  '*.{js,jsx,ts,tsx}': [buildEslintCommand],
}

如果你已经有了单独的 ESLint 配置,并且想要保持原有的配置,那你可以基于目前的配置,直接从 Next.js ESLint 插件(eslint-plugin-next)中拓展规则:

module.exports = {
  extends: [
    //...
    'plugin:@next/next/recommended',
  ],
}

或者是引入 eslint-config-next,不过确保它在其他配置项后,举个例子:

// .eslintrc.json
{
  "extends": ["eslint:recommended", "next"]
}
  1. Configuring: TypeScript | Next.js
  2. Configuring: ESLint | Next.js