@deot/docs-playground 1.1.1 → 1.1.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/README.md CHANGED
@@ -53,6 +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` | `false` | 在当前文档编译并挂载,不使用 iframe。`vue` 使用宿主同一份实例;其余 import map 仍是 URL,加载后作为模块变量。 |
56
57
  | `options` | `PlaygroundOptions` | `{}` | 传给 Vue REPL store 的实例级选项;`cdnURL` 会同时作用于预览样式和默认 import map。 |
57
58
  | `previewOptions` | `SandboxProps['previewOptions']` | `undefined` | 传给当前 iframe 的 preview 选项。 |
58
59
  | `locale` | `Language` | `en-US` | 界面语言;未传入时使用上层 Locale Provider。 |
@@ -70,6 +71,66 @@ const files = ref({
70
71
 
71
72
  Playground 只接受来自自身 iframe 的导航消息。宿主应用应监听 `navigate` 并交给自己的 Router 处理。
72
73
 
74
+ ## 预览内的临时高度
75
+
76
+ Playground 会在预览应用中注入 `docs:playground`,也可通过 iframe 内的 `window.$docsPlayground` 访问同一服务。适用于 ActionSheet、Modal、Drawer、Message 等依赖视口高度的示例,无需自行撑高 DOM 或轮询 `window.innerHeight`。
77
+
78
+ ```ts
79
+ import { inject, ref } from 'vue';
80
+ import type { PlaygroundService } from '@deot/docs-playground';
81
+
82
+ const playground = inject<PlaygroundService>('docs:playground')!;
83
+ const visible = ref(false);
84
+
85
+ // 定义时不执行,点击后先等待高度就绪,再打开弹层;visible 关闭时恢复。
86
+ // visible 是 ref 且省略 handler 时,默认执行 visible.value = true。
87
+ const handleOpen = playground.run(560, { visible });
88
+
89
+ // 也可生成 boolean 状态处理函数:true 扩展高度,false 恢复高度。
90
+ const handleVisibleChange = playground.run(560);
91
+
92
+ // 支持 getter 和 computed;不能传入普通 boolean 快照。
93
+ const openDrawer = playground.run(560, { visible: () => state.drawerVisible }, () => {
94
+ state.drawerVisible = true;
95
+ });
96
+
97
+ // 回调返回的 Promise 表示弹层关闭时,可省略 options。
98
+ const openActionSheet = playground.run(560, async () => {
99
+ await MActionSheet.open({ title: '请选择操作' });
100
+ });
101
+ ```
102
+
103
+ `run(height)`、`run(height, handler)` 和 `run(height, options, handler)` 均返回事件处理函数。传入 handler 时会透传调用参数和回调结果,最后一个参数始终为 handler。`options` 当前只包含 `visible?: Ref<boolean> | (() => boolean)`;传入可写 `Ref<boolean>` 时可以省略 handler,默认将其设为 `true`。getter 和只读 computed 需要显式 handler。示例中的 `state`、`MActionSheet` 由业务代码提供。
104
+
105
+ | 接口 | 行为 |
106
+ | --- | --- |
107
+ | `enable(height): Promise<boolean>` | 设置手动临时高度,等待 iframe 实际高度达到要求后返回 `true`。连续调用替换旧的手动请求。 |
108
+ | `disable(): void` | 释放手动请求;重复调用无副作用,不影响 `run` 的请求。 |
109
+ | `run(height)` | 返回 boolean 事件处理函数;收到 `true` 时调用 `enable(height)`,收到 `false` 时调用 `disable()`。 |
110
+ | `run(height, handler)` | 等待高度后执行;同步返回或 Promise 完成时恢复高度,异常也恢复并继续抛出。 |
111
+ | `run(height, { visible: ref })` | 等待高度后默认执行 `visible.value = true`,并在状态关闭时恢复高度。 |
112
+ | `run(height, { visible }, handler)` | 执行前监听状态,关闭时恢复;回调完成后未打开或回调抛错时也释放。异步打开流程应由回调返回的 Promise 覆盖。 |
113
+
114
+ 同一个 `run` 返回函数在回调与弹层均结束前会复用当前 Promise,避免重复点击打开;不同函数的请求独立管理并取最大高度。Message / Toast 等立即返回的 API 应通过 `visible` 或关闭回调恢复高度,不能把函数返回当成弹层关闭。
115
+
116
+ ```ts
117
+ const handleMessage = async () => {
118
+ if (!await playground.enable(240)) return;
119
+ try {
120
+ Message.info({ content: '已保存', onClose: () => playground.disable() });
121
+ } catch (error) {
122
+ playground.disable();
123
+ throw error;
124
+ }
125
+ };
126
+ ```
127
+
128
+ 高度为正有限 CSS px,表示 iframe 内部可视高度,不包含 `previewInset` 与边框。普通预览取原有高度与临时请求的最大值,释放后恢复当前视口规则;独立窗口受可用屏幕空间限制。等待超时(2 秒)、取消或空间不足时返回 `false`;`run` 在这些情况下不执行回调并返回 `undefined`。
129
+
130
+ 在 `setup` 中创建的 `run` 会随当前 Vue scope 自动取消等待、移除监听并释放高度;其他位置创建时绑定预览应用。手动 `enable/disable` 绑定整个预览应用,子组件单独卸载时按需自行 `disable()`。刷新、重新运行及应用卸载会统一释放请求。服务不取消已经启动的业务任务,也不调用组件的 `destroy()`;示例保留必要的业务 `onUnmounted`,只需删除高度等待使用的 `disposed` 标记。
131
+
132
+ 类型导出为 `PlaygroundService`、`PlaygroundRunOptions` 和 `PlaygroundWindow`。Window 类型可通过 `(window as PlaygroundWindow).$docsPlayground` 显式使用;服务仅存在于 Playground 预览应用,不向普通页面声明必然存在的全局变量。接入项目需使用包含该接口的新版 Playground。
133
+
73
134
  ## 其他公共导出
74
135
 
75
136
  | 导出 | 说明 |
@@ -98,6 +159,7 @@ Playground 只接受来自自身 iframe 的导航消息。宿主应用应监听
98
159
  - Vue SFC 的 `<style lang="scss">` / `lang="sass"` 以及独立 `.scss` / `.sass` 文件会在浏览器里编译;`_partial.scss` 只作为 `@use` 依赖。
99
160
  - `options.builtinImportMap.imports` 可以覆盖默认模块 URL。Vue 运行时仍从 `play.vuejs.org` 加载。
100
161
  - 每个 Playground 实例都使用自己的 preview 配置和 iframe 消息来源校验。
162
+ - `local` 默认开启:同一份 `files` 在当前文档里编译。内置依赖默认用宿主副本;`modules` 里与默认 CDN 不同的地址会覆盖,包括 `vue` 和 `vue/server-renderer`。直接渲染不撑开预览高度,`expandable` 固定为关闭,`docs:playground.run` 不会申请临时高度;`<DocsLink>` 仍通过 `docs:navigate` 通知。`local: false` 改回 iframe Sandbox。
101
163
 
102
164
  ## 仓库内 examples
103
165
 
@@ -0,0 +1,94 @@
1
+ //#region packages/playground/src/constants.ts
2
+ var DEFAULT_ENTRY = "App.vue";
3
+ /** 预览样式和默认 import map 共用的 npm CDN 前缀。 */
4
+ var DEFAULT_CDN_URL = "https://cdn.jsdelivr.net/npm";
5
+ var NEW_SFC_CODE = `<script setup lang="ts"><\/script>\n\n<template>\n <div>\n <slot />\n <\/div>\n<\/template>\n`;
6
+ //#endregion
7
+ //#region packages/playground/src/cdn.ts
8
+ var PREVIEW_STYLE_ASSETS = [
9
+ "@deot/style/dist/index.normalize-only.css",
10
+ "@deot/vc-components/dist/index.style.css",
11
+ "@deot/style/dist/index.css"
12
+ ];
13
+ var BUILTIN_IMPORT_ASSETS = {
14
+ "@deot/vc": "@deot/vc/dist/index.js",
15
+ "@deot/vc-shared": "@deot/vc-shared/dist/index.js",
16
+ "@deot/vc-hooks": "@deot/vc-hooks/dist/index.js",
17
+ "@deot/vc-locale": "@deot/vc-locale/dist/index.js",
18
+ "@deot/vc-components": "@deot/vc-components/dist/index.js",
19
+ "@deot/helper-resize": "@deot/helper-resize/dist/index.js",
20
+ "@deot/helper-utils": "@deot/helper-utils/dist/index.js",
21
+ "@deot/helper-cache": "@deot/helper-cache/dist/index.js",
22
+ "@deot/helper-fp": "@deot/helper-fp/dist/index.js",
23
+ "@deot/helper-dom": "@deot/helper-dom/dist/index.js",
24
+ "@deot/helper-wheel": "@deot/helper-wheel/dist/index.js",
25
+ "@deot/helper-validator": "@deot/helper-validator/dist/index.js",
26
+ "@deot/helper-load": "@deot/helper-load/dist/index.js",
27
+ "@deot/helper-scheduler": "@deot/helper-scheduler/dist/index.js",
28
+ "@deot/helper-emitter": "@deot/helper-emitter/dist/index.js",
29
+ "@deot/helper-is": "@deot/helper-is/dist/index.js",
30
+ "@deot/helper-device": "@deot/helper-device/dist/index.js",
31
+ "@deot/helper-route": "@deot/helper-route/dist/index.js",
32
+ "@deot/helper-unicode": "@deot/helper-unicode/dist/index.js",
33
+ "@deot/helper": "@deot/helper/dist/index.js",
34
+ "normalize-wheel": "normalize-wheel-es/dist/index.mjs",
35
+ "photoswipe": "photoswipe/dist/photoswipe.esm.js",
36
+ "photoswipe/lightbox": "photoswipe/dist/photoswipe-lightbox.esm.js",
37
+ "vue": "vue/dist/vue.runtime.esm-browser.js",
38
+ "vue/server-renderer": "vue/dist/server-renderer.esm-browser.js"
39
+ };
40
+ var normalizeCdnURL = (cdnURL = DEFAULT_CDN_URL) => {
41
+ return cdnURL.trim().replace(/\/+$/u, "") || "https://cdn.jsdelivr.net/npm";
42
+ };
43
+ var cdnJoin = (origin, asset) => `${origin}/${asset.replace(/^\/+/u, "")}`;
44
+ var jsdelivrEsmURL = (asset) => cdnJoin(DEFAULT_CDN_URL, asset);
45
+ var SASS_CDN_URL = jsdelivrEsmURL("sass/+esm");
46
+ var cdnAsset = (cdnURL, asset) => cdnJoin(normalizeCdnURL(cdnURL), asset);
47
+ var createBuiltinImports = (cdnURL = DEFAULT_CDN_URL) => ({
48
+ ...Object.fromEntries(Object.entries(BUILTIN_IMPORT_ASSETS).map(([specifier, asset]) => [specifier, cdnAsset(cdnURL, asset)])),
49
+ "lodash-es": jsdelivrEsmURL("lodash-es/+esm")
50
+ });
51
+ var createBuiltinStyles = (cdnURL = DEFAULT_CDN_URL) => Object.fromEntries(PREVIEW_STYLE_ASSETS.map((asset) => [asset, cdnAsset(cdnURL, asset)]));
52
+ //#endregion
53
+ Object.defineProperty(exports, "DEFAULT_CDN_URL", {
54
+ enumerable: true,
55
+ get: function() {
56
+ return DEFAULT_CDN_URL;
57
+ }
58
+ });
59
+ Object.defineProperty(exports, "DEFAULT_ENTRY", {
60
+ enumerable: true,
61
+ get: function() {
62
+ return DEFAULT_ENTRY;
63
+ }
64
+ });
65
+ Object.defineProperty(exports, "NEW_SFC_CODE", {
66
+ enumerable: true,
67
+ get: function() {
68
+ return NEW_SFC_CODE;
69
+ }
70
+ });
71
+ Object.defineProperty(exports, "SASS_CDN_URL", {
72
+ enumerable: true,
73
+ get: function() {
74
+ return SASS_CDN_URL;
75
+ }
76
+ });
77
+ Object.defineProperty(exports, "createBuiltinImports", {
78
+ enumerable: true,
79
+ get: function() {
80
+ return createBuiltinImports;
81
+ }
82
+ });
83
+ Object.defineProperty(exports, "createBuiltinStyles", {
84
+ enumerable: true,
85
+ get: function() {
86
+ return createBuiltinStyles;
87
+ }
88
+ });
89
+ Object.defineProperty(exports, "normalizeCdnURL", {
90
+ enumerable: true,
91
+ get: function() {
92
+ return normalizeCdnURL;
93
+ }
94
+ });
@@ -0,0 +1,53 @@
1
+ //#region packages/playground/src/constants.ts
2
+ var DEFAULT_ENTRY = "App.vue";
3
+ /** 预览样式和默认 import map 共用的 npm CDN 前缀。 */
4
+ var DEFAULT_CDN_URL = "https://cdn.jsdelivr.net/npm";
5
+ var NEW_SFC_CODE = `<script setup lang="ts"><\/script>\n\n<template>\n <div>\n <slot />\n <\/div>\n<\/template>\n`;
6
+ //#endregion
7
+ //#region packages/playground/src/cdn.ts
8
+ var PREVIEW_STYLE_ASSETS = [
9
+ "@deot/style/dist/index.normalize-only.css",
10
+ "@deot/vc-components/dist/index.style.css",
11
+ "@deot/style/dist/index.css"
12
+ ];
13
+ var BUILTIN_IMPORT_ASSETS = {
14
+ "@deot/vc": "@deot/vc/dist/index.js",
15
+ "@deot/vc-shared": "@deot/vc-shared/dist/index.js",
16
+ "@deot/vc-hooks": "@deot/vc-hooks/dist/index.js",
17
+ "@deot/vc-locale": "@deot/vc-locale/dist/index.js",
18
+ "@deot/vc-components": "@deot/vc-components/dist/index.js",
19
+ "@deot/helper-resize": "@deot/helper-resize/dist/index.js",
20
+ "@deot/helper-utils": "@deot/helper-utils/dist/index.js",
21
+ "@deot/helper-cache": "@deot/helper-cache/dist/index.js",
22
+ "@deot/helper-fp": "@deot/helper-fp/dist/index.js",
23
+ "@deot/helper-dom": "@deot/helper-dom/dist/index.js",
24
+ "@deot/helper-wheel": "@deot/helper-wheel/dist/index.js",
25
+ "@deot/helper-validator": "@deot/helper-validator/dist/index.js",
26
+ "@deot/helper-load": "@deot/helper-load/dist/index.js",
27
+ "@deot/helper-scheduler": "@deot/helper-scheduler/dist/index.js",
28
+ "@deot/helper-emitter": "@deot/helper-emitter/dist/index.js",
29
+ "@deot/helper-is": "@deot/helper-is/dist/index.js",
30
+ "@deot/helper-device": "@deot/helper-device/dist/index.js",
31
+ "@deot/helper-route": "@deot/helper-route/dist/index.js",
32
+ "@deot/helper-unicode": "@deot/helper-unicode/dist/index.js",
33
+ "@deot/helper": "@deot/helper/dist/index.js",
34
+ "normalize-wheel": "normalize-wheel-es/dist/index.mjs",
35
+ "photoswipe": "photoswipe/dist/photoswipe.esm.js",
36
+ "photoswipe/lightbox": "photoswipe/dist/photoswipe-lightbox.esm.js",
37
+ "vue": "vue/dist/vue.runtime.esm-browser.js",
38
+ "vue/server-renderer": "vue/dist/server-renderer.esm-browser.js"
39
+ };
40
+ var normalizeCdnURL = (cdnURL = DEFAULT_CDN_URL) => {
41
+ return cdnURL.trim().replace(/\/+$/u, "") || "https://cdn.jsdelivr.net/npm";
42
+ };
43
+ var cdnJoin = (origin, asset) => `${origin}/${asset.replace(/^\/+/u, "")}`;
44
+ var jsdelivrEsmURL = (asset) => cdnJoin(DEFAULT_CDN_URL, asset);
45
+ var SASS_CDN_URL = jsdelivrEsmURL("sass/+esm");
46
+ var cdnAsset = (cdnURL, asset) => cdnJoin(normalizeCdnURL(cdnURL), asset);
47
+ var createBuiltinImports = (cdnURL = DEFAULT_CDN_URL) => ({
48
+ ...Object.fromEntries(Object.entries(BUILTIN_IMPORT_ASSETS).map(([specifier, asset]) => [specifier, cdnAsset(cdnURL, asset)])),
49
+ "lodash-es": jsdelivrEsmURL("lodash-es/+esm")
50
+ });
51
+ var createBuiltinStyles = (cdnURL = DEFAULT_CDN_URL) => Object.fromEntries(PREVIEW_STYLE_ASSETS.map((asset) => [asset, cdnAsset(cdnURL, asset)]));
52
+ //#endregion
53
+ export { DEFAULT_CDN_URL as a, normalizeCdnURL as i, createBuiltinImports as n, DEFAULT_ENTRY as o, createBuiltinStyles as r, NEW_SFC_CODE as s, SASS_CDN_URL as t };
@@ -0,0 +1,333 @@
1
+ import { n as compileScssSource } from "./scss-Co2it4vB.js";
2
+ import { collectReachableFilenames, createFileIndex, getExtension, normalizeVirtualPath, resolveEntry } from "./files-C5UMzYxA.js";
3
+ import { n as transformScript } from "./script-xKUjE8pf.js";
4
+ import { n as collectCss } from "./local-sandbox-B8IJ5jWb.js";
5
+ import { compileString } from "sass";
6
+ //#region packages/playground/src/core/runtime/local/compile/scss.ts
7
+ /**
8
+ * 直接渲染使用已安装的 sass,importer 与 iframe 预览共用,不注入额外 BEM。
9
+ */
10
+ var compiler = { compileString(source, options) {
11
+ return { css: compileString(source, options).css };
12
+ } };
13
+ var toScssFiles = (files) => {
14
+ const map = {};
15
+ Object.entries(files || {}).forEach(([filename, code]) => {
16
+ const normalized = filename.replace(/\\/gu, "/");
17
+ map[normalized] = code;
18
+ map[normalized.replace(/^\/+/u, "")] = code;
19
+ });
20
+ return map;
21
+ };
22
+ var compileLocalScss = (source, filename, files, lang) => {
23
+ try {
24
+ return compileScssSource(source, filename.replace(/^\/+/u, ""), compiler, toScssFiles(files), lang);
25
+ } catch (error) {
26
+ /* istanbul ignore next -- Sass 失败时 message 总是字符串 */
27
+ const message = error instanceof Error ? error.message : String(error);
28
+ throw new Error(`[Playground] SCSS 编译失败 ${filename}: ${message}`, { cause: error });
29
+ }
30
+ };
31
+ //#endregion
32
+ //#region packages/playground/src/core/runtime/local/compile/sfc.ts
33
+ var compilerPromise = null;
34
+ /**
35
+ * 只加载浏览器构建,避免 `@vue/compiler-sfc` 的 Node 主入口碰文件系统。
36
+ * @returns 浏览器版 compiler-sfc。
37
+ */
38
+ var loadCompiler = () => {
39
+ compilerPromise ??= import("@vue/compiler-sfc/dist/compiler-sfc.esm-browser.js").then((mod) => {
40
+ return mod.default || mod;
41
+ });
42
+ return compilerPromise;
43
+ };
44
+ var hashId = (filename) => {
45
+ let hash = 0;
46
+ for (let i = 0; i < filename.length; i += 1) {
47
+ hash = (hash << 5) - hash + filename.charCodeAt(i);
48
+ hash |= 0;
49
+ }
50
+ return Math.abs(hash).toString(36).slice(0, 8);
51
+ };
52
+ var messageOf$1 = (error) => error instanceof Error ? error.message : String(error);
53
+ var isScssLang = (lang) => lang === "scss" || lang === "sass";
54
+ var isSupportedStyleLang = (lang) => !lang || lang === "css" || isScssLang(lang);
55
+ var compileSfc = async (code, filename, files = {}) => {
56
+ const compiler = await loadCompiler();
57
+ const errors = [];
58
+ const id = hashId(filename);
59
+ const { descriptor, errors: parseErrors } = compiler.parse(code, {
60
+ filename,
61
+ sourceMap: false
62
+ });
63
+ parseErrors.forEach((error) => {
64
+ errors.push(messageOf$1(error));
65
+ });
66
+ if (errors.length) return {
67
+ js: "export default {}",
68
+ css: "",
69
+ errors
70
+ };
71
+ const srcBlock = [
72
+ descriptor.script,
73
+ descriptor.scriptSetup,
74
+ descriptor.template,
75
+ ...descriptor.styles
76
+ ].find((block) => block?.src);
77
+ if (srcBlock) {
78
+ errors.push(`[Playground] 不支持 <${srcBlock.type} src>,请把源码放进 files。文件: ${filename}`);
79
+ return {
80
+ js: "export default {}",
81
+ css: "",
82
+ errors
83
+ };
84
+ }
85
+ const hasScript = !!(descriptor.script || descriptor.scriptSetup);
86
+ const isTS = !!(descriptor.script?.lang?.startsWith("ts") || descriptor.scriptSetup?.lang?.startsWith("ts"));
87
+ let clientCode;
88
+ let bindings;
89
+ if (hasScript) try {
90
+ const scriptResult = compiler.compileScript(descriptor, {
91
+ id,
92
+ inlineTemplate: true,
93
+ genDefaultAs: "__sfc__",
94
+ templateOptions: {
95
+ filename,
96
+ id,
97
+ scoped: descriptor.styles.some((style) => style.scoped)
98
+ }
99
+ });
100
+ clientCode = scriptResult.content;
101
+ bindings = scriptResult.bindings;
102
+ } catch (error) {
103
+ errors.push(`[Playground] compileScript ${filename}: ${messageOf$1(error)}`);
104
+ return {
105
+ js: "export default {}",
106
+ css: "",
107
+ errors
108
+ };
109
+ }
110
+ else clientCode = "const __sfc__ = {};";
111
+ const templateInlined = !!(descriptor.scriptSetup && hasScript);
112
+ if (descriptor.template && !templateInlined) try {
113
+ const templateResult = compiler.compileTemplate({
114
+ id,
115
+ filename,
116
+ source: descriptor.template.content,
117
+ scoped: descriptor.styles.some((style) => style.scoped),
118
+ compilerOptions: {
119
+ bindingMetadata: bindings,
120
+ isTS
121
+ }
122
+ });
123
+ if (templateResult.errors?.length) templateResult.errors.forEach((error) => {
124
+ errors.push(messageOf$1(error));
125
+ });
126
+ else {
127
+ clientCode += `\n${templateResult.code}`;
128
+ clientCode += "\n__sfc__.render = typeof render === \"function\" ? render : exports.render;";
129
+ }
130
+ } catch (error) {
131
+ errors.push(`[Playground] compileTemplate ${filename}: ${messageOf$1(error)}`);
132
+ }
133
+ if (descriptor.styles.some((style) => style.scoped)) clientCode += `\n__sfc__.__scopeId = ${JSON.stringify(`data-v-${id}`)};`;
134
+ clientCode += `\n__sfc__.__file = ${JSON.stringify(filename)};`;
135
+ clientCode += "\nexport default __sfc__;";
136
+ let css = "";
137
+ for (const style of descriptor.styles) {
138
+ if (!isSupportedStyleLang(style.lang)) {
139
+ errors.push(`[Playground] <style lang="${style.lang}"> 暂不支持(仅 CSS / SCSS / Sass)。文件: ${filename}`);
140
+ continue;
141
+ }
142
+ let source = style.content;
143
+ if (isScssLang(style.lang)) try {
144
+ source = compileLocalScss(source, filename, files, style.lang);
145
+ } catch (error) {
146
+ errors.push(messageOf$1(error));
147
+ continue;
148
+ }
149
+ try {
150
+ const styleResult = await compiler.compileStyleAsync({
151
+ id: style.scoped ? `data-v-${id}` : id,
152
+ filename,
153
+ source,
154
+ scoped: !!style.scoped
155
+ });
156
+ if (styleResult.errors?.length) styleResult.errors.forEach((error) => {
157
+ errors.push(messageOf$1(error));
158
+ });
159
+ else css += `${styleResult.code}\n`;
160
+ } catch (error) {
161
+ /* istanbul ignore next -- 样式编译失败会走 errors 数组,这里只兜底抛错 */
162
+ errors.push(`[Playground] compileStyle ${filename}: ${messageOf$1(error)}`);
163
+ }
164
+ }
165
+ return {
166
+ js: clientCode,
167
+ css,
168
+ errors
169
+ };
170
+ };
171
+ //#endregion
172
+ //#region packages/playground/src/core/runtime/local/compile/index.ts
173
+ var JSON_IDENT = /^[$A-Z_][0-9A-Z_$]*$/iu;
174
+ var messageOf = (error) => error instanceof Error ? error.message : String(error);
175
+ /**
176
+ * Vite 语义:default 是整份对象,合法标识符键再挂成具名导出。
177
+ * @param parsed JSON.parse 的结果。
178
+ * @param filename 当前虚拟文件名,写入 sourceURL。
179
+ * @returns 可执行的 CJS 文本。
180
+ */
181
+ var transformJson = (parsed, filename) => {
182
+ const lines = [`exports.default = ${JSON.stringify(parsed)};`];
183
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) Object.keys(parsed).forEach((key) => {
184
+ if (!JSON_IDENT.test(key) || key === "__esModule" || key === "default") return;
185
+ lines.push(`exports[${JSON.stringify(key)}] = exports.default[${JSON.stringify(key)}];`);
186
+ });
187
+ lines.push(`//# sourceURL=playground://${filename}`);
188
+ return lines.join("\n");
189
+ };
190
+ var compileOne = async (filename, code, files) => {
191
+ const ext = getExtension(filename);
192
+ const errors = [];
193
+ if (ext === ".vue") {
194
+ if (/lang\s*=\s*["']jsx["']/iu.test(code) || /lang\s*=\s*["']tsx["']/iu.test(code)) {
195
+ errors.push(`[Playground] JSX/TSX 暂不支持: ${filename}`);
196
+ return {
197
+ filename,
198
+ js: "exports.default = {}",
199
+ css: "",
200
+ errors
201
+ };
202
+ }
203
+ const result = await compileSfc(code, filename, files);
204
+ errors.push(...result.errors);
205
+ return {
206
+ filename,
207
+ js: transformScript(result.js, filename, { typescript: true }),
208
+ css: result.css,
209
+ errors
210
+ };
211
+ }
212
+ if (ext === ".css") return {
213
+ filename,
214
+ js: `exports.default = {};\n//# sourceURL=playground://${filename}`,
215
+ css: code,
216
+ errors
217
+ };
218
+ if (ext === ".scss" || ext === ".sass") try {
219
+ const css = compileLocalScss(code, filename, files);
220
+ return {
221
+ filename,
222
+ js: `exports.default = {};\n//# sourceURL=playground://${filename}`,
223
+ css,
224
+ errors
225
+ };
226
+ } catch (error) {
227
+ errors.push(messageOf(error));
228
+ return {
229
+ filename,
230
+ js: "exports.default = {};",
231
+ css: "",
232
+ errors
233
+ };
234
+ }
235
+ if (ext === ".json") try {
236
+ return {
237
+ filename,
238
+ js: transformJson(JSON.parse(code), filename),
239
+ css: "",
240
+ errors
241
+ };
242
+ } catch (error) {
243
+ errors.push(`[Playground] JSON 解析失败 ${filename}: ${messageOf(error)}`);
244
+ return {
245
+ filename,
246
+ js: "exports.default = {};",
247
+ css: "",
248
+ errors
249
+ };
250
+ }
251
+ if (ext === ".js" || ext === ".ts" || ext === ".jsx" || ext === ".tsx") {
252
+ if (ext === ".jsx" || ext === ".tsx") {
253
+ errors.push(`[Playground] JSX/TSX 暂不支持: ${filename}`);
254
+ return {
255
+ filename,
256
+ js: "exports.default = {};",
257
+ css: "",
258
+ errors
259
+ };
260
+ }
261
+ try {
262
+ return {
263
+ filename,
264
+ js: transformScript(code, filename, { typescript: ext === ".ts" }),
265
+ css: "",
266
+ errors
267
+ };
268
+ } catch (error) {
269
+ errors.push(messageOf(error));
270
+ return {
271
+ filename,
272
+ js: "exports.default = {};",
273
+ css: "",
274
+ errors
275
+ };
276
+ }
277
+ }
278
+ errors.push(`[Playground] 不支持的文件类型: ${filename}`);
279
+ return {
280
+ filename,
281
+ js: "exports.default = {};",
282
+ css: "",
283
+ errors
284
+ };
285
+ };
286
+ /**
287
+ * 浏览器内编译整份 files。入口和相对路径与 Playground 的 files 键一致。
288
+ * @param files 当前 Playground 文件表。
289
+ * @param entryOrOptions 入口文件名,或带入口的编译选项。
290
+ * @returns 编译出的模块、样式和错误。
291
+ */
292
+ var compilePlayground = async (files, entryOrOptions) => {
293
+ const options = typeof entryOrOptions === "string" ? { entry: entryOrOptions } : entryOrOptions || {};
294
+ const errors = [];
295
+ let entry;
296
+ try {
297
+ entry = resolveEntry(files, options.entry);
298
+ } catch (error) {
299
+ return {
300
+ entry: "",
301
+ modules: {},
302
+ css: [],
303
+ errors: [messageOf(error)]
304
+ };
305
+ }
306
+ const fileIndex = createFileIndex(files);
307
+ const reachable = collectReachableFilenames(files, entry);
308
+ const modules = {};
309
+ const cssChunks = [];
310
+ for (const filename of reachable) {
311
+ const code = fileIndex.get(filename);
312
+ if (typeof code !== "string") continue;
313
+ const compiled = await compileOne(filename, code, files);
314
+ errors.push(...compiled.errors);
315
+ modules[filename] = {
316
+ filename,
317
+ js: compiled.js,
318
+ css: compiled.css
319
+ };
320
+ cssChunks.push(...collectCss([compiled.css]));
321
+ }
322
+ const normalizedEntry = normalizeVirtualPath(entry);
323
+ /* istanbul ignore if -- 可达编译总会留下入口模块 */
324
+ if (!modules[normalizedEntry]) errors.push(`[Playground] 入口编译结果缺失: ${normalizedEntry}`);
325
+ return {
326
+ entry: normalizedEntry,
327
+ modules,
328
+ css: cssChunks,
329
+ errors
330
+ };
331
+ };
332
+ //#endregion
333
+ export { compilePlayground };