Next 项目搭建指南(写着写着就 3 万多字了~~~)

引言

因公司项目需求, 开始研究 NextJS 本篇主要记录个人在搭建项目的一些记录!!

如果你用过 React 并且想尝试 NextJS, 来搭建 React 项目, 那么本篇文章一定可以给你带来些收获!

一、初始 Next

正如官网所描述的, NextJS 是一个构建于 NodeJS 之上的一个 Web 开发框架。它基于 React 特性进行了一些列的扩展!!

首先它是基于 NodeJS 环境, 所以我们在部署 NextJS 需要有 NodeJS 环境, 因为它是基于 NodeJS 环境或者说它本质上在部署时是通过 NodeJS 启动了一个服务, 它既有客户代码、又有服务端的代码, 所以基于 NodeJS 你可以做很多事情!!

目前 React 官方文档优先推荐的创建项目的方式便是使用 NextJS。目前 NextJSGitHub 已有 116k star, Npm 周下载量 241W 左右, 堪称最受欢迎的开发框架之一。

它主要做的事情有以下几点:

  • 完善的工程化机制: 通过脚手架搭建项目, 该有的配置都给你弄好, 同时还支持扩展配置
  • 基于文件、目录的路由系统, 支持动态路由、路由分组、平行路由、路由拦截等特性
  • 基于 NodeJS 可配置中间件, 对用户访问的页面进行重定向、鉴权等操作
  • 基于 NodeJS 可以编写简单的 API 接口
  • 支持服务端组件、客户端组件, 服务端组件可直接调用 NodeJS 相关 API、可直接访问数据库
  • 支持各种渲染方式
    • CSR(Client-side Rendering): 客户端渲染, 简单理解就是正常的 React 项目, 页面的渲染是由浏览器端通过 JS 渲染出来的
    • SSR(Server-side Rendering): 服务端渲染, 每次用户访问路由, 在服务端直接通过 NodeJS 对页面进行渲染, 渲染成完整的页面后返回给用户, 从而提高页面访问速度和 SEO, 因为是服务端生成的页面, 所以你可以轻松访问数据、调用接口(速度更快)、或者直接调用 NodeJS API 访问服务端本地文件资源...
    • SSG(Static Site Generation): 静态页面生成, 在项目编译、部署阶段, 就直接在服务端直接通过 NodeJS 将相关页面进行渲染, 最后生成多个 HTML 文件, 当用户请求相关路由其实就是访问这些 HTML 页面
    • ISR(Incremental Static Regeneration): 增量静态再生, 可以理解是 SSG 静态页面生成的补充, SSG 静态页面生成的页面是在编译阶段就生成了, 后续用户的访问的页面将永远不变, 而 ISR 增量静态再生的作用就是可以服务运行期间重新生成新的静态页面, 比如可以定时每隔一段时间生成新的静态页面
  • 支持国际化, 可能很方便进行配置
  • 提供 ImageFontLinkScript 等组件, 针对相应的资源请求做了很多优化处理, 比如 Image 组件默认支持懒加载等功能
  • 更多功能期待你的探索...

二、初始化项目

下面开始正式搭建项目....

2.1 初始化

这里我们直接按照 官方文档 来, 通过官方脚手架 create-next-app 来快速搭建项目

执行命令 npx create-next-app@latest, 根据提示选择需要的配置项

好的, 项目已经起来了, 是不是很简单。

2.2 目录介绍

生成的项目配置如下, 可以简单看下注释说明:

  1. React 项目, 一般情况下 public 目录下的资源会被当做静态资源被打包, 如此在项目中我们就可以通过 / 来引用里面的资源
diff 复制代码
<div className="relative flex">
  <Image
+   src="/next.svg"
    alt="Next.js Logo"
  />
</div>
  1. src/app 目录下的资源会根据特定的文件、目录被解析为相应的路由, 比如当我们:
  • 访问 / 路径, 将访问 src/app/page.tsx 页面
  • 访问 /about 路径, 将访问 src/app/about/page.tsx 页面
  • 访问 /about/list 路径, 将访问 src/app/about/list/page.tsx 页面
  1. src/app 目录下不同文件将会有不同的作用, 比如:
  • page.tsx 将会被作为页面被展示
  • layout.tsx 将会被作为当前页面、下级页面的父容器被嵌套使用
  • 除此之外还有很多其他特殊作用的页面, 比如: template.jsnot-found.jserror.tsxloading.tsxnot-findroute.jsmiddleware.js

