使用ESLint来实现禁止Angular中目录级别的依赖的规则

在收看ng-japan OnAir时,我了解到了一个看起来很方便的 Lint 插件。

采用此规则后,可以禁止从任意目录到另一个目录的依赖关系。

哎呀……!我不知道……!!!

如果可以在文件保存时删除不必要的导入语句,并在文件保存时进行代码格式化,并且能够在目录级别上禁止依赖的话,那将非常方便。

因此,在本文中,我将记录创建一个全新的 Angular 项目并运行 Lint 的步骤。

写文章的环境

    • VSCode の利用を前提

 

    • Angular v15

 

    node 18

步骤 (bù

创建 Angular 项目

在创建项目时的问题与本文的内容无关,因此可以选择一个合适的问题。

npx -p @angular/cli@15 ng new

? What name would you like to use for the new workspace and initial project?
> demo
? Would you like to add Angular routing?
> No
? Which stylesheet format would you like to use?
> CSS
...

✔ Packages installed successfully.
    Successfully initialized git.

ESLint的安装配置

在第一次运行 Lint 时,Schematics 会为我设置 ESLint。非常方便。

npm run ng lint

? Would you like to share pseudonymous usage data about this project ...
> No
Would you like to add ESLint now?
> Yes
Would you like to proceed?
> Yes
...

✔ Packages installed successfully.

安装 ESLint 插件

安装各种插件。它们将帮助我们实现前文中的规则。

npm install -D \
  eslint-import-resolver-typescript\
  eslint-plugin-angular\
  eslint-plugin-import\
  eslint-plugin-prettier\
  prettier\
  prettier-eslint\
  eslint-config-prettier\
  eslint-plugin-unused-imports
这是写文章时的版本
npm安装-D \
eslint-import-resolver-typescript@3.5.5\
eslint-plugin-angular@4.1.0\
eslint-plugin-import@2.27.5\
eslint-plugin-prettier@4.2.1\
prettier@2.8.7\
prettier-eslint@15.0.1\
eslint-config-prettier@8.8.0\
eslint-plugin-unused-imports@2.0.0

创建 Prettier 的配置文件

创建一个设置文件。内容可以根据您的喜好自定义。

module.exports = {
  trailingComma: "es5",
  tabWidth: 4,
  semi: false,
  singleQuote: true,
};

创建 ESLint 的配置文件

由于JavaScript风格更受欢迎,所以我修改了文件,尽管Angular已经为我准备好了`.eslint.json`。

module.exports = {
  ignorePatterns: [
    "/node_modules",
    "/dist",
    "**/*.html",
    "**/*.scss",
    // 設定ファイル群を除外
    "*rc.js",
    "*.config.js",
  ],
  root: true,
  extends: [
    // Prettier の設定を ESLint のルールとして読み込む
    "eslint:recommended",
    "plugin:prettier/recommended",
    "plugin:import/recommended",
    "plugin:@angular-eslint/recommended",
    "plugin:@typescript-eslint/recommended",
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    project: "./tsconfig.json",
  },
  // インストールしたプラグイン群を読み込む
  plugins: ["import", "unused-imports", "prettier"],
  overrides: [
    // .ts ファイルを解決する
    {
      files: ["*.ts"],
      settings: {
        "import/resolver": {
          typescript: {
            alwaysTryTypes: true,
            project: ["./tsconfig.json"],
          },
        },
      },
      rules: {
        // import/no-restricted-paths と衝突しないための設定
        "@typescript-eslint/no-unused-vars": "off",
        // 不要なインポート文を削除
        "unused-imports/no-unused-imports": "error",
        // インポート文を並べ替え
        "import/order": ["error", { alphabetize: { order: "asc" } }],
        // ディレクトリ参照の禁止ルール
        "import/no-restricted-paths": [
          "error",
          {
            zones: [
              {
                target: "./src/app/models",
                from: "./src/app/!(models)/*",
                message: "モデルはこのフォルダからインポートできません",
              },
              {
                target: "./src/app/repositories",
                from: "./src/app/components",
                message: "リポジトリはコンポーネントをインポートできません",
              },
            ],
          },
        ],
      },
    },
  ],
};

执行一次 Lint

事先将 ESLint 规则应用于 Angular 的模板代码。

npm run lint -- --fix

安装VSCode扩展

为了实现开头的“在保存文件时~”功能,请安装 ESLint 扩展。
在设置中追加以下内容。

"editor.codeActionsOnSave": {
    // ESLint の推奨する内容でコードを上書きする
    "source.fixAll.eslint": true,
  },
  // .ts ファイルの保存時に ESLint のルールにそってフォーマットする
  "[typescript]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "dbaeumer.vscode-eslint"
  },

