mountly-vite-plugin 1.3.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { readFileSync as readFileSync2 } from "fs";
2
+ import { readFileSync as readFileSync3 } from "fs";
3
3
  import { exportKeyToSubpath as exportKeyToSubpath3 } from "mountly-manifest";
4
4
 
5
5
  // src/fragment.ts
@@ -144,7 +144,7 @@ function mountlyManifestFragmentPlugin(options) {
144
144
  if (options.team) vertical.team = options.team;
145
145
  if (options.version) vertical.version = options.version;
146
146
  if (options.featureExport) vertical.featureExport = options.featureExport;
147
- const declarations = emitDeclarations(entry, exposeEntries);
147
+ const declarations2 = emitDeclarations(entry, exposeEntries);
148
148
  const namedExports = await collectNamedExports([
149
149
  ...entry ? [entry] : [],
150
150
  ...Object.values(exposeEntries)
@@ -158,7 +158,7 @@ function mountlyManifestFragmentPlugin(options) {
158
158
  mapped[key] = value.startsWith("./") ? value : `./${exportKeyToSubpath(value)}`;
159
159
  const exposeEntry = exposeEntries[key];
160
160
  const exportNames = exposeEntry ? namedExports.get(exposeEntry) ?? [] : [];
161
- const declaration = declarations.exportDeclarations[key];
161
+ const declaration = declarations2.exportDeclarations[key];
162
162
  if (exportNames.length > 0 || declaration) {
163
163
  typedExports[key] = declaration || exportNames.length > 0 ? {
164
164
  ...declaration ? { declaration } : {},
@@ -167,16 +167,16 @@ function mountlyManifestFragmentPlugin(options) {
167
167
  }
168
168
  }
169
169
  vertical.exports = mapped;
170
- if (moduleTypes.length > 0 || declarations.rootDeclaration || Object.keys(typedExports).length > 0) {
170
+ if (moduleTypes.length > 0 || declarations2.rootDeclaration || Object.keys(typedExports).length > 0) {
171
171
  vertical.types = {
172
- ...declarations.rootDeclaration ? { declaration: declarations.rootDeclaration } : {},
172
+ ...declarations2.rootDeclaration ? { declaration: declarations2.rootDeclaration } : {},
173
173
  ...moduleTypes.length > 0 ? { module: [...new Set(moduleTypes)].sort() } : {},
174
174
  ...Object.keys(typedExports).length > 0 ? { exports: typedExports } : {}
175
175
  };
176
176
  }
177
- } else if (moduleTypes.length > 0 || declarations.rootDeclaration) {
177
+ } else if (moduleTypes.length > 0 || declarations2.rootDeclaration) {
178
178
  vertical.types = {
179
- ...declarations.rootDeclaration ? { declaration: declarations.rootDeclaration } : {},
179
+ ...declarations2.rootDeclaration ? { declaration: declarations2.rootDeclaration } : {},
180
180
  module: [...new Set(moduleTypes)].sort()
181
181
  };
182
182
  }
@@ -332,12 +332,12 @@ async function materializeTypeGraph(root, verticalId, source, cacheDir, seen) {
332
332
  }
333
333
  return localFile;
334
334
  }
335
- async function writeTypesFile(manifest, outFile, root, remotes, strict) {
335
+ async function writeTypesFile(manifest2, outFile, root, remotes, strict) {
336
336
  if (!outFile) return;
337
337
  const cacheDir = join2(dirname2(outFile), ".mountly", "remotes");
338
338
  const declarationModules = /* @__PURE__ */ new Map();
339
339
  const seen = /* @__PURE__ */ new Map();
340
- for (const vertical of manifest.verticals) {
340
+ for (const vertical of manifest2.verticals) {
341
341
  const rootSpecifier = vertical.alias ?? vertical.id;
342
342
  const remoteOrigin = remotes[vertical.id];
343
343
  const entries = [
@@ -374,7 +374,7 @@ async function writeTypesFile(manifest, outFile, root, remotes, strict) {
374
374
  }
375
375
  }
376
376
  }
377
- const next = codegenManifestTypes(manifest, {
377
+ const next = codegenManifestTypes(manifest2, {
378
378
  resolveDeclarationModule: ({ specifier }) => declarationModules.get(specifier) ?? null,
379
379
  resolveExportNames: ({ vertical, exportKey }) => exportNamesForVertical(vertical, exportKey)
380
380
  });
@@ -383,8 +383,8 @@ async function writeTypesFile(manifest, outFile, root, remotes, strict) {
383
383
  if (prev === next) return;
384
384
  writeFileSync2(outFile, next);
385
385
  }
386
- function verticalIdFromSpecifier(manifest, specifier) {
387
- for (const vertical of manifest.verticals) {
386
+ function verticalIdFromSpecifier(manifest2, specifier) {
387
+ for (const vertical of manifest2.verticals) {
388
388
  const roots = [vertical.id, vertical.alias].filter(Boolean);
389
389
  for (const root of roots) {
390
390
  if (specifier === root || specifier.startsWith(`${root}/`)) {
@@ -394,10 +394,10 @@ function verticalIdFromSpecifier(manifest, specifier) {
394
394
  }
395
395
  return null;
396
396
  }
397
- function resolveDevUrl(manifest, specifier, remotes) {
398
- const manifestUrl = resolveRemoteImport(manifest, specifier);
397
+ function resolveDevUrl(manifest2, specifier, remotes) {
398
+ const manifestUrl = resolveRemoteImport(manifest2, specifier);
399
399
  if (!manifestUrl) return null;
400
- const verticalId = verticalIdFromSpecifier(manifest, specifier);
400
+ const verticalId = verticalIdFromSpecifier(manifest2, specifier);
401
401
  const remoteOrigin = verticalId ? remotes[verticalId] : void 0;
402
402
  if (!remoteOrigin) return manifestUrl;
403
403
  if (manifestUrl.startsWith("/")) {
@@ -413,9 +413,9 @@ function resolveDevUrl(manifest, specifier, remotes) {
413
413
  }
414
414
  return `${remoteOrigin.replace(/\/$/, "")}/${manifestUrl.replace(/^\/+/, "")}`;
415
415
  }
416
- function isRemoteSpecifier(manifest, id) {
417
- if (resolveRemoteImport(manifest, id)) return true;
418
- return manifest.verticals.some((vertical) => {
416
+ function isRemoteSpecifier(manifest2, id) {
417
+ if (resolveRemoteImport(manifest2, id)) return true;
418
+ return manifest2.verticals.some((vertical) => {
419
419
  const roots = [vertical.id, vertical.alias].filter(Boolean);
420
420
  return roots.some((root) => id.startsWith(`${root}/`));
421
421
  });
@@ -432,7 +432,7 @@ function applyBaseToImportUrl(url, base) {
432
432
  }
433
433
  async function fetchRemoteFragments(remotes) {
434
434
  const verticals = [];
435
- for (const [name, urlRaw] of Object.entries(remotes)) {
435
+ for (const [name2, urlRaw] of Object.entries(remotes)) {
436
436
  const trimmed = urlRaw.replace(/\/+$/, "");
437
437
  const isFragmentUrl = trimmed.endsWith(".json");
438
438
  const fragmentUrl = isFragmentUrl ? trimmed : `${trimmed}/mountly.manifest.fragment.json`;
@@ -440,7 +440,7 @@ async function fetchRemoteFragments(remotes) {
440
440
  const response = await fetch(fragmentUrl);
441
441
  if (!response.ok) {
442
442
  throw new Error(
443
- `[mountly:host] remote "${name}": failed to fetch ${fragmentUrl} (${response.status}) \u2014 is the remote built and served?`
443
+ `[mountly:host] remote "${name2}": failed to fetch ${fragmentUrl} (${response.status}) \u2014 is the remote built and served?`
444
444
  );
445
445
  }
446
446
  const fragment = await response.json();
@@ -450,7 +450,7 @@ async function fetchRemoteFragments(remotes) {
450
450
  verticals.push({
451
451
  ...vertical,
452
452
  // A `mountlyRemote` build emits one vertical; key it by the host's remote name.
453
- id: fragmentVerticals.length === 1 ? name : vertical.id,
453
+ id: fragmentVerticals.length === 1 ? name2 : vertical.id,
454
454
  url: absolute
455
455
  });
456
456
  }
@@ -470,7 +470,7 @@ function mountlyHostPlugin(options) {
470
470
  }
471
471
  };
472
472
  const strictTypes = options.types !== false && typeof options.types === "object" ? options.types.strict ?? true : options.types !== false;
473
- let manifest;
473
+ let manifest2;
474
474
  let root = process.cwd();
475
475
  let isProduction = false;
476
476
  let sourceFiles = [];
@@ -520,11 +520,11 @@ function mountlyHostPlugin(options) {
520
520
  async config(_config, env) {
521
521
  isProduction = env.command === "build";
522
522
  await ensureUrlRemotes();
523
- manifest = loadCurrentManifest();
524
- maybeWriteManifest(manifest);
523
+ manifest2 = loadCurrentManifest();
524
+ maybeWriteManifest(manifest2);
525
525
  typesOutFile = resolveTypesOutFile(root, options.manifest, options.types);
526
- void writeTypesFile(manifest, typesOutFile, root, devOrigins, false);
527
- const external = manifestRemoteSpecifiers(manifest);
526
+ void writeTypesFile(manifest2, typesOutFile, root, devOrigins, false);
527
+ const external = manifestRemoteSpecifiers(manifest2);
528
528
  return {
529
529
  build: {
530
530
  rollupOptions: {
@@ -540,15 +540,15 @@ function mountlyHostPlugin(options) {
540
540
  root = config.root;
541
541
  viteBase = config.base;
542
542
  await ensureUrlRemotes();
543
- manifest = loadCurrentManifest();
543
+ manifest2 = loadCurrentManifest();
544
544
  typesOutFile = resolveTypesOutFile(root, options.manifest, options.types);
545
- void writeTypesFile(manifest, typesOutFile, root, devOrigins, false);
545
+ void writeTypesFile(manifest2, typesOutFile, root, devOrigins, false);
546
546
  },
547
547
  async buildStart() {
548
548
  await ensureUrlRemotes();
549
- manifest = loadCurrentManifest();
550
- maybeWriteManifest(manifest);
551
- await writeTypesFile(manifest, typesOutFile, root, devOrigins, strictTypes);
549
+ manifest2 = loadCurrentManifest();
550
+ maybeWriteManifest(manifest2);
551
+ await writeTypesFile(manifest2, typesOutFile, root, devOrigins, strictTypes);
552
552
  for (const file of sourceFiles) this.addWatchFile(file);
553
553
  },
554
554
  configureServer(server) {
@@ -556,12 +556,12 @@ function mountlyHostPlugin(options) {
556
556
  },
557
557
  handleHotUpdate(ctx) {
558
558
  if (!sourceFiles.includes(ctx.file)) return;
559
- manifest = loadCurrentManifest();
560
- void writeTypesFile(manifest, typesOutFile, root, devOrigins, false);
559
+ manifest2 = loadCurrentManifest();
560
+ void writeTypesFile(manifest2, typesOutFile, root, devOrigins, false);
561
561
  },
562
562
  resolveId(id) {
563
- if (!isRemoteSpecifier(manifest, id)) return null;
564
- const url = isProduction ? null : resolveDevUrl(manifest, id, devOrigins);
563
+ if (!isRemoteSpecifier(manifest2, id)) return null;
564
+ const url = isProduction ? null : resolveDevUrl(manifest2, id, devOrigins);
565
565
  if (url) {
566
566
  return { id: url, external: true };
567
567
  }
@@ -573,9 +573,9 @@ function mountlyHostPlugin(options) {
573
573
  let next = html;
574
574
  if (injectImportMap) {
575
575
  const imports = Object.fromEntries(
576
- Object.entries(manifestToImportMap(manifest)).map(([specifier, url]) => [
576
+ Object.entries(manifestToImportMap(manifest2)).map(([specifier, url]) => [
577
577
  specifier,
578
- isProduction ? applyBaseToImportUrl(url, viteBase) : resolveDevUrl(manifest, specifier, devOrigins) ?? url
578
+ isProduction ? applyBaseToImportUrl(url, viteBase) : resolveDevUrl(manifest2, specifier, devOrigins) ?? url
579
579
  ])
580
580
  );
581
581
  const snippet = `<script type="importmap">
@@ -592,7 +592,7 @@ ${JSON.stringify({ imports }, null, 2)}
592
592
  };
593
593
  }
594
594
 
595
- // src/index.ts
595
+ // src/externals.ts
596
596
  var PLATFORM_EXTERNALS = ["mountly", /^mountly\//];
597
597
  var FRAMEWORK_EXTERNALS = {
598
598
  react: ["react", "react/jsx-runtime", "react-dom", "react-dom/client", "mountly-react"],
@@ -610,6 +610,1028 @@ function getFrameworkPeerExternals(framework) {
610
610
  function getSelfContainedExternals(framework) {
611
611
  return FRAMEWORK_EXTERNALS[framework].filter((id) => !FRAMEWORK_BUNDLE[framework].includes(id));
612
612
  }
613
+
614
+ // src/embed.ts
615
+ import { globSync, readFileSync as readFileSync2 } from "fs";
616
+ import { basename, dirname as dirname3, extname, relative as relative3, resolve as resolve3 } from "path";
617
+ import { parseAst as parseAst3 } from "vite";
618
+
619
+ // src/docs.ts
620
+ import { parseAst as parseAst2 } from "vite";
621
+
622
+ // src/props.ts
623
+ import { parseAst } from "vite";
624
+ function kebab(name2) {
625
+ return name2.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
626
+ }
627
+ function eventName(prop) {
628
+ return kebab(prop.replace(/^on(?=[A-Z])/, ""));
629
+ }
630
+ function unwrapUnion(type) {
631
+ if (type.type !== "TSUnionType") return type;
632
+ const real = type.types.filter(
633
+ (t) => t.type !== "TSUndefinedKeyword" && t.type !== "TSNullKeyword" && !(t.type === "TSLiteralType" && t.literal?.value === null)
634
+ );
635
+ if (real.length === 1) return unwrapUnion(real[0]);
636
+ if (real.length > 1 && real.every((t) => typeof t.literal?.value === "string")) {
637
+ return { type: "TSStringKeyword" };
638
+ }
639
+ return { type: "TSUnknown" };
640
+ }
641
+ function kindOf(type, types, seen = 0) {
642
+ if (!type || seen > 4) return "auto";
643
+ const resolved = unwrapUnion(type);
644
+ if (resolved.type === "TSLiteralType") {
645
+ const value = resolved.literal?.value;
646
+ if (typeof value === "string") return "string";
647
+ if (typeof value === "number") return "number";
648
+ if (typeof value === "boolean") return "boolean";
649
+ return "json";
650
+ }
651
+ switch (resolved.type) {
652
+ case "TSStringKeyword":
653
+ return "string";
654
+ case "TSNumberKeyword":
655
+ return "number";
656
+ case "TSBooleanKeyword":
657
+ return "boolean";
658
+ case "TSFunctionType":
659
+ return "function";
660
+ case "TSTypeReference": {
661
+ if (resolved.typeArguments) return "json";
662
+ const alias = types.get(resolved.typeName?.name);
663
+ if (alias) return kindOf(alias, types, seen + 1);
664
+ return "auto";
665
+ }
666
+ default:
667
+ return "json";
668
+ }
669
+ }
670
+ function localTypes(body) {
671
+ const types = /* @__PURE__ */ new Map();
672
+ for (const raw of body) {
673
+ const node = raw.type === "ExportNamedDeclaration" && raw.declaration ? raw.declaration : raw;
674
+ if (node.type === "TSInterfaceDeclaration") types.set(node.id.name, node);
675
+ if (node.type === "TSTypeAliasDeclaration") types.set(node.id.name, node.typeAnnotation);
676
+ }
677
+ return types;
678
+ }
679
+ function membersOf(type, types, seen = 0) {
680
+ if (!type || seen > 4) return null;
681
+ if (type.type === "TSTypeLiteral" || type.type === "TSInterfaceBody") {
682
+ return type.body ?? type.members;
683
+ }
684
+ if (type.type === "TSTypeReference" && type.typeName?.type === "Identifier") {
685
+ return membersOf(types.get(type.typeName.name), types, seen + 1);
686
+ }
687
+ if (type.type === "TSInterfaceDeclaration") {
688
+ const inherited = [];
689
+ for (const heritage of type.extends ?? []) {
690
+ const base = membersOf(types.get(heritage.expression?.name), types, seen + 1);
691
+ if (!base) return null;
692
+ inherited.push(...base);
693
+ }
694
+ return [...inherited, ...type.body?.body ?? []];
695
+ }
696
+ if (type.type === "TSIntersectionType") {
697
+ const all = [];
698
+ for (const part of type.types) {
699
+ const members = membersOf(part, types, seen + 1);
700
+ if (!members) return null;
701
+ all.push(...members);
702
+ }
703
+ return all;
704
+ }
705
+ return null;
706
+ }
707
+ function isComponent(node) {
708
+ return node.type === "FunctionDeclaration" || node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
709
+ }
710
+ function findExport(body, exportName) {
711
+ if (exportName === "default") {
712
+ const node = body.find((n) => n.type === "ExportDefaultDeclaration");
713
+ if (!node) return null;
714
+ if (isComponent(node.declaration)) return node.declaration;
715
+ if (node.declaration.type === "Identifier") return findVariable(body, node.declaration.name);
716
+ return null;
717
+ }
718
+ for (const raw of body) {
719
+ if (raw.type !== "ExportNamedDeclaration" || !raw.declaration) continue;
720
+ const declaration = raw.declaration;
721
+ if (declaration.type === "FunctionDeclaration" && declaration.id?.name === exportName) {
722
+ return declaration;
723
+ }
724
+ if (declaration.type === "VariableDeclaration") {
725
+ const match = declaration.declarations.find((d) => d.id?.name === exportName);
726
+ if (match) return match;
727
+ }
728
+ }
729
+ return findVariable(body, exportName);
730
+ }
731
+ function findVariable(body, name2) {
732
+ for (const raw of body) {
733
+ const node = raw.type === "ExportNamedDeclaration" && raw.declaration ? raw.declaration : raw;
734
+ if (node.type !== "VariableDeclaration") continue;
735
+ const match = node.declarations.find((d) => d.id?.name === name2);
736
+ if (match) return match;
737
+ }
738
+ return null;
739
+ }
740
+ function componentFn(declaration) {
741
+ if (isComponent(declaration)) return declaration;
742
+ const init = declaration.init;
743
+ if (init && isComponent(init)) return init;
744
+ if (init?.type === "CallExpression") {
745
+ const inner = init.arguments?.find((argument) => isComponent(argument));
746
+ if (inner) return inner;
747
+ }
748
+ return void 0;
749
+ }
750
+ function propsType(declaration) {
751
+ const annotation = componentFn(declaration)?.params?.[0]?.typeAnnotation?.typeAnnotation;
752
+ if (annotation) return annotation;
753
+ return declaration.id?.typeAnnotation?.typeAnnotation?.typeArguments?.params?.[0] ?? declaration.init?.typeArguments?.params?.[0];
754
+ }
755
+ function destructuredNames(declaration) {
756
+ const param = componentFn(declaration)?.params?.[0];
757
+ if (param?.type !== "ObjectPattern") return [];
758
+ return param.properties.filter((p) => p.type === "Property" && p.key?.type === "Identifier").map((p) => p.key.name);
759
+ }
760
+ function takesProps(declaration) {
761
+ const fn = componentFn(declaration);
762
+ if (fn) return Boolean(fn.params?.length) || Boolean(propsType(declaration));
763
+ return propsType(declaration) ? true : "unknown";
764
+ }
765
+ function toSpec(name2, kind) {
766
+ if (/^on[A-Z]/.test(name2)) return { name: name2, kind: "event", event: eventName(name2) };
767
+ if (kind === "event" || kind === "function") return { name: name2, kind: "function" };
768
+ return { name: name2, attribute: kebab(name2), kind };
769
+ }
770
+ var VUE_CTOR = {
771
+ String: "string",
772
+ Number: "number",
773
+ Boolean: "boolean",
774
+ Array: "json",
775
+ Object: "json",
776
+ Function: "function"
777
+ };
778
+ function vueOptions(body) {
779
+ const exported = body.find((n) => n.type === "ExportDefaultDeclaration")?.declaration;
780
+ if (!exported) return null;
781
+ const object = exported.type === "CallExpression" ? exported.arguments?.[0] : exported;
782
+ if (object?.type !== "ObjectExpression") return null;
783
+ for (const property of object.properties) {
784
+ if (property.type === "SpreadElement") return null;
785
+ const key = keyOf(property);
786
+ if (key === "mixins" || key === "extends") return null;
787
+ }
788
+ const entry = (key) => object.properties.find((p) => p.type === "Property" && keyOf(p) === key)?.value;
789
+ const declaredProps = entry("props");
790
+ const specs = [];
791
+ if (declaredProps?.type === "ArrayExpression") {
792
+ for (const element of declaredProps.elements) {
793
+ if (typeof element?.value !== "string") return null;
794
+ specs.push(toSpec(element.value, "auto"));
795
+ }
796
+ } else if (declaredProps?.type === "ObjectExpression") {
797
+ for (const property of declaredProps.properties) {
798
+ const name2 = keyOf(property);
799
+ if (!name2) return null;
800
+ const value = property.value;
801
+ const ctor = value?.type === "ObjectExpression" ? value.properties.find((q) => keyOf(q) === "type")?.value?.name : value?.name;
802
+ specs.push(toSpec(name2, ctor ? VUE_CTOR[ctor] ?? "json" : "auto"));
803
+ }
804
+ } else if (declaredProps) {
805
+ return null;
806
+ }
807
+ const declaredEmits = entry("emits");
808
+ if (declaredEmits?.type === "ArrayExpression") {
809
+ for (const element of declaredEmits.elements) {
810
+ if (typeof element?.value !== "string") return null;
811
+ specs.push({ name: onProp(element.value), kind: "event", event: element.value });
812
+ }
813
+ } else if (declaredEmits) {
814
+ return null;
815
+ }
816
+ return specs.length || declaredProps || declaredEmits ? specs : null;
817
+ }
818
+ function onProp(event) {
819
+ return `on${event.replace(/(^|-)([a-z])/g, (_m, _dash, char) => char.toUpperCase())}`;
820
+ }
821
+ function scriptOf(code) {
822
+ const blocks = [...code.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/g)].map((m) => m[1]);
823
+ return blocks.join("\n");
824
+ }
825
+ function keyOf(node) {
826
+ if (node.key?.type === "Identifier") return node.key.name;
827
+ if (node.key?.type === "Literal" && typeof node.key.value === "string") return node.key.value;
828
+ return null;
829
+ }
830
+ function specsFrom(members, types) {
831
+ const specs = [];
832
+ for (const member2 of members) {
833
+ if (member2.type !== "TSPropertySignature" && member2.type !== "TSMethodSignature") continue;
834
+ const name2 = keyOf(member2);
835
+ if (!name2) continue;
836
+ specs.push(
837
+ member2.type === "TSMethodSignature" ? toSpec(name2, "function") : toSpec(name2, kindOf(member2.typeAnnotation?.typeAnnotation, types))
838
+ );
839
+ }
840
+ return specs;
841
+ }
842
+ function macroCall(body, macro) {
843
+ const find = (node, depth = 0) => {
844
+ if (!node || node.type !== "CallExpression" || depth > 3) return void 0;
845
+ if (node.callee?.name === macro) return node;
846
+ for (const argument of node.arguments ?? []) {
847
+ const nested = find(argument, depth + 1);
848
+ if (nested) return nested;
849
+ }
850
+ return void 0;
851
+ };
852
+ for (const raw of body) {
853
+ if (raw.type !== "VariableDeclaration" && raw.type !== "ExpressionStatement") continue;
854
+ const roots = raw.type === "VariableDeclaration" ? raw.declarations.map((d) => d.init) : [raw.expression];
855
+ for (const root of roots) {
856
+ const call = find(root);
857
+ if (call) return call;
858
+ }
859
+ }
860
+ return void 0;
861
+ }
862
+ function vueEvents(body, types) {
863
+ const call = macroCall(body, "defineEmits");
864
+ if (!call) return [];
865
+ const array = call.arguments?.[0];
866
+ if (array?.type === "ArrayExpression") {
867
+ const names2 = array.elements.map((element) => element?.value);
868
+ return names2.every((name2) => typeof name2 === "string") ? names2.map((event) => ({ name: onProp(event), kind: "event", event })) : null;
869
+ }
870
+ const members = membersOf(call.typeArguments?.params?.[0], types);
871
+ if (!members) return null;
872
+ const names = members.flatMap((m) => {
873
+ if (m.type === "TSPropertySignature") return [m.key?.value ?? m.key?.name];
874
+ const literal = m.params?.[0]?.typeAnnotation?.typeAnnotation?.literal?.value;
875
+ return literal ? [literal] : [];
876
+ });
877
+ if (!names.every((name2) => typeof name2 === "string")) return null;
878
+ return names.map((event) => ({ name: onProp(event), kind: "event", event }));
879
+ }
880
+ function svelteProps(body) {
881
+ for (const raw of body) {
882
+ if (raw.type !== "VariableDeclaration") continue;
883
+ for (const declarator of raw.declarations) {
884
+ if (declarator.init?.type === "CallExpression" && declarator.init.callee?.name === "$props") {
885
+ return declarator;
886
+ }
887
+ }
888
+ }
889
+ return null;
890
+ }
891
+ function extractProps(code, options = {}) {
892
+ const file = options.file ?? "component.tsx";
893
+ const sfc = /\.(vue|svelte)$/.exec(file)?.[1];
894
+ const source = sfc ? scriptOf(code) : code;
895
+ const body = parseAst(source, { lang: sfc ? "ts" : "tsx" }).body;
896
+ const types = localTypes(body);
897
+ if (sfc === "vue") {
898
+ const events = vueEvents(body, types);
899
+ if (!events) return null;
900
+ const call = macroCall(body, "defineProps");
901
+ if (call) {
902
+ const members2 = membersOf(call.typeArguments?.params?.[0], types);
903
+ if (!members2) return null;
904
+ return [...specsFrom(members2, types), ...events];
905
+ }
906
+ const options2 = vueOptions(body);
907
+ if (options2) return [...options2, ...events];
908
+ return events.length ? events : null;
909
+ }
910
+ if (sfc === "svelte") {
911
+ const declarator = svelteProps(body);
912
+ if (!declarator) return null;
913
+ const declared2 = declarator.id?.typeAnnotation?.typeAnnotation;
914
+ const members2 = membersOf(declared2, types);
915
+ if (members2) return specsFrom(members2, types);
916
+ if (declared2) return null;
917
+ const names = (declarator.id?.properties ?? []).filter((p) => p.type === "Property" && p.key?.type === "Identifier").map((p) => p.key.name);
918
+ return names.length ? names.map((name2) => toSpec(name2, "auto")) : null;
919
+ }
920
+ const declaration = findExport(body, options.exportName ?? "default");
921
+ if (!declaration) return null;
922
+ const declared = propsType(declaration);
923
+ const members = membersOf(declared, types);
924
+ if (members) return specsFrom(members, types);
925
+ if (declared) return null;
926
+ const destructured = destructuredNames(declaration);
927
+ if (destructured.length) return destructured.map((name2) => toSpec(name2, "auto"));
928
+ return takesProps(declaration) === false ? [] : null;
929
+ }
930
+
931
+ // src/docs.ts
932
+ function docBlock(source, start) {
933
+ const match = /\/\*\*((?:[^*]|\*(?!\/))*)\*\/\s*$/.exec(source.slice(0, start));
934
+ if (!match) return { tags: [] };
935
+ const lines = (match[1] ?? "").split("\n").map((line) => line.replace(/^\s*\*? ?/, "").trim());
936
+ const joined = lines.join("\n").replace(/\s+@(?=[a-zA-Z])/g, "\n@");
937
+ const [prose = "", ...rest] = joined.split(/\n(?=@)/);
938
+ const tags = rest.map((tag) => {
939
+ const [, name2 = "", value = ""] = /^@(\w+)\s*([\s\S]*)$/.exec(tag.trim()) ?? [];
940
+ return [name2, value.replace(/\s+/g, " ").trim()];
941
+ });
942
+ const text = prose.trim();
943
+ return { ...text ? { text } : {}, tags };
944
+ }
945
+ var NUMERIC_TAGS = ["minimum", "maximum", "minLength", "maxLength", "minItems", "maxItems"];
946
+ function constraints(tags) {
947
+ const out = {};
948
+ for (const [name2, value] of tags) {
949
+ if (NUMERIC_TAGS.includes(name2) && value !== "" && Number.isFinite(Number(value))) {
950
+ out[name2] = Number(value);
951
+ }
952
+ if (name2 === "pattern" || name2 === "format") out[name2] = value;
953
+ }
954
+ return out;
955
+ }
956
+ function schemaOf(type, types, source, depth = 0) {
957
+ if (!type || depth > 6) return {};
958
+ switch (type.type) {
959
+ case "TSStringKeyword":
960
+ return { type: "string" };
961
+ case "TSNumberKeyword":
962
+ return { type: "number" };
963
+ case "TSBooleanKeyword":
964
+ return { type: "boolean" };
965
+ case "TSNullKeyword":
966
+ return { type: "null" };
967
+ case "TSLiteralType": {
968
+ const value = type.literal?.value;
969
+ return value === void 0 ? {} : { const: value };
970
+ }
971
+ case "TSArrayType":
972
+ return { type: "array", items: schemaOf(type.elementType, types, source, depth + 1) };
973
+ case "TSParenthesizedType":
974
+ return schemaOf(type.typeAnnotation, types, source, depth);
975
+ case "TSUnionType": {
976
+ const parts = type.types.filter(
977
+ (t) => t.type !== "TSUndefinedKeyword" && t.type !== "TSNullKeyword"
978
+ );
979
+ if (parts.length === 1) return schemaOf(parts[0], types, source, depth);
980
+ const values = parts.map((t) => t.literal?.value);
981
+ if (parts.every((t) => t.type === "TSLiteralType") && values.every((v) => v !== void 0)) {
982
+ const kinds = new Set(values.map((v) => typeof v));
983
+ return kinds.size === 1 ? { type: [...kinds][0], enum: values } : { enum: values };
984
+ }
985
+ return { anyOf: parts.map((t) => schemaOf(t, types, source, depth + 1)) };
986
+ }
987
+ case "TSTypeLiteral":
988
+ case "TSInterfaceDeclaration":
989
+ case "TSIntersectionType":
990
+ return objectSchema(membersOf(type, types), types, source, depth);
991
+ case "TSTypeReference": {
992
+ const name2 = type.typeName?.name;
993
+ const args = type.typeArguments?.params ?? [];
994
+ if ((name2 === "Array" || name2 === "ReadonlyArray") && args[0]) {
995
+ return { type: "array", items: schemaOf(args[0], types, source, depth + 1) };
996
+ }
997
+ if (name2 === "Record" && args[1]) {
998
+ return {
999
+ type: "object",
1000
+ additionalProperties: schemaOf(args[1], types, source, depth + 1)
1001
+ };
1002
+ }
1003
+ const local = types.get(name2);
1004
+ if (!local) return {};
1005
+ return local.type === "TSInterfaceDeclaration" ? objectSchema(membersOf(local, types), types, source, depth) : schemaOf(local, types, source, depth + 1);
1006
+ }
1007
+ default:
1008
+ return {};
1009
+ }
1010
+ }
1011
+ function objectSchema(members, types, source, depth) {
1012
+ if (!members) return { type: "object" };
1013
+ const properties = {};
1014
+ const required = [];
1015
+ for (const member2 of members) {
1016
+ if (member2.type !== "TSPropertySignature") continue;
1017
+ const name2 = keyOf(member2);
1018
+ if (!name2) continue;
1019
+ const doc = docBlock(source, member2.start);
1020
+ properties[name2] = {
1021
+ ...schemaOf(member2.typeAnnotation?.typeAnnotation, types, source, depth + 1),
1022
+ ...constraints(doc.tags),
1023
+ ...doc.text ? { description: doc.text } : {}
1024
+ };
1025
+ if (!member2.optional) required.push(name2);
1026
+ }
1027
+ return {
1028
+ type: "object",
1029
+ properties,
1030
+ ...required.length ? { required } : {},
1031
+ additionalProperties: false
1032
+ };
1033
+ }
1034
+ function statementOf(body, node) {
1035
+ return body.find((s) => s.start <= node.start && node.start < s.end);
1036
+ }
1037
+ function htmlCommentBeforeScript(code) {
1038
+ const match = /<!--((?:(?!-->)[\s\S])*?)-->\s*<script\b/.exec(code);
1039
+ const text = match?.[1]?.trim();
1040
+ return text || void 0;
1041
+ }
1042
+ function extractDocs(code, options = {}) {
1043
+ try {
1044
+ const file = options.file ?? "component.tsx";
1045
+ const sfc = /\.(vue|svelte)$/.exec(file)?.[1];
1046
+ const source = sfc ? scriptOf(code) : code;
1047
+ const body = parseAst2(source, { lang: sfc ? "ts" : "tsx" }).body;
1048
+ const types = localTypes(body);
1049
+ let declared;
1050
+ let description;
1051
+ let component = { tags: [] };
1052
+ if (sfc === "vue") {
1053
+ const call = macroCall(body, "defineProps");
1054
+ declared = call?.typeArguments?.params?.[0];
1055
+ const statement = call && statementOf(body, call);
1056
+ component = statement ? docBlock(source, statement.start) : component;
1057
+ description = component.text ?? htmlCommentBeforeScript(code);
1058
+ } else if (sfc === "svelte") {
1059
+ const declarator = svelteProps(body);
1060
+ declared = declarator?.id?.typeAnnotation?.typeAnnotation;
1061
+ const statement = declarator && statementOf(body, declarator);
1062
+ component = statement ? docBlock(source, statement.start) : component;
1063
+ description = component.text ?? htmlCommentBeforeScript(code);
1064
+ } else {
1065
+ const declaration = findExport(body, options.exportName ?? "default");
1066
+ if (declaration) {
1067
+ const statement = statementOf(body, declaration);
1068
+ const before = statement ? docBlock(source, statement.start) : { tags: [] };
1069
+ component = before.text || before.tags.length ? before : docBlock(source, declaration.start);
1070
+ description = component.text;
1071
+ declared = propsType(declaration);
1072
+ }
1073
+ }
1074
+ const props = {};
1075
+ for (const member2 of membersOf(declared, types) ?? []) {
1076
+ const name2 = keyOf(member2);
1077
+ if (!name2) continue;
1078
+ const annotation = member2.typeAnnotation?.typeAnnotation;
1079
+ const doc = docBlock(source, member2.start);
1080
+ const type = annotation ? source.slice(annotation.start, annotation.end) : void 0;
1081
+ const schema = annotation ? { ...schemaOf(annotation, types, source), ...constraints(doc.tags) } : void 0;
1082
+ props[name2] = {
1083
+ ...doc.text ? { description: doc.text } : {},
1084
+ ...type ? { type } : {},
1085
+ ...schema && Object.keys(schema).length ? { schema } : {}
1086
+ };
1087
+ }
1088
+ const slots = component.tags.filter(([tag]) => tag === "slot").map(([, value]) => {
1089
+ const [, name2 = "", text = ""] = /^(?:([A-Za-z][\w-]*)\s*)?-?\s*([\s\S]*)$/.exec(value) ?? [];
1090
+ return { name: name2, description: text.trim() };
1091
+ });
1092
+ return {
1093
+ ...description ? { description } : {},
1094
+ props,
1095
+ ...slots.length ? { slots } : {}
1096
+ };
1097
+ } catch {
1098
+ return { props: {} };
1099
+ }
1100
+ }
1101
+
1102
+ // src/embed.ts
1103
+ function frameworkFor(file) {
1104
+ if (file.endsWith(".vue")) return "vue";
1105
+ if (file.endsWith(".svelte")) return "svelte";
1106
+ return "react";
1107
+ }
1108
+ var TS_TYPE = {
1109
+ string: "string",
1110
+ number: "number",
1111
+ boolean: "boolean",
1112
+ json: "unknown",
1113
+ auto: "unknown",
1114
+ function: "(...args: never[]) => unknown"
1115
+ };
1116
+ function tagFor(file, prefix) {
1117
+ return `${prefix}-${kebab(basename(file, extname(file)))}`;
1118
+ }
1119
+ function normalize(options, root) {
1120
+ const prefix = kebab(options.prefix);
1121
+ const seen = /* @__PURE__ */ new Set();
1122
+ const unique = (entries2) => {
1123
+ for (const [tag, entry] of entries2) {
1124
+ if (seen.has(tag)) {
1125
+ throw new Error(`[mountly] two components both map to <${tag}>: ${entry.component}`);
1126
+ }
1127
+ seen.add(tag);
1128
+ }
1129
+ return entries2;
1130
+ };
1131
+ if (typeof options.elements === "string" || Array.isArray(options.elements)) {
1132
+ const patterns = [options.elements].flat();
1133
+ const files = patterns.flatMap((pattern) => globSync(pattern, { cwd: root }).sort());
1134
+ if (!files.length) {
1135
+ throw new Error(`[mountly] no components matched ${patterns.join(", ")}`);
1136
+ }
1137
+ return unique(files.map((file) => [tagFor(file, prefix), { component: file }]));
1138
+ }
1139
+ const entries = Object.entries(options.elements).map(
1140
+ ([name2, entry]) => [
1141
+ // The namespace is the point of `prefix`, so it always applies. Naming a
1142
+ // key that already carries it is allowed, and means the same tag.
1143
+ name2.startsWith(`${prefix}-`) ? name2 : `${prefix}-${kebab(name2)}`,
1144
+ typeof entry === "string" ? { component: entry } : entry
1145
+ ]
1146
+ );
1147
+ if (!entries.length) throw new Error("[mountly] elements must contain at least one component");
1148
+ return unique(entries);
1149
+ }
1150
+ function declarations(elements) {
1151
+ const blocks = elements.map(({ tag, props }) => {
1152
+ const id = name(tag);
1153
+ const members = props.filter((spec) => spec.kind !== "event").map((spec) => ` ${member(spec.name)}?: ${TS_TYPE[spec.kind] ?? "unknown"};`);
1154
+ const events = props.filter((spec) => spec.kind === "event").map((spec) => ` ${JSON.stringify(spec.event)}: CustomEvent;`);
1155
+ const block = (kind, lines) => lines.length ? `export interface ${id}${kind} {
1156
+ ${lines.join("\n")}
1157
+ }` : `export interface ${id}${kind} {}`;
1158
+ return `${block("Props", members)}
1159
+
1160
+ ${block("EventMap", events)}
1161
+
1162
+ export interface ${id}Element extends MountlyElement<${id}Props> {
1163
+ addEventListener<K extends keyof ${id}EventMap>(
1164
+ type: K,
1165
+ listener: (this: ${id}Element, event: ${id}EventMap[K]) => unknown,
1166
+ options?: boolean | AddEventListenerOptions,
1167
+ ): void;
1168
+ addEventListener<K extends keyof HTMLElementEventMap>(
1169
+ type: K,
1170
+ listener: (this: ${id}Element, event: HTMLElementEventMap[K]) => unknown,
1171
+ options?: boolean | AddEventListenerOptions,
1172
+ ): void;
1173
+ addEventListener(
1174
+ type: string,
1175
+ listener: EventListenerOrEventListenerObject,
1176
+ options?: boolean | AddEventListenerOptions,
1177
+ ): void;
1178
+ }`;
1179
+ });
1180
+ const tagMap = elements.map(({ tag }) => ` ${JSON.stringify(tag)}: ${name(tag)}Element;`);
1181
+ const jsx = elements.map(
1182
+ ({ tag }) => ` ${JSON.stringify(tag)}: ${name(tag)}Props & { children?: unknown; class?: string; id?: string };`
1183
+ );
1184
+ return `// Generated by mountly-vite-plugin. Do not edit.
1185
+ // Self-contained: a consumer with only the script tag needs no package installed.
1186
+
1187
+ type MountlyElement<Props> = HTMLElement &
1188
+ Partial<Props> & {
1189
+ /** Retry a failed load, or mount before the configured trigger. */
1190
+ mount(): Promise<void>;
1191
+ };
1192
+
1193
+ ${blocks.join("\n\n")}
1194
+
1195
+ declare global {
1196
+ interface HTMLElementTagNameMap {
1197
+ ${tagMap.join("\n")}
1198
+ }
1199
+ // The global JSX namespace, which Preact, Solid and Vue JSX read.
1200
+ namespace JSX {
1201
+ interface IntrinsicElements {
1202
+ ${jsx.join("\n")}
1203
+ }
1204
+ }
1205
+ }
1206
+ `;
1207
+ }
1208
+ function reactDeclarations(elements) {
1209
+ const jsx = elements.map(
1210
+ ({ tag }) => ` ${JSON.stringify(tag)}: ${name(tag)}Props & { children?: React.ReactNode; class?: string; id?: string; ref?: React.Ref<HTMLElement> };`
1211
+ );
1212
+ const imports = elements.map(({ tag }) => `${name(tag)}Props`).join(", ");
1213
+ return `// Generated by mountly-vite-plugin. Do not edit.
1214
+ // Reference this file instead of embed.d.ts in a React host.
1215
+ /// <reference path="./embed.d.ts" />
1216
+ import type * as React from "react";
1217
+ import type { ${imports} } from "./embed.js";
1218
+
1219
+ declare module "react" {
1220
+ namespace JSX {
1221
+ interface IntrinsicElements {
1222
+ ${jsx.join("\n")}
1223
+ }
1224
+ }
1225
+ }
1226
+ `;
1227
+ }
1228
+ function member(prop) {
1229
+ return /^[A-Za-z_$][\w$]*$/.test(prop) ? prop : JSON.stringify(prop);
1230
+ }
1231
+ function name(tag) {
1232
+ return tag.replace(/(^|-)([a-z])/g, (_m, _d, char) => char.toUpperCase());
1233
+ }
1234
+ function manifest(elements) {
1235
+ return JSON.stringify(
1236
+ {
1237
+ schemaVersion: "1.0.0",
1238
+ readme: "",
1239
+ modules: elements.map(({ tag, entry, props, docs }) => {
1240
+ const cemType = (spec) => ({
1241
+ text: docs.props[spec.name]?.type ?? TS_TYPE[spec.kind] ?? "unknown"
1242
+ });
1243
+ const described = (spec) => {
1244
+ const { description, schema } = docs.props[spec.name] ?? {};
1245
+ return { ...description ? { description } : {}, ...schema ? { schema } : {} };
1246
+ };
1247
+ return {
1248
+ kind: "javascript-module",
1249
+ path: entry.component,
1250
+ declarations: [
1251
+ {
1252
+ kind: "class",
1253
+ name: name(tag),
1254
+ ...docs.description ? { description: docs.description } : {},
1255
+ ...docs.slots ? { slots: docs.slots } : {},
1256
+ customElement: true,
1257
+ tagName: tag,
1258
+ members: props.filter((spec) => spec.kind !== "event").map((spec) => ({
1259
+ kind: "field",
1260
+ name: spec.name,
1261
+ type: cemType(spec),
1262
+ ...described(spec)
1263
+ })),
1264
+ attributes: props.filter((spec) => spec.attribute).map((spec) => ({
1265
+ name: spec.attribute,
1266
+ fieldName: spec.name,
1267
+ type: cemType(spec),
1268
+ ...described(spec)
1269
+ })),
1270
+ events: props.filter((spec) => spec.kind === "event").map((spec) => ({
1271
+ name: spec.event,
1272
+ type: { text: "CustomEvent" },
1273
+ ...described(spec)
1274
+ }))
1275
+ }
1276
+ ],
1277
+ exports: [
1278
+ {
1279
+ kind: "custom-element-definition",
1280
+ name: tag,
1281
+ declaration: { name: name(tag), module: entry.component }
1282
+ }
1283
+ ]
1284
+ };
1285
+ })
1286
+ },
1287
+ null,
1288
+ 2
1289
+ );
1290
+ }
1291
+ var FRAMEWORK_PLUGIN = {
1292
+ vue: {
1293
+ prefix: "vite:vue",
1294
+ module: "@vitejs/plugin-vue",
1295
+ load: async () => [(await import("@vitejs/plugin-vue")).default()].flat()
1296
+ },
1297
+ svelte: {
1298
+ prefix: "vite-plugin-svelte",
1299
+ module: "@sveltejs/vite-plugin-svelte",
1300
+ load: async () => [(await import("@sveltejs/vite-plugin-svelte")).svelte()].flat()
1301
+ }
1302
+ // React needs none: `oxc.jsx` below already transforms JSX.
1303
+ };
1304
+ async function frameworkPlugins(frameworks) {
1305
+ const added = [];
1306
+ for (const framework of frameworks) {
1307
+ const entry = FRAMEWORK_PLUGIN[framework];
1308
+ if (!entry) continue;
1309
+ try {
1310
+ added.push(...await entry.load());
1311
+ } catch {
1312
+ throw new Error(
1313
+ `[mountly] ${framework} components need ${entry.module}. Install it, or add your own compiler plugin to the config.`
1314
+ );
1315
+ }
1316
+ }
1317
+ return added;
1318
+ }
1319
+ function assertSingleCompiler(plugins = []) {
1320
+ for (const entry of Object.values(FRAMEWORK_PLUGIN)) {
1321
+ const count = plugins.filter((plugin) => plugin?.name === entry.prefix).length;
1322
+ if (count > 1) {
1323
+ throw new Error(
1324
+ `[mountly] ${entry.module} is registered ${count} times. defineElementsConfig adds it for you \u2014 remove it from your own plugins array.`
1325
+ );
1326
+ }
1327
+ }
1328
+ }
1329
+ var CSS_TOKEN = "__MOUNTLY_EMBED_CSS__";
1330
+ function resolveCssToken(bundle) {
1331
+ const stylesheet = Object.keys(bundle).find((file) => file.endsWith(".css"));
1332
+ const token = new RegExp(`(["'\`])${CSS_TOKEN}\\1`, "g");
1333
+ for (const output of Object.values(bundle)) {
1334
+ if (output.type !== "chunk" || !output.code.includes(CSS_TOKEN)) continue;
1335
+ if (!stylesheet) {
1336
+ output.code = output.code.replace(token, "void 0");
1337
+ continue;
1338
+ }
1339
+ const href = relative3(dirname3(output.fileName), stylesheet).replace(/\\/g, "/");
1340
+ output.code = output.code.replace(
1341
+ token,
1342
+ `new URL(${JSON.stringify(`./${href}`)}, import.meta.url).href`
1343
+ );
1344
+ }
1345
+ }
1346
+ var WORKER_SHIM = 'function __mountlyWorker(u,o){var h=String(u);if(new URL(h,location.href).origin===location.origin)return new Worker(u,o);var s=o&&o.type==="module"?"import "+JSON.stringify(h)+";":"importScripts("+JSON.stringify(h)+");";return new Worker(URL.createObjectURL(new Blob([s],{type:"text/javascript"})),o)}';
1347
+ var FUNCTIONS = /* @__PURE__ */ new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
1348
+ var VAR_SCOPES = /* @__PURE__ */ new Set([...FUNCTIONS, "StaticBlock"]);
1349
+ function patternNames(pattern) {
1350
+ if (!pattern) return [];
1351
+ switch (pattern.type) {
1352
+ case "Identifier":
1353
+ return [pattern.name];
1354
+ case "ObjectPattern":
1355
+ return pattern.properties.flatMap(
1356
+ (p) => patternNames(p.type === "RestElement" ? p.argument : p.value)
1357
+ );
1358
+ case "ArrayPattern":
1359
+ return pattern.elements.flatMap(patternNames);
1360
+ case "RestElement":
1361
+ return patternNames(pattern.argument);
1362
+ case "AssignmentPattern":
1363
+ return patternNames(pattern.left);
1364
+ default:
1365
+ return [];
1366
+ }
1367
+ }
1368
+ function children(node) {
1369
+ const out = [];
1370
+ for (const key in node) {
1371
+ if (key === "parent") continue;
1372
+ const value = node[key];
1373
+ if (Array.isArray(value)) {
1374
+ for (const item of value) if (item && typeof item.type === "string") out.push(item);
1375
+ } else if (value && typeof value.type === "string") out.push(value);
1376
+ }
1377
+ return out;
1378
+ }
1379
+ function varNames(node) {
1380
+ if (VAR_SCOPES.has(node.type)) return [];
1381
+ const own = node.type === "VariableDeclaration" && node.kind === "var" ? node.declarations.flatMap((d) => patternNames(d.id)) : [];
1382
+ return [...own, ...children(node).flatMap(varNames)];
1383
+ }
1384
+ function lexicalNames(statements) {
1385
+ return statements.flatMap((statement) => {
1386
+ const declared = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
1387
+ if (!declared) return [];
1388
+ switch (declared.type) {
1389
+ case "VariableDeclaration":
1390
+ return declared.kind === "var" ? [] : declared.declarations.flatMap((d) => patternNames(d.id));
1391
+ case "FunctionDeclaration":
1392
+ case "ClassDeclaration":
1393
+ return declared.id ? [declared.id.name] : [];
1394
+ case "ImportDeclaration":
1395
+ return declared.specifiers.map((spec) => spec.local.name);
1396
+ default:
1397
+ return [];
1398
+ }
1399
+ });
1400
+ }
1401
+ function scopeOf(node, parent) {
1402
+ switch (node.type) {
1403
+ case "Program":
1404
+ return [...lexicalNames(node.body), ...varNames({ type: "Block", body: node.body })];
1405
+ case "FunctionDeclaration":
1406
+ case "FunctionExpression":
1407
+ case "ArrowFunctionExpression":
1408
+ return [
1409
+ ...node.params.flatMap(patternNames),
1410
+ // A named function expression sees its own name.
1411
+ ...node.type === "FunctionExpression" && node.id ? [node.id.name] : []
1412
+ ];
1413
+ case "BlockStatement":
1414
+ return FUNCTIONS.has(parent?.type) && parent?.body === node ? [...lexicalNames(node.body), ...node.body.flatMap(varNames)] : lexicalNames(node.body);
1415
+ case "StaticBlock":
1416
+ return [...lexicalNames(node.body), ...node.body.flatMap(varNames)];
1417
+ case "SwitchStatement":
1418
+ return lexicalNames(node.cases.flatMap((c) => c.consequent));
1419
+ case "ForStatement":
1420
+ case "ForInStatement":
1421
+ case "ForOfStatement": {
1422
+ const head = node.type === "ForStatement" ? node.init : node.left;
1423
+ return head?.type === "VariableDeclaration" && head.kind !== "var" ? head.declarations.flatMap((d) => patternNames(d.id)) : [];
1424
+ }
1425
+ case "CatchClause":
1426
+ return patternNames(node.param);
1427
+ case "ClassExpression":
1428
+ return node.id ? [node.id.name] : [];
1429
+ default:
1430
+ return null;
1431
+ }
1432
+ }
1433
+ function shimWorkers(code) {
1434
+ if (!code.includes("Worker")) return null;
1435
+ const callees = [];
1436
+ const walk = (node, parent, shadowed) => {
1437
+ const scope = scopeOf(node, parent);
1438
+ const hidden = shadowed || Boolean(scope?.includes("Worker"));
1439
+ if (!hidden && node.type === "NewExpression" && node.callee.type === "Identifier" && node.callee.name === "Worker") {
1440
+ callees.push(node.callee);
1441
+ }
1442
+ for (const child of children(node)) {
1443
+ const outside = node.type === "SwitchStatement" && child === node.discriminant;
1444
+ walk(child, node, outside ? shadowed : hidden);
1445
+ }
1446
+ };
1447
+ walk(parseAst3(code, { lang: "js" }), void 0, false);
1448
+ if (!callees.length) return null;
1449
+ let out = code;
1450
+ for (const { start, end } of callees.sort((a, b) => b.start - a.start)) {
1451
+ out = out.slice(0, start) + "__mountlyWorker" + out.slice(end);
1452
+ }
1453
+ return WORKER_SHIM + out;
1454
+ }
1455
+ var NATIVE_BUBBLING = /* @__PURE__ */ new Set([
1456
+ "click",
1457
+ "dblclick",
1458
+ "input",
1459
+ "change",
1460
+ "submit",
1461
+ "reset",
1462
+ "select",
1463
+ "keydown",
1464
+ "keyup",
1465
+ "focusin",
1466
+ "focusout",
1467
+ "pointerdown",
1468
+ "pointerup",
1469
+ "mousedown",
1470
+ "mouseup",
1471
+ "contextmenu",
1472
+ "wheel",
1473
+ "beforeinput",
1474
+ "invalid"
1475
+ ]);
1476
+ function warnNativeEvents(elements, warn) {
1477
+ for (const { tag, props } of elements) {
1478
+ for (const spec of props) {
1479
+ if (spec.kind === "event" && spec.event && NATIVE_BUBBLING.has(spec.event)) {
1480
+ warn(
1481
+ `[mountly] <${tag}> ${spec.name} dispatches "${spec.event}", which is also a native event bubbling out of the component. Listeners will get both. Rename the prop (e.g. ${spec.name}Item) so its event is unambiguous.`
1482
+ );
1483
+ }
1484
+ }
1485
+ }
1486
+ }
1487
+ function readElementSource(entry, tag, root) {
1488
+ const source = readFileSync2(resolve3(root, entry.component), "utf8");
1489
+ const opts = { file: entry.component, exportName: entry.exportName };
1490
+ const props = entry.props ?? extractProps(source, opts);
1491
+ if (!props) {
1492
+ throw new Error(
1493
+ `[mountly] cannot read the props of ${entry.component} for <${tag}>. Declare the props type in that file, or pass \`props\` on the element entry.`
1494
+ );
1495
+ }
1496
+ return { props, docs: extractDocs(source, opts) };
1497
+ }
1498
+ function defineElementsConfig(options) {
1499
+ const entryId = "virtual:mountly-embed";
1500
+ const widgetPrefix = "virtual:mountly-widget/";
1501
+ const shadow = options.shadow === true;
1502
+ let elements = [];
1503
+ let root = options.root ?? process.cwd();
1504
+ const frameworks = new Set(
1505
+ normalize(options, root).map(([, entry]) => entry.framework ?? frameworkFor(entry.component))
1506
+ );
1507
+ const plugin = {
1508
+ name: "mountly:elements",
1509
+ configResolved(config) {
1510
+ root = config.root;
1511
+ assertSingleCompiler(config.plugins);
1512
+ elements = normalize(options, root).map(([tag, entry]) => {
1513
+ const { props, docs } = readElementSource(entry, tag, root);
1514
+ return {
1515
+ tag,
1516
+ entry,
1517
+ props,
1518
+ framework: entry.framework ?? frameworkFor(entry.component),
1519
+ docs
1520
+ };
1521
+ });
1522
+ const warn = config.logger?.warn.bind(config.logger) ?? console.warn;
1523
+ warnNativeEvents(elements, warn);
1524
+ if (!shadow) {
1525
+ for (const { tag } of elements.filter((element) => element.docs.slots)) {
1526
+ warn(
1527
+ `[mountly] <${tag}> declares @slot, but slots project only in a shadow root. Build the distribution with \`shadow: true\`, or the page's children render after the component instead of inside it.`
1528
+ );
1529
+ }
1530
+ }
1531
+ },
1532
+ resolveId(id) {
1533
+ if (id === entryId || id.startsWith(widgetPrefix)) return `\0${id}`;
1534
+ },
1535
+ load(id) {
1536
+ if (id === `\0${entryId}`) {
1537
+ const definitions = elements.map(
1538
+ ({ tag, entry, props, docs }) => `${JSON.stringify(tag)}: {
1539
+ load: () => import(${JSON.stringify(widgetPrefix + tag)}),
1540
+ props: ${JSON.stringify(props)},
1541
+ trigger: ${JSON.stringify(entry.trigger ?? "connected")}${docs.slots ? `,
1542
+ slots: ${JSON.stringify(docs.slots.map((slot) => slot.name))}` : ""}
1543
+ }`
1544
+ );
1545
+ return `import { defineElements } from "mountly/embed";
1546
+ defineElements({
1547
+ ${definitions.join(",\n ")}
1548
+ });`;
1549
+ }
1550
+ if (id.startsWith(`\0${widgetPrefix}`)) {
1551
+ const tag = id.slice(widgetPrefix.length + 1);
1552
+ const element = elements.find((candidate) => candidate.tag === tag);
1553
+ if (!element) return;
1554
+ const path = resolve3(root, element.entry.component).replace(/\\/g, "/");
1555
+ const widgetOptions = shadow ? `, { shadow: true, cssUrl: ${JSON.stringify(CSS_TOKEN)} }` : "";
1556
+ return `import * as component from ${JSON.stringify(path)};
1557
+ import { createWidget } from ${JSON.stringify(`mountly-${element.framework}`)};
1558
+ export default createWidget(component[${JSON.stringify(element.entry.exportName ?? "default")}]${widgetOptions});`;
1559
+ }
1560
+ },
1561
+ // Vite has already rewritten `new Worker(new URL(…))` to the emitted file by
1562
+ // now, so only the constructor call needs rerouting.
1563
+ renderChunk(code) {
1564
+ const shimmed = shimWorkers(code);
1565
+ return shimmed ? { code: shimmed, map: null } : null;
1566
+ },
1567
+ // Vite writes the stylesheet from its own `generateBundle`, so the name to
1568
+ // point at only exists once every other plugin has had its turn.
1569
+ generateBundle: {
1570
+ order: "post",
1571
+ handler(_options, bundle) {
1572
+ if (shadow) resolveCssToken(bundle);
1573
+ this.emitFile({ type: "asset", fileName: "embed.d.ts", source: declarations(elements) });
1574
+ this.emitFile({
1575
+ type: "asset",
1576
+ fileName: "embed.react.d.ts",
1577
+ source: reactDeclarations(elements)
1578
+ });
1579
+ this.emitFile({
1580
+ type: "asset",
1581
+ fileName: "custom-elements.json",
1582
+ source: manifest(
1583
+ elements.map((element) => ({
1584
+ ...element,
1585
+ entry: {
1586
+ ...element.entry,
1587
+ component: relative3(root, resolve3(root, element.entry.component))
1588
+ }
1589
+ }))
1590
+ )
1591
+ });
1592
+ }
1593
+ }
1594
+ };
1595
+ return {
1596
+ ...options.root ? { root: options.root } : {},
1597
+ // Vite's application pipeline already handles lazy CSS, asset rebasing,
1598
+ // and shared framework chunks. A library build would make us recreate it.
1599
+ base: "./",
1600
+ // Only enable the JSX transform when React is in play: it would otherwise
1601
+ // try to parse a `.vue` or `.svelte` file before its compiler sees it.
1602
+ oxc: frameworks.has("react") ? { jsx: { runtime: "automatic" } } : void 0,
1603
+ plugins: [plugin, frameworkPlugins(frameworks)],
1604
+ // ES workers keep their own `import.meta.url` when the shim imports them
1605
+ // from a blob:, so their relative URLs still resolve against the provider.
1606
+ // Classic (iife) workers would see the blob's URL instead.
1607
+ worker: { format: "es" },
1608
+ build: {
1609
+ sourcemap: true,
1610
+ // Light DOM wants Vite's per-chunk stylesheets, which arrive with the
1611
+ // component that needs them. Shadow roots need the opposite: one
1612
+ // stylesheet nothing injects, which each element adopts for itself.
1613
+ cssCodeSplit: !shadow,
1614
+ rollupOptions: {
1615
+ ...options.peer ? {
1616
+ external: [
1617
+ ...new Set(
1618
+ [...frameworks].flatMap((framework) => [
1619
+ ...getFrameworkPeerExternals(framework),
1620
+ // Compiled Svelte imports `svelte/internal/client`; bundling it
1621
+ // beside an external `svelte` would mean two runtimes.
1622
+ ...framework === "svelte" ? [/^svelte\//] : []
1623
+ ])
1624
+ )
1625
+ ]
1626
+ } : {},
1627
+ input: entryId,
1628
+ output: { entryFileNames: "embed.js", chunkFileNames: "assets/[name]-[hash].js" }
1629
+ }
1630
+ }
1631
+ };
1632
+ }
1633
+
1634
+ // src/index.ts
613
1635
  function matchesFilter(id, include) {
614
1636
  return include.some(
615
1637
  (pattern) => typeof pattern === "string" ? id.includes(pattern) : pattern.test(id)
@@ -623,7 +1645,7 @@ function mountlyCssAsText(options = {}) {
623
1645
  load(id) {
624
1646
  if (!id.endsWith(".css")) return null;
625
1647
  if (includePatterns && !matchesFilter(id, includePatterns)) return null;
626
- const source = readFileSync2(id, "utf8");
1648
+ const source = readFileSync3(id, "utf8");
627
1649
  return {
628
1650
  code: `export default ${JSON.stringify(source)};`,
629
1651
  map: { mappings: "" }
@@ -781,7 +1803,10 @@ function defineMountlyWidgetConfig(options = {}) {
781
1803
  return configs;
782
1804
  }
783
1805
  export {
1806
+ defineElementsConfig,
784
1807
  defineMountlyWidgetConfig,
1808
+ extractDocs,
1809
+ extractProps,
785
1810
  getFrameworkPeerExternals,
786
1811
  getSelfContainedExternals,
787
1812
  mountlyCssAsText,