Ts src что это
Перейти к содержимому

Ts src что это

  • автор:

Заголовочные файлы

Для установки связи с внешними файлами скриптов javascript в TS служат декларативные или заголовочные файлы. Это файлы с расширением .d.ts , они описывают синтаксис и структуру функций и свойств, которые могут использоваться в программе, не предоставляя при этом конкретной реализации. Их действие во многом похоже на работу файлов с расширением .h в языках C/C++. Они выполняют своего рода роль оберток над библиотеками JavaScript.

Рассмотрим, как мы можем использовать заголовочные файлы. Иногда в программах на javascript используются глобальные переменные, которые должны быть видны для всех функций приложения. Например, пусть на веб-странице (или во внешнем подключаемом файле javascript) в коде js определена переменная:

    Metanit.com  

Приложение на TypeScript

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

И, допустим, мы хотим использовать эту переменную message в коде TypeScript в файле app.ts:

console.log(message);

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

declare let message: string;

С помощью ключевого слова declare в программу на TS подключается определение глобальной переменной.

То есть у нас получится следующая структура проекта:

  • app.ts
  • globals.d.ts
  • index.html

Компиляция

Если мы компилируем, передавая компилятору в консоли название файла:

tsc app.ts

То в этом случае компилятор не найдет автоматически файл globals.d.ts. В этом случае нам надо в файле app.ts явно указать расположение файла globals.d.ts с помощью директивы reference

/// console.log(message);

Заголовочные файлы в TypeScript

Если же мы полагаемся на файл конфигурации tsconfig.json, просто выполняя команду

Подобным образом мы можем подключать другие компоненты кода JavaScript — функции, объекты, классы. Рассмотрим их подключение.

Функции

Пусть на веб-странице в коде js объявлены две следующие функции:

let message = «Hello TypeScript!»; function hello() < console.log(message); >function sum(a, b)

Функция hello() выводит значение переменной message на консоль, а функция sum() возвращает сумму двух чисел.

И, допустим, в коде TS мы хотим вызывать эти функции:

hello(); let result = sum(2, 5); console.log(result);

В этом случае подключение в файле globals.d.ts выглядело бы так:

declare function hello(): void; declare function sum(a: number, b: number): number;

Подключение объектов

Пусть в коде JavaScript есть следующий объект:

const tom = < name: "Tom", age: 37, print()< console.log(`Name: $Age: $`); > >

Используем этот объект в коде typescript:

tom.print();

В этом случае определение объекта в файле globals.d.ts выглядело бы так:

declare const tom: void>;

Подключени сложных объектов

Однако может возникнуть сложность с подключением более сложных объектов. Например, пусть есть такой объект javascript:

var points = [< X: 10, Y: 34 >, < X: 24, Y: 65 >, < X: 89, Y: 12 >];

Для данного массива объектов в файле globals.d.ts мы можем определить соответствующий отдельному объекту интерфейс и подключить массив объектов некоторого интерфейса, который содержит два свойства X и Y:

interface IPoint < X: number; Y: number; >declare var points: IPoint[];

И в TS мы сможем использовать этот массив:

for (let point of points) < console.log(`Точка с координатами X = $Y = $`); >

Консольный вывод браузера:

Точка с координатами X = 10 Y = 34 Точка с координатами X = 24 Y = 65 Точка с координатами X = 89 Y = 12

Подключение классов

Рассмотрим последний пример — подключение в typescript классов, определенных в javascript. Пусть в коде JavaScript определен следующий класс Person:

class Person < constructor(name, age)< this.name = name; this.age = age; >display() < console.log(this.name, this.age); >>

Для этого класса в файле globals.d.ts определим следующее объявление класса:

declare class Person

Для класса прописываем все его поля и методы, при этом методы (в том числе конструктор) не имеют реализации, для них только определяются параметры и их типы и тип возвращаемого значения.

И в коде TypeScript используем этот класс:

let tom = new Person("Tom", 37); tom.display(); // Tom 37 console.log(tom.name); // Tom

Вопросы по TypeScript

Что такое TypeScript и зачем использовать его вместо JavaScript?​

  • это надмножество JS, которое добавляет в него возможность явного статического назначения типов. (этот язык проект Microsoft)
  • Одним из серьёзных преимуществ TS перед JS является возможность, которая позволяет, прямо в процессе ввода кода, выявлять ошибки

Что такое обобщения(Generic) и зачем они нужны?​

  • Это инструмент, который позволяет писать на TypeScript функции и классы, способные работать с различными типами данных, сохраняя работоспособность проверки типов

Поддерживает ли TypeScript все принципы ООП?​

  • Да, Есть 4 принципа: — Инкапсуляция — размещение в оболочке(изоляция), закрытие чего-либо инородного с целью исключения влияния на окружающее — Наследование — позволяет использовать повторно функциональность и свойства существующего объекта в новом — Абстракция — означает, что для каждого объекта мы задаём минимальное количество методов, полей и описаний, которые позволят нам решить задачу. — Полиморфизм — означает, что функция или метод может работать с разными типами