尝试一下

为了确认Lint的行为,我准备了一个简单的模型、仓库和组件的示例代码。

export type Demo = { id: number }
import { Injectable } from '@angular/core'
import { Demo } from 'src/app/models/demo.model'

@Injectable({
    providedIn: 'root',
})
export class DemoRepository {
    fetch(): Demo {
        return { id: 1 }
    }
}
import { Component, inject } from '@angular/core'
import { DemoRepository } from 'src/app/repositories/demo.repository'

@Component({
    selector: 'app-demo',
    template: ``,
})
export class demoComponent {
    private readonly repository = inject(DemoRepository)
}

目录结构如下:
依赖方向设定为:组件 > 仓库 > 模型 单向。

├── src
│   ├── app
│   │   ├── components
│   │   │    └── demo.component.ts
│   │   ├── models
│   │   │    └── demo.model.ts
│   │   ├── repositories
│   │   │    └── demo.repository.ts

重新启动 ESLint.

在文件保存时,使用Prettier对代码进行格式化。

试着随意打破它。

export 
  type Demo       = {
           id: number}

文件保存时,会对缩进、空格等进行格式化处理。

+ export type Demo = {
+     id: number
+ }

保存文件时删除不需要的导入语句。

为了预防通过删除逻辑等操作而导致不再需要的导入,尝试添加一行代码。

import { Component, inject } from '@angular/core'
import { DemoRepository } from 'src/app/repositories/demo.repository'
import { Demo } from 'src/app/models/demo.model' // 適当にインポートを追加

在保存文件时,相关的导入将被删除。

import { Component, inject } from '@angular/core'
import { DemoRepository } from 'src/app/repositories/demo.repository'
+ // 削除

禁止在目录单位上的依赖

试试在存储库中添加对组件的依赖。这违反了我们设定的依赖方向规则,“依赖方向应为组件 > 存储库 > 模型”的一方向。

import { Injectable } from '@angular/core'
import { Demo } from 'src/app/models/demo.model'
import { demoComponent } from 'src/app/components/demo.component' // 依存方向に違反したもの

@Injectable({
    providedIn: 'root',
})
export class DemoRepository {
    private readonly component = new demoComponent()
    ...

通过.eslintrc.js中的import/no-restricted-paths规则,可以在按照文件夹单位检测到从一个特定文件夹到另一个文件夹的依赖关系违反情况。一旦发现违反情况,会立即出现红色的虚线,从而使得违反更加显眼,而且还可以自定义错误信息,使得内容更加清晰易懂。

import { Injectable } from '@angular/core'
import { Demo } from 'src/app/models/demo.model'
import { demoComponent } from 'src/app/components/demo.component'
                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// リポジトリはコンポーネントをインポートできません eslint(import/no-restricted-paths)

@Injectable({
    providedIn: 'root',
})
export class DemoRepository {
    private readonly component = new demoComponent()
    ...

想法

Lint已经不再是简单的语法检查器,而是为了改进开发体验而不断进化。

为了体验本次设置内容的便利性,必须使用编辑器的扩展插件,因此希望能够使用.extensions.json等方式在团队中共享。

{
  "recommendations": [
    "dbaeumer.vscode-eslint"
  ]
}
广告
将在 10 秒后关闭
bannerAds