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 🙏

© 2026 – Pkg Stats / Ryan Hefner

npro

v0.0.4

Published

npro,声明式构建和配置html文档

Downloads

33

Readme

npro

简介

npro是一个返璞归真的html文档构建和处理库。 提供声明式构建、链式配置、组件化、样式隔离、WebComponents集成五大核心功能。 npro不是MVVM框架,无脚手架,无虚拟DOM,无数据响应式,专注于高性能和对DOM节点精细控制的场景。

安装

  • 原生脚本引入
<script src="npro.min.js"></script>
  • npm安装
npm i npro
  • 模块化导入
import {npro,$} from "npro"  //$是npro的别名,以下默认使用$

快速上手

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="npro.min.js"></script>
</head>
<body>
  <div id="app"></div>
  <script>

    const data = [
      { id: 1, name: "张向明" },
      { id: 2, name: "蒋海艳" }
    ]

    const node = $.table`id="table1" class="list" draggable="true"` (
      $.thead(           
        $.tr(
          $.th("学号"),
          $.th("姓名")
        )
      ),
      $.tbody(           
        data.map( item => $.tr`id=${item.id}`(
          $.td(item.id),
          $.td(item.name)
        ))
      )
    )

    $("#app").render(node)

  </script>
</body>
</html>

模板


const title = "提示"
const data = [1,2,3]
function clickHandle(event){
  console.log(this.data)
}

const input = $.input`          //标签:所有的html原生标签和自定义标签都可以从$上获取
  id="div1"                     //attribute字面量
  title="${title}"              //attribute插值:插值表达式外围一定要加双引号
  value="&quot;引号&quot;"      //attribute值中的双引号一定要转义
  disabled                      //布尔型attribute
  data=${data}                  //property必须使用插值
  style=${{color:"red"}}        //style插值
  onclick=${clickHandle}               //事件赋值
  click=${[clickHandle,{once:true}]}   //事件监听`

$("#app").render(input)

子节点

const node = $.div`id="div1"`(
  "文本内容",
  $.ul( 
    $.li("项目1"),
    $.li("项目2"),
    [
      $.li("项目3"),
      $.li("项目4")
    ],                  //数组会自动展开
    ()=>$.li("项目5")    //函数会自动运行
  )
)
$("#app").render(node)

节点代理

【节点代理】是真实DOM节点的代理,主要作用是链式配置真节点的属性。可以通过如下几种方式获得节点代理。

  1. 【查询获取】:使用npro函数传入查询字符串,获取已经存在的真实DOM节点,并包装成节点代理。如:npro("#div1")
  2. 【加代理】:使用npro函数传入一个真实DOM节点,得到一个节点代理,我们称这一过程为加代理。如:npro(document.querySelector("#div1"))
  3. 【解代理】:通过节点代理的下标0可以得到真实节点,我们称这一个过程为解代理。如nodeProxy[0]
  4. 【标签构建】:通过标签函数构建一个全新的节点。如:div("内容")

在节点代理上可以通过属性函数链式配置节点的属性。

  1. 【配置标签属性】首字母是_,如:nodeProxy._id("myid")
  2. 【配置样式属性】以$开始,如:nodeProxy.$color("red")
  3. 【配置事件】以on开始,值是函数,如:nodeProxy.onclick(clickHandle)
  4. 【配置对象属性】除以上三种情况之外都是配置对象属性:如:draggable(true)
  5. 【prop】配置对象属性,如:nodeProxy.prop({disabled:true,readonly:true})
  6. 【attr】配置标签属性,如:nodeProxy.attr({id:"myid",name:"myname"})
  7. 【css】配置样式属性,如:nodeProxy.css({color:"red",backgoundColor:"green"})
  8. 【on】配置事件,如:nodeProxy.on("click",clickHandle)
//查询获取
const nodeProxy = $("#app");

//链式配置属性
nodeProxy
  .className("class1 class2")           //对象属性
  ._title("提示")                       //标签属性
  .$backgroundColor("green")            //样式
  .onclick(event=>console.log(event))   //事件
  .prop({draggable:true})               //对象属性
  .attr({name:"myname"})                //标签属性
  .css({color:"#fff",fontSize:"16px"})  //样式
  .on("click",()=>console.log(event))   //添加事件监听

