9 个版本
0.2.3 | 2024 年 8 月 8 日 |
---|---|
0.2.2 | 2024 年 8 月 6 日 |
0.2.1 | 2024 年 5 月 15 日 |
0.2.0 | 2024 年 3 月 31 日 |
0.1.1 | 2023 年 11 月 27 日 |
#77 in 模板引擎
每月 251 次下载
65KB
1K SLoC
CEWT
带有(类型守卫)模板的定制元素。发音像“cute”
此项目的范围目前正在变化。该项目最初打算最终成为一个 SSR/SSG 套件的一部分,但我意识到,“这过于复杂了”!我不认为 TypeScript 代码生成会有太多(如果有的话)变化,所以如果您想将其用作项目的一部分,最好只使用 cewt codegen
命令和 --inline-html
选项。
安装
目前我不提供任何预编译的二进制文件,因此现在您可以 cargo install cewt
。
概述
Cewt 不打算强迫您接受一种全新的抽象(即:撒谎并让用户承担后果)来与 DOM 交互。从 HTML 片段自动生成 TypeScript 代码有 2 个主要优点。
- 运行时的计算开销更少
- 对其为您所做的工作完全透明。
TypeScript 代码生成
在定义您的元素时,cewt
会递归地在指定的文件夹中搜索 .html
文件。它要查找的 .html
文件是那些在根级别定义了一个或多个 <template>
元素的文件。这些模板可以用来创建 自主定制元素 和 定制内置元素。
自主定制元素
- 扩展
HTMLElement
- 在文档中显示为(例如)
<my-custom-element>
- 因此可以查询选择为
my-custom-element
- 因此可以查询选择为
- 利用模板片段,通过克隆内部元素到Shadow-DOM中
- 允许使用
<slot>
元素来帮助实现SSR - 类似于自己的文档树,这意味着查询选择文档无法触及由模板定义的元素。
- 类似于自己的CSS上下文,允许您链接CSS文件而不会影响整个文档,尽管文档的CSS规则仍然可能影响您的元素,例如继承字体样式。
- 允许使用
自定义内置元素
- 扩展您希望扩展的任何元素的类
- 在文档中以(例如)
<div is="my-custom-element">
的形式显示- 因此,可按如下查询选择:
div[is="my-custom-element"]
- 因此,可按如下查询选择:
- 通过将内部元素作为自定义元素的子元素来克隆您的模板片段,但这只有在从服务器检索元素时该元素为空的情况下才有效。
- 如果您只想有一个与HTML运行相关联的元素,并且不想改变文档的语义,那么这很有用。
注意:在撰写本文时,Safari目前不能做自定义内置元素,在苹果的典型的“不同”风格。Cewt本身不提供任何polyfill。我的个人解决方案是在以下检查失败的情况下,可选地导入@ungap/custom-elements
npm包。
function supportsExtendingBuiltinElements() {
try {
const newElemName = "test-button-" + Date.now().toString(36);
class HTMLTestButton extends HTMLButtonElement {};
customElements.define(newElemName, HTMLTestButton, { extends: "button" });
const newBtn = document.createElement("button", { is: newElemName });
return newBtn instanceof HTMLButtonElement && newBtn instanceof HTMLTestButton;
}catch(ex: any) {
return false;
}
}
如果您想在Safari上使用自定义内置元素,则必须这样做(鉴于iPhone上只有这个唯一浏览器,在iOS上,苹果只给您一个选择的错觉)。如果您的项目只使用自主自定义元素,或者您不关心iPhone,那么polyfill不是必需的。如果您确实使用了polyfill,请记住在注册自定义元素之前导入它。
如何使用
使用cewt有两个步骤。
- 定义您的模板
- 扩展自动生成的代码
- 生成HTML
- 不一定需要。我可能无论如何都会去掉这一步。
模板定义
要使用Cewt定义您的自定义元素,请使用cewt codegen
命令。
- 此命令有一个必需参数
<PATH>
,一个包含HTML模板的文件夹,这些模板可能位于子文件夹中。
- 此命令还有两个可选参数
--exclude <NAME>
,(可以指定多次)指定要排除的文件夹名称,默认为node_modules
。--inline-html
,这将包括您的模板片段的内部HTML作为自动生成代码的一部分。- 如果没有指定,默认情况下,自动生成的代码将引用一个由唯一自动生成的ID指定的
<template>
。尽管这需要下面的“捆绑”步骤。 - 您可以在以下情况下指定此选项
- 您无法使用内联模板(无代码的模板)。
- 您希望自定义元素成为可移植模块的一部分。
- 在每个生成的完整 *.html 文件中包含您的模板 HTML 片段似乎很愚蠢。
- 如果没有指定,默认情况下,自动生成的代码将引用一个由唯一自动生成的ID指定的
自动生成的 TypeScript 文件的位置取决于您的 HTML 片段文件是否被命名为 template.html
或类似 my-element-thing.html
。如果您的 HTML 片段文件被命名为 template.html
,它将在 HTML 文件所在的同一文件夹中创建一个 _autogen.ts
文件,否则如果不存在,它将创建一个 _autogen
文件夹,并创建一个与 .html
文件具有相同基本名称的 .ts
文件。例如
- (...)
- my-component
- _autogen.ts (请勿触摸)
- template.html (包含 1 个或多个模板)
- index.ts (您的代码在此,文件可以命名为任何名字)
- my-other-component
- _autogen.ts (请勿触摸)
- template.html (包含 1 个或多个模板)
- index.ts (您的代码在此,文件可以命名为任何名字)
- my-component
- (...)
- _autogen (请勿触摸)
- my-component.ts
- my-other-component.ts
- my-component.html
- my-component.ts
- my-other-component.html
- my-other-component.ts
- _autogen (请勿触摸)
请注意,您可以在每个 HTML 片段文件中有多个模板定义。所有自动生成的类都将位于同一个结果 .ts
文件中。
基本示例
以下是一个基本 自主自定义元素 定义的示例。
<template cewt-name="cewt-intro">
<h1>The Custom Element</h1>
<p>Says: <slot name="intro-text"></slot></p>
</template>
请注意,在生成 TypeScript 代码时,它检查 <slot>
元素的第一子元素以确定哪些元素将填充该插槽。如果没有找到,它假设插槽将由一个 <span>
填充。当然,没有任何保证 slots.introText
是一个 HTMLSpanElement
,但 TypeScript 是一种真正的语言,我们只是对自己说谎,让我们的 IDE 的自动完成稍微更了解我们的代码要做的事情,这就是我选择使用“类型守卫”而不是“类型安全”来描述这个工具的原因。
假设文件被命名为 template.html
,我们可以创建以下 TypeScript。
import {CewtIntroAutogen} from "./_autogen.ts";
class CewtIntroElement extends CewtIntroAutogen {
constructor() {
super();
// Custom behaviour here if desired.
// Note that the element might not be in the document at this point.
// This is called after registration if the element was already on the page.
}
connectedCallback() {
// Called whenever the element has been added to the page.
// Also called after registration if the element was already on the page.
// It is recommended to move have logic here as elements might be constructed only to be thrown away.
// Though also note elementes removed from the page may also be re-added.
// All properties of `this.slots` are getter functions. If an element doesn't exist, it is created.
console.log("<cewt-intro> has been added to the page, and it says: " + this.slots.introText.innerText);
}
disconnectedCallback() {
// cleanup logic, etc.
console.log("<cewt-intro> has been removed from the page");
}
adoptedCallback() {
// Not needed in most cases, see https://stackoverflow.com/questions/50995139/ for details.
}
}
CewtIntroElement.registerElement(); // Required. Make sure this is called with the child class.
现在,当以下内容在发送给用户的 HTML 文档中(假设您的代码也已导入)
<cewt-intro>
<span slot="intro-text">This is the first example!</span>
</cewt-intro>
以下内容将打印到控制台
<cewt-intro> has been added to the page, and it says: This is the first example!
要程序化创建此元素,您只需 new CewtIntroElement()
,出于某种原因,document.createElement("cewt-intro")
在所有浏览器中都不起作用。
自定义属性示例
如果您想使自定义元素具有自定义属性,您可以定义它们如下
<template cewt-name="example-element" cewt-attributes="my-attribute, my-other-attribute">
</template>
这允许您像这样使用它们
import {ExampleElementAutogen} from "./_autogen.ts";
class ExampleElementElement extends ExampleElementAutogen {
constructor() {
super();
}
onMyAttributeChanged(oldValue: string | null, newValue: string | null) {
// called when the function name implies
}
onMyOtherAttributeChanged(oldValue: string | null, newValue: string | null) {
// called when the function name implies
}
connectedCallback() {
// The super-class includes getters/setters for your custom attributes
console.log("my-attribute is:", this.myAttribute);
console.log("my-other-attribute is:", this.myOtherAttribute);
}
}
ExampleElementElement.registerElement(); // Required. Make sure this is called with the child class.
这样可以在文档中引用
<example-element my-attribute="value" my-other-attribute="valueeee"></example-element>
扩展内置元素
尽管 Webkit 团队对此事的看法,但扩展内置元素实际上是有用的,因为您可以使用自定义定义的行为创建 <button>
元素或 <dialog>
元素,这些行为存在于作用域上下文中,即您的扩展类。有关更多信息,请参阅WHATWG 的“自主自定义元素的缺点”
无论如何,这里有一个示例。
<template cewt-name="counter-example" cewt-extends="button">
You've clicked me <span cewt-ref="count">0</span> time(s)!
</template>
class CounterExampleElement extends CounterExampleAutogen {
count: number;
constructor() {
super();
this.count = 0;
this.addEventListener("click", (ev) => {
this.count += 1;
// Refs are just references to elements that aren't a part of the slot system
this.refs.count.innerText = this.count;
})
}
}
CounterExampleElement.registerElement();
由于这扩展了 <button>
,你可以随心所欲地进行制表符焦点和点击。只需将 <button is="counter-example"></button>
添加到文档中,或者程序化地构建一个 new CounterExampleElement()
即可。
HTML文档生成
如果需要这一步
- 你有内联模板
- 在TypeScript代码生成时没有指定
--inline-html
我在争论HTML文档生成是否超出这个工具的范围,所以我不打算写详细的文档,但现在,你可以运行 cewt bundle-single --help
来开始。
依赖关系
~11–20MB
~261K SLoC