@yaoxiu/marketing-dsl 1.0.0
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/README.md +111 -0
- package/dist/index.cjs +1519 -0
- package/dist/index.d.cts +430 -0
- package/dist/index.d.ts +430 -0
- package/dist/index.js +1496 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @yaoxiu/marketing-dsl
|
|
2
|
+
|
|
3
|
+
营销弹窗 DSL 的解释器核心。运营用一段 JSON 描述弹窗 / Banner / 公告条的排版和交互,这个包负责把它解释成可渲染的数据。
|
|
4
|
+
|
|
5
|
+
**纯逻辑,不依赖任何框架,也不碰 DOM。** 换 Vue3 / React 时这个包一行都不用改。
|
|
6
|
+
|
|
7
|
+
## 为什么要有这个包
|
|
8
|
+
|
|
9
|
+
运营发活动时弹窗形态千奇百怪,最初的方案是让他们写 JS。这条路走不通——配置一旦变成可执行代码,就等于把 XSS 入口开在了自己家里。
|
|
10
|
+
|
|
11
|
+
所以改成 DSL:**排版是数据,不是逻辑**。表达式求值器是手写的词法 + 递归下降,绝不使用 `eval` / `new Function`;样式、链接协议、数据源名、可调用方法全部走白名单。
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @yaoxiu/marketing-dsl
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
渲染还需要对应框架的壳:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @yaoxiu/marketing-dsl-vue2
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 两种用法
|
|
26
|
+
|
|
27
|
+
### 1. 校验配置
|
|
28
|
+
|
|
29
|
+
编辑器和后端保存前都该跑一遍。这个包不依赖 DOM,Node 里可以直接用。
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { validate, formatIssues } from '@yaoxiu/marketing-dsl';
|
|
33
|
+
|
|
34
|
+
const result = validate(dsl);
|
|
35
|
+
if (!result.valid) {
|
|
36
|
+
throw new Error(formatIssues(result.errors));
|
|
37
|
+
}
|
|
38
|
+
// warnings 不阻断保存,但该提示运营,比如样式属性不在白名单里会被忽略
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. 渲染
|
|
42
|
+
|
|
43
|
+
一般不直接用,交给框架壳。自己接的话是这样:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { createRuntime } from '@yaoxiu/marketing-dsl';
|
|
47
|
+
|
|
48
|
+
const runtime = createRuntime(dsl, {
|
|
49
|
+
user: { shopName: '麦爆了旗舰店', version: '专业版' },
|
|
50
|
+
sources: { listRenewTiers: (params) => api.getTiers(params) },
|
|
51
|
+
handlers: { setNeverRemind: () => localStorage.setItem('never', '1') },
|
|
52
|
+
emit: (event, payload) => console.log(event, payload),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const unsubscribe = runtime.subscribe(() => {
|
|
56
|
+
render(runtime.getTree()); // 每次通知后重新取树画一遍
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// 卸载时必须调,否则倒计时定时器不会停
|
|
60
|
+
runtime.destroy();
|
|
61
|
+
unsubscribe();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 渲染树
|
|
65
|
+
|
|
66
|
+
`getTree()` 返回的是纯数据,所有解释工作都已经做完:插值算完了、条件判断过了、循环展开了、样式转成 CSS 了、坐标换算好了、动作绑好了。
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
{
|
|
70
|
+
ready: true, // 数据源没回来时是 false,什么都别画
|
|
71
|
+
hasPopup: true,
|
|
72
|
+
rootStyle: { ... },
|
|
73
|
+
layers: [{
|
|
74
|
+
name: 'main',
|
|
75
|
+
isTop: true,
|
|
76
|
+
mask: true, // 只有栈顶的弹窗层才画遮罩
|
|
77
|
+
onMaskClick: fn,
|
|
78
|
+
layerStyle: {...}, maskStyle: {...}, scrollStyle: {...},
|
|
79
|
+
stageStyle: {...}, clipStyle: {...},
|
|
80
|
+
closeButton: { key, tag, style, onClick, text },
|
|
81
|
+
nodes: [{ key: 'main-0', tag: 'div', style: {...}, onClick: fn, children: [...] }],
|
|
82
|
+
}],
|
|
83
|
+
countdownEndTimes: [], // 有倒计时时运行时会自己起定时器
|
|
84
|
+
countdownPrecision: 's',
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
框架壳照着 layer 的结构套四层 div 即可,**不需要自带任何 CSS**,基础样式已经写进行内样式了。
|
|
89
|
+
|
|
90
|
+
### 框架壳必须遵守的一条契约
|
|
91
|
+
|
|
92
|
+
节点有 `onClick` 时,挂事件要**阻止冒泡**。
|
|
93
|
+
|
|
94
|
+
父子都可点是常见排版——公告条整条点开弹窗、行末 × 关闭公告条——不阻止的话点 × 会连带触发外层,关掉的瞬间又弹出来。没有 `onClick` 的节点不要绑任何事件,让它正常冒泡到有 `onClick` 的祖先。
|
|
95
|
+
|
|
96
|
+
## 安全边界
|
|
97
|
+
|
|
98
|
+
这几条是设计红线,不会因为「就差一点点」而放开:
|
|
99
|
+
|
|
100
|
+
| 边界 | 做法 |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| 不执行任意代码 | 手写解析器,不用 `eval` / `new Function`;不支持函数调用和赋值 |
|
|
103
|
+
| 不能沿原型链逃逸 | `constructor` / `__proto__` / `prototype` 一律读不到,函数值也读不到 |
|
|
104
|
+
| 不能通过样式作恶 | 只有白名单里的属性会输出,`position: fixed` 这类会被丢弃 |
|
|
105
|
+
| 不能执行 `javascript:` | 链接只允许 `http` / `https` / `mailto` / `tel` 和相对路径 |
|
|
106
|
+
| 不能打任意接口 | 数据源用注册名引用,配置里写不了 URL |
|
|
107
|
+
| 不能调任意方法 | `call` 只能调宿主注册在 `handlers` 里的方法 |
|
|
108
|
+
|
|
109
|
+
## 完整 DSL 语法
|
|
110
|
+
|
|
111
|
+
见调试台的「开发文档」页,或从那里导出 markdown / AI 提示词。
|