@antdv-next/docs-plugins 0.0.1

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/demo/formatter.d.ts +17 -0
  4. package/dist/demo/formatter.js +32 -0
  5. package/dist/demo/get-demo-id.d.ts +9 -0
  6. package/dist/demo/get-demo-id.js +19 -0
  7. package/dist/demo/index.d.ts +26 -0
  8. package/dist/demo/index.js +359 -0
  9. package/dist/demo/tsToJs.d.ts +9 -0
  10. package/dist/demo/tsToJs.js +60 -0
  11. package/dist/demo/types.d.ts +23 -0
  12. package/dist/index.d.ts +20 -0
  13. package/dist/index.js +19 -0
  14. package/dist/isolate-styles.d.ts +14 -0
  15. package/dist/isolate-styles.js +30 -0
  16. package/dist/markdown.d.ts +39 -0
  17. package/dist/markdown.js +115 -0
  18. package/dist/md-plugin.d.ts +18 -0
  19. package/dist/md-plugin.js +9 -0
  20. package/dist/md2vue.d.ts +16 -0
  21. package/dist/md2vue.js +106 -0
  22. package/dist/plugins/container.d.ts +16 -0
  23. package/dist/plugins/container.js +53 -0
  24. package/dist/plugins/demo.d.ts +29 -0
  25. package/dist/plugins/demo.js +139 -0
  26. package/dist/plugins/github-alerts.d.ts +6 -0
  27. package/dist/plugins/github-alerts.js +49 -0
  28. package/dist/plugins/image.d.ts +12 -0
  29. package/dist/plugins/image.js +17 -0
  30. package/dist/plugins/link.d.ts +14 -0
  31. package/dist/plugins/link.js +25 -0
  32. package/dist/plugins/pre-wrapper.d.ts +10 -0
  33. package/dist/plugins/pre-wrapper.js +26 -0
  34. package/dist/plugins/stackblitz.d.ts +5 -0
  35. package/dist/plugins/stackblitz.js +21 -0
  36. package/dist/plugins/table.d.ts +5 -0
  37. package/dist/plugins/table.js +25 -0
  38. package/dist/shared.d.ts +7 -0
  39. package/dist/shared.js +7 -0
  40. package/dist/utils/short-hash.d.ts +4 -0
  41. package/dist/utils/short-hash.js +21 -0
  42. package/package.json +94 -0
  43. package/src/components/code-demo/code-editor-bridge.vue +36 -0
  44. package/src/components/code-demo/compile-sfc.ts +207 -0
  45. package/src/components/code-demo/context.ts +86 -0
  46. package/src/components/code-demo/expand-icon.vue +12 -0
  47. package/src/components/code-demo/external-link-icon.vue +5 -0
  48. package/src/components/code-demo/index.vue +682 -0
  49. package/src/components/code-demo/virtual.d.ts +38 -0
  50. package/src/demo/formatter.ts +50 -0
  51. package/src/demo/get-demo-id.ts +33 -0
  52. package/src/demo/index.ts +498 -0
  53. package/src/demo/tsToJs.ts +79 -0
  54. package/src/demo/types.ts +23 -0
  55. package/src/index.ts +19 -0
  56. package/src/isolate-styles.ts +47 -0
  57. package/src/markdown.ts +188 -0
  58. package/src/md-plugin.ts +24 -0
  59. package/src/md2vue.ts +163 -0
  60. package/src/plugins/container.ts +135 -0
  61. package/src/plugins/demo.ts +282 -0
  62. package/src/plugins/github-alerts.ts +69 -0
  63. package/src/plugins/image.ts +29 -0
  64. package/src/plugins/link.ts +32 -0
  65. package/src/plugins/pre-wrapper.ts +49 -0
  66. package/src/plugins/stackblitz.ts +32 -0
  67. package/src/plugins/table.ts +39 -0
  68. package/src/shared.ts +4 -0
  69. package/src/utils/short-hash.ts +27 -0
