---
sidebar_position: 2
---

# 路由规范

## 代码规范

### 通用准则

-   **保持一致！**
-   避免使用已经被废弃的特性。
-   避免修改 `yarn.lock` 和 `package.json`，除非您添加了新的依赖。
-   将重复的代码合并为函数。
-   保持中间值不可变，构造新对象而不是修改已有对象。
-   优先使用更高版本的 ECMAScript 标准特性，而不是使用低版本特性。
-   按字母顺序排序（大写字母优先），以便更容易找到条目。
-   尽量使用 HTTPS 而非 HTTP 传输数据。
-   尽量使用 AVIF/HEIF/WebP 格式而非 JPG 格式，因为前者支持更好的压缩。

### 代码格式

#### 缩进

-   使用 4 个空格缩进。

#### 分号

-   在每条语句结尾添加分号。

#### 字符串

-   使用单引号而不是双引号。
-   使用 [模板字符串](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals) 而非复杂的字符串拼接。
-   对于 GraphQL 查询，使用 [模板字符串](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals)。

#### 空格

-   在每个文件末尾添加一个空行。
-   避免尾随空格，代码应整洁易读。

### 语言特性

#### 类型转换

-   避免重复转换同一类型。

#### 函数

-   优先使用 [箭头函数](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions)，而不是使用 `function` 关键字定义函数。

#### 循环

