Предотвращение несоответствующего импорта и обеспечение иерархии проекта в Typescript

В проекте TS я бы хотел, чтобы было заблокировано следующее:

  • Файл из папки общий импорт из папки projectA
  • Файл из папки projectB импортируется из папки projectA

Я бы хотел, чтобы было разрешено следующее:

  • Файл из папки projectA импортируется из папки common.

Я знаю ссылки. Однако, насколько я понимаю, им требуется сборка для проверки типов (если такое разделение сделано, нужно сначала выполнить сборку для создания файлов d.ts), чего я бы предпочел избежать.

Какие у меня есть варианты? Можно ли просто добиться этого с помощью отдельных файлов tsconfig для каждого из этих проектов / папок?


person Ben Carp    schedule 16.04.2020    source источник
comment
Насколько сильна ваша причина избегать использования ссылок на проекты из-за зависимости сборки? См. Контрапункты в моем ответе   -  person Inigo    schedule 28.04.2020


Ответы (2)


Я предлагаю использовать линтер для этой работы, нет необходимости настраивать этап сборки или использовать ссылки на проекты.

eslint-plugin-import - довольно популярный плагин ESLint, совместимый с TS и может делать то, что вы хотите . После настройки typescript-eslint (если это еще не сделано), вы можете поиграть со следующими правилами:

Давайте попробуем со следующей структурой проекта:

|   .eslintrc.js
|   package.json
|   tsconfig.json
\---src
    +---common
    |       common.ts
    |       
    +---projectA
    |       a.ts
    |       
    \---projectB
            b.ts

.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",
  },
};

Каждая зона состоит из целевого пути и от пути. Цель - это путь, по которому должен применяться ограниченный импорт. Путь 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";

Правило import/no-relative-parent-imports также нарушает оба импорта, например a.ts:

Относительный импорт из родительских каталогов не допускается. Пожалуйста, либо передайте то, что вы импортируете, во время выполнения (внедрение зависимостей), переместите common.ts в тот же каталог, что и ../projectA/a, или подумайте о создании ../projectA/a пакета.

Третье правило import/no-internal-modules не использовалось, но я также перечисляю его здесь, поскольку может быть очень полезно ограничить доступ к дочерним папкам / модулям и имитировать (по крайней мере) какой-то внутренний модификатор пакета в TS.

person ford04    schedule 27.04.2020
comment
Это очень интересно, завтра попробую. Однако решение Typecript (если есть такое решение без дополнительных сборок) должно быть лучше, поскольку оно будет показывать только доступные / разрешенные импортные операции для автозаполнения. - person Ben Carp; 27.04.2020
comment
›Относительный импорт из родительских каталогов не разрешен. Если у меня есть файл ProjectA/innerFolder/innerFolder/file.ts, который пытается импортировать из projectA/consts.ts, будет ли он заблокирован? - person Ben Carp; 27.04.2020
comment
Хм, я сомневаюсь, что есть прямая функция TS, которая запрещает определенные шаблоны импорта модулей (или, по крайней мере, я этого не знаю). Если вам нужна более сильная инкапсуляция, вы обычно создаете другой пакет npm. Для автозаполнения: возможно несколько конфигураций, которые ограничивают ввод, но это будет иметь свои собственные организационные затраты и не помешает разрешению / импорту модуля. - person ford04; 27.04.2020
comment
да, eslint выдаст ошибку для всего, что имеет ../ в пути импорта - person ford04; 27.04.2020
comment
Чтобы было ясно, предотвращать это означает, что запуск линтера приведет к ошибкам. Но на самом деле это не помешает tsc компилировать нежелательные зависимости и помещать указанный код в outDir, верно? - person Inigo; 28.04.2020
comment
@ Иниго, я считаю, что ты написал правильно. Однако, если вы считаете линтер важной частью своей цепочки инструментов, то 1) Все ошибки отображаются в редакторе (в зависимости от плагина) 2) Линтер запускается перед каждой сборкой (в зависимости от скрипта) - person Ben Carp; 28.04.2020
comment
Я реализовал это. Он блокирует относительный импорт, но не блокирует абсолютный импорт. В настоящее время не решает нашу проблему. Спасибо. - person Ben Carp; 02.05.2020
comment
Подскажите, пожалуйста, как ограничить импорт по абсолютному пути для конкретной зоны с помощью eslint-plugin-import / no -hibited-paths? - person jocoders; 24.10.2020

TL; DR; Вам действительно стоит просто использовать ссылки. Это именно то, для чего они нужны.

