@weapp-core/shared 3.2.7 → 3.3.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 CHANGED
@@ -11,6 +11,7 @@
11
11
  - 对象合并与判定工具
12
12
  - 统一导出常用第三方工具(`defu`、`get`、`set`)
13
13
  - 通过 `@weapp-core/shared/node` 提供 Node 专用哈希与文件系统能力
14
+ - 通过独立、浏览器安全的 `@weapp-core/shared/rich-text` 子入口提供富文本安全树规范化,根入口不加载 HTML 解析器
14
15
 
15
16
  ## 安装
16
17
 
@@ -35,6 +36,16 @@ const hash = objectHash({ foo: 'bar' })
35
36
  const exists = await fs.pathExists('app.json')
36
37
  ```
37
38
 
39
+ ### 富文本安全树
40
+
41
+ ```ts
42
+ import { normalizeRichTextNodes } from '@weapp-core/shared/rich-text'
43
+
44
+ const nodes = normalizeRichTextNodes('<p>Hello <strong>world</strong></p>')
45
+ ```
46
+
47
+ 该子入口接受 HTML 字符串或结构化节点,返回命名的 `SafeRichTextNode` 文本/元素联合类型。Web 原生组件与 headless 模拟器共用同一份标签、属性、URL 和样式规则;输出是惰性的内容树,不执行脚本或模板。宿主保留自己的元数据边界,例如模拟器另外拒绝外部 `data-sim-*` 属性。
48
+
38
49
  ## 配置
39
50
 
40
51
  暂无额外配置。
@@ -5,7 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
8
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
9
9
  var __copyProps = (to, from, except, desc) => {
10
10
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
11
  key = keys[i];
@@ -88,4 +88,5 @@ export declare const fs: {
88
88
  };
89
89
  export type FsDirent = Dirent;
90
90
  export type FsStats = Stats;
91
- //#endregion
91
+ //#endregion
92
+ export {};
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import { MINI_PROGRAM_PLATFORM_DESCRIPTORS } from "./platforms/descriptors.js";
6
6
  import { MINI_PROGRAM_PLATFORM_ALIASES, getDefaultMiniProgramPlatform, getDefaultMiniProgramRuntimeGlobalKey, getMiniProgramPlatformByRuntimeGlobalKey, getMiniProgramRouteRuntimeGlobalKeys, getMiniProgramRuntimeCapabilities, getMiniProgramRuntimeGlobalKey, getMiniProgramRuntimeGlobalKeys, getMiniProgramRuntimeGlobalKeysByResolvePriority, getMiniProgramRuntimeHostConfigKey, getSupportedMiniProgramPlatforms, normalizeMiniProgramPlatform, resolveMiniProgramPageKeys, resolveMiniProgramPlatform, supportsMiniProgramRuntimeCapability } from "./platforms/runtime/helpers.js";
7
7
  import { getMiniProgramAppTypesPackage, getMiniProgramDefaultBuildTarget, getMiniProgramPlatformDescriptor, getMiniProgramTemplatePreset, supportsMiniProgramAutoTouchAppStyle } from "./platforms/helpers.js";
8
8
  import { escapeWxmlAttribute, escapeWxmlText } from "./wxml.js";
9
- import index_default from "./node_modules/.pnpm/get-value@4.1.0/node_modules/get-value/dist/index.js";
10
9
  import { require_set_value } from "./node_modules/.pnpm/set-value@4.1.0/node_modules/set-value/index.js";
10
+ import get from "get-value";
11
11
  //#region src/index.ts
12
12
  var import_set_value = /* @__PURE__ */ __toESM(require_set_value(), 1);
13
13
  const SPECIAL_CHARS_RE = /[|\\{}()[\]^$+*?.]/g;
@@ -79,4 +79,4 @@ function isEmptyObject(obj) {
79
79
  }
80
80
  //#endregion
81
81
  var set = import_set_value.default;
82
- export { MINI_PROGRAM_PLATFORM_ALIASES, MINI_PROGRAM_PLATFORM_DESCRIPTORS, addExtension, arrify, createMiniProgramLayoutHostRegistry, defu, defuOverrideArray, escapeStringRegexp, escapeWxmlAttribute, escapeWxmlText, index_default as get, getDefaultMiniProgramPlatform, getDefaultMiniProgramRuntimeGlobalKey, getMiniProgramAppTypesPackage, getMiniProgramDefaultBuildTarget, getMiniProgramDirectivePrefix, getMiniProgramPlatformByRuntimeGlobalKey, getMiniProgramPlatformDescriptor, getMiniProgramRouteRuntimeGlobalKeys, getMiniProgramRuntimeCapabilities, getMiniProgramRuntimeGlobalKey, getMiniProgramRuntimeGlobalKeys, getMiniProgramRuntimeGlobalKeysByResolvePriority, getMiniProgramRuntimeHostConfigKey, getMiniProgramTemplatePreset, getSupportedMiniProgramDirectivePrefixes, getSupportedMiniProgramPlatforms, isEmptyObject, isObject, normalizeMiniProgramLayoutHostKeys, normalizeMiniProgramPlatform, removeExtension, removeExtensionDeep, resolveMiniProgramPageKeys, resolveMiniProgramPlatform, set, supportsMiniProgramAutoTouchAppStyle, supportsMiniProgramRuntimeCapability };
82
+ export { MINI_PROGRAM_PLATFORM_ALIASES, MINI_PROGRAM_PLATFORM_DESCRIPTORS, addExtension, arrify, createMiniProgramLayoutHostRegistry, defu, defuOverrideArray, escapeStringRegexp, escapeWxmlAttribute, escapeWxmlText, get, getDefaultMiniProgramPlatform, getDefaultMiniProgramRuntimeGlobalKey, getMiniProgramAppTypesPackage, getMiniProgramDefaultBuildTarget, getMiniProgramDirectivePrefix, getMiniProgramPlatformByRuntimeGlobalKey, getMiniProgramPlatformDescriptor, getMiniProgramRouteRuntimeGlobalKeys, getMiniProgramRuntimeCapabilities, getMiniProgramRuntimeGlobalKey, getMiniProgramRuntimeGlobalKeys, getMiniProgramRuntimeGlobalKeysByResolvePriority, getMiniProgramRuntimeHostConfigKey, getMiniProgramTemplatePreset, getSupportedMiniProgramDirectivePrefixes, getSupportedMiniProgramPlatforms, isEmptyObject, isObject, normalizeMiniProgramLayoutHostKeys, normalizeMiniProgramPlatform, removeExtension, removeExtensionDeep, resolveMiniProgramPageKeys, resolveMiniProgramPlatform, set, supportsMiniProgramAutoTouchAppStyle, supportsMiniProgramRuntimeCapability };
@@ -0,0 +1,21 @@
1
+ //#region src/rich-text.d.ts
2
+ export interface RichTextSourceNode {
3
+ type?: string;
4
+ name?: string;
5
+ attrs?: Record<string, unknown>;
6
+ children?: RichTextSourceNode[];
7
+ text?: string;
8
+ }
9
+ export type SafeRichTextNode = {
10
+ type: 'text';
11
+ text: string;
12
+ } | {
13
+ type: 'element';
14
+ name: string;
15
+ attrs: Record<string, string>;
16
+ children: SafeRichTextNode[];
17
+ };
18
+ export declare function sanitizeRichTextStyle(value: unknown): string;
19
+ export declare function sanitizeRichTextAttributes(value: unknown, tagName: string): Record<string, string>;
20
+ export declare function normalizeRichTextNodes(value: unknown): SafeRichTextNode[];
21
+ //#endregion
@@ -0,0 +1,227 @@
1
+ import { parseDocument } from "htmlparser2";
2
+ //#region src/rich-text.ts
3
+ const ALLOWED_TAGS = /* @__PURE__ */ new Set([
4
+ "a",
5
+ "abbr",
6
+ "address",
7
+ "article",
8
+ "aside",
9
+ "b",
10
+ "bdi",
11
+ "bdo",
12
+ "big",
13
+ "blockquote",
14
+ "br",
15
+ "caption",
16
+ "center",
17
+ "cite",
18
+ "code",
19
+ "col",
20
+ "colgroup",
21
+ "dd",
22
+ "del",
23
+ "div",
24
+ "dl",
25
+ "dt",
26
+ "em",
27
+ "fieldset",
28
+ "font",
29
+ "h1",
30
+ "h2",
31
+ "h3",
32
+ "h4",
33
+ "h5",
34
+ "h6",
35
+ "header",
36
+ "hr",
37
+ "i",
38
+ "img",
39
+ "ins",
40
+ "label",
41
+ "legend",
42
+ "li",
43
+ "mark",
44
+ "ol",
45
+ "p",
46
+ "pre",
47
+ "q",
48
+ "rt",
49
+ "ruby",
50
+ "s",
51
+ "section",
52
+ "small",
53
+ "span",
54
+ "strong",
55
+ "sub",
56
+ "sup",
57
+ "table",
58
+ "tbody",
59
+ "td",
60
+ "tfoot",
61
+ "th",
62
+ "thead",
63
+ "tr",
64
+ "tt",
65
+ "u",
66
+ "ul"
67
+ ]);
68
+ const ALLOWED_ATTRIBUTES = /* @__PURE__ */ new Set([
69
+ "abbr",
70
+ "align",
71
+ "alt",
72
+ "axis",
73
+ "bgcolor",
74
+ "border",
75
+ "cellpadding",
76
+ "cellspacing",
77
+ "class",
78
+ "colspan",
79
+ "dir",
80
+ "height",
81
+ "href",
82
+ "id",
83
+ "lang",
84
+ "rowspan",
85
+ "scope",
86
+ "span",
87
+ "start",
88
+ "style",
89
+ "summary",
90
+ "target",
91
+ "title",
92
+ "type",
93
+ "valign",
94
+ "width"
95
+ ]);
96
+ const URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src"]);
97
+ const ALLOWED_STYLE_PROPERTIES = /* @__PURE__ */ new Set([
98
+ "background",
99
+ "background-color",
100
+ "border",
101
+ "border-bottom",
102
+ "border-color",
103
+ "border-left",
104
+ "border-radius",
105
+ "border-right",
106
+ "border-style",
107
+ "border-top",
108
+ "border-width",
109
+ "color",
110
+ "display",
111
+ "font-family",
112
+ "font-size",
113
+ "font-style",
114
+ "font-weight",
115
+ "height",
116
+ "letter-spacing",
117
+ "line-height",
118
+ "margin",
119
+ "margin-bottom",
120
+ "margin-left",
121
+ "margin-right",
122
+ "margin-top",
123
+ "max-height",
124
+ "max-width",
125
+ "min-height",
126
+ "min-width",
127
+ "opacity",
128
+ "overflow",
129
+ "padding",
130
+ "padding-bottom",
131
+ "padding-left",
132
+ "padding-right",
133
+ "padding-top",
134
+ "text-align",
135
+ "text-decoration",
136
+ "text-indent",
137
+ "text-overflow",
138
+ "text-transform",
139
+ "vertical-align",
140
+ "white-space",
141
+ "width",
142
+ "word-break",
143
+ "word-wrap"
144
+ ]);
145
+ function isRecord(value) {
146
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
147
+ }
148
+ function isSafeUrl(value, attribute) {
149
+ const normalized = Array.from(value).filter((character) => character.charCodeAt(0) > 32).join("").toLowerCase();
150
+ if (!normalized || normalized.startsWith("#") || normalized.startsWith("/") || normalized.startsWith("./") || normalized.startsWith("../")) return true;
151
+ if (/^(?:https?:|mailto:|tel:)/.test(normalized)) return true;
152
+ return attribute === "src" && /^data:image\/(?:gif|jpe?g|png|webp);base64,/.test(normalized);
153
+ }
154
+ function isSafeStyleValue(value) {
155
+ const normalized = value.toLowerCase().replace(/\s+/g, "");
156
+ return !normalized.includes("url(") && !normalized.includes("expression(") && !normalized.includes("javascript:") && !normalized.includes("behavior:") && !normalized.includes("-moz-binding");
157
+ }
158
+ function sanitizeRichTextStyle(value) {
159
+ const source = isRecord(value) ? Object.entries(value).map(([property, propertyValue]) => `${property}:${String(propertyValue)}`).join(";") : String(value ?? "");
160
+ const declarations = [];
161
+ for (const declaration of source.split(";")) {
162
+ const separator = declaration.indexOf(":");
163
+ if (separator <= 0) continue;
164
+ const property = declaration.slice(0, separator).trim().toLowerCase();
165
+ const propertyValue = declaration.slice(separator + 1).trim();
166
+ if (ALLOWED_STYLE_PROPERTIES.has(property) && propertyValue && isSafeStyleValue(propertyValue)) declarations.push(`${property}: ${propertyValue}`);
167
+ }
168
+ return declarations.join("; ");
169
+ }
170
+ function sanitizeRichTextAttributes(value, tagName) {
171
+ if (!isRecord(value)) return {};
172
+ const attributes = {};
173
+ for (const [rawName, rawValue] of Object.entries(value)) {
174
+ const name = rawName.trim().toLowerCase();
175
+ if (name.startsWith("on") || name === "srcdoc") continue;
176
+ if (!ALLOWED_ATTRIBUTES.has(name) && name !== "src" && !name.startsWith("aria-") && !name.startsWith("data-")) continue;
177
+ if (name === "src" && tagName !== "img") continue;
178
+ const serialized = name === "style" ? sanitizeRichTextStyle(rawValue) : String(rawValue ?? "");
179
+ if (!serialized || URL_ATTRIBUTES.has(name) && !isSafeUrl(serialized, name)) continue;
180
+ attributes[name] = serialized;
181
+ }
182
+ if (tagName === "a") attributes.rel = "noopener noreferrer";
183
+ return attributes;
184
+ }
185
+ function convertSourceNode(value) {
186
+ if (!isRecord(value)) return [];
187
+ if (value.type === "text" || !value.name && typeof value.text === "string") return [{
188
+ type: "text",
189
+ text: String(value.text ?? "")
190
+ }];
191
+ const children = Array.isArray(value.children) ? value.children.flatMap(convertSourceNode) : [];
192
+ const name = String(value.name ?? "").toLowerCase();
193
+ if (!ALLOWED_TAGS.has(name)) return children;
194
+ return [{
195
+ type: "element",
196
+ name,
197
+ attrs: sanitizeRichTextAttributes(value.attrs, name),
198
+ children
199
+ }];
200
+ }
201
+ function convertParsedNode(node) {
202
+ if (node.type === "text") return [{
203
+ type: "text",
204
+ text: node.data
205
+ }];
206
+ if (node.type !== "tag") return [];
207
+ const element = node;
208
+ const children = element.children.flatMap(convertParsedNode);
209
+ const name = element.name.toLowerCase();
210
+ if (!ALLOWED_TAGS.has(name)) return children;
211
+ return [{
212
+ type: "element",
213
+ name,
214
+ attrs: sanitizeRichTextAttributes(element.attribs, name),
215
+ children
216
+ }];
217
+ }
218
+ function normalizeRichTextNodes(value) {
219
+ if (typeof value === "string") return parseDocument(value, {
220
+ decodeEntities: true,
221
+ lowerCaseAttributeNames: true,
222
+ lowerCaseTags: true
223
+ }).children.flatMap(convertParsedNode);
224
+ return (Array.isArray(value) ? value : [value]).flatMap(convertSourceNode);
225
+ }
226
+ //#endregion
227
+ export { normalizeRichTextNodes, sanitizeRichTextAttributes, sanitizeRichTextStyle };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@weapp-core/shared",
3
3
  "type": "module",
4
- "version": "3.2.7",
4
+ "version": "3.3.0",
5
5
  "description": "Shared runtime-safe utilities and platform contracts for the weapp-vite ecosystem.",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",
@@ -36,6 +36,10 @@
36
36
  "types": "./src/index.ts",
37
37
  "default": "./src/index.ts"
38
38
  },
39
+ "./rich-text": {
40
+ "types": "./src/rich-text.ts",
41
+ "default": "./src/rich-text.ts"
42
+ },
39
43
  "./node": {
40
44
  "types": "./src/node.ts",
41
45
  "default": "./src/node.ts"
@@ -62,6 +66,10 @@
62
66
  "types": "./dist/index.d.ts",
63
67
  "import": "./dist/index.js"
64
68
  },
69
+ "./rich-text": {
70
+ "types": "./dist/rich-text.d.ts",
71
+ "import": "./dist/rich-text.js"
72
+ },
65
73
  "./node": {
66
74
  "types": "./dist/node.d.ts",
67
75
  "import": "./dist/node.js"
@@ -88,6 +96,12 @@
88
96
  "engines": {
89
97
  "node": "^20.19.0 || >=22.12.0"
90
98
  },
99
+ "dependencies": {
100
+ "@types/set-value": "^4.0.3",
101
+ "domhandler": "^6.0.1",
102
+ "get-value": "^4.1.0",
103
+ "htmlparser2": "^12.0.0"
104
+ },
91
105
  "publishConfig": {
92
106
  "access": "public",
93
107
  "registry": "https://registry.npmjs.org"
@@ -97,11 +111,12 @@
97
111
  "build": "tsdown",
98
112
  "test": "vitest run",
99
113
  "test:dev": "vitest",
100
- "test:types": "tsd --files \"test-d/**/*.test-d.ts\"",
114
+ "test:types": "pnpm run test:types:check",
115
+ "test:types:check": "tsd --files \"test-d/**/*.test-d.ts\"",
101
116
  "typecheck": "tsc --noEmit",
102
117
  "release": "pnpm publish",
103
118
  "lint": "eslint .",
104
119
  "lint:fix": "eslint . --fix",
105
- "sync": "cnpm sync @weapp-core/shared"
120
+ "sync": "repo release sync-npmmirror --package @weapp-core/shared"
106
121
  }
107
122
  }
@@ -1,75 +0,0 @@
1
- //#region ../../node_modules/.pnpm/get-value@4.1.0/node_modules/get-value/dist/index.mjs
2
- var __defProp = Object.defineProperty;
3
- var __name = (target, value) => __defProp(target, "name", {
4
- value,
5
- configurable: true
6
- });
7
- var isObject = /* @__PURE__ */ __name((v) => v !== null && typeof v === "object", "isObject");
8
- var join = /* @__PURE__ */ __name((segs, joinChar, options) => {
9
- if (typeof options.join === "function") return options.join(segs);
10
- return segs[0] + joinChar + segs[1];
11
- }, "join");
12
- var split = /* @__PURE__ */ __name((path, splitChar, options) => {
13
- if (typeof options.split === "function") return options.split(path);
14
- return path.split(splitChar);
15
- }, "split");
16
- var isValid = /* @__PURE__ */ __name((key, target = {}, options) => {
17
- if (typeof options?.isValid === "function") return options.isValid(key, target);
18
- return true;
19
- }, "isValid");
20
- var isValidObject = /* @__PURE__ */ __name((v) => {
21
- return isObject(v) || typeof v === "function";
22
- }, "isValidObject");
23
- var getValue = /* @__PURE__ */ __name((target, path, options = {}) => {
24
- if (!isObject(options)) options = { default: options };
25
- if (!isValidObject(target)) return typeof options.default !== "undefined" ? options.default : target;
26
- if (typeof path === "number") path = String(path);
27
- const pathIsArray = Array.isArray(path);
28
- const pathIsString = typeof path === "string";
29
- const splitChar = options.separator || ".";
30
- const joinChar = options.joinChar || (typeof splitChar === "string" ? splitChar : ".");
31
- if (!pathIsString && !pathIsArray) return target;
32
- let value = target[path];
33
- if (value === void 0 && typeof target.get === "function") value = target.get(path);
34
- if (value !== void 0) return isValid(path, target, options) ? value : options.default;
35
- const segs = pathIsArray ? path : split(path, splitChar, options);
36
- const len = segs.length;
37
- let idx = 0;
38
- do {
39
- let prop = segs[idx];
40
- if (typeof prop !== "string") prop = String(prop);
41
- while (prop && prop.slice(-1) === "\\") prop = join([prop.slice(0, -1), segs[++idx] || ""], joinChar, options);
42
- value = target[prop];
43
- if (value === void 0 && typeof target.get === "function") value = target.get(prop);
44
- if (value !== void 0) {
45
- if (!isValid(prop, target, options)) return options.default;
46
- target = value;
47
- } else {
48
- let hasProp = false;
49
- let n = idx + 1;
50
- while (n < len) {
51
- prop = join([prop, segs[n++]], joinChar, options);
52
- value = target[prop];
53
- if (value === void 0 && typeof target.get === "function") value = target.get(prop);
54
- if (hasProp = value !== void 0) {
55
- if (!isValid(prop, target, options)) return options.default;
56
- target = value;
57
- idx = n - 1;
58
- break;
59
- }
60
- }
61
- if (!hasProp) return options.default;
62
- }
63
- } while (++idx < len && isValidObject(target));
64
- if (idx === len) return target;
65
- return options.default;
66
- }, "getValue");
67
- var index_default = getValue;
68
- /*!
69
- * get-value <https://github.com/jonschlinkert/get-value>
70
- *
71
- * Copyright (c) 2014-present, Jon Schlinkert.
72
- * Released under the MIT License.
73
- */
74
- //#endregion
75
- export { index_default as default, getValue };