@deot/docs-playground 1.1.6 → 1.1.8

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
@@ -145,6 +145,7 @@ const handleMessage = async () => {
145
145
  | `slugifyPlaygroundTitle(title)` / `resolvePlaygroundTitleId(title, id?)` | 标题锚点 id,规则对齐 markdown-it-anchor。 |
146
146
  | `DEFAULT_CDN_URL` | 默认 CDN 根地址(jsDelivr)。 |
147
147
  | `createBuiltinImports(cdnURL?)` / `createBuiltinStyles(cdnURL?)` | 按 CDN 生成内置 import map 与预览样式表 URL。 |
148
+ | `resolveCdnURL(url, cdnURL?, config?)` | 内置转换后调用可选 `resolve.cdn`;默认读取当前站点配置。 |
148
149
  | `normalizeCdnURL(value?)` | 规范化 CDN 根地址。 |
149
150
 
150
151
  同时导出 `PlaygroundFiles`、`PlaygroundView`、`PlaygroundViewport`、`PlaygroundOptions`、`PlaygroundPreviewOptions`、`EditorFilesChange` 和 `EditorFilesChangeAction` 类型。
@@ -154,11 +155,12 @@ const handleMessage = async () => {
154
155
  ## 运行环境
155
156
 
156
157
  - iframe 运行时通过公共 CDN 加载 Vue 和默认 import map 中的依赖;local 运行时加载 URL 覆盖或宿主未提供的模块时也需要网络访问。
157
- - 默认 CDN 为 `https://cdn.jsdelivr.net/npm`,可通过 `options.cdnURL` 换成 unpkg 等兼容 `/{package}/{file}` 路径的镜像;该地址用于预览样式和内置 import map。
158
- - `lodash-es` 和在线 Sass 编译器使用 jsDelivr 的 `/+esm` 入口;unpkg 等 CDN 不支持该路径。
159
- - 直接渲染时,宿主未提供且未配置的 npm 模块(含作用域包和子路径)自动从 jsDelivr 的 `/+esm` 入口加载;组件包的传递依赖也适用,`import()` 在调用时加载。可用 `options.builtinImportMap.imports` 指定依赖的版本和 URL。
158
+ - 默认 CDN 为 `https://cdn.jsdelivr.net/npm`;未使用 launch 时可通过 `options.cdnURL` 调整默认节点。launch 已选点时统一使用 `$docs.runtime.cdn`,优先于实例 `cdnURL`。
159
+ - 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。
160
161
  - Vue SFC 的 `<style lang="scss">` / `lang="sass"` 以及独立 `.scss` / `.sass` 文件会在浏览器里编译;`_partial.scss` 只作为 `@use` 依赖。
161
162
  - local 模式按模块识别显式 URL 覆盖,即使地址与内置默认相同也会加载该 URL。覆盖 `vue` 时,预览的编译产物、挂载、组件引用和注入使用这份 Vue;内置组件、hooks、locale、Pinia 和 vue-router 随之重新加载以匹配 Vue,未覆盖的普通工具模块继续复用宿主。不同预览的覆盖互不影响,显式覆盖的模块在各自预览内执行。
163
+ - local 模式在当前页面按 URL 缓存外部模块的下载和转换成果,并合并并发请求;切换文档时可复用,最多保留最近使用的 128 个模块。各预览仍独立执行模块并解析依赖覆盖;失败的加载不缓存,同源模块实时读取,刷新页面会清空缓存。
162
164
  - 每个 Playground 实例都使用自己的 preview 配置和 iframe 消息来源校验。
163
165
  - `local` 默认开启:同一份 `files` 在当前文档里编译。直接渲染不撑开预览高度,`expandable` 固定为关闭,`docs:playground.run` 不会申请临时高度;`<DocsLink>` 仍通过 `docs:navigate` 通知。`local: false` 改回 iframe Sandbox。
164
166
 
@@ -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-DleINdoA.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-C25yWQa_.js";
2
2
  import { compileString } from "sass";
3
3
  //#region packages/playground/src/core/runtime/local/compile/scss.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- const require_css = require("./css-DpqJBQ5c.cjs");
1
+ const require_css = require("./css-O1VYXZtT.cjs");
2
2
  let sass = require("sass");
3
3
  //#region packages/playground/src/core/runtime/local/compile/scss.ts
4
4
  /**
@@ -53,6 +53,51 @@ var createBuiltinImports = (cdnURL = DEFAULT_CDN_URL) => ({
53
53
  });
54
54
  var createBuiltinStyles = (cdnURL = DEFAULT_CDN_URL) => Object.fromEntries(PREVIEW_STYLE_ASSETS.map((asset) => [asset, cdnAsset(cdnURL, asset)]));
55
55
  var resolveNpmImport = (specifier) => /^https?:\/\//.test(specifier) ? specifier : jsdelivrEsmURL(`${specifier}/+esm`);
56
+ var docsCdnConfig = () => globalThis.window?.$docs || {};
57
+ var npmCdnUrl = (value) => {
58
+ if (!/^https?:\/\//i.test(value)) return null;
59
+ const url = new URL(value);
60
+ const prefix = /(?:^|\.)jsdelivr\.net$/u.test(url.hostname) && url.pathname.startsWith("/npm/") ? "/npm/" : /^(?:esm\.)?unpkg\.com$/u.test(url.hostname) ? "/" : null;
61
+ if (!prefix) return null;
62
+ const asset = url.pathname.slice(prefix.length);
63
+ const match = asset.match(/^((?:@[^/]+\/)?[^/]+)(.*)$/u);
64
+ return match ? {
65
+ url,
66
+ asset,
67
+ name: match[1].replace(/@[^/@]+$/u, ""),
68
+ path: match[2]
69
+ } : null;
70
+ };
71
+ var cdnModuleName = (url) => {
72
+ 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
+ };
75
+ var isCdnEsmURL = (url) => /\/\+esm(?:[?#]|$)/u.test(url) || npmCdnUrl(url)?.url.hostname === "esm.unpkg.com";
76
+ var resolveCdnURL = (url, cdnURL, config = docsCdnConfig()) => {
77
+ const parsed = npmCdnUrl(url);
78
+ const target = config.runtime?.cdn || cdnURL;
79
+ let converted = url;
80
+ if (parsed && target) {
81
+ const origin = normalizeCdnURL(target);
82
+ const unpkg = new URL(origin).hostname === "esm.unpkg.com";
83
+ 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`;
86
+ converted = `${origin}/${asset}${parsed.url.search}${parsed.url.hash}`;
87
+ }
88
+ return config.resolve?.cdn?.({ url: converted }) || converted;
89
+ };
90
+ var createCdnResolver = (cdnURL) => {
91
+ const resolved = /* @__PURE__ */ new Map();
92
+ return (url) => {
93
+ if (!resolved.has(url)) {
94
+ const target = resolveCdnURL(url, cdnURL);
95
+ resolved.set(url, target);
96
+ resolved.set(target, target);
97
+ }
98
+ return resolved.get(url);
99
+ };
100
+ };
56
101
  //#endregion
57
102
  //#region packages/playground/src/core/scss.ts
58
103
  var SCHEME = "docs-playground";
@@ -84,7 +129,7 @@ var resolveSassModule = async (url) => {
84
129
  return sass;
85
130
  };
86
131
  var loadSass = () => {
87
- loadPromise ??= (() => resolveSassModule(SASS_CDN_URL))().then((sass) => {
132
+ loadPromise ??= (() => resolveSassModule(resolveCdnURL(SASS_CDN_URL)))().then((sass) => {
88
133
  loadedSass = sass;
89
134
  return sass;
90
135
  });
@@ -577,4 +622,4 @@ var removeStyle = (id) => {
577
622
  document.querySelector(`style[data-playground-id="${id}"]`)?.remove();
578
623
  };
579
624
  //#endregion
580
- export { normalizeCdnURL as C, NEW_SFC_CODE as D, DEFAULT_ENTRY as E, createBuiltinStyles as S, DEFAULT_CDN_URL as T, bindPlaygroundScss as _, transformScript as a, whenSassReady as b, createFileIndex as c, isIgnoredFilename as d, isInsideRoot as f, resolveModulePath as g, resolveEntry as h, rewriteImportMeta as i, getCommonRoot as l, normalizeVirtualPath as m, injectStyle as n, collectBareSpecifiers as o, isRelativeSpecifier as p, removeStyle as r, collectReachableFilenames as s, collectCss as t, getExtension as u, compileScssSource as v, resolveNpmImport as w, createBuiltinImports as x, extractScssExports as y };
625
+ export { DEFAULT_ENTRY as A, createBuiltinStyles as C, resolveCdnURL as D, normalizeCdnURL as E, resolveNpmImport as O, createBuiltinImports as S, isCdnEsmURL as T, bindPlaygroundScss as _, transformScript as a, whenSassReady as b, createFileIndex as c, isIgnoredFilename as d, isInsideRoot as f, resolveModulePath as g, resolveEntry as h, rewriteImportMeta as i, NEW_SFC_CODE as j, DEFAULT_CDN_URL as k, getCommonRoot as l, normalizeVirtualPath as m, injectStyle as n, collectBareSpecifiers as o, isRelativeSpecifier as p, removeStyle as r, collectReachableFilenames as s, collectCss as t, getExtension as u, compileScssSource as v, createCdnResolver as w, cdnModuleName as x, extractScssExports as y };
@@ -85,6 +85,51 @@ var createBuiltinImports = (cdnURL = DEFAULT_CDN_URL) => ({
85
85
  });
86
86
  var createBuiltinStyles = (cdnURL = DEFAULT_CDN_URL) => Object.fromEntries(PREVIEW_STYLE_ASSETS.map((asset) => [asset, cdnAsset(cdnURL, asset)]));
87
87
  var resolveNpmImport = (specifier) => /^https?:\/\//.test(specifier) ? specifier : jsdelivrEsmURL(`${specifier}/+esm`);
88
+ var docsCdnConfig = () => globalThis.window?.$docs || {};
89
+ var npmCdnUrl = (value) => {
90
+ if (!/^https?:\/\//i.test(value)) return null;
91
+ const url = new URL(value);
92
+ const prefix = /(?:^|\.)jsdelivr\.net$/u.test(url.hostname) && url.pathname.startsWith("/npm/") ? "/npm/" : /^(?:esm\.)?unpkg\.com$/u.test(url.hostname) ? "/" : null;
93
+ if (!prefix) return null;
94
+ const asset = url.pathname.slice(prefix.length);
95
+ const match = asset.match(/^((?:@[^/]+\/)?[^/]+)(.*)$/u);
96
+ return match ? {
97
+ url,
98
+ asset,
99
+ name: match[1].replace(/@[^/@]+$/u, ""),
100
+ path: match[2]
101
+ } : null;
102
+ };
103
+ var cdnModuleName = (url) => {
104
+ const parsed = npmCdnUrl(url);
105
+ return parsed && (!parsed.path || parsed.path === "/+esm" || /^\/(?:es\d+|esnext)\/[^/]+\.m?js$/u.test(parsed.path)) ? parsed.name : null;
106
+ };
107
+ var isCdnEsmURL = (url) => /\/\+esm(?:[?#]|$)/u.test(url) || npmCdnUrl(url)?.url.hostname === "esm.unpkg.com";
108
+ var resolveCdnURL = (url, cdnURL, config = docsCdnConfig()) => {
109
+ const parsed = npmCdnUrl(url);
110
+ const target = config.runtime?.cdn || cdnURL;
111
+ let converted = url;
112
+ if (parsed && target) {
113
+ const origin = normalizeCdnURL(target);
114
+ const unpkg = new URL(origin).hostname === "esm.unpkg.com";
115
+ let asset = parsed.asset;
116
+ if (unpkg) asset = asset.replace(/\/\+esm$/u, "");
117
+ 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`;
118
+ converted = `${origin}/${asset}${parsed.url.search}${parsed.url.hash}`;
119
+ }
120
+ return config.resolve?.cdn?.({ url: converted }) || converted;
121
+ };
122
+ var createCdnResolver = (cdnURL) => {
123
+ const resolved = /* @__PURE__ */ new Map();
124
+ return (url) => {
125
+ if (!resolved.has(url)) {
126
+ const target = resolveCdnURL(url, cdnURL);
127
+ resolved.set(url, target);
128
+ resolved.set(target, target);
129
+ }
130
+ return resolved.get(url);
131
+ };
132
+ };
88
133
  //#endregion
89
134
  //#region packages/playground/src/core/scss.ts
90
135
  var SCHEME = "docs-playground";
@@ -117,7 +162,7 @@ var resolveSassModule = async (url) => {
117
162
  return sass;
118
163
  };
119
164
  var loadSass = () => {
120
- loadPromise ??= (testLoader ?? (() => resolveSassModule(SASS_CDN_URL)))().then((sass) => {
165
+ loadPromise ??= (testLoader ?? (() => resolveSassModule(resolveCdnURL(SASS_CDN_URL))))().then((sass) => {
121
166
  loadedSass = sass;
122
167
  return sass;
123
168
  });
@@ -646,6 +691,12 @@ Object.defineProperty(exports, "bindPlaygroundScss", {
646
691
  return bindPlaygroundScss;
647
692
  }
648
693
  });
694
+ Object.defineProperty(exports, "cdnModuleName", {
695
+ enumerable: true,
696
+ get: function() {
697
+ return cdnModuleName;
698
+ }
699
+ });
649
700
  Object.defineProperty(exports, "collectBareSpecifiers", {
650
701
  enumerable: true,
651
702
  get: function() {
@@ -682,6 +733,12 @@ Object.defineProperty(exports, "createBuiltinStyles", {
682
733
  return createBuiltinStyles;
683
734
  }
684
735
  });
736
+ Object.defineProperty(exports, "createCdnResolver", {
737
+ enumerable: true,
738
+ get: function() {
739
+ return createCdnResolver;
740
+ }
741
+ });
685
742
  Object.defineProperty(exports, "createFileIndex", {
686
743
  enumerable: true,
687
744
  get: function() {
@@ -712,6 +769,12 @@ Object.defineProperty(exports, "injectStyle", {
712
769
  return injectStyle;
713
770
  }
714
771
  });
772
+ Object.defineProperty(exports, "isCdnEsmURL", {
773
+ enumerable: true,
774
+ get: function() {
775
+ return isCdnEsmURL;
776
+ }
777
+ });
715
778
  Object.defineProperty(exports, "isIgnoredFilename", {
716
779
  enumerable: true,
717
780
  get: function() {
@@ -748,6 +811,12 @@ Object.defineProperty(exports, "removeStyle", {
748
811
  return removeStyle;
749
812
  }
750
813
  });
814
+ Object.defineProperty(exports, "resolveCdnURL", {
815
+ enumerable: true,
816
+ get: function() {
817
+ return resolveCdnURL;
818
+ }
819
+ });
751
820
  Object.defineProperty(exports, "resolveEntry", {
752
821
  enumerable: true,
753
822
  get: function() {
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_css = require("./css-DpqJBQ5c.cjs");
2
+ const require_css = require("./css-O1VYXZtT.cjs");
3
3
  let vue = require("vue");
4
4
  vue = require_css.__toESM(vue);
5
5
  let _deot_vc = require("@deot/vc");
@@ -2066,14 +2066,16 @@ var moduleSpecifier = (item) => item.type === "import-meta" || !item.specifier |
2066
2066
  * 发现自定义 ESM 的传递引用;缺失的 npm 包映射到 CDN,动态依赖只登记地址。
2067
2067
  * @param imports 当前预览的模块覆盖。
2068
2068
  * @param specifiers 源文件实际引用的模块。
2069
+ * @param resolve 本次预览共用的最终 URL 转换。
2069
2070
  * @returns 补齐后的 import map。
2070
2071
  */
2071
- var resolveReplImports = async (imports, specifiers) => {
2072
+ var resolveReplImports = async (imports, specifiers, resolve = require_css.createCdnResolver()) => {
2072
2073
  const resolved = { ...imports };
2073
2074
  const visited = /* @__PURE__ */ new Set();
2074
2075
  const visit = async (specifier, importer) => {
2075
- const url = require_css.isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier) ? new URL(specifier, importer || location.href).href : resolved[specifier] ||= require_css.resolveNpmImport(specifier);
2076
- if (visited.has(url) || /\/\+esm(?:[?#]|$)/.test(url)) return;
2076
+ const source = require_css.isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier) ? new URL(specifier, importer || location.href).href : resolved[specifier] ||= require_css.resolveNpmImport(specifier);
2077
+ const url = resolve(source);
2078
+ if (visited.has(url) || require_css.isCdnEsmURL(source)) return;
2077
2079
  visited.add(url);
2078
2080
  const response = await fetch(url);
2079
2081
  if (!response.ok) throw new Error(`${response.status} ${url}`);
@@ -2138,8 +2140,10 @@ var rewriteImportedExports = (code) => {
2138
2140
  /**
2139
2141
  * REPL 将 ./ 引用按 src 根目录解析,编译结果需要先转成文件表中的完整路径。
2140
2142
  * @param store iframe 预览的文件和编译结果。
2143
+ * @param getImportMap 未转换的原始 import map。
2144
+ * @param resolve 本次预览共用的最终 URL 转换。
2141
2145
  */
2142
- var bindReplModules = (store) => {
2146
+ var bindReplModules = (store, getImportMap = store.getImportMap.bind(store), resolve = require_css.createCdnResolver()) => {
2143
2147
  (0, vue.watch)(() => Object.values(store.files).map((file) => [
2144
2148
  file.filename,
2145
2149
  file.code,
@@ -2147,7 +2151,7 @@ var bindReplModules = (store) => {
2147
2151
  file.compiled.ssr
2148
2152
  ]), () => {
2149
2153
  const files = require_css.createFileIndex(Object.fromEntries(Object.entries(store.files).map(([name, file]) => [name, file.code])));
2150
- const importMap = store.getImportMap();
2154
+ const importMap = getImportMap();
2151
2155
  const imports = { ...importMap.imports };
2152
2156
  let importsChanged = false;
2153
2157
  for (const file of Object.values(store.files)) for (const output of ["js", "ssr"]) {
@@ -2167,7 +2171,11 @@ var bindReplModules = (store) => {
2167
2171
  const target = `./${resolved.replace(/^\/?src\//, "")}`;
2168
2172
  const text = item.type === "dynamic" ? JSON.stringify(target) : target;
2169
2173
  code = code.slice(0, item.start) + text + code.slice(item.end);
2170
- } else if (!/^https?:\/\//.test(specifier) && !imports[specifier]) {
2174
+ } else if (/^https?:\/\//.test(specifier)) {
2175
+ const target = resolve(specifier);
2176
+ const text = item.type === "dynamic" ? JSON.stringify(target) : target;
2177
+ code = code.slice(0, item.start) + text + code.slice(item.end);
2178
+ } else if (!imports[specifier]) {
2171
2179
  imports[specifier] = require_css.resolveNpmImport(specifier);
2172
2180
  importsChanged = true;
2173
2181
  }
@@ -2204,13 +2212,14 @@ var escapeHtmlAttr = (value) => value.replace(/&/gu, "&amp;").replace(/"/gu, "&q
2204
2212
  * `--vc-background-color-lightest` 不同,否则会出现色带割裂。
2205
2213
  */
2206
2214
  var PLAYGROUND_RUNTIME_CANVAS_BACKGROUND = "var(--vc-background-color-lightest, var(--docs-background-color, #fff))";
2207
- var createRuntimePreviewOptions = (cdnURL = require_css.DEFAULT_CDN_URL, options = {}) => {
2215
+ var createRuntimePreviewOptions = (cdnURL, options = {}) => {
2208
2216
  const previewScroller = options.previewScroller === true;
2217
+ const resolve = require_css.createCdnResolver(cdnURL);
2209
2218
  const hrefs = Object.values({
2210
2219
  ...require_css.createBuiltinStyles(cdnURL),
2211
2220
  ...getPlaygroundSiteStyles(),
2212
2221
  ...getPlaygroundStyleOverrides()
2213
- });
2222
+ }).map(resolve);
2214
2223
  const styleBlocks = [
2215
2224
  `html,body{height:auto;min-height:0;background:${PLAYGROUND_RUNTIME_CANVAS_BACKGROUND}}`,
2216
2225
  "body{color:var(--vc-foreground-color,#080f20)}",
@@ -2254,7 +2263,7 @@ var createRuntimeImportOverrides = (builtinImportMap) => ({
2254
2263
  * @param cdnURL
2255
2264
  * @param builtinImportMap
2256
2265
  */
2257
- var createRuntimeImports = (cdnURL = require_css.DEFAULT_CDN_URL, builtinImportMap) => ({
2266
+ var createRuntimeImports = (cdnURL, builtinImportMap) => ({
2258
2267
  ...require_css.createBuiltinImports(cdnURL),
2259
2268
  ...createRuntimeImportOverrides(builtinImportMap)
2260
2269
  });
@@ -2268,8 +2277,14 @@ var createRuntimeStore = (files, entry, options) => {
2268
2277
  const mainFile = (0, vue.ref)(toReplFilename(entry));
2269
2278
  const activeFilename = (0, vue.ref)(toReplFilename(entry));
2270
2279
  const imports = createRuntimeImports(cdnURL, builtinImportMap);
2280
+ const resolve = require_css.createCdnResolver(cdnURL);
2281
+ const resourceLinks = (0, vue.unref)(storeOptions.resourceLinks);
2271
2282
  const store = (0, _vue_repl.useStore)({
2272
2283
  ...storeOptions,
2284
+ resourceLinks: (0, vue.ref)({
2285
+ ...resourceLinks,
2286
+ esModuleShims: resolve(resourceLinks?.esModuleShims || "https://cdn.jsdelivr.net/npm/es-module-shims@1.5.18/dist/es-module-shims.wasm.js?raw")
2287
+ }),
2273
2288
  files: replFiles,
2274
2289
  mainFile,
2275
2290
  activeFilename,
@@ -2288,7 +2303,17 @@ var createRuntimeStore = (files, entry, options) => {
2288
2303
  "import-map.json": store.files["import-map.json"].code,
2289
2304
  ...nextFiles
2290
2305
  }, nextEntry);
2291
- bindReplModules(store);
2306
+ const getImportMap = store.getImportMap.bind(store);
2307
+ bindReplModules(store, getImportMap, resolve);
2308
+ store.getImportMap = () => {
2309
+ const map = getImportMap();
2310
+ const convert = (items = {}) => Object.fromEntries(Object.entries(items).filter((item) => typeof item[1] === "string").map(([id, url]) => [id, resolve(url)]));
2311
+ return {
2312
+ ...map,
2313
+ imports: convert(map.imports),
2314
+ scopes: map.scopes && Object.fromEntries(Object.entries(map.scopes).map(([scope, items]) => [resolve(scope), convert(items)]))
2315
+ };
2316
+ };
2292
2317
  require_css.whenSassReady(files, () => {
2293
2318
  const specifiers = require_css.collectBareSpecifiers(files, Object.keys(files));
2294
2319
  if (!specifiers.length) {
@@ -2296,8 +2321,12 @@ var createRuntimeStore = (files, entry, options) => {
2296
2321
  return;
2297
2322
  }
2298
2323
  store.loading = true;
2299
- resolveReplImports(imports, specifiers).then((resolved) => {
2300
- if (Object.keys(resolved).length !== Object.keys(imports).length) store.setImportMap({ imports: resolved }, true);
2324
+ const currentImports = {
2325
+ ...imports,
2326
+ ...filterSafeHrefs(getImportMap().imports)
2327
+ };
2328
+ resolveReplImports(currentImports, specifiers, resolve).then((resolved) => {
2329
+ if (Object.keys(resolved).length !== Object.keys(currentImports).length) store.setImportMap({ imports: resolved }, true);
2301
2330
  store.init();
2302
2331
  }).catch((error) => {
2303
2332
  store.errors.push(error);
@@ -3054,6 +3083,43 @@ var linkPlayground = (compiled, files, options = {}) => {
3054
3083
  //#region packages/playground/src/core/runtime/local/remote.ts
3055
3084
  var HOST_VUE = /* @__PURE__ */ new Set(["vue", "vue/server-renderer"]);
3056
3085
  var moduleUrl = (url) => new URL(url, globalThis.location?.href).href;
3086
+ var REMOTE_SOURCE_CACHE_LIMIT = 128;
3087
+ var sourceCache = /* @__PURE__ */ new Map();
3088
+ var prepareRemoteSource = async (url) => {
3089
+ const response = await fetch(url);
3090
+ if (!response.ok) throw new Error(`${response.status} ${url}`);
3091
+ const text = await response.text();
3092
+ await (0, es_module_lexer.init)();
3093
+ const [specifiers] = (0, es_module_lexer.parse)(text);
3094
+ let source = text;
3095
+ for (const item of [...specifiers].reverse()) if (item.type === "dynamic") source = source.slice(0, item.importStart) + "__import" + source.slice(item.importStart + 6);
3096
+ const cjs = (0, sucrase.transform)(require_css.rewriteImportMeta(source, url), {
3097
+ transforms: ["imports"],
3098
+ disableESTransforms: true,
3099
+ production: true,
3100
+ filePath: url
3101
+ }).code;
3102
+ return {
3103
+ dependencies: specifiers.filter((item) => item.type === "static" || item.type === "reexport-star").map((item) => item.specifier),
3104
+ run: new Function("require", "module", "exports", "__import", cjs)
3105
+ };
3106
+ };
3107
+ var readRemoteSource = (url) => {
3108
+ if (new URL(url).origin === globalThis.location?.origin) return prepareRemoteSource(url);
3109
+ const cached = sourceCache.get(url);
3110
+ if (cached) {
3111
+ sourceCache.delete(url);
3112
+ sourceCache.set(url, cached);
3113
+ return cached;
3114
+ }
3115
+ const task = prepareRemoteSource(url).catch((error) => {
3116
+ if (sourceCache.get(url) === task) sourceCache.delete(url);
3117
+ throw error;
3118
+ });
3119
+ sourceCache.set(url, task);
3120
+ if (sourceCache.size > REMOTE_SOURCE_CACHE_LIMIT) sourceCache.delete(sourceCache.keys().next().value);
3121
+ return task;
3122
+ };
3057
3123
  /**
3058
3124
  * 把 CDN 上的 ESM 先拉完整张依赖图,再在当前页面里执行。
3059
3125
  * 裸导入优先走 import map,缺失的 npm 依赖回退到 CDN ESM;import() 按需加载。
@@ -3061,20 +3127,23 @@ var moduleUrl = (url) => new URL(url, globalThis.location?.href).href;
3061
3127
  * @param builtins 宿主 Vue 与 vue-router。
3062
3128
  * @param overrides 显式配置的模块名,与自动补齐的默认地址区分。
3063
3129
  * @param importModule 浏览器原生 ESM 加载器。
3130
+ * @param cdnURL 没有 launch 选点结果时使用的实例 CDN。
3064
3131
  * @returns 按 URL 加载并执行远程模块的加载器。
3065
3132
  */
3066
3133
  var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imports)), importModule = (url) => import(
3067
3134
  /* @vite-ignore */
3068
3135
  url
3069
- )) => {
3136
+ ), cdnURL) => {
3137
+ const resolve = require_css.createCdnResolver(cdnURL);
3138
+ const resolveUrl = (url) => resolve(moduleUrl(url));
3070
3139
  const hostModules = {
3071
3140
  "vue": builtins.vue,
3072
3141
  "vue-router": builtins.vueRouter
3073
3142
  };
3074
3143
  const pendingBuiltins = /* @__PURE__ */ new Set();
3075
- const texts = /* @__PURE__ */ new Map();
3144
+ const sources = /* @__PURE__ */ new Map();
3145
+ const pendingSources = /* @__PURE__ */ new Map();
3076
3146
  const evaluated = /* @__PURE__ */ new Map();
3077
- const crawled = /* @__PURE__ */ new Set();
3078
3147
  const nativeModules = /* @__PURE__ */ new Map();
3079
3148
  const loadNative = (url) => {
3080
3149
  let pending = nativeModules.get(url);
@@ -3103,16 +3172,16 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3103
3172
  const dependencySpecifier = (from, specifier) => {
3104
3173
  if (require_css.isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier)) {
3105
3174
  const url = new URL(specifier, from);
3106
- const match = url.origin === "https://cdn.jsdelivr.net" ? url.pathname.match(/^\/npm\/((?:@[^/]+\/)?[^/@]+)(?:@[^/]+)?\/\+esm$/) : null;
3107
- if (match && (imports[match[1]] || localBuiltinIds.has(match[1]))) return match[1];
3175
+ const name = require_css.cdnModuleName(url.href);
3176
+ if (name && (imports[name] || localBuiltinIds.has(name))) return name;
3108
3177
  return url.href;
3109
3178
  }
3110
3179
  return specifier;
3111
3180
  };
3112
3181
  const dependencyUrl = (from, id) => {
3113
3182
  const specifier = dependencySpecifier(from, id);
3114
- if (/^https?:\/\//.test(specifier)) return specifier;
3115
- if (imports[specifier] && !prefersLocalBuiltin(specifier, imports, overrides)) return moduleUrl(imports[specifier]);
3183
+ if (/^https?:\/\//.test(specifier)) return resolveUrl(specifier);
3184
+ if (imports[specifier] && !prefersLocalBuiltin(specifier, imports, overrides)) return resolveUrl(imports[specifier]);
3116
3185
  if (HOST_VUE.has(specifier) && prefersLocalBuiltin(specifier, imports, overrides)) return null;
3117
3186
  let cursor = specifier;
3118
3187
  while (cursor) {
@@ -3127,29 +3196,27 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3127
3196
  const target = imports[specifier] ? specifier : resolveImportTarget(specifier, imports);
3128
3197
  if (!target) {
3129
3198
  imports[specifier] = require_css.resolveNpmImport(specifier);
3130
- return imports[specifier];
3199
+ return resolveUrl(imports[specifier]);
3131
3200
  }
3132
- return moduleUrl(imports[target]);
3201
+ return resolveUrl(imports[target]);
3133
3202
  };
3134
- const crawl = async (url) => {
3135
- if (!overrides.size && /\/\+esm(?:[?#]|$)/.test(url)) {
3203
+ const crawl = async (url, visited = /* @__PURE__ */ new Set()) => {
3204
+ if (!overrides.size && require_css.isCdnEsmURL(url)) {
3136
3205
  await loadNative(url);
3137
3206
  return;
3138
3207
  }
3139
- if (crawled.has(url)) return;
3140
- crawled.add(url);
3141
- const response = await fetch(url);
3142
- if (!response.ok) throw new Error(`${response.status} ${url}`);
3143
- const text = await response.text();
3144
- await (0, es_module_lexer.init)();
3145
- const [specifiers] = (0, es_module_lexer.parse)(text);
3146
- let source = text;
3147
- for (const item of [...specifiers].reverse()) if (item.type === "dynamic") source = source.slice(0, item.importStart) + "__import" + source.slice(item.importStart + 6);
3148
- texts.set(url, source);
3149
- await Promise.all(specifiers.map(async (item) => {
3150
- if (item.type !== "static" && item.type !== "reexport-star") return;
3151
- const next = dependencyUrl(url, item.specifier);
3152
- if (next) await crawl(next);
3208
+ if (visited.has(url)) return;
3209
+ visited.add(url);
3210
+ let pending = pendingSources.get(url);
3211
+ if (!pending) {
3212
+ pending = readRemoteSource(url);
3213
+ pendingSources.set(url, pending);
3214
+ }
3215
+ const source = await pending;
3216
+ sources.set(url, source);
3217
+ await Promise.all(source.dependencies.map(async (specifier) => {
3218
+ const next = dependencyUrl(url, specifier);
3219
+ if (next) await crawl(next, visited);
3153
3220
  }));
3154
3221
  };
3155
3222
  const requireFrom = (from) => (specifier) => {
@@ -3160,7 +3227,7 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3160
3227
  if (require_css.isRelativeSpecifier(id) || /^https?:\/\//.test(id)) return evaluate(dependencyUrl(from, id));
3161
3228
  const target = imports[id] ? id : resolveImportTarget(id, imports);
3162
3229
  if (!target || !imports[target]) throw new Error(`[Playground] 未注册的模块 "${id}"`);
3163
- const loaded = evaluate(moduleUrl(imports[target]));
3230
+ const loaded = evaluate(resolveUrl(imports[target]));
3164
3231
  if (target === id) return loaded;
3165
3232
  if (!resolveHostModule(id, { [target]: loaded }))
3166
3233
  /* istanbul ignore next -- 父包没有对应导出时才拒绝 */
@@ -3169,17 +3236,12 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3169
3236
  };
3170
3237
  const evaluate = (url) => {
3171
3238
  if (evaluated.has(url)) return evaluated.get(url);
3172
- const text = texts.get(url);
3173
- if (text == null) throw new Error(`[Playground] 远程模块尚未下载: ${url}`);
3174
- const cjs = (0, sucrase.transform)(require_css.rewriteImportMeta(text, url), {
3175
- transforms: ["imports"],
3176
- disableESTransforms: true,
3177
- production: true,
3178
- filePath: url
3179
- }).code;
3239
+ const source = sources.get(url);
3240
+ if (!source) throw new Error(`[Playground] 远程模块尚未下载: ${url}`);
3180
3241
  const module = { exports: {} };
3181
3242
  evaluated.set(url, module.exports);
3182
- new Function("require", "module", "exports", "__import", cjs)(requireFrom(url), module, module.exports, async (id) => {
3243
+ const runner = source.run;
3244
+ runner(requireFrom(url), module, module.exports, async (id) => {
3183
3245
  const next = dependencyUrl(url, id);
3184
3246
  if (next) await load(next);
3185
3247
  else {
@@ -3193,7 +3255,7 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3193
3255
  };
3194
3256
  const loading = /* @__PURE__ */ new Map();
3195
3257
  const load = (sourceUrl) => {
3196
- const url = moduleUrl(sourceUrl);
3258
+ const url = resolveUrl(sourceUrl);
3197
3259
  const pending = loading.get(url);
3198
3260
  if (pending) return pending;
3199
3261
  const task = (async () => {
@@ -3353,7 +3415,7 @@ var local_sandbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0
3353
3415
  publishError();
3354
3416
  destroyPreview();
3355
3417
  try {
3356
- const { compilePlayground } = await Promise.resolve().then(() => require("./compile-29EUKPOy.cjs"));
3418
+ const { compilePlayground } = await Promise.resolve().then(() => require("./compile-QZ6cd8o0.cjs"));
3357
3419
  /* istanbul ignore if -- 并发编译时只保留最后一次 */
3358
3420
  if (current !== version) return;
3359
3421
  const compiled = await compilePlayground(props.files, { entry: props.entry });
@@ -3372,7 +3434,7 @@ var local_sandbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0
3372
3434
  const remote = createRemoteLoader(imports, {
3373
3435
  vue,
3374
3436
  vueRouter: vue_router
3375
- }, overrides);
3437
+ }, overrides, void 0, props.options.cdnURL);
3376
3438
  const loaded = await loadLocalModules(specifiers, imports, {
3377
3439
  vue,
3378
3440
  vueRouter: vue_router
@@ -4247,6 +4309,7 @@ exports.normalizeCdnURL = require_css.normalizeCdnURL;
4247
4309
  exports.registerVueHighlight = registerVueHighlight;
4248
4310
  exports.removePlaygroundImportMapOverride = removePlaygroundImportMapOverride;
4249
4311
  exports.removePlaygroundStyleOverride = removePlaygroundStyleOverride;
4312
+ exports.resolveCdnURL = require_css.resolveCdnURL;
4250
4313
  exports.resolveHighlightLanguage = resolveHighlightLanguage;
4251
4314
  exports.resolvePlaygroundTitleId = resolvePlaygroundTitleId;
4252
4315
  exports.setPlaygroundImportMapOverrides = setPlaygroundImportMapOverrides;
package/dist/index.d.ts CHANGED
@@ -81,6 +81,17 @@ export declare const applyPlaygroundStyleOverride: (alias: string, url: string)
81
81
  [x: string]: string;
82
82
  };
83
83
 
84
+ declare interface CdnConfig {
85
+ runtime?: {
86
+ cdn?: string;
87
+ };
88
+ resolve?: {
89
+ cdn?: (context: {
90
+ url: string;
91
+ }) => string | null | undefined;
92
+ };
93
+ }
94
+
84
95
  export declare const clearPlaygroundMaps: () => void;
85
96
 
86
97
  export declare const CodePreview: typeof __VLS_export;
@@ -250,6 +261,8 @@ export declare const removePlaygroundStyleOverride: (alias: string) => {
250
261
  [x: string]: string;
251
262
  };
252
263
 
264
+ export declare const resolveCdnURL: (url: string, cdnURL?: string, config?: CdnConfig) => string;
265
+
253
266
  export declare const resolveHighlightLanguage: (filename: string) => "xml" | "javascript" | "typescript" | "css" | "scss" | "vue" | "json" | "plaintext";
254
267
 
255
268
  export declare const resolvePlaygroundTitleId: (title: string, explicitId?: string, isOccupied?: (id: string) => boolean) => string;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as normalizeCdnURL, D as NEW_SFC_CODE, E as DEFAULT_ENTRY, S as createBuiltinStyles, T as DEFAULT_CDN_URL, _ as bindPlaygroundScss, a as transformScript, b as whenSassReady, c as createFileIndex, d as isIgnoredFilename, f as isInsideRoot, g as resolveModulePath, i as rewriteImportMeta, l as getCommonRoot, m as normalizeVirtualPath, n as injectStyle, o as collectBareSpecifiers, p as isRelativeSpecifier, r as removeStyle, s as collectReachableFilenames, w as resolveNpmImport, x as createBuiltinImports } from "./css-DleINdoA.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-C25yWQa_.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
4
  import * as Vc from "@deot/vc";
@@ -2068,14 +2068,16 @@ var moduleSpecifier = (item) => item.type === "import-meta" || !item.specifier |
2068
2068
  * 发现自定义 ESM 的传递引用;缺失的 npm 包映射到 CDN,动态依赖只登记地址。
2069
2069
  * @param imports 当前预览的模块覆盖。
2070
2070
  * @param specifiers 源文件实际引用的模块。
2071
+ * @param resolve 本次预览共用的最终 URL 转换。
2071
2072
  * @returns 补齐后的 import map。
2072
2073
  */
2073
- var resolveReplImports = async (imports, specifiers) => {
2074
+ var resolveReplImports = async (imports, specifiers, resolve = createCdnResolver()) => {
2074
2075
  const resolved = { ...imports };
2075
2076
  const visited = /* @__PURE__ */ new Set();
2076
2077
  const visit = async (specifier, importer) => {
2077
- const url = isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier) ? new URL(specifier, importer || location.href).href : resolved[specifier] ||= resolveNpmImport(specifier);
2078
- if (visited.has(url) || /\/\+esm(?:[?#]|$)/.test(url)) return;
2078
+ const source = isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier) ? new URL(specifier, importer || location.href).href : resolved[specifier] ||= resolveNpmImport(specifier);
2079
+ const url = resolve(source);
2080
+ if (visited.has(url) || isCdnEsmURL(source)) return;
2079
2081
  visited.add(url);
2080
2082
  const response = await fetch(url);
2081
2083
  if (!response.ok) throw new Error(`${response.status} ${url}`);
@@ -2140,8 +2142,10 @@ var rewriteImportedExports = (code) => {
2140
2142
  /**
2141
2143
  * REPL 将 ./ 引用按 src 根目录解析,编译结果需要先转成文件表中的完整路径。
2142
2144
  * @param store iframe 预览的文件和编译结果。
2145
+ * @param getImportMap 未转换的原始 import map。
2146
+ * @param resolve 本次预览共用的最终 URL 转换。
2143
2147
  */
2144
- var bindReplModules = (store) => {
2148
+ var bindReplModules = (store, getImportMap = store.getImportMap.bind(store), resolve = createCdnResolver()) => {
2145
2149
  watch(() => Object.values(store.files).map((file) => [
2146
2150
  file.filename,
2147
2151
  file.code,
@@ -2149,7 +2153,7 @@ var bindReplModules = (store) => {
2149
2153
  file.compiled.ssr
2150
2154
  ]), () => {
2151
2155
  const files = createFileIndex(Object.fromEntries(Object.entries(store.files).map(([name, file]) => [name, file.code])));
2152
- const importMap = store.getImportMap();
2156
+ const importMap = getImportMap();
2153
2157
  const imports = { ...importMap.imports };
2154
2158
  let importsChanged = false;
2155
2159
  for (const file of Object.values(store.files)) for (const output of ["js", "ssr"]) {
@@ -2169,7 +2173,11 @@ var bindReplModules = (store) => {
2169
2173
  const target = `./${resolved.replace(/^\/?src\//, "")}`;
2170
2174
  const text = item.type === "dynamic" ? JSON.stringify(target) : target;
2171
2175
  code = code.slice(0, item.start) + text + code.slice(item.end);
2172
- } else if (!/^https?:\/\//.test(specifier) && !imports[specifier]) {
2176
+ } else if (/^https?:\/\//.test(specifier)) {
2177
+ const target = resolve(specifier);
2178
+ const text = item.type === "dynamic" ? JSON.stringify(target) : target;
2179
+ code = code.slice(0, item.start) + text + code.slice(item.end);
2180
+ } else if (!imports[specifier]) {
2173
2181
  imports[specifier] = resolveNpmImport(specifier);
2174
2182
  importsChanged = true;
2175
2183
  }
@@ -2206,13 +2214,14 @@ var escapeHtmlAttr = (value) => value.replace(/&/gu, "&amp;").replace(/"/gu, "&q
2206
2214
  * `--vc-background-color-lightest` 不同,否则会出现色带割裂。
2207
2215
  */
2208
2216
  var PLAYGROUND_RUNTIME_CANVAS_BACKGROUND = "var(--vc-background-color-lightest, var(--docs-background-color, #fff))";
2209
- var createRuntimePreviewOptions = (cdnURL = DEFAULT_CDN_URL, options = {}) => {
2217
+ var createRuntimePreviewOptions = (cdnURL, options = {}) => {
2210
2218
  const previewScroller = options.previewScroller === true;
2219
+ const resolve = createCdnResolver(cdnURL);
2211
2220
  const hrefs = Object.values({
2212
2221
  ...createBuiltinStyles(cdnURL),
2213
2222
  ...getPlaygroundSiteStyles(),
2214
2223
  ...getPlaygroundStyleOverrides()
2215
- });
2224
+ }).map(resolve);
2216
2225
  const styleBlocks = [
2217
2226
  `html,body{height:auto;min-height:0;background:${PLAYGROUND_RUNTIME_CANVAS_BACKGROUND}}`,
2218
2227
  "body{color:var(--vc-foreground-color,#080f20)}",
@@ -2256,7 +2265,7 @@ var createRuntimeImportOverrides = (builtinImportMap) => ({
2256
2265
  * @param cdnURL
2257
2266
  * @param builtinImportMap
2258
2267
  */
2259
- var createRuntimeImports = (cdnURL = DEFAULT_CDN_URL, builtinImportMap) => ({
2268
+ var createRuntimeImports = (cdnURL, builtinImportMap) => ({
2260
2269
  ...createBuiltinImports(cdnURL),
2261
2270
  ...createRuntimeImportOverrides(builtinImportMap)
2262
2271
  });
@@ -2270,8 +2279,14 @@ var createRuntimeStore = (files, entry, options) => {
2270
2279
  const mainFile = ref(toReplFilename(entry));
2271
2280
  const activeFilename = ref(toReplFilename(entry));
2272
2281
  const imports = createRuntimeImports(cdnURL, builtinImportMap);
2282
+ const resolve = createCdnResolver(cdnURL);
2283
+ const resourceLinks = unref(storeOptions.resourceLinks);
2273
2284
  const store = useStore({
2274
2285
  ...storeOptions,
2286
+ resourceLinks: ref({
2287
+ ...resourceLinks,
2288
+ esModuleShims: resolve(resourceLinks?.esModuleShims || "https://cdn.jsdelivr.net/npm/es-module-shims@1.5.18/dist/es-module-shims.wasm.js?raw")
2289
+ }),
2275
2290
  files: replFiles,
2276
2291
  mainFile,
2277
2292
  activeFilename,
@@ -2290,7 +2305,17 @@ var createRuntimeStore = (files, entry, options) => {
2290
2305
  "import-map.json": store.files["import-map.json"].code,
2291
2306
  ...nextFiles
2292
2307
  }, nextEntry);
2293
- bindReplModules(store);
2308
+ const getImportMap = store.getImportMap.bind(store);
2309
+ bindReplModules(store, getImportMap, resolve);
2310
+ store.getImportMap = () => {
2311
+ const map = getImportMap();
2312
+ const convert = (items = {}) => Object.fromEntries(Object.entries(items).filter((item) => typeof item[1] === "string").map(([id, url]) => [id, resolve(url)]));
2313
+ return {
2314
+ ...map,
2315
+ imports: convert(map.imports),
2316
+ scopes: map.scopes && Object.fromEntries(Object.entries(map.scopes).map(([scope, items]) => [resolve(scope), convert(items)]))
2317
+ };
2318
+ };
2294
2319
  whenSassReady(files, () => {
2295
2320
  const specifiers = collectBareSpecifiers(files, Object.keys(files));
2296
2321
  if (!specifiers.length) {
@@ -2298,8 +2323,12 @@ var createRuntimeStore = (files, entry, options) => {
2298
2323
  return;
2299
2324
  }
2300
2325
  store.loading = true;
2301
- resolveReplImports(imports, specifiers).then((resolved) => {
2302
- if (Object.keys(resolved).length !== Object.keys(imports).length) store.setImportMap({ imports: resolved }, true);
2326
+ const currentImports = {
2327
+ ...imports,
2328
+ ...filterSafeHrefs(getImportMap().imports)
2329
+ };
2330
+ resolveReplImports(currentImports, specifiers, resolve).then((resolved) => {
2331
+ if (Object.keys(resolved).length !== Object.keys(currentImports).length) store.setImportMap({ imports: resolved }, true);
2303
2332
  store.init();
2304
2333
  }).catch((error) => {
2305
2334
  store.errors.push(error);
@@ -3053,6 +3082,43 @@ var linkPlayground = (compiled, files, options = {}) => {
3053
3082
  //#region packages/playground/src/core/runtime/local/remote.ts
3054
3083
  var HOST_VUE = /* @__PURE__ */ new Set(["vue", "vue/server-renderer"]);
3055
3084
  var moduleUrl = (url) => new URL(url, globalThis.location?.href).href;
3085
+ var REMOTE_SOURCE_CACHE_LIMIT = 128;
3086
+ var sourceCache = /* @__PURE__ */ new Map();
3087
+ var prepareRemoteSource = async (url) => {
3088
+ const response = await fetch(url);
3089
+ if (!response.ok) throw new Error(`${response.status} ${url}`);
3090
+ const text = await response.text();
3091
+ await init();
3092
+ const [specifiers] = parse(text);
3093
+ let source = text;
3094
+ for (const item of [...specifiers].reverse()) if (item.type === "dynamic") source = source.slice(0, item.importStart) + "__import" + source.slice(item.importStart + 6);
3095
+ const cjs = transform(rewriteImportMeta(source, url), {
3096
+ transforms: ["imports"],
3097
+ disableESTransforms: true,
3098
+ production: true,
3099
+ filePath: url
3100
+ }).code;
3101
+ return {
3102
+ dependencies: specifiers.filter((item) => item.type === "static" || item.type === "reexport-star").map((item) => item.specifier),
3103
+ run: new Function("require", "module", "exports", "__import", cjs)
3104
+ };
3105
+ };
3106
+ var readRemoteSource = (url) => {
3107
+ if (new URL(url).origin === globalThis.location?.origin) return prepareRemoteSource(url);
3108
+ const cached = sourceCache.get(url);
3109
+ if (cached) {
3110
+ sourceCache.delete(url);
3111
+ sourceCache.set(url, cached);
3112
+ return cached;
3113
+ }
3114
+ const task = prepareRemoteSource(url).catch((error) => {
3115
+ if (sourceCache.get(url) === task) sourceCache.delete(url);
3116
+ throw error;
3117
+ });
3118
+ sourceCache.set(url, task);
3119
+ if (sourceCache.size > REMOTE_SOURCE_CACHE_LIMIT) sourceCache.delete(sourceCache.keys().next().value);
3120
+ return task;
3121
+ };
3056
3122
  /**
3057
3123
  * 把 CDN 上的 ESM 先拉完整张依赖图,再在当前页面里执行。
3058
3124
  * 裸导入优先走 import map,缺失的 npm 依赖回退到 CDN ESM;import() 按需加载。
@@ -3060,20 +3126,23 @@ var moduleUrl = (url) => new URL(url, globalThis.location?.href).href;
3060
3126
  * @param builtins 宿主 Vue 与 vue-router。
3061
3127
  * @param overrides 显式配置的模块名,与自动补齐的默认地址区分。
3062
3128
  * @param importModule 浏览器原生 ESM 加载器。
3129
+ * @param cdnURL 没有 launch 选点结果时使用的实例 CDN。
3063
3130
  * @returns 按 URL 加载并执行远程模块的加载器。
3064
3131
  */
3065
3132
  var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imports)), importModule = (url) => import(
3066
3133
  /* @vite-ignore */
3067
3134
  url
3068
- )) => {
3135
+ ), cdnURL) => {
3136
+ const resolve = createCdnResolver(cdnURL);
3137
+ const resolveUrl = (url) => resolve(moduleUrl(url));
3069
3138
  const hostModules = {
3070
3139
  "vue": builtins.vue,
3071
3140
  "vue-router": builtins.vueRouter
3072
3141
  };
3073
3142
  const pendingBuiltins = /* @__PURE__ */ new Set();
3074
- const texts = /* @__PURE__ */ new Map();
3143
+ const sources = /* @__PURE__ */ new Map();
3144
+ const pendingSources = /* @__PURE__ */ new Map();
3075
3145
  const evaluated = /* @__PURE__ */ new Map();
3076
- const crawled = /* @__PURE__ */ new Set();
3077
3146
  const nativeModules = /* @__PURE__ */ new Map();
3078
3147
  const loadNative = (url) => {
3079
3148
  let pending = nativeModules.get(url);
@@ -3102,16 +3171,16 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3102
3171
  const dependencySpecifier = (from, specifier) => {
3103
3172
  if (isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier)) {
3104
3173
  const url = new URL(specifier, from);
3105
- const match = url.origin === "https://cdn.jsdelivr.net" ? url.pathname.match(/^\/npm\/((?:@[^/]+\/)?[^/@]+)(?:@[^/]+)?\/\+esm$/) : null;
3106
- if (match && (imports[match[1]] || localBuiltinIds.has(match[1]))) return match[1];
3174
+ const name = cdnModuleName(url.href);
3175
+ if (name && (imports[name] || localBuiltinIds.has(name))) return name;
3107
3176
  return url.href;
3108
3177
  }
3109
3178
  return specifier;
3110
3179
  };
3111
3180
  const dependencyUrl = (from, id) => {
3112
3181
  const specifier = dependencySpecifier(from, id);
3113
- if (/^https?:\/\//.test(specifier)) return specifier;
3114
- if (imports[specifier] && !prefersLocalBuiltin(specifier, imports, overrides)) return moduleUrl(imports[specifier]);
3182
+ if (/^https?:\/\//.test(specifier)) return resolveUrl(specifier);
3183
+ if (imports[specifier] && !prefersLocalBuiltin(specifier, imports, overrides)) return resolveUrl(imports[specifier]);
3115
3184
  if (HOST_VUE.has(specifier) && prefersLocalBuiltin(specifier, imports, overrides)) return null;
3116
3185
  let cursor = specifier;
3117
3186
  while (cursor) {
@@ -3126,29 +3195,27 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3126
3195
  const target = imports[specifier] ? specifier : resolveImportTarget(specifier, imports);
3127
3196
  if (!target) {
3128
3197
  imports[specifier] = resolveNpmImport(specifier);
3129
- return imports[specifier];
3198
+ return resolveUrl(imports[specifier]);
3130
3199
  }
3131
- return moduleUrl(imports[target]);
3200
+ return resolveUrl(imports[target]);
3132
3201
  };
3133
- const crawl = async (url) => {
3134
- if (!overrides.size && /\/\+esm(?:[?#]|$)/.test(url)) {
3202
+ const crawl = async (url, visited = /* @__PURE__ */ new Set()) => {
3203
+ if (!overrides.size && isCdnEsmURL(url)) {
3135
3204
  await loadNative(url);
3136
3205
  return;
3137
3206
  }
3138
- if (crawled.has(url)) return;
3139
- crawled.add(url);
3140
- const response = await fetch(url);
3141
- if (!response.ok) throw new Error(`${response.status} ${url}`);
3142
- const text = await response.text();
3143
- await init();
3144
- const [specifiers] = parse(text);
3145
- let source = text;
3146
- for (const item of [...specifiers].reverse()) if (item.type === "dynamic") source = source.slice(0, item.importStart) + "__import" + source.slice(item.importStart + 6);
3147
- texts.set(url, source);
3148
- await Promise.all(specifiers.map(async (item) => {
3149
- if (item.type !== "static" && item.type !== "reexport-star") return;
3150
- const next = dependencyUrl(url, item.specifier);
3151
- if (next) await crawl(next);
3207
+ if (visited.has(url)) return;
3208
+ visited.add(url);
3209
+ let pending = pendingSources.get(url);
3210
+ if (!pending) {
3211
+ pending = readRemoteSource(url);
3212
+ pendingSources.set(url, pending);
3213
+ }
3214
+ const source = await pending;
3215
+ sources.set(url, source);
3216
+ await Promise.all(source.dependencies.map(async (specifier) => {
3217
+ const next = dependencyUrl(url, specifier);
3218
+ if (next) await crawl(next, visited);
3152
3219
  }));
3153
3220
  };
3154
3221
  const requireFrom = (from) => (specifier) => {
@@ -3159,7 +3226,7 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3159
3226
  if (isRelativeSpecifier(id) || /^https?:\/\//.test(id)) return evaluate(dependencyUrl(from, id));
3160
3227
  const target = imports[id] ? id : resolveImportTarget(id, imports);
3161
3228
  if (!target || !imports[target]) throw new Error(`[Playground] 未注册的模块 "${id}"`);
3162
- const loaded = evaluate(moduleUrl(imports[target]));
3229
+ const loaded = evaluate(resolveUrl(imports[target]));
3163
3230
  if (target === id) return loaded;
3164
3231
  if (!resolveHostModule(id, { [target]: loaded }))
3165
3232
  /* istanbul ignore next -- 父包没有对应导出时才拒绝 */
@@ -3168,17 +3235,12 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3168
3235
  };
3169
3236
  const evaluate = (url) => {
3170
3237
  if (evaluated.has(url)) return evaluated.get(url);
3171
- const text = texts.get(url);
3172
- if (text == null) throw new Error(`[Playground] 远程模块尚未下载: ${url}`);
3173
- const cjs = transform(rewriteImportMeta(text, url), {
3174
- transforms: ["imports"],
3175
- disableESTransforms: true,
3176
- production: true,
3177
- filePath: url
3178
- }).code;
3238
+ const source = sources.get(url);
3239
+ if (!source) throw new Error(`[Playground] 远程模块尚未下载: ${url}`);
3179
3240
  const module = { exports: {} };
3180
3241
  evaluated.set(url, module.exports);
3181
- new Function("require", "module", "exports", "__import", cjs)(requireFrom(url), module, module.exports, async (id) => {
3242
+ const runner = source.run;
3243
+ runner(requireFrom(url), module, module.exports, async (id) => {
3182
3244
  const next = dependencyUrl(url, id);
3183
3245
  if (next) await load(next);
3184
3246
  else {
@@ -3192,7 +3254,7 @@ var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imp
3192
3254
  };
3193
3255
  const loading = /* @__PURE__ */ new Map();
3194
3256
  const load = (sourceUrl) => {
3195
- const url = moduleUrl(sourceUrl);
3257
+ const url = resolveUrl(sourceUrl);
3196
3258
  const pending = loading.get(url);
3197
3259
  if (pending) return pending;
3198
3260
  const task = (async () => {
@@ -3352,7 +3414,7 @@ var local_sandbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
3352
3414
  publishError();
3353
3415
  destroyPreview();
3354
3416
  try {
3355
- const { compilePlayground } = await import("./compile-CM_OyqYN.js");
3417
+ const { compilePlayground } = await import("./compile-BHlM2G35.js");
3356
3418
  /* istanbul ignore if -- 并发编译时只保留最后一次 */
3357
3419
  if (current !== version) return;
3358
3420
  const compiled = await compilePlayground(props.files, { entry: props.entry });
@@ -3371,7 +3433,7 @@ var local_sandbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ de
3371
3433
  const remote = createRemoteLoader(imports, {
3372
3434
  vue: Vue,
3373
3435
  vueRouter: VueRouter
3374
- }, overrides);
3436
+ }, overrides, void 0, props.options.cdnURL);
3375
3437
  const loaded = await loadLocalModules(specifiers, imports, {
3376
3438
  vue: Vue,
3377
3439
  vueRouter: VueRouter
@@ -4221,4 +4283,4 @@ var playground_default = /* @__PURE__ */ defineComponent({
4221
4283
  }
4222
4284
  });
4223
4285
  //#endregion
4224
- export { code_preview_default as CodePreview, DEFAULT_CDN_URL, Editor, PREVIEW_SCROLLER_USE_CODE, PREVIEW_SCROLL_CONTENT_CLASS, PREVIEW_SCROLL_HTML_CLASS, playground_default as Playground, PlaygroundService, applyPlaygroundImportMapOverride, applyPlaygroundStyleOverride, clearPlaygroundMaps, createBuiltinImports, createBuiltinStyles, createRuntimePreviewOptions, getPlaygroundImportMapOverrides, getPlaygroundSiteModules, getPlaygroundSiteStyles, getPlaygroundStyleOverrides, highlightCode, highlightCodeByLanguage, isSafePlaygroundHref, normalizeCdnURL, registerVueHighlight, removePlaygroundImportMapOverride, removePlaygroundStyleOverride, resolveHighlightLanguage, resolvePlaygroundTitleId, setPlaygroundImportMapOverrides, setPlaygroundSiteModules, setPlaygroundSiteStyles, setPlaygroundStyleOverrides, slugifyPlaygroundTitle, vueHighlight };
4286
+ export { code_preview_default as CodePreview, DEFAULT_CDN_URL, Editor, PREVIEW_SCROLLER_USE_CODE, PREVIEW_SCROLL_CONTENT_CLASS, PREVIEW_SCROLL_HTML_CLASS, playground_default as Playground, PlaygroundService, applyPlaygroundImportMapOverride, applyPlaygroundStyleOverride, clearPlaygroundMaps, createBuiltinImports, createBuiltinStyles, createRuntimePreviewOptions, getPlaygroundImportMapOverrides, getPlaygroundSiteModules, getPlaygroundSiteStyles, getPlaygroundStyleOverrides, highlightCode, highlightCodeByLanguage, isSafePlaygroundHref, normalizeCdnURL, registerVueHighlight, removePlaygroundImportMapOverride, removePlaygroundStyleOverride, resolveCdnURL, resolveHighlightLanguage, resolvePlaygroundTitleId, setPlaygroundImportMapOverrides, setPlaygroundSiteModules, setPlaygroundSiteStyles, setPlaygroundStyleOverrides, slugifyPlaygroundTitle, vueHighlight };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deot/docs-playground",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {
@@ -18,8 +18,8 @@
18
18
  "access": "public"
19
19
  },
20
20
  "dependencies": {
21
- "@deot/docs-locale": "^1.1.4",
22
- "@deot/docs-theme": "^1.1.4",
21
+ "@deot/docs-locale": "^1.1.5",
22
+ "@deot/docs-theme": "^1.1.5",
23
23
  "@deot/helper": "^1.1.21",
24
24
  "@deot/http": "^1.1.10",
25
25
  "@deot/vc": "^1.2.3",