@adep/web-container 0.2.1 → 0.2.3
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/dist/bundle-export.d.ts +75 -0
- package/dist/function-fetch-proxy.d.ts +17 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +439 -61
- package/dist/sim/runtime.d.ts +1 -0
- package/dist/vite-dev.d.ts +8 -0
- package/dist/web-container.esm.js +439 -61
- package/dist/web-container.iife.js +439 -61
- package/package.json +3 -3
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 产物导出(PV-006):把浏览器内 dev server 的 **blob 文档**序列化成一份可经真实 HTTP 源服务的
|
|
3
|
+
* 自包含产物(`{ html, modules, paths }`),交给平台预览子域。
|
|
4
|
+
*
|
|
5
|
+
* 背景(为什么需要它):Web IDE 的前端预览由 `@adep/web-container` 的 `vite` 命令在浏览器内编译,
|
|
6
|
+
* 产物是一个 **`blob:` 入口文档**——模块之间的 import 说明符在编译期被改写成彼此的 blob URL。
|
|
7
|
+
* blob URL 只在「创建它的那个标签页」里有效:复制出去在别的浏览器打开必然失效,且 blob 是不可
|
|
8
|
+
* 分层 scheme,`history.pushState('/about')` 这类单页路由调用直接抛错。于是「像 CodeSandbox 那样
|
|
9
|
+
* 复制预览链接」这件事在 blob 形态下不可能做到。
|
|
10
|
+
*
|
|
11
|
+
* 本模块的做法:**顺着 blob 引用抓一份完整模块图,改写成内容寻址的 HTTP 路径**——
|
|
12
|
+
* 1. 读入口文档 → 找出其中所有 `blob:` 引用(`<script src="blob:…">`);
|
|
13
|
+
* 2. 逐个读取这些 blob 的源码,再从中找出它们的 `blob:` 引用(编译期已改写为依赖的 URL)→ 递归,
|
|
14
|
+
* 直到闭包;
|
|
15
|
+
* 3. 每份源码按内容哈希命名(同内容必然同路径 → 跨版本复用、天然去重、变更必然换名),
|
|
16
|
+
* 路径形如 `/_adep/m/<hash>.js`(与服务端 `PREVIEW_MODULE_URL_RE` 白名单一致);
|
|
17
|
+
* 4. 把所有源码里的 blob URL 替换成对应路径,得到自包含产物。
|
|
18
|
+
*
|
|
19
|
+
* 增量发布(`known`):每次发布都把**完整闭包读一遍**(blob 就在页内存里,读取代价近乎为零),
|
|
20
|
+
* 但只把「服务端还没有的模块」放进产物体 `modules`——内容寻址下「同路径 ⇔ 同内容」,判据就是
|
|
21
|
+
* 「路径是否出现在上一次发布的映射值里」。产物另带 `paths`(本次入口所需的**全部**模块路径,
|
|
22
|
+
* 含未重复上传的那些),服务端据此写清单并清理幽灵模块:**清单必须完整**,漏报会把仍在用的
|
|
23
|
+
* 模块当幽灵删掉(页面白屏)。
|
|
24
|
+
*
|
|
25
|
+
* 为什么不干脆「已知模块连读都跳过」(更省一层):跳读会丢掉该模块的依赖边,除非调用方保证
|
|
26
|
+
* `known` 是**完整闭包**——把正确性押在调用方的记性上,一旦传了半张表就静默产出缺模块的产物,
|
|
27
|
+
* 白屏且极难诊断。读一遍是最便宜的保险;真正贵的是网络上传,而那部分已经省掉了。
|
|
28
|
+
*
|
|
29
|
+
* 产物随后由 `POST /api/v1/projects/:id/preview` 落到项目桶,由预览子域服务:真实 HTTP 源 →
|
|
30
|
+
* 跨浏览器可用、SPA 路由(history 模式)原生可用、`/api/*` 走平台网关执行项目函数。
|
|
31
|
+
*
|
|
32
|
+
* 边界:只搬运**文本模块**。图片等二进制资产若出现在 VFS 里,dev server 本就不会把它编译成模块
|
|
33
|
+
* (解析链只认 JS/TS/CSS/JSON/Vue),故不在此处理;`readText` 拿不到内容时响亮抛错,不静默产出残缺产物。
|
|
34
|
+
*/
|
|
35
|
+
/** 模块 URL 前缀(与 `server/domains/preview/store.ts` 的 `PREVIEW_MODULE_URL_PREFIX` 对齐)。 */
|
|
36
|
+
export declare const BUNDLE_MODULE_PATH_PREFIX = "/_adep/m/";
|
|
37
|
+
export interface BundleExportOptions {
|
|
38
|
+
/** 入口文档 URL(dev server 产出的 blob 文档)。 */
|
|
39
|
+
documentUrl: string;
|
|
40
|
+
/**
|
|
41
|
+
* 读取 URL 文本(默认 `fetch(url).then(r => r.text())`)。
|
|
42
|
+
* 注入缝:Node 单测用假实现,不依赖真实 blob 存储。
|
|
43
|
+
*/
|
|
44
|
+
readText?: (url: string) => Promise<string>;
|
|
45
|
+
/** 模块 URL 前缀(默认 `/_adep/m/`)。 */
|
|
46
|
+
modulePathPrefix?: string;
|
|
47
|
+
/** 内容 → 模块名(不含扩展名)。默认 sha256 前 16 位 hex;注入以便单测确定化。 */
|
|
48
|
+
moduleName?: (code: string) => string | Promise<string>;
|
|
49
|
+
/** 注入到入口文档 `</body>` 前的脚本(IDE 侧用于错误中继等;默认不注入)。 */
|
|
50
|
+
appendScript?: string;
|
|
51
|
+
/**
|
|
52
|
+
* 上一次发布的 blob URL → 模块路径映射(即上次 `ExportedBundle.urls`)。
|
|
53
|
+
* 用途只有一个:**跳过重复上传**(值集 = 服务端已有的路径集)。传空/不传 = 全量发布。
|
|
54
|
+
*/
|
|
55
|
+
known?: ReadonlyMap<string, string>;
|
|
56
|
+
}
|
|
57
|
+
export interface ExportedBundle {
|
|
58
|
+
/** 自包含入口文档(blob 引用已改写为模块路径)。 */
|
|
59
|
+
html: string;
|
|
60
|
+
/** **需要上传**的模块(路径 → 源码):已在上一次发布里存在的模块不再出现在这里。 */
|
|
61
|
+
modules: Record<string, string>;
|
|
62
|
+
/** 本次入口文档所需的**全部**模块路径(含本次未重复上传的;服务端据此写清单与清理幽灵)。 */
|
|
63
|
+
paths: string[];
|
|
64
|
+
/** 本次闭包的 blob URL → 路径映射;调用方保存,下次作为 `known` 传入。 */
|
|
65
|
+
urls: Map<string, string>;
|
|
66
|
+
}
|
|
67
|
+
/** 默认模块名:sha256 前 16 位 hex(浏览器均有 `crypto.subtle`)。 */
|
|
68
|
+
export declare function defaultModuleName(code: string): Promise<string>;
|
|
69
|
+
/** 找出文本里的全部 blob 引用(去重、保序)。 */
|
|
70
|
+
export declare function findBlobRefs(text: string): string[];
|
|
71
|
+
/**
|
|
72
|
+
* 导出产物:入口文档 + 递归闭包内全部模块(内容寻址命名 + 引用改写)。
|
|
73
|
+
* 读不到某个 blob(已被撤销 / 网络异常)→ 抛错(残缺产物比失败更糟:页面白屏且难以诊断)。
|
|
74
|
+
*/
|
|
75
|
+
export declare function exportBundle(options: BundleExportOptions): Promise<ExportedBundle>;
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* fetch 拦截器,把 `/api/*` 请求经 `window.parent.postMessage` 转发到 IDE 主线程;主线程
|
|
7
7
|
* 用 `OfflineRunner` 执行**当前项目的云函数草稿**(改函数即生效,因为每次都实时取草稿),
|
|
8
8
|
* 再把执行结果 postMessage 回 iframe。与 origin 无关:blob URL 与异源 Nodebox 隧道都能用。
|
|
9
|
+
* 新窗口打开预览(window.open)时 window.parent === window,回退到 window.opener.postMessage,
|
|
10
|
+
* 主线程经 event.source 单播回响应——新窗口与 iframe 共用同一套协议。
|
|
9
11
|
*
|
|
10
12
|
* 消息协议(两侧逐字对齐;改任一侧必须同步另一侧——见 web-project-template.ts 的
|
|
11
13
|
* web/vite.config.ts 模板里那份逐字复制的 FN_PROXY_SCRIPT):
|
|
@@ -28,9 +30,21 @@ export interface OfflineRunner {
|
|
|
28
30
|
/**
|
|
29
31
|
* 浏览器端 fetch 拦截器脚本(IIFE,纯 JS——必须能直接内联进 `<script>`,不能有 TS / ESM)。
|
|
30
32
|
*
|
|
31
|
-
* **同步纪律**:本字符串与 `
|
|
32
|
-
* `
|
|
33
|
-
* vite-dev
|
|
33
|
+
* **同步纪律**:本字符串与 `packages/vite-plugin/src/ide-proxy.ts` 的
|
|
34
|
+
* `FN_FETCH_INTERCEPTOR_SCRIPT`(单一真相源)必须**逐字一致**(两处复制粘贴,无共享
|
|
35
|
+
* import——预览 iframe 与浏览器内 vite-dev 是两个独立加载上下文,共享一份源码反而会引入
|
|
36
|
+
* 打包耦合)。改这里务必同步另一侧。
|
|
37
|
+
*
|
|
38
|
+
* blob base 兼容(2026-09-11 修复):浏览器内 vite-dev 预览文档的 baseURI 是 blob: URL,
|
|
39
|
+
* `new URL('/api/x', 'blob:…')` 抛 TypeError → 拦截器退化成原生 fetch 报
|
|
40
|
+
* "Failed to parse URL"。首次解析失败时用 `window.location.origin`(blob 继承创建者
|
|
41
|
+
* origin)兜底再解析一次。
|
|
42
|
+
*
|
|
43
|
+
* 新窗口预览(2026-09-12):`window.open` 打开的新标签页 `window.parent === window`,
|
|
44
|
+
* 回退到 `window.opener.postMessage` 转发 /api/* 请求(不能用 noopener,否则 opener 为 null)。
|
|
45
|
+
* blob URL SPA 路由:patch `history.pushState/replaceState`,对相对路径直接吞掉(blob 不可
|
|
46
|
+
* 分层,原生 pushState 解析相对路径抛 TypeError),`location.pathname` 覆盖为当前路由路径,
|
|
47
|
+
* sessionStorage 持久化,刷新后恢复。
|
|
34
48
|
*/
|
|
35
49
|
export declare const FN_FETCH_INTERCEPTOR_SCRIPT: string;
|
|
36
50
|
/** IDE 主线程侧的消息处理器类型:直接挂到 `window.addEventListener('message', handler)`。 */
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export type { ModuleCache, RequireOptions } from './module-loader';
|
|
|
31
31
|
export { createViteServer, scanEsmSpecifiers, ViteDevError } from './vite-dev';
|
|
32
32
|
export type { SfcCompiler, ViteServer, ViteServerOptions, ViteCommandPort, ViteCommandOutcome, EsmSpecifierHit, } from './vite-dev';
|
|
33
33
|
export * as posix from './path';
|
|
34
|
+
export { exportBundle, findBlobRefs, defaultModuleName, BUNDLE_MODULE_PATH_PREFIX, } from './bundle-export';
|
|
35
|
+
export type { BundleExportOptions, ExportedBundle } from './bundle-export';
|
|
34
36
|
export { snapshotVfs, restoreVfs, entriesToTree, createMemoryFsBackend, createIndexedDbFsBackend, pickDefaultFsBackend, createFsPersistence, createMemoryKvStorage, createCommandHistory, } from './persistence';
|
|
35
37
|
export type { FsPersistEntry, FsPersistenceBackend, FsPersistence, FsPersistenceOptions, CommandHistory, KvStorage, } from './persistence';
|
|
36
38
|
export { SHELL_COMMANDS, EXTRA_COMMANDS, COMPLETABLE_COMMANDS, parseCompletionContext, completeWithListing, completeShellInput, completeContainerInput, applyCompletion, candidateLabel, } from './completion';
|