Что представляют собой .map-файлы в TypeScript?​

Это файлы карты, которые позволяют инструментам сопоставлять выпущенный код JavaScript и исходные файлы TypeScript, которые его создали.

Что такое геттеры и сеттеры в TypeScript?​

  • TypeScript поддерживает геттеры и сеттеры, которые позволяют управлять доступом к элементам класса.- Они дают разработчику средства контроля над чтением и записью свойств объектов. Для объектов можно использовать Object.defineProperty для определения геттеров и сеттеров https://www.dropbox.com/s/5lpp2l246j2ow98/ObjectSettersGetters.png?dl=0 и это уже не связанно с TypeScript Указание set и get в литералах(константах) — https://www.dropbox.com/s/c3m9c2e8zojxflj/ObjectSettersGettrersInConst.png?dl=0

Можно ли использовать TypeScript в серверной разработке, и если да — то как?​

  • Да, лучший способ сделать это — установить nodemon и ts-node в зависимости разработки и добавить 2 срипта: — start для развертывания на сервере «ts-node ./src/app.ts» — dev для локального запуска с авто-обновлением сервера при обновлении файлов: «nodemon ./src/app.ts»

Расскажите об основных компонентах TypeScript​

  • Язык. Синтаксис который позволяет писать на TypeScript
  • Компилятор. Он выполняет преобразование TypeScript-кода в JavaScript-код и сообщает об ошибках типов если они имеются. Еще он позволяет объединять несколько TypeScript-файлов в один выходной JS-файл и умеет создавать карты кода
  • Вспомогательные инструменты. Это среды разработки такие как VS Code, которые могут быстро запускать TS-код

Что такое перегрузка функции и поддерживает ли её TypeScript?​

Это объявление функции с тем же именем несколько раз. Чтобы компилятор мог отличать «перегруженные» функции между собой, у них должны быть разные параметры- Да, TS поддерживает перегрузку, но не совсем так как в других языках- Перегрузка может быть полезна для предопределения типа возвращаемого значения, в зависимости от типов передаваемых в функции аргументов- https://www.dropbox.com/s/5ysnsui9ixto663/functionOverloadTS.png?dl=0 пример перегрузки в TypeScript

Чем различаются ключевые слова interface и type в TypeScript?​

  • Интерфейсом можно описать только тип объекта, а типом можно описать примитивные значения
  • Но у интерфейса есть преимущества: Интерфейс может быть использован в выражении extends или implements Интерфейс может иметь несколько объединённых объявлений«слияние объявлений» — означает, что компилятор может объединять объявления, объявленные с одним и тем же именем, в одно определение

Расскажите о том, когда в TypeScript используют ключевое слово declare?​

  • Ключевое слово declare используется в TypeScript для объявления переменных, источником которых может служить некий файл, не являющийся TypeScript-файлом.

Что такое внешние объявления переменных в TypeScript и когда их нужно использовать?​

  • Это механизм, который позволяет сообщать компилятору TypeScript о том, что некий исходный код существует где-то за пределами текущего файла- Внешние объявления помогают интегрировать в TS-программы сторонние JavaScript-библиотеки.- Эти объявления делают в файле объявления типов с расширением .d.ts

Можно ли автоматически генерировать файлы объявлений TypeScript из JS-библиотек?​

  • Microsoft/dts-gen — официальное средство, используемое Microsoft как отправная точка при создании объявлений типов- Установить опцию компилятора на true в файле tsconfig.json»compilerOptions»:

Какая польза от файла tsconfig.json?​

Файл tsconfig.json определяет корневые файлы и параметры компилятора, необходимые для компиляции проекта.

Какое ключевое слово используется для наследования в TypeScript?​

  • extends — это ключевое слово, которое вам следует искать

Можно ли в TypeScript унаследовать от класса?​

  • Да, через ключевое слово extends

Что такое пространство имен в TypeScript?​

  • Это просто способ логически сгруппировать связанные классы или интерфейсы в оболочке

Что такое декораторы в TypeScript?​

  • Декораторы — это просто функции, которые изменяют класс, свойство, метод или параметр метода.

Как объявить декоратора?​

  • Синтаксис — это символ «@», за которым следует функция- @readonly class foo — На текущий момент декораторы являются экпериментальной функциональностью языка TypeScript, поэтому при компиляции следует указывать параметр experimentalDecorators

Для чего используется ключевое слово as в TypeScript?​

as — это дополнительный синтаксис для утверждения типа в TypeScript

Когда используется ключевое слово «unknown»?​

Когда вы не хотите использовать ключевое слово any и/или заранее не знаете точный тип, но хотите назначить его позже.