//通过标签函数构建
const child = $.ul(
  $.li("项目1"),
  $.li("项目2")
)

//挂载子节点
nodeProxy.render(child)

//解代理
console.log(nodeProxy[0])

通用属性函数

节点代理上提供了大量可链式调用的函数, 称为【属性函数】。 属性函数用来配置和获取节点的对象属性、标签属性、样式属性、事件。

  1. 【prop(name,value)】配置对象属性
  2. 【prop(name)】获取对象属性值
  3. 【prop(obj)】将obj的键值配置到节点对应的对象属性上
  4. 【arrt(name,value)】配置标签属性
  5. 【arrt(name,false)】移除标签属性
  6. 【arrt(name,fn)】以fn的返回值为value递归调用attr(name,value)
  7. 【attr(name)】获取标签属性值
  8. 【attr(obj)】将obj的键值配置到节点对应的标签属性上
  9. 【css(name,value)】配置样式属性
  10. 【css(name,fn)】以fn的返回值为value递归调用css(name,value)
  11. 【css(name)】获取样式属性值
  12. 【css(obj)】将obj的键值配置到节点对应的样式属性上
  13. 【class(class1)】添加class1
  14. 【class([class1,calss2])】添加class1和class2
  15. 【class(class1,false)】移除class1
  16. 【class(fn)】将fn的返回值添加到className
  17. 【class(class1,fn)】如果fn的返回值是false则移除class1,否则添加class1
  18. 【on(name,fn,option)】配置事件
  19. 特别注意:attr的第二个参数如何为false,会删除该标签属性,如果要将标签属性设置为false请使用字符串"false"
$("#app")                              //加代理
  .prop("innerHTML","内容")             //配置对象属性
  .attr("draggable","true")             //配置标签属性                     
  .class("class1")                      //配置class
  .class("class2 class3 class4")        //添加多个class
  .class("class3 class4",false)         //移除class
  .css("display","block")               //配置样式
  .on("click",()=>console.log("单击"))  //配置事件
  
  .attr("title",function(){             //attr和css两个链函数的第二个参数可以是函数,函数的返回值就是属性值      
    let title = this.className ;        //this指向当前真节点 
    return title;
  })

//当prop、attr、css三个函数只有一个参数时,用来获取属性值
const node = $("#app")
console.log(node.prop("innerHTML"))
console.log(node.attr("draggable"))
console.log(node.css("display"))

动态属性函数

可以通过【属性函数】配置节点的对象属性、标签属性、样式属性、事件。 除此之外npro还提供了一套按函数名来配置属性的动态函数, 由于这套函数是完全由你的意图动态生成的, 因此我们称为【动态属性函数】。 动态属性函数的函数名约定如下:

  1. 以_开始的动态链函数用来配置节点的标签属性;
  2. 以$开始的动态链函数用来配置节点的样式;
  3. 以on开始的动态链函数用来配置节点的事件;
  4. 注意on与动态onxxx的区别,on使用的是addEventListener,而动态的onxxx使用的是属性赋值;
  5. 其他动态链函数用来配置节点的对象属性。

$("#app")  

  //配置对象属性,innerHTML既是函数名,也是对象属性名,函数的参数是对象属性值          
  .innerHTML("当前时间:"+new Date())    
  
  //配置标签属性,以下划线开始
  ._title("提示文字") 
  
  //配置样式,美元符号+小驼峰命名
  .$padding(20).$backgroundColor("lightgray")
  
  //以属性赋值的方式配置click事件
  .onclick(()=>console.log("onclick"))

  //以addEventListener方式配置事件
  .on(
    "click",
    ()=>console.log("addEventListener"),
    {once:true}
  )
  
  //标签属性函数和样式属性函数的参数可以是一个函数,函数的返回值就是属性值
  ._draggable(function(){
    let time = new Date().getTime()
    return `\${time%2===0}`
  });

//当动态属性函数无参时,将返回对应的属性值
const node = $("#app");
console.log(node.innerHTML())

插入html字符串

虽然提倡用npro声明式构建DOM树, 但也难免需要在节点上直接插入一个待解析的html字符串, 特别是在webpack等工程化环境中。 此时可以使用【节点代理】的html方法。

