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

ns-framework

v1.0.8

Published

A lightweight Vue-like micro framework for Chrome extensions and browser apps

Readme

NS Framework

Language: English | Tiếng Việt | 日本語 | 中文

NS Framework 是一个专为 Chrome 扩展打造的超轻量迷你框架,语法接近 Vue,但完全不需要构建工具。

主要特性

  • ⚡ 无外部依赖
  • 🔄 基于 Proxy 的实时绑定
  • 🛡️ 兼容 Manifest V3 / CSP
  • 🧩 通过 ES Module 支持组件、props、插槽(默认 + 具名)
  • 🔒 每个组件独立的作用域隔离
  • 🧮 computed 计算属性(缓存、自动失效、支持 setter)
  • 👀 watch、接近 Vue 的生命周期钩子
  • 🧠 大量接近 Vue 语法的指令

安装

npm

npm i ns-framework

原生 JS

lib/ns.js 复制到扩展的 lib 目录,然后在 HTML 中加载:

<script src="./lib/ns.js"></script>

基本用法

<div id="app"></div>
<script src="./lib/ns.js"></script>
<script src="./popup.js"></script>
// popup.js
new NS({
  el: "#app",
  data: {
    title: "Hello NS",
    count: 0,
    isVisible: true
  },
  methods: {
    increment() {
      this.count += 1;
    }
  },
  mounted() {
    console.log("Ready");
  },
  template: `
    <div>
      <h1 ns-text="title"></h1>
      <p>Count: <span ns-text="count"></span></p>
      <button ns-click="increment">Increment</button>
      <div ns-show="isVisible">Visible block</div>
    </div>
  `
});

支持的指令

Text / HTML

<span ns-text="name"></span>
<p ns-html="message"></p>

指令中的表达式只是一条(可嵌套的)属性路径,而不是任意 JS 表达式:

<span ns-text="user.profile.name"></span>
<div ns-if="user?.profile?.name"></div> <!-- 支持可选链 "?.",效果等同于 "." -->

ns-text / ns-if / ns-show不能在模板里直接运行三元表达式或比较运算,比如 a ? b : ccount > 5 - 请用 computed 预先算好结果,再绑定到对应的属性上。

Props 与组件

⚠️ 重要提示:HTML 解析时会将属性名统一转为小写(不仅是 .html 文件,.js 文件里通过 innerHTML 赋值的 template 字符串同样受影响)。在 props 中以驼峰命名(camelCase)声明的多单词 prop,在模板中用 : 绑定时必须写成 kebab-case(短横线分隔),否则属性名会丢失大小写信息,无法与 prop 匹配上。

<ns-header title="WebBlock | MLight"></ns-header>
<ns-footer :app-config-info="appConfigInfo"></ns-footer>
// footer.js
export default {
  props: {
    title: {
      type: String,      // "type" 仅作文档说明用,不会被校验或做类型转换
      default: "MLight"  // 父组件未传值时会使用 "default"
    },
    appConfigInfo: {
      type: Object
    }
  },
  template: `
    <div>
      <h1 ns-text="title"></h1>
    </div>
  `
};

props 也可以声明为一个名字数组(无需 default/type):props: ["title", "appConfigInfo"]

对象/数组类型的 prop 与父组件共享同一引用:如果你直接修改(mutate)对象内部的字段(this.appConfigInfo.author.name = "x"),改动会自动传播并在子组件中正确重新渲染(包括依赖该字段的 computed)。但如果父组件整体重新赋值这个 prop(用一个全新对象赋值 this.appConfigInfo = {...},或修改 string/number/boolean 类型的 prop),子组件不会自动重新同步 - props 目前只在挂载时读取一次。

Computed

computed: {
  // 简单 getter 形式 - 有缓存,依赖项变化时自动重新计算
  // (包括嵌套路径,以及从父组件按引用共享的 prop)
  isTextEmpty() {
    return !this.input.text.value;
  },
  // 带 setter 的形式 - 给 this.fullName 赋值会调用 set()
  fullName: {
    get() {
      return `${this.first} ${this.last}`;
    },
    set(value) {
      const [first, last] = value.split(" ");
      this.first = first;
      this.last = last;
    }
  }
}
<div ns-show="isTextEmpty">为空</div>
<span ns-text="fullName"></span>

Watch

watch: {
  // 当 "count" 属性变化时调用
  count(newValue, oldValue) {
    console.log(newValue, oldValue);
  },
  // "*" 会捕获所有属性变化(顶层与嵌套均可)
  "*": (newValue, oldValue) => {
    console.log("changed:", newValue, oldValue);
  }
}

插槽(默认 + 具名)

