Внешние модули¶
Внешний модуль TypeScript предоставляет много возможностей и удобства. Здесь мы обсудим его возможности и некоторые паттерны, используемые на практике.
Прояснение: модули commonjs, amd, es modules, и другие¶
Прежде всего нам нужно прояснить (ужасную) несогласованность систем модулей. Я просто дам вам свою текущую рекомендацию и уберу шум, т.е. скрою некоторые возможные варианты работы.
Из одного и того же TypeScript вы можете генерировать разный код JavaScript в зависимости от опции module
. Вот вещи, которые вы можете игнорировать (я не заинтересован в объяснении устаревших технологий):
- AMD: Не используйте. Использовались только в браузерах.
- SystemJS: Был неплохим вариантом. Заменен ES модулями.
- ES Модули: Еще не готовы.
Теперь это всего лишь варианты генерации JavaScript. Вместо этих опций используйте module:commonjs
То, как вы пишете модули TypeScript, также немного запутанно. Опять же, вот как лучше не делать сегодня:
import foo = require('foo')
. т.е.import/require
. Вместо этого используйте синтаксис модуля ES.
Окей, вместе с тем давайте посмотрим на синтаксис модуля ES.
Резюме: используйте
module:commonjs
и используйте синтаксис модуля ES для импорта / экспорта / создания модулей.
Синтаксис ES модулей¶
- Экспортировать переменную (или тип) можно просто добавив ключевое слово
export
, например
1 2 3 4 5 |
|
- Экспорт переменной или типа в отдельной
export
инструкции, например
1 2 3 4 5 6 |
|
- Экспорт переменной или типа в отдельной
export
инструкции с переименованием, например
1 2 3 |
|
- Импортируйте переменную или тип, используя
import
, например
1 2 |
|
- Импортировать переменную или тип, используя
import
с переименованием, например
1 2 |
|
- Импортируйте все из модуля под новым именем с помощью
import * as
, например
1 2 3 4 |
|
- Импорт файла для общего доступа с помощью одной только инструкции импорта:
1 |
|
- Реэкспорт всех переменных из другого модуля
1 |
|
- Реэкспорт только некоторых переменных из другого модуля
1 |
|
- Реэкспорт только некоторых переменных из другого модуля с переименованием
1 |
|
exports/imports по умолчанию¶
Как вы узнаете позже, я не фанат экспорта по умолчанию. Тем не менее есть синтаксис для экспорта и использования экспорта по умолчанию
- Экспорт с использованием
export по умолчанию
- перед переменной (не требуется
let / const / var
) - перед функцией
- перед классом
- перед переменной (не требуется
1 2 3 4 5 6 |
|
- Импорт с использованием
import someName from "someModule"
(вы можете назвать импорт как угодно), например
1 |
|
Пути к модулям¶
Я собираюсь предложить использовать
moduleResolution: commonjs
. Эта опция должна быть в вашей конфигурации TypeScript. А эта настройкаmodule:commonjs
подразумевается автоматически.
Есть два разных вида модулей. Различие обусловлено секцией пути в операторе импорта (например, import foo from 'ЭТО_СЕКЦИЯ_ПУТИ'
).
- Модули с относительным путем (где путь начинается с
.
, например,./someFile
или../../someFolder/someFile
и т.д.) - Другие модули с динамической подстановкой (например,
'core-js'
или'typestyle'
или'react'
или даже'react/core'
и т.д.)
Основное отличие состоит в том, как модуль находится в файловой системе.
Я буду использовать концептуальный термин место, который я объясню после упоминания шаблона поиска.
Относительные пути к модулям¶
Это легко, просто следуй по пути :), например
- если файл
bar.ts
выполняетimport * as foo from './foo';
, то файлfoo
должен существовать в той же папке. - если файл
bar.ts
выполняетimport * as foo from '../foo';
, то файлfoo
должен существовать в папке выше. - если файл
bar.ts
выполняетimport * as foo from '../someFolder/foo';
, то в папке выше должна быть папкаsomeFolder
с местомfoo
Или любой другой относительный путь, который вы можете придумать :)
Динамическая подстановка¶
Когда путь импорта не относительный, поиск определяется node style resolution. Здесь я приведу только простой пример:
-
У вас есть
import * as foo from 'foo'
, ниже перечислены места, которые проверяются по порядку./node_modules/foo
../node_modules/foo
../../node_modules/foo
- И далее до корня файловой системы
-
У вас есть
import * as foo from 'something/foo'
, ниже перечислены места, которые проверяются по порядку./node_modules/something/foo
../node_modules/something/foo
../../node_modules/something/foo
- И далее до корня файловой системы
Что такое место¶
Когда я говорю проверенные места, я имею в виду, что в этом месте проверяются следующие вещи. Например, для места foo
:
- Если место представляет собой файл, например,
foo.ts
, завершено! - иначе, если место - это папка, а есть файл
foo/index.ts
, завершено! - в противном случае, если местом является папка и существует
foo/package.json
с указанным и существующим в ключеtypes
файлом, тогда завершено! - в противном случае, если местом является папка и существует package.json с указанным и существующим в ключе
main
файлом, то завершено!
Под файлом я имею в виду .ts
/ .d.ts
и .js
.
И это все. Теперь вы являетесь экспертами по поиску модулей (немаленький подвиг!).
Отмена динамического поиска только для типов¶
Вы можете объявить модуль глобально для вашего проекта, используя declare module 'somePath'
, и тогда импорт исполнит волшебным образом этот путь,
например,
1 2 3 4 5 |
|
и потом:
1 2 3 4 |
|
import/require
только для импорта типа¶
Следующая инструкция:
1 |
|
на самом деле делает две вещи:
- Импортирует информацию о типах модуля foo.
- Определяет необходимые зависимости в среде выполнения для модуля foo.
Вы можете выбрать, чтобы загружалась только информация о типе и избежать загрузки зависимостей. Прежде чем продолжить, вы можете вспомнить раздел области объявлений этой книги.
Если вы не используете импортированное имя в области объявления переменных, то импорт полностью удаляется из сгенерированного JavaScript. Это лучше всего объяснить на примерах. Как только вы поймете это, мы представим вам варианты использования.
Пример 1¶
1 |
|
сгенерирует следующий JavaScript:
1 |
|
Всё в порядке. Пустой файл, так как foo
не используется.
Пример 2¶
1 2 |
|
сгенерирует следующий JavaScript:
1 |
|
Это потому, что foo
(или любое из его свойств, например, foo.bas
) никогда не используется в качестве переменной.
Пример 3¶
1 2 |
|
сгенерирует следующий JavaScript (при условии использования commonjs
параметра конфига):
1 2 |
|
Это потому что foo
используется как переменная.
Вариант использования: Отложенная загрузка¶
Вывод типа должен быть сделан предварительно. Это означает, что если вы хотите использовать какой-либо тип из файла foo
в файле bar
, вам придется сделать следующее:
1 2 |
|
Однако вы можете захотеть загружать файл foo
только во время выполнения при определенных условиях. В таких случаях вы должны использовать импортированное
значение только в описаниях типа, но не в качестве переменной. Это удаляет любой предварительный код зависимостей в среде выполнения, внедренный TypeScript. Затем вручную импортируйте реальный модуль, используя код, необходимый для вашего загрузчика модулей.
В качестве примера рассмотрим следующий код, основанный на commonjs
, где мы загружаем модуль 'foo'
только при вызове определенной функции:
1 2 3 4 5 6 7 8 |
|
Подобный пример в amd
(используя requirejs) будет выглядеть так:
1 2 3 4 5 6 7 8 9 |
|
Этот шаблон обычно используется:
- в веб-приложениях, где вы загружаете определенный JavaScript на определенных роутах,
- в nodejs приложениях, где вы загружаете только определенные модули, если это необходимо для ускорения загрузки приложений.
Вариант использования: размыкание круговых зависимостей¶
Как и в случае с отложенной загрузкой, некоторые загрузчики модулей (commonjs/node и amd/requirejs) плохо работают с циклическими зависимостями. В таких случаях полезно иметь отложенную загрузку кода в одном направлении и загружать модули предварительно в противоположном направлении.
Вариант использования: безопасный импорт¶
Иногда вы хотите загрузить файл только для побочного эффекта (например, для регистрации в какой-то библиотеке CodeMirror addons и т. д.). Тем не менее, если вы просто выполните import/require
, транспиленный JavaScript не будет содержать зависимости от модуля, и ваш загрузчик модулей (например, webpack) может полностью игнорировать импорт. В таких случаях вы можете использовать переменную ensureImport
, чтобы гарантировать, что скомпилированный JavaScript принимает зависимость от модуля, например:
1 2 3 4 |
|