2.3 常见脚本介绍

初始化完项目, 我们肯定想启动项目, 所以我们第一件事自然是看下 package.json 中的 npm 脚本

js 复制代码
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint"
},

如你所见, 这里有四个 npm 脚本 "dev": "next dev""build": "next build""start": "next start""lint": "next lint", 相信你从命令名称也能猜到这几个命令的作用, 下面我们进行简单介绍:

2.3.1 next dev

开发模式下, 使用 next dev 来运行项目, 开发模式下将具有 热加载错误报告等功能, 同时默认情况下, 项目运行后可通过 http://localhost:3000 进行访问, 如下可以通过执行 npm run dev 来运行 next dev 命令

当然如果你需要修改端口可通过 -p 参数来调整, 比如 next dev -p 3001, 如下执行命令 npm run dev -- -p 3001, 通过 --npm 脚本进行传参, 所以实际上执行的就是 next dev -p 3001

如果你需要修改主机名, 可通过 -H 来实现, 比如: next dev -H 192.168.0.105, 这样的话其他局域网内用户就可以通过 IP 来访问你的项目了, 如下执行命令 npm run dev -- -H 192.168.0.105, 通过 --npm 脚本进行传参, 所以实际上执行的就是 next dev -H 192.168.0.105

2.3.2 next build

构建项目, 将会对项目进行打包, 构建生成环境所需的产物, 如下可以通过执行 npm run build 来运行 next build 命令, build 的产物都会放在 .next 目录下

next build 执行完成会输出所有路由(页面、接口)、相关资源的路径、大小, 如下图所示:

  • Route (app): 列举出当前项目所有的路由(页面、接口), 简单理解就是列出项目中定义的所有页面、接口
  • First Load JS shared by all: 表示首次加载任何路由(页面、接口), 所需要加载的共同的资源路径、大小; 简单理解就是所有页面、接口都需要用到的资源
  • Size: 表示当前何路由(页面、接口), 在加载时所需的最小资源大小(非公共的资源大小)
  • First Load JS: 首次加载当前路由(页面、接口)所需的资源大小, 简单来讲就是如果访问当前页面、接口, 需要加载的资源大小, 其实就是等于 Size 加上 First Load JS shared by all 的总和

--debug 参数, next build 命令有个 --debug 参数, 加上该参数后, 在 build 成功后会额外输出 next.config.js 中 rewritesredirectsheaders 等配置

--profile 参数, next build 命令有个 --profile 参数, 默认情况下 ReactProfiler(性能分析) 只会在开发环境下生效, 但是如果我们在 build 时加上 --profile 参数, 如此在生产环境下, 也是可以使 Profiler(性能分析) 生效

2.3.3 next start

在生产环境部署时, 当我们执行 next build 构建出生产代码之后, 就可以通过 next start 来运行生产环境项目

next dev 开发模式相同, 程序默认开启在 http://localhost:3000 如果你想更改端口号, 可通过 -p 参数来调整, 例: next start -p 3001, 如下执行命令 npm run dev -- -p 3001, 通过 --npm 脚本进行传参, 所以实际上执行的就是 next dev -p 3001

如果你需要修改主机名, 可通过 -H 来实现, 比如: next start -H 192.168.0.105, 这样的话其他局域网内用户就可以通过 IP 来访问你的项目了, 如下执行命令 npm run dev -- -H 192.168.0.105, 通过 --npm 脚本进行传参, 所以实际上执行的就是 next dev -H 192.168.0.105

2.3.4 next lint

执行 next lint 命令, 将会针对 pages/app/components/lib/src/ 等目录下的代码进行 ESLint 语法校验, 同时如果你没有安装 ESLint, 执行该该命令会提供一个安装指导!

如果你想要指定检查的目录, 可使用 --dir 参数, 如下命名将会检测 utils 目录下的所有代码

2.3.5 next info

除了上面几个命名, 官方还提供了一个 next info 命令, 可用于打印当前系统相关的信息, 在你遇到问题, 需要请求帮助的时候就特别有用, 你可以将这些信息和你的问题一起上报给他人, 方便其他人帮忙排查问题!

三、切换 pnpm

3.1 pnpm 简介

