Сегодня, в 2017 году, многие вечнозеленые браузеры поддерживают модули ES6 из коробки. В некоторых браузерах он скрыт за флагом, в том числе в Node.js. Но можно ли поддерживать старую и новую среду одним и тем же пакетом npm? Да!

Примечание редактора. Модули ES6 иногда называют модулями ES2015, ESM или module скриптами, а иногда даже расширением .mjs, которое произносится как «скрипты Майкла Джексона». Мы все говорим об одном и том же, поэтому не запутайтесь, если услышите разные термины.

Я не буду вдаваться в подробности использования модулей ES6 или почему TypeScript - это круто, потому что в блогах было много сообщений, подробно описывающих их. Вместо этого я сосредоточусь на публикации пакета в npm, который написан на TypeScript, но развернут как .mjs (ESM) и .js (CommonJS), чтобы любой потребитель мог использовать ваш пакет!

Начиная

Первый шаг - настроить ваш tsconfig.json файл так, чтобы TypeScript использовал новейшие и лучшие функции JavaScript, например:

{
  "compilerOptions": {
    "module": "es2015",
    "target": "ES2017",
    "rootDir": "src",
    "outDir": "dist",
    "sourceMap": false,
    "strict": true
  }
}

Очевидно, мы хотим, чтобы модули были es2015, потому что это в заголовке этой статьи, поэтому мы должны решить это в какой-то момент! Давайте нацелимся на es2017, чтобы мы могли использовать ключевые слова async и await, как JS Ninja. Вы можете называть свои rootDir и outDir как хотите, но это своего рода соглашение использовать dist для вывода в JS Land. Карты источников не являются обязательными, но я предпочитаю отключать их, пока они мне не понадобятся. Строгий режим также является необязательным, но проще начать строгий и получить немного расслабленного тупого духа, если вам понадобится слишком поздно. Я настоятельно рекомендую включить его, так как по умолчанию он отключен.

Подключение его

Теперь мы можем обсудить файл package.json. Вот пример пакета под названием copee:

{ 
  "name": "copee",
  "version": "1.0.0",
  "description": "Copy text from browser to clipboard...natively!",
  "repository": "styfle/copee",
  "files": [ "dist" ],
  "main": "dist/copee",
  "types": "dist/copee.d.ts",
  "scripts": {
    "mjs": "tsc -d && mv dist/copee.js dist/copee.mjs",
    "cjs": "tsc -m commonjs",
    "build": "npm run mjs && npm run cjs"
  },
  "devDependencies": {
    "typescript": "^2.5.3"
  }
}

Первые четыре строки определяют пакет name, version, description и GitHub repository, которые не требуют пояснений.

Затем мы определяем files, который мы просто определяем как одну папку dist. Это файлы, которые будут опубликованы в npm.

Точка входа в ваш пакет определяется как main, и именно здесь происходит волшебство. Обратите внимание, что нет расширения файла (например, .js), как и следовало ожидать. Это позволит Node выбрать файл в зависимости от способа, которым потребитель импортирует ваш пакет - либо устаревший CJS, либо новый ESM.

Далее идет types, который необходим для потребителей, которые хотят импортировать через TypeScript. Если вы пишете свой пакет на TypeScript, вам обязательно нужно включить types, чтобы ваши коллеги-пользователи TS получили безопасность типов! Серьезно, это как раз то, что нужно сделать.

А теперь самое интересное: scripts. Это ваши шаги сборки, которые можно запустить через npm run thenameofthescriptgoeshere. На первом этапе сборки mjs используется компилятор TypeScript (tsc) для сборки нашего кода с использованием tsconfig.json файла, который мы определили ранее, плюс флаг -d, который испускает наши определения типа .d.ts. Также обратите внимание на команду mv, которая перемещает (или, скорее, переименовывает) выходной файл с .js на .mjs. Это наш вывод ESM.

Наш следующий сценарий, cjs, использует компилятор TypeScript (tsc) для создания того же исходного кода, но выводит результат как модуль CommonJS. Это модульная система для Node.js, которую понимают browserify, webpack и т. Д.

Наконец, у нас есть devDependencies, которые являются вашими инструментами сборки. В этом случае все, что нам нужно, это typescript, который включает команду tsc, использованную выше.

Использование узла

Я собираюсь показать вам, как написать потребителя, который импортирует указанный выше пакет. Если вы уже регулярно используете Node, перейдите к следующему разделу об использовании ESM.

Сначала установите пакет copee:

npm install --save copee

Затем создайте index.js файл со следующим:

const { toClipboard } = require('copee'); console.log('CJS: We found a ', typeof toClipboard);

Новую программу можно выполнить так:

node index.js

Использование узла ESM

Я собираюсь показать вам, как написать потребителя, который импортирует пакет copee, указанный выше.

После установки copee создайте index.mjs файл. Вы должны использовать расширение сценария Майкла Джексона (.mjs).

import { toClipboard } from 'copee';
console.log('ESM: We found a ', typeof toClipboard);

Новую программу можно выполнить так:

node --experimental-modules index.mjs

Использование ESM в браузере

Использование Node не так впечатляюще, потому что с момента его создания были модули, но прелесть ESM в том, что тот же самый код, который выполняется в модуле Node, будет работать в браузере без изменений! Да, это правда! Обратите внимание на этот элегантный фрагмент кода ниже:

<script type="module">
  import {
    toClipboard
  } from 'https://cdn.jsdelivr.net/npm/copee/dist/copee.mjs';
  $('#btn').on('click', () => {
    const win = toClipboard('Wow, "copee" works!');
      if (win) {
        // it worked, check your clipboard!
      }
  });
</script>

У нас есть новый тип сценария для module, и мы используем jsDelivr для автоматического размещения нашего кода в CDN. Это позволяет легко написать одну строку импорта и использовать пакет copee в браузерах по всему миру!

Устаревшие браузеры

Вы скажете, что насчет устаревших браузеров? Не все поддерживают ESM? Что ж, это можно решить, связав UMD с rollup. После установки rollup добавьте это в раздел scripts вашего package.json файла.

{
  "umd": "rollup -i dist/copee.mjs -o dist/copee.umd.js -f umd -n copee"
}

Вы можете без конфликтов включать сборки ESM и UMD на одну страницу. См. Фрагмент ниже:

<script nomodule src="https://cdn.jsdelivr.net/npm/copee/dist/copee.umd.js"></script> <script type="module">
  import {
    toClipboard
  } from 'https://cdn.jsdelivr.net/npm/copee/dist/copee.mjs';
</script>

Используя атрибут nomodule, вы указываете новым браузерам игнорировать сценарий UMD. Используя type=module, вы говорите старым браузерам игнорировать ESM. Теперь все выигрывают!

Вы можете увидеть рабочую демонстрацию этого решения на Демонстрационной странице.

Также, пожалуйста, загляните в репозиторий GitHub для получения более подробной информации и, конечно же, рабочего исходного кода!

Первоначально опубликовано на www.ceriously.com 16 октября 2017 г.