// 子组件
template: `
  <div class="input-group">
    <slot name="before"></slot>
    <slot></slot>
    <slot name="after"></slot>
  </div>
`
<!-- 父组件 -->
<my-input>
  <i slot="before" class="fa fa-search"></i>
  <span>默认内容</span>
  <button slot="after">Clear</button>
</my-input>

如果希望插槽出口是普通元素而不是 <slot> 标签,可以用 ns-slot="before" 代替 <slot name="before">

组件的 emit 与自定义事件

// 子组件
methods: {
  save() {
    this.$emit("saved", { ok: true });
  }
}
<!-- 父组件:用 @event="handler" 绑定(推荐) -->
<child-component @saved="onSaved"></child-component>
// 父组件
methods: {
  onSaved(payload) {
    console.log(payload);
  }
}

如果不绑定 @event,父组件上与事件同名的方法(saved())仍会自动被调用(向后兼容)- 但建议使用 @event="handler" 来指定一个不同的方法名,避免多个实例 emit 同一个事件名时互相冲突。

组件上的 v-model(双向绑定)

<child-component v-model="value"></child-component>

子组件通过 modelValue 这个 prop 接收值,并通过 this.$emit("update:modelValue", newValue) 把变化传回父组件。

双向绑定

<input ns-model="name" />
<textarea ns-model="description"></textarea>

Model 修饰符

<input ns-model="name.trim" />
<input ns-model="age.number" />
<input ns-model="search.lazy" />
<input ns-model="query.debounce300" />
<input ns-model="title.capitalize" />
<input ns-model="email.lowercase" />

支持的元素:

  • checkbox
  • radio
  • select
  • select multiple

事件

<button ns-click="save"></button>
<button ns-click="save.prevent.stop"></button>
<button @click="save"></button>
<div ns-on="input:updateValue"></div>
<div @input="updateValue"></div>

条件渲染

<div ns-if="isLoggedIn">已登录</div>
<div ns-else-if="isLoading">加载中...</div>
<div ns-else>未登录</div>

列表渲染

同时支持数组和普通对象(item/key),带不带 Vue 风格的括号都可以:

<ul>
  <li ns-for="item in items" ns-text="item.name"></li>
  <li ns-for="(item, index) in items" ns-text="item.name"></li>
  <li ns-for="(value, key) in someObject" ns-text="key"></li>
</ul>

Class / Style / 属性绑定

<div ns-class="className"></div>
<div ns-class="{ active: isActive, disabled: isDisabled }"></div>
<div ns-style="styleObject"></div>
<a ns-bind="href:linkUrl"></a>
<a :href="linkUrl"></a>

配置项

| 选项 | 说明 | | --- | --- | | el | 选择器或 DOM 元素 | | data | 对象或函数(组件推荐使用函数,避免多个实例共享状态) | | template | HTML 字符串,会覆盖 el 的内容 | | methods | 包含方法的对象,方法内的 this 指向响应式数据 | | computed | getter(或 {get, set})组成的对象,带缓存并自动失效 | | watch | 监听属性变化的对象(按 key"*") | | components | 子组件注册表({ "tag-name": ComponentConfig }) | | props | 组件的 prop 声明(名字数组,或带 type/default 的对象) | | stateChanged | (prop, value) => {},每当某个属性的值发生变化时调用 | | beforeCreate / created | 响应式数据初始化前/后触发的钩子 | | beforeMount / mounted | 首次渲染并挂载 DOM 前/后触发的钩子 | | beforeUpdate / updated | 每次因状态变化而重新渲染前/后触发的钩子 |

生命周期顺序

beforeCreate → (创建响应式 Proxy) → created → (绑定 methods)
  → (加载模板、渲染插槽、挂载子组件、首次渲染、绑定事件)
  → beforeMount → mounted

每次属性变化时:beforeUpdate → (整个组件子树重新渲染) → updated → stateChanged

组件示例

// Header.js
export default {
  template: `
    <header>
      <h1 ns-text="title"></h1>
    </header>
  `,
  data() {
    return {
      title: "Header Component"
    };
  }
};
import Header from "./Header.js";

new NS({
  el: "#app",
  components: {
    "app-header": Header
  }
});
<div id="app">
  <app-header></app-header>
</div>
<script type="module" src="./main.js"></script>

说明

  • 使用 ES Module 时,请通过本地服务器加载,或以已解压的扩展方式加载。
  • 在 Chrome 扩展中,逻辑代码应放在独立的 JS 文件中,而不是内联脚本。
  • 自定义元素(<my-component>)在 HTML 中不能用 <my-component /> 自闭合(HTML 不会尊重普通元素上的自闭合写法)- 请始终显式写成 <my-component></my-component>
  • ns-for 上的 :key 只是一个普通的 HTML 属性,框架目前并未用它来做 DOM 差异对比/复用 - 每次渲染时整个列表都会从头重建。