$("#app").html(`
  <h3>营利项目</h3>
  <ul>
    <li>项目1</li>
    <li>项目2</li>
    <li>项目3</li>
  </ul>
`)

事件处理

npro的事件处理有两种方式:

1. 将事件当属性配置

nodeProxy
  .onmousedown(event=>console.log("鼠标按下"))
  .onmouseup(event=>console.log("鼠标松开"))

2. 用on(type,fn,option)配置事件

【节点代理】的on函数是原生addEventListener的加强版, 会在内部生成一个新的事件处理函数, 由于该内部事件处理函数对外不可见, 因此无法通过removeEventListener移除。 需要使用AbortController的abort方法来移除该事件处理函数。

const controller = new AbortController()
$("#text").on(  
  "keydown",                   //事件类型
  function(event){             //事件处理函数
    console.log(event.key)
  },
  {                            //事件行为配置,每一项默认值为false
    capture: false,            //true表示只在捕获节点处理
    once: false,               //true表示只触发一次
    passive: false,            //true表示阻止调用event.preventDefault()
    signal: controller.signal, //将来调用controller的abort函数时将移除事件
    prevent: true,             //true表示阻止默认行为
    stop: true,                //true表示阻止冒泡
    self: true,                //true表示只在本节点上触发,子节点不触发
    letf: true,                //true表示只在按下鼠标左键时才触发事件
    right: false,              //true表示只在按下鼠标右键时才触发事件
    ctrl: true,                //true表示只在按下了ctrl键不松才触发事件
    alt: false,                //true表示只在按下了alt键不松才触发事件
    shift: false,              //true表示只在按下了shift键不松才触发事件
    meta: false,               //true表示只在按下了meta键不松才触发事件
    debounce: 200              //200毫秒防抖
  }
)

方法代理

节点代理的首字母大写的方法称为【方法代理】, 调用方法代理时,内部会先将首字母小写,然后调用真节点的同名方法。 特别注意【方法代理】的返回值是当前【节点代理】,而不是真节点的方法返回值。

//链式调用真节点的setAttribute方法
$("#app")
  .SetAttribute("title","提示文字")    
  .SetAttribute("draggable",true)

动态构建

标签函数的参数可以是一个节点数组, 也可以是一个能返回节点(或节点数组)的函数, 从而实现对子节点的动态构建。 动态构建为组件化提供天然的支持。

//准备数据
const data = ["item1","item2"]

//构建节点    
const node = $.div(
  $.div(
    "当前时间:",
    ()=>$.label(new Date())         //无参函数会自动运行,并将返回值插入当前节点
  ),
  $.ul(
    append=>{                     //函数会自动运行,参数append是一个钩子函数,指向上级节点代理的append方法
      for( let i=0; i<5; i++ ){
        append($.li("项目"+i))      //此处append钩子函数实际调用的是ul节点代理的append方法
      }
    }
  ),
  $.ul( 
    data.map(item => $.li(item)),  //数组会自动展开
  )
  
) 

//挂载节点
$("#app").render(node)

无参函数组件

所谓【函数组件】就是一个有返回值的函数, 命名为函数组件是为了区别于WebComponents组件。 函数组件的返回值通常是节点代理、字符串、属性配置对象, 以及上述类型的数组。


//创建组件
function component() {
  return $.ul(
    $.li("项目1"),
    $.li("项目2"),
    $.li("项目3")
  )
}

//使用组件
const node = $.div(
  $.div("项目列表"),
  component   //等价与component(),框架会自动调用无参的函数组件
)

//渲染节点
$("#app").render(node)

有参函数组件

无论是无参函数组件还是有参函数组件,其本质都是一个能返回节点代理(或节点数组)的函数,唯一的不同点是有参的函数组件在使用时必须手动传参调用。


//定义组件
function component(data) {
  return $.ul(
    data.map(item =>
      $.li(
        $.span(item.id),
        $.span(item.name))
    )
  )
}

//准备数据
const data = [{id:1,name:"项目1"},{id:2,name:"项目2"},{id:3,name:"项目3"}]

//使用组件
const node = $.div(
  $.div("项目列表"),
  component(data)  //调用组件函数并传入参数
)