Но сначала давайте обратимся к некоторым из ваших конкретных мыслей:

  1. Можно ли просто использовать отдельные файлы tsconfig для каждого из этих проектов / папок?

    Да, но это не очень гибко. Вы можете изолировать обычные, установив для rootDir значение .. Тогда вы получите '/path/to/projectA' is not under 'rootDir' ошибку, если попытаетесь импортировать projectA в common. Но чтобы иметь возможность импортировать common в projectA, его rootDir должен быть более глобальным, но тогда это позволит вам импортировать projectB.

    Более того, согласно документации Project References:

    Раньше с этой структурой было довольно неудобно работать, если вы использовали один файл tsconfig:

    • It was possible for the implementation files to import the test files
    • Невозможно было собрать test и src одновременно без того, чтобы src не отображался в имени выходной папки, что вам, вероятно, не нужно.
    • Изменение только внутренних компонентов в файлах реализации потребовало повторной проверки типов тестов, даже если это никогда не вызовет новых ошибок.
    • Изменение только тестов потребовало повторной проверки типов реализации, даже если ничего не изменилось

    Вы можете использовать несколько файлов tsconfig для решения некоторых из этих проблем, но появятся новые:

    • Встроенной проверки обновлений нет, поэтому вы всегда будете запускать tsc дважды
    • Двойной вызов tsc увеличивает накладные расходы времени на запуск
    • tsc -w нельзя запускать сразу несколько файлов конфигурации
  2. Мне известны ссылки. Однако, насколько я понимаю, им требуется сборка для проверки типов (если такое разделение сделано, нужно сначала выполнить сборку для создания файлов d.ts), чего я бы предпочел избежать.

    В чем причина этого отвращения?

    • Если это предварительная стоимость создания нового клона проекта, она будет более чем компенсирована улучшенным временем сборки (см. аргументы в пользу ниже). Выгоды последнего для продуктивности разработчиков намного перевешивают издержки первого.

      По иронии судьбы, чем больше вы беспокоитесь о первоначальных затратах, тем больше выгода от улучшенного времени сборки!

    • Если вы хотите иметь возможность перемещаться по новому клону в редакторе с поддержкой типов и связей, таком как VS Code или WebStorm, без необходимости сборки, вы можете добиться этого, проверив файлы .d.ts в системе управления версиями.


    Вот что конкретно говорится в документации:

    Поскольку зависимые проекты используют .d.ts файлы, созданные на основе их зависимостей, вам придется либо проверить определенные выходные данные сборки , либо построить проект после его клонирования, прежде чем вы сможете перемещаться по проекту в редакторе без видя ложные ошибки. Мы работаем над скрытым процессом создания .d.ts, который должен помочь смягчить это, но пока мы рекомендуем сообщить разработчикам, что они должны создавать после клонирования.

Аргумент в пользу ссылок на проекты

Из документов:

  • вы можете значительно сократить время сборки

    Долгожданная функция - интеллектуальные инкрементальные сборки для проектов TypeScript. В версии 3.0 вы можете использовать --buildflag с tsc. По сути, это новая точка входа для tsc, которая больше похожа на оркестратор сборки, чем на простой компилятор.

    Запуск tsc --build (для краткости tsc -b) будет делать следующее:

    • Find all referenced projects
    • Определите, актуальны ли они
    • Собирайте устаревшие проекты в правильном порядке

    Не беспокойтесь об упорядочивании файлов, которые вы передаете в командной строке - 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" }
      ]
    }
    
    • src/common/tsconfig.json

      Поскольку common не имеет ссылок, импорт ограничен целевыми объектами в его каталоге и npm_modules. Я считаю, что можно даже ограничить последнее, предоставив ему собственное package.json.

          {
           "compilerOptions": {
              "rootDir": ".",
              "outDir": "../../build/common",
              "composite": true
            }
          }
      
    • src/projectA/tsconfig.json

      projectA может импортировать common из-за объявленной ссылки.

          {
            "compilerOptions": {
              "rootDir": ".",
              "outDir": "../../build/projectA",
              "composite": true
            },
            "references": [
              { "path": "../common" }
            ]
          }
      
    • src/projectB/tsconfig.json

      projectB может импортировать common И projectA из-за объявленных ссылок.

          {
            "compilerOptions": {
              "rootDir": ".",
              "outDir": "../../build/projectB",
              "composite": true
            },
            "references": [
              { "path": "../common" }
              { "path": "../projectA" }
            ]
          }
      

Строит

Это всего лишь несколько примеров. Я использую сокращенные формы tsc переключателей ниже, например -b вместо --build. Все команды выполняются из корня репо.

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.

person Inigo    schedule 28.04.2020
comment
Спасибо! Если я изменю тип экспортируемого параметра, мне нужно запустить tsc -p src / common для создания файлов d.ts, и правильность ввода импорта из общих, верно? - person Ben Carp; 29.04.2020
comment
@BenCarp Да, именно так. Или, если вы измените общие элементы во время работы над projectA, tsc -b src/projectA/ перекомпилирует измененные общие элементы и элементы в projectA, которые затронуты. И перед фиксацией ваших изменений вы можете tsc -b src убедиться, что все построено, или оставить это вашей системе непрерывной интеграции. Я добавил к своему ответу образцы команд сборки и исправил образцы tsconfigs. - person Inigo; 30.04.2020
comment
Иниго, этот конкретный проект фактически построен Webpack и скомпилирован babel. Я думаю, это делает команды сборки неуместными для этого проекта, я вижу, что есть команда watch. Позволит ли он автоматически скомпилировать изменения типа к общим и повлиять на проекты? - person Ben Carp; 02.05.2020
comment
Команды tsc --build не неактуальны именно по той причине, которую вы указали, и, как я уже объяснил выше, любые зависимости, которые устарели. Достаточно легко настроить простой проект, чтобы поиграть и ответить на все ваши вопросы. Я не знаю, как это работает с Webpack и Babel. - person Inigo; 02.05.2020