防止不适当的导入并在 Typescript 中强制执行项目层次结构

Ben*_*arp 14 dependency-management typescript typescript3.0

在 TS 项目中,我希望阻止以下内容:

  • 来自文件夹的文件 common 从文件夹导入 projectA
  • 来自文件夹 projectB 的文件从文件夹导入 projectA

我希望允许以下内容:

  • 从文件夹 projectA 导入的文件common

我知道参考文献。但是,据我了解,它们需要构建用于类型检查(如果进行了这种分离,则必须先构建以创建 d.ts 文件),我宁愿避免这种情况。

我有哪些选择?是否可以简单地通过每个项目/文件夹的单独 tsconfig 文件来实现?

for*_*d04 11

我建议为该工作使用 linter,无需调整构建步骤或使用项目引用。

eslint-plugin-import是一款非常流行的 ESLint 插件,兼容 TS,可以为所欲为。在配置了typescript-eslint(如果还没有完成)之后,你可以使用这些规则:

让我们尝试使用以下项目结构:

|   .eslintrc.js
|   package.json
|   tsconfig.json
\---src
    +---common
    |       common.ts
    |       
    +---projectA
    |       a.ts
    |       
    \---projectB
            b.ts
Run Code Online (Sandbox Code Playgroud)

.eslintrc.js:

module.exports = {
  extends: ["plugin:import/typescript"],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    sourceType: "module",
    project: "./tsconfig.json",
  },
  plugins: ["@typescript-eslint", "import"],
  rules: {
    "import/no-restricted-paths": [
      "error",
      {
        basePath: "./src",
        zones: [
          // disallow import from projectB in common
          { target: "./common", from: "./projectB" }, 
          // disallow import from projectB in projectA
          { target: "./projectA", from: "./projectB" },     
        ],
      },
    ],
    "import/no-relative-parent-imports": "error",
  },
};
Run Code Online (Sandbox Code Playgroud)

每个区域目标路径和来自路径组成。目标是应应用受限导入的路径。from 路径定义了不允许在导入中使用的文件夹。

查看文件./src/common/common.ts

import { a } from "../projectA/a"; // works 
// Error: Unexpected path "../projectB/b" imported in restricted zone.
import { b } from "../projectB/b";
Run Code Online (Sandbox Code Playgroud)

import/no-relative-parent-imports规则还对这两种进口抱怨,例如a.ts

不允许从父目录进行相对导入。请在运行时传递您正在导入的内容(依赖注入),移动common.ts到与此相同的目录../projectA/a或考虑制作../projectA/a一个包。

import/no-internal-modules没有使用第三条规则,但我也在此处列出它,因为它可以非常有用地限制对子文件夹/模块的访问并在 TS 中模拟(至少)某种包内部修饰符


vas*_*vas 11

TLDR;你真的应该只使用引用。这正是他们的目的。

但让我们先谈谈您的一些具体想法:

  1. 是否可以简单地通过每个项目/文件夹的单独 tsconfig 文件来实现?

    是的,但它不是很灵活。您可以通过将其设置为来隔离common。如果您尝试将projectA导入common ,则会出现错误。但是为了能够将common导入projectA,它必须更加全局化,但这将允许您导入projectBrootDir.'/path/to/projectA' is not under 'rootDir'rootDir

    不仅如此,根据项目参考文档:

    以前,如果您使用单个 tsconfig 文件,则这种结构很难使用:

    • 实现文件可以导入测试文件
    • 这是不可能的构建testsrc在同一时间,而不src出现在输出文件夹的名字,你可能不希望
    • 改变只是内部在执行文件所需类型检查再次测试,尽管这不会造成过新的错误
    • 仅更改测试需要再次对实现进行类型检查,即使没有任何更改

    您可以使用多个 tsconfig 文件来解决其中一些问题,会出现新的问题:

    • 没有内置的最新检查,所以你最终总是运行tsc两次
    • 调用tsc两次会导致更多的启动时间开销
    • tsc -w 不能一次在多个配置文件上运行
  2. 我知道参考文献。但是,据我了解,它们需要构建用于类型检查(如果进行了这种分离,则必须先构建以创建 d.ts 文件),我宁愿避免这种情况。

    这种厌恶的原因是什么?

    • 如果这是构建新项目克隆的前期成本,那么改进的构建时间将弥补这一点(请参阅下面的参数)。后者对开发人员生产力的好处将远远超过前者的成本。

      具有讽刺意味的是,您对前期成本的关注越大,缩短构建时间带来的收益就越大!

    • 如果您希望能够在 VS Code 或 WebStorm 等类型和链接感知编辑器中导航新克隆而无需构建,您可以通过将.d.ts文件签入源代码控制来实现这一点。


    以下是文档的具体说明:

    由于依赖项目使用.d.ts从其依赖项构建的文件,因此您必须检查某些构建输出在克隆项目后构建项目,然后才能在编辑器中导航项目而不会看到虚假错误。我们正在开发一个应该能够缓解这种情况的幕后 .d.ts 生成过程,但现在我们建议通知开发人员他们应该在克隆后进行构建。