本质上 pnpm 本质上就是一个包管理器, 从作用上来看跟 npm/yarn 其实没有区别, 但它作为杀手锏的两个优势在于:

  1. 包安装速度极快
  2. 高效利用磁盘空间:
  • 不会重复安装同一个包, 用 npm/yarn 的时候, 如果 100 个项目都依赖 lodash, 那么 lodash 很可能就被安装了 100 次, 磁盘中就有 100 个地方写入了这部分代码。但在使用 pnpm 只会安装一次, 磁盘中只有一个地方写入, 后面再次使用都会直接使用 hardlink(硬链接)
  • 即使一个包的不同版本, pnpm 也会极大程度地复用之前版本的代码。举个例子, 比如 lodash100 个文件, 更新版本之后多了一个文件, 那么磁盘当中并不会重新写入 101 个文件, 而是保留原来的 100 个文件的 hardlink(硬链接), 仅仅写入那一个新增的文件!!
  1. 支持 monorepo: pnpmnpm/yarn 另外一个很大的不同就是支持了 monorepo, 体现在各个子命令的功能上, 比如在根目录下 pnpm add A -r, 那么所有的 package 中都会被添加 A 这个依赖, 当然也支持 --filter 字段来对 package 进行过滤
  2. 安全性高: 之前在使用 npm/yarn 的时候, 由于 node_module 的扁平结构, 如果 A 依赖 B, B 依赖 C, 那么 A 当中是可以直接使用 C 的, 但问题是 A 当中并没有声明 C 这个依赖。因此会出现这种非法访问的情况。但 pnpm 脑洞特别大, 自创了一套依赖管理方式, 可以很好的 规避非法访问依赖的风险的, 从而保证了安全性

3.2 迁移

  1. 全局安装 pnpm 这里我们直接通过 npm 来进行安装
sh 复制代码
npm i -g pnpm
  1. 删除 node_modules 目录

  2. 执行 pnpm import, 该命令的作用其实就是基于现有的 package-lock.jsonlock 文件生成一份 pnpm-lock.yaml

sh 复制代码
pnpm import
  1. 删除 package-lock.json 等多余的 lock 文件, 然后执行 pnpm i 来重新安装依赖
sh 复制代码
pnpm i
  1. 到处迁移基本已经完毕, 但是这里我们还可以加个配置, 避免其他人通过 npm installyarn 来安装依赖, 从而避免项目依赖被污染!! 如下代码, 新增了一条 npm 脚本 preinstall 该脚本在安装依赖前会被执行, 然后通过 only-allow 来限制包管理器
json 复制代码
{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}

补充:

  • 即便有 pnpm import 的帮忙, 但一切皆有可能, 所以不管如何请提前做好备份, 在迁移完成后一段时间确保在发生问题时能够及时回退
  • 如果可以的话, 我们完全可以在最开始时就使用 pnpm 来初始化 NextJS 项目, 例如:
sh 复制代码
pnpm create next-app new-next-app

# 使用 TS
pnpm create next-app ts-next-app --template typescript

到此执行 pnpm dev 将运行开发环境:

sh 复制代码
$ pnpm dev

> init@0.1.0 dev /Users/qianyin/coding/next-play/init
> next dev

   ▲ Next.js 14.1.0
   - Local:        http://localhost:3000

浏览器访问 http://localhost:3000 将呈现如下画面:

四、安装 NextUI

4.1 为什么要选择 NextUI?

首先, NextUINextJS 官方出品的一个 UI 组件库, 和常规的 UI 组件库的商务风、老旧相比, NextUI 主打一个好看、现代风, 当然从官网来看, 确实挺好看的!!

NextUIReactUI 库, 可帮助您构建美观且易于访问的用户界面, 它是在 Tailwind CSSReact Aria 之上创建。并且使用 Framer Motion 来对某些组件进行动画处理, 从而能够以更直接、更高效的方式处理这些动画。

NextUI 的优点如下:

  • 构建于 Tailwind CSS 之上, 这意味着捆绑包中没有运行时样式, 也没有不必要的类
  • 自动暗模式识别, NextUI 在检测到 HTML theme prop 变化时自动更改主题
  • NextUI 是完全类型化的, 可以最大限度地减少学习曲线, 并提供最佳的开发人员体验

4.2 安装

根据 官网 来, 官网提供了两种安装方式, 这里我采用的是全局安装方式

  1. 安装依赖包
