Skip to content

Instantly share code, notes, and snippets.

@zerkalica
Last active June 16, 2024 12:26
Show Gist options
  • Save zerkalica/d69c267d4bbac9cd49047e22336d5110 to your computer and use it in GitHub Desktop.
Save zerkalica/d69c267d4bbac9cd49047e22336d5110 to your computer and use it in GitHub Desktop.
Заметки о tsconfig

Признаки хорошо настроенного tsconfig

Все нижеперечисленное справедливо в основном для монорепы. В проектах, где используются алиасы в импортах между либами и все зависимости в инклудах, алгоритмы упрощаются и кол-во проблем должно быть меньше.

Надо проверять работу и в vscode и в webstorm, т.к. у последнего, поверх алгоритма tsserver, накладываются свои алгоритмы автоимпортов модулей. То, что работает в webstorm, может не работать в других ide, которые полагаются только на tsserver.

tsconfig считается настроенным, если:

  1. Проходит сборка через tsc --build для либ и webpack для приложух
  2. Чекаются типы в ide и tsc --noEmit, в том числе, в файлах, которые не попадают в сборку: тесты, storybook-файлы
  3. Ctrl-click на интерфейсе/классе ведет на исходный код, а не сбилженный d.ts
  4. Автоимпорт показывает только то, что в ближайшем package.json или в include (в ближайшем tsconfig)
  5. Автоимпорт одинаково работает как с нуля, так и в случае, если уже что-то рядом заимпорчено из либы, зависимость которой вы собираетесь автоимпортировать
  6. В автоимпорте нет дублей, когда одна и таже зависимость присутствует в автокомплите с разными путями.
  7. Автоимпорт одинаково работает для чистой монорепы и полностью пребилженной
  8. На скорость ts чекера не должен влиять размер монорепы, а только зависимости редактируемого проекта, т.е. в корневом tsconfig.json нельзя инклудить всю монорепу
  9. Опционально, если в проекте не используются реекспорты, т.е. нет index.ts в корне пакета, автоимпорт должен подсказывать полный импорт к файлу с этой зависимостью

Как работает автоимпорт

Вызов getImportCompletionAction приводит к сбору экспортов из модулей через getAllReExportingModules и forEachExternalModuleToImportFrom.

Последний, через forEachExternalModuleToImportFromInProgram вытаскивает импорты из program, т.е. из includes и пр.

А также, вытаскивает импорты через autoImportProvider из package.json (isFromPackageJson = true), если useAutoImportProvider=true, т.е. настройка tsserver includePackageJsonAutoImports on или auto.

AutoImportProvider вытаскивает зависимости из ближайшего package.json и через primaryLookup ищет d.ts в typeRoots, через secondaryLookup ищет d.ts через package.json / types.

secondaryLookup вызывает loadModuleFromImmediateNodeModulesDirectory, который в случае успешного ресолва отдает {path и ext} файла с экспортами.

Path и ext loadModuleFromImmediateNodeModulesDirectory пытается вытащить сперва из ключей в package.json: typings или types, candidate при этом имеет вид node_modules/@psy/core. Если types в package.json нет, алгоритм пытается ресолвить через @types, candidate имеет в этом случае вид node_modules/@types/psy__core.

Оба пути приводят к вызову сперва loadModuleFromFile, который добавляет d.ts к кандидату, например node_modules/@demo/lib-browser.d.ts или node_modules/@types/@demo/lib-browser/index.d.ts и если файл существует, возвращает path и ext.

Если loadModuleFromFile не вернул path, ext, вызывается loadNodeModuleFromDirectoryWorker, который через ключ types в package.json, возвращает что-то вроде node_modules/@psy/mobx-ssr/dist/index.d.ts.

Если package.json нету, алгоритм пытается искать package.json в неймспейсе

В результате собирается rootNames с путями к файлам, из которых tsserver далее вытащит экспорты.

