@vizejs/vite-plugin-musea 0.0.1-alpha.99 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/{a11y-CHcxz6UR.d.ts → a11y-Bvx5TJb8.d.ts} +2 -2
  2. package/dist/{a11y-CHcxz6UR.d.ts.map → a11y-Bvx5TJb8.d.ts.map} +1 -1
  3. package/dist/a11y.d.ts +2 -2
  4. package/dist/gallery/assets/abap-Cry0R76c.js +1 -0
  5. package/dist/gallery/assets/apex-GS4zZi0I.js +1 -0
  6. package/dist/gallery/assets/azcli-DMImymmY.js +1 -0
  7. package/dist/gallery/assets/bat-D6epFECU.js +1 -0
  8. package/dist/gallery/assets/bicep-7klDZ283.js +2 -0
  9. package/dist/gallery/assets/cameligo-PvLD8t4t.js +1 -0
  10. package/dist/gallery/assets/clojure-BTbSGpb3.js +1 -0
  11. package/dist/gallery/assets/codicon-DCmgc-ay.ttf +0 -0
  12. package/dist/gallery/assets/coffee-Bhl_9YuJ.js +1 -0
  13. package/dist/gallery/assets/cpp-CM5j04eT.js +1 -0
  14. package/dist/gallery/assets/csharp-Dh0Ee7SY.js +1 -0
  15. package/dist/gallery/assets/csp-CLRC61y6.js +1 -0
  16. package/dist/gallery/assets/css-B0t_muXd.js +3 -0
  17. package/dist/gallery/assets/css.worker-Cbw1kvi8.js +88 -0
  18. package/dist/gallery/assets/cssMode-CJhQ5_ix.js +4 -0
  19. package/dist/gallery/assets/cypher-C5e5inIh.js +1 -0
  20. package/dist/gallery/assets/dart-DIK3l8YT.js +1 -0
  21. package/dist/gallery/assets/dockerfile-D7OAO0hl.js +1 -0
  22. package/dist/gallery/assets/ecl-CP7nM2KN.js +1 -0
  23. package/dist/gallery/assets/editor-B55U_qvj.css +1 -0
  24. package/dist/gallery/assets/editor-F8AxQWwE.css +1 -0
  25. package/dist/gallery/assets/editor.api-ASE8WyAM.js +644 -0
  26. package/dist/gallery/assets/editor.main-Cma4vQHs.js +63 -0
  27. package/dist/gallery/assets/editor.worker-Cs7HTPcl.js +12 -0
  28. package/dist/gallery/assets/elixir-DNRIIj6-.js +1 -0
  29. package/dist/gallery/assets/flow9-BC5Cr9X0.js +1 -0
  30. package/dist/gallery/assets/freemarker2-DBfUbCzr.js +3 -0
  31. package/dist/gallery/assets/fsharp-52P4yqMh.js +1 -0
  32. package/dist/gallery/assets/go-yKE3zUfB.js +1 -0
  33. package/dist/gallery/assets/graphql-D3sNVCLc.js +1 -0
  34. package/dist/gallery/assets/handlebars-D8J8fxvx.js +1 -0
  35. package/dist/gallery/assets/hcl-BB7aW7AX.js +1 -0
  36. package/dist/gallery/assets/html-CV__5YWO.js +1 -0
  37. package/dist/gallery/assets/html.worker-CYmk49z4.js +495 -0
  38. package/dist/gallery/assets/htmlMode-Bf6TSizS.js +4 -0
  39. package/dist/gallery/assets/index-0yy-2NJQ.js +63 -0
  40. package/dist/gallery/assets/index-DLBj3lpz.css +1 -0
  41. package/dist/gallery/assets/ini-BdRufzJj.js +1 -0
  42. package/dist/gallery/assets/java-CeUu-z7Y.js +1 -0
  43. package/dist/gallery/assets/javascript-Bgcd5n0p.js +1 -0
  44. package/dist/gallery/assets/json.worker-CWR6J9Qf.js +51 -0
  45. package/dist/gallery/assets/jsonMode-C3cW8QC6.js +10 -0
  46. package/dist/gallery/assets/julia-CXu-Fn93.js +1 -0
  47. package/dist/gallery/assets/kotlin-TwsjxLJ3.js +1 -0
  48. package/dist/gallery/assets/less-CviwWNG4.js +2 -0
  49. package/dist/gallery/assets/lexon-BTOivnjP.js +1 -0
  50. package/dist/gallery/assets/liquid-B-CbADyN.js +1 -0
  51. package/dist/gallery/assets/lua-6W3WJOvj.js +1 -0
  52. package/dist/gallery/assets/m3-tlthQ8Fo.js +1 -0
  53. package/dist/gallery/assets/markdown-CPR4Kr9O.js +1 -0
  54. package/dist/gallery/assets/mdx-wbp4TmId.js +1 -0
  55. package/dist/gallery/assets/mips-BfxZbsD8.js +1 -0
  56. package/dist/gallery/assets/monaco.contribution-xYWKV-A3.js +2 -0
  57. package/dist/gallery/assets/msdax-eKsr2VtO.js +1 -0
  58. package/dist/gallery/assets/mysql-D6-LO0bt.js +1 -0
  59. package/dist/gallery/assets/objective-c-DYtfYpNc.js +1 -0
  60. package/dist/gallery/assets/pascal-CPGyHbal.js +1 -0
  61. package/dist/gallery/assets/pascaligo-Dsp_VKxo.js +1 -0
  62. package/dist/gallery/assets/perl-CUVa2_Cu.js +1 -0
  63. package/dist/gallery/assets/pgsql-C2nbbU56.js +1 -0
  64. package/dist/gallery/assets/php-DxX2tlkL.js +1 -0
  65. package/dist/gallery/assets/pla-D55LHImG.js +1 -0
  66. package/dist/gallery/assets/postiats-Dw_nWtoT.js +1 -0
  67. package/dist/gallery/assets/powerquery-BldVOeNZ.js +1 -0
  68. package/dist/gallery/assets/powershell-fdqyoMut.js +1 -0
  69. package/dist/gallery/assets/protobuf-C-2cnAYL.js +2 -0
  70. package/dist/gallery/assets/pug-bDrVOc6m.js +1 -0
  71. package/dist/gallery/assets/python-DFBsc7m6.js +1 -0
  72. package/dist/gallery/assets/qsharp-B83Ol6AR.js +1 -0
  73. package/dist/gallery/assets/r-DAxg6zn-.js +1 -0
  74. package/dist/gallery/assets/razor-KMe1wwP0.js +1 -0
  75. package/dist/gallery/assets/redis-BSRYxJDu.js +1 -0
  76. package/dist/gallery/assets/redshift-BrtVU4Ki.js +1 -0
  77. package/dist/gallery/assets/restructuredtext-DdU6AjLQ.js +1 -0
  78. package/dist/gallery/assets/ruby-C-s7ovR-.js +1 -0
  79. package/dist/gallery/assets/rust-CmSb_pkG.js +1 -0
  80. package/dist/gallery/assets/sb-Bfo5Ukmr.js +1 -0
  81. package/dist/gallery/assets/scala-Cx2bkddK.js +1 -0
  82. package/dist/gallery/assets/scheme-DkT6GPaV.js +1 -0
  83. package/dist/gallery/assets/scss-DOPngiM2.js +3 -0
  84. package/dist/gallery/assets/shell-NYt6Xulf.js +1 -0
  85. package/dist/gallery/assets/solidity-CUiq_T3F.js +1 -0
  86. package/dist/gallery/assets/sophia-B4sI8Ij-.js +1 -0
  87. package/dist/gallery/assets/sparql-BNfhekQe.js +1 -0
  88. package/dist/gallery/assets/sql-Bzn3OZV3.js +1 -0
  89. package/dist/gallery/assets/st-CV0zI_0P.js +1 -0
  90. package/dist/gallery/assets/swift-CR3-zK7D.js +1 -0
  91. package/dist/gallery/assets/systemverilog-BxgPwTIi.js +1 -0
  92. package/dist/gallery/assets/tcl-BSnnsp36.js +1 -0
  93. package/dist/gallery/assets/ts.worker-B_5n269U.js +51339 -0
  94. package/dist/gallery/assets/tsMode-Bh__KITB.js +11 -0
  95. package/dist/gallery/assets/twig-Bx06vatZ.js +1 -0
  96. package/dist/gallery/assets/typescript-Dueie-TY.js +1 -0
  97. package/dist/gallery/assets/typespec-COSap3s7.js +1 -0
  98. package/dist/gallery/assets/vb-DU0VXhXP.js +1 -0
  99. package/dist/gallery/assets/wgsl-BegdTer-.js +298 -0
  100. package/dist/gallery/assets/xml-JPuKTaLo.js +1 -0
  101. package/dist/gallery/assets/yaml-ChmKv6PR.js +1 -0
  102. package/dist/gallery/index.html +16 -0
  103. package/dist/index.d.ts +2 -2
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +328 -112
  106. package/dist/index.js.map +1 -1
  107. package/dist/{vrt-m01uFerp.d.ts → vrt-Vb4aqPZE.d.ts} +23 -1
  108. package/dist/vrt-Vb4aqPZE.d.ts.map +1 -0
  109. package/dist/vrt.d.ts +1 -1
  110. package/package.json +4 -5
  111. package/dist/vrt-m01uFerp.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -86,15 +86,15 @@ function extractSubcategories(obj) {
86
86
  function isTokenValue(value) {
87
87
  if (typeof value !== "object" || value === null) return false;
88
88
  const obj = value;
89
- return "value" in obj && (typeof obj.value === "string" || typeof obj.value === "number");
89
+ return "value" in obj && (typeof obj.value === "string" || typeof obj.value === "number") || "$value" in obj && (typeof obj.$value === "string" || typeof obj.$value === "number");
90
90
  }
91
91
  /**
92
92
  * Normalize token to DesignToken interface.
93
93
  */
94
94
  function normalizeToken(raw) {
95
95
  const token = {
96
- value: raw.value,
97
- type: raw.type,
96
+ value: raw.value ?? raw.$value,
97
+ type: raw.type ?? raw.$type,
98
98
  description: raw.description,
99
99
  attributes: raw.attributes
100
100
  };
@@ -549,6 +549,71 @@ function loadNative() {
549
549
  }
550
550
  }
551
551
  /**
552
+ * JS-based fallback for SFC analysis when native `analyzeSfc` is not available.
553
+ * Uses regex parsing to extract props and emits from Vue SFC source.
554
+ */
555
+ function analyzeSfcFallback(source, _options) {
556
+ try {
557
+ const props = [];
558
+ const emits = [];
559
+ const scriptSetupMatch = source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
560
+ if (!scriptSetupMatch) {
561
+ const scriptMatch = source.match(/<script[^>]*>([\s\S]*?)<\/script>/);
562
+ if (!scriptMatch) return {
563
+ props: [],
564
+ emits: []
565
+ };
566
+ }
567
+ const scriptContent = scriptSetupMatch?.[1] || "";
568
+ const propsMatch = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}>\s*\(/);
569
+ const propsMatch2 = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}>/);
570
+ const propsBody = propsMatch?.[1] || propsMatch2?.[1];
571
+ if (propsBody) {
572
+ const lines = propsBody.split("\n");
573
+ let i = 0;
574
+ while (i < lines.length) {
575
+ const line = lines[i].trim();
576
+ if (line.startsWith("/**") || line.startsWith("*") || line.startsWith("*/")) {
577
+ i++;
578
+ continue;
579
+ }
580
+ const propMatch = line.match(/^(\w+)(\?)?:\s*(.+?)(?:;?\s*)$/);
581
+ if (propMatch) {
582
+ const name = propMatch[1];
583
+ const optional = !!propMatch[2];
584
+ let type = propMatch[3].replace(/;$/, "").trim();
585
+ const defaultPattern = new RegExp(`\\b${name}\\s*=\\s*([^,}\\n]+)`);
586
+ const defaultMatch = scriptContent.match(defaultPattern);
587
+ const defaultValue = defaultMatch ? defaultMatch[1].trim() : void 0;
588
+ props.push({
589
+ name,
590
+ type,
591
+ required: !optional && defaultValue === void 0,
592
+ ...defaultValue !== void 0 ? { default_value: defaultValue } : {}
593
+ });
594
+ }
595
+ i++;
596
+ }
597
+ }
598
+ const emitsMatch = scriptContent.match(/defineEmits\s*<\s*\{([\s\S]*?)\}>/);
599
+ if (emitsMatch) {
600
+ const emitsBody = emitsMatch[1];
601
+ const emitRegex = /(\w+)\s*:/g;
602
+ let match;
603
+ while ((match = emitRegex.exec(emitsBody)) !== null) emits.push(match[1]);
604
+ }
605
+ return {
606
+ props,
607
+ emits
608
+ };
609
+ } catch {
610
+ return {
611
+ props: [],
612
+ emits: []
613
+ };
614
+ }
615
+ }
616
+ /**
552
617
  * Build the theme config object from plugin options for runtime injection.
553
618
  */