sh 复制代码
pnpm i @nextui-org/react framer-motion
  1. Tailwind CSS 设置

NextUI 构建在 Tailwind CSS 之上, 因此我们这里还需要配置下 tailwind.config.ts(Tailwind CSS 配置文件):

  • content 配置项, 新增 @nextui-org 相关文件, 需要解析里面的 Tailwind CSS
  • darkMode: 'selector', 修改 Tailwind CSS 主题设置模式, 默认情况下是会根据浏览器主题, 来主动切换主题, 这里我将其修改为 selector, 如此就可以根据外层的类名来实现主题的切换, 具体参考: toggling-dark-mode-manually
  • nextui(), 新增 NextUI 相关插件
diff 复制代码
import type { Config } from 'tailwindcss'
+ import { nextui } from '@nextui-org/react'

const config: Config = {
  content: [
    ...
+   "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", // nextui 相关
  ],
  theme: {
    ....
  },
+ darkMode: 'selector', // 修改主题切换的模式, see: https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually
+ plugins: [
+   nextui(), // nextui 相关
+ ],
}
export default config

上面👆🏻我们修改了, 主题切换的模式, 所以我们还需要在 app/layout.tsx 中为根元素 html 设置一个 className, 来指定项目的主题色! 如下代码, 我设置的 classNamedark 即暗色模式

diff 复制代码
+ <html lang="en" className="dark">
....     
</html>
  1. Provider Setup

在入口处 app/layout.tsx 中使用 NextUI 组件 NextUIProvider 对所有页面内容进行一个全局包裹

  • 设置了 use client 因为 NextUIProvider 需要在客户端组件中使用
  • 删除 metadata 相关配置, 因为它只能在服务端组件中导出, 这里可以移到 page.tsx 页面中
  • 使用 NextUIProvider 来包裹所有 children
diff 复制代码
+ 'use client'
- import type { Metadata } from "next";
import { Inter } from "next/font/google";
+ import { NextUIProvider } from "@nextui-org/react";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

- export const metadata: Metadata = {
-   title: "Create Next App",
-   description: "Generated by create next app",
- };

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
+       <NextUIProvider>
+         {children}
+       </NextUIProvider>
      </body>
    </html>
  );
}

如果你还是想在 app/layout.tsx 中统一设置 metadata, 那么可以将 NextUIProvider 包裹的这块相关代码挪到一个新的文件 app/Provider.tsx 中, 该文件导出一个客户端组件, 并接收一个 children, 其完整代码如下:

js 复制代码
'use client'
import { NextUIProvider } from "@nextui-org/react";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <NextUIProvider>
      {children}
    </NextUIProvider>
  );
}

下面我们只需要在 app/layout.tsx 使用上文的 Provider 组件即可, 这样就即能保持 app/layout.tsx 服务端组件的特性, 又可以完成 NextUIProvider 组件的使用

diff 复制代码
import type { Metadata } from "next";
import { Inter } from "next/font/google";
+ import Provider from './Provider'
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
+       <Provider>
+         {children}
+       </Provider>
      </body>
    </html>
  );
}

那么问题来了 NextUIProvider 到底是啥, 我们为什么要加这个? 这里可以看 NextUI 官方的一个说明: setup-pnpm-optional

  1. pnpm 设置(可选)

这里因为我使用的是 pnpm, 所以还需要在项目根目录下创建一个 .npmrc文件(如果没有的话), 并添加如下内容:

