@mzzsfy/dsh-toast 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MECHANISM.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-toast 装载机制原理
2
2
 
3
- 本文记录公共 client 依赖包(dsh-toast 为参考实现)不进 profile 表层 manifest 却能被安装、解析、装载的完整原理。规约条目见 AGENTS.md「公共 client 依赖包规约」与「profile 链接」第 4 条;本文是其推导与实证依据。
3
+ 本文记录公共 client 依赖包(dsh-toast 为参考实现)不进 profile 表层 manifest 却能被安装、解析、装载的完整原理。规约条目见 `DEVELOPMENT/client-dependency.md` 与 `DEVELOPMENT/profile-link.md` 第 4 条;本文是其推导与实证依据。
4
4
 
5
5
  ## 问题:市场为什么曾显示它
6
6
 
package/README.md CHANGED
@@ -63,3 +63,7 @@ toast('回合完成', { holdMs: 6 * 1000 }) // 自定义展示期
63
63
  - 样式全部取宿主 `--dsw-*` 令牌且带就近 fallback(宿主升级更名令牌时降级为可用默认形态,不静默失效);样式挂宿主文档级、幂等且内容变化原位替换,容器在场时随每次挂载自愈
64
64
  - store 位于模块闭包单例;渲染容器惰性自举、幂等、自愈(旧 root 卸载后重建),容器带代际标记防 HMR 两代互拆;不依赖宿主生命周期
65
65
  - react / react-dom/client 由宿主平台模块表提供,peerDependencies 声明 react 与 react-dom
66
+
67
+ ## dsh 版本兼容
68
+
69
+ 公共依赖包,由消费方(dsh-cron-board 等)代挂装载,三版本装载层均正常,无独立激活单元。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mzzsfy/dsh-toast",
3
3
  "description": "全局浮出通知 Toast 库:多条并存栈式展示,自动消失与常驻确认两种生命周期;普通 npm 依赖(非 dsh 插件),由消费插件的 cordis.patch.yml 代挂宿主占位条目",