-   对于数组，使用 `for-of`，而不是使用 `for`。([javascript:S4138](https://rules.sonarsource.com/javascript/RSPEC-4138))

#### 变量

-   使用 `const` 和 `let` 而不是 `var`。
-   每次声明一个变量。

### 命名

-   使用 `lowerCamelCase` 命名变量和函数
-   使用 `kebab-case` 命名文件和文件夹。
-   使用 `CONSTANT_CASE` 命名常量。

### 反面模式

-   暴力尝试匹配选择器。

:::details

```javascript
const randomGuessingSelectors = ['spray-and-pray', 'a-bunch-of-selectors', 'or-classes', 'that-most-does-not-exist', 'except-one-that-actually-exist'];
for (const selector of randomGuessingSelectors) {
    const element = $(selector);
    if (element.length > 0) {
        // ...
        break;
    }
}
```
:::

-   通过向数组 `push` 再 `join` 的方式拼接字符串。

:::details

```javascript
const strArr = ['foo'];
strArr.push('bar');
const str = strArr.join('');
```
:::

-   罗列指向同一元素的多种选择器组合。

:::details

```html
<ul><!-- 假设我们想要提取 "Three" 元素的文本 -->
    <li>One</li>
    <li>Two</li>
    <li class="blue sel">Three</li>
    <li class="red">Four</li>
</ul>
```

```javascript
const text = $('ul li.blue.sel, li.blue.sel, .blue.sel, li.blue, .blue, li.sel, .sel').text();
// 或
const text = $('ul li.blue.sel').text() || $('li.blue.sel').text() || $('.blue.sel').text() || $('li.blue').text() || $('.blue').text() || $('li.sel').text() || $('.sel').text();
```
:::

-   对嵌套的同一内容容器逐层回退取值。

:::details

```html
<div class="root">
    <div class="content-container">
        <div class="content">
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit...</p>
        </div>
    </div>
</div>
```

```javascript
const description = $('.content').html() || $('.content-container').html() || $('.root').html();
```
:::

-   在没有任何父元素或类名限定的情况下选择所有带 `href` 属性的 `a` 元素。

:::details

```javascript
const seen = new Set();
$('a[href="some/url"]').each((_, a) => {
    // ...
    if (seen.has(a.href)) {
        return;
    }
    seen.add(a.href);
    // ...
});
```
:::

-   静默返回空的 `Data` 对象而不是抛出错误。

:::details

```javascript
if (something) {
    return {
        title: 'RSS Feed',
        item: [],
    };
}
```
:::

-   不必要的 `flatMap()`。

:::details

```javascript
const ids = array.flatMap((value) => (value.active ? [value.id] : []));
```
:::

-   为可选属性赋值时使用不必要的 `if` 判断。

:::details

```javascript
const item = {};
if (author) {
    item.author = author;
}
if (description) {
    item.description = description;
}
```
:::

-   不必要的去重。

:::details

```javascript
const seen = new Set();
$('li a.all-of-the-matched-elements-are-unique').each((_, a) => {
    // ...
    if (seen.has(a.href)) {
        return;
    }
    seen.add(a.href);
    // ...
});
```
:::

-   对页面中仅出现一次的元素使用 `.first()` 或 `.last()` 进行选取。

:::details

```javascript
const element = $('.some-unique-element-that-appears-only-once').first();
// 或
const element = $('.some-unique-element-that-appears-only-once').last();
```
:::

## 禁止行为

### 虚假署名

严禁在路由的 `maintainers` 字段中填写其他开发者的 GitHub ID，除非对方已明确同意。仅填写属于您自己或已获得本人许可的贡献者的 GitHub ID。

```ts
export const route: Route = {
    // ...
    name: '某路由',
    maintainers: ['未经同意的其他开发者'],
    handler,
};
```

每次违规将导致临时禁止提交 PR（1 个月）。禁令通过 [vouch](https://github.com/mitchellh/vouch) 机制执行。被禁作者新提交的 PR 将被自动关闭。维护者在审查具体情况后可以解除禁令。

## v2 路由规范

:::danger

v2 路由规范已被弃用。所有新路由都应遵循[制作路由](/joinus/new-rss/start-code)

:::

::::details

当在 RSSHub 中编写新的路由时，需要按特定方式组织文件。命名空间文件夹应该存储在 `lib/routes` 目录下，并且应包括三个必需文件：

-   `router.ts` 注册路由
-   `maintainer.ts` 提供路由维护者信息
-   `radar.ts` 为每个路由提供对应 [RSSHub Radar](https://github.com/DIYgod/RSSHub-Radar) 规则

命名空间文件夹结构应该像这样：

    ├───lib/routes
    │   ├───furstar
    │       ├─── router.ts
    │       ├─── maintainer.ts
    │       ├─── radar.ts
    │       ├─── someOtherJs.ts
    │   └───test
    │   └───someOtherNamespaces
    ...

**所有符合条件的，在 `lib/routes` 路径下的路由将会被自动载入，无需更新 `lib/router.ts`**

### 命名空间

RSSHub 会将所有路由命名空间的文件夹名附加到路由前面。路由维护者可命名空间视为根目录。

#### 命名规范

-   使用二级域名 (second-level domain, SLD) 作为命名空间。有关 URL 结构的更多信息，请参阅 [此页面](/zh/joinus/new-radar#顶层对象键)。
-   不要创建相同命名空间的变体。有关更多信息，请参阅 [此页面](/zh/joinus/new-rss/before-start#创建命名空间)

### 注册路由

`router.ts` 文件应导出一个方法，提供一个 Hoho route handler。

### 维护者列表

`maintainer.ts` 文件应导出一个对象，提供与路由相关的维护者信息，包括：

-   键: 对应的路由
-   值：一个字符串数组，包括所有维护者的 GitHub ID。

要生成维护者列表，可使用以下命令：`pnpm run build`，它将在 `assets/build/` 目录下一份维护者列表。

:::danger

路由应该与相应的文档中添加命名空间前的 `path` 一致。

:::

### Radar 规则

所有路由都需要包含 `radar.ts` 文件，其中包括相应的域名。最低要求是规则出现在相应的站点上，即需要填写 `title` 和 `docs` 字段。

要生成完整的 `radar-rules.js` 文件，可使用以下命令：`yarn build`，它将在 `assets/build/` 目录下创建文件。

:::tip

在提交代码之前，请记得删除所有在 `assets/build/` 中的生成的资源。

:::

::::

<!-- markdownlint-enable MD046 -->

## v1 路由规范

:::danger

v1 路由规范已被弃用。所有新路由都应遵循[制作路由](/joinus/new-rss/start-code)

:::
