npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

compelem

v0.2.3-beta

Published

A modern, reactive, fast, lightweight and flexible lib for building web components

Downloads

452

Readme

CompElem

一个现代化、响应式、快速、轻量的WebComponent开发库。为开发者提供丰富、灵活、可扩展的声明式接口

概览

CompElem 基于 Class 进行构建,该模型允许开发者使用装饰器进行声明式编码,核心特性包括:

  • 类JSX的原生模板系统
  • 丰富的装饰器及指令
  • 可选的生命周期
  • 原生插槽系统
  • 响应式域样式
  • ...

创建一个WebComponent总会从声明一个组件元素(CompElem子类)开始

const Slogan = ['complete', 'componentize', 'compact', 'companion']
@tag("page-test")
export class PageTest extends CompElem {
  //////////////////////////////////// props
  @prop arg:any

  @state colorR = Math.random() * 255 % 255 >> 0;
  @state colorG = Math.random() * 255 % 255 >> 0;
  @state colorB = Math.random() * 255 % 255 >> 0;
  @state rotation = 0

  //////////////////////////////////// computed
  @computed
  get color() {
    return `linear-gradient(90deg,rgb(${this.colorR},${this.colorG},${this.colorB}), rgb(${255 - this.colorR},${255 - this.colorG},${255 - this.colorB}));`
  }

  //////////////////////////////////// watch
  @watch('rotation')
  function(nv:number) {
    console.log(nv)
  }

  //////////////////////////////////// styles
  //静态样式
  static get styles(): Array<string | CSSStyleSheet> {
    return [`:host{
        font-size:16px;
      }...`];
  }
  //动态样式
  get css() {
    return `h2,p,i,h3{
      background-image:${this.color}
      filter:hue-rotate(${this.rotation}deg)
    }`
  }

  @query('i[name="text"]')
  text: HTMLElement
  sloganIndex = 0

  //////////////////////////////////// lifecycles
  mounted(): void {
    setInterval(() => {
      this.rotation += 1
    }, 24);

    setInterval(() => {
      this.text.classList.add('hide')
      setTimeout(() => {
        this.text.innerHTML = Slogan[this.sloganIndex % 4]
        this.sloganIndex++
        this.text.classList.remove('hide')
      }, 500);
    }, 5000);
  }
  render(): Template {
    return html`<div>
            <i>Welcome to</i>
            <br>
            <h2>CompElem</h2>
            <br>
            <i>A modern, reactive, fast and lightweight library</i>
            <br>
            <i>for building</i>
            <h3>Web Components</h3>
            <p>
              &lt;c-element&gt; <i name="text">...</i> &lt;/c-element&gt;
            </p>
            ${this.arg}
        </div>`
  }
}

而后即可在HTML中直接使用,与使用一个原生元素如DIV没有任何区别

<body>
    <page-test arg="args..."></page-test>
</body>

当然,也可以直接嵌入其他UI库中只要引入编译后的js即可