//渲染节点
$("#app").render(node)

文档碎片

fragment是一个特殊的【标签函数】,其用途是创建一个文档碎片节点代理,其真节点是一个DocumentFragment实例。

//创建文档碎片
const content = $.fragment(
  $.li("项目1"),
  $.li("项目2"),
  $.li("项目3")
)

//使用文档碎片
const node = $.ul(
  content
)

//渲染节点
$("#app").render(node)

shadowRoot

npro实现了对WebComponents API的全面支持。 你可以使用customElement这种重型的实现, 也可以仅使用shadowRoot这种轻型实现。 shadowRoot可以做到最完美的样式隔离和样式穿透。 可以使用以下几种方式实现shadowRoot:

  1. 调用容器节点代理的shadowNode(node)函数
  2. 调用容器节点代理的shadowHTML(html)函数
  3. 设置子节点的root=true,声明式启用shadowRoot

shadowNode函数

$("#container").shadowNode(
  $.ul(
    $.li("项目1"),
    $.li("项目2")
  )
)

shadowHTML函数

$("#container").shadowHTML(`
  <ul>
    <li>项目1</li>
    <li>项目1</li>
  </ul>
`)

声明式启用shadowRoot

//ul设置了root属性,将先在容器上创建shadowRoot,然后再将ul插入容器。
//注意root属性要配置到容器的子节点上,而不是配置到容器本身
$("#container")(
  $.ul`shadow=${true}`(    
    $.li("项目1"),
    $.li("项目2")
  )
)

样式隔离

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="../npro.js"></script>
  </head>
  <body>
    <div>这组件外边的div,样式没有被污染</div>
    <div id="app"></div>
    <script>
      const node = $.template`shadow=${true}`(
        $.style(`
          li{
            color:var(--color);   /*使用外部定义的css变量*/
          }`),
        $.ul(
          $.li("项目1"),
          $.li("项目2"),
          $.li("项目3")
        )
      )
      $("#app").render(node)
  </script>
  </body>
</html>

样式穿透

shadowRoot将隔离容器外部与内部的样式, 在通常情况下这是优点, 但有时确实需要在容器内部使用外部的样式。 使用浏览器原生支持的css变量,可以深度穿透多层ShadowRoot, 是目前最优的样式穿透方案。

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      :root{--color:green;}
    </style>
    <script src="../npro.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
    const node = $.fragment`shadow=${true}`(
      $.style(`
        li{
          color:var(--color);   /*使用外部定义的css变量*/
        }`),
      $.ul(
        $.li("项目1"),
        $.li("项目2"),
        $.li("项目3")
      )
    )
    $("#app").render(node)
  </script>
  </body>
</html>

托管脚本

shadowRoot的子节点可以包含script块, 当子节点被插入到容器的shadowRoot时, 这些脚本块将被托管运行, 脚本块中的this指向容器, 脚本块中的root指向容器的shadowRoot。

const node = $.template`shadow=${true}`(
  $.ul(
    $.li("项目1"),
    $.li("项目2"),
    $.li("项目3")
  ),
  $.script(`
    this.onclick = ()=>{
      console.log("this is ",this)
      console.log("root is ",root)
    }
  `)
)
$("#app").render(node)

插槽


//创建一个包含插槽的template,并配置shadow为true
const temp = $.template`shadow=${true}`(
  $.style(`
    ::slotted(*){
      padding:10px;
      border:1px solid lightgray;
    }
  `),
  $.header("页头"),
  $.main(
    $.slot    //没有命名的插槽是一个默认插槽
  ),
  $.footer(
    $.slot`name="slot1"`
  )
)

$("#app")(temp)(              //将template的内容插入shadowRoot
  $.div("主体"),              //插入默认插槽
  $.div`slot=slot1`("页脚")   //插入具名插槽
)

CustomElement

//定义CustomElement类,请参考w3c官网
//以下示例省略了生命周期函数
//此处完全使用原生代码来创建CustomElement,
//其实完全可以用npro来构建shadowRoot的内容
class MyDiv extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode:"open"})
    this.shadowRoot.innerHTML = `
      <style>
        :host{display:block;border:1px solid lightgray;}
        *{padding:10px;}
        header{border-bottom:1px solid lightgray;font-weight:bolder;s}
      </style>
      <header>这是一个webcomponent组件</header>
      <main>
        <slot></slot>
      </main>`
  }
}

