@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 +1 -1
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/client.js +5 -1
- package/test/manifest-form.test.mjs +51 -0
- package/test/mount.test.mjs +6 -1
package/MECHANISM.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-toast 装载机制原理
|
|
2
2
|
|
|
3
|
-
本文记录公共 client 依赖包(dsh-toast 为参考实现)不进 profile 表层 manifest 却能被安装、解析、装载的完整原理。规约条目见
|
|
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
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
|
+
})
|
package/test/mount.test.mjs
CHANGED
|
@@ -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 内容齐备:选择器、变体、动画与减弱动态块', () => {
|