## 一次打开的生命周期

```text
扫描清单 → 加载样式和有序脚本 → 注册定义
→ activate(api, host) → 用户操作
→ deactivate() → 回收自己的视图和业务资源
```

宿主运行时公开了 `register`、`load`、`activate`、`deactivate`、`disable`、`status` 和 `get`。业务组件通常只需要注册定义，由宿主完成打开与装配，不必自行反复加载依赖。

## activate 应该做什么

- 验证挂载节点和必要接口。
- 创建组件自己的根节点、状态与事件。
- 读取打开当前视图真正需要的最少信息。
- 对初始化失败给出原因，而不是留下空白窗口。

初始化可以返回 Promise。不要在每次激活时插入重复脚本、覆盖公共全局变量，或扫描用户整个项目来“预热”。

## deactivate 怎样清理

| 资源 | 应保存什么 | 停用时处理 |
| --- | --- | --- |
| DOM 事件 | 节点、事件名、函数引用 | 移除自己注册的监听 |
| 订阅 | 实际返回的取消函数 | 调用本次订阅的清理函数 |
| 计时器 | timer ID | `clearTimeout` / `clearInterval` |
| Observer | Observer 实例 | `disconnect()` |
| 编辑器、画布实例 | 组件实例 | 使用它自己的 dispose / destroy |
| 网络请求 | 支持取消的控制器 | 取消请求，并阻止旧回调写回 |
| 后台计算 | 真实任务或进程身份 | 调用受支持的停止机制 |
| 临时预览 | 自己生成的对象 URL | `URL.revokeObjectURL()` |

> [!NOTE]
> 移除 script 标签不能撤销已经执行的 JavaScript。当前运行时停用时主要回收样式和组件自己的生命周期资源，因此清理逻辑不能省略。

## 一个可以复用的清理容器

下面是插件内部的组织工具，不是宿主 API。资源建立后立即登记清理，比到停用时再猜有哪些监听更可靠。

```javascript
function createCleanup() {
  const callbacks = [];
  return {
    add(dispose) {
      if (typeof dispose === 'function') callbacks.push(dispose);
    },
    run() {
      const errors = [];
      while (callbacks.length) {
        try { callbacks.pop()(); } catch (error) { errors.push(error); }
      }
      return errors;
    }
  };
}
```

不要用 `removeAllListeners` 清空宿主或其他组件的订阅。部分旧事件接口会替换之前的回调，也不返回取消函数；必须逐个核对 preload 的真实实现。

## 界面、样式和可访问性

根节点使用唯一类名；按钮声明 `type="button"`，输入提供标签，进度或错误使用 `role="status"` 等可理解的反馈。长文件名可换行或截断，但应保留完整名称的查看方式。

窄窗口中把操作区放在能触达的位置，局部表格与代码块各自滚动。减少动态效果时停掉装饰动画，页面失焦或视口外不持续运行绘图循环。

## 本地资源与正式加密资源

清单中的脚本和样式路径相对组件根目录。开发态可读的文件，在正式包中可能通过受支持的组件资源地址提供，不能硬编码开发机盘符、用户目录或假定 `file://` 始终可用。

加密组件窗口的资源地址和授权准备由宿主负责。不要自己拼接解密地址、复用别的组件窗口的资源授权，或把密钥写进源码。

## 关闭、未保存状态与重新打开

编辑工具应明确：哪些变化只在内存里，哪些已经保存。保存过程中又发生输入时，要保留较新内容的未保存状态；旧保存回调不能清掉新修改。

需要拦截关闭的组件，先核对目标版本支持的窗口关闭约定。不能只监听网页 `unload` 就承诺所有文件一定保存成功。关闭后再次打开的验收，应包括错误恢复和清理结果，而不只是界面出现。