//为CustomElement定义标签
customElements.define("my-div",MyDiv)

//注意my_div的下划线对应是my-div的中划线
const node = $.my_div("这是组件的内容")

//渲染节点
$("#app").render(node)

文档处理

npro对常用的html文档处理函数做了轻量封装。

  1. 【nodeProxy[0]】通过节点代理的下标0可以获取代理的真实节点。
  2. 【nodeProxy[-1]】通过节点代理的下标-1可以获得真节点的shadowRoot。
  3. 【nodeProxy.html(value)】设置节点的innerHTML。
  4. 【nodeProxy.text(value)】设置节点的innerText。
  5. 【nodeProxy.append(...children)】添加子节点或配置属性。
  6. 【nodeProxy.prepend(...children)】在节点部的第一个子元素之前插入子节点
  7. 【nodeProxy.appendTo(container)】将当前节点追加到容器
  8. 【nodeProxy.prependTo(container)】将当前节点插入到容器的第一个子节点的前面
  9. 【nodeProxy.render(...chidren)】先清空本节点,在追加子节点
  10. 【nodeProxy.insert(value,where)】向当前节点插入子节点或html字符串内容
  11. 【nodeProxy.before(value)】在当前节点之前插入兄弟节点,value为空表示获取当前节点的上一个兄弟节点
  12. 【nodeProxy.after(value)】在当前节点之后插入兄弟节点,value为空表示获取当前节点的下一个兄弟节点
  13. 【nodeProxy.wrap(value)】用value来包裹当前节点
  14. 【nodeProxy.unwrap()】剥离当前节点的外层节点
  15. 【nodeProxy.up()】如果存在上一个兄弟节点,则与之交换位置
  16. 【nodeProxy.down()】如果存在下一个兄弟节点,则与之交换位置
  17. 【nodeProxy.top()】与父节点的第一个子节点交换位置
  18. 【nodeProxy.bottom()】与父节点的最后一个子节点交换位置
  19. 【nodeProxy.swap(target)】与父节点的指定子节点交互位置,target可以是下标、查询字符串、节点、节点代理
  20. 【nodeProxy.replace(target)】用当前节点替换目标节点

svg

$("#app")(
  $.svg `width="100" height="100"` (
    $.svg_path `d="M0,0 L100,100" stroke="red" stroke-width="1"`
  )
)
$("#app").render(node)

构建范式

【构建范式】是指你使用npro构建DOM树的习惯,并非强制要求。


//事件处理函数,不建议使用箭头函数,会丢失this
function clickHandle(event){
  console.log(this);
  console.log(event)
}

//【对象式构建】优点:性能最高,最方便属性共享。缺点:过多的大括号
const ul3 = $.ul.prop({id:"ul1",draggable:true,onclick:clickHandle}).css({color:"#fff",backgroundColor:"green"})(
  $.li.prop({id:"li11",title:"项目1"})("项目1"),
  $.li.prop({id:"li12",title:"项目2"})("项目2"),
  $.li.prop({id:"li13",title:"项目3"})("项目3")
)

//【链式构建】,优点:结构清晰,没有大括号。缺点:需要用_和$区分标签属性和样式,
const ul2 = $.ul._id("ul2").draggable(true).$color("#fff").$backgroundColor("green").onclick(clickHandle)(
  $.li.id("li21").title("项目1")("项目1"),
  $.li.id("li22").title("项目2")("项目2"),
  $.li.id("li23").title("项目3")("项目3")
)

//【模板字符串构建】,优点:最接近原生HTML文档。缺点:性能稍逊于【对象式构建】
const ul1 = $.ul`id="ul3" draggable=${true} onclick=\${clickHandle} style="color:#fff;background-color:green;"` (    
  $.li`id="li31" title="项目1"`("项目1"),
  $.li`id="li32" title="项目2"`("项目2"),
  $.li`id="li33" title="项目3"`("项目3")
)

//将节点挂载到app
$("#app")(ul1, ul2, ul3)