@@ -0,0 +1,49 @@
1
+ //#region src/plugins/github-alerts.ts
2
+ const markerRE = /^\[!(TIP|NOTE|INFO|IMPORTANT|WARNING|CAUTION|DANGER)\]([^\n\r]*)/i;
3
+ function gitHubAlertsPlugin(md, options) {
4
+ const titleMark = {
5
+ tip: options?.tipLabel || "TIP",
6
+ note: options?.noteLabel || "NOTE",
7
+ info: options?.infoLabel || "INFO",
8
+ important: options?.importantLabel || "IMPORTANT",
9
+ warning: options?.warningLabel || "WARNING",
10
+ caution: options?.cautionLabel || "CAUTION",
11
+ danger: options?.dangerLabel || "DANGER"
12
+ };
13
+ md.core.ruler.after("block", "github-alerts", (state) => {
14
+ const tokens = state.tokens;
15
+ for (let i = 0; i < tokens.length; i++) {
16
+ if (tokens[i].type !== "blockquote_open") continue;
17
+ const startIndex = i;
18
+ const open = tokens[startIndex];
19
+ let endIndex = i + 1;
20
+ while (endIndex < tokens.length && (tokens[endIndex].type !== "blockquote_close" || tokens[endIndex].level !== open.level)) endIndex++;
21
+ if (endIndex === tokens.length) continue;
22
+ const close = tokens[endIndex];
23
+ const firstContent = tokens.slice(startIndex, endIndex + 1).find((token) => token.type === "inline");
24
+ if (!firstContent) continue;
25
+ const match = firstContent.content.match(markerRE);
26
+ if (!match) continue;
27
+ const type = match[1].toLowerCase();
28
+ const title = match[2]?.trim() || titleMark[type] || capitalize(type);
29
+ firstContent.content = firstContent.content.slice(match[0].length).trimStart();
30
+ open.type = "github_alert_open";
31
+ open.tag = "div";
32
+ open.meta = {
33
+ title,
34
+ type
35
+ };
36
+ close.type = "github_alert_close";
37
+ close.tag = "div";
38
+ }
39
+ });
40
+ md.renderer.rules.github_alert_open = function(tokens, idx) {
41
+ const { title, type } = tokens[idx].meta;
42
+ return `<div class="${type} custom-block github-alert"><p class="custom-block-title">${title}</p>\n`;
43
+ };
44
+ }
45
+ function capitalize(str) {
46
+ return str.charAt(0).toUpperCase() + str.slice(1);
47
+ }
48
+ //#endregion
49
+ export { gitHubAlertsPlugin };
@@ -0,0 +1,12 @@
1
+ import MarkdownIt from "markdown-it";
2
+ //#region src/plugins/image.d.ts
3
+ interface ImagePluginOptions {
4
+ /**
5
+ * Support native lazy loading for the `<img>` tag.
6
+ * @default false
7
+ */
8
+ lazyLoading?: boolean;
9
+ }
10
+ declare function imagePlugin(md: MarkdownIt, { lazyLoading }?: ImagePluginOptions): void;
11
+ //#endregion
12
+ export { ImagePluginOptions, imagePlugin };
@@ -0,0 +1,17 @@
1
+ import { EXTERNAL_URL_RE } from "../shared.js";
2
+ //#region src/plugins/image.ts
3
+ function imagePlugin(md, { lazyLoading } = {}) {
4
+ const imageRule = md.renderer.rules.image;
5
+ md.renderer.rules.image = (tokens, idx, options, env, self) => {
6
+ const token = tokens[idx];
7
+ let url = token.attrGet("src");
8
+ if (url && !EXTERNAL_URL_RE.test(url)) {
9
+ if (!/^\.?\//.test(url)) url = `./${url}`;
10
+ token.attrSet("src", decodeURIComponent(url));
11
+ }
12
+ if (lazyLoading) token.attrSet("loading", "lazy");
13
+ return imageRule(tokens, idx, options, env, self);
14
+ };
15
+ }
16
+ //#endregion
17
+ export { imagePlugin };
@@ -0,0 +1,14 @@
1
+ import MarkdownIt from "markdown-it";
2
+ //#region src/plugins/link.d.ts
3
+ /**
4
+ * 让 markdown 中的外部链接在新标签页打开。
5
+ *
6
+ * 仅对带协议(http(s)://、mailto: 等)的外部链接生效,
7
+ * 站内相对链接保持默认行为,避免影响路由内跳转与锚点导航。
8
+ *
9
+ * 已通过 `markdown-it-attrs` 等方式显式声明的 `target` / `rel`
10
+ * 不会被覆盖,保留作者对单个链接的控制权。
11
+ */
12
+ declare function linkPlugin(md: MarkdownIt): void;
13
+ //#endregion
14
+ export { linkPlugin };
@@ -0,0 +1,25 @@
1
+ import { EXTERNAL_URL_RE } from "../shared.js";
2
+ //#region src/plugins/link.ts
3
+ /**
4
+ * 让 markdown 中的外部链接在新标签页打开。
5
+ *
6
+ * 仅对带协议(http(s)://、mailto: 等)的外部链接生效,
7
+ * 站内相对链接保持默认行为,避免影响路由内跳转与锚点导航。
8
+ *
9
+ * 已通过 `markdown-it-attrs` 等方式显式声明的 `target` / `rel`
10
+ * 不会被覆盖,保留作者对单个链接的控制权。
11
+ */
12
+ function linkPlugin(md) {
13
+ const defaultLinkRender = md.renderer.rules.link_open || ((tokens, idx, options, _env, self) => self.renderToken(tokens, idx, options));
14
+ md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
15
+ const token = tokens[idx];
16
+ const href = token.attrGet("href");
17
+ if (href && EXTERNAL_URL_RE.test(href)) {
18
+ if (!token.attrGet("target")) token.attrSet("target", "_blank");
19
+ if (!token.attrGet("rel")) token.attrSet("rel", "noopener noreferrer");
20
+ }
21
+ return defaultLinkRender(tokens, idx, options, env, self);
22
+ };
23
+ }
24
+ //#endregion
25
+ export { linkPlugin };
@@ -0,0 +1,10 @@
1
+ import MarkdownIt from "markdown-it";
2
+ //#region src/plugins/pre-wrapper.d.ts
3
+ interface Options {
4
+ hasSingleTheme: boolean;
5
+ }
6
+ declare function preWrapperPlugin(md: MarkdownIt, options: Options): void;
7
+ declare function getAdaptiveThemeMarker(options: Options): "" | " ant-code-theme";
8
+ declare function extractTitle(info: string, html?: boolean): string;
9
+ //#endregion
10
+ export { Options, extractTitle, getAdaptiveThemeMarker, preWrapperPlugin };
@@ -0,0 +1,26 @@
1
+ //#region src/plugins/pre-wrapper.ts
2
+ function preWrapperPlugin(md, options) {
3
+ const fence = md.renderer.rules.fence;
4
+ md.renderer.rules.fence = (...args) => {
5
+ const [tokens, idx] = args;
6
+ const token = tokens[idx];
7
+ token.info = token.info.replace(/\[.*\]/, "");
8
+ const active = / active(?: |$)/.test(token.info) ? " active" : "";
9
+ token.info = token.info.replace(/ active$/, "").replace(/ active /, " ");
10
+ const lang = extractLang(token.info);
11
+ const rawCode = fence(...args);
12
+ return `<div class="language-${lang}${getAdaptiveThemeMarker(options)}${active}"><button title="Copy Code" class="copy"></button><span class="lang">${lang}</span>${rawCode}</div>`;
13
+ };
14
+ }
15
+ function getAdaptiveThemeMarker(options) {
16
+ return options.hasSingleTheme ? "" : " ant-code-theme";
17
+ }
18
+ function extractTitle(info, html = false) {
19
+ if (html) return info.replace(/<!--[\s\S]*?-->/g, "").match(/data-title="(.*?)"/)?.[1] || "";
20
+ return info.match(/\[(.*)\]/)?.[1] || extractLang(info) || "txt";
21
+ }
22
+ function extractLang(info) {
23
+ return info.trim().replace(/=(\d*)/, "").replace(/:(no-)?line-numbers(\{| |$|=\d*).*/, "").replace(/(-vue|\{| ).*$/, "").replace(/^vue-html$/, "template").replace(/^ansi$/, "");
24
+ }
25
+ //#endregion
26
+ export { extractTitle, getAdaptiveThemeMarker, preWrapperPlugin };
@@ -0,0 +1,5 @@
1
+ import MarkdownIt from "markdown-it";
2
+ //#region src/plugins/stackblitz.d.ts
3
+ declare function stackblitzPlugin(md: MarkdownIt): void;
4
+ //#endregion
5
+ export { stackblitzPlugin };
@@ -0,0 +1,21 @@
1
+ //#region src/plugins/stackblitz.ts
2
+ function stackblitzPlugin(md) {
3
+ const fence = md.renderer.rules.fence;
4
+ md.renderer.rules.fence = (...args) => {
5
+ const [tokens, idx] = args;
6
+ const token = tokens[idx];
7
+ const info = token.info.trim();
8
+ if (info.startsWith("stackblitz")) {
9
+ const code = token.content;
10
+ const titleMatch = info.match(/\{[^}]*title\s*=\s*"([^"]*)"[^}]*\}/);
11
+ const title = titleMatch ? titleMatch[1] : "";
12
+ return `<stackblitz code="${encodeURIComponent(code)}" title="${escapeHtml(title)}"></stackblitz>`;
13
+ }
14
+ return fence(...args);
15
+ };
16
+ }
17
+ function escapeHtml(str) {
18
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
19
+ }
20
+ //#endregion
21
+ export { stackblitzPlugin };
@@ -0,0 +1,5 @@
1
+ import MarkdownIt from "markdown-it";
2
+ //#region src/plugins/table.d.ts
3
+ declare function tablePlugin(md: MarkdownIt): void;
4
+ //#endregion
5
+ export { tablePlugin };
@@ -0,0 +1,25 @@
1
+ //#region src/plugins/table.ts
2
+ function tablePlugin(md) {
3
+ md.core.ruler.push("table_api_attribute", (state) => {
4
+ const tokens = state.tokens;
5
+ let inApiSection = false;
6
+ for (let i = 0; i < tokens.length; i++) {
7
+ const token = tokens[i];
8
+ if (token.type === "heading_open" && token.tag === "h2") {
9
+ const inlineToken = tokens[i + 1];
10
+ if (inlineToken && inlineToken.type === "inline") {
11
+ if (inlineToken.content.trim().toLowerCase() === "api") inApiSection = true;
12
+ else inApiSection = false;
13
+ }
14
+ }
15
+ if (inApiSection && token.type === "table_open") {
16
+ const existingClass = token.attrGet("class") || "";
17
+ const newClass = existingClass ? `${existingClass} component-table-api` : "component-table-api";
18
+ token.attrSet("class", newClass);
19
+ }
20
+ }
21
+ return true;
22
+ });
23
+ }
24
+ //#endregion
25
+ export { tablePlugin };
@@ -0,0 +1,7 @@
1
+ //#region src/shared.d.ts
2
+ declare const EXTERNAL_URL_RE: RegExp;
3
+ declare const SCRIPT_REGEX: RegExp;
4
+ declare const STYLE_REGEX: RegExp;
5
+ declare const DOCS_REGEX: RegExp;
6
+ //#endregion
7
+ export { DOCS_REGEX, EXTERNAL_URL_RE, SCRIPT_REGEX, STYLE_REGEX };
package/dist/shared.js ADDED
@@ -0,0 +1,7 @@
1
+ //#region src/shared.ts
2
+ const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
3
+ const SCRIPT_REGEX = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
4
+ const STYLE_REGEX = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
5
+ const DOCS_REGEX = /<docs\b[^>]*>[\s\S]*?<\/docs>/gi;
6
+ //#endregion
7
+ export { DOCS_REGEX, EXTERNAL_URL_RE, SCRIPT_REGEX, STYLE_REGEX };
@@ -0,0 +1,4 @@
1
+ //#region src/utils/short-hash.d.ts
2
+ declare function shortHash(str: string): string;
3
+ //#endregion
4
+ export { shortHash };
@@ -0,0 +1,21 @@
1
+ //#region src/utils/short-hash.ts
2
+ const BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
3
+ function toBase62(num) {
4
+ let str = "";
5
+ do {
6
+ str = BASE62[num % 62] + str;
7
+ num = Math.floor(num / 62);
8
+ } while (num > 0);
9
+ return str;
10
+ }
11
+ function shortHash(str) {
12
+ const bytes = new TextEncoder().encode(str);
13
+ let hash = 2166136261;
14
+ for (let i = 0; i < bytes.length; i++) {
15
+ hash ^= bytes[i];
16
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
17
+ }
18
+ return toBase62(hash >>> 0);
19
+ }
20
+ //#endregion
21
+ export { shortHash };
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@antdv-next/docs-plugins",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "private": false,
6
+ "description": "Vite / markdown-it plugins and demo components for docs-base documentation sites",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/antdv-next/docs-plugins.git"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "./dist/*": {
19
+ "types": "./dist/*.d.ts",
20
+ "import": "./dist/*.js",
21
+ "default": "./dist/*.js"
22
+ },
23
+ "./component/*": {
24
+ "types": "./src/components/*.d.ts",
25
+ "import": "./src/components/*"
26
+ }
27
+ },
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "files": [
31
+ "LICENSE",
32
+ "README.md",
33
+ "dist",
34
+ "package.json",
35
+ "src"
36
+ ],
37
+ "dependencies": {
38
+ "@mdit-vue/plugin-frontmatter": "^3.0.2",
39
+ "@mdit-vue/plugin-headers": "^3.0.2",
40
+ "@mdit-vue/plugin-title": "^3.0.2",
41
+ "@mdit-vue/plugin-toc": "^3.0.2",
42
+ "@mdit-vue/shared": "^3.0.2",
43
+ "@mdit-vue/types": "^3.0.2",
44
+ "@shikijs/markdown-it": "^4.4.1",
45
+ "@shikijs/transformers": "^4.4.1",
46
+ "lru-cache": "^11.5.2",
47
+ "markdown-it": "^14.3.0",
48
+ "markdown-it-anchor": "^9.2.1",
49
+ "markdown-it-async": "^2.2.0",
50
+ "markdown-it-attrs": "^4.5.0",
51
+ "markdown-it-container": "^4.0.0",
52
+ "markdown-it-emoji": "^3.1.0",
53
+ "mlly": "^1.8.2",
54
+ "nanoid": "^5.1.16",
55
+ "oxfmt": "^0.41.0",
56
+ "pathe": "^2.0.3",
57
+ "picomatch": "^4.0.5",
58
+ "postcss-selector-parser": "^7.1.4",
59
+ "shiki": "^4.4.1",
60
+ "vite": "^8.2.0",
61
+ "vue": "^3.5.40"
62
+ },
63
+ "devDependencies": {
64
+ "@antdv-next/icons": "^1.1.1",
65
+ "@antfu/eslint-config": "^7.7.3",
66
+ "@codesandbox/sandpack-themes": "^2.0.21",
67
+ "@types/markdown-it": "14.1.2",
68
+ "@types/markdown-it-attrs": "^4.1.3",
69
+ "@types/markdown-it-container": "^2.0.11",
70
+ "@types/node": "^25.9.5",
71
+ "@types/picomatch": "^4.0.3",
72
+ "@vue/compiler-sfc": "^3.5.40",
73
+ "@vue/tsconfig": "^0.8.1",
74
+ "@vueuse/core": "^14.4.0",
75
+ "antdv-style": "^1.0.0-rc.1",
76
+ "eslint": "^9.39.5",
77
+ "postcss": "^8.5.25",
78
+ "sandpack-vue3": "^3.1.12",
79
+ "sucrase": "^3.35.1",
80
+ "tsdown": "^0.22.14",
81
+ "typescript": "~5.9.3",
82
+ "vue-router": "^4.6.4"
83
+ },
84
+ "publishConfig": {
85
+ "access": "public"
86
+ },
87
+ "scripts": {
88
+ "build": "tsdown",
89
+ "dev": "tsdown --watch",
90
+ "lint": "eslint .",
91
+ "lint:fix": "eslint --fix .",
92
+ "typecheck": "tsc --noEmit"
93
+ }
94
+ }
@@ -0,0 +1,36 @@
1
+ <script setup lang="ts">
2
+ import { SandpackCodeEditor, useActiveCode, useSandpack } from 'sandpack-vue3'
3
+ import { watch } from 'vue'
4
+
5
+ defineProps<{
6
+ readOnly?: boolean
7
+ }>()
8
+
9
+ const emit = defineEmits<{
10
+ (e: 'update:code', code: string): void
11
+ }>()
12
+
13
+ const { code } = useActiveCode()
14
+ const { sandpack } = useSandpack()
15
+
16
+ function resetCode(newCode: string) {
17
+ sandpack.updateCurrentFile(newCode)
18
+ }
19
+
20
+ defineExpose({ resetCode })
21
+
22
+ watch(code, (val) => {
23
+ if (val !== undefined) {
24
+ emit('update:code', val)
25
+ }
26
+ })
27
+ </script>
28
+
29
+ <template>
30
+ <SandpackCodeEditor
31
+ :show-tabs="false"
32
+ :show-line-numbers="false"
33
+ :read-only="readOnly"
34
+ style="min-height: 100px;"
35
+ />
36
+ </template>
@@ -0,0 +1,207 @@
1
+ import * as Vue from 'vue'
2
+
3
+ let compilerSfc: typeof import('@vue/compiler-sfc') | null = null
4
+ let sucraseModule: typeof import('sucrase') | null = null
5
+
6
+ // 演示源码可 import 的模块由站点注入,逐模块容错:
7
+ // 某个模块不可用只影响其自身的编辑预览,不阻断整个编译链路
8
+ const resolvedExtraModules = new WeakMap<Record<string, () => Promise<any>>, Record<string, any>>()
9
+
10
+ async function ensureDependencies(extraModules: Record<string, () => Promise<any>>) {
11
+ if (!compilerSfc) {
12
+ compilerSfc = await import('@vue/compiler-sfc')
13
+ }
14
+ if (!sucraseModule) {
15
+ sucraseModule = await import('sucrase')
16
+ }
17
+ if (!resolvedExtraModules.has(extraModules)) {
18
+ const resolved: Record<string, any> = {}
19
+ await Promise.all(
20
+ Object.entries(extraModules).map(async ([name, loader]) => {
21
+ try {
22
+ const mod = await loader()
23
+ resolved[name] = (mod as any).default || mod
24
+ }
25
+ catch {
26
+ // 依赖不可用时跳过,保持 modulesMap 中无该条目
27
+ }
28
+ }),
29
+ )
30
+ resolvedExtraModules.set(extraModules, resolved)
31
+ }
32
+ }
33
+
34
+ function buildModulesMap(extraModules: Record<string, () => Promise<any>>) {
35
+ return {
36
+ vue: Vue,
37
+ ...resolvedExtraModules.get(extraModules),
38
+ }
39
+ }
40
+
41
+ function transformCode(code: string): string {
42
+ let result = code
43
+
44
+ // Named imports: import { a, b as c } from 'module'
45
+ // Need to convert 'as' to ':' for destructuring syntax
46
+ result = result.replace(
47
+ /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]\s*;?/g,
48
+ (_, names, source) => {
49
+ const transformedNames = names.replace(/\bas\b/g, ':')
50
+ return `const {${transformedNames}} = (__modules__["${source}"] || {});`
51
+ },
52
+ )
53
+
54
+ // Default import: import Name from 'module'
55
+ result = result.replace(
56
+ /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]\s*;?/g,
57
+ (_, name, source) => `const ${name} = (__modules__["${source}"] || {}).default || (__modules__["${source}"] || {});`,
58
+ )
59
+
60
+ // Namespace import: import * as Name from 'module'
61
+ result = result.replace(
62
+ /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]\s*;?/g,
63
+ (_, name, source) => `const ${name} = (__modules__["${source}"] || {});`,
64
+ )
65
+
66
+ // Side-effect imports: import 'module'
67
+ result = result.replace(/^\s*import\s*['"][^'"]+['"]\s*(?:;\s*)?$/gm, '')
68
+
69
+ // export default → __exports__.default =
70
+ result = result.replace(/export\s+default\s+/g, '__exports__.default = ')
71
+
72
+ // export function name → function name + track for later export
73
+ const exportedNames: string[] = []
74
+ result = result.replace(/export\s+function\s+(\w+)/g, (_, name) => {
75
+ exportedNames.push(name)
76
+ return `function ${name}`
77
+ })
78
+
79
+ result = result.replace(/export\s+(const|let|var)\s+(\w+)/g, (_, keyword, name) => {
80
+ exportedNames.push(name)
81
+ return `${keyword} ${name}`
82
+ })
83
+
84
+ // Append exports for named declarations
85
+ for (const name of exportedNames) {
86
+ result += `\n__exports__["${name}"] = ${name};`
87
+ }
88
+
89
+ return result
90
+ }
91
+
92
+ export async function compileSfcSource(
93
+ source: string,
94
+ extraModules: Record<string, () => Promise<any>> = {},
95
+ ): Promise<{ component: any, error: string | null }> {
96
+ await ensureDependencies(extraModules)
97
+
98
+ const modulesMap = buildModulesMap(extraModules)
99
+
100
+ try {
101
+ const { compileScript, compileTemplate, parse } = compilerSfc!
102
+ const { transform } = sucraseModule!
103
+
104
+ const id = `live-${Math.random().toString(36).slice(2, 8)}`
105
+ const { descriptor, errors } = parse(source, { filename: 'Demo.vue' })
106
+
107
+ if (errors.length > 0) {
108
+ return { component: null, error: errors.map(e => e.message).join('\n') }
109
+ }
110
+
111
+ let jsCode: string
112
+
113
+ if (descriptor.scriptSetup) {
114
+ // <script setup> — use inlineTemplate to bake render into setup()
115
+ const compiled = compileScript(descriptor, {
116
+ id,
117
+ inlineTemplate: true,
118
+ })
119
+ jsCode = compiled.content
120
+ }
121
+ else if (descriptor.script) {
122
+ // Options API <script>
123
+ const compiled = compileScript(descriptor, { id })
124
+ jsCode = compiled.content
125
+
126
+ if (descriptor.template) {
127
+ const templateResult = compileTemplate({
128
+ source: descriptor.template.content,
129
+ filename: 'Demo.vue',
130
+ id,
131
+ compilerOptions: {
132
+ bindingMetadata: compiled.bindings,
133
+ },
134
+ })
135
+ if (templateResult.errors.length) {
136
+ return {
137
+ component: null,
138
+ error: templateResult.errors
139
+ .map(e => (typeof e === 'string' ? e : e.message))
140
+ .join('\n'),
141
+ }
142
+ }
143
+ jsCode += `\n${templateResult.code}`
144
+ }
145
+ }
146
+ else if (descriptor.template) {
147
+ // Template-only component
148
+ const templateResult = compileTemplate({
149
+ source: descriptor.template.content,
150
+ filename: 'Demo.vue',
151
+ id,
152
+ })
153
+ if (templateResult.errors.length) {
154
+ return {
155
+ component: null,
156
+ error: templateResult.errors
157
+ .map(e => (typeof e === 'string' ? e : e.message))
158
+ .join('\n'),
159
+ }
160
+ }
161
+ jsCode = templateResult.code
162
+ }
163
+ else {
164
+ return { component: null, error: 'No template or script found' }
165
+ }
166
+
167
+ // Strip TypeScript type annotations
168
+ try {
169
+ const result = transform(jsCode, {
170
+ transforms: ['typescript'],
171
+ disableESTransforms: true,
172
+ })
173
+ jsCode = result.code
174
+ }
175
+ catch {
176
+ // May already be plain JS — continue
177
+ }
178
+
179
+ // Transform imports / exports to work with new Function
180
+ jsCode = transformCode(jsCode)
181
+
182
+ // Evaluate the compiled code
183
+ const __exports__: Record<string, any> = {}
184
+ // eslint-disable-next-line no-new-func
185
+ const fn = new Function('__modules__', '__exports__', jsCode)
186
+ fn(modulesMap, __exports__)
187
+
188
+ // For options API + separate template, attach the render function
189
+ if (!descriptor.scriptSetup && descriptor.script && descriptor.template) {
190
+ const comp = __exports__.default || {}
191
+ if (__exports__.render) {
192
+ comp.render = __exports__.render
193
+ }
194
+ return { component: comp, error: null }
195
+ }
196
+
197
+ // For template-only, create component with render
198
+ if (!descriptor.scriptSetup && !descriptor.script && descriptor.template) {
199
+ return { component: { render: __exports__.render }, error: null }
200
+ }
201
+
202
+ return { component: __exports__.default, error: null }
203
+ }
204
+ catch (e: any) {
205
+ return { component: null, error: e.message || String(e) }
206
+ }
207
+ }