Для чего нужен тип «Omit»?​

  • Это новый тип, в котором можно указать свойства, которые будут исключены из исходного типа
type Person = type QuantumPerson = Omit // Аналогично следующей строке QuantumPerson = 

Что делает объект instanceof в TS?​

Оператор instanceof используется для проверки, является ли объект объектом указанного класса

Чем Отличаются Друг От Друга Типы Объединения(«|») И Пересечения («&»)?​

  • Объединение, позволяет создавать из множества существующих типов данных логическое условие, по которому данные могут принадлежать к одному из указанных типов
  • Пересечение, позволяет рассматривать множество типов данных, как один

Что делает тип Record?​

  • Создает тип объекта, ключами свойств которого являются Keys, а значениями свойств — Type. Эту утилиту можно использовать для отображения свойств одного типа на другой тип- https://www.dropbox.com/s/8dkakvsdkqvtv7n/RecordTypeScript.png?dl=0 пример

Как быстро сделать все свойства в интерфейсе опциональными?​

  • используйте утилитный тип «Partial». Он создает тип со всеми свойствами Type, установленными как необязательные- Partial

Как сделать интерфейс не ограниченным по свойствам​

Что такое перечесления(enums) в TypeScript?​

Перечисления — это функция, которая упрощает работу с именованными наборами констант. По умолчанию перечисление основано на числах, начиная с нуля, и каждому параметру присваивается приращение на единицу. Это полезно, когда значение не важно- Пример — https://www.dropbox.com/s/l88ttuf0v2s5usz/EnumInTypeScript.png?dl=0

Объяснить значение кортежей(tuples)​

В TS- Tuple (Кортеж данных) — это последовательность значений- Особености: — Tuple объявляется с помощью TypeScript с определенным количеством элементов и указывает тип для каждого элементаlet myTuple: [dataType1, dataType2, dataTypeN] = [value1, value2, valueN] — Начальным значением должен быть массив с тем же количеством элементов, что и в описании типа

EnglishMoji!

Проблемы?​

Пишите в Discord или телеграмм чат, а также подписывайтесь на наши новости

TypeScript: Раскладываем tsconfig по полочкам. Часть 1

Я большой фанат TypeScript. Каждый свой новый проект я предпочитаю писать на нём, а не на чистом JavaScript. В данной статье я не буду рассматривать причины выбора TypeScript или о его преимуществах и недостатках. Я хочу, чтобы данный пост стал своего рода шпаргалкой для тех, кто хочет понять, как настраивать tsconfig , разложить по полочкам его многочисленные флаги и, возможно, узнать некоторые полезные трюки.

Итак, в данной статье я хочу предоставить переработанную и упорядоченную выжимку документации, которая, я уверен, будет полезна тем, кто только начинает свой путь в мире TypeScript или тем, кто до этого момента не нашёл времени и сил, чтобы разобраться в деталях и теперь хочет закрыть этот пробел.

Если открыть официальный референс tsconfig , то там будет полный список всех настроек, разделённых по группам. Однако, это не даёт понимания, с чего начать и что из данного обширного списка опций обязательно, а на что можно не обращать внимания (по крайней мере до поры до времени). Плюс, иногда опции сгруппированы по некому техническому, а не логическому смыслу. Например, некоторые флаги проверок можно найти в группе Strict Checks , некоторые в Linter Checks , а другие в Advanced . Это не всегда удобно для понимания.

Все опции, как и саму статью, я разделил на две группы – базовые и «проверки». В первой части статьи разговор пойдёт про базовые настройки, а во второй уже про различные проверки, т. е. про тюнинг строгости компилятора.

Структура tsconfig

Рассмотрим структуру и некоторые особенности конфига.

  • tsconfig.json состоит из двух частей. Какие-то опции необходимо указывать в root , а какие-то в compilerOptions
  • tsconfig.json поддерживает комментарии. Такие IDE как WebStorm и Visual Studio Code знают об этом и не выделяют комментарии как синтаксическую ошибку
  • tsconfig.json поддерживает наследование. Опции можно разделить по некоторому принципу, описать их в разных файлах и объединить с помощью специальной директивы

Это болванка нашего tsconfig.json :

К root опциям относится только следующие: extends , files , include , exclude , references , typeAcquisition . Из них мы будем рассматривать первые 4. Все остальные опции размещаются в compilerOptions .

Иногда в root секции конфига можно встретить такие опции как compileOnSave и ts-node . Эти опции не являются официальными и используются IDE для своих целей.

Секция root

extends

Type: string | false, default: false.

Указывает путь к файлу из которого нужно унаследовать опции. По большей части, служит инструментом упорядочивания. Можно разделить опции по некой логике, чтобы они не смешивались. Например, вынести настройки строгости в отдельный файл, как в примере болванки конфига. Однако, учитывая поддержку комментариев в tsconfig.json это можно сделать проще:

