New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

vanjs-router

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

vanjs-router - npm Package Compare versions

Comparing version
2.0.8
to
2.1.0
+53
log.md
## Vanjs Router 的异常情况(2024 年 11 月 4 日)
Vanjs Router 的基本原理是创建一个 `now` 状态,通过监听 Window 的 `hashchange` 事件来更新 `now` 的值。每个路由内部通过 `van.derive(() => now.val)` 来监听路由的改变,而不是直接使用 `hashchange`。此外,`onLoad` 和 `onFirst` 也是在 `van.derive` 内部被执行的。接下来我们看下面的代码:
```ts
const count = van.state(0)
// 可以监听到 count 值的变化
van.derive(() => {
return count.val * 2
})
// 可以监听到 count 值的变化
van.derive(() => {
count.val
setTimeout(() => {
count.val * 2
}, 1000)
})
// 可以监听到 count 值的变化
van.derive(async () => {
count.val // 此处在同步上下文中保留了一个引用
await new Promise((resolve) => setTimeout(resolve, 1000))
return count.val * 2 // 该处不属于同步上下文,所以是无效引用,但由于有上面的引用,这个 derive 可以监听
})
// 无法监听到 count 值的变化
van.derive(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return count.val * 2 // 该处不属于同步上下文,且同步上下文中不存在状态值的引用,因此无法监听。
})
const func = async () => {
return count.val * 2
}
van.derive(async () => {
return await func() // 可以监听到,因为 func 的同步上下文也是 derive 回调函数的同步上下文,因此可以被监听。
})
```
可以看到,为了能监听到 `derive` 回调函数中的状态值变化,就必须让状态值位于回调函数的同步上下文中。
如果 `onLoad` 和 `onFirst` 的同步上下文中存在某些状态值的引用,当这些值被异步更新时,就会导致与 `onLoad` 和 `onFirst` 处于同一同步上下文的 `now` 值监听函数被触发,进而导致 `onLoad` 和 `onFirst` 被重复触发的 Bug。
因为函数的同步上下文会继承外部的同步上下文,而在继承上下文中的多个作用域中的状态值,都会被 `derive` 监听到。我们要解决的是确保 `onLoad` 和 `onFirst` 的同步上下文中存在的状态值引用不会触发外部的 `derive`,从而导致 `onLoad` 和 `onFirst` 被重复触发。
我们监听 `now` 的目的是实现对 `hashchange` 监听器的动态更新。只需创建一个全局的 `hashchange` 监听器,各个路由通过 `derive` 来共享 `hashchange` 的动态更新通知。期望 `derive` 的监听效果比 `hashchange` 更好,操作也更加便捷,且具备更好的可拓展性,比如可以将 `derive` 的返回值作为新的状态用于其他开发场景。
然而,我们要注意的是,写这样的 `derive` 来监听 `now` 的主要目的实际上就是监听 `hashchange`。创建多个 `hashchange` 监听器并不会对性能产生明显影响。如果确实需要用到 `Van State` 来监听路由变化,其实我们已经有 `now`,并且它被全局的 `hashchange` 自动更新。这个 `now` 本身就可以作为拓展性的接口状态值按需使用,而各个路由需要监听路由变化时,最好使用正常的独立 `hashchange` 监听器。这样可以实现最纯粹的路由变化监听,而无需担心 `derive` 在监听 `now` 的同时带来的数据和事件异常情况。
+1
-1

@@ -1,1 +0,1 @@