text 复制代码
public-hoist-pattern[]=*@nextui-org/*

根据官方提示, 修改完成 .npmrc 文件后, 需要重新 pnpm install 以确保依赖安装正确

4.3 测试

运行项目, 并访问 http://localhost:3000, 其结果如下:

下面在 app/page.tsx 来引用个 Button 组件来测试下

js 复制代码
import { Button } from "@nextui-org/react"
<Button color="primary">Button</Button>

如下, 只要按钮能够正常展示即说明 NextUI 安装成功!

4.4 一个小问题

也行你已经发现了, 相比刚初始化完项目, 安装完 NextUI 后, 首页背景图却丢失了!!!

经排查, 发现是背景图部分, DOM 节点层级太低了, 中间那个 divz-index 居然是 -1

解决办法很简单, 直接将层级改为 0 就可以了

修改完成后刷新页面即可:

至于为什么最开始样式没有出现问题, 安装完 NextUI 就出现问题了? 咱们就不去深究咯!! 这也不是本文的重点, 就不管咯!!!

五、Stylelint

Stylelint 是一个强大、先进的 CSS 代码检查器, 可以帮助我们规避 CSS 代码中的错误并保持一致的编码风格

下面我们来配置下 Stylelint ...

  1. 安装所需的包
sh 复制代码
pnpm i stylelint stylelint-config-standard --save-dev
  1. 创建配置文件文件 .stylelintrc.mjs, 配置内容如下:
json 复制代码
export default {
  extends: "stylelint-config-standard",
}
  1. 配置完成, 打开 src/app/globals.css 不出意外, 应该会有 Stylelint 报错信息(当然在这之前需要你 vscode 安装了 Stylelint 插件才行)
  1. 上面报错的部分是 Tailwind 自带的一些规则, Stylelint 是无法识别的, 这里我们可以通过配置 at-rule-no-unknown 规则来忽略它们
js 复制代码
export default {
  extends: "stylelint-config-standard",
  rules: {
    "at-rule-no-unknown": [
      true,
      {
        ignoreAtRules: [
          "tailwind",
          "apply",
          "variants",
          "responsive",
          "screen"
        ]
      }
    ]
  }
}
  1. 回头看下 src/app/globals.css, Stylelint 报错信息不再有

Stylelint 配置就到此结束, 你可以根据自己的喜好调整相关的规则配置, 具体可查阅 官网

六、Eslint

Eslint 官方脚手架 就已经做了支持, 但好像只是添加了规则集合, 好多规则都没严格设置, 所以这里还可以根据自己的习惯, 配置一些规则。当然实际上我还做了一些其他调整, 具体内容如下:

6.1 修改配置文件格式

官方默认的配置文件是 JSON 格式的即 .eslintrc.json, 这里我为了方便添加注释所以采用的是 JS 格式, 所以这里我将配置文件名修改为 .eslintrc.js 并做了最基础的配置, 关于 Next 官方更多配置信息可查阅 官网

js 复制代码
module.exports = {
  extends: ['next/core-web-vitals'],
  rules: {}
}

6.2 Typescript 相关

项目中启用了 Typescript, 所以这里我配置了 Typescript 相关规则和解析器

  1. 先安装下所需的依赖:
sh 复制代码
pnpm i @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
  1. 修改 ESLint 配置(.eslintrc.js):
diff 复制代码
module.exports = {
+ parser: '@typescript-eslint/parser',
  extends: [
    'next/core-web-vitals',
+   'plugin:@typescript-eslint/eslint-recommended',
+   'plugin:@typescript-eslint/recommended',
  ],
  rules: {}
}

6.3 Tailwindcss 相关

项目中启用了 Tailwindcss, 下面我们可以引入 Tailwindcss 相关的 ESLint 插件: eslint-plugin-tailwindcss

那么插件有什么作用呢?

  • 践行 Tailwind CSS 最佳实践, 帮助我们编写一致性代码
  • 可以根据最佳实践, 来排序我们的类目
  • 根据我们编写的类名给出更好的推荐, 简化类名, 例如: w-5 h-5 实际上等价于 size-5, 如果我们写了 w-5 h-5 插件就可以帮我们推荐、优化为 size-5
  • 如果类名中存在非 Tailwind CSS 支持的类名, 会给出提示, 避免使用无效类目
  • .....
  1. 安装依赖
sh 复制代码
pnpm i eslint-plugin-tailwindcss -D
  1. 修改 ESLint 配置(.eslintrc.js):
diff 复制代码
module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    'next/core-web-vitals',
    'plugin:@typescript-eslint/eslint-recommended',
    'plugin:@typescript-eslint/recommended',
+   'plugin:tailwindcss/recommended',
  ],
  rules: {}
}

6.4 一个小问题

在配置过程中如果你遇到下面 👇🏻 这个错误提示:

请不要慌张, 这大概率是因为你在 VSCode 打开的项目并不是 NextJs 项目的根目录所引起的:

所以解决办法就很简单了: VSCode 请直接打开 NextJs 项目

七、Prettier

正如上图所描述的 Prettier 是一个 代码格式化 工具, 其支持大部分的编程语言, 基本所有编辑器都是支持的!!

补充: 和 ESLint 不同, ESLint 专门做语法规则的校验的, 而 Prettier 专注于格式化, 虽然在 VSCode 中也是支持基于 ESLint 配置来进行格式化, 但是单论格式化还是 Prettier 更为强大

Prettier 安装配置还是比较简单的:

7.1 安装

首先先安装 prettier

sh 复制代码
pnpm i --save-dev prettier

7.2 项目配置

项目根目录下创建 配置文件 .prettierrc.mjs 并添加如下内容(同上配置这里我还是尽可能选择使用 JS 格式, 并且尽量使用 ES 语法来编写):

js 复制代码
/** @type {import("prettier").Config} */
const config = {
  singleQuote: true,            // 除了 jsx 属性, 其他都应该使用单引号
  singleAttributePerLine: true,  // 对应 eslint react/jsx-max-props-per-line
  jsxBracketSameLine: true       // 对应 eslint react/jsx-closing-bracket-location
};

