Автор Джон Рейли✏️
Выпуск TypeScript 4.7 включает серьезное обновление поддержки модулей ECMAScript для Node.js. В этой статье мы рассмотрим, что это значит.
- Поддержка TypeScript
- Создание модуля
- Добавление TypeScript 4.7
- Написание модулей TypeScript ECMAScript
- ECMAScript и CommonJS бок о бок
- Какие файлы создаются?
- Краткая история модулей ECMAScript
- Поддержка TypeScript
- Создание модуля
- Добавление TypeScript 4.7
- Написание модулей TypeScript ECMAScript
- ECMAScript и CommonJS бок о бок
- Какие файлы испускаются?
- Заключение
- Много пишете на TypeScript? Посмотрите запись нашей недавней встречи по TypeScript, чтобы узнать о написании более читабельного кода.
Краткая история модулей ECMAScript
Когда в 2015 году был выпущен ES6, вместе с ним появилась концепция модулей для JavaScript. Тогда они были известны как “модули ES6”. Сегодня они называются модулями ECMAScript (ESM).
В то время как написание кода с использованием семантики модулей ECMAScript быстро пришло для front end, для back end (который обычно представляет собой Node.js) это не так. На это есть несколько причин:
- В Node.js уже существовала устоявшаяся система модулей под названием CommonJS.
- Сам Node.js изначально не предлагал поддержку модулей ECMAScript; в значительной степени из-за проблем, связанных с поддержкой CommonJS, а также модулей ECMAScript.
Однако с выходом Node.js 14 поддержка модулей ECMAScript появилась. Если вас интересуют подробности поддержки этих модулей, то стоит прочитать эту статью о модулях ECMAScript.
Поддержка TypeScript
Команда TypeScript экспериментировала с тем, как предложить поддержку модулей ECMAScript с точки зрения Node.js, и с выходом TypeScript 4.7 поддержка появилась.
В этой заметке мы протестируем эту поддержку, попытавшись создать простой модуль на TypeScript с использованием новой поддержки модулей ECMAScript. По ходу дела мы обсудим, как выглядит авторство модулей ECMAScript для Node.js в TypeScript.
Поехали!
Создание модуля
Мы создадим модуль с именем greeter
– давайте инициализируем его:
mkdir greeter
cd greeter
npm init --yes
Теперь у нас есть package.json
, который выглядит примерно так:
{
"name": "greeter",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Node.js поддерживает новую настройку в package.json
под названием type
. Он может быть установлен либо в “module”, либо в “commonjs”. Цитируем документацию:
Файлы, заканчивающиеся на
.js
, загружаются как модули ES, если ближайший родительский файл package.json содержит поле верхнего уровня"type"
со значением"module"
.
Исходя из этого, мы добавим "type": "модуль"
в наш package.json
.
Теперь мы совместимы с поддержкой модулей ECMAScript, давайте начнем добавлять TypeScript.
Добавление TypeScript 4.7
Для того чтобы мы могли использовать поддержку модулей TypeScript ECMAScript, мы установим TypeScript 4.7 (в настоящее время он находится в бета-версии):
npm install typescript@4.7.0-beta --save
Установив его, инициализируем проект TypeScript:
npx tsc --init
Это создаст файл tsconfig.json
, который содержит множество опций. Мы изменим опцию module
на nodenext
, чтобы включить поддержку модулей ECMAScript:
{
"compilerOptions": {
// ...
"module": "nodenext" /* Specify what module code is generated. */,
"outDir": "./lib" /* Specify an output folder for all emitted files. */,
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// ...
}
}
Мы также установили опцию outDir
, чтобы скомпилированный JavaScript попадал в этот каталог, и опцию declaration
, чтобы генерировались файлы .d.ts
. Мы также обновим раздел "scripts"
нашего package.json
, чтобы включить скрипты build
и start
:
"scripts": {
"build": "tsc",
"start": "node lib/index.js"
},
Написание модулей TypeScript ECMAScript
Когда все готово, мы готовы написать несколько модулей TypeScript ECMAScript. Сначала мы напишем модуль greetings.ts
:
export function helloWorld(): string {
return 'hello world!';
}
В этом нет ничего нового или удивительного; это просто модуль, экспортирующий единственную функцию с именем helloWorld
. Все становится интереснее, когда мы пишем наш модуль index.ts
:
import { helloWorld } from './greetings.js';
const greeting = helloWorld();
console.log(greeting);
Код выше импортирует нашу функцию helloWorld
и затем выполняет ее, записывая вывод в консоль.
Это не особенно примечательно, однако то, как мы импортируем, является примечательным.
Мы импортируем из './greetings.js'
. В прошлом мы бы написали:
import { helloWorld } from './greetings';
Теперь мы пишем:
import { helloWorld } from './greetings.js';
Это может показаться немного странным и неестественным, потому что у нас нет greetings.js
в нашей кодовой базе; только greetings.ts
. Импорты, которые мы пишем, отражают код, который в конечном итоге будет выполнен; после того, как наш TypeScript будет скомпилирован в JavaScript. В модулях ES относительные пути импорта должны использовать расширения.
Самый простой способ продемонстрировать, что это законно, – выполнить следующий код:
npm run build && npm start
Что приводит к:
> greeter@1.0.0 build
> tsc
> greeter@1.0.0 start
> node lib/index.js
hello world!
Итак, это работает!
ECMAScript и CommonJS бок о бок
Частью поддержки модулей ECMAScript является возможность указать тип модуля файла на основе суффикса файла. Если вы используете .mjs
, вы явно указываете, что файл является модулем ECMAScript. Если вы используете .cjs
, вы явно говорите, что файл является модулем CommonJS. Если вы разрабатываете на TypeScript, вы используете mts
и cts
соответственно, и они будут транспонированы в mjs
и cjs
.
К счастью, Node.js позволяет модулям ES импортировать модули CommonJS, как если бы они были модулями ES с экспортом по умолчанию; это хорошая новость для взаимодействия. Давайте проверим это, написав модуль oldGreetings.cts
:
export function helloOldWorld(): string {
return 'hello old world!';
}
Синтаксис точно такой же, как и раньше.
Мы настроим наш index.ts
для использования этого:
import { helloWorld } from './greetings.js';
import { helloOldWorld } from './oldGreetings.cjs';
console.log(helloWorld());
console.log(helloOldWorld());
Обратите внимание, что мы импортируем из './oldGreetings.cjs'
. Посмотрим, работает ли это:
npm run build && npm start
Что приводит к:
> greeter@1.0.0 build
> tsc
> greeter@1.0.0 start
> node lib/index.js
hello world!
hello old world!
Это действительно работает!
Какие файлы испускаются?
Прежде чем мы завершим работу, может быть интересно посмотреть, что делает TypeScript, когда мы запускаем нашу npm run build
. Он переносит наш TypeScript в JavaScript в нашу директорию lib
: Обратите внимание, что файл
greetings.ts
привел к файлам greetings.js
и greetings.d.ts
, тогда как oldGreetings.cts
привел к файлам oldGreetings.cjs
и oldGreetings.d.cts
; это отражает различные типы представленных модулей.
Также интересно посмотреть на разницу в JavaScript. Если учесть, насколько похожи были исходные файлы. Если вы посмотрите на greetings.js
:
export function helloWorld() {
return 'hello world!';
}
Это тот же код, что и greetings.ts
, но со снятыми типами. Однако, если мы посмотрим на oldGreetings.cjs
, то увидим следующее:
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.helloOldWorld = void 0;
function helloOldWorld() {
return 'hello old world!';
}
exports.helloOldWorld = helloOldWorld;
В середине находится тот же код, что и в oldGreetings.cts
, но с убранными типами, а вокруг – код-шаблон, который TypeScript выдает для нас, чтобы облегчить взаимодействие.
Заключение
Мы увидели, как выглядит поддержка TypeScript для модулей ECMAScript и как настроить модуль для ее использования.
Если вы хотите более подробно ознакомиться с этой темой, то заметки о выпуске бета-версии TypeScript 4.7 – отличный ресурс.
Много пишете на TypeScript? Посмотрите запись нашей недавней встречи по TypeScript, чтобы узнать о написании более читабельного кода.
TypeScript привносит безопасность типов в JavaScript. Между безопасностью типов и читабельностью кода может возникнуть противоречие. Посмотрите запись, чтобы узнать о некоторых новых возможностях TypeScript 4.4.