var B=Object.defineProperty;var D=(t,e,s)=>e in t?B(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var o=(t,e,s)=>D(t,typeof e!="symbol"?e+"":e,s);let d=Object.getPrototypeOf,p,g,a,h,x={isConnected:1},J=1e3,v,A={},Q=d(x),H=d(d),c,M=(t,e,s,r)=>(t??(setTimeout(s,r),new Set)).add(e),W=(t,e,s)=>{let r=a;a=e;try{return t(s)}catch(n){return console.error(n),s}finally{a=r}},y=t=>t.filter(e=>{var s;return(s=e._dom)==null?void 0:s.isConnected}),k=t=>v=M(v,t,()=>{for(let e of v)e._bindings=y(e._bindings),e._listeners=y(e._listeners);v=c},J),b={get val(){var t;return(t=a==null?void 0:a._getters)==null||t.add(this),this.rawVal},get oldVal(){var t;return(t=a==null?void 0:a._getters)==null||t.add(this),this._oldVal},set val(t){var e;(e=a==null?void 0:a._setters)==null||e.add(this),t!==this.rawVal&&(this.rawVal=t,this._bindings.length+this._listeners.length?(g==null||g.add(this),p=M(p,this,U)):this._oldVal=t)}},G=t=>({__proto__:b,rawVal:t,_oldVal:t,_bindings:[],_listeners:[]}),m=(t,e)=>{let s={_getters:new Set,_setters:new Set},r={f:t},n=h;h=[];let l=W(t,s,e);l=(l??document).nodeType?l:new Text(l);for(let i of s._getters)s._setters.has(i)||(k(i),i._bindings.push(r));for(let i of h)i._dom=l;return h=n,r._dom=l},V=(t,e=G(),s)=>{let r={_getters:new Set,_setters:new Set},n={f:t,s:e};n._dom=s??(h==null?void 0:h.push(n))??x,e.val=W(t,r,e.rawVal);for(let l of r._getters)r._setters.has(l)||(k(l),l._listeners.push(n));return e},I=(t,...e)=>{for(let s of e.flat(1/0)){let r=d(s??0),n=r===b?m(()=>s.val):r===H?m(s):s;n!=c&&t.append(n)}return t},R=(t,e,...s)=>{var i;let[r,...n]=d(s[0]??0)===Q?s:[{},...s],l=t?document.createElementNS(t,e):document.createElement(e);for(let[f,_]of Object.entries(r)){let E=w=>w?Object.getOwnPropertyDescriptor(w,f)??E(d(w)):c,P=e+","+f,C=A[P]??(A[P]=((i=E(d(l)))==null?void 0:i.set)??0),F=f.startsWith("on")?(w,z)=>{let j=f.slice(2);l.removeEventListener(j,z),l.addEventListener(j,w)}:C?C.bind(l):l.setAttribute.bind(l,f),L=d(_??0);f.startsWith("on")||L===H&&(_=V(_),L=b),L===b?m(()=>(F(_.val,_._oldVal),l)):F(_)}return I(l,n)},T=t=>({get:(e,s)=>R.bind(c,t,s)}),K=(t,e)=>e?e!==t&&t.replaceWith(e):t.remove(),U=()=>{let t=0,e=[...p].filter(r=>r.rawVal!==r._oldVal);do{g=new Set;for(let r of new Set(e.flatMap(n=>n._listeners=y(n._listeners))))V(r.f,r.s,r._dom),r._dom=c}while(++t<100&&(e=[...g]).length);let s=[...p].filter(r=>r.rawVal!==r._oldVal);p=c;for(let r of new Set(s.flatMap(n=>n._bindings=y(n._bindings))))K(r._dom,m(r.f,r._dom)),r._dom=c;for(let r of s)r._oldVal=r.rawVal};const S={tags:new Proxy(t=>new Proxy(R,T(t)),T()),hydrate:(t,e)=>K(t,m(e,t)),add:I,state:G,derive:V},O=()=>location.hash?location.hash.slice(2):"home",u=S.state(O());window.addEventListener("hashchange",t=>{u.val=O()});class N{constructor(e){o(this,"rule");o(this,"args",[]);o(this,"Loader");o(this,"delayed",!1);o(this,"onFirst");o(this,"onLoad");o(this,"element");o(this,"isFirstLoad",!0);if(!e)throw new Error("config 不能为空");if(!e.rule)throw new Error("rule 不能为空");if(!e.Loader)throw new Error("Loader 不能为空");this.rule=e.rule,this.Loader=e.Loader,this.delayed=e.delayed||!1,this.onFirst=e.onFirst||(async()=>{}),this.onLoad=e.onLoad||(async()=>{}),this.element=this.Loader(),this.element.hidden=!0,S.derive(()=>{const s=this.matchHash();s?(async()=>(this.args.splice(0),this.args.push(...s.args),this.isFirstLoad&&(this.isFirstLoad=!1,await this.onFirst()),await this.onLoad(),this.delayed||this.show()))():this.hide()})}matchHash(){if(this.rule instanceof RegExp){const s=u.val.match(this.rule);return s?{hash:u.val,args:[...s].slice(1)}:!1}const e=u.val.split("/").filter(s=>s.length>0);return e.length<1&&e.push("home"),e[0]==this.rule?{hash:u.val,args:e.slice(1)}:!1}show(){this.element.hidden=!1}hide(){this.element.hidden=!0}}const $=t=>new N(t).element,q=(t,...e)=>{location.hash=t=="home"&&e.length==0?"":`/${[t,...e].join("/")}`},X=(t,e)=>{$({rule:t,Loader:S.tags.div,onLoad(){q(e)}})},Y={nowHash:O,now:u,Handler:N,Route:$,goto:q,redirect:X};Object.defineProperty(window,"router",{value:Y});
var B=Object.defineProperty;var D=(t,e,s)=>e in t?B(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var o=(t,e,s)=>D(t,typeof e!="symbol"?e+"":e,s);let d=Object.getPrototypeOf,p,g,a,h,T={isConnected:1},J=1e3,v,j={},Q=d(T),x=d(d),c,H=(t,e,s,r)=>(t??(setTimeout(s,r),new Set)).add(e),M=(t,e,s)=>{let r=a;a=e;try{return t(s)}catch(n){return console.error(n),s}finally{a=r}},y=t=>t.filter(e=>{var s;return(s=e._dom)==null?void 0:s.isConnected}),W=t=>v=H(v,t,()=>{for(let e of v)e._bindings=y(e._bindings),e._listeners=y(e._listeners);v=c},J),b={get val(){var t;return(t=a==null?void 0:a._getters)==null||t.add(this),this.rawVal},get oldVal(){var t;return(t=a==null?void 0:a._getters)==null||t.add(this),this._oldVal},set val(t){var e;(e=a==null?void 0:a._setters)==null||e.add(this),t!==this.rawVal&&(this.rawVal=t,this._bindings.length+this._listeners.length?(g==null||g.add(this),p=H(p,this,U)):this._oldVal=t)}},k=t=>({__proto__:b,rawVal:t,_oldVal:t,_bindings:[],_listeners:[]}),m=(t,e)=>{let s={_getters:new Set,_setters:new Set},r={f:t},n=h;h=[];let l=M(t,s,e);l=(l??document).nodeType?l:new Text(l);for(let i of s._getters)s._setters.has(i)||(W(i),i._bindings.push(r));for(let i of h)i._dom=l;return h=n,r._dom=l},V=(t,e=k(),s)=>{let r={_getters:new Set,_setters:new Set},n={f:t,s:e};n._dom=s??(h==null?void 0:h.push(n))??T,e.val=M(t,r,e.rawVal);for(let l of r._getters)r._setters.has(l)||(W(l),l._listeners.push(n));return e},G=(t,...e)=>{for(let s of e.flat(1/0)){let r=d(s??0),n=r===b?m(()=>s.val):r===x?m(s):s;n!=c&&t.append(n)}return t},I=(t,e,...s)=>{var i;let[r,...n]=d(s[0]??0)===Q?s:[{},...s],l=t?document.createElementNS(t,e):document.createElement(e);for(let[_,f]of Object.entries(r)){let E=w=>w?Object.getOwnPropertyDescriptor(w,_)??E(d(w)):c,O=e+","+_,P=j[O]??(j[O]=((i=E(d(l)))==null?void 0:i.set)??0),C=_.startsWith("on")?(w,z)=>{let F=_.slice(2);l.removeEventListener(F,z),l.addEventListener(F,w)}:P?P.bind(l):l.setAttribute.bind(l,_),L=d(f??0);_.startsWith("on")||L===x&&(f=V(f),L=b),L===b?m(()=>(C(f.val,f._oldVal),l)):C(f)}return G(l,n)},A=t=>({get:(e,s)=>I.bind(c,t,s)}),R=(t,e)=>e?e!==t&&t.replaceWith(e):t.remove(),U=()=>{let t=0,e=[...p].filter(r=>r.rawVal!==r._oldVal);do{g=new Set;for(let r of new Set(e.flatMap(n=>n._listeners=y(n._listeners))))V(r.f,r.s,r._dom),r._dom=c}while(++t<100&&(e=[...g]).length);let s=[...p].filter(r=>r.rawVal!==r._oldVal);p=c;for(let r of new Set(s.flatMap(n=>n._bindings=y(n._bindings))))R(r._dom,m(r.f,r._dom)),r._dom=c;for(let r of s)r._oldVal=r.rawVal};const K={tags:new Proxy(t=>new Proxy(I,A(t)),A()),hydrate:(t,e)=>R(t,m(e,t)),add:G,state:k,derive:V},S=()=>location.hash?location.hash.slice(2):"home",u=K.state(S());window.addEventListener("hashchange",()=>{u.val=S()});class N{constructor(e){o(this,"rule");o(this,"args",[]);o(this,"Loader");o(this,"delayed",!1);o(this,"onFirst");o(this,"onLoad");o(this,"element");o(this,"isFirstLoad",!0);if(!e)throw new Error("config 不能为空");if(!e.rule)throw new Error("rule 不能为空");if(!e.Loader)throw new Error("Loader 不能为空");this.rule=e.rule,this.Loader=e.Loader,this.delayed=e.delayed||!1,this.onFirst=e.onFirst||(async()=>{}),this.onLoad=e.onLoad||(async()=>{}),this.element=this.Loader(),this.element.hidden=!0;const s=async()=>{const r=this.matchHash();r?(this.args.splice(0),this.args.push(...r.args),this.isFirstLoad&&(this.isFirstLoad=!1,await this.onFirst()),await this.onLoad(),this.delayed||this.show()):this.hide()};window.addEventListener("hashchange",s),s()}matchHash(){if(this.rule instanceof RegExp){const s=u.val.match(this.rule);return s?{hash:u.val,args:[...s].slice(1)}:!1}const e=u.val.split("/").filter(s=>s.length>0);return e.length<1&&e.push("home"),e[0]==this.rule?{hash:u.val,args:e.slice(1)}:!1}show(){this.element.hidden=!1}hide(){this.element.hidden=!0}}const $=t=>new N(t).element,q=(t,...e)=>{location.hash=t=="home"&&e.length==0?"":`/${[t,...e].join("/")}`},X=(t,e)=>{$({rule:t,Loader:K.tags.div,onLoad(){q(e)}})},Y={nowHash:S,now:u,Handler:N,Route:$,goto:q,redirect:X};Object.defineProperty(window,"router",{value:Y});

@@ -6,3 +6,3 @@ import van from 'vanjs-core';

export const now = van.state(nowHash());
window.addEventListener('hashchange', event => {
window.addEventListener('hashchange', () => {
now.val = nowHash();

@@ -40,6 +40,5 @@ });

// 根据 Hash 的变化,自动更新路由状态
van.derive(() => {
const func = async () => {
// 获取当前路由的命中状态
const match = this.matchHash();
const hidden = !match;
if (!match) {

@@ -51,17 +50,16 @@ // 未被命中,刷新路由,页面隐藏。

// 路由命中
const func = async () => {
// 将接收到的路由参数保存起来
this.args.splice(0);
this.args.push(...match.args);
if (this.isFirstLoad) {
this.isFirstLoad = false;
await this.onFirst();
}
await this.onLoad();
if (!this.delayed)
this.show();
};
func();
// 将接收到的路由参数保存起来
this.args.splice(0); // 清空存储的旧参数
this.args.push(...match.args);
if (this.isFirstLoad) {
this.isFirstLoad = false;
await this.onFirst();
}
await this.onLoad();
if (!this.delayed)
this.show();
}
});
};
window.addEventListener('hashchange', func);
func();
}

@@ -68,0 +66,0 @@ /** 判断当前 Hash 是否与本路由的规则匹配 */

{
"devDependencies": {
"typescript": "^5.5.4",
"vite": "^5.3.5"
},
"dependencies": {
"vanjs-core": "^1.5.1"
},
"type": "module",
"name": "vanjs-router",
"version": "2.0.8",
"main": "js/router.js",
"types": "src/router.ts",
"scripts": {
"build": "vite build && tsc"
}
"devDependencies": {
"typescript": "^5.5.4",
"vite": "^5.3.5"
},
"dependencies": {
"vanjs-core": "^1.5.1"
},
"scripts": {
"build": "vite build && tsc"
},
"type": "module",
"name": "vanjs-router",
"version": "2.1.0",
"main": "js/router.js",
"types": "src/router.ts"
}

@@ -22,3 +22,4 @@ # vanjs-router

```html
<script src="https://cdn.jsdelivr.net/npm/vanjs-router@latest/dist/vanjs-router.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.2.nomodule.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vanjs-router@latest/dist/vanjs-router.min.js"></script>
<script>

@@ -37,6 +38,6 @@ const { Route, goto } = router;

},
onFirst() {
async onFirst() {
console.log("home onfirst");
},
onLoad() {
async onLoad() {
console.log("home onload");

@@ -56,3 +57,3 @@ },

},
onLoad() {
async onLoad() {
this.show();

@@ -59,0 +60,0 @@ },

@@ -9,3 +9,3 @@ import van, { State } from 'vanjs-core'

window.addEventListener('hashchange', event => {
window.addEventListener('hashchange', () => {
now.val = nowHash()

@@ -65,6 +65,5 @@ })

// 根据 Hash 的变化,自动更新路由状态
van.derive(() => {
const func = async () => {
// 获取当前路由的命中状态
const match = this.matchHash()
const hidden = !match
if (!match) {

@@ -75,16 +74,15 @@ // 未被命中,刷新路由,页面隐藏。

// 路由命中
const func = async () => {
// 将接收到的路由参数保存起来
this.args.splice(0)
this.args.push(...match.args)
if (this.isFirstLoad) {
this.isFirstLoad = false
await this.onFirst()
}
await this.onLoad()
if (!this.delayed) this.show()
// 将接收到的路由参数保存起来
this.args.splice(0) // 清空存储的旧参数
this.args.push(...match.args)
if (this.isFirstLoad) {
this.isFirstLoad = false
await this.onFirst()
}
func()
await this.onLoad()
if (!this.delayed) this.show()
}
})
}
window.addEventListener('hashchange', func)
func()
}

@@ -91,0 +89,0 @@