export default config;

7.3 VSCode 格式化方式设置

  • 首先先安装个 prettier-vscode 插件
  • 在项目根目录下创建 VSCode 配置文件 .vscode/settings.json
  • 添加如下配置: 下面配置对于样式部分将根据 stylelint 进行格式化、对于 JSTSJSON 部分则采用 prettier 进行格式化
json 复制代码
{
  //#region 格式化
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": "explicit"
  },

  "editor.defaultFormatter": "esbenp.prettier-vscode", // 默认使用 prettier 进行格式化

  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true, // 保存的时候自动格式化
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true, // 保存的时候自动格式化
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true, // 保存的时候自动格式化
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true, // 保存的时候自动格式化
  },
}

7.4 与 ESLint 相爱相杀

实际上 PrettierESLint 之间的冲突是难免的, 比如我们使用 Prettier 自定格式化代码后, ESLint 却不能够通过, 提示错误!

而解决办法核心就是 "协调":

  • 同一规则, 如果 PrettierESLint 都提供配置, 那么只需要保证他们配置一致即可
  • 同一规则, 如果 PrettierESLint 只有其中一方提供了配置, 那么退而求其次, 以 Prettier 规则为准了
  • 解决和 ESLint 冲突的办法还有一个, 就是使用 eslint-config-prettier 插件, 本质上就是将 prettier 中的配置合并到 ESLint 中, 还是以 Prettier 为准, 所以我这里就没安装这个插件, 如果有冲突, 手动调整算了!

最后给出几个我自己的配置:

js 复制代码
/** @type {import("prettier").Config} */
const config = {
  tabWidth: 2,
  printWidth: 120,
  endOfLine: 'lf',
  singleQuote: true, // 除了 jsx 属性, 其他都应该使用单引号
  singleAttributePerLine: true, // 对应 eslint react/jsx-max-props-per-line
  jsxBracketSameLine: true, // 对应 eslint react/jsx-closing-bracket-location
};

export default config;

八、最后的防线: lint-staged + husky

上文我们配置了 ESLintStylelint 它们的作用主要就是对我们项目的代码格式、语法进行了一个校验, 但这里我们也只是做了校验, 如果校验不通过也仅仅只会在 VSCode 会进行提示...

也就是说, 即便 ESLintStylelint 校验不通过, 你的小伙伴也可以不调整代码, 系统不会有任何问题, 代码依然能够进行提交!!

所以这一步, 我们要做的其实就是避免你的小伙伴, 将不符合 Lint 规则的代码进行提交....

大体思路就是借助 githooks 在提交代码时, 对要提交的代码进行 Lint 规则校验, 只有校验通过才允许提交代码!

8.1 lint-staged

lint-staged 可以帮助我们针对暂存的 git 文件运行相应的 linter, 避免让糟糕的 💩 混入你的仓库中!!!

  1. 安装依赖包
sh 复制代码
pnpm i -D lint-staged
  1. 添加 lint-staged 配置文件 .lintstagedrc.mjs, 配置内容如下:
  • 配置, 导出的就是一个对象
  • 对象的 key 用于限制文件格式
  • 对象的 value 是将要对匹配到的文件执行的命令
js 复制代码
export default {
  '**.{js,ts,jsx,tsx,mjs,cjs}': 'eslint',
  '**/*.{css,scss,sass,less}': 'stylelint',
};
  1. 测试: 上面我们完成了 lint-staged 安装和配置, 配置规则则会对暂存中的相关文件执行 eslintstylelint 命令