项目引用的论据

从文档:

  • 您可以大大缩短构建时间

    期待已久的功能是针对 TypeScript 项目的智能增量构建。在 3.0 中,您可以将--build标志与tsc. 这实际上是一个新的入口点,tsc它的行为更像是一个构建协调器,而不是一个简单的编译器。

    运行tsc --buildtsc -b简称)将执行以下操作:

    • 查找所有引用的项目
    • 检测它们是否是最新的
    • 以正确的顺序构建过时的项目

    不要担心对您在命令行上传递的文件进行排序 -tsc如果需要,将重新排序它们,以便始终首先构建依赖项。

  • 强制组件之间的逻辑分离

  • 以新的更好的方式组织您的代码。

项目参考文档中有一些更有用的好处/功能。

示例设置

  • src/tsconfig.json

    即使您在根目录中没有代码,此 tsconfig 也可以是所有通用设置的所在(其他设置将从它继承),并且它可以轻松tsc --build src构建整个项目(并--force从头开始构建它)。

    {
      "compilerOptions": {
        "rootDir": ".",
        "outDir": "../build",
        "composite": true
      },
      // this root project has no source of its own
      "files": [],
      // but building this project will build all of the following:
      "references": [  
        { "path": "./common" }
        { "path": "./projectA" }
        { "path": "./projectB" }
      ]
    }
    
    Run Code Online (Sandbox Code Playgroud)
    • src/common/tsconfig.json

      因为common没有引用,所以导入仅限于其目录和npm_modules. 我相信,您甚至可以通过给它自己的package.json.

          {
           "compilerOptions": {
              "rootDir": ".",
              "outDir": "../../build/common",
              "composite": true
            }
          }
      
      Run Code Online (Sandbox Code Playgroud)
    • src/projectA/tsconfig.json

      由于声明的引用,projectA可以导入common

          {
            "compilerOptions": {
              "rootDir": ".",
              "outDir": "../../build/projectA",
              "composite": true
            },
            "references": [
              { "path": "../common" }
            ]
          }
      
      Run Code Online (Sandbox Code Playgroud)
    • src/projectB/tsconfig.json

      由于声明的引用,projectB可以导入common AND projectA

          {
            "compilerOptions": {
              "rootDir": ".",
              "outDir": "../../build/projectB",
              "composite": true
            },
            "references": [
              { "path": "../common" }
              { "path": "../projectA" }
            ]
          }
      
      Run Code Online (Sandbox Code Playgroud)

构建

这些只是一些例子。我使用下面的缩写形式的tsc开关,例如-b 代替--build. 从 repo 根执行的所有命令。

tsc -b src - 构建整个树。

tsc -p src/projectA/ 只编译 projectA。

tsc -b src/projectA/ 构建 projectA 和任何过时的依赖项。

tsc -b -w src - 构建并观察整棵树。

tsc -b --clean src - 删除整个树的输出。

tsc -b -f src- 强制重建整个树。

使用 -d-dry 开关来预览tsc -b 将要执行的操作。