Ограничение этого алгоритма: один пакет - один экспорт в виде index.ts или dist/index.d.ts или @types/pkg_name.d.ts.

В монорепе один и тот же пакет может быть доступен через node_modules и через includes, если из node_modules пришел файл, который уже есть в includes, то он не будет включен в rootNames

Если какая-либо зависимость из библиотеки уже была заимпорчена в проекте, где происходит автокомплит, то все остальные экспорты из файла с зависимостью также попадут в автокомплит. Например, последние две зависимости заимпорчены в проекте, а зависимости выше пришли через node_modules

mobx-psy/psy/boot/-/info/pkg.d.ts
mobx-psy/psy/boot/-/info/info.d.ts
mobx-psy/psy/boot/-/info/tsConfig.d.ts
mobx-psy/psy/boot/-/info/workspaces.d.ts
mobx-psy/psy/boot/-/info/index.d.ts
mobx-psy/psy/boot/-/jestConfig.d.ts
mobx-psy/psy/boot/-/prettierConfig.d.ts
mobx-psy/psy/boot/-/patchModules.d.ts
mobx-psy/psy/boot/-/index.d.ts

mobx-psy/psy/boot/info/pkg.ts
mobx-psy/psy/boot/info/workspaces.ts

Как работает goto reference

При открытии первого ts-файла в проекте, посылается команда UpdateOpen, что приводит к созданию program, в параметрах, при создании, будет массив открытых в ide файлов (rootNames).

Каждый импорт ресолвится в путь, перебором пути содержащего импорт файла в loadModuleFromNearestNodeModulesDirectoryWorker.

К этому пути подставляется node_modules и когда эта директория найдена, то запускается поиск index-файла в loadModuleFromSpecificNodeModulesDirectory.

Алгоритм в loadNodeModuleFromDirectoryWorker пытается вычислить путь к пакету при помощи typings из package.json, если этот файл не найден, алгоритм посчитает, что в typings путь без расширения и начнет в tryAddingExtensions подставлять ts, tsx, d.ts и проверять наличие файла. Если файл не находится, то алгоритм вырезает d.ts, ts, js, tsx, jsx, json в (removeFileExtension) и снова подставляет ts, tsx, d.ts.

Если index-файл пакета все еще не найден, то в конец директории к пакету подставляется index или tsconfig и прокручивается вышеописанный алгоритм с расширениями.

Если импорт содержит путь к файлу (rest !== '', например, @demo/core/some или core/some, т.е. часть после неймспейса не считается путем, в обоих примерах путь /some), запускается вычисление пути через typesVersions. К полученному versionPaths, вместо звездочки будет подставлятся путь, который содержит импорт и снова искаться файл

Поэтому, если в монорепе правильно настроен package.json и либа сбилжена, то ts отрезолвит путь в dist, а не в исходный код. Если же сломать typings в package.json для проектов из монорепы или включить их в include (правда это приведет к ошибкам, если задан rootDir, т.к. нельзя инклудить за его пределами), то ресолв будет на index.ts каждого проекта.

Если полученный путь - симлинка, то она развернется в реальный путь, поэтому ссылки на пакеты монорепы ведут в сорцы, а не в node_modules.

Алгоритм, который пытается вычислить путь к index.ts, если нет файла в typings в package.json, не работает для автоимпорта, в нем строго берутся импорты только из файла в typings. Из-за того, что при резолве передается флаг d.ts only и алгоритм подставляет только d.ts расширение.

реально ts делает резолв package.json и @types:

