@rmc-toolkit/vite 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,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,eAE3C,6BAA6B,KAAG,MAwBlC,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;AAEF,eAAO,MAAM,sBAAsB,GAAI,iCAIpC,6BAA6B,KAAG,MAmCjC,CAAC;AAEH,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
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
- export const includeHostedImportMap = ({ manifest, path = "/js/importmap.js", localSlice, }) => ({
69
- name: "runtime-module-composition-hosted-import-map",
70
- configureServer(server) {
71
- server.middlewares.use(path, (req, res, next) => {
72
- // Connect (which Vite's dev server middlewares are built on) mounts
73
- // handlers by path *prefix*: it matches any request whose pathname
74
- // starts with `path` and is followed by end-of-string, "/", or ".",
75
- // then strips the matched prefix from `req.url` before invoking the
76
- // handler. That means req.url here is *relative to the mount point*,
77
- // and a request for "/js/importmap.js.map" or
78
- // "/js/importmap.js/anything" also reaches this handler (as
79
- // req.url === "/.map" or "/anything"), not just the exact endpoint.
80
- // Comparing req.url against the outer `path` would never match,
81
- // since Connect has already removed that prefix the only way to
82
- // detect "this is genuinely the exact endpoint" is to check that the
83
- // remaining, query-stripped req.url is empty or "/".
84
- const method = req.method ?? "GET";
85
- if (method !== "GET" && method !== "HEAD") {
86
- next();
87
- return;
88
- }
89
- const remainder = (req.url ?? "/").split("?")[0] ?? "/";
90
- if (remainder !== "" && remainder !== "/") {
91
- next();
92
- return;
93
- }
94
- const script = localSlice
95
- ? buildLocalImportMapScript(manifest, localSlice)
96
- : createImportMapBootstrapScript(manifest, { environment: "development" });
97
- res.setHeader("Content-Type", "text/javascript");
98
- res.end(script);
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";
@@ -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
  });
@@ -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,UA2B7D,CAAC"}
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"}
@@ -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"],
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmc-toolkit/vite",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,14 +17,14 @@
17
17
  "access": "public"
18
18
  },
19
19
  "peerDependencies": {
20
- "@rmc-toolkit/core": "0.1.0",
20
+ "@rmc-toolkit/core": "0.2.0",
21
21
  "vite": ">=5"
22
22
  },
23
23
  "dependencies": {
24
- "@rmc-toolkit/core": "0.1.0"
24
+ "@rmc-toolkit/core": "0.2.0",
25
+ "vite-plugin-js-importmap-script": "^1.0.0"
25
26
  },
26
27
  "scripts": {
27
28
  "build": "tsc -b"
28
29
  }
29
30
  }
30
-