APIs

  • 视图模板

    使用render()函数定义组件视图模板

    render(): Template{
      return html`<div>Hello CompElem</div>`
    }
  • 视图模板-属性表达式

    通过再视图模板中插入表达式可以实现动态视图,表达式通过在不同位置使用分为不同类型见(#### 指令类型)。其中属性表达式根据前缀分为

    | 前缀 | 描述 | 示例 | | ---- | ------------------------------------------------------------- | -------------------------------------- | | @ | 事件属性,可用于任何标签 | <div @click="${this.onClick}"> | | . | 参数属性,仅用于给组件标签传递参数 | <l-input .value="${this.text}"> | | ? | 可选属性,用于 disabled/readonly 等 toggle 类属性 | <input ?disabled="${this.disabled}"> | | * | 引用属性,表达式求值后才会设置该属性。常用于 SVG 相关属性设置 | <circle *r="${this.r}"> |

    引用属性可通过属性参数进行格式转换,如

    render(): Template{
      return html`<svg *view-box:camel="">...</svg>`// <svg viewBox="">
    }

    支持格式包括:

    • camel 驼峰式
    • kebab 短横线
    • snake 下划线
  • 样式

    使用静态函数定义组件样式或全局样式(如弹框)

    static get styles(): Array<string | CSSStyleSheet> {
      return [];
    }
    static get globalStyles(): Array<string> {
      return [];
    }

    对于需要动态控制 host 元素样式可以使用组件实例 getter

    get css():string{
      return `:host{
        ${this.border?'border: 1px solid rgb(var(--l-color-border-secondary)); ':''}
      }`
    }
  • 属性

    属性是由组件外部提供参数的响应变量,可通过@prop注解定义

    @prop({ type: Boolean }) loading = false;//显式定义属性类型
    @prop round = true;//通过默认值自动推断属性类型
    @prop({ type: [Boolean,String] }) round = true;//多种类型使用数组定义
    @prop({ type: Array }) datalist: Array<string>;//没有默认值必须显式指定属性类型
    @prop({ type: [String, Number], sync: true }) //通过get/set设置属性
    get value() {
      return this.__innerValue ?? ''
    }
    set value(v: any) {
      this.__innerValue = v
      if (isNil(v)) {
        this.__innerValue = '';
      }
    }

    属性可以在组件内修改但默认不会同步父组件,除非显式指定sync或自行 emit update 事件 全部注解参数见 PropOption

    在某些无法使用装饰器的场景中(如MixinClass),可以使用函数makeProp(...)定义prop

    export function Loadable<T extends Constructor<any>>(spuerClass: T) {
      return class Loadable extends spuerClass {
          //declare a prop
          name: string
    
          constructor(...args: any[]) {
              super()
              //make the prop reactive
              Decorator.call(this, prop, 'name', { type: String })
          }
      }
    }
  • 状态

    状态是仅由组件内部初始化的响应变量,可通过@state注解定义

    @state hasLeft = false;//定义状态
    @state({//自定义变化判断
      hasChanged(nv: any[], ov: any[]) {
        return isEqual(nv , ov)
      }
    }) private nodes: Array<Record<string, any>>;

    状态仅能在组件内修改 全部注解参数见 StateOption

  • 状态监视

    使用@watch注解可以对属性/状态进行变化监视

    @prop width = "auto";
    
    @watch("width", { immediate: true })
    watchWidth(nv: string, ov: string, sourceName: string) {
      this.style.width = nv;
    }

    对于同类属性共享处理逻辑的监视,可以批量处理

    @watch(['height', 'minHeight', 'maxHeight'], { immediate: true })
    watchHeight(nv: string, ov: string, sourceName: string) {
      this.style[srcName] = nv;
    }
  • 计算状态

    计算状态会缓存 return 结果,只有当内部使用的任意属性/状态发生变化时才会重新计算 使用@computed注解的 Getter,如

    @computed
    get hasHeader() {
      return !isEmpty(this.slots.header) || !!this.header
    }
  • 节点引用

    使用@query/all注解及ref属性

    //query
    @query('l-icon')
    iconEl: HTMLElement
    //ref
    refNode: HTMLElement
    divRef = createRef<HTMLDivElement>();
    //视图片段
    return html` <l-icon></l-icon>
      <div ref="${divRef}"></div>`;
  • 内置属性及函数

    • readonly parentComponent 父组件引用,可能为空
    • readonly renderRoot/renderRoots 渲染根元素/渲染根元素列表
    • readonly shadowRoot 阴影DOM
    • readonly slots 插槽元素映射
    • readonly slotHooks 动态插槽钩子映射
    • readonly styles 组件样式对象列表
    • readonly attrs 组件特性
    • readonly props 组件属性
    • slotComponent 所在插槽组件
    • on(evName: string, hook: (e: Event) => void) 在root元素上绑定事件
    • emit(evName: string, arg: Record<string, any>, options?: {event?: Event;bubbles?: boolean;composed?: boolean;}) 抛出自定义事件
    • nextTick(cbk: () => void) 下一帧执行函数
    • forceUpdate() 强制更新一次视图

组件渲染流程

CompElem 组件既可以在 CompElem 环境内调用,也可以直接在原生环境调用,区别只是原生环境无法像组件传递类型参数。流程如下:

创建流程

| 功能 | | 生命周期 | | ------------------------------------------------------------ | --- | ----------- | | 1. 创建组件实例,完成类属性默认值设置(prop/state/...) | | | | 2. 初始化类全局样式(仅一次)及 实例样式(产生 styles 数组) | | | | 3. 创建 shadowRoot 并挂载组件样式 | | constructor | | 4. 绑定 parentComponent | | connected | | 5. 获取 attrs 及 parentProps 进行验证及初始化 props | | propsReady | | 6. 注入 link 外部样式表 | | | | 7. 渲染 render 及依赖绑定 | | render | | 8. 绑定 renderRoot 及 renderRoots | | | | 9. 执行 ref | | | | 10. 执行 @query 注解 | | | | 11. 执行 @watch(immediate) 注解 | | | | 12. 执行 @event 注解 | | mounted | | 13. 执行 slot filter / 动态 slot | | slotChange |

更新流程【普通】

| 功能 | 生命周期 | | ------------------------------ | ------------ | | 1. 父组件 props 变更【或】 | propsReady | | 1. 子组件 states 变更【或】 | | | 2. 执行@watch 注解 | | | 3. 合并变更内容并判断是否更新 | shouldUpdate | | 4. 更新依赖域指令(非 render) | | | 5. 更新动态 slot | | | 6. 执行 ref | | | 7. 执行@query 注解 | update |

更新流程【强制】

| 功能 | 生命周期 | | ------------------ | -------- | | 1. forceUpdate | | | 2. 渲染 render | render | | 3. 执行@query 注解 | update |

插槽 Slot

定义插槽

使用原生 <slot></slot> 标签来嵌入插槽,可以通过node-filter属性过滤插槽内容

<slot
  .node-filter="${{
  type: [HTMLElement, CompElem],
  maxCount:1
}}"
></slot>
<!-- 或使用函数精细控制 -->
<slot .node-filter="${(nodes:Node[])=>Node[]}"></slot>