4
- "version": "0.2.1",
4
+ "version": "0.2.3",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "exports": {
package/src/client.js CHANGED
@@ -120,15 +120,19 @@ window.__ModuleLoader__.load({
120
120
 
121
121
  // ---- 渲染:容器直挂 body,不依赖宿主生命周期 ----
122
122
 
123
- // 样式内容原位比对:一致跳过,不一致(HMR 新代 CSS 变化)原位替换
123
+ // 样式内容原位比对:一致跳过,不一致(HMR 新代 CSS 变化)原位替换;
124
+ // 在位节点无论新旧一律幂等补 data-plugin,防历史代残留缺标记被宿主 claimStyles 误归属
124
125
  function ensureStyle() {
125
126
  const stale = document.getElementById(STYLE_ID)
126
127
  if (stale !== null) {
127
128
  if (stale.textContent !== CSS) stale.textContent = CSS
129
+ stale.setAttribute('data-plugin', '@mzzsfy/dsh-toast')
128
130
  return
129
131
  }
130
132
  const style = document.createElement('style')
131
133
  style.id = STYLE_ID
134
+ // 自带 data-plugin:缺失时宿主 claimStyles 会把它归属给后续材质化插件,其 HMR 重建即误删
135
+ style.setAttribute('data-plugin', '@mzzsfy/dsh-toast')
132
136
  style.textContent = CSS
133
137
  document.head.appendChild(style)
134
138
  }
@@ -0,0 +1,51 @@
1
+ // 公共依赖包形态守卫:本包不声明 dsh.bundle.patch、不自带 cordis.patch.yml、
2
+ // 不进 profile 表层 manifest 依赖行与装载层。表层 dependencies 行是插件市场
3
+ // 「已安装」列表的唯一数据源(全量展示,仅过滤官方内盒),本包一旦被显式
4
+ // add 写入表层行,市场即把它当插件展示——该现象曾出现并已修复(表层数行
5
+ // 由 dev-link 对公共依赖包一律删除),此测试把约束固化为机器断言防回归。
6
+ // 规约见 DEVELOPMENT/client-dependency.md 与 DEVELOPMENT/profile-link.md 第 4 条,
7
+ // 机制推导见本包 MECHANISM.md。
8
+ import test from 'node:test'
9
+ import assert from 'node:assert/strict'
10
+ import { existsSync, readFileSync } from 'node:fs'
11
+ import { fileURLToPath } from 'node:url'
12
+ import { dirname, join } from 'node:path'
13
+ import { homedir } from 'node:os'
14
+
15
+ const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
16
+ const PKG_NAME = '@mzzsfy/dsh-toast'
17
+ // profile 路径与 scripts/dev-link.mjs 同构;不存在(如 CI)时终态用例跳过
18
+ const PROFILE_MANIFEST = join(homedir(), '.dsh', 'profiles', 'web', 'package.json')
19
+
20
+ test('package.json 不声明 dsh.bundle.patch,维持公共依赖包身份', () => {
21
+ const manifest = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8'))
22
+ assert.equal(
23
+ manifest.dsh?.bundle?.patch, undefined,
24
+ '声明 dsh.bundle.patch 即成为插件包:官方 reconcile 会将其入 dsh.profile.bundles,dev-link 也失去删表层行的依据',
25
+ )
26
+ })
27
+
28
+ test('不自带 cordis.patch.yml,装载靠消费方代挂占位', () => {
29
+ assert.equal(
30
+ existsSync(join(PKG_ROOT, 'cordis.patch.yml')), false,
31
+ '公共依赖包禁止自带 patch;client 进入模块表由消费插件 cordis.patch.yml 代挂占位条目承载',
32
+ )
33
+ })
34
+
35
+ test('profile 表层 dependencies 无本包行,市场已装列表不可见', (t) => {
36
+ if (!existsSync(PROFILE_MANIFEST)) return t.skip('本机无 profile,终态由 dev-link 维护')
37
+ const manifest = JSON.parse(readFileSync(PROFILE_MANIFEST, 'utf8').replace(/^\uFEFF/, ''))
38
+ assert.equal(
39
+ manifest.dependencies?.[PKG_NAME], undefined,
40
+ '表层依赖行存在即市场已装列表展示本包;安装由消费方 dependencies 声明承载,表层行一律删除',
41
+ )
42
+ })
43
+
44
+ test('profile dsh.profile.bundles 无本包,不进宿主装载层', (t) => {
45
+ if (!existsSync(PROFILE_MANIFEST)) return t.skip('本机无 profile,终态由 dev-link 维护')
46
+ const manifest = JSON.parse(readFileSync(PROFILE_MANIFEST, 'utf8').replace(/^\uFEFF/, ''))
47
+ assert.equal(
48
+ manifest.dsh?.profile?.bundles?.includes(PKG_NAME), false,
49
+ '公共依赖包无宿主半区可载,入装载层即「装而不载」的伪插件形态',
50
+ )
51
+ })
@@ -22,7 +22,7 @@ function makeState() {
22
22
  function makeDocument(state) {
23
23
  const byId = state.byId
24
24
  const createElement = (tag) => {
25
- const node = { tag, id: '', style: {}, writes: 0, remove() { byId.delete(this.id) } }
25
+ const node = { tag, id: '', style: {}, writes: 0, attrs: {}, setAttribute(name, value) { this.attrs[name] = value }, remove() { byId.delete(this.id) } }
26
26
  Object.defineProperty(node, 'textContent', {
27
27
  get() { return node._text || '' },
28
28
  set(value) { node._text = value; node.writes += 1 },
@@ -94,6 +94,8 @@ test('首次 show 惰性挂载:容器挂 body、样式挂 head', () => {
94
94
  assert.notEqual(state.byId.get(HOST_ID), undefined)
95
95
  assert.equal(state.headChildren.some((node) => node.id === STYLE_ID), true, '样式挂宿主文档级')
96
96
  assert.equal(state.bodyChildren.some((node) => node.id === HOST_ID), true, '容器直挂 body')
97
+ const styleNode = state.headChildren.find((node) => node.id === STYLE_ID)
98
+ assert.equal(styleNode.attrs['data-plugin'], '@mzzsfy/dsh-toast', '样式自带 data-plugin,防宿主 claimStyles 误归属后随他插件 HMR 整批误删')
97
99
  })
98
100
 
99
101
  test('重复 show 幂等:容器只创建一次,样式一致零写入', () => {
@@ -260,10 +262,13 @@ test('样式内容不一致原位替换:节点不重建,内容与写入计数还
260
262
  // 模拟 HMR 期间 CSS 变更或旧代残留:在位样式内容与本代不一致
261
263
  // (直写 _text 绕过写入计数,只统计实现侧的写入)
262
264
  styleNode._text = '/* 残留的旧代样式 */'
265
+ // 模拟修复前旧代注入的无标记残留:原位替换须幂等补标记
266
+ delete styleNode.attrs['data-plugin']
263
267
  mod.show('触发替换')
264
268
  assert.equal(state.byId.get(STYLE_ID), styleNode, '样式节点原位保留')
265
269
  assert.equal(styleNode.writes, 2, '替换恰新增一次写入')
266
270
  assert.equal(styleNode.textContent, currentCss, '内容还原为当前 CSS')
271
+ assert.equal(styleNode.attrs['data-plugin'], '@mzzsfy/dsh-toast', '原位替换路径幂等补 data-plugin')
267
272
  })
268
273
 
269
274
  test('CSS 内容齐备:选择器、变体、动画与减弱动态块', () => {