下面我们可以随便写点 ESLint 校验不通过的代码:

将修改内容添加到暂存区 git add ., 然后执行 lint-staged 命令即 npx lint-staged, 这时将会根据配置文件 .lintstagedrc.mjs 对暂存的文件执行相应的命令

如上图, 执行 lint-staged 终端中 ESLint 校验未通过, 成功抛出错误!!!

8.2 husky

husky 主要作用就是帮助我们, 方便且优雅的为项目设置 git hooks

  1. 安装依赖包
sh 复制代码
pnpm i husky -D
  1. 执行 npx husky init 来初始化项目, 该命令做了两件事:
  • 新增 npm 脚本, preparenpm 的一个 hooks 它会执行 husky 命令
  • 项目下新增 .husky 目录, 该目录下用于保存我们要设置的 git hooks, 该目录一个文件映射对应的 git hooks 文件内则是对应 hooks 被触发时会执行的命令
  • 补充: 当时实际上, husky 肯定也对项目下的 .git 中的相关 git 配置做了调整

8.3 结合

在上文我们只是简单安装、配置了下 husky 以及 lint-staged, 下面我们需要将它们结合起来使用!!

也就是说我们需要在 .husky 中配置下 git hooks 作用就是在执行 git commit 之前, 对暂存的内容部分通过 lint-staged 来进行一个校验, 从而确保上传到仓库的代码都是符合我们设置的 linter

  1. 修改 .husky/pre-commit 配置, 新增一行, 内容为 npx lint-staged
  1. 这样的话, 当我们执行 git commit(触发 pre-commit hooks) 时就会执行 lint-staged 命令, 即开启 linter 校验
sh 复制代码
git add .
git commit -m 'feat: add lint-staged'

如上图所示, 我在执行 git commit 时, ESLint 校验没有通过, 则 git commit 失败!!!

九、Git Commit 校验

Github 你可能会看到如下图所示的提交信息

这种提交格式, 是目前比较流行的一种「Git 提供规范」了, 具体可参考 Commitlint, 后面我们也会使用到该工具, 实现 Git Commit 校验

补: 该规范, Git Commit 提交信息规范格式如下, 更多详见 Conventionalcommits

js 复制代码
type(scope?): subject
body?
footer?

9.1 安装依赖

首先先安装下相关的依赖:

  • @commitlint/config-conventional: 配置文件, 预定义好目前一套比较流行的规则配置, 省去自己配置
  • @commitlint/cli: 提供 CLI 可用于检查 commit 信息是否符合特定格式, 配置简单、可自定义规则、可共享配置规则..
sh 复制代码
pnpm i -D @commitlint/config-conventional @commitlint/cli

9.2 添加配置文件

项目根目录下, 执行下面 👇🏻 命令: 将会创建配置文件 commitlint.config.js 并添加配置(使用 extends 关键词继承 @commitlint/config-conventional 导出的规则)

sh 复制代码
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

生成的配置文件内容如下(做了格式化):

9.3 添加 Git Hooks

到此我们已经可以使用 commitlint 来检验 Git Commit, 下面我们可以简单做个测试: 我们使用 echo 输出一个文本信息, 通过管道作为 npx commitlint 输入, 执行 commitlint 对文本内容进行校验

新增配置文件 .husky/commit-msg, 并添加内容 npx --no -- commitlint --edit ${1}

9.4 测试

下面我们使用 init project 作为提交信息进行提交, 不出意外 commitlint 抛出错误, 代码提交失败

下面我们使用 feat: init project 作为提交信息进行提交, commitlint 校验通过, 代码提交成功

9.5 czg: 交互式 CLI

czg, 一个交互式 CLI 命令, 帮助我们快速生成标准化提交消息

  1. 安装依赖包: 这里我只在项目下进行安装, 当然你也可以全局安装使用
sh 复制代码
pnpm i -D czg
  1. 安装完成即可使用
  1. 当然如果你不想使用 npx, 我们可以额外加个 npm 脚本来调用 czg
diff 复制代码
// package.json
{
  "name": "next-play",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    ...
+   "cz": "czg"
  },

下面我们就可以通过 pnpm cz 来调用(其实也没少打多少 🐶)

更多内容可以参考 《git commit 规范校验配置和版本发布配置》

十、版本发布