Рассмотрим другой use-case, где комментариями отделаться не получится. Если необходимо создать production и development конфиги. Так бы мог выглядеть tsconfig-dev.json версия конфига:

В целом, я рекомендую пользоваться extends . Однако, сильно дробить настройки не рекомендую. Это может привести к запутыванию. В том числе по причине того, что множественное наследование не поддерживается.

Если вы решите использовать эту опцию. То увидеть итоговую, объединённую версию конфига поможет команда tsc —showConfig .

files

Type: string[] | false, default: false, связана с include .

Указать список конкретных файлов для компиляции можно использовав данную опцию.

< "compilerOptions": <>, "files": [ "core.ts", "app.ts" ] >

Данная опция подходит лишь для совсем маленьких проектов из нескольких файлов.

include

Type string[], default: зависит от значения files , связана с exclude .

Если опция files не указана, то TypeScript будет использовать эту директиву для поиска компилируемых файлов. Если include так же не указана, то её значение будет неявно объявлено как [«**/*»] . Это означает, что поиск файлов будет осуществляться во всех папках и их подпапках. Такое поведение не оптимально, поэтому в целях производительности лучше всегда указывать конкретные пути. Можно прописывать как пути к конкретным файлам, так и паттерны путей.

< "compilerOptions": <>, "include": [ "src/**/*", "tests/**/*" ] >

Если паттерны не указывают конкретных расширений, то TypeScript будет искать файлы с расширениями .ts , .tsx и .d.ts . А если включен флаг allowJs , то ещё .js и .jsx .

Следующие форматы записей делают одно и тоже src , ./src , src/**/* . Я предпочитаю вариант ./src .

Технически, используя опции include и exclude , TypeScript сгенерирует список всех подходящих файлов и поместит их в files . Это можно наблюдать если выполнить команду tsc —showConfig .

exclude

Type: string[], default: [«node_modules», «bower_components», «jspm_packages»].

Директива служит для того, чтобы исключать некоторые лишние пути или файлы, которые включились директивой include . По умолчанию, опция имеет значение путей пакетных менеджеров npm , bower и jspm , так как модули в них уже собраны. Помимо этого, TypeScript будет так же игнорировать папку из опции outDir , если она указана. Это папка, куда помещаются собранные артефакты сборки. Логично, что их нужно исключить. Если добавить свои значения в эту опцию, то необходимо не забыть восстановить умолчания. Так как пользовательские значения не объединяются со значениями по умолчанию. Другими словами, необходимо вручную указать корень модулей своего пакетного менеджера.

< "compilerOptions": <>, "exclude": [ "node_modules", "./src/**/*.spec.ts" ] >

Опция exclude не может исключить файлы, указанные через files .

Опция exclude не может исключить файлы, если они импортируются в других файлах, которые не исключены.

Секция compilerOptions

target

Type: string, default: ES3 , влияет на опции lib , module .

Версия стандарта ECMAScript, в которую будет скомпилирован код. Здесь большой выбор: ES3 , ES5 , ES6 (он же ES2015 ), ES2016 , ES2017 , ES2018 , ES2019 , ES2020 , ESNext . Для backend приложений/пакетов подойдёт ES6 , если рассчитываете только на современные версии Node.js и ES5 , если хотите поддержать более старые версии. На данный момент стандарт ES6 , с небольшими оговорками, поддерживается 97.29% браузеров. Так что для frontend приложений ситуация аналогичная.

module

Type: string, default: зависит от target , влияет на опцию moduleResolution .

Модульная система, которую будет использовать ваше собранное приложение. На выбор: None , CommonJS , AMD , System , UMD , ES6 , ES2015 , ES2020 или ESNext . Для backend приложений/пакетов подойдёт ES6 или CommonJS в зависимости от версий Node.js , которые хотите поддерживать. Для frontend приложений под современные браузеры также подходит ES6 . А для поддержки более старых браузеров и для изоморфных приложений, определённо стоит выбрать UMD .

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

moduleResolution

Type: string, default: зависит от module .

Стратегия, которая будет использоваться для импорта модулей. Здесь всего две опции: node и classic . При этом classic в 99% не будет использоваться, так как это legacy. Однако, я специально упомянул этот флаг, так как он меняется в зависимости от предыдущего флага. При изменении значения module режим moduleResolution может переключиться на classic и в консоли начнут появляться сообщения об ошибках на строчках с импортами.

Во избежание описанной ситуации, я рекомендую всегда явно указывать значение node для данного флага.

lib

Type: string[], default: зависит от target .

В зависимости от того какой target установлен в конфиге, TypeScript подключает тайпинги ( *.d.ts-файлы ) для поддержки соответствующих спецификаций. Например, если ваш target установлен в ES6 , то TypeScript подключит поддержку array.find и прочих вещей, которые есть в стандарте. Но если target стоит ES5 , то использовать метод массива find нельзя, так как его не существует в этой версии JavaScript. Можно подключить полифилы. Однако, для того, чтобы TypeScript понял, что теперь данную функциональность можно использовать, необходимо подключить необходимые тайпинги в секции lib . При этом, можно подключить как весь стандарт ES2015 , так и его часть ES2015.Core (только методы find , findIndex и т. д.).

Конечно, правильным выбором будет подключать тайпинги только той функциональности, для которой установлены полифилы.

Для --target ES5 подключаются: DOM, ES5, ScriptHost Для --target ES6: DOM, ES6, DOM.Iterable, ScriptHost

Как только вы что-либо добавляете в lib умолчания сбрасываются. Необходимо руками добавить то, что нужно, например DOM :

outDir

Type: string, default: равняется корневой директории.

Конечная папка, куда будут помещаться собранные артефакты. К ним относятся: .js , .d.ts , и .js.map файлы. Если не указывать значение для данной опции, то все вышеуказанные файлы будут повторять структуру исходных файлов в корне вашего проекта. В таком случае будет сложно удалять предыдущие билды и описывать .gitignore файлы. Да и кодовая база будет похожа на свалку. Советую складывать все артефакты в одну папку, которую легко удалить или заигнорировать системой контроля версий.

Если оставить опцию outDir пустой:

├── module │ └── core.js │ └── core.ts ├── index.js └── index.ts

Если указать outDir :

├── dist │ └── module │ | └── core.js │ └── index.js ├── module │ └── core.ts └── index.ts

outFile

Type: string, default: none.

Судя по описанию, данная опция позволяет объединить все файлы в один. Кажется, что бандлеры вроде webpack больше не нужны… Однако, опция работает только если значение module указано None , System или AMD . К огромному сожалению, опция не будет работать с модулями CommonJS или ES6 . Поэтому скорее всего использовать outFile не придётся. Так как опция выглядит максимально привлекательно, но работает не так как ожидается, я решил предупредить вас об этом гигантском подводном камне.

allowSyntheticDefaultImports

Type: boolean, default: зависит от module или esModuleInterop .

Если какая-либо библиотека не имеет default import , лоадеры вроде ts-loader или babel-loader автоматически создают их. Однако, d.ts-файлы библиотеки об этом не знают. Данный флаг говорит компилятору, что можно писать следующим образом:

// вместо такого импорта import * as React from 'react'; // можно писать такой import React from 'react';

Опция включена по умолчанию, если включен флаг esModuleInterop или module === «system».

esModuleInterop

Type: boolean, default: false.

За счёт добавления болерплейта в выходной код, позволяет импортировать CommonJS пакеты как ES6 .

// библиотека moment экспортируется только как CommonJS // пытаемся импортировать её как ES6 import Moment from 'moment'; // без флага esModuleInterop результат undefined console.log(Moment); // c флагом результат [object Object] console.log(Moment);

Данный флаг по зависимости активирует allowSyntheticDefaultImports . Вместе они помогают избавиться от зоопарка разных импортов и писать их единообразно по всему проекту.

alwaysStrict

Type: boolean, default: зависит от strict .

Компилятор будет парсить код в strict mode и добавлять “use strict” в выходные файлы.

По умолчанию false, но если включен флаг strict , то true.

downlevelIteration

Type: boolean, default: false.

Спецификация ES6 добавила новый синтаксис для итерирования: цикл for / of , array spread , arguments spread . Если код проекта преобразовывается в ES5 , то конструкция с циклом for / of будет преобразована в обычный for :

// код es6 const str = 'Hello!'; for (const s of str)
// код es5 без downlevelIteration var str = "Hello!"; for (var _i = 0, str_1 = str; _i < str_1.length; _i++)

Однако, некоторые символы, такие как emoji кодируются с помощью двух символов. Т. е. такое преобразование в некоторых местах будет работать не так, как ожидается. Включенный флаг downlevelIteration генерирует более многословный и более «правильный», но менее производительный код. Код получается действительно очень большим, поэтому не буду занимать место на экране. Если интересно посмотреть пример, то перейдите в playground и выберете в настройках target -> es5 , downlevelIteration -> true .

Для работы данного флага, необходимо, чтобы в браузере была реализация Symbol.iterator . В противном случае необходимо установить полифил.

forceConsistentCasingInFileNames

Type: boolean, default: false.

Включает режим чувствительности к регистру (case-sensitive) для импорта файлов. Таким образом, даже в case-insensitive файловых системах при попытке сделать импорт import FileManager from ‘./FileManager.ts’ , если файл в действительности называется fileManager.ts , приведёт к ошибке. Перестраховаться лишний раз не повредит. TypeScript — это про строгость.

Опции секции compilerOptions, которые нужны не в каждом проекте

declaration

Type: boolean, default: false.

С помощью включения данного флага, помимо JavaScript файлов, к ним будут генерироваться файлы-аннотации, известные как d.ts -файлы или тайпинги. Благодаря тайпингам становится возможным определение типов для уже скомпилированных js файлов. Другими словами код попадает в js , а типы в d.ts -файлы. Это полезно в случае, например, если вы публикуете свой пакет в npm . Такой библиотекой смогут пользоваться разработчики, которые пишут как на чистом JavaScript, так и на TypeScript.

declarationDir

Type: string, default: none, связан с declaration .

По умолчанию тайпинги генерируются рядом с js -файлами. Используя данную опцию можно перенаправить все d.ts -файлы в отдельную папку.

emitDeclarationOnly

Type: boolean, default: false, связан с declaration .

Если по какой-то причине вам нужны только d.ts -файлы, то включение данного флага предотвратит генерацию js -файлов.

allowJs

Type: boolean, default: false.

Портировать ваш JavaScript проект на TypeScript поможет данный флаг. Активировав allowJs TypeScript компилятор будет обрабатывать не только ts файлы, но и js . Нет нужды полностью мигрировать проект, прежде чем продолжить его разработку. Можно это делать файл за файлом, просто меняя расширение и добавляя типизацию. А новый функционал сразу можно писать на TypeScript.

checkJs

Type: boolean, default: false, связан с allowJs .

TypeScript будет проверять ошибки не только в ts , но и в js -файлах. Помимо встроенных тайпингов для языковых конструкций JavaScript, TS-компилятор так же умеет использовать jsDoc для анализа файлов. Я предпочитаю не использовать этот флаг, а наводить порядок в коде в момент его типизации. Однако, если в вашем проекте хорошее покрытие кода jsDoc, стоит попробовать.

С версии 4.1 при включении checkJs , флаг allowJs включается автоматически.

experimentalDecorators и emitDecoratorMetadata

Type: boolean, default: false.

Декоратор — это стандартный паттерн из мира ООП и его можно реализовывать классическим образом, создавая классы или функции-обёртки. Однако, с помощью двух вышеперечисленных флагов можно включить экспериментальный синтаксис декораторов. Данный синтаксис позволяет декорировать классы, их методы и свойства, модификаторы доступа, а так же аргументы функций используя простой и распространённый во многих языках программирования синтаксис @ .

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

Для работы emitDecoratorMetadata необходимо подтянуть в проект библиотеку reflect-metadata.

resolveJsonModule

Type: boolean, default: false.

Флаг позволяет включить возможность импортировать *.json файлы. Ничего дополнительно устанавливать не требуется.

// необходимо указывать расширение .json import config from './config.json'

jsx

Type: string, default: none.

Если проект использует React, необходимо включить поддержку jsx . В подавляющем большинстве случаев будет достаточно опций react или react-native . Так же есть возможность оставить jsx-код нетронутым с помощью опции preserve или использовать кастомные преобразователи react-jsx и react-jsxdev .

Завершение первой части

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

UPD: Здесь можно прочитать вторую часть статьи.

Декларации¶

Декларации это очень важная часть TypeScript, благодаря которой магия статической типизации проецируется на динамический JavaScript. Поэтому декларациям будет посвящена вся данная глава, рекомендуемая тем, кто только собирается писать свою первую типизированную библиотеку, которую планируется распространять с помощью npm репозитория.

Что такое декларация (Declaration)¶

Поскольку при разработке программ на TypeScript используются библиотеки, написанные на JavaScript, компилятор tsc, чьей главной задачей является проверка типов, чувствует себя будто у него завязаны глаза. Несмотря на то, что с каждой новой версией вывод типов все лучше и лучше учится разбирать JavaScript, до идеала ещё далеко. Кроме того, разбор JavaScript кода добавляет нагрузку на процессор, драгоценного времени которого при разработке современных приложений порой и так недостаточно.

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

1 2 3 4 5 6 7 8
Файл Animal.ts export default class Animal  public name: string = 'animal'; public voice(): void <> > 
1 2 3 4 5 6 7 8 9
Файл Animal.d.ts declare module "Animal"  export default class Animal  name: string; voice(): void; > > 

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

Установка деклараций с помощью @types¶

Если декларация распространяется отдельно от библиотеки, то она скорее всего, попадет в огромный репозиторий на github под названием DefinitelyTyped, содержащий огромное количество деклараций. Чтобы было проще ориентироваться в этом множестве, помимо сайта «TypeSearch», выступающего в роли поисковика, был создан менеджер деклараций под названием Typed. Но о нем мы говорить не будем, поскольку он применяется при работе с TypeScript версиями меньше чем v2.0, поэтому речь пойдет о его развитии в образе команды пакетного менеджера npm, а именно @types.

Для того чтобы установить требующуюся декларацию, в терминале необходимо выполнить команду, часть которой состоит из директивы @types , после которой через косую черту / следует имя библиотеки.

npm i -D @types/name 

Для примера, воспользуемся проектом, созданным в теме, посвященной настройке рабочего окружения, и для демонстрации работы директивы @types установим всем известную библиотеку React.

Первым делом установим саму библиотеку React, выполнив в терминале, запущенном из-под директории проекта, следующую команду.

npm i -D react 

Открыв директорию /node_modules/ можно убедиться, что библиотека React успешно установлена, поэтому сразу же попытаемся импортировать её в файл index.ts , расположенный в директории src, предварительно изменив его расширение на требуемое для работы с React — .tsx .

1 2 3
// @filename: src/index.tsx import React,  Component > from 'react'; // Error 

Несмотря на установленную на предыдущем шаге библиотеку React, при попытке импортировать её модули возникла ошибка. Возникла она потому, что компилятору TypeScript ничего неизвестно о библиотеке React, поскольку декларация, описывающая типы, поставляется отдельно от неё. Чтобы tsc понял, что от него хотят, требуется дополнительно установить декларацию при помощи команды @types пакетного менеджера npm .

npm i -D @types/react 

Ошибка, возникающая при импорте модулей React исчезла, а если заглянуть в директорию _/node_modules/ , то можно увидеть новую примечательную поддиректорию /@types , предназначенную для хранения устанавливаемых с помощью опции @types деклараций.

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

npm i -D react-dom 

Кроме того, нужно установить необходимую для работы с ним декларацию.

npm i -D @types/react-dom 

Осталось только активировать опцию —jsx в _tsconfig.json и скомпилировать проект, как это было показано ранее.

1 2 3 4 5 6 7 8 9
import React,  Component > from 'react'; // Ok import * as ReactDOM from 'react-dom'; // Ok const HelloReact = () => h1>Hello react!/h1>; ReactDOM.render( HelloReact />, document.querySelector('#root') ); 

Подготовка к созданию декларации¶

Помимо того, что декларацию можно написать руками, её также можно сгенерировать автоматически, при условии, что код написан на TypeScript. Для того, чтобы tsc при компиляции генерировал декларации, нужно активировать опцию компилятора —declaration .

Будет нелишним напомнить, что декларацию нужно генерировать только тогда, когда библиотека полностью готова. Другими словами, активировать опцию —declaration нужно в конфигурационном файле production сборки. Кроме того, в декларации нуждается только код, который будет собран в подключаемую библиотеку. Поэтому точкой входа в библиотеку должен быть файл, который содержит только импорты нужных модулей. Но разработка библиотеки невозможна без её запуска, а значит и точки входа, в которой будет создан и инициализирован её экземпляр. Поэтому, чтобы избежать чувства «что-то пошло не так», необходимо помнить, что при создании библиотеки, требующей декларацию, в проекте может быть несколько точек входа. Точкой входа самого компилятора служит конфигурационный файл, который ему был установлен при запуске. Это означает, что если проект находится в директории src , то в декларации путь будет указан как src/libname вместо требуемого lib .

1 2 3 4 5
// Ожидается declare module 'libname'  //. > 
1 2 3 4 5
// Есть declare module 'src/libname'  //. > 

Это в свою очередь означает, что при импорте модулей придется учитывать лишнюю директорию.

1 2 3
// Ожидается import  libname > from 'libname'; 
1 2 3
// Есть import  libname > from 'src/libname'; 

Это проблему можно решить, разместив конфигурационный файл в директории исходного кода, в нашем случае это директория src . Кто-то не придаст этому значения, кому-то это может показаться неэстетичным. Поэтому при рассмотрении генерации деклараций с помощью tsc, конфигурационный файл будет лежать непосредственно в директории src . Но при рассмотрении генерации деклараций с помощью сторонних библиотек, будет освещен альтернативный вариант.

Но и это ещё не все. Представьте, что Вы создаете библиотеку React, которая в коде представляется одноимённым классом, расположенным в файле React.ts . При этом модуль, который будет представлять вашу библиотеку, должен называться react , что в свою очередь обязывает задать имя файлу, являющемуся точкой входа как `react.js. Ну и что, спросите вы? Если вы ещё не знаете ответ на этот вопрос, то будете удивлены, узнав, что существуют операционные системы, как, например, Windows, которые расценивают пути до файлов React.ts и react.ts идентичными. Простыми словами, если в директории присутствует файл с идентификатором Identifier , то ОС просто не позволит создать одноимённый файл, даже если его символы будут отличаться регистром. Именно об этом и будет сказано в ошибке, возникающей, когда TypeScript обнаружит одноимённые файлы в одной директории. Кроме того, если ваша операционная система позволяет создавать файлы, чьи идентификаторы отличаются только регистром, помните, что разработчик, работающий с вами в одной команде, не сможет даже установить проект себе на машину, если его операционная система работает по другим правилам.

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

И поскольку TypeScript является компилируемым языком, не будет лишним напомнить правила именования директории, в которую будет компилироваться результат. В случае разработки приложения, директорию, содержащую скомпилированный результат, принято называть dest (сокращение от слова destination). При разработке внешней библиотеки или фреймворка, директорию для собранных файлов принято называть dist (сокращение от слова distributive).

Разновидности деклараций¶

На самом деле эта глава должна называться «разновидности библиотек», так как именно о них и пойдет речь. Дело в том, что совсем недавно вершиной хорошего тона считалось объединение всего кода в один файл. Это же правило соблюдалось и при создании библиотек. Но сейчас все кардинально поменялось, и дело вот в чем.

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

Tree Shaking — это механизм, позволяющий включать в сборку исключительно используемый код. Простыми словами, данный механизм позволяет взять только используемую часть от всей библиотеки. В перспективе это должно быть спасением, но на деле оказалось не совсем так.

Дело в том, что на данный момент Tree Shaking работает только если библиотека разбита на множество модулей. К примеру, такие именитые библиотеки, как lodash или rxjs, для каждой отдельной функции создают отдельную точку входа, что при их использовании позволяет значительно сократить размер конечного кода. Обозначим подобные библиотеки как библиотеки с множеством точек входа. Кроме того, существуют библиотеки, сопоставимые с монолитом, поскольку при использовании их малой части в конечную сборку они попадают целиком. Обозначим такие библиотеки как библиотеки с единственной точкой входа.

Декларации и область видимости¶

Важным моментом при создании деклараций для библиотек является понимание того, как их трактует компилятор. Дело в том, что все доступные компилятору декларации находятся в общей для всех области видимости. Это означает, что они так же, как переменные, функции и классы способны затенять или, другими словами, перекрывать друг друга. Кроме того, идентификатор файла не играет никакой роли, поскольку компилятор рассматривает только определение деклараций с помощью ключевого слова declare . Проще говоря, два файла, имеющие отличные идентификаторы, но идентичные объявления, будут затенять друг друга.

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Файл ./types/petanimal.d.ts declare module 'Pig'  // Error export default class Pig <> > declare module 'Goat'  // Error export default class Goat <> > declare module 'petanimal'  // Ok export  default as Pig > from 'Pig'; export  default as Goat > from 'Goat'; > 
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Файл ./types/wildanimal.d.ts declare module 'Pig'  // Error export default class Pig <> > declare module 'Goat'  // Error export default class Goat <> > declare module 'wildanimal'  // Ok export  default as Pig > from 'Pig'; export  default as Goat > from 'Goat'; > 
1 2 3 4
// Файл index.ts import Pig from Pig; // From which library should import module? 

Погружение в область видимости стоит начать с понимания процесса компилятора, стоящего за установлением принадлежности к декларации в случаях, когда она распространяется не через менеджер @types . Прежде всего, компилятор ищет в файле package.json свойство types и при его отсутствии или пустом значении “” переходит к поиску файла index.d.ts в корне директории. Если свойство types ссылается на конкретную декларацию, то точкой входа считается она. В противном случае файл index.d.ts . Стоит учесть, что при разработке будет возможно только взаимодействовать с модулями, подключенными к точке входа. Кроме того, ограничить область видимости можно при помощи конструкций, объявленных при помощи ключевых слов module или namespace . Единственное, о чем сейчас стоит упомянуть, что области, определяемые обеими конструкциями, нужно расценивать как обычные модули, поскольку они могут включать только одно объявление экспорта по умолчанию ( export default ).

Если не уделить должного внимания области видимости при создании деклараций для подключаемых библиотек, у разработчиков, которые будут использовать подобные декларации с другими декларациями, имеющими идентичное определение, могут возникнуть ошибки на этапе компиляции. Решений у этой проблемы всего два — сокрытие определений и уточнение определений. Способ, к которому стоит прибегнуть, зависит от вида разрабатываемой библиотеки.

Декларации для библиотек с одной точкой входа¶

В проекте, созданном в теме, посвященной настройке рабочего пространства, в директории src создайте две точки входа, одну для разработки index.ts , а другую для prod-версии, имя которой должно соответствовать имени библиотеки, в нашем случае это будет index.lib.ts .

По умолчанию точкой входа, как npm пакета, так и декларации, является файл с именем index . Поэтому, если в проект библиотеки имеет несколько точек входа, то важно не забыть указать имя файла с помощью свойства types package.json . Если для сборки используется webpack, то будет значительно проще изменить имя на index во время компиляции.

Кроме того, создайте два файла: IAnimal.ts и Zoo.ts . Также в директории /src создайте директорию /animal , в которой будут размещены два файла: Bird.ts и Fish.ts . В итоге должна получиться следующая структура:

 1 2 3 4 5 6 7 8 9 10 11 12
* / * src * utils * string-util.ts * animal * Bird.ts * Fish.ts * IAnimal.ts * Zoo.ts * index.ts * index.lib.ts * tsconfig.prod.ts 
1 2 3 4 5

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *