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 🙏

© 2025 – Pkg Stats / Ryan Hefner

nova-view

v0.1.18

Published

轻量级响应式 UI 库,提供信号、计算属性、侦听等能力,专注于直接操作真实 DOM

Downloads

2,073

Readme

Nova View

把 UI 写回 DOM,把更新交给 Signal,把产能交给 AI。

没有 JSX、没有虚拟 DOM:写下去的是原生 DOM,状态一变就精准更新。

npm version License: MIT TypeScript Gzip Size

📖 在线文档 | 📦 npm | 💬 Issues


  • ⚡️ 极简主义的反叛
    不依赖模板语法和虚拟 DOM 的黑盒;CDN 场景可零构建直用。链式 API 像搭积木一样构建 UI,AI 不用学框架语法也更不容易写错。

  • 🎯 手术刀级的更新
    基于 Signal 的细粒度依赖追踪。状态变化时,只更新对应的 TextNode 或属性,绝不触碰无关 DOM。

  • 🏎️ 性能控制权回归
    既有自动 Diff 的便捷,也有 ForController 的极致。需要榨干性能时,你可以绕过 Diff 算法,直接对列表进行增删改查。


⚡ 5 秒快速体验

复制这段代码到浏览器控制台,立即感受“原生 DOM + 响应式”的魔法:

const s=document.createElement('script');s.src='https://unpkg.com/nova-view/dist/lib/nova-dom.umd.min.js';s.onload=()=>{const{Dom:$,ref:r,mount:m}=window['nova-view'],{div:d,button:b,h1:h}=$,c=r(0),app=d().style('position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;padding:2rem;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.1);text-align:center;z-index:9999;')(h().style('color:#333;margin-bottom:1rem;')('Hello Nova View!'),b().style('background:#007bff;color:white;border:none;padding:0.5rem 1rem;border-radius:4px;cursor:pointer;').onclick(()=>c.value++)(()=>('点击次数: '+c.value)));m(app,document.body)};document.head.appendChild(s)

🤖 为什么它是“AI Native”的 UI 库?

并不是所有框架都适合让 AI 写。Nova View 的设计天然契合 LLM 的代码生成特性:

  1. 极低的语法“幻觉”率

    • 传统框架:AI 常搞混 Vue2/3 写法、React Hook 依赖数组或 JSX 闭合标签。
    • Nova View:纯函数调用链(div().class().onclick())。结构即逻辑,AI 极难写出语法错误的函数调用。
  2. 原生的调试反馈回路

    • AI 生成的代码直接返回 HTMLElement。如果出了问题,AI 可以直接生成原生 DOM API(如 querySelector)代码来自我修正,无需理解复杂的框架运行时内部状态。
  3. 统一的状态心智

    • 没有复杂的指令集。动态内容要么是函数 () => val,要么是 ref。这种高度统一的模式让 AI 生成逻辑时异常稳定。

💡 提示词模板"用 nova-view 写一个计数器,使用 ref 管理状态,div 作为容器,style 使用对象写法..." -> AI 一次通过率极高。


💡 为什么选择 Nova View?

原生 DOM API - 冗长且容易出错:

const div = document.createElement('div');
div.className = 'container';
div.style.padding = '1rem';
const span = document.createElement('span');
span.textContent = 'Hello';
div.appendChild(span);

Nova View - 优雅且强大:

// 底层基于 Proxy 实现按需拦截,内置属性缓存与 Style Diff
div().class('container').style({ padding: '1rem' })(span()('Hello'));

代码量减少 70%,却拥有了完整的响应式能力和 TypeScript 支持。


📦 安装

包管理器安装 (推荐)

npm install nova-view
# 或 yarn add nova-view
# 或 pnpm add nova-view

CDN 直接使用 (零构建)

无需安装,直接在 HTML 中引入(推荐使用 unpkg):

<!DOCTYPE html>
<html>
<head>
  <script type="importmap">
    {
      "imports": {
        "nova-view": "https://unpkg.com/nova-view/dist/lib/nova-dom.min.js"
      }
    }
  </script>
</head>
<body>
  <div id="app"></div>
  <script type="module">
    import { Dom, ref, component, mount } from 'nova-view';

    const { div, button, h1 } = Dom;

    const Counter = component(() => {
      const count = ref(0);
      return div()(
        h1()(() => `Count: ${count.value}`),
        button().onclick(() => count.value++)('点击')
      );
    });

    mount(Counter(), document.getElementById('app'));
  </script>
</body>
</html>

🚀 核心概念与 API

1. 链式 API:不仅仅是语法糖

Nova View 的链式 API 背后的 DomElement 做了大量脏活累活:

  • 智能属性缓存:同值不重复写入 DOM,减少 Layout/Paint。
  • Style Diff:对象写法支持细粒度更新,动态样式自动解绑副作用。
  • Proxy 懒加载:只有访问到的属性才会创建 Setter。
import { Dom, ref } from 'nova-view';
const { div, span, button } = Dom;

const count = ref(0);

// 第一个括号:配置属性(支持静态值、Ref、函数)
div()
  .class('card')
  .style(() => ({ 
    color: count.value > 5 ? 'red' : 'black', // 动态样式
    padding: '1rem' 
  }))
// 第二个括号:添加内容(支持字符串、节点、数组、函数)
(
  span()('Current count: '),
  span()(() => count.value), // 细粒度更新 TextNode
  button().onclick(() => count.value++)('+')
);

2. 响应式系统:Signal 驱动

基于 track/trigger 的响应式系统,支持 batch 批量更新。

import { ref, computed, watchEffect, batch } from 'nova-view';

const count = ref(0);
const double = computed(() => count.value * 2);

watchEffect(() => {
  console.log(`Count: ${count.value}, Double: ${double.value}`);
});

// 批量更新:多次修改状态,只触发一次 DOM 刷新
batch(() => {
  count.value++;
  count.value++;
});

3. 组件:返回真实 Node

组件本质上就是返回 DOM 节点的函数。

import { Dom, component, onMounted } from 'nova-view';
const { div } = Dom;

const MyButton = component(() => {
  // 这里的代码只在组件初始化时运行一次
  const btn = document.createElement('button'); // 甚至可以混合原生 DOM
  
  onMounted(() => console.log('Mounted!'));

  return div()(btn); // 返回 Node
});

🎮 控制流与列表:性能直通车

Show / Switch:精确的 DOM 替换

利用 节点池 (Node Pool) 技术复用 Text/Comment 节点,减少高频切换时的 GC 压力。

Show(() => isLoading.value, {
  when: () => div()('Loading...'),
  fallback: () => div()('Content'),
  keepAlive: true // 可选:使用 display:none 切换而非移除 DOM
});

For:智能列表渲染

内置 LIS (最长递增子序列) 算法,确保最小化 DOM 移动。

For(() => items.value, {
  key: item => item.id,
  children: item => div()(item.name)
});

🚀 ForController:绕过 Diff 的极致性能

哪怕是最高效的 Diff 算法也有开销。在处理万级数据或高频实时更新(如股票K线、即时日志)时,你可以使用 controller 获得“上帝权限”:

let listController: ForController<Item> | null = null;

For(() => state.items, {
  key: it => it.id,
  children: (it) => ItemComponent(it),
  controller: (ctrl) => { listController = ctrl; }
});

// ❌ 传统方式:修改数据 -> 触发 Diff -> 更新 DOM
// list.value = newList;

// ✅ Nova 方式:直接操作 DOM 映射,0 Diff 开销
// 比 React 快 20%~80%(视操作密度而定)
batch(() => {
  // 直接更新特定项,不触发整个列表的重渲染
  listController?.updateItem(itemId, item => ({ ...item, price: newPrice }));
  
  // 复用现有 DOM 节点进行全量替换
  listController?.replaceAll(newItems, { reuseDOM: true });
});

