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 +98 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +39 -11
- package/package.json +9 -6
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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, [
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|