@rmc-toolkit/vite 0.1.0 → 0.2.3
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 +71 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -36
- package/dist/index.test.js +111 -11
- package/dist/slice-build.d.ts +1 -0
- package/dist/slice-build.d.ts.map +1 -1
- package/dist/slice-build.js +5 -0
- package/dist/slice-build.test.js +20 -0
- package/package.json +9 -4
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @rmc-toolkit/vite
|
|
2
|
+
|
|
3
|
+
Vite/Rollup adapter for [Runtime Module Composition](https://runtime-module-composition.dev): injects the generated import map into a host's HTML before any module script runs, externalizes import-map-owned specifiers so Vite doesn't bundle or rewrite them, and provides `defineSliceBuild()` for a slice's own build config.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rmc-toolkit/vite @rmc-toolkit/core vite
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick example
|
|
12
|
+
|
|
13
|
+
**Host** — wire the plugin into `vite.config.ts`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { defineConfig } from "vite";
|
|
17
|
+
import { runtimeComposition } from "@rmc-toolkit/vite";
|
|
18
|
+
import { manifest } from "./runtime-composition.manifest";
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
plugins: [
|
|
22
|
+
...runtimeComposition({
|
|
23
|
+
manifest,
|
|
24
|
+
environment: "development",
|
|
25
|
+
}),
|
|
26
|
+
],
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`runtimeComposition()` returns two plugins: one that injects the generated import map into `index.html`, and one that tells Vite not to bundle or rewrite specifiers the import map owns.
|
|
31
|
+
|
|
32
|
+
**Slice** — build as an ESM library that externalizes anything the import map owns:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { defineConfig } from "vite";
|
|
36
|
+
import { defineSliceBuild, createRollupExternal } from "@rmc-toolkit/vite";
|
|
37
|
+
import { manifest } from "./runtime-composition.manifest";
|
|
38
|
+
|
|
39
|
+
export default defineConfig(({ mode }) => {
|
|
40
|
+
const sliceBuild = defineSliceBuild({ mode, devPort: 5174, sliceName: "search" });
|
|
41
|
+
|
|
42
|
+
return mode === "development"
|
|
43
|
+
? sliceBuild
|
|
44
|
+
: {
|
|
45
|
+
...sliceBuild,
|
|
46
|
+
build: {
|
|
47
|
+
...sliceBuild.build,
|
|
48
|
+
rollupOptions: { external: createRollupExternal(manifest) },
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`sliceName` determines where the production build lands (`dist/{sliceName}/index.mjs`), matching the path convention `resolveRoute()`/`createImportMap()` already assume — a slice's build output requires no separate assembly step to match its production URL.
|
|
55
|
+
|
|
56
|
+
## What's in here
|
|
57
|
+
|
|
58
|
+
- `runtimeComposition` — the combined host-side plugin pair (import-map injection + externalization).
|
|
59
|
+
- `includeRuntimeImportMap`, `externalizeRuntimeComposition` — the two plugins individually, if you need only one.
|
|
60
|
+
- `createRollupExternal` — a Rollup `external` function, for a slice's production build.
|
|
61
|
+
- `defineSliceBuild` — mode-aware Vite config for a slice (dev-server port, the library-build `process.env.NODE_ENV` fix, entry auto-detection).
|
|
62
|
+
- `includeHostedImportMap`, `buildLocalImportMapScript` — for serving the import map as a standalone hosted script rather than inlined into HTML (e.g. a production static-asset host), with local-dev override support.
|
|
63
|
+
|
|
64
|
+
Full signatures and behavior for every export: [API Reference](https://runtime-module-composition.dev/api-reference/#vite-adapter-rmc-toolkitvite).
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
- [Getting Started](https://runtime-module-composition.dev/getting-started/) — install and wire up a host + slice end to end
|
|
69
|
+
- [API Reference](https://runtime-module-composition.dev/api-reference/#vite-adapter-rmc-toolkitvite)
|
|
70
|
+
- [Technical Implementation](https://runtime-module-composition.dev/technical-implementation/) — the architecture and failure modes behind the pattern
|
|
71
|
+
- [Multi-Framework Demo](https://runtime-module-composition.dev/demo/) — a full, runnable reference implementation
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type RuntimeCompositionViteOptions = {
|
|
|
7
7
|
externalize?: boolean;
|
|
8
8
|
};
|
|
9
9
|
export declare const createRollupExternal: (manifest: RuntimeCompositionManifest) => ((source: string) => boolean);
|
|
10
|
-
export declare const externalizeRuntimeComposition: ({ manifest, }: RuntimeCompositionViteOptions) => Plugin;
|
|
10
|
+
export declare const externalizeRuntimeComposition: ({ manifest, environment, }: RuntimeCompositionViteOptions) => Plugin;
|
|
11
11
|
export declare const includeRuntimeImportMap: ({ manifest, environment, }: RuntimeCompositionViteOptions) => Plugin;
|
|
12
12
|
export declare const runtimeComposition: (options: RuntimeCompositionViteOptions) => Plugin[];
|
|
13
13
|
export type LocalSliceOverride = {
|
|
@@ -20,6 +20,6 @@ export type IncludeHostedImportMapOptions = {
|
|
|
20
20
|
path?: string;
|
|
21
21
|
localSlice?: LocalSliceOverride;
|
|
22
22
|
};
|
|
23
|
-
export declare const includeHostedImportMap: ({ manifest, path, localSlice, }: IncludeHostedImportMapOptions) => Plugin;
|
|
23
|
+
export declare const includeHostedImportMap: ({ manifest, path, localSlice, }: IncludeHostedImportMapOptions) => Plugin[];
|
|
24
24
|
export { defineSliceBuild, type SliceBuildOptions } from "./slice-build.js";
|
|
25
25
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,EAAE,0BAA0B,CAAC;IACrC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,UAAU,0BAA0B,KACnC,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAoC,CAAC;AAEpE,eAAO,MAAM,6BAA6B,GAAI,4BAG3C,6BAA6B,KAAG,MAoDlC,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,4BAGrC,6BAA6B,KAAG,MAgBjC,CAAC;AAEH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,6BAA6B,KACrC,MAAM,EAYR,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,UAAU,0BAA0B,EACpC,YAAY,kBAAkB,KAC7B,MAkBF,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,EAAE,0BAA0B,CAAC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACjC,CAAC;AA+BF,eAAO,MAAM,sBAAsB,GAAI,iCAIpC,6BAA6B,KAAG,MAAM,EAsCxC,CAAC;AAEF,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { createExternalMatcher, createImportMap, createImportMapBootstrapScript, } from "@rmc-toolkit/core";
|
|
1
|
+
import { createExternalMatcher, createImportMap, createImportMapBootstrapScript, resolveImportMapSpecifier, } from "@rmc-toolkit/core";
|
|
2
|
+
import { jsImportMapScriptPlugin } from "vite-plugin-js-importmap-script";
|
|
2
3
|
export const createRollupExternal = (manifest) => createExternalMatcher(manifest);
|
|
3
|
-
export const externalizeRuntimeComposition = ({ manifest, }) => {
|
|
4
|
+
export const externalizeRuntimeComposition = ({ manifest, environment, }) => {
|
|
4
5
|
const isExternal = createExternalMatcher(manifest);
|
|
6
|
+
let devImportMap = null;
|
|
5
7
|
return {
|
|
6
8
|
name: "runtime-module-composition-externalize",
|
|
7
9
|
enforce: "pre",
|
|
@@ -12,12 +14,37 @@ export const externalizeRuntimeComposition = ({ manifest, }) => {
|
|
|
12
14
|
},
|
|
13
15
|
};
|
|
14
16
|
},
|
|
17
|
+
configResolved(config) {
|
|
18
|
+
// Vite's production build (Rollup) already handles a bare specifier
|
|
19
|
+
// marked `external: true` correctly — it preserves it untouched in
|
|
20
|
+
// the output bundle for the browser's import map to resolve. Vite's
|
|
21
|
+
// dev server does not: its import-analysis step rewrites any bare
|
|
22
|
+
// specifier merely marked external into an internal /@id/<specifier>
|
|
23
|
+
// placeholder request, and nothing serves that path. Only in dev do
|
|
24
|
+
// we need to resolve to the real URL ourselves instead.
|
|
25
|
+
if (config.command === "serve") {
|
|
26
|
+
devImportMap = createImportMap(manifest, {
|
|
27
|
+
environment: environment ?? "development",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
},
|
|
15
31
|
resolveId(source) {
|
|
16
32
|
if (!isExternal(source)) {
|
|
17
33
|
return null;
|
|
18
34
|
}
|
|
35
|
+
// `resolved` can be undefined here only if devImportMap is null (build
|
|
36
|
+
// mode — see configResolved above) or if resolveImportMapSpecifier
|
|
37
|
+
// can't resolve `source` against it. The latter isn't expected to be
|
|
38
|
+
// reachable for a well-formed manifest: every specifier isExternal()
|
|
39
|
+
// matches (namespace prefix, external-deps prefix, exactImports keys,
|
|
40
|
+
// sliceOverrides specifiers) has a corresponding entry unconditionally
|
|
41
|
+
// added by createImportMap, so the same prefixes/keys this matcher
|
|
42
|
+
// checks are exactly what the import map also contains. The `?? source`
|
|
43
|
+
// fallback exists as a safety net for that theoretical case, not
|
|
44
|
+
// because it's expected to fire.
|
|
45
|
+
const resolved = devImportMap && resolveImportMapSpecifier(devImportMap, source);
|
|
19
46
|
return {
|
|
20
|
-
id: source,
|
|
47
|
+
id: resolved ?? source,
|
|
21
48
|
external: true,
|
|
22
49
|
};
|
|
23
50
|
},
|
|
@@ -65,38 +92,70 @@ export const buildLocalImportMapScript = (manifest, localSlice) => {
|
|
|
65
92
|
environment: "development",
|
|
66
93
|
});
|
|
67
94
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
// Consuming HTML must declare the hosted import map as an ordinary external
|
|
96
|
+
// script, in this exact attribute order, for the ordering-safety plugin
|
|
97
|
+
// below to find and reposition it:
|
|
98
|
+
//
|
|
99
|
+
// <script data-src-type="importmap" src="/js/importmap.js"></script>
|
|
100
|
+
//
|
|
101
|
+
// Per the HTML spec, a document's "import maps allowed" flag flips to false
|
|
102
|
+
// (permanently, for that page load) the moment the first module script or
|
|
103
|
+
// modulepreload is fetched — Vite's dev server always injects its own
|
|
104
|
+
// `type="module"` scripts (the HMR client, and the React refresh preamble
|
|
105
|
+
// when using @vitejs/plugin-react). If either of those gets prepared before
|
|
106
|
+
// this script tag is parsed, the import map is rejected outright (not
|
|
107
|
+
// merely reordered), and every bare-specifier import in the app fails. Once
|
|
108
|
+
// rejected, a later import map can't be reinserted to recover from it either
|
|
109
|
+
// — the flag never flips back to true for that page load.
|
|
110
|
+
//
|
|
111
|
+
// jsImportMapScriptPlugin() closes this by removing the tag from wherever
|
|
112
|
+
// it naturally sits in the document and force-reinserting it immediately
|
|
113
|
+
// after <head> on every dev request, rather than trusting that this
|
|
114
|
+
// plugin's own transformIndexHtml hook happens to run after Vite's
|
|
115
|
+
// internal ones (an ordering relationship Vite doesn't document or
|
|
116
|
+
// guarantee across versions). It also appends `?dev`/`&dev` to the src so
|
|
117
|
+
// the served script can detect dev mode the same way
|
|
118
|
+
// createImportMapBootstrapScript()'s own `?dev` detection does.
|
|
119
|
+
//
|
|
120
|
+
// Restricted to `apply: "serve"`: the plugin unconditionally appends the
|
|
121
|
+
// dev-flag query on every transformIndexHtml call it receives, with no
|
|
122
|
+
// mode-awareness of its own — the calling app is responsible for only
|
|
123
|
+
// wiring it in for dev, exactly as it must never run during `vite build`.
|
|
124
|
+
export const includeHostedImportMap = ({ manifest, path = "/js/importmap.js", localSlice, }) => [
|
|
125
|
+
{
|
|
126
|
+
name: "runtime-module-composition-hosted-import-map",
|
|
127
|
+
configureServer(server) {
|
|
128
|
+
server.middlewares.use(path, (req, res, next) => {
|
|
129
|
+
// Connect (which Vite's dev server middlewares are built on) mounts
|
|
130
|
+
// handlers by path *prefix*: it matches any request whose pathname
|
|
131
|
+
// starts with `path` and is followed by end-of-string, "/", or ".",
|
|
132
|
+
// then strips the matched prefix from `req.url` before invoking the
|
|
133
|
+
// handler. That means req.url here is *relative to the mount point*,
|
|
134
|
+
// and a request for "/js/importmap.js.map" or
|
|
135
|
+
// "/js/importmap.js/anything" also reaches this handler (as
|
|
136
|
+
// req.url === "/.map" or "/anything"), not just the exact endpoint.
|
|
137
|
+
// Comparing req.url against the outer `path` would never match,
|
|
138
|
+
// since Connect has already removed that prefix — the only way to
|
|
139
|
+
// detect "this is genuinely the exact endpoint" is to check that the
|
|
140
|
+
// remaining, query-stripped req.url is empty or "/".
|
|
141
|
+
const method = req.method ?? "GET";
|
|
142
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
143
|
+
next();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const remainder = (req.url ?? "/").split("?")[0] ?? "/";
|
|
147
|
+
if (remainder !== "" && remainder !== "/") {
|
|
148
|
+
next();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const script = localSlice
|
|
152
|
+
? buildLocalImportMapScript(manifest, localSlice)
|
|
153
|
+
: createImportMapBootstrapScript(manifest, { environment: "development" });
|
|
154
|
+
res.setHeader("Content-Type", "text/javascript");
|
|
155
|
+
res.end(script);
|
|
156
|
+
});
|
|
157
|
+
},
|
|
100
158
|
},
|
|
101
|
-
}
|
|
159
|
+
{ ...jsImportMapScriptPlugin(), apply: "serve" },
|
|
160
|
+
];
|
|
102
161
|
export { defineSliceBuild } from "./slice-build.js";
|
package/dist/index.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineManifest } from "@rmc-toolkit/core";
|
|
2
2
|
import { describe, expect, test } from "vitest";
|
|
3
|
-
import { buildLocalImportMapScript, defineSliceBuild, includeHostedImportMap, includeRuntimeImportMap, runtimeComposition, } from "./index.js";
|
|
3
|
+
import { buildLocalImportMapScript, defineSliceBuild, externalizeRuntimeComposition, includeHostedImportMap, includeRuntimeImportMap, runtimeComposition, } from "./index.js";
|
|
4
4
|
const manifest = defineManifest({
|
|
5
5
|
namespace: "@acme",
|
|
6
6
|
assetsOrigin: "https://assets.example.com",
|
|
@@ -43,11 +43,94 @@ describe("vite adapter", () => {
|
|
|
43
43
|
const config = defineSliceBuild({
|
|
44
44
|
mode: "development",
|
|
45
45
|
devPort: 5301,
|
|
46
|
+
sliceName: "react-app",
|
|
46
47
|
entry: "src/index.ts",
|
|
47
48
|
});
|
|
48
49
|
expect(config).toEqual({ server: { port: 5301 } });
|
|
49
50
|
});
|
|
50
51
|
});
|
|
52
|
+
describe("externalizeRuntimeComposition dev-server resolution", () => {
|
|
53
|
+
test("build mode returns the bare specifier unchanged (existing, verified-working behavior)", () => {
|
|
54
|
+
const plugin = externalizeRuntimeComposition({
|
|
55
|
+
manifest,
|
|
56
|
+
environment: "development",
|
|
57
|
+
});
|
|
58
|
+
if (typeof plugin.configResolved !== "function") {
|
|
59
|
+
throw new TypeError("Expected configResolved to be a function.");
|
|
60
|
+
}
|
|
61
|
+
plugin.configResolved({
|
|
62
|
+
command: "build",
|
|
63
|
+
});
|
|
64
|
+
if (typeof plugin.resolveId !== "function") {
|
|
65
|
+
throw new TypeError("Expected resolveId to be a function.");
|
|
66
|
+
}
|
|
67
|
+
const result = plugin.resolveId("@esm.sh/react");
|
|
68
|
+
expect(result).toEqual({ id: "@esm.sh/react", external: true });
|
|
69
|
+
});
|
|
70
|
+
test("dev server mode resolves to the real URL instead of the bare specifier", () => {
|
|
71
|
+
const plugin = externalizeRuntimeComposition({
|
|
72
|
+
manifest: {
|
|
73
|
+
...manifest,
|
|
74
|
+
externalDeps: [{ name: "react", version: "19.2.4" }],
|
|
75
|
+
},
|
|
76
|
+
environment: "development",
|
|
77
|
+
});
|
|
78
|
+
if (typeof plugin.configResolved !== "function") {
|
|
79
|
+
throw new TypeError("Expected configResolved to be a function.");
|
|
80
|
+
}
|
|
81
|
+
plugin.configResolved({
|
|
82
|
+
command: "serve",
|
|
83
|
+
});
|
|
84
|
+
if (typeof plugin.resolveId !== "function") {
|
|
85
|
+
throw new TypeError("Expected resolveId to be a function.");
|
|
86
|
+
}
|
|
87
|
+
const result = plugin.resolveId("@esm.sh/react");
|
|
88
|
+
expect(result).toEqual({
|
|
89
|
+
id: "https://esm.sh/react@19.2.4",
|
|
90
|
+
external: true,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
test("dev server mode resolves a subpath entry to its own real URL, not the catch-all prefix", () => {
|
|
94
|
+
const plugin = externalizeRuntimeComposition({
|
|
95
|
+
manifest: {
|
|
96
|
+
...manifest,
|
|
97
|
+
externalDeps: [{ name: "react-dom/client", version: "19.2.4" }],
|
|
98
|
+
},
|
|
99
|
+
environment: "development",
|
|
100
|
+
});
|
|
101
|
+
if (typeof plugin.configResolved !== "function") {
|
|
102
|
+
throw new TypeError("Expected configResolved to be a function.");
|
|
103
|
+
}
|
|
104
|
+
plugin.configResolved({
|
|
105
|
+
command: "serve",
|
|
106
|
+
});
|
|
107
|
+
if (typeof plugin.resolveId !== "function") {
|
|
108
|
+
throw new TypeError("Expected resolveId to be a function.");
|
|
109
|
+
}
|
|
110
|
+
const result = plugin.resolveId("@esm.sh/react-dom/client");
|
|
111
|
+
expect(result).toEqual({
|
|
112
|
+
id: "https://esm.sh/react-dom@19.2.4/client",
|
|
113
|
+
external: true,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
test("resolveId still returns null for a non-external specifier in dev server mode", () => {
|
|
117
|
+
const plugin = externalizeRuntimeComposition({
|
|
118
|
+
manifest,
|
|
119
|
+
environment: "development",
|
|
120
|
+
});
|
|
121
|
+
if (typeof plugin.configResolved !== "function") {
|
|
122
|
+
throw new TypeError("Expected configResolved to be a function.");
|
|
123
|
+
}
|
|
124
|
+
plugin.configResolved({
|
|
125
|
+
command: "serve",
|
|
126
|
+
});
|
|
127
|
+
if (typeof plugin.resolveId !== "function") {
|
|
128
|
+
throw new TypeError("Expected resolveId to be a function.");
|
|
129
|
+
}
|
|
130
|
+
const result = plugin.resolveId("lodash");
|
|
131
|
+
expect(result).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
51
134
|
const createMockServer = () => {
|
|
52
135
|
const middlewares = [];
|
|
53
136
|
const use = (path, handler) => {
|
|
@@ -65,9 +148,9 @@ describe("includeHostedImportMap", () => {
|
|
|
65
148
|
expect(script).toContain('"@acme/":"https://assets.example.com/"');
|
|
66
149
|
});
|
|
67
150
|
test("serves the generated script from the default middleware path", () => {
|
|
68
|
-
const plugin = includeHostedImportMap({ manifest });
|
|
151
|
+
const [plugin] = includeHostedImportMap({ manifest });
|
|
69
152
|
const { server, middlewares } = createMockServer();
|
|
70
|
-
if (typeof plugin.configureServer !== "function") {
|
|
153
|
+
if (!plugin || typeof plugin.configureServer !== "function") {
|
|
71
154
|
throw new TypeError("Expected configureServer to be a function.");
|
|
72
155
|
}
|
|
73
156
|
plugin.configureServer(server);
|
|
@@ -90,21 +173,21 @@ describe("includeHostedImportMap", () => {
|
|
|
90
173
|
expect(body).toContain('"@acme/":"https://assets.example.com/"');
|
|
91
174
|
});
|
|
92
175
|
test("uses a custom middleware path when provided", () => {
|
|
93
|
-
const plugin = includeHostedImportMap({ manifest, path: "/custom.js" });
|
|
176
|
+
const [plugin] = includeHostedImportMap({ manifest, path: "/custom.js" });
|
|
94
177
|
const { server, middlewares } = createMockServer();
|
|
95
|
-
if (typeof plugin.configureServer !== "function") {
|
|
178
|
+
if (!plugin || typeof plugin.configureServer !== "function") {
|
|
96
179
|
throw new TypeError("Expected configureServer to be a function.");
|
|
97
180
|
}
|
|
98
181
|
plugin.configureServer(server);
|
|
99
182
|
expect(middlewares[0]?.path).toBe("/custom.js");
|
|
100
183
|
});
|
|
101
184
|
test("applies the localSlice override when serving the script", () => {
|
|
102
|
-
const plugin = includeHostedImportMap({
|
|
185
|
+
const [plugin] = includeHostedImportMap({
|
|
103
186
|
manifest,
|
|
104
187
|
localSlice: { name: "search", port: 5173 },
|
|
105
188
|
});
|
|
106
189
|
const { server, middlewares } = createMockServer();
|
|
107
|
-
if (typeof plugin.configureServer !== "function") {
|
|
190
|
+
if (!plugin || typeof plugin.configureServer !== "function") {
|
|
108
191
|
throw new TypeError("Expected configureServer to be a function.");
|
|
109
192
|
}
|
|
110
193
|
plugin.configureServer(server);
|
|
@@ -119,9 +202,9 @@ describe("includeHostedImportMap", () => {
|
|
|
119
202
|
expect(body).toContain('"@acme/search/":"http://localhost:5173/"');
|
|
120
203
|
});
|
|
121
204
|
test("calls next() instead of end() for a method other than GET/HEAD", () => {
|
|
122
|
-
const plugin = includeHostedImportMap({ manifest });
|
|
205
|
+
const [plugin] = includeHostedImportMap({ manifest });
|
|
123
206
|
const { server, middlewares } = createMockServer();
|
|
124
|
-
if (typeof plugin.configureServer !== "function") {
|
|
207
|
+
if (!plugin || typeof plugin.configureServer !== "function") {
|
|
125
208
|
throw new TypeError("Expected configureServer to be a function.");
|
|
126
209
|
}
|
|
127
210
|
plugin.configureServer(server);
|
|
@@ -141,9 +224,9 @@ describe("includeHostedImportMap", () => {
|
|
|
141
224
|
expect(endCalled).toBe(false);
|
|
142
225
|
});
|
|
143
226
|
test("calls next() instead of end() when the matched path is a prefix-extended variant", () => {
|
|
144
|
-
const plugin = includeHostedImportMap({ manifest });
|
|
227
|
+
const [plugin] = includeHostedImportMap({ manifest });
|
|
145
228
|
const { server, middlewares } = createMockServer();
|
|
146
|
-
if (typeof plugin.configureServer !== "function") {
|
|
229
|
+
if (!plugin || typeof plugin.configureServer !== "function") {
|
|
147
230
|
throw new TypeError("Expected configureServer to be a function.");
|
|
148
231
|
}
|
|
149
232
|
plugin.configureServer(server);
|
|
@@ -166,4 +249,21 @@ describe("includeHostedImportMap", () => {
|
|
|
166
249
|
expect(nextCalled).toBe(true);
|
|
167
250
|
expect(endCalled).toBe(false);
|
|
168
251
|
});
|
|
252
|
+
test("includes the ordering-safety HTML transform plugin, restricted to dev", () => {
|
|
253
|
+
const [, htmlTransformPlugin] = includeHostedImportMap({ manifest });
|
|
254
|
+
expect(htmlTransformPlugin?.apply).toBe("serve");
|
|
255
|
+
expect(typeof htmlTransformPlugin?.transformIndexHtml).toBe("function");
|
|
256
|
+
});
|
|
257
|
+
test("the HTML transform plugin repositions the importmap script tag to the front of <head> and appends ?dev", () => {
|
|
258
|
+
const [, htmlTransformPlugin] = includeHostedImportMap({ manifest });
|
|
259
|
+
if (typeof htmlTransformPlugin?.transformIndexHtml !== "function") {
|
|
260
|
+
throw new TypeError("Expected transformIndexHtml to be a function.");
|
|
261
|
+
}
|
|
262
|
+
const transformIndexHtml = htmlTransformPlugin.transformIndexHtml;
|
|
263
|
+
const html = "<html><head><title>t</title>" +
|
|
264
|
+
'<script data-src-type="importmap" src="/js/importmap.js"></script>' +
|
|
265
|
+
"</head><body></body></html>";
|
|
266
|
+
const result = transformIndexHtml(html);
|
|
267
|
+
expect(result.indexOf('src="/js/importmap.js?dev"')).toBeLessThan(result.indexOf("<title>"));
|
|
268
|
+
});
|
|
169
269
|
});
|
package/dist/slice-build.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const resolveEntry: (cwd: string, entry?: string) => string;
|
|
|
3
3
|
export type SliceBuildOptions = {
|
|
4
4
|
mode: string;
|
|
5
5
|
devPort: number;
|
|
6
|
+
sliceName: string;
|
|
6
7
|
entry?: string;
|
|
7
8
|
};
|
|
8
9
|
export declare const defineSliceBuild: (options: SliceBuildOptions) => UserConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slice-build.d.ts","sourceRoot":"","sources":["../src/slice-build.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,EAAE,QAAQ,MAAM,KAAG,MAgB1D,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,SAAS,iBAAiB,KAAG,
|
|
1
|
+
{"version":3,"file":"slice-build.d.ts","sourceRoot":"","sources":["../src/slice-build.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,EAAE,QAAQ,MAAM,KAAG,MAgB1D,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,SAAS,iBAAiB,KAAG,UAgC7D,CAAC"}
|
package/dist/slice-build.js
CHANGED
|
@@ -30,6 +30,11 @@ export const defineSliceBuild = (options) => {
|
|
|
30
30
|
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
31
31
|
},
|
|
32
32
|
build: {
|
|
33
|
+
// Lands the slice's own build output at the same {sliceName} path
|
|
34
|
+
// segment resolveRoute()/createImportMap() already assume it will be
|
|
35
|
+
// deployed at ({assetsOrigin}/{sliceName}/index.mjs) — see
|
|
36
|
+
// docs/superpowers/specs/2026-07-07-slice-build-output-path-design.md.
|
|
37
|
+
outDir: `dist/${options.sliceName}`,
|
|
33
38
|
lib: {
|
|
34
39
|
entry: resolvedEntry,
|
|
35
40
|
formats: ["es"],
|
package/dist/slice-build.test.js
CHANGED
|
@@ -43,6 +43,7 @@ describe("defineSliceBuild", () => {
|
|
|
43
43
|
const config = defineSliceBuild({
|
|
44
44
|
mode: "development",
|
|
45
45
|
devPort: 5301,
|
|
46
|
+
sliceName: "react-app",
|
|
46
47
|
entry: "src/index.tsx",
|
|
47
48
|
});
|
|
48
49
|
expect(config).toEqual({ server: { port: 5301 } });
|
|
@@ -51,6 +52,7 @@ describe("defineSliceBuild", () => {
|
|
|
51
52
|
const config = defineSliceBuild({
|
|
52
53
|
mode: "production",
|
|
53
54
|
devPort: 5301,
|
|
55
|
+
sliceName: "react-app",
|
|
54
56
|
entry: "src/index.tsx",
|
|
55
57
|
});
|
|
56
58
|
expect(config.preview).toEqual({ cors: true });
|
|
@@ -68,4 +70,22 @@ describe("defineSliceBuild", () => {
|
|
|
68
70
|
expect(lib.fileName("es", "index")).toBe("index.mjs");
|
|
69
71
|
}
|
|
70
72
|
});
|
|
73
|
+
test("build mode writes to dist/{sliceName}, not a flat dist/", () => {
|
|
74
|
+
const config = defineSliceBuild({
|
|
75
|
+
mode: "production",
|
|
76
|
+
devPort: 5301,
|
|
77
|
+
sliceName: "react-app",
|
|
78
|
+
entry: "src/index.tsx",
|
|
79
|
+
});
|
|
80
|
+
expect(config.build?.outDir).toBe("dist/react-app");
|
|
81
|
+
});
|
|
82
|
+
test("dev mode is unaffected by sliceName", () => {
|
|
83
|
+
const config = defineSliceBuild({
|
|
84
|
+
mode: "development",
|
|
85
|
+
devPort: 5301,
|
|
86
|
+
sliceName: "react-app",
|
|
87
|
+
entry: "src/index.tsx",
|
|
88
|
+
});
|
|
89
|
+
expect(config).toEqual({ server: { port: 5301 } });
|
|
90
|
+
});
|
|
71
91
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmc-toolkit/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/runtime-module-composition/rmc-toolkit",
|
|
8
|
+
"directory": "packages/vite"
|
|
9
|
+
},
|
|
5
10
|
"main": "./dist/index.js",
|
|
6
11
|
"types": "./dist/index.d.ts",
|
|
7
12
|
"exports": {
|
|
@@ -17,14 +22,14 @@
|
|
|
17
22
|
"access": "public"
|
|
18
23
|
},
|
|
19
24
|
"peerDependencies": {
|
|
20
|
-
"@rmc-toolkit/core": "0.
|
|
25
|
+
"@rmc-toolkit/core": "0.2.3",
|
|
21
26
|
"vite": ">=5"
|
|
22
27
|
},
|
|
23
28
|
"dependencies": {
|
|
24
|
-
"@rmc-toolkit/core": "0.
|
|
29
|
+
"@rmc-toolkit/core": "0.2.3",
|
|
30
|
+
"vite-plugin-js-importmap-script": "^1.0.0"
|
|
25
31
|
},
|
|
26
32
|
"scripts": {
|
|
27
33
|
"build": "tsc -b"
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
|
-
|