@deot/docs-playground 1.1.8 → 1.2.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
@@ -53,7 +53,7 @@ const files = ref({
53
53
  | `id` | `string` | `''` | 标题锚点 id;未传时从 `title` 自动生成。仅挂在内联 runtime 标题上,弹窗不重复。 |
54
54
  | `expandable` | `true \| number` | `undefined` | 开启预览高度展开;未传不显示控件;`true` 展开到剩余视口;正数为目标高度(px)。 |
55
55
  | `previewScroller` | `boolean` | `false` | 在 iframe 内用 `@deot/vc` Scroller 替换原生滚动条;开启后会动态探测 `Scroller`,不存在则回退原生滚动。 |
56
- | `local` | `boolean` | `true` | 在当前文档编译并挂载。内置模块默认继承宿主,可通过 import map 逐个覆盖;预览使用所选 Vue 运行时挂载。 |
56
+ | `local` | `boolean` | `true` | 在当前文档编译并挂载。业务模块通过运行时 ESM 加载,可通过 import map 逐个覆盖;预览使用所选 Vue 运行时挂载。 |
57
57
  | `options` | `PlaygroundOptions` | `{}` | 传给 Vue REPL store 的实例级选项;`cdnURL` 会同时作用于预览样式和默认 import map。 |
58
58
  | `previewOptions` | `SandboxProps['previewOptions']` | `undefined` | 传给当前 iframe 的 preview 选项。 |
59
59
  | `locale` | `Language` | `en-US` | 界面语言;未传入时使用上层 Locale Provider。 |
@@ -154,16 +154,25 @@ const handleMessage = async () => {
154
154
 
155
155
  ## 运行环境
156
156
 
157
- - iframe 运行时通过公共 CDN 加载 Vue 和默认 import map 中的依赖;local 运行时加载 URL 覆盖或宿主未提供的模块时也需要网络访问。
157
+ - iframe 运行时通过公共 CDN 加载 Vue 和默认 import map 中的依赖;local 运行时默认复用宿主 Vue 和 vue-router,业务包通过 ESM 加载,需要网络访问或配置可访问的本地 ESM 地址。
158
+ - iframe 模式初始化时会补齐宿主页缺失的 `globalThis.process` 和 `process.env`,兼容 `@vue/repl` 的 JSX/TSX 编译;已有对象及环境值保持不变,不模拟其他 Node API。
158
159
  - 默认 CDN 为 `https://cdn.jsdelivr.net/npm`;未使用 launch 时可通过 `options.cdnURL` 调整默认节点。launch 已选点时统一使用 `$docs.runtime.cdn`,优先于实例 `cdnURL`。
159
160
  - jsDelivr npm、旧 `unpkg.com` 和 `esm.unpkg.com` 地址在实际加载时转换,再经过可选 `$docs.resolve.cdn({ url })`。jsDelivr 转换入口使用 `/+esm`,ESM UNPKG 使用包或文件入口。预览 CSS、import map、裸包回退和 Sass 共用该规则;原始映射与缓存不被改写。
160
- - 直接渲染时,宿主未提供且未配置的 npm 模块(含作用域包和子路径)自动通过上述 ESM 规则加载;组件包的传递依赖也适用,`import()` 在调用时加载。可用 `options.builtinImportMap.imports` 指定依赖的版本和 URL。
161
+ - 直接渲染时,未配置的 npm 模块(含作用域包、版本和子路径)自动通过上述 ESM 规则加载,无需预先登记包名。组件包的传递依赖也适用,预览文件和远程模块中的 `import()` 在调用时加载。可用 `options.builtinImportMap.imports` 指定依赖的版本和 URL;显式 URL 加载失败会直接报错。
161
162
  - Vue SFC 的 `<style lang="scss">` / `lang="sass"` 以及独立 `.scss` / `.sass` 文件会在浏览器里编译;`_partial.scss` 只作为 `@use` 依赖。
162
- - local 模式按模块识别显式 URL 覆盖,即使地址与内置默认相同也会加载该 URL。覆盖 `vue` 时,预览的编译产物、挂载、组件引用和注入使用这份 Vue;内置组件、hooks、locale、Pinia 和 vue-router 随之重新加载以匹配 Vue,未覆盖的普通工具模块继续复用宿主。不同预览的覆盖互不影响,显式覆盖的模块在各自预览内执行。
163
+ - local 模式按模块识别显式 URL 覆盖,即使地址与内置默认相同也会加载该 URL。组件包和工具包统一经运行时加载器读取 ESM,不复用宿主的业务包副本。远程模块的 Vue 导入统一绑定到所选运行时;覆盖 `vue` 时,编译产物、挂载、组件引用、注入和 vue-router 使用对应的远程运行时。不同预览的覆盖互不影响,各预览独立执行远程模块。
163
164
  - local 模式在当前页面按 URL 缓存外部模块的下载和转换成果,并合并并发请求;切换文档时可复用,最多保留最近使用的 128 个模块。各预览仍独立执行模块并解析依赖覆盖;失败的加载不缓存,同源模块实时读取,刷新页面会清空缓存。
164
165
  - 每个 Playground 实例都使用自己的 preview 配置和 iframe 消息来源校验。
165
166
  - `local` 默认开启:同一份 `files` 在当前文档里编译。直接渲染不撑开预览高度,`expandable` 固定为关闭,`docs:playground.run` 不会申请临时高度;`<DocsLink>` 仍通过 `docs:navigate` 通知。`local: false` 改回 iframe Sandbox。
166
167
 
168
+ ## 运行时模块加载
169
+
170
+ 加载器按模块说明符解析依赖,不识别组件名,不维护可选依赖白名单。静态导入在挂载前准备,动态导入在实际调用时准备;组件库内部的可选依赖沿用组件自身的加载时机。因此普通示例不会因加载组件包而预先下载所有动态依赖,也无需给组件额外增加异步包装。
171
+
172
+ 显式模块地址优先于默认 CDN。CDN 返回的带版本依赖地址会保留版本;同名显式覆盖可以替换该依赖。失败后可再次调用动态导入或刷新预览重试,错误包含请求地址与原因;模块自身的执行异常继续报错。
173
+
174
+ 依赖须能作为浏览器 ESM 执行。若 CDN 转换产物无法正常运行,可通过现有模块配置指定包的原始 ESM 文件,无需增加包名适配。加载器不猜测 npm 包对应的全局变量名,也不向 `window` 写入依赖;若库自身优先读取已有全局实现,该行为由库保留。已在宿主页其他位置执行的原生 `import()` 不受接管,iframe Sandbox 继续使用自己的 import map 机制。
175
+
167
176
  ## 仓库内 examples
168
177
 
169
178
  在 monorepo 根目录执行 `npm run dev` 可预览 [`packages/playground/examples`](examples/) 下的单文件与多文件 Playground 示例。
@@ -1,4 +1,4 @@
1
- import { a as transformScript, c as createFileIndex, h as resolveEntry, m as normalizeVirtualPath, s as collectReachableFilenames, t as collectCss, u as getExtension, v as compileScssSource, y as extractScssExports } from "./css-C25yWQa_.js";
1
+ import { a as transformScript, c as createFileIndex, h as resolveEntry, m as normalizeVirtualPath, s as collectReachableFilenames, t as collectCss, u as getExtension, v as compileScssSource, y as extractScssExports } from "./css-sV_dM9Hl.js";
2
2
  import { compileString } from "sass";
3
3
  //#region packages/playground/src/core/runtime/local/compile/scss.ts
4
4
  /**
@@ -8,6 +8,7 @@ var DEFAULT_CDN_URL = "https://cdn.jsdelivr.net/npm";
8
8
  var NEW_SFC_CODE = `<script setup lang="ts"><\/script>\n\n<template>\n <div>\n <slot />\n <\/div>\n<\/template>\n`;
9
9
  //#endregion
10
10
  //#region packages/playground/src/cdn.ts
11
+ var DOCS_ESM_ENTRY = /^@deot\/docs-(?:client|locale|markdown|playground|renderer|theme)$/u;
11
12
  var PREVIEW_STYLE_ASSETS = [
12
13
  "@deot/style/dist/index.normalize-only.css",
13
14
  "@deot/vc-components/dist/index.style.css",
@@ -70,7 +71,7 @@ var npmCdnUrl = (value) => {
70
71
  };
71
72
  var cdnModuleName = (url) => {
72
73
  const parsed = npmCdnUrl(url);
73
- return parsed && (!parsed.path || parsed.path === "/+esm" || /^\/(?:es\d+|esnext)\/[^/]+\.m?js$/u.test(parsed.path)) ? parsed.name : null;
74
+ return parsed && (!parsed.path || parsed.path === "/+esm" || DOCS_ESM_ENTRY.test(parsed.name) && parsed.path === "/dist/index.js" || /^\/(?:es\d+|esnext)\/[^/]+\.m?js$/u.test(parsed.path)) ? parsed.name : null;
74
75
  };
75
76
  var isCdnEsmURL = (url) => /\/\+esm(?:[?#]|$)/u.test(url) || npmCdnUrl(url)?.url.hostname === "esm.unpkg.com";
76
77
  var resolveCdnURL = (url, cdnURL, config = docsCdnConfig()) => {
@@ -81,8 +82,10 @@ var resolveCdnURL = (url, cdnURL, config = docsCdnConfig()) => {
81
82
  const origin = normalizeCdnURL(target);
82
83
  const unpkg = new URL(origin).hostname === "esm.unpkg.com";
83
84
  let asset = parsed.asset;
84
- if (unpkg) asset = asset.replace(/\/\+esm$/u, "");
85
- else if (!parsed.path.endsWith("/+esm") && (!parsed.path || !/\.[^/]+$/u.test(parsed.path) || /unpkg\.com$/u.test(parsed.url.hostname) && /\.[cm]?[jt]sx?$/u.test(parsed.path))) asset = `${asset.replace(/\/+$/u, "")}/+esm`;
85
+ if (unpkg) {
86
+ asset = asset.replace(/\/\+esm$/u, "");
87
+ if (DOCS_ESM_ENTRY.test(parsed.name) && (!parsed.path || parsed.path === "/+esm")) asset += "/dist/index.js";
88
+ } else if (!parsed.path.endsWith("/+esm") && (!parsed.path || !/\.[^/]+$/u.test(parsed.path) || /unpkg\.com$/u.test(parsed.url.hostname) && /\.[cm]?[jt]sx?$/u.test(parsed.path))) asset = `${asset.replace(/\/+$/u, "")}/+esm`;
86
89
  converted = `${origin}/${asset}${parsed.url.search}${parsed.url.hash}`;
87
90
  }
88
91
  return config.resolve?.cdn?.({ url: converted }) || converted;
@@ -454,12 +457,14 @@ var isInsideRoot = (filename, root) => {
454
457
  /**
455
458
  * 静态 import / export-from / import() 的模块说明符。
456
459
  * @param code 源码文本。
460
+ * @param includeDynamic 是否收集动态导入;local 运行时在调用时单独加载。
457
461
  * @returns 按出现顺序收集到的说明符。
458
462
  */
459
- var listImportSpecifiers = (code) => {
463
+ var listImportSpecifiers = (code, includeDynamic = true) => {
460
464
  const result = [];
461
465
  const source = String(code || "");
462
- for (const match of source.matchAll(/(?:\bfrom\s+|import\s*\(\s*|import\s+)['"]([^'"]+)['"]/gu)) if (match[1]) result.push(match[1]);
466
+ const re = includeDynamic ? /(?:\bfrom\s+|import\s*\(\s*|import\s+)['"]([^'"]+)['"]/gu : /(?:\bfrom\s+|import\s+)['"]([^'"]+)['"]/gu;
467
+ for (const match of source.matchAll(re)) if (match[1]) result.push(match[1]);
463
468
  return result;
464
469
  };
465
470
  var isRelativeSpecifier = (id) => id === "." || id === ".." || id.startsWith("./") || id.startsWith("../") || id.startsWith("/");
@@ -486,12 +491,12 @@ var collectReachableFilenames = (files, entry) => {
486
491
  visit(entry);
487
492
  return ordered;
488
493
  };
489
- var collectBareSpecifiers = (files, filenames) => {
494
+ var collectBareSpecifiers = (files, filenames, includeDynamic = true) => {
490
495
  const index = createFileIndex(files);
491
496
  const result = [];
492
497
  const seen = /* @__PURE__ */ new Set();
493
498
  filenames.forEach((filename) => {
494
- listImportSpecifiers(index.get(normalizeVirtualPath(filename)) || "").forEach((specifier) => {
499
+ listImportSpecifiers(index.get(normalizeVirtualPath(filename)) || "", includeDynamic).forEach((specifier) => {
495
500
  if (!specifier || isRelativeSpecifier(specifier) || seen.has(specifier) || /\.(?:css|scss|sass)(?:[?#]|$)/.test(specifier)) return;
496
501
  seen.add(specifier);
497
502
  result.push(specifier);
@@ -501,7 +506,15 @@ var collectBareSpecifiers = (files, filenames) => {
501
506
  };
502
507
  //#endregion
503
508
  //#region packages/playground/src/core/runtime/local/compile/script.ts
504
- var { babelParse, walkIdentifiers, MagicString } = VueCompiler;
509
+ var { babelParse, walk, walkIdentifiers, MagicString } = VueCompiler;
510
+ var rewriteDynamicImports = (code) => {
511
+ if (!code.includes("import")) return code;
512
+ const source = new MagicString(code);
513
+ walk(babelParse(code, { sourceType: "module" }), { enter(node) {
514
+ if (node.type === "ImportExpression" || node.type === "CallExpression" && node.callee?.type === "Import") source.overwrite(node.start, node.start + 6, "__docs_import");
515
+ } });
516
+ return source.toString();
517
+ };
505
518
  var rewriteJsxAttributes = (code) => {
506
519
  const replacements = [];
507
520
  for (const match of code.matchAll(/\b([\w-]+:[\w-]+)\s*=\s*\{/g)) {
@@ -580,15 +593,17 @@ var transformScript = (code, filename, options = {}) => {
580
593
  try {
581
594
  const script = wantTs ? removeImportedTypeExports(code) : code;
582
595
  const source = options.jsx ? `import { h as __docs_h, Fragment as __docs_Fragment } from 'vue';\n${rewriteJsxAttributes(script)}` : script;
583
- return `${rewriteImportMeta(transform(source, {
596
+ const result = transform(source, {
584
597
  transforms,
598
+ preserveDynamicImport: true,
585
599
  disableESTransforms: true,
586
600
  production: true,
587
601
  jsxRuntime: "classic",
588
602
  jsxPragma: "__docs_h",
589
603
  jsxFragmentPragma: "__docs_Fragment",
590
604
  filePath: filename
591
- }).code, filename)}\n//# sourceURL=playground://${filename}`;
605
+ });
606
+ return `${rewriteImportMeta(options.esm ? result.code : rewriteDynamicImports(result.code), filename)}\n//# sourceURL=playground://${filename}`;
592
607
  } catch (error) {
593
608
  const message = error instanceof Error ? error.message : String(error);
594
609
  throw new Error(`[Playground] 脚本转换失败 ${filename}: ${message}`, { cause: error });
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
- import { A as DEFAULT_ENTRY, C as createBuiltinStyles, D as resolveCdnURL, E as normalizeCdnURL, O as resolveNpmImport, S as createBuiltinImports, T as isCdnEsmURL, _ as bindPlaygroundScss, a as transformScript, b as whenSassReady, c as createFileIndex, d as isIgnoredFilename, f as isInsideRoot, g as resolveModulePath, i as rewriteImportMeta, j as NEW_SFC_CODE, k as DEFAULT_CDN_URL, l as getCommonRoot, m as normalizeVirtualPath, n as injectStyle, o as collectBareSpecifiers, p as isRelativeSpecifier, r as removeStyle, s as collectReachableFilenames, w as createCdnResolver, x as cdnModuleName } from "./css-C25yWQa_.js";
1
+ import { A as DEFAULT_ENTRY, C as createBuiltinStyles, D as resolveCdnURL, E as normalizeCdnURL, O as resolveNpmImport, S as createBuiltinImports, T as isCdnEsmURL, _ as bindPlaygroundScss, a as transformScript, b as whenSassReady, c as createFileIndex, d as isIgnoredFilename, f as isInsideRoot, g as resolveModulePath, i as rewriteImportMeta, j as NEW_SFC_CODE, k as DEFAULT_CDN_URL, l as getCommonRoot, m as normalizeVirtualPath, n as injectStyle, o as collectBareSpecifiers, p as isRelativeSpecifier, r as removeStyle, s as collectReachableFilenames, w as createCdnResolver, x as cdnModuleName } from "./css-sV_dM9Hl.js";
2
2
  import * as Vue from "vue";
3
3
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineAsyncComponent, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, shallowRef, toDisplayString, unref, vModelText, vShow, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
4
- import * as Vc from "@deot/vc";
5
4
  import { Clipboard, Dropdown, DropdownItem, DropdownMenu, Message, Popconfirm, Portal, Scroller, TransitionFade } from "@deot/vc";
6
5
  import { provideLocale, useLocale } from "@deot/docs-locale";
7
6
  import hljs from "highlight.js";
@@ -15,9 +14,6 @@ import { init, parse } from "es-module-lexer";
15
14
  import { transform } from "sucrase";
16
15
  import * as VueCompiler from "@vue/compiler-sfc/dist/compiler-sfc.esm-browser.js";
17
16
  import * as VueRouter from "vue-router";
18
- import * as Pinia from "pinia";
19
- import * as VcComponents from "@deot/vc-components";
20
- import * as VcHooks from "@deot/vc-hooks";
21
17
  //#region \0rolldown/runtime.js
22
18
  var __defProp = Object.defineProperty;
23
19
  var __exportAll = (all, no_symbols) => {
@@ -2270,6 +2266,9 @@ var createRuntimeImports = (cdnURL, builtinImportMap) => ({
2270
2266
  ...createRuntimeImportOverrides(builtinImportMap)
2271
2267
  });
2272
2268
  var createRuntimeStore = (files, entry, options) => {
2269
+ const host = globalThis;
2270
+ const replProcess = host.process ??= {};
2271
+ replProcess.env ??= {};
2273
2272
  const { cdnURL, builtinImportMap, ...storeOptions } = options;
2274
2273
  const replFiles = ref(Object.fromEntries(Object.entries(files).map(([filename, code]) => {
2275
2274
  const file = createReplFile(filename, code);
@@ -2698,56 +2697,6 @@ var formatPlaygroundRuntimeError = (error) => {
2698
2697
  return text;
2699
2698
  };
2700
2699
  //#endregion
2701
- //#region packages/playground/src/core/runtime/local/builtins.ts
2702
- var loaders = {
2703
- "vue": () => import("vue"),
2704
- "vue-router": () => import("vue-router"),
2705
- "pinia": async () => Pinia,
2706
- "@deot/helper": () => import("@deot/helper"),
2707
- "@deot/helper-cache": () => import("@deot/helper-cache"),
2708
- "@deot/helper-device": () => import("@deot/helper-device"),
2709
- "@deot/helper-dom": () => import("@deot/helper-dom"),
2710
- "@deot/helper-emitter": () => import("@deot/helper-emitter"),
2711
- "@deot/helper-fp": () => import("@deot/helper-fp"),
2712
- "@deot/helper-is": () => import("@deot/helper-is"),
2713
- "@deot/helper-load": () => import("@deot/helper-load"),
2714
- "@deot/helper-resize": () => import("@deot/helper-resize"),
2715
- "@deot/helper-route": () => import("@deot/helper-route"),
2716
- "@deot/helper-scheduler": () => import("@deot/helper-scheduler"),
2717
- "@deot/helper-shared": () => import("@deot/helper-shared"),
2718
- "@deot/helper-unicode": () => import("@deot/helper-unicode"),
2719
- "@deot/helper-utils": () => import("@deot/helper-utils"),
2720
- "@deot/helper-validator": () => import("@deot/helper-validator"),
2721
- "@deot/helper-wheel": () => import("@deot/helper-wheel"),
2722
- "@deot/http-client": () => import("@deot/http-client"),
2723
- "@deot/http-core": () => import("@deot/http-core"),
2724
- "@deot/http-hooks": () => import("@deot/http-hooks"),
2725
- "@deot/http": () => import("@deot/http"),
2726
- "@deot/vc": async () => Vc,
2727
- "@deot/vc-components": async () => VcComponents,
2728
- "@deot/vc-hooks": async () => VcHooks,
2729
- "@deot/vc-locale": () => import("@deot/vc-locale"),
2730
- "lodash-es": () => import("lodash-es"),
2731
- "echarts": () => import("echarts"),
2732
- "photoswipe": () => import("photoswipe"),
2733
- "jszip": () => import("jszip"),
2734
- "jspdf": () => import("jspdf"),
2735
- "dayjs": async () => (await import("dayjs")).default
2736
- };
2737
- var cache = /* @__PURE__ */ new Map();
2738
- /** 直接渲染可从宿主包注入的说明符;与 Vue 无关的大型依赖按需加载。 */
2739
- var localBuiltinIds = new Set(Object.keys(loaders));
2740
- var ensureLocalBuiltin = (id) => {
2741
- const loader = loaders[id];
2742
- if (!loader) return Promise.reject(/* @__PURE__ */ new Error(`[Playground] 没有本地模块 "${id}"`));
2743
- let pending = cache.get(id);
2744
- if (!pending) {
2745
- pending = loader();
2746
- cache.set(id, pending);
2747
- }
2748
- return pending;
2749
- };
2750
- //#endregion
2751
2700
  //#region packages/playground/src/core/runtime/local/modules.ts
2752
2701
  var SKIP_COPY_KEYS = /* @__PURE__ */ new Set([
2753
2702
  "prototype",
@@ -2817,15 +2766,10 @@ var resolveHostModule = (id, modules) => {
2817
2766
  }
2818
2767
  return null;
2819
2768
  };
2820
- var HOST_VUE_SPECIFIERS = /* @__PURE__ */ new Set(["vue", "vue/server-renderer"]);
2821
- var VUE_DEPENDENT_BUILTINS = /* @__PURE__ */ new Set([
2769
+ var HOST_MODULES = /* @__PURE__ */ new Set([
2770
+ "vue",
2822
2771
  "vue/server-renderer",
2823
- "vue-router",
2824
- "pinia",
2825
- "@deot/vc",
2826
- "@deot/vc-components",
2827
- "@deot/vc-hooks",
2828
- "@deot/vc-locale"
2772
+ "vue-router"
2829
2773
  ]);
2830
2774
  var messageOf = (error) => error instanceof Error ? error.message : String(error);
2831
2775
  /**
@@ -2845,13 +2789,13 @@ var resolveImportTarget = (id, imports) => {
2845
2789
  cursor = parent;
2846
2790
  }
2847
2791
  };
2848
- var prefersLocalBuiltin = (id, imports, overrides = new Set(Object.keys(imports))) => {
2849
- if (!localBuiltinIds.has(id) && !HOST_VUE_SPECIFIERS.has(id)) return false;
2792
+ var usesHostRuntime = (id, imports, overrides = new Set(Object.keys(imports))) => {
2793
+ if (!HOST_MODULES.has(id)) return false;
2850
2794
  if (overrides.has(id)) return false;
2851
- return !VUE_DEPENDENT_BUILTINS.has(id) || !overrides.has("vue");
2795
+ return id === "vue" || !overrides.has("vue");
2852
2796
  };
2853
2797
  /**
2854
- * 内置依赖默认注入宿主副本,显式 URL 按模块覆盖,包括与默认 CDN 相同的地址。
2798
+ * Vue 运行时默认注入宿主副本,其他模块按显式 URL 或默认 CDN 加载。
2855
2799
  * @param specifiers 本次预览用到的裸模块说明符。
2856
2800
  * @param imports 合并后的 import map。
2857
2801
  * @param builtins 宿主 Vue 与 vue-router。
@@ -2868,43 +2812,18 @@ var loadLocalModules = async (specifiers, imports, builtins, loader, overrides =
2868
2812
  };
2869
2813
  const errors = [];
2870
2814
  const targets = /* @__PURE__ */ new Set();
2871
- const localIds = /* @__PURE__ */ new Set();
2872
- specifiers.forEach((specifier) => {
2873
- let local = false;
2874
- if (prefersLocalBuiltin(specifier, imports, overrides)) localIds.add(specifier);
2875
- else {
2876
- let cursor = specifier;
2877
- while (true) {
2878
- const slash = cursor.lastIndexOf("/");
2879
- if (slash <= 0) break;
2880
- const parent = cursor.slice(0, slash);
2881
- if (prefersLocalBuiltin(parent, imports, overrides)) {
2882
- localIds.add(parent);
2883
- local = true;
2884
- break;
2885
- }
2886
- cursor = parent;
2887
- }
2888
- }
2889
- const target = resolveImportTarget(specifier, imports);
2890
- if (target && prefersLocalBuiltin(target, imports, overrides)) localIds.add(target);
2891
- else if (target) targets.add(target);
2892
- else if (!local && !localIds.has(specifier)) {
2893
- imports[specifier] = resolveNpmImport(specifier);
2894
- targets.add(specifier);
2895
- }
2896
- });
2897
- await Promise.all([...localIds].map(async (id) => {
2898
- if (id === "vue" || id === "vue-router") return;
2899
- if (id === "vue/server-renderer") {
2900
- modules[id] = builtins.vue;
2901
- return;
2815
+ for (const specifier of specifiers) {
2816
+ if (usesHostRuntime(specifier, imports, overrides)) {
2817
+ if (specifier === "vue/server-renderer") modules[specifier] = builtins.vue;
2818
+ continue;
2902
2819
  }
2903
- modules[id] = await ensureLocalBuiltin(id);
2904
- }));
2820
+ const target = resolveImportTarget(specifier, imports) || specifier;
2821
+ imports[target] ||= resolveNpmImport(target);
2822
+ targets.add(target);
2823
+ }
2905
2824
  for (const id of targets) {
2906
2825
  const url = imports[id];
2907
- if (!url || prefersLocalBuiltin(id, imports, overrides)) continue;
2826
+ if (!url || usesHostRuntime(id, imports, overrides)) continue;
2908
2827
  try {
2909
2828
  modules[id] = await loader(url);
2910
2829
  } catch (error) {
@@ -3040,7 +2959,11 @@ var linkPlayground = (compiled, files, options = {}) => {
3040
2959
  cache.set(key, module.exports);
3041
2960
  visiting.add(key);
3042
2961
  try {
3043
- new Function("require", "module", "exports", mod.js)(requireFn(key), module, module.exports);
2962
+ new Function("require", "module", "exports", "__docs_import", mod.js)(requireFn(key), module, module.exports, async (id) => {
2963
+ if (isRelativeSpecifier(id) || resolveHostModule(id, modules)) return requireFn(key)(id);
2964
+ if (options.loadModule) return toHostModule(await options.loadModule(id));
2965
+ return requireFn(key)(id);
2966
+ });
3044
2967
  cache.set(key, module.exports);
3045
2968
  return module.exports;
3046
2969
  } catch (error) {
@@ -3080,7 +3003,6 @@ var linkPlayground = (compiled, files, options = {}) => {
3080
3003
  };
3081
3004
  //#endregion
3082
3005
  //#region packages/playground/src/core/runtime/local/remote.ts
3083
- var HOST_VUE = /* @__PURE__ */ new Set(["vue", "vue/server-renderer"]);
3084
3006
  var moduleUrl = (url) => new URL(url, globalThis.location?.href).href;
3085
3007
  var REMOTE_SOURCE_CACHE_LIMIT = 128;
3086
3008
  var sourceCache = /* @__PURE__ */ new Map();
@@ -3125,54 +3047,25 @@ var readRemoteSource = (url) => {
3125
3047
  * @param imports 合并后的 import map。
3126
3048
  * @param builtins 宿主 Vue 与 vue-router。
3127
3049
  * @param overrides 显式配置的模块名,与自动补齐的默认地址区分。
3128
- * @param importModule 浏览器原生 ESM 加载器。
3129
3050
  * @param cdnURL 没有 launch 选点结果时使用的实例 CDN。
3130
3051
  * @returns 按 URL 加载并执行远程模块的加载器。
3131
3052
  */
3132
- var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imports)), importModule = (url) => import(
3133
- /* @vite-ignore */
3134
- url
3135
- ), cdnURL) => {
3053
+ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imports)), cdnURL) => {
3136
3054
  const resolve = createCdnResolver(cdnURL);
3137
3055
  const resolveUrl = (url) => resolve(moduleUrl(url));
3138
3056
  const hostModules = {
3139
3057
  "vue": builtins.vue,
3058
+ "vue/server-renderer": builtins.vue,
3140
3059
  "vue-router": builtins.vueRouter
3141
3060
  };
3142
- const pendingBuiltins = /* @__PURE__ */ new Set();
3143
3061
  const sources = /* @__PURE__ */ new Map();
3144
3062
  const pendingSources = /* @__PURE__ */ new Map();
3145
3063
  const evaluated = /* @__PURE__ */ new Map();
3146
- const nativeModules = /* @__PURE__ */ new Map();
3147
- const loadNative = (url) => {
3148
- let pending = nativeModules.get(url);
3149
- if (!pending) {
3150
- pending = importModule(url).then((module) => {
3151
- const exports = toHostModule(module);
3152
- evaluated.set(url, exports);
3153
- return exports;
3154
- });
3155
- nativeModules.set(url, pending);
3156
- }
3157
- return pending;
3158
- };
3159
- const localBuiltinId = (id) => {
3160
- if (imports[id] && !prefersLocalBuiltin(id, imports, overrides)) return null;
3161
- if (prefersLocalBuiltin(id, imports, overrides)) return id;
3162
- let cursor = id;
3163
- while (true) {
3164
- const slash = cursor.lastIndexOf("/");
3165
- if (slash <= 0) return null;
3166
- const parent = cursor.slice(0, slash);
3167
- if (prefersLocalBuiltin(parent, imports, overrides)) return parent;
3168
- cursor = parent;
3169
- }
3170
- };
3171
3064
  const dependencySpecifier = (from, specifier) => {
3172
3065
  if (isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier)) {
3173
3066
  const url = new URL(specifier, from);
3174
3067
  const name = cdnModuleName(url.href);
3175
- if (name && (imports[name] || localBuiltinIds.has(name))) return name;
3068
+ if (name && (overrides.has(name) || usesHostRuntime(name, imports, overrides))) return name;
3176
3069
  return url.href;
3177
3070
  }
3178
3071
  return specifier;
@@ -3180,35 +3073,21 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3180
3073
  const dependencyUrl = (from, id) => {
3181
3074
  const specifier = dependencySpecifier(from, id);
3182
3075
  if (/^https?:\/\//.test(specifier)) return resolveUrl(specifier);
3183
- if (imports[specifier] && !prefersLocalBuiltin(specifier, imports, overrides)) return resolveUrl(imports[specifier]);
3184
- if (HOST_VUE.has(specifier) && prefersLocalBuiltin(specifier, imports, overrides)) return null;
3185
- let cursor = specifier;
3186
- while (cursor) {
3187
- if (localBuiltinIds.has(cursor) && prefersLocalBuiltin(cursor, imports, overrides)) {
3188
- pendingBuiltins.add(cursor);
3189
- return null;
3190
- }
3191
- const slash = cursor.lastIndexOf("/");
3192
- if (slash <= 0) break;
3193
- cursor = cursor.slice(0, slash);
3194
- }
3076
+ if (imports[specifier] && !usesHostRuntime(specifier, imports, overrides)) return resolveUrl(imports[specifier]);
3077
+ if (usesHostRuntime(specifier, imports, overrides)) return null;
3195
3078
  const target = imports[specifier] ? specifier : resolveImportTarget(specifier, imports);
3196
- if (!target) {
3197
- imports[specifier] = resolveNpmImport(specifier);
3198
- return resolveUrl(imports[specifier]);
3199
- }
3079
+ if (!target) return resolveUrl(resolveNpmImport(specifier));
3200
3080
  return resolveUrl(imports[target]);
3201
3081
  };
3202
3082
  const crawl = async (url, visited = /* @__PURE__ */ new Set()) => {
3203
- if (!overrides.size && isCdnEsmURL(url)) {
3204
- await loadNative(url);
3205
- return;
3206
- }
3207
3083
  if (visited.has(url)) return;
3208
3084
  visited.add(url);
3209
3085
  let pending = pendingSources.get(url);
3210
3086
  if (!pending) {
3211
- pending = readRemoteSource(url);
3087
+ pending = readRemoteSource(url).catch((error) => {
3088
+ pendingSources.delete(url);
3089
+ throw error;
3090
+ });
3212
3091
  pendingSources.set(url, pending);
3213
3092
  }
3214
3093
  const source = await pending;
@@ -3220,12 +3099,10 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3220
3099
  };
3221
3100
  const requireFrom = (from) => (specifier) => {
3222
3101
  const id = dependencySpecifier(from, specifier);
3223
- if (HOST_VUE.has(id) && prefersLocalBuiltin(id, imports, overrides)) return toHostModule(builtins.vue);
3224
- const builtinId = localBuiltinId(id);
3225
- if (builtinId) return toHostModule(hostModules[builtinId]);
3102
+ if (usesHostRuntime(id, imports, overrides)) return toHostModule(hostModules[id]);
3226
3103
  if (isRelativeSpecifier(id) || /^https?:\/\//.test(id)) return evaluate(dependencyUrl(from, id));
3227
3104
  const target = imports[id] ? id : resolveImportTarget(id, imports);
3228
- if (!target || !imports[target]) throw new Error(`[Playground] 未注册的模块 "${id}"`);
3105
+ if (!target) return evaluate(dependencyUrl(from, id));
3229
3106
  const loaded = evaluate(resolveUrl(imports[target]));
3230
3107
  if (target === id) return loaded;
3231
3108
  if (!resolveHostModule(id, { [target]: loaded }))
@@ -3237,18 +3114,19 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3237
3114
  if (evaluated.has(url)) return evaluated.get(url);
3238
3115
  const source = sources.get(url);
3239
3116
  if (!source) throw new Error(`[Playground] 远程模块尚未下载: ${url}`);
3240
- const module = { exports: {} };
3117
+ const module = { exports: Object.defineProperty({}, "__esModule", {
3118
+ value: true,
3119
+ writable: true
3120
+ }) };
3241
3121
  evaluated.set(url, module.exports);
3242
3122
  const runner = source.run;
3243
- runner(requireFrom(url), module, module.exports, async (id) => {
3244
- const next = dependencyUrl(url, id);
3245
- if (next) await load(next);
3246
- else {
3247
- const builtinId = localBuiltinId(dependencySpecifier(url, id));
3248
- if (builtinId && !(builtinId in hostModules)) hostModules[builtinId] = await ensureLocalBuiltin(builtinId);
3249
- }
3250
- return requireFrom(url)(id);
3251
- });
3123
+ try {
3124
+ runner(requireFrom(url), module, module.exports, (id) => loadModule(id, url));
3125
+ } catch (error) {
3126
+ evaluated.delete(url);
3127
+ throw error;
3128
+ }
3129
+ module.exports.__esModule = true;
3252
3130
  evaluated.set(url, module.exports);
3253
3131
  return module.exports;
3254
3132
  };
@@ -3259,16 +3137,23 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3259
3137
  if (pending) return pending;
3260
3138
  const task = (async () => {
3261
3139
  await crawl(url);
3262
- await Promise.all([...pendingBuiltins].map(async (id) => {
3263
- if (id === "vue" || id === "vue-router") return;
3264
- hostModules[id] = await ensureLocalBuiltin(id);
3265
- }));
3266
3140
  return evaluate(url);
3267
- })();
3141
+ })().catch((error) => {
3142
+ loading.delete(url);
3143
+ throw new Error(`[Playground] 无法加载模块 (${url}): ${error instanceof Error ? error.message : String(error)}`, { cause: error });
3144
+ });
3268
3145
  loading.set(url, task);
3269
3146
  return task;
3270
3147
  };
3271
- return { load };
3148
+ const loadModule = async (id, from = globalThis.location?.href) => {
3149
+ const url = dependencyUrl(from, id);
3150
+ if (url) await load(url);
3151
+ return requireFrom(from)(id);
3152
+ };
3153
+ return {
3154
+ load,
3155
+ loadModule
3156
+ };
3272
3157
  };
3273
3158
  //#endregion
3274
3159
  //#region packages/playground/src/core/runtime/local/local-sandbox.vue?vue&type=script&setup=true&lang.ts
@@ -3414,7 +3299,7 @@ var local_sandbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
3414
3299
  publishError();
3415
3300
  destroyPreview();
3416
3301
  try {
3417
- const { compilePlayground } = await import("./compile-BHlM2G35.js");
3302
+ const { compilePlayground } = await import("./compile-D33div4Y.js");
3418
3303
  /* istanbul ignore if -- 并发编译时只保留最后一次 */
3419
3304
  if (current !== version) return;
3420
3305
  const compiled = await compilePlayground(props.files, { entry: props.entry });
@@ -3427,13 +3312,13 @@ var local_sandbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
3427
3312
  }
3428
3313
  const imports = createRuntimeImports(props.options.cdnURL, props.options.builtinImportMap);
3429
3314
  const overrides = new Set(Object.keys(createRuntimeImportOverrides(props.options.builtinImportMap)));
3430
- const specifiers = collectBareSpecifiers(props.files, collectReachableFilenames(props.files, compiled.entry));
3315
+ const specifiers = collectBareSpecifiers(props.files, collectReachableFilenames(props.files, compiled.entry), false);
3431
3316
  specifiers.push("vue");
3432
3317
  if (props.previewScroller === true) specifiers.push("@deot/vc");
3433
3318
  const remote = createRemoteLoader(imports, {
3434
3319
  vue: Vue,
3435
3320
  vueRouter: VueRouter
3436
- }, overrides, void 0, props.options.cdnURL);
3321
+ }, overrides, props.options.cdnURL);
3437
3322
  const loaded = await loadLocalModules(specifiers, imports, {
3438
3323
  vue: Vue,
3439
3324
  vueRouter: VueRouter
@@ -3445,7 +3330,10 @@ var local_sandbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
3445
3330
  publishError();
3446
3331
  return;
3447
3332
  }
3448
- const linked = linkPlayground(compiled, props.files, { modules: loaded.modules });
3333
+ const linked = linkPlayground(compiled, props.files, {
3334
+ modules: loaded.modules,
3335
+ loadModule: remote.loadModule
3336
+ });
3449
3337
  /* istanbul ignore if -- 并发编译时只保留最后一次 */
3450
3338
  if (current !== version) return;
3451
3339
  if (linked.errors.length || !linked.component) {
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@deot/docs-playground",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
+ "type": "module",
4
5
  "main": "dist/index.js",
5
6
  "types": "dist/index.d.ts",
6
7
  "exports": {
@@ -18,8 +19,8 @@
18
19
  "access": "public"
19
20
  },
20
21
  "dependencies": {
21
- "@deot/docs-locale": "^1.1.5",
22
- "@deot/docs-theme": "^1.1.5",
22
+ "@deot/docs-locale": "^1.2.0",
23
+ "@deot/docs-theme": "^1.2.0",
23
24
  "@deot/helper": "^1.1.21",
24
25
  "@deot/http": "^1.1.10",
25
26
  "@deot/vc": "^1.2.3",
@@ -30,15 +31,8 @@
30
31
  "@vue/compiler-sfc": "^3.5.43",
31
32
  "@vue/repl": "^4.7.2",
32
33
  "codemirror": "^6.0.2",
33
- "dayjs": "^1.11.23",
34
- "echarts": "^6.1.0",
35
34
  "es-module-lexer": "^3.0.3",
36
35
  "highlight.js": "^11.12.0",
37
- "jspdf": "^4.2.1",
38
- "jszip": "^3.10.2",
39
- "lodash-es": "^4.18.1",
40
- "photoswipe": "^5.4.4",
41
- "pinia": "^4.0.3",
42
36
  "sass": "^1.105.1",
43
37
  "sucrase": "^3.35.1",
44
38
  "vue-router": "^5.4.0"