@weapp-vite/glass-easel-web-adapter 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ice breaker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @weapp-vite/glass-easel-web-adapter
2
+
3
+ 实验性的 glass-easel Web 组件后端。它复用 glass-easel 官方组件树、模板编译和浏览器 backend,向 weapp-vite Web host 提供受控的组件挂载、数据更新、事件和生命周期桥接。
4
+
5
+ 该包不会替换 `@weapp-vite/web`,也不实现页面路由、WXSS/rpx、微信 API 或全部原生组件。调用方必须显式创建 adapter;当前默认 Web runtime 不受影响。
6
+
7
+ `mountComponent` 的第四个参数可传入 `slots`,用于将受控 HTML 投影到模板中的 `<slot />`。这是 PoC 的 DOM 桥接,复杂 slot 关系仍需由后续组件树适配器实现。
8
+
9
+ ## 验证
10
+
11
+ ```bash
12
+ pnpm --filter @weapp-vite/glass-easel-web-adapter typecheck
13
+ pnpm --filter @weapp-vite/glass-easel-web-adapter test
14
+ pnpm --filter @weapp-vite/glass-easel-web-adapter test:types
15
+ pnpm --filter @weapp-vite/glass-easel-web-adapter test:browser
16
+ pnpm --filter @weapp-vite/glass-easel-web-adapter build
17
+ ```
@@ -0,0 +1,53 @@
1
+ //#region src/index.d.ts
2
+ export interface GlassEaselWebHost {
3
+ createElement?: (tagName: string) => HTMLElement;
4
+ dispatchEvent?: (event: Event, context: unknown) => void;
5
+ warn?: (message: string) => void;
6
+ }
7
+ export interface GlassEaselComponentDefinition {
8
+ name: string;
9
+ template: string;
10
+ properties?: Record<string, unknown>;
11
+ methods?: Record<string, (...args: unknown[]) => unknown>;
12
+ lifetimes?: Record<string, (...args: unknown[]) => void>;
13
+ }
14
+ export interface GlassEaselWebAdapterOptions {
15
+ host?: GlassEaselWebHost;
16
+ strict?: boolean;
17
+ }
18
+ export interface GlassEaselWebSnapshot {
19
+ name: string;
20
+ props: Record<string, unknown>;
21
+ html: string;
22
+ lifecycle: string[];
23
+ disposed: boolean;
24
+ }
25
+ export interface GlassEaselWebAdapter {
26
+ registerComponent: (definition: GlassEaselComponentDefinition) => void;
27
+ mountComponent: (name: string, container: Element, props?: Record<string, unknown>, slots?: Record<string, string>) => GlassEaselWebInstance;
28
+ updateComponent: (instance: unknown, props: Record<string, unknown>) => void;
29
+ triggerEvent: (instance: unknown, name: string, detail?: unknown) => void;
30
+ unmountComponent: (instance: unknown) => void;
31
+ getSnapshot: (instance: unknown) => GlassEaselWebSnapshot;
32
+ dispose: () => void;
33
+ }
34
+ /** 使用官方 ComponentSpace API 创建可复用的 glass-easel 组件定义。 */
35
+ export declare function createGlassEaselComponentDefinition(definition: GlassEaselComponentDefinition): unknown;
36
+ export interface GlassEaselWebInstance {
37
+ root: HTMLElement;
38
+ definition: GlassEaselComponentDefinition;
39
+ props: Record<string, unknown>;
40
+ disposed: boolean;
41
+ native?: any;
42
+ backendContext?: any;
43
+ lifecycle: string[];
44
+ }
45
+ export declare function createGlassEaselWebAdapter(options?: GlassEaselWebAdapterOptions): GlassEaselWebAdapter;
46
+ export interface GlassEaselRuntimeInfo {
47
+ version: string;
48
+ hasDomBackend: boolean;
49
+ hasTemplateCompiler: boolean;
50
+ }
51
+ /** 返回当前安装的 glass-easel 能力探针,供宿主决定是否启用实验后端。 */
52
+ export declare function getGlassEaselRuntimeInfo(): GlassEaselRuntimeInfo;
53
+ //#endregion
package/dist/index.mjs ADDED
@@ -0,0 +1,135 @@
1
+ import * as glassEasel from "glass-easel";
2
+ import { wxml } from "glass-easel-template-compiler";
3
+ //#region src/index.ts
4
+ /** 使用官方 ComponentSpace API 创建可复用的 glass-easel 组件定义。 */
5
+ function createGlassEaselComponentDefinition(definition) {
6
+ const space = glassEasel.getDefaultComponentSpace();
7
+ const params = { template: wxml(definition.template) };
8
+ if (definition.properties) params.properties = definition.properties;
9
+ if (definition.methods) params.methods = definition.methods;
10
+ if (definition.lifetimes) params.lifetimes = definition.lifetimes;
11
+ return space.define().definition(params).registerComponent();
12
+ }
13
+ function render(template, props, host) {
14
+ const root = host.createElement?.("div") ?? document.createElement("div");
15
+ root.innerHTML = template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, key) => String(key.split(".").reduce((value, part) => value?.[part], props) ?? ""));
16
+ return root;
17
+ }
18
+ function createGlassEaselWebAdapter(options = {}) {
19
+ const definitions = /* @__PURE__ */ new Map();
20
+ const instances = /* @__PURE__ */ new Set();
21
+ if (options.strict && !getGlassEaselRuntimeInfo().hasDomBackend) throw new Error("glass-easel Web backend is unavailable in this environment");
22
+ return {
23
+ registerComponent(definition) {
24
+ if (!definition.name || !definition.template) throw new TypeError("glass-easel component requires name and template");
25
+ if (definitions.has(definition.name)) throw new Error(`component already registered: ${definition.name}`);
26
+ definitions.set(definition.name, {
27
+ ...definition,
28
+ native: createGlassEaselComponentDefinition(definition)
29
+ });
30
+ },
31
+ mountComponent(name, container, props = {}, slots = {}) {
32
+ const definition = definitions.get(name);
33
+ if (!definition) throw new Error(`component is not registered: ${name}`);
34
+ let root;
35
+ let native;
36
+ let backendContext;
37
+ if (definition.native && getGlassEaselRuntimeInfo().hasDomBackend) {
38
+ const placeholder = document.createElement("glass-easel-placeholder");
39
+ container.append(placeholder);
40
+ backendContext = new glassEasel.CurrentWindowBackendContext();
41
+ backendContext.onEvent(glassEasel.Event.triggerBackendEvent);
42
+ native = glassEasel.Component.createWithContext(name, definition.native, backendContext, (component) => {
43
+ component.setData(props);
44
+ });
45
+ glassEasel.Element.replaceDocumentElement(native, placeholder.parentNode, placeholder);
46
+ root = container.firstElementChild;
47
+ projectSlots(root, slots);
48
+ } else {
49
+ root = render(definition.template, props, options.host ?? {});
50
+ container.append(root);
51
+ }
52
+ const instance = {
53
+ root,
54
+ definition,
55
+ props: { ...props },
56
+ disposed: false,
57
+ native,
58
+ backendContext,
59
+ lifecycle: []
60
+ };
61
+ for (const name of [
62
+ "created",
63
+ "attached",
64
+ "ready",
65
+ "detached"
66
+ ]) instance.native?.addLifetimeListener?.(name, () => instance.lifecycle.push(name));
67
+ instances.add(instance);
68
+ return instance;
69
+ },
70
+ updateComponent(value, props) {
71
+ const instance = value;
72
+ if (instance.disposed) throw new Error("cannot update a disposed glass-easel component");
73
+ instance.props = {
74
+ ...instance.props,
75
+ ...props
76
+ };
77
+ if (instance.native) instance.native.setData(props);
78
+ else {
79
+ const replacement = render(instance.definition.template, instance.props, options.host ?? {});
80
+ instance.root.replaceWith(replacement);
81
+ instance.root = replacement;
82
+ }
83
+ },
84
+ triggerEvent(value, name, detail) {
85
+ const instance = value;
86
+ if (instance.disposed) throw new Error("cannot trigger an event on a disposed glass-easel component");
87
+ if (instance.native) instance.native.triggerEvent(name, detail);
88
+ options.host?.dispatchEvent?.(new CustomEvent(name, { detail }), instance);
89
+ },
90
+ unmountComponent(value) {
91
+ const instance = value;
92
+ if (!instances.delete(instance) || instance.disposed) return;
93
+ instance.native?.triggerLifetime?.("detached");
94
+ if (!instance.native) instance.lifecycle.push("detached");
95
+ instance.root?.remove();
96
+ instance.disposed = true;
97
+ },
98
+ getSnapshot(value) {
99
+ const instance = value;
100
+ return {
101
+ name: instance.definition.name,
102
+ props: { ...instance.props },
103
+ html: instance.root.innerHTML,
104
+ lifecycle: [...instance.lifecycle],
105
+ disposed: instance.disposed
106
+ };
107
+ },
108
+ dispose() {
109
+ for (const instance of [...instances]) this.unmountComponent(instance);
110
+ definitions.clear();
111
+ }
112
+ };
113
+ }
114
+ /** 将受控 PoC slot 内容投影到官方 backend 生成的 DOM。复杂 slot 仍需由宿主组件树实现。 */
115
+ function projectSlots(root, slots) {
116
+ let projected = false;
117
+ for (const slot of Array.from(root.querySelectorAll("slot"))) {
118
+ const content = slots[slot.getAttribute("name") || "default"];
119
+ if (content === void 0) continue;
120
+ const fragment = document.createRange().createContextualFragment(content);
121
+ slot.replaceWith(fragment);
122
+ projected = true;
123
+ }
124
+ if (!projected && slots.default !== void 0) (root.firstElementChild ?? root).insertAdjacentHTML("beforeend", slots.default);
125
+ }
126
+ /** 返回当前安装的 glass-easel 能力探针,供宿主决定是否启用实验后端。 */
127
+ function getGlassEaselRuntimeInfo() {
128
+ return {
129
+ version: "1.2.0",
130
+ hasDomBackend: typeof glassEasel.CurrentWindowBackendContext === "function",
131
+ hasTemplateCompiler: typeof wxml === "function"
132
+ };
133
+ }
134
+ //#endregion
135
+ export { createGlassEaselComponentDefinition, createGlassEaselWebAdapter, getGlassEaselRuntimeInfo };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@weapp-vite/glass-easel-web-adapter",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "Experimental glass-easel component backend for weapp-vite Web runtime",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.mts",
11
+ "import": "./dist/index.mjs"
12
+ }
13
+ },
14
+ "main": "./dist/index.mjs",
15
+ "module": "./dist/index.mjs",
16
+ "types": "./dist/index.d.mts",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "peerDependencies": {
21
+ "glass-easel": "^1.2.0",
22
+ "glass-easel-miniprogram-adapter": "^1.2.0"
23
+ },
24
+ "dependencies": {
25
+ "glass-easel-template-compiler": "1.2.0"
26
+ },
27
+ "devDependencies": {
28
+ "playwright": "1.63.0",
29
+ "tsd": "0.33.0",
30
+ "tsdown": "0.23.0",
31
+ "tsx": "4.23.13",
32
+ "typescript": "^6.0.3",
33
+ "vite": "8.3.0",
34
+ "vitest": "~5.0.0"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org"
39
+ },
40
+ "scripts": {
41
+ "build": "tsdown",
42
+ "test": "vitest run",
43
+ "test:browser": "pnpm build && tsx test/browser.e2e.ts",
44
+ "typecheck": "tsc --noEmit",
45
+ "test:types": "pnpm build && tsd --typings dist/index.d.mts --files test-d/public-api.test-d.ts"
46
+ }
47
+ }