给网站加插件
两种方式:
- 固定插件:
wt pack --target web把插件打成静态站点,多个插件打成带入口页的站点,部署到任何静态托管。 - 动态安装:网站引入
@whatevertools/web-host,用户在浏览器里安装.upx插件包,每个插件跑在自己的 iframe 里。
动态安装
需要三个静态文件,页面必须是 https 或 localhost(Service Worker 的要求):
@whatevertools/web-host/sw.js:放在能覆盖作用域的位置,比如/__wt/sw.js(作用域默认/__wt/)。- 浏览器运行时:把
@whatevertools/web/full打成 iife,全局名__whatevertoolsRuntime:bashnpx esbuild node_modules/@whatevertools/web/dist/full.js --bundle --format=iife --global-name=__whatevertoolsRuntime --minify --outfile=public/wt-runtime.js - 你的页面(用打包工具引入本包)。
用法
ts
import { createWebHost } from '@whatevertools/web-host'
const host = createWebHost({
swUrl: '/__wt/sw.js',
runtimeUrl: '/wt-runtime.js',
container: document.querySelector('#plugin-area')!,
policy: { max: 5, deny: ['evil-*'] }, // 同 core 的 InstallPolicy
prompt: async (c) => myDialog(`安装「${c.name}」${c.version}?`), // 默认浏览器 confirm
apiName: 'acme', // 原生 API 注入名;uTools 兼容层默认照常注入 window.utools
})
await host.ready
input.onchange = async () => {
try {
const rec = await host.install(input.files![0]) // File / Blob / Uint8Array;文件夹传 FileList(<input webkitdirectory>)
host.open(rec.id, { feature: 'main' })
} catch (e: any) {
alert(e.message) // e.code:EWTINSTALL(策略 / 取消 / 描述文件)、EWTENCRYPTED(upxs)
}
}
host.list() // 已安装的插件
host.close('some-id') // 关掉它的 iframe
await host.uninstall('some-id', { purgeData: true })host.hooks上可以挂plugin:install(apply,args[0]是InstallCandidate,返回false拒绝),接自己的审核或签名校验。- 描述文件按其他平台的规则读取(
whatevertools.json优先,再plugin.json),插件 id 一致;声明了targets且不含web的插件会被拒绝。 - 同一个 id 再装一次就是升级:新版本文件写完后才切换记录,已打开的 iframe 自动重新加载。
- preload 不需要构建:运行时用
preloadMode: 'cjs',在浏览器里按 Node 规则require插件里的相对文件、JSON 和node_modules。
限制
- iframe 和宿主页同源,插件之间、插件和宿主之间不是安全边界(存储按插件 id 分开,但恶意插件能访问
parent)。只装可信的插件;需要和主站隔离时,把整个宿主(页面 + 作用域)放到单独的子域名下。 - 插件文件在 IndexedDB 里,受浏览器存储配额限制。
- preload 里用到浏览器端没有的 Node 能力(原生模块、真实子进程)时会报错;需要的话接
@whatevertools/bridge(通过boot选项传启动配置)。
MIT
在自己的页面里启动运行时
ts
import { boot } from '@whatevertools/web/full' // 带 uTools 兼容层和默认适配器
const rt = await boot({
manifestFiles: { 'plugin.json': pluginJson },
files: { 'index.html': 1234, 'preload.js': 567 }, // 插件文件清单(相对路径 → 字节数)
base: '/plugins/my-plugin/',
apiName: 'acme',
compat: [{ type: 'utools' }],
preloadMode: 'cjs', // preload 在浏览器里直接按 CommonJS 执行,不用预先打包
fileDialogs: { openDialog: 'enter' },
})
await rt.start()