/home/user/projects/mobx-psy/node_modules/@types/@demo/lib-browser/package.json
/home/user/projects/mobx-psy/node_modules/@types/@demo/lib-browser/index.d.ts
/home/user/projects/mobx-psy/demo/lib/ttt/node_modules/@demo/lib-browser/package.json
/home/user/projects/mobx-psy/demo/lib/ttt/node_modules/@demo/lib-browser.d.ts
/home/user/projects/mobx-psy/demo/lib/ttt/node_modules/@demo/lib-browser/index.d.ts
/home/user/projects/mobx-psy/demo/lib/ttt/node_modules/@types/demo__lib-browser/package.json
/home/user/projects/mobx-psy/demo/lib/ttt/node_modules/@types/demo__lib-browser.d.ts
/home/user/projects/mobx-psy/demo/lib/ttt/node_modules/@types/demo__lib-browser/index.d.ts
/home/user/projects/mobx-psy/demo/lib/node_modules/@demo/lib-browser/package.json
/home/user/projects/mobx-psy/demo/lib/node_modules/@demo/lib-browser.d.ts
/home/user/projects/mobx-psy/demo/lib/node_modules/@demo/lib-browser/index.d.ts
/home/user/projects/mobx-psy/demo/lib/node_modules/@types/demo__lib-browser/package.json
/home/user/projects/mobx-psy/demo/lib/node_modules/@types/demo__lib-browser.d.ts
/home/user/projects/mobx-psy/demo/lib/node_modules/@types/demo__lib-browser/index.d.ts
/home/user/projects/mobx-psy/demo/node_modules/@demo/lib-browser/package.json
/home/user/projects/mobx-psy/demo/node_modules/@demo/lib-browser.d.ts
/home/user/projects/mobx-psy/demo/node_modules/@demo/lib-browser/index.d.ts
/home/user/projects/mobx-psy/demo/node_modules/@types/demo__lib-browser/package.json
/home/user/projects/mobx-psy/demo/node_modules/@types/demo__lib-browser.d.ts
/home/user/projects/mobx-psy/demo/node_modules/@types/demo__lib-browser/index.d.ts
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser.d.ts
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser/-/index.d2.ts
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser/-/index.d2.ts.ts
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser/-/index.d2.ts.tsx
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser/-/index.d2.ts.d.ts
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser/-/index.d2.ts/index.ts
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser/-/index.d2.ts/index.tsx
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser/-/index.d2.ts/index.d.ts
/home/user/projects/mobx-psy/node_modules/@demo/lib-browser/index.d.ts
/home/user/projects/mobx-psy/node_modules/@types/demo__lib-browser/package.json
/home/user/projects/mobx-psy/node_modules/@types/demo__lib-browser.d.ts
/home/user/projects/mobx-psy/node_modules/@types/demo__lib-browser/index.d.ts
/home/user/projects/node_modules/@demo/lib-browser/package.json
/home/user/projects/node_modules/@demo/lib-browser.d.ts
/home/user/projects/node_modules/@demo/lib-browser/index.d.ts
/home/user/projects/node_modules/@types/demo__lib-browser/package.json
/home/user/projects/node_modules/@types/demo__lib-browser.d.ts
/home/user/projects/node_modules/@types/demo__lib-browser/index.d.ts
/home/user/node_modules/@demo/lib-browser/package.json
/home/user/node_modules/@demo/lib-browser.d.ts
/home/user/node_modules/@demo/lib-browser/index.d.ts
/home/user/node_modules/@types/demo__lib-browser/package.json
/home/user/node_modules/@types/demo__lib-browser.d.ts
/home/user/node_modules/@types/demo__lib-browser/index.d.ts
/home/node_modules/@demo/lib-browser/package.json
/home/node_modules/@demo/lib-browser.d.ts
/home/node_modules/@demo/lib-browser/index.d.ts
/home/node_modules/@types/demo__lib-browser/package.json
/home/node_modules/@types/demo__lib-browser.d.ts
/home/node_modules/@types/demo__lib-browser/index.d.ts
/node_modules/@demo/lib-browser/package.json
/node_modules/@demo/lib-browser.d.ts
/node_modules/@demo/lib-browser/index.d.ts
/node_modules/@types/demo__lib-browser/package.json
/node_modules/@types/demo__lib-browser.d.ts
/node_modules/@types/demo__lib-browser/index.d.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment