mountly-vite-plugin 1.0.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # mountly-vite-plugin
2
+
3
+ Vite plugins for both ends of a [mountly](https://npmjs.com/package/mountly)
4
+ setup: building a widget so other teams can load it, and configuring the host
5
+ page that loads them.
6
+
7
+ ```bash
8
+ pnpm add -D mountly-vite-plugin
9
+ ```
10
+
11
+ ## Building a widget
12
+
13
+ One config produces two builds from the same source:
14
+
15
+ ```ts
16
+ // vite.config.ts
17
+ import { defineMountlyWidgetConfig } from "mountly-vite-plugin";
18
+
19
+ export default defineMountlyWidgetConfig({
20
+ framework: "react",
21
+ entry: "src/index.ts",
22
+ verticalId: "billing",
23
+ featureExport: "checkout",
24
+ exposes: { "./Checkout": "src/Checkout.tsx" },
25
+ });
26
+ ```
27
+
28
+ - **`dist/index.js`** — React inlined. Drops onto any page, no host setup.
29
+ - **`dist/peer.js`** — React external. The host supplies one copy through its
30
+ import map, so three widgets share it instead of shipping three.
31
+
32
+ Widget authors pick neither; both are emitted, and the host decides which URL
33
+ it points at.
34
+
35
+ It also writes **`dist/mountly.manifest.fragment.json`**, the entry your CI
36
+ merges into the host's manifest — including a `types` block naming what each
37
+ entry exports, read from the TypeScript checker rather than guessed. Declaration
38
+ files land in `dist/types/`, so a host that imports `billing/Checkout` gets real
39
+ types for a module it never compiled.
40
+
41
+ `vite build` outputs both, plus the fragment, with no separate build script.
42
+
43
+ ## Configuring the host
44
+
45
+ ```ts
46
+ // vite.config.ts
47
+ import { mountlyHostPlugin } from "mountly-vite-plugin";
48
+
49
+ export default {
50
+ plugins: [mountlyHostPlugin({ manifest: "./mountly.manifest.json" })],
51
+ };
52
+ ```
53
+
54
+ The host plugin externalizes every remote the manifest declares — so Vite
55
+ doesn't try to bundle code that lives on someone else's CDN — and injects the
56
+ import map into the served HTML. Vite's `base` is applied to the map, so a site
57
+ served from a subpath resolves correctly.
58
+
59
+ Point it at fragments instead and it composes the manifest itself, which is
60
+ useful in a monorepo where the remotes are built alongside the host:
61
+
62
+ ```ts
63
+ mountlyHostPlugin({
64
+ verticals: [{ fragment: "./packages/billing/dist/mountly.manifest.fragment.json" }],
65
+ });
66
+ ```
67
+
68
+ ## Individual plugins
69
+
70
+ `defineMountlyWidgetConfig` is a preset over these, exported for when you need
71
+ to compose them into a config you already own:
72
+
73
+ | Export | Does |
74
+ | ------------------------------- | -------------------------------------------------------- |
75
+ | `mountlyHostPlugin` | externalize remotes, inject the import map |
76
+ | `mountlyRemote` | build one vertical, emit its manifest fragment |
77
+ | `mountlyManifestFragmentPlugin` | emit the fragment and declarations for an existing build |
78
+ | `mountlyCssAsText` | import CSS as a string, for adopting into a shadow root |
79
+ | `getFrameworkPeerExternals` | the externals list for a peer build |
80
+
81
+ ## TypeScript
82
+
83
+ `mountlyManifestFragmentPlugin` reads exports from the TypeScript checker and
84
+ drives `tsc` for declaration emit, so `typescript` is a peer dependency. Nearly
85
+ every project that builds a widget already has it. Without it, the build still
86
+ succeeds — the fragment is written without its `types` block, and typed remotes
87
+ are unavailable until you install it.
88
+
89
+ TypeScript 7 or later is required, since it uses the `typescript/unstable`
90
+ checker API.
91
+
92
+ ## Documentation
93
+
94
+ <https://mountly.dev/concepts/manifest-hosts/>
95
+
96
+ ## License
97
+
98
+ Apache-2.0
package/dist/index.d.ts CHANGED
@@ -4,7 +4,10 @@ export { mountlyManifestFragmentPlugin, type ManifestFragmentPluginOptions } fro
4
4
  export type MountlyWidgetFramework = "react" | "vue" | "svelte";
5
5
  export declare function getFrameworkPeerExternals(framework: MountlyWidgetFramework): Array<string | RegExp>;
6
6
  export declare function getSelfContainedExternals(framework: MountlyWidgetFramework): Array<string | RegExp>;
7
- export declare function mountlyCssAsText(): Plugin;
7
+ export interface MountlyCssAsTextOptions {
8
+ include?: string | RegExp | (string | RegExp)[];
9
+ }
10
+ export declare function mountlyCssAsText(options?: MountlyCssAsTextOptions): Plugin;
8
11
  export interface MountlyWidgetPluginOptions {
9
12
  framework?: MountlyWidgetFramework;
10
13
  }
package/dist/index.js CHANGED
@@ -8,9 +8,20 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
8
8
  import { dirname, join, parse, relative, resolve } from "path";
9
9
  import { fileURLToPath } from "url";
10
10
  import { exportKeyToSubpath } from "mountly-manifest";
11
- import { API, SymbolFlags } from "typescript/unstable/sync";
11
+ async function loadTypeScript() {
12
+ try {
13
+ return await import("typescript/unstable/sync");
14
+ } catch {
15
+ return void 0;
16
+ }
17
+ }
12
18
  function tscBin() {
13
- return join(dirname(fileURLToPath(import.meta.resolve("typescript/package.json"))), "bin", "tsc");
19
+ try {
20
+ const packageJson = fileURLToPath(import.meta.resolve("typescript/package.json"));
21
+ return join(dirname(packageJson), "bin", "tsc");
22
+ } catch {
23
+ return void 0;
24
+ }
14
25
  }
15
26
  function mountlyManifestFragmentPlugin(options) {
16
27
  let outDir = options.outDir ?? "dist";
@@ -25,10 +36,13 @@ function mountlyManifestFragmentPlugin(options) {
25
36
  dir = next;
26
37
  }
27
38
  }
28
- function collectNamedExports(entries) {
39
+ async function collectNamedExports(entries) {
29
40
  const sources = entries.filter((entry) => /\.(?:[cm]?[jt]sx?)$/i.test(entry));
30
41
  const names = /* @__PURE__ */ new Map();
31
42
  if (sources.length === 0) return names;
43
+ const typescript = await loadTypeScript();
44
+ if (!typescript) return names;
45
+ const { API, SymbolFlags } = typescript;
32
46
  const api = new API();
33
47
  try {
34
48
  const snapshot = api.updateSnapshot({ openFiles: [...sources] });
@@ -64,16 +78,24 @@ function mountlyManifestFragmentPlugin(options) {
64
78
  return relative(rootDir, entry).replace(/\.(?:[cm]?[jt]sx?)$/i, ".d.ts");
65
79
  }
66
80
  function emitDeclarations(entry, exposeEntries) {
67
- if (!entry) return { exportDeclarations: {} };
81
+ const tsc = tscBin();
82
+ if (!entry || !tsc) return { exportDeclarations: {} };
68
83
  const sourceEntries = [entry, ...Object.values(exposeEntries)];
69
84
  const rootDir = commonDir(sourceEntries);
70
85
  const declarationDir = resolve(outDir, "types");
71
86
  mkdirSync(declarationDir, { recursive: true });
72
87
  const emitConfigPath = join(declarationDir, "tsconfig.emit.json");
88
+ const baseConfigPath = findConfigFile();
73
89
  writeFileSync(
74
90
  emitConfigPath,
75
91
  JSON.stringify({
76
- ...findConfigFile() ? { extends: findConfigFile() } : {},
92
+ ...baseConfigPath ? { extends: baseConfigPath } : {},
93
+ // `files` and an inherited `include` are additive, and anything outside
94
+ // `rootDir` escapes the `outDir` mapping and lands beside its source.
95
+ // Only the entries below may be compiled.
96
+ include: [],
97
+ exclude: [],
98
+ files: sourceEntries,
77
99
  compilerOptions: {
78
100
  declaration: true,
79
101
  emitDeclarationOnly: true,
@@ -83,12 +105,11 @@ function mountlyManifestFragmentPlugin(options) {
83
105
  outDir: declarationDir,
84
106
  declarationDir,
85
107
  rootDir
86
- },
87
- files: sourceEntries
108
+ }
88
109
  })
89
110
  );
90
111
  try {
91
- execFileSync(process.execPath, [tscBin(), "-p", emitConfigPath], { stdio: "pipe" });
112
+ execFileSync(process.execPath, [tsc, "-p", emitConfigPath], { stdio: "pipe" });
92
113
  } catch {
93
114
  } finally {
94
115
  rmSync(emitConfigPath, { force: true });
@@ -108,7 +129,7 @@ function mountlyManifestFragmentPlugin(options) {
108
129
  root = config.root;
109
130
  outDir = resolve(root, options.outDir ?? "dist");
110
131
  },
111
- closeBundle() {
132
+ async closeBundle() {
112
133
  const entry = options.entry ? resolve(root, options.entry) : void 0;
113
134
  const exposeEntries = Object.fromEntries(
114
135
  Object.entries(options.exposeEntries ?? {}).map(([key, value]) => [
@@ -124,7 +145,7 @@ function mountlyManifestFragmentPlugin(options) {
124
145
  if (options.version) vertical.version = options.version;
125
146
  if (options.featureExport) vertical.featureExport = options.featureExport;
126
147
  const declarations = emitDeclarations(entry, exposeEntries);
127
- const namedExports = collectNamedExports([
148
+ const namedExports = await collectNamedExports([
128
149
  ...entry ? [entry] : [],
129
150
  ...Object.values(exposeEntries)
130
151
  ]);
@@ -592,12 +613,19 @@ function getSelfContainedExternals(framework) {
592
613
  ...FRAMEWORK_EXTERNALS[framework].filter((id) => !FRAMEWORK_BUNDLE[framework].includes(id))
593
614
  ];
594
615
  }
595
- function mountlyCssAsText() {
616
+ function matchesFilter(id, include) {
617
+ return include.some(
618
+ (pattern) => typeof pattern === "string" ? id.includes(pattern) : pattern.test(id)
619
+ );
620
+ }
621
+ function mountlyCssAsText(options = {}) {
622
+ const includePatterns = options.include ? Array.isArray(options.include) ? options.include : [options.include] : null;
596
623
  return {
597
624
  name: "mountly-css-as-text",
598
625
  enforce: "pre",
599
626
  load(id) {
600
627
  if (!id.endsWith(".css")) return null;
628
+ if (includePatterns && !matchesFilter(id, includePatterns)) return null;
601
629
  const source = readFileSync2(id, "utf8");
602
630
  return {
603
631
  code: `export default ${JSON.stringify(source)};`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mountly-vite-plugin",
3
- "version": "1.0.0",
3
+ "version": "1.2.2",
4
4
  "description": "Vite lib build plugin for mountly vertical widgets (dual ESM output)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -23,18 +23,21 @@
23
23
  "publishConfig": {
24
24
  "access": "public"
25
25
  },
26
- "dependencies": {
27
- "typescript": "^7.0.2"
28
- },
29
26
  "devDependencies": {
30
27
  "tsup": "^8.5.1",
31
28
  "typescript": "^7.0.2",
32
29
  "vite": "^8.1.5",
33
- "mountly-manifest": "1.0.0"
30
+ "mountly-manifest": "1.0.3"
34
31
  },
35
32
  "peerDependencies": {
33
+ "typescript": ">=7.0.0",
36
34
  "vite": "^8.2.1",
37
- "mountly-manifest": "1.0.0"
35
+ "mountly-manifest": "1.0.3"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "typescript": {
39
+ "optional": true
40
+ }
38
41
  },
39
42
  "scripts": {
40
43
  "build": "tsup && tsc -p tsconfig.json --noEmit false --declaration --emitDeclarationOnly --declarationMap false --outDir dist",