Перейти к содержанию

Публикация

На этой странице представлены рекомендации по публикации компонента Lit на npm, менеджере пакетов, используемом подавляющим большинством библиотек JavaScript и разработчиков. Многоразовые шаблоны компонентов, настроенные для публикации на npm, смотрите в Starter Kits.

Публикация на npm

Чтобы опубликовать свой компонент на npm, см. инструкции по созданию пакетов npm.

В конфигурации package.json должны быть поля type, main и module:

package.json

1
2
3
4
5
{
    "type": "module",
    "main": "my-element.js",
    "module": "my-element.js"
}

Вам также следует создать README, описывающий, как использовать ваш компонент.

Публикация современного JavaScript

Мы рекомендуем публиковать модули JavaScript в стандартном синтаксисе ES2021, поскольку он поддерживается во всех вечнозеленых браузерах и позволяет получить самый быстрый и компактный JavaScript. Пользователи вашего пакета всегда могут использовать компилятор для поддержки старых браузеров, но они не смогут преобразовать устаревший JavaScript в современный синтаксис, если вы предварительно скомпилируете свой код перед публикацией.

Тем не менее, если вы используете недавно предложенные или нестандартные функции JavaScript, такие как TypeScript, декораторы и поля классов, вам следует скомпилировать эти функции до стандарта ES2021, поддерживаемого браузерами нативно, перед публикацией в npm.

Компиляция с TypeScript

Следующий JSON-пример представляет собой частичный tsconfig.json, который использует рекомендуемые опции для ориентации на ES2021, включает компиляцию декораторов и выводит типы .d.ts для пользователей:

tsconfig.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
    "compilerOptions": {
        "target": "es2021",
        "module": "es2015",
        "moduleResolution": "node",
        "lib": ["es2021", "dom"],
        "declaration": true,
        "declarationMap": true,
        "experimentalDecorators": true,
        "useDefineForClassFields": false
    }
}

Обратите внимание, что установка useDefineForClassFields в false требуется только в том случае, если target установлен на es2022 или выше, включая esnext, но рекомендуется явно убедиться, что эта настройка false.

При компиляции из TypeScript необходимо включить файлы декларации (сгенерированные на основе declaration: true выше) для типов вашего компонента в поле types файла package.json, а также убедиться, что файлы .d.ts и .d.ts.map также опубликованы:

package.json

1
2
3
{
    "types": "my-element.d.ts"
}

Дополнительную информацию см. в документации tsconfig.json.

Компиляция с помощью Babel

Чтобы скомпилировать компонент Lit, который использует предложенные возможности JavaScript, еще не включенные в ES2021, используйте Babel.

Установите Babel и необходимые вам плагины Babel. Например:

1
2
3
npm install --save-dev @babel/core
npm install --save-dev @babel/plugin-proposal-class-properties
npm install --save-dev @babel/plugin-proposal-decorators

Настройте Babel. Например:

babel.config.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const assumptions = {
    setPublicClassFields: true,
};

const plugins = [
    [
        '@babel/plugin-proposal-decorators',
        { decoratorsBeforeExport: true },
    ],
    ['@babel/plugin-proposal-class-properties'],
];

module.exports = { assumptions, plugins };

Вы можете запустить Babel через плагин bundler, например @rollup/plugin-babel, или из командной строки. Дополнительную информацию см. в документации Babel.

Лучшие практики публикации

Ниже перечислены другие передовые методы, которым следует следовать при публикации многократно используемых веб-компонентов.

Не импортируйте полифиллы в модули

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

Пакеты могут зависеть от полифиллов для тестов и демонстраций, поэтому, если они необходимы, их следует помещать только в devDependencies.

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

Пакетирование и другие оптимизации - это забота приложения. Пакетирование многократно используемого компонента перед публикацией в npm также может привести к появлению нескольких версий Lit (и других пакетов) в приложении пользователя, поскольку npm не может дедуплицировать пакеты. Это приводит к раздуванию и может вызвать ошибки.

Оптимизация модулей перед публикацией также может препятствовать оптимизации на уровне приложения.

Пакетирование и другие оптимизации могут быть полезны при обслуживании модуля из CDN, но поскольку пользователям может потребоваться использовать несколько пакетов, зависящих от Lit, обслуживание из CDN может привести к тому, что пользователи будут загружать больше кода, чем необходимо. По этим причинам мы рекомендуем приложениям, чувствительным к производительности, всегда собирать из npm, где пакеты могут быть дедуплицированы, а не загружать пакеты из CDN.

Если вы хотите поддерживать использование из CDN, мы рекомендуем четко разделить модули для CDN и модули, предназначенные для использования в производстве. Например, поместить их в отдельную папку или добавлять только как часть релиза на GitHub, не добавляя их в опубликованный модуль npm.

Включать расширения файлов в спецификаторы импорта

Разрешение модулей Node не требует расширений файлов, потому что оно выполняет поиск по файловой системе в поисках одного из нескольких расширений файлов, если оно не задано. Когда вы импортируете some-package/foo, Node импортирует some-package/foo.js, если он существует. Аналогично, инструменты сборки, которые преобразуют спецификаторы пакетов в URL, также могут выполнять такой поиск в файловой системе во время сборки.

Однако спецификация import maps, которая начинает поставляться в браузерах, позволит браузеру загружать модули с пустыми спецификаторами пакетов из исходного кода без преобразования, предоставляя сопоставление спецификаторов импорта с URL в манифесте import map (который, вероятно, будет сгенерирован инструментом на основе вашей, например, установки npm).

Карты импорта позволяют сопоставлять импорт с URL, но у них есть только два типа сопоставлений: точное и префиксное. Это означает, что можно легко псевдонимизировать все модули из данного пакета, сопоставив имя пакета с одним префиксом URL. Однако если вы пишете импорты без расширений файлов, это означает, что каждый файл в вашем пакете будет нуждаться в записи в карте импорта. Это может сильно раздуть карту импорта.

Таким образом, чтобы подготовить ваши исходные тексты к оптимальной совместимости с картами импорта, мы рекомендуем писать импорты с расширениями файлов.

Публикуйте типизации TypeScript

Чтобы ваш элемент было легко использовать из TypeScript, мы рекомендуем вам:

  • Добавить запись HTMLElementTagNameMap для всех элементов, созданных на TypeScript.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    @customElement('my-element')
    export class MyElement extends LitElement {
        /* ... */
    }
    
    declare global {
        interface HTMLElementTagNameMap {
            'my-element': MyElement;
        }
    }
    
  • Опубликуйте свои типизации .d.ts в пакете npm.

Подробнее о HTMLElementTagNameMap смотрите в Обеспечение хороших TypeScript-типизаций.

Самоопределяющиеся элементы

Модуль, в котором объявляется класс веб-компонента, всегда должен содержать вызов customElements.define() (или декоратора @customElement) для определения элемента.

В настоящее время веб-компоненты всегда определяются в глобальном реестре. Каждое определение пользовательского элемента должно использовать уникальное имя тега и уникальный класс JavaScript. Попытка дважды зарегистрировать одно и то же имя тега или один и тот же класс приведет к ошибке. Простой экспорт класса и ожидание того, что пользователь вызовет define(), является хрупким. Если два разных компонента зависят от общего третьего компонента и оба пытаются определить его, то один из них потерпит неудачу. Это не проблема, если элемент всегда определяется в том же модуле, где объявлен его класс.

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

В настоящее время ведется работа над добавлением в платформу Scoped Custom Element Registries. Скопированные реестры позволяют пользователю компонента выбирать имя тега пользовательского элемента для заданной корневой области тени. Как только браузеры начнут поставлять эту функцию, станет практичным публиковать два модуля для каждого компонента: один экспортирует класс пользовательского элемента без побочных эффектов, а другой регистрирует его глобально с именем тега.

До тех пор мы рекомендуем продолжать регистрировать элементы в глобальном реестре.

Экспорт классов элементов

Для поддержки подклассификации экспортируйте класс элемента из модуля, который его определяет. Это позволит создавать подклассы для целей расширения, а также для регистрации в Scoped Custom Element Registries в будущем.

Для дополнительного чтения

Более общее руководство по созданию высококачественных многократно используемых веб-компонентов см. в Gold Standard Checklist for Web Components.

Комментарии