插入节点(静态)

<l-tooltip>
  <l-button>默认插槽内容</l-button>
  <div slot="content">命名插槽内容</div>
</l-tooltip>

插入节点(动态)

动态内容插入仅可在 CompElem 组件中编码,可以通过组件注入参数动态生成插槽内容

//仅可用于CompElem组件中
return html` <l-tooltip>
  //动态内容通过slot指令定义 ${slot(
    (args: Record<string, any>) => html`
      <div>我是动态内容-${args.data.id}</div>
    `,
    "content"
  )}
</l-tooltip>`;

slot标签上注入参数

<slot .data="${this.row}"></slot>

注意,动态插槽必须关闭自动插槽,否则无效

static get autoSlot() {
  return false;
}

指令 Directive

指令用于分支/循环/动态插槽等结构及隐藏显示等

指令类型

不同的指令类型限制了指令仅能用于对应的插入点 |指令|插入位置|描述|示例|
|-------|-------|-------|-------| |ATTR|特性|可用于任何特性值之中,仅能插入一个|attr="${xxx}"| |PROP|属性|可用于任何属性值之中,必须是组件标签,仅能插入一个|.value="${xxx}"| |TEXT|文本|标签体内,可插入多个|<div>${xx1}${xx2}${...}</div>| |CLASS|样式类|用于 class 属性值中,仅能插入一个|class="a ${b}"| |STYLE|样式规则|用于 style 属性值中,仅能插入一个|style="a:1;${b}"| |SLOT|插槽|与 TEXT 类似,但标签必须是组件|| |TAG|标签|直接插入在节点标签上|<div a="b" ${show(..)}>|

内置指令

| 指令 | 类型 | 描述 | 示例 | | ------- | --------- | ----------------------------------------------------------- | ------------------------------------------------------ | | bind | TAG | 绑定属性/特性到标签上,根据标签类型及组件 prop 定义自动判断 | <div a="b" ${bind(obj)}> | | show | TAG | 隐藏/显示标签(基于 display) | <div a="b" ${show(visible)}> | | model | TAG | 双向绑定 | <div a="b" ${model(xx,modelPath?)}> | | classes | CLASS | 绑定样式类属性,支持对象/数组/字符串。可以和静态字符混用 | <div class="otherClass ${classes(obj)}>" | | styles | STYLE | 绑定样式规则属性,支持对象/字符串。可以和静态字符混用 | <div style="a:b;${styles(obj)}>" | | forEach | TEXT/SLOT | 输出循环结构 | ...>${forEach(ary,(item)=>html...)}<... | | ifTrue | TEXT/SLOT | 当条件为 true 时输出模板内容 | ...>${ifTrue(condition,()=>html...)}<... | | ifElse | TEXT/SLOT | 当条件为 true/false 时输出对应模板内容 | ...>${ifElse(condition,()=>html``,()=>html``)}<... | | when | TEXT/SLOT | 多条件分支,支持 switch/ifelse 两种模式 | ...>${when(condition,{c1:()=>html``,c2:...})}<... | | slot | SLOT | 动态插槽 | ...>${slot((args) => html``)}<... |

装饰器 Decorator

  • @state 定义组件内状态属性。可选参数{prop},可指定 propName 初始化值
  • @prop 定义父组件参数,默认不可修改。可选参数{type,required,sync,getter,setter}

    设置 getter/setter 后,该属性的@watch 将会失效

  • @query/queryAll 定义 CssSelector 查询结果
  • @tag 自定义组件的标签名
  • @event 定义全局事件
  • @watch 监控 state/prop 变更
  • @computed 计算属性,仅在响应变量变更时更新缓存值

装饰器还可通过函数方式进行调用,如

//Decorator.call(class, decorator, fieldName, ...args)
Decorator.call(this, prop, 'name', { type: String })

注意,调用入口必须放在类的构造函数中

事件

事件分为三类

  1. 原生事件 —— <div @click="...",监听器回调参数返回原生事件对象
  2. 组件自定义事件 —— <l-select @change="...",监听器回调参数返回CustomEvent事件对象
  3. 扩展原生事件 —— <div @resize="...",监听器回调参数返回CustomEvent事件对象