你正在查看 Lit 的旧版本文档。点击 这里查看最新版本。

发布

本页提供将 Lit 组件发布到 npm 的指南,npm 是绝大多数 JavaScript 库和开发者使用的包管理器。请参阅入门套件获取可复用组件模板,已为发布到 npm 进行了配置。

要将你的组件发布到 npm,请参阅贡献 npm 包的说明

你的 package.json 配置应包含 typemainmodule 字段:

package.json

{
"type": "module",
"main": "my-element.js",
"module": "my-element.js"
}

你还应该创建一个 README 来描述如何使用你的组件。

我们推荐发布使用标准 ES2019 语法的 JavaScript 模块,因为这在所有常青浏览器上都受支持,并且产生最快、最小的 JavaScript。你的包的用户始终可以使用编译器来支持旧版浏览器,但如果你在发布前预编译代码,他们无法将旧版 JavaScript 转换为现代语法。

然而,重要的是,如果你使用了新提出或非标准的 JavaScript 特性,如 TypeScript、装饰器和类字段,你_应该_在发布到 npm 之前将这些特性编译为浏览器原生支持的标准 ES2019。

以下 JSON 示例是 tsconfig.json 的部分内容,使用推荐的选项来针对 ES2019,启用装饰器编译,并为用户输出 .d.ts 类型:

tsconfig.json

"compilerOptions": {
"target": "es2019",
"module": "es2015",
"moduleResolution": "node",
"lib": ["es2019", "dom"],
"declaration": true,
"declarationMap": true,
"experimentalDecorators": true,
"useDefineForClassFields": false
}

注意,将 useDefineForClassFields 设置为 false 仅在 target 设置为 esnext 或更高版本时才需要,但建议明确确保此设置为 false

从 TypeScript 编译时,你应该在 package.jsontypes 字段中包含组件类型的声明文件(基于上面的 declaration: true 生成),并确保 .d.ts.d.ts.map 文件也被发布:

package.json

{
...
"types": "my-element.d.ts"
}

有关更多信息,请参阅 tsconfig.json 文档

要编译使用尚未包含在 ES2019 中的 JavaScript 特性的 Lit 组件,请使用 Babel。

安装 Babel 和你需要的 Babel 插件。例如:

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

const assumptions = {
"setPublicClassFields": true
};

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

];

module.exports = { assumptions, plugins };

你可以通过打包器插件(如 @rollup/plugin-babel)或从命令行运行 Babel。有关更多信息,请参阅 Babel 文档

以下是发布可复用 Web Components 时应遵循的其他最佳实践。

Polyfill 是应用层面的考虑,因此应用应该直接依赖它们,而不是依赖各个包。所需的精确 polyfill 通常取决于应用需要支持的浏览器,这个选择最好留给使用你组件的应用开发者。你的组件文档应该清楚地标识它使用的可能需要 polyfill 的 API。

包可能需要为测试和示例依赖 polyfill,因此如果需要,它们应该只放在 devDependencies 中。

不要打包、压缩或优化模块

Permalink to "不要打包、压缩或优化模块"

打包和其他优化是应用层面的考虑。在发布到 npm 之前打包可复用组件还可能将 Lit(和其他包)的多个版本引入用户的应用中,因为 npm 无法去重这些包。这会导致代码膨胀并可能引起 bug。

在发布前优化模块也可能阻止应用层面的优化。

在从 CDN 提供模块时,打包和其他优化可能很有价值,但由于用户可能需要使用多个依赖 Lit 的包,从 CDN 提供可能导致用户加载比必要更多的代码。出于这些原因,我们建议对性能敏感的应用始终从 npm 构建,在那里包可以被去重,而不是从 CDN 加载打包的包。

如果你想支持从 CDN 使用,我们建议在 CDN 模块和用于生产环境的模块之间做出明确区分。例如,将它们放在单独的文件夹中,或者只作为 GitHub release 的一部分添加,而不添加到发布的 npm 模块中。

在导入标识符中包含文件扩展名

Permalink to "在导入标识符中包含文件扩展名"

Node 模块解析不需要文件扩展名,因为它在没有给出扩展名时会在文件系统中搜索几种文件扩展名。当你导入 some-package/foo 时,如果 some-package/foo.js 存在,Node 将导入它。同样,将包标识符解析为 URL 的构建工具也可以在构建时执行此文件系统搜索。

然而,import maps 规范正在浏览器中开始推出,它将允许浏览器通过在 import map 清单中提供导入标识符到 URL 的映射(可能由工具基于你的 npm 安装生成)来加载_未转换_的源代码中的裸包标识符模块。

Import maps 将允许将导入映射到 URL,但它们只有两种类型的映射:精确匹配和前缀匹配。这意味着可以轻松地通过将包名映射到单个 URL 前缀来为给定包下的_所有_模块设置别名。但是,如果你编写不带文件扩展名的导入,这意味着你包中的_每个文件_都需要在 import map 中有一个条目。 这可能会极大地膨胀 import map。

因此,为了让你的源代码现在就能与 import maps 最佳兼容,我们建议在导入时使用文件扩展名。

为了让你的元素易于在 TypeScript 中使用,我们建议你:

  • 为所有用 TypeScript 编写的元素添加 HTMLElementTagNameMap 条目。

    @customElement('my-element')
    export class MyElement extends LitElement { /* ... */ }

    declare global {
    interface HTMLElementTagNameMap {
    "my-element": MyElement;
    }
    }
  • 在你的 npm 包中发布 .d.ts 类型定义。

有关 HTMLElementTagNameMap 的更多信息,请参阅提供良好的 TypeScript 类型定义

声明 Web Component 类的模块应始终包含对 customElements.define()(或 @customElement 装饰器)的调用来定义元素。

目前,Web Components 始终在全局注册表中定义。每个自定义元素定义需要使用唯一的标签名唯一的 JavaScript 类。尝试注册相同的标签名两次或相同的类两次都会失败并报错。仅仅导出一个类并期望用户调用 define() 是脆弱的。如果两个不同的组件都依赖一个共享的第三方组件,并且都尝试定义它,其中一个会失败。如果一个元素始终在其类声明的同一模块中定义,这就不是问题。

这种方法的一个缺点是,如果两个不同的元素使用相同的标签名,它们不能同时被导入到同一个项目中。

作用域自定义元素注册表的工作正在进行中。作用域注册表允许自定义元素的标签名由组件的使用者为给定的 shadow root 作用域选择。一旦浏览器开始推出此功能,为每个组件发布两个模块将变得可行:一个导出没有副作用的自定义元素类,一个使用标签名在全局注册。

在此之前,我们建议继续在全局注册表中注册元素。

为了支持子类化,请从定义元素的模块中导出元素类。这允许出于扩展目的进行子类化,以及将来在作用域自定义元素注册表中注册。

有关创建高质量可复用 Web Components 的更一般性指南,请参阅 Web Components 黄金标准检查清单