这是 Nova View 区别于大多数 MVVM 框架的硬核能力:我们允许你为了性能“作弊”。


⚡ 性能基准 (Benchmark)

我们在 js-framework-benchmark 的标准场景下进行了测试。得益于无 Virtual DOM 开销精细的更新策略,Nova View 在多项指标上表现优异。

  • DOM 创建与替换:直接操作原生节点,无额外对象创建开销。
  • 内存占用:得益于 TextNode/CommentNode 对象池 技术,内存波动极低。
  • 列表更新
    • 常规模式 (For):自动 Diff,性能与主流框架持平。
    • 极速模式 (Controller):在特定场景下(如全部替换、定点更新),通过绕过 Diff 逻辑,比 React/Vue 快 20%~84%视具体操作密度而定,详见 bench 报告)。

注:性能数据基于 M1/M2 芯片测试环境,实际收益取决于业务场景。推荐在大量数据更新场景使用 Controller 模式。


📚 更多功能

  • VirtualList: 开箱即用的虚拟滚动组件,支持动态高度。
  • ErrorBoundary: 优雅处理组件树错误。
  • Context API: provide / inject 依赖注入。
  • 异步组件: 支持 async/await 组件定义。

🎯 使用场景

  • AI 辅助开发 - 语法简单,AI 生成代码准确率高。
  • 中小型 Web 应用 - 快速构建,打包体积极小。
  • 极致性能场景 - 大屏数据展示、高频实时更新列表。
  • 微前端 / 嵌入式 - 无需全家桶,轻松嵌入现有项目。

📚 深入指南(细节版)

你可以把上面当作“快速入口”。下面这一段是面向想把 Nova View 用到生产环境的人:把常见能力、边界和技巧一次讲透。

🎨 链式 API:双括号 ()() 的完整心智模型

Nova View 把 UI 写法拆成两个阶段:

  • 第一段 ():创建元素并“配置”(属性、事件、样式、class…)
  • 第二段 ():添加内容(子节点/字符串/函数/Ref…)
import { Dom } from 'nova-view';
const { div, button, span } = Dom;

div()                                    // 创建元素
  .class('container')                    // 配置:class / id / style / on*
  .id('app')
  .style({ padding: '1rem' })
  (                                       // 添加内容
    span()('Hello'),
    button().onclick(() => console.log('clicked'))('Click'),
  );

样式绑定:静态 / 混合 / 全响应式

import { Dom, ref } from 'nova-view';
const { div } = Dom;

// 1) 静态对象
div().style({ padding: '1rem', borderRadius: '8px' });

// 2) 混合:静态 + 响应式函数
const size = ref(16);
div().style({
  padding: '1rem',
  fontSize: () => `${size.value}px`,
  color: () => (size.value > 20 ? 'red' : '#111'),
});

// 3) 全响应式:函数返回对象
div().style(() => ({
  transform: `translateY(${size.value}px)`,
  opacity: size.value > 20 ? 0.8 : 1,
}));

响应式属性绑定:函数或 Ref

import { Dom, ref } from 'nova-view';
const { button } = Dom;

const count = ref(0);

button()
  .class(() => `btn ${count.value > 0 ? 'active' : ''}`)
  .disabled(() => count.value >= 10)
  .onclick(() => count.value++)
  (() => `Count: ${count.value}`);

嵌套结构:用代码保持层级清晰

import { Dom } from 'nova-view';
const { div, header, h1, nav, a, main, section } = Dom;

div().class('app')(
  header().class('header')(
    h1()('My App'),
    nav().class('nav')(
      a().href('/home')('Home'),
      a().href('/about')('About'),
    ),
  ),
  main().class('main')(
    section().class('content')('Content here'),
  ),
);

🎯 直接返回真实 DOM(这不是“抽象对象”)

Nova View 的元素就是浏览器原生节点,你可以直接调用任何 DOM API。

import { Dom, component, onMounted } from 'nova-view';
const { button } = Dom;

const EnhancedButton = component(() => {
  const el = button()
    .class('my-button')
    .onclick(() => console.log('clicked'))
    ('Click me');

  onMounted(() => {
    const rect = el.getBoundingClientRect();
    console.log('按钮位置:', rect.x, rect.y);
    el.style.transform = 'scale(1.05)';
  });

  return el;
});

⚡ 响应式系统:ref / reactive / computed / watch / batch

import { ref, reactive, computed, watch, watchEffect, batch } from 'nova-view';

const count = ref(0);
const user = reactive({ name: 'Alice', age: 25 });

const double = computed(() => count.value * 2);

watch(() => count.value, (n, o) => console.log('count:', o, '->', n));
watchEffect(() => console.log('double:', double.value));

batch(() => {
  user.name = 'Bob';
  count.value += 2;
});

🧩 组件系统(含插槽/事件/异步)

基础组件

import { Dom, ref, component, mount } from 'nova-view';
const { div, h1, button } = Dom;

const Counter = component(() => {
  const count = ref(0);
  return div().class('counter')(
    h1()(() => `Count: ${count.value}`),
    button().onclick(() => count.value++)('+'),
    button().onclick(() => count.value--)('-'),
  );
});

mount(Counter(), document.getElementById('app')!);

插槽(slots):默认插槽 / 具名插槽

import { Dom, component, type Slots } from 'nova-view';
const { div, button } = Dom;

const Card = component<{ title: string }>((props, { slots }) => {
  return div().class('card')(
    div().class('card-header')(props.title),
    div().class('card-body')(slots.default?.()),
    div().class('card-footer')(slots.footer?.()),
  );
});

const App = component(() =>
  Card({ title: '我的卡片' }, {
    default: () => div()('这是卡片内容'),
    footer: () => button()('确定'),
  })
);

作用域插槽:给插槽传数据

import { Dom, component } from 'nova-view';
const { div, span } = Dom;

type Item = { id: number; name: string; status: 'active' | 'inactive' };

const DataList = component<{ items: Item[] }>((props, { slots }) => {
  return div().class('data-list')(
    ...props.items.map(item =>
      slots.default?.(item) || div()(item.name)
    )
  );
});

const App = component(() => {
  const items: Item[] = [
    { id: 1, name: '项目1', status: 'active' },
    { id: 2, name: '项目2', status: 'inactive' },
  ];

  return DataList({ items }, {
    default: (item: Item) =>
      div().class(() => `item ${item.status}`)(
        span()(item.name),
        span().class('status')(item.status === 'active' ? '✓' : '✗'),
      ),
  });
});

自定义事件:emit + onXxx

import { Dom, component } from 'nova-view';
const { button } = Dom;

const CustomButton = component<{ variant?: 'primary' | 'secondary' }>((props, { emit }) => {
  return button()
    .class(() => `btn btn-${props.variant || 'primary'}`)
    .onclick(() => emit('click', { timestamp: Date.now() }))
    ('点击我');
});

const App = component(() =>
  CustomButton({ variant: 'primary' }, {
    onClick: (data) => console.log('按钮被点击', data),
  })
);

异步组件:直接 async 返回 Node

import { Dom, component } from 'nova-view';
const { div, h2, p } = Dom;

const AsyncData = component(async () => {
  const resp = await fetch('/api/data');
  const data = await resp.json();
  return div().class('data-view')(
    h2()('异步数据'),
    p()(data.message),
  );
});

注意:await 与生命周期/上下文的关系(务必看)
在当前实现下,async component 会在第一次 await 之后丢失“当前组件实例上下文”
这意味着:

  • await 之前调用 onMounted/onUpdated/onUnmounted/provide/inject 等“依赖当前实例”的 API 是可靠的
  • ⚠️ await 之后再调用这些 API,通常不会被注册到该组件实例上(表现为钩子不触发 / provide 不生效)

推荐写法:把钩子注册和 provide/inject 放在 await 之前;await 之后只做数据获取,然后更新 ref/reactive 来驱动 UI。


🧷 Hook 组件:把组件也做成链式 API

import { Dom, component, hook, mount } from 'nova-view';
const { div } = Dom;

const MyCard = component<{ title: string; active?: boolean }>((props) => {
  return div().class(() => `card ${props.active ? 'active' : ''}`)(
    div().class('title')(props.title),
  );
});

const Card = hook(MyCard);
mount(Card().title('Hello').active(true)(), document.body);

🧠 条件渲染 / 多分支 / 列表渲染(更完整示例)

Show:支持 keepAlive

import { Dom, ref, Show } from 'nova-view';
const { div } = Dom;

const isLoading = ref(true);

Show(() => isLoading.value, {
  when: () => div()('加载中...'),
  fallback: () => div()('内容'),
  keepAlive: true,
});

Switch:多分支更直观

import { Dom, ref, Switch } from 'nova-view';
const { div } = Dom;

const status = ref<'loading' | 'error' | 'success'>('loading');

Switch(() => status.value, [
  [() => status.value === 'loading', () => div()('加载中')],
  [() => status.value === 'error', () => div()('出错了!')],
  [true, () => div()('成功')],
]);

For:keyed 列表 + controller

import { Dom, ref, For, type ForController } from 'nova-view';
const { div } = Dom;

type Row = { id: number; name: string };
const items = ref<Row[]>([
  { id: 1, name: 'A' },
  { id: 2, name: 'B' },
]);

let controller: ForController<Row> | null = null;

For(() => items.value, {
  key: (it) => it.id,
  children: (it) => div().class('row')(it.name),
  controller: (ctrl) => { controller = ctrl; },
});

// controller?.updateItem(1, (it) => ({ ...it, name: 'A+' }));
// controller?.removeItem(2);
// controller?.replaceAll(newItems, { reuseDOM: true });

🧱 VirtualList:虚拟滚动(配置说明)

适合超大列表,只渲染可视区内容,保持滚动流畅。

核心属性:

  • items() => T[] 列表数据(必填)
  • height:容器高度(必填)
  • itemHeight:固定高度或 (item, index) => number
  • overscan:额外渲染的行数,减少闪烁
  • key:为每行生成 key
  • children:渲染函数(必填)
  • onReachEnd / threshold:触底加载

🛡️ ErrorBoundary:错误边界(配置说明)

捕获子组件树中的错误,避免整个应用崩溃。

核心属性:

  • children() => Node(必填)
  • fallback(error, reset) => Node(可选)
  • onError:错误上报回调(可选)

💡 最佳实践(更贴近真实项目)

  • 批量更新:高频更新时优先用 batch() 合并刷新
  • 列表性能:常规用 For;明确知道更新路径/追求极限时用 ForController
  • KeepAliveShow(..., { keepAlive: true }) 适合昂贵组件的频繁切换
  • 组织方式:把业务逻辑抽到组合函数(useXxx),UI 保持纯粹

🚚 迁移指南(从 React / Vue / 原生)

迁移的核心不是“语法替换”,而是心智转换:

  • 从 ReactuseState -> ref / reactiveuseEffect -> watchEffectref + useEffect 才能拿 DOM -> 直接返回 DOM
  • 从 Vueref/reactive/computed/watch 基本同构;模板 -> 链式 DSL
  • 从原生:命令式创建 -> 声明式组合;事件监听 -> .onclick/.oninput...

📖 文档与资源

🤝 贡献

欢迎提交 Issue 和 Pull Request!我们非常欢迎社区的贡献。

📄 许可证

MIT


Made with ❤️ by the Nova View team

官网 | npm | GitHub