554
619
  function buildThemeConfig(theme) {
@@ -577,9 +642,13 @@ function musea(options = {}) {
577
642
  let inlineArt = options.inlineArt ?? false;
578
643
  const tokensPath = options.tokensPath;
579
644
  const themeConfig = buildThemeConfig(options.theme);
645
+ const previewCss = options.previewCss ?? [];
646
+ const previewSetup = options.previewSetup;
580
647
  let config;
581
648
  let server = null;
582
649
  const artFiles = new Map();
650
+ let resolvedPreviewCss = [];
651
+ let resolvedPreviewSetup = null;
583
652
  const mainPlugin = {
584
653
  name: "vite-plugin-musea",
585
654
  enforce: "pre",
@@ -597,12 +666,14 @@ function musea(options = {}) {
597
666
  if (options.storybookCompat === void 0 && mc.storybookCompat !== void 0) storybookCompat = mc.storybookCompat;
598
667
  if (options.inlineArt === void 0 && mc.inlineArt !== void 0) inlineArt = mc.inlineArt;
599
668
  }
669
+ resolvedPreviewCss = previewCss.map((cssPath) => path.isAbsolute(cssPath) ? cssPath : path.resolve(resolvedConfig.root, cssPath));
670
+ if (previewSetup) resolvedPreviewSetup = path.isAbsolute(previewSetup) ? previewSetup : path.resolve(resolvedConfig.root, previewSetup);
600
671
  },
601
672
  configureServer(devServer) {
602
673
  server = devServer;
603
674
  devServer.middlewares.use(basePath, async (req, res, next) => {
604
675
  const url = req.url || "/";
605
- if (url === "/" || url === "/index.html" || url.startsWith("/tokens") || url.startsWith("/component/")) {
676
+ if (url === "/" || url === "/index.html" || url.startsWith("/tokens") || url.startsWith("/component/") || url.startsWith("/tests")) {
606
677
  const galleryDistDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), "gallery");
607
678
  const indexHtmlPath = path.join(galleryDistDir, "index.html");
608
679
  try {
@@ -610,7 +681,6 @@ function musea(options = {}) {
610
681
  let html = await fs.promises.readFile(indexHtmlPath, "utf-8");
611
682
  const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${JSON.stringify(themeConfig)};` : "";
612
683
  html = html.replace("</head>", `<script>window.__MUSEA_BASE_PATH__='${basePath}';${themeScript}</script></head>`);
613
- html = await devServer.transformIndexHtml(basePath + url, html);
614
684
  res.setHeader("Content-Type", "text/html");
615
685
  res.end(html);
616
686
  return;
@@ -647,6 +717,19 @@ function musea(options = {}) {
647
717
  }
648
718
  next();
649
719
  });
720
+ devServer.middlewares.use(`${basePath}/vendor/axe-core.min.js`, async (_req, res, _next) => {
721
+ try {
722
+ const require = createRequire(import.meta.url);
723
+ const axeCorePath = require.resolve("axe-core/axe.min.js");
724
+ const content = await fs.promises.readFile(axeCorePath, "utf-8");
725
+ res.setHeader("Content-Type", "application/javascript");
726
+ res.setHeader("Cache-Control", "public, max-age=86400");
727
+ res.end(content);
728
+ } catch {
729
+ res.statusCode = 404;
730
+ res.end("axe-core not installed");
731
+ }
732
+ });
650
733
  devServer.middlewares.use(`${basePath}/preview-module`, async (req, res, _next) => {
651
734
  const url = new URL(req.url || "", `http://localhost`);
652
735
  const artPath = url.searchParams.get("art");
@@ -669,7 +752,7 @@ function musea(options = {}) {
669
752
  return;
670
753
  }
671
754
  const variantComponentName = toPascalCase(variant.name);
672
- const moduleCode = generatePreviewModule(art, variantComponentName, variant.name);
755
+ const moduleCode = generatePreviewModule(art, variantComponentName, variant.name, resolvedPreviewCss, resolvedPreviewSetup);
673
756
  try {
674
757
  const result = await devServer.transformRequest(`virtual:musea-preview:${artPath}:${variantName}`);
675
758
  if (result) {
@@ -704,8 +787,7 @@ function musea(options = {}) {
704
787
  res.end("Variant not found");
705
788
  return;
706
789
  }
707
- const rawHtml = generatePreviewHtml(art, variant, basePath);
708
- const html = await devServer.transformIndexHtml(`${basePath}/preview?art=${encodeURIComponent(artPath)}&variant=${encodeURIComponent(variantName)}`, rawHtml);
790
+ const html = generatePreviewHtml(art, variant, basePath, config.base);
709
791
  res.setHeader("Content-Type", "text/html");
710
792
  res.end(html);
711
793
  });
@@ -1021,16 +1103,57 @@ function musea(options = {}) {
1021
1103
  try {
1022
1104
  const source = await fs.promises.readFile(artPath$1, "utf-8");
1023
1105
  const binding = loadNative();
1024
- if (binding.generateArtPalette) {
1025
- const palette = binding.generateArtPalette(source, { filename: artPath$1 });
1026
- sendJson(palette);
1027
- } else sendJson({
1106
+ let palette;
1107
+ if (binding.generateArtPalette) palette = binding.generateArtPalette(source, { filename: artPath$1 });
1108
+ else palette = {
1028
1109
  title: art$1.metadata.title,
1029
1110
  controls: [],
1030
1111
  groups: [],
1031
1112
  json: "{}",
1032
1113
  typescript: ""
1033
- });
1114
+ };
1115
+ if (palette.controls.length === 0 && art$1.metadata.component) {
1116
+ const resolvedComponentPath = path.isAbsolute(art$1.metadata.component) ? art$1.metadata.component : path.resolve(path.dirname(artPath$1), art$1.metadata.component);
1117
+ try {
1118
+ const componentSource = await fs.promises.readFile(resolvedComponentPath, "utf-8");
1119
+ const analysis = binding.analyzeSfc ? binding.analyzeSfc(componentSource, { filename: resolvedComponentPath }) : analyzeSfcFallback(componentSource, { filename: resolvedComponentPath });
1120
+ if (analysis.props.length > 0) {
1121
+ palette.controls = analysis.props.map((prop) => {
1122
+ let control = "text";
1123
+ if (prop.type === "boolean") control = "boolean";
1124
+ else if (prop.type === "number") control = "number";
1125
+ else if (prop.type.includes("|") && !prop.type.includes("=>")) control = "select";
1126
+ const options$1 = [];
1127
+ if (control === "select") {
1128
+ const optionMatches = prop.type.match(/"([^"]+)"/g);
1129
+ if (optionMatches) for (const opt of optionMatches) {
1130
+ const val = opt.replace(/"/g, "");
1131
+ options$1.push({
1132
+ label: val,
1133
+ value: val
1134
+ });
1135
+ }
1136
+ }
1137
+ return {
1138
+ name: prop.name,
1139
+ control,
1140
+ default_value: prop.default_value !== void 0 ? prop.default_value === "true" ? true : prop.default_value === "false" ? false : typeof prop.default_value === "string" && prop.default_value.startsWith("\"") ? prop.default_value.replace(/^"|"$/g, "") : prop.default_value : void 0,
1141
+ description: void 0,
1142
+ required: prop.required,
1143
+ options: options$1,
1144
+ range: void 0,
1145
+ group: void 0
1146
+ };
1147
+ });
1148
+ palette.json = JSON.stringify({
1149
+ title: palette.title,
1150
+ controls: palette.controls
1151
+ }, null, 2);
1152
+ palette.typescript = `export interface ${palette.title}Props {\n${palette.controls.map((c) => ` ${c.name}${c.required ? "" : "?"}: ${c.control === "boolean" ? "boolean" : c.control === "number" ? "number" : c.control === "select" ? c.options.map((o) => `"${String(o.value)}"`).join(" | ") : "string"};`).join("\n")}\n}\n`;
1153
+ }
1154
+ } catch {}
1155
+ }
1156
+ sendJson(palette);
1034
1157
  } catch (e) {
1035
1158
  sendError(e instanceof Error ? e.message : String(e));
1036
1159
  }
@@ -1051,10 +1174,10 @@ function musea(options = {}) {
1051
1174
  if (binding.analyzeSfc) {
1052
1175
  const analysis = binding.analyzeSfc(source, { filename: resolvedComponentPath });
1053
1176
  sendJson(analysis);
1054
- } else sendJson({
1055
- props: [],
1056
- emits: []
1057
- });
1177
+ } else {
1178
+ const analysis = analyzeSfcFallback(source, { filename: resolvedComponentPath });
1179
+ sendJson(analysis);
1180
+ }
1058
1181
  } else sendJson({
1059
1182
  props: [],
1060
1183
  emits: []
@@ -1087,7 +1210,17 @@ function musea(options = {}) {
1087
1210
  minIndent = Math.min(minIndent, indent);
1088
1211
  }
1089
1212
  if (minIndent === Infinity) minIndent = 0;
1090
- const formatted = lines.map((line) => line.slice(minIndent)).join("\n");
1213
+ let formatted;
1214
+ if (minIndent > 0) formatted = lines.map((line) => line.slice(minIndent)).join("\n");
1215
+ else {
1216
+ let restIndent = Infinity;
1217
+ for (let i = 1; i < lines.length; i++) if (lines[i].trim()) {
1218
+ const indent = lines[i].match(/^(\s*)/)?.[1].length || 0;
1219
+ restIndent = Math.min(restIndent, indent);
1220
+ }
1221
+ if (restIndent === Infinity || restIndent === 0) formatted = lines.join("\n");
1222
+ else formatted = lines.map((line, i) => i === 0 ? line : line.slice(restIndent)).join("\n");
1223
+ }
1091
1224
  return "```" + lang + "\n" + formatted + "```";
1092
1225
  });
1093
1226
  sendJson({
@@ -1144,7 +1277,7 @@ function musea(options = {}) {
1144
1277
  return;
1145
1278
  }
1146
1279
  const variantComponentName = toPascalCase(variant.name);
1147
- const moduleCode = generatePreviewModuleWithProps(art, variantComponentName, variant.name, propsOverride);
1280
+ const moduleCode = generatePreviewModuleWithProps(art, variantComponentName, variant.name, propsOverride, resolvedPreviewCss, resolvedPreviewSetup);
1148
1281
  res.setHeader("Content-Type", "application/javascript");
1149
1282
  res.end(moduleCode);
1150
1283
  } catch (e) {
@@ -1266,6 +1399,7 @@ function musea(options = {}) {
1266
1399
  };
1267
1400
  },
1268
1401
  async buildStart() {
1402
+ console.log(`[musea] config.root: ${config.root}, include: ${JSON.stringify(include)}`);
1269
1403
  const files = await scanArtFiles(config.root, include, exclude, inlineArt);
1270
1404
  console.log(`[musea] Found ${files.length} art files`);
1271
1405
  for (const file of files) await processArtFile(file);
@@ -1277,15 +1411,15 @@ function musea(options = {}) {
1277
1411
  if (id.startsWith("virtual:musea-preview:")) return "\0musea-preview:" + id.slice(22);
1278
1412
  if (id.startsWith("virtual:musea-art:")) {
1279
1413
  const artPath = id.slice(18);
1280
- if (artFiles.has(artPath)) return "\0musea-art:" + artPath;
1414
+ if (artFiles.has(artPath)) return "\0musea-art:" + artPath + "?musea-virtual";
1281
1415
  }
1282
1416
  if (id.endsWith(".art.vue")) {
1283
1417
  const resolved = path.resolve(config.root, id);
1284
- if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved;
1418
+ if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
1285
1419
  }
1286
1420
  if (inlineArt && id.endsWith(".vue") && !id.endsWith(".art.vue")) {
1287
1421
  const resolved = path.resolve(config.root, id);
1288
- if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved;
1422
+ if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
1289
1423
  }
1290
1424
  return null;
1291
1425
  },
@@ -1301,17 +1435,17 @@ function musea(options = {}) {
1301
1435
  const art = artFiles.get(artPath);
1302
1436
  if (art) {
1303
1437
  const variantComponentName = toPascalCase(variantName);
1304
- return generatePreviewModule(art, variantComponentName, variantName);
1438
+ return generatePreviewModule(art, variantComponentName, variantName, resolvedPreviewCss, resolvedPreviewSetup);
1305
1439
  }
1306
1440
  }
1307
1441
  }
1308
1442
  if (id.startsWith("\0musea-art:")) {
1309
- const artPath = id.slice(11);
1443
+ const artPath = id.slice(11).replace(/\?musea-virtual$/, "");
1310
1444
  const art = artFiles.get(artPath);
1311
1445
  if (art) return generateArtModule(art, artPath);
1312
1446
  }
1313
1447
  if (id.startsWith(VIRTUAL_MUSEA_PREFIX)) {
1314
- const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length);
1448
+ const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length).replace(/\?musea-virtual$/, "");
1315
1449
  const art = artFiles.get(realPath);
1316
1450
  if (art) return generateArtModule(art, realPath);
1317
1451
  }
@@ -1321,7 +1455,7 @@ function musea(options = {}) {
1321
1455
  const { file } = ctx;
1322
1456
  if (file.endsWith(".art.vue") && artFiles.has(file)) {
1323
1457
  await processArtFile(file);
1324
- const virtualId = VIRTUAL_MUSEA_PREFIX + file;
1458
+ const virtualId = VIRTUAL_MUSEA_PREFIX + file + "?musea-virtual";
1325
1459
  const modules = server?.moduleGraph.getModulesByFile(virtualId);
1326
1460
  if (modules) return [...modules];
1327
1461
  }
@@ -1355,12 +1489,13 @@ function musea(options = {}) {
1355
1489
  variants: parsed.variants.map((v) => ({
1356
1490
  name: v.name,
1357
1491
  template: v.template,
1358
- isDefault: v.is_default,
1359
- skipVrt: v.skip_vrt
1492
+ isDefault: v.isDefault,
1493
+ skipVrt: v.skipVrt
1360
1494
  })),
1361
- hasScriptSetup: parsed.has_script_setup,
1362
- hasScript: parsed.has_script,
1363
- styleCount: parsed.style_count,
1495
+ hasScriptSetup: isInline ? false : parsed.hasScriptSetup,
1496
+ scriptSetupContent: !isInline && parsed.hasScriptSetup ? extractScriptSetupContent(source) : void 0,
1497
+ hasScript: parsed.hasScript,
1498
+ styleCount: parsed.styleCount,
1364
1499
  isInline,
1365
1500
  componentPath: isInline ? filePath : void 0
1366
1501
  };
@@ -1378,7 +1513,8 @@ function shouldProcess(file, include, exclude, root) {
1378
1513
  return false;
1379
1514
  }
1380
1515
  function matchGlob(filepath, pattern) {
1381
- const regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*(?!\*)/g, "[^/]*");
1516
+ const PLACEHOLDER = "<<GLOBSTAR>>";
1517
+ const regex = pattern.replaceAll("**", PLACEHOLDER).replace(/\./g, "\\.").replace(/\*/g, "[^/]*").replaceAll(PLACEHOLDER, ".*");
1382
1518
  return new RegExp(`^${regex}$`).test(filepath);
1383
1519
  }
1384
1520
  async function scanArtFiles(root, include, exclude, scanInlineArt = false) {
@@ -1418,35 +1554,32 @@ function generateGalleryHtml(basePath, themeConfig) {
1418
1554
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1419
1555
  <title>Musea - Component Gallery</title>
1420
1556
  <script>window.__MUSEA_BASE_PATH__='${basePath}';${themeScript}</script>
1421
- <link rel="preconnect" href="https://fonts.googleapis.com">
1422
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1423
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
1424
1557
  <style>
1425
1558
  :root {
1426
- --musea-bg-primary: #0d0d0d;
1427
- --musea-bg-secondary: #1a1815;
1428
- --musea-bg-tertiary: #252220;
1429
- --musea-bg-elevated: #2d2a27;
1430
- --musea-accent: #a34828;
1431
- --musea-accent-hover: #c45a32;
1432
- --musea-accent-subtle: rgba(163, 72, 40, 0.15);
1433
- --musea-text: #e6e9f0;
1434
- --musea-text-secondary: #c4c9d4;
1435
- --musea-text-muted: #7b8494;
1436
- --musea-border: #3a3530;
1437
- --musea-border-subtle: #2a2725;
1438
- --musea-success: #4ade80;
1439
- --musea-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
1440
- --musea-radius-sm: 6px;
1441
- --musea-radius-md: 8px;
1442
- --musea-radius-lg: 12px;
1559
+ --musea-bg-primary: #E6E2D6;
1560
+ --musea-bg-secondary: #ddd9cd;
1561
+ --musea-bg-tertiary: #d4d0c4;
1562
+ --musea-bg-elevated: #E6E2D6;
1563
+ --musea-accent: #121212;
1564
+ --musea-accent-hover: #2a2a2a;
1565
+ --musea-accent-subtle: rgba(18, 18, 18, 0.08);
1566
+ --musea-text: #121212;
1567
+ --musea-text-secondary: #3a3a3a;
1568
+ --musea-text-muted: #6b6b6b;
1569
+ --musea-border: #c8c4b8;
1570
+ --musea-border-subtle: #d4d0c4;
1571
+ --musea-success: #16a34a;
1572
+ --musea-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
1573
+ --musea-radius-sm: 4px;
1574
+ --musea-radius-md: 6px;
1575
+ --musea-radius-lg: 8px;
1443
1576
  --musea-transition: 0.15s ease;
1444
1577
  }
1445
1578
 
1446
1579
  * { box-sizing: border-box; margin: 0; padding: 0; }
1447
1580
 
1448
1581
  body {
1449
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
1582
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
1450
1583
  background: var(--musea-bg-primary);
1451
1584
  color: var(--musea-text);
1452
1585
  min-height: 100vh;
@@ -1493,7 +1626,7 @@ function generateGalleryHtml(basePath, themeConfig) {
1493
1626
  .logo-icon svg {
1494
1627
  width: 16px;
1495
1628
  height: 16px;
1496
- color: white;
1629
+ color: var(--musea-text);
1497
1630
  }
1498
1631
 
1499
1632
  .header-subtitle {
@@ -1769,7 +1902,7 @@ function generateGalleryHtml(basePath, themeConfig) {
1769
1902
  }
1770
1903
 
1771
1904
  .variant-preview-code {
1772
- font-family: 'SF Mono', 'Fira Code', monospace;
1905
+ font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
1773
1906
  font-size: 0.75rem;
1774
1907
  color: var(--musea-text-muted);
1775
1908
  background: var(--musea-bg-primary);
@@ -1914,31 +2047,19 @@ function generateGalleryHtml(basePath, themeConfig) {
1914
2047
  <div class="header-left">
1915
2048
  <a href="${basePath}" class="logo">
1916
2049
  <svg class="logo-svg" width="32" height="32" viewBox="0 0 200 200" fill="none">
1917
- <defs>
1918
- <linearGradient id="metal-grad" x1="0%" y1="0%" x2="100%" y2="20%">
1919
- <stop offset="0%" stop-color="#f0f2f5"/>
1920
- <stop offset="50%" stop-color="#9ca3b0"/>
1921
- <stop offset="100%" stop-color="#e07048"/>
1922
- </linearGradient>
1923
- <linearGradient id="metal-grad-dark" x1="0%" y1="0%" x2="100%" y2="30%">
1924
- <stop offset="0%" stop-color="#d0d4dc"/>
1925
- <stop offset="60%" stop-color="#6b7280"/>
1926
- <stop offset="100%" stop-color="#c45530"/>
1927
- </linearGradient>
1928
- </defs>
1929
- <g transform="translate(40, 40)">
1930
- <g transform="skewX(-12)">
1931
- <path d="M 100 0 L 60 120 L 105 30 L 100 0 Z" fill="url(#metal-grad-dark)" stroke="#4b5563" stroke-width="0.5"/>
1932
- <path d="M 30 0 L 60 120 L 80 20 L 30 0 Z" fill="url(#metal-grad)" stroke-width="0.5" stroke-opacity="0.4"/>
2050
+ <g transform="translate(30, 25) scale(1.2)">
2051
+ <g transform="translate(15, 10) skewX(-15)">
2052
+ <path d="M 65 0 L 40 60 L 70 20 L 65 0 Z" fill="currentColor"/>
2053
+ <path d="M 20 0 L 40 60 L 53 13 L 20 0 Z" fill="currentColor"/>
1933
2054
  </g>
1934
2055
  </g>
1935
2056
  <g transform="translate(110, 120)">
1936
- <line x1="5" y1="10" x2="5" y2="50" stroke="#e07048" stroke-width="3" stroke-linecap="round"/>
1937
- <line x1="60" y1="10" x2="60" y2="50" stroke="#e07048" stroke-width="3" stroke-linecap="round"/>
1938
- <path d="M 0 10 L 32.5 0 L 65 10" fill="none" stroke="#e07048" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
1939
- <rect x="15" y="18" width="14" height="12" rx="1" fill="none" stroke="#e07048" stroke-width="1.5" opacity="0.7"/>
1940
- <rect x="36" y="18" width="14" height="12" rx="1" fill="none" stroke="#e07048" stroke-width="1.5" opacity="0.7"/>
1941
- <rect x="23" y="35" width="18" height="12" rx="1" fill="none" stroke="#e07048" stroke-width="1.5" opacity="0.6"/>
2057
+ <line x1="5" y1="10" x2="5" y2="50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
2058
+ <line x1="60" y1="10" x2="60" y2="50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
2059
+ <path d="M 0 10 L 32.5 0 L 65 10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
2060
+ <rect x="15" y="18" width="14" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.7"/>
2061
+ <rect x="36" y="18" width="14" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.7"/>
2062
+ <rect x="23" y="35" width="18" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.6"/>
1942
2063
  </g>
1943
2064
  </svg>
1944
2065
  Musea
@@ -2382,11 +2503,11 @@ function __museaInitAddons(container, variantName) {
2382
2503
  // Run axe-core a11y test
2383
2504
  (async () => {
2384
2505
  try {
2385
- // Dynamically load axe-core from CDN if not already loaded
2506
+ // Dynamically load axe-core from local vendor route if not already loaded
2386
2507
  if (!window.axe) {
2387
2508
  const script = document.createElement('script');
2388
- script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.2/axe.min.js';
2389
- script.crossOrigin = 'anonymous';
2509
+ const _basePath = location.pathname.replace(/\\/preview$/, '');
2510
+ script.src = _basePath + '/vendor/axe-core.min.js';
2390
2511
  await new Promise((resolve, reject) => {
2391
2512
  script.onload = resolve;
2392
2513
  script.onerror = reject;
@@ -2438,10 +2559,15 @@ function __museaInitAddons(container, variantName) {
2438
2559
  window.parent.postMessage({ type: 'musea:ready', payload: {} }, '*');
2439
2560
  }
2440
2561
  `;
2441
- function generatePreviewModule(art, variantComponentName, variantName) {
2562
+ function generatePreviewModule(art, variantComponentName, variantName, cssImports = [], previewSetup = null) {
2442
2563
  const artModuleId = `virtual:musea-art:${art.path}`;
2443
2564
  const escapedVariantName = escapeTemplate(variantName);
2565
+ const cssImportStatements = cssImports.map((cssPath) => `import '${cssPath}';`).join("\n");
2566
+ const setupImport = previewSetup ? `import __museaPreviewSetup from '${previewSetup}';` : "";
2567
+ const setupCall = previewSetup ? "await __museaPreviewSetup(app);" : "";
2444
2568
  return `
2569
+ ${cssImportStatements}
2570
+ ${setupImport}
2445
2571
  import { createApp, reactive, h } from 'vue';
2446
2572
  import * as artModule from '${artModuleId}';
2447
2573
 
@@ -2462,6 +2588,9 @@ window.__museaSetProps = (props) => {
2462
2588
  };
2463
2589
 
2464
2590
  window.__museaSetSlots = (slots) => {
2591
+ for (const key of Object.keys(slotsOverride)) {
2592
+ delete slotsOverride[key];
2593
+ }
2465
2594
  Object.assign(slotsOverride, slots);
2466
2595
  };
2467
2596
 
@@ -2477,6 +2606,7 @@ async function mount() {
2477
2606
 
2478
2607
  // Create and mount the app
2479
2608
  const app = createApp(VariantComponent);
2609
+ ${setupCall}
2480
2610
  container.innerHTML = '';
2481
2611
  container.className = 'musea-variant';
2482
2612
  app.mount(container);
@@ -2486,19 +2616,21 @@ async function mount() {
2486
2616
  __museaInitAddons(container, '${escapedVariantName}');
2487
2617
 
2488
2618
  // Override set-props to remount with raw component + props
2489
- if (RawComponent) {
2490
- window.__museaSetProps = (props) => {
2491
- for (const key of Object.keys(propsOverride)) {
2492
- delete propsOverride[key];
2493
- }
2494
- Object.assign(propsOverride, props);
2495
- remountWithProps(RawComponent);
2496
- };
2497
- window.__museaSetSlots = (slots) => {
2498
- Object.assign(slotsOverride, slots);
2499
- remountWithProps(RawComponent);
2500
- };
2501
- }
2619
+ const TargetComponent = RawComponent || VariantComponent;
2620
+ window.__museaSetProps = (props) => {
2621
+ for (const key of Object.keys(propsOverride)) {
2622
+ delete propsOverride[key];
2623
+ }
2624
+ Object.assign(propsOverride, props);
2625
+ remountWithProps(TargetComponent);
2626
+ };
2627
+ window.__museaSetSlots = (slots) => {
2628
+ for (const key of Object.keys(slotsOverride)) {
2629
+ delete slotsOverride[key];
2630
+ }
2631
+ Object.assign(slotsOverride, slots);
2632
+ remountWithProps(TargetComponent);
2633
+ };
2502
2634
  } catch (error) {
2503
2635
  console.error('[musea-preview] Failed to mount:', error);
2504
2636
  container.innerHTML = \`
@@ -2511,7 +2643,7 @@ async function mount() {
2511
2643
  }
2512
2644
  }
2513
2645
 
2514
- function remountWithProps(Component) {
2646
+ async function remountWithProps(Component) {
2515
2647
  if (currentApp) {
2516
2648
  currentApp.unmount();
2517
2649
  }
@@ -2519,15 +2651,14 @@ function remountWithProps(Component) {
2519
2651
  setup() {
2520
2652
  return () => {
2521
2653
  const slotFns = {};
2522
- if (slotsOverride.default) {
2523
- slotFns.default = () => h('span', { innerHTML: slotsOverride.default });
2654
+ for (const [name, content] of Object.entries(slotsOverride)) {
2655
+ if (content) slotFns[name] = () => h('span', { innerHTML: content });
2524
2656
  }
2525
- return h('div', { class: 'musea-variant' }, [
2526
- h(Component, { ...propsOverride }, slotFns)
2527
- ]);
2657
+ return h(Component, { ...propsOverride }, slotFns);
2528
2658
  };
2529
2659
  }
2530
2660
  });
2661
+ ${setupCall}
2531
2662
  container.innerHTML = '';
2532
2663
  app.mount(container);
2533
2664
  currentApp = app;
@@ -2540,6 +2671,57 @@ function generateManifestModule(artFiles) {
2540
2671
  const arts = Array.from(artFiles.values());
2541
2672
  return `export const arts = ${JSON.stringify(arts, null, 2)};`;
2542
2673
  }
2674
+ /**
2675
+ * Extract the content of the first <script setup> block from a Vue SFC source.
2676
+ */
2677
+ function extractScriptSetupContent(source) {
2678
+ const match = source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
2679
+ return match?.[1]?.trim();
2680
+ }
2681
+ /**
2682
+ * Parse script setup content into imports and setup body.
2683
+ * Returns the import lines, setup body lines, and all identifiers to expose.
2684
+ */
2685
+ function parseScriptSetupForArt(content) {
2686
+ const lines = content.split("\n");
2687
+ const imports = [];
2688
+ const setupBody = [];
2689
+ const returnNames = new Set();
2690
+ for (const line of lines) {
2691
+ const trimmed = line.trim();
2692
+ if (!trimmed || trimmed.startsWith("//")) continue;
2693
+ if (trimmed.startsWith("import ")) {
2694
+ imports.push(line);
2695
+ const defaultMatch = trimmed.match(/^import\s+(\w+)/);
2696
+ if (defaultMatch && defaultMatch[1] !== "type") returnNames.add(defaultMatch[1]);
2697
+ const namedMatch = trimmed.match(/\{([^}]+)\}/);
2698
+ if (namedMatch) for (const part of namedMatch[1].split(",")) {
2699
+ const name = part.trim().split(/\s+as\s+/).pop()?.trim();
2700
+ if (name && !name.startsWith("type ")) returnNames.add(name);
2701
+ }
2702
+ } else {
2703
+ setupBody.push(line);
2704
+ const constMatch = trimmed.match(/^(?:const|let|var)\s+(\w+)/);
2705
+ if (constMatch) returnNames.add(constMatch[1]);
2706
+ const destructMatch = trimmed.match(/^(?:const|let|var)\s+\{([^}]+)\}/);
2707
+ if (destructMatch) for (const part of destructMatch[1].split(",")) {
2708
+ const name = part.trim().split(/\s*:\s*/).shift()?.trim();
2709
+ if (name) returnNames.add(name);
2710
+ }
2711
+ const arrayMatch = trimmed.match(/^(?:const|let|var)\s+\[([^\]]+)\]/);
2712
+ if (arrayMatch) for (const part of arrayMatch[1].split(",")) {
2713
+ const name = part.trim();
2714
+ if (name && name !== "...") returnNames.add(name);
2715
+ }
2716
+ }
2717
+ }
2718
+ returnNames.delete("type");
2719
+ return {
2720
+ imports,
2721
+ setupBody,
2722
+ returnNames: [...returnNames]
2723
+ };
2724
+ }
2543
2725
  function generateArtModule(art, filePath) {
2544
2726
  let componentImportPath;
2545
2727
  let componentName;
@@ -2551,12 +2733,27 @@ function generateArtModule(art, filePath) {
2551
2733
  componentImportPath = path.isAbsolute(comp) ? comp : path.resolve(path.dirname(filePath), comp);
2552
2734
  componentName = path.basename(comp, ".vue");
2553
2735
  }
2736
+ const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
2554
2737
  let code = `
2555
2738
  // Auto-generated module for: ${path.basename(filePath)}
2556
2739
  import { defineComponent, h } from 'vue';
2557
2740
  `;
2741
+ if (scriptSetup) {
2742
+ const artDir = path.dirname(filePath);
2743
+ for (const imp of scriptSetup.imports) {
2744
+ const resolved = imp.replace(/from\s+(['"])(\.[^'"]+)\1/, (_match, quote, relPath) => {
2745
+ const absPath = path.resolve(artDir, relPath);
2746
+ return `from ${quote}${absPath}${quote}`;
2747
+ });
2748
+ code += `${resolved}\n`;
2749
+ }
2750
+ }
2558
2751
  if (componentImportPath && componentName) {
2559
- code += `import ${componentName} from '${componentImportPath}';\n`;
2752
+ const alreadyImported = scriptSetup?.imports.some((imp) => {
2753
+ if (imp.includes(`from '${componentImportPath}'`) || imp.includes(`from "${componentImportPath}"`)) return true;
2754
+ return new RegExp(`^import\\s+${componentName}[\\s,]`).test(imp.trim());
2755
+ });
2756
+ if (!alreadyImported) code += `import ${componentName} from '${componentImportPath}';\n`;
2560
2757
  code += `export const __component__ = ${componentName};\n`;
2561
2758
  }
2562
2759
  code += `
@@ -2568,12 +2765,27 @@ export const variants = ${JSON.stringify(art.variants)};
2568
2765
  let template = variant.template;
2569
2766
  if (componentName) template = template.replace(/<Self/g, `<${componentName}`).replace(/<\/Self>/g, `</${componentName}>`);
2570
2767
  const escapedTemplate = template.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
2571
- const fullTemplate = `<div class="musea-variant" data-variant="${variant.name}">${escapedTemplate}</div>`;
2572
- if (componentName) code += `
2573
- export const ${variantComponentName} = {
2768
+ const fullTemplate = `<div data-variant="${variant.name}">${escapedTemplate}</div>`;
2769
+ const componentNames = new Set();
2770
+ if (componentName) componentNames.add(componentName);
2771
+ if (scriptSetup) {
2772
+ for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.add(name);
2773
+ }
2774
+ const components = componentNames.size > 0 ? ` components: { ${[...componentNames].join(", ")} },\n` : "";
2775
+ if (scriptSetup && scriptSetup.setupBody.length > 0) code += `
2776
+ export const ${variantComponentName} = defineComponent({
2574
2777
  name: '${variantComponentName}',
2575
- components: { ${componentName} },
2778
+ ${components} setup() {
2779
+ ${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
2780
+ return { ${scriptSetup.returnNames.join(", ")} };
2781
+ },
2576
2782
  template: \`${fullTemplate}\`,
2783
+ });
2784
+ `;
2785
+ else if (componentName) code += `
2786
+ export const ${variantComponentName} = {
2787
+ name: '${variantComponentName}',
2788
+ ${components} template: \`${fullTemplate}\`,
2577
2789
  };
2578
2790
  `;
2579
2791
  else code += `
@@ -2609,11 +2821,16 @@ function toPascalCase(str) {
2609
2821
  function escapeTemplate(str) {
2610
2822
  return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
2611
2823
  }
2612
- function generatePreviewModuleWithProps(art, variantComponentName, variantName, propsOverride) {
2824
+ function generatePreviewModuleWithProps(art, variantComponentName, variantName, propsOverride, cssImports = [], previewSetup = null) {
2613
2825
  const artModuleId = `virtual:musea-art:${art.path}`;
2614
2826
  const escapedVariantName = escapeTemplate(variantName);
2615
2827
  const propsJson = JSON.stringify(propsOverride);
2828
+ const cssImportStatements = cssImports.map((cssPath) => `import '${cssPath}';`).join("\n");
2829
+ const setupImport = previewSetup ? `import __museaPreviewSetup from '${previewSetup}';` : "";
2830
+ const setupCall = previewSetup ? "await __museaPreviewSetup(app);" : "";
2616
2831
  return `
2832
+ ${cssImportStatements}
2833
+ ${setupImport}
2617
2834
  import { createApp, h } from 'vue';
2618
2835
  import * as artModule from '${artModuleId}';
2619
2836
 
@@ -2636,6 +2853,7 @@ async function mount() {
2636
2853
  };
2637
2854
 
2638
2855
  const app = createApp(WrappedComponent);
2856
+ ${setupCall}
2639
2857
  container.innerHTML = '';
2640
2858
  container.className = 'musea-variant';
2641
2859
  app.mount(container);
@@ -2650,14 +2868,16 @@ async function mount() {
2650
2868
  mount();
2651
2869
  `;
2652
2870
  }
2653
- function generatePreviewHtml(art, variant, basePath) {
2654
- const previewModuleUrl = `${basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;
2871
+ function generatePreviewHtml(art, variant, _basePath, viteBase) {
2872
+ const previewModuleUrl = `${_basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;
2873
+ const base = (viteBase || "/").replace(/\/$/, "");
2655
2874
  return `<!DOCTYPE html>
2656
2875
  <html lang="en">
2657
2876
  <head>
2658
2877
  <meta charset="UTF-8">
2659
2878
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2660
2879
  <title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>
2880
+ <script type="module" src="${base}/@vite/client"></script>
2661
2881
  <style>
2662
2882
  * { box-sizing: border-box; margin: 0; padding: 0; }
2663
2883
  html, body {
@@ -2669,10 +2889,6 @@ function generatePreviewHtml(art, variant, basePath) {
2669
2889
  background: #ffffff;
2670
2890
  }
2671
2891
  .musea-variant {
2672
- padding: 1.5rem;
2673
- display: flex;
2674
- align-items: center;
2675
- justify-content: center;
2676
2892
  min-height: 100vh;
2677
2893
  }
2678
2894
  .musea-error {