在我们发布一个正式版本时, 我们可能需要做以下几件事:

  • 生成 CHANGELOG, 其包含本次版本新增的主要内容
  • 为我们的 Git 仓库, 打标签(Tag)
  • 修改当前项目的版本号
  • ...

这些步骤如果全部我们人工处理, 相对来说还是比较繁琐的, 好在社区就有提供一些自动化工具, 可以帮助我们完成部分的工作!

这里我推荐使用 standard-version, 虽然目前作者已不再维护, 但还是可以正常使用的。该工具可以帮助我们完成以下几件事:

  • 自动生成 CHANGELOG, 如何我们遵守上文配置的提交规范, 它就会从我们的提交记录提取出 关键 的提交记录, 并生成当前版本的 CHANGELOG
  • 帮我们快速修改项目版本号
  • 帮我们生成 Git Tag

下面我们开始配置....

10.1 安装依赖

首先我们先安装下依赖

sh 复制代码
pnpm i -D standard-version

10.2 配置「npm scripts」

安装完依赖, 我们可以直接通过 npx standard-version 来调用依赖包里提供的 CLI 工具, 但为了方便使用, 这里我还配置了几个 npm 脚本:

diff 复制代码
{
  "scripts": {
+   "release": "standard-version", 
+   "release:100": "npm run release -- major",
+   "release:010": "npm run release -- minor",
+   "release:001": "npm run release -- patch",
  }
}
  1. release 脚本: 将直接执行 standard-version 脚本, 在使用时需要通过 -- 来传参, 比如我们需要指定修改一个版本号(1.1.0)、并生成对应 CHANGELOGGit Tag 则可以执行 npm run release -- --release-as 1.1.0
  2. release:100 脚本: 其实执行的就是 standard-version major, 它会基于当前版本升级主版本号, 比如如果当前版本为 1.1.1, 执行 npm run release:100 那么将发布 2.0.0 版本
  3. release:010 脚本: 其实执行的就是 standard-version minor, 它会基于当前版本升级次要版本号, 比如如果当前版本为 1.1.1, 执行 npm run release:010 那么将发布 1.2.0 版本
  4. release:001 脚本: 其实执行的就是 standard-version patch, 它会基于当前版本升级补丁版本号, 比如如果当前版本为 1.1.1, 执行 npm run release:001 那么将发布 1.1.2 版本
脚本 示例 效果
release npm run release -- --release-as 1.1.0 不管当前项目版本号是多少, 直接升级为 1.1.0
release:100 npm run release:100 升级主版本, 如果当前版本号为 1.1.1 则发布 2.0.0
release:010 standard-version minor 升级次要版本, 如果当前版本号为 1.1.1 则发布 1.2.0
release:001 npm run release:001 升级补丁版本, 如果当前版本号为 1.1.1 则发布 1.1.2

10.3 测试

下面我们尝试执行 release:001 来看下效果:

上图是执行 npm run release:001 的结果(图中我用的是 pnpm 但结果是一样的), 最后我们看一眼 CHANGELOG 生成的内容

十一、遇到的一些问题

记录搭建过程中踩到的坑....

11.1 TS 解析错误

Typescript 定义 interface 时, 定义函数 ESLint 报如下错误:

问题原因是因为 ESLint 将其视为普通函数进行解析

解决也简单, 为 ESLint 配置正确的解析器, 即 @typescript-eslint/parser

  1. 安装相关依赖
sh 复制代码
pnpm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
  1. 修改 .eslintrc.js 配置
diff 复制代码
module.exports = {
+ parser: '@typescript-eslint/parser',
  extends: {
+   'plugin:@typescript-eslint/eslint-recommended',
+   'plugin:@typescript-eslint/recommended',
  },
  rules: {
+   'no-unused-vars': 'off',
+   '@typescript-eslint/no-unused-vars': ['error'],
  }
}

参考资料 ESLint - Configuring "no-unused-vars" for TypeScript

11.2 ESLint 报错: 字符串不允许使用 ...

如题, 当我们尝试对字符串使用 ... 将其展开为字符数组, 会报如下错误:

解决办法, 修改 tsconfig.jsontarget 字段(如果没有就加一个), 将其设置为 es6 即可

js 复制代码
{
  "compilerOptions": {
+   "target": "es6",
    ...
  },
  ...
}

十二、参考

相关推荐
阿伟来咯~29 分钟前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端34 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱36 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai1 小时前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js