@izumisy/vite-plugin-react-preview 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IzumiSy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @izumisy/vite-plugin-react-preview
2
+
3
+ Low-level Vite plugin and utilities for rendering React component previews. This package is the shared engine used by both `@izumisy/md-react-preview` (CLI) and `@izumisy/vitepress-plugin-react-preview` (VitePress integration).
4
+
5
+ ## Features
6
+
7
+ - **Preview block parsing** — `parseMeta()` extracts options (`wrap`, `height`, `standalone`, …) from fenced code block meta strings
8
+ - **Virtual module generation** — each preview block becomes a virtual Vite module (`virtual:mrp-preview-{blockId}`) containing the component code, CSS import, and a default export
9
+ - **Standalone preview pages** — generates full-viewport HTML pages served at `/__preview/{blockId}` with theme support (`?theme=dark|light`)
10
+ - **Block registry** — a shared `Map<string, PreviewBlockEntry>` that tracks discovered blocks and coordinates between the markdown parser and Vite plugin
11
+ - **Browser-safe sub-path** — `@izumisy/vite-plugin-react-preview/dom` exports constants without pulling in Node.js dependencies, safe for browser/Vue SFC imports
12
+
13
+ ## Exports
14
+
15
+ ### `@izumisy/vite-plugin-react-preview` (main)
16
+
17
+ | Export | Description |
18
+ |--------|-------------|
19
+ | `createBasePreviewPlugin(name, options)` | Creates the core Vite plugin array (resolve/load/transform + dev server middleware) |
20
+ | `createPreviewBuildPlugin(options)` | Vite plugin for production builds — emits standalone HTML pages for each block |
21
+ | `createPreviewHooks(options)` | Lower-level hooks (`resolveId`, `load`, `transform`) for custom plugin composition |
22
+ | `generatePreviewModuleCode(blockId, entry, css?)` | Generates the virtual module source for a single preview block |
23
+ | `generateStandaloneHtml(scriptSrc)` | Generates the HTML shell for standalone preview pages |
24
+ | `parseMeta(meta)` | Parses `key="value"` pairs and boolean flags from fence meta strings |
25
+ | `simpleHash(str)` | MD5-based short hash for generating block IDs |
26
+ | `resolveCssImportPath(css, hostRoot?)` | Resolves CSS import paths (package specifiers vs relative paths) |
27
+
28
+ ### `@izumisy/vite-plugin-react-preview/dom` (browser-safe)
29
+
30
+ | Export | Description |
31
+ |--------|-------------|
32
+ | `WRAP_STYLES` | Layout style map (`row`, `column`) |
33
+ | `ALIGN_STYLES` | Alignment style map (`center`, `start`, `end`) |
34
+
35
+ ## Usage
36
+
37
+ This package is not typically used directly. It is consumed by:
38
+
39
+ - **`@izumisy/md-react-preview`** — uses `createBasePreviewPlugin` and `createPreviewBuildPlugin` to power the CLI preview server
40
+ - **`@izumisy/vitepress-plugin-react-preview`** — uses the same plugins to integrate previews into VitePress
41
+
42
+ If you are building a custom integration, use `createBasePreviewPlugin()` with a block registry:
43
+
44
+ ```ts
45
+ import {
46
+ createBasePreviewPlugin,
47
+ createPreviewBuildPlugin,
48
+ type PreviewBlockEntry,
49
+ } from "@izumisy/vite-plugin-react-preview";
50
+
51
+ // blockRegistry is defined externally so that multiple plugins (e.g. a
52
+ // markdown-it plugin that registers blocks and the Vite plugin that serves
53
+ // them) can share the same mutable state.
54
+ const blockRegistry = new Map<string, PreviewBlockEntry>();
55
+
56
+ // createBasePreviewPlugin: provides virtual module resolution, code
57
+ // generation, JSX compilation, and the dev server middleware for serving
58
+ // standalone preview pages at /__preview/:blockId.
59
+ const basePlugins = createBasePreviewPlugin("my-preview-plugin", {
60
+ blockRegistry,
61
+ cssImport: "@my-lib/styles",
62
+ });
63
+
64
+ // createPreviewBuildPlugin: production build only — emits static HTML pages
65
+ // for each preview block so they can be embedded as iframes in the built site.
66
+ const buildPlugin = createPreviewBuildPlugin({
67
+ blockRegistry,
68
+ scanBlocks: (root) => {
69
+ // Populate blockRegistry before Rollup resolves virtual modules.
70
+ },
71
+ });
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT
package/dist/dom.d.mts ADDED
@@ -0,0 +1,2 @@
1
+ import { a as WRAP_STYLES, t as ALIGN_STYLES } from "./preview-module-CkwXFASr.mjs";
2
+ export { ALIGN_STYLES, WRAP_STYLES };
package/dist/dom.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { a as WRAP_STYLES, t as ALIGN_STYLES } from "./preview-module-FAlHEUCG.mjs";
2
+ export { ALIGN_STYLES, WRAP_STYLES };
@@ -0,0 +1,51 @@
1
+ import { a as WRAP_STYLES, c as PreviewBlockEntry, d as simpleHash, i as VIRTUAL_PREFIX, l as parseMeta, n as REGISTRY_MODULE_ID, o as generatePreviewModuleCode, r as STANDALONE_CLIENT_MODULE_ID, s as generateStandaloneHtml, t as ALIGN_STYLES, u as resolveCssImportPath } from "./preview-module-CkwXFASr.mjs";
2
+ import * as vite from "vite";
3
+ import { Plugin } from "vite";
4
+
5
+ //#region src/preview-plugin.d.ts
6
+ interface PreviewPluginOptions {
7
+ blockRegistry: Map<string, PreviewBlockEntry>;
8
+ /** CSS file to import in preview blocks (e.g. "@my-lib/styles") */
9
+ cssImport?: string;
10
+ /**
11
+ * When true, skip the configureServer middleware for standalone preview
12
+ * pages (the consumer provides its own). Defaults to false.
13
+ */
14
+ skipStandaloneServer?: boolean;
15
+ }
16
+ /**
17
+ * Create the common preview hook functions for virtual module resolution,
18
+ * code generation, and JSX compilation.
19
+ *
20
+ * Shared between the standalone CLI previewer and framework-specific plugins.
21
+ */
22
+ declare function createPreviewHooks(options: PreviewPluginOptions): {
23
+ resolveId: (this: any, id: string, importer?: string) => any;
24
+ load: (id: string) => string | undefined;
25
+ transform: (code: string, id: string) => Promise<vite.ESBuildTransformResult | undefined>;
26
+ };
27
+ /**
28
+ * Create a complete preview Vite plugin from the common hooks.
29
+ * Suitable for consumers that don't need to extend the plugin (e.g. VitePress).
30
+ */
31
+ declare function createBasePreviewPlugin(name: string, options: PreviewPluginOptions): Plugin[];
32
+ interface PreviewBuildPluginOptions {
33
+ blockRegistry: Map<string, PreviewBlockEntry>;
34
+ /**
35
+ * Called during `buildStart` to pre-populate the blockRegistry before
36
+ * Rollup loads any modules. This is critical for frameworks like VitePress
37
+ * where blocks are normally registered lazily during the transform phase
38
+ * — too late for the registry virtual module to include them.
39
+ */
40
+ scanBlocks?: (root: string) => void | Promise<void>;
41
+ }
42
+ /**
43
+ * Vite plugin that generates standalone preview HTML pages during build.
44
+ *
45
+ * Each preview block gets its own HTML page at `__preview/{blockId}.html`.
46
+ * Use alongside `createBasePreviewPlugin` for consumers like VitePress that
47
+ * need static preview pages in production.
48
+ */
49
+ declare function createPreviewBuildPlugin(options: PreviewBuildPluginOptions): Plugin;
50
+ //#endregion
51
+ export { ALIGN_STYLES, type PreviewBlockEntry, type PreviewPluginOptions, REGISTRY_MODULE_ID, STANDALONE_CLIENT_MODULE_ID, VIRTUAL_PREFIX, WRAP_STYLES, createBasePreviewPlugin, createPreviewBuildPlugin, createPreviewHooks, generatePreviewModuleCode, generateStandaloneHtml, parseMeta, resolveCssImportPath, simpleHash };
package/dist/index.mjs ADDED
@@ -0,0 +1,227 @@
1
+ import { a as WRAP_STYLES, i as VIRTUAL_PREFIX, n as REGISTRY_MODULE_ID, o as generatePreviewModuleCode, r as STANDALONE_CLIENT_MODULE_ID, s as generateStandaloneHtml, t as ALIGN_STYLES } from "./preview-module-FAlHEUCG.mjs";
2
+ import { createHash } from "node:crypto";
3
+ import { resolve } from "node:path";
4
+ import { transformWithEsbuild } from "vite";
5
+ //#region src/preview-utils.ts
6
+ function simpleHash(str) {
7
+ return createHash("md5").update(str).digest("hex").slice(0, 8);
8
+ }
9
+ /**
10
+ * Parse key="value" pairs and boolean flags from a meta string.
11
+ */
12
+ function parseMeta(meta) {
13
+ const result = {};
14
+ const kvRe = /(\w+)="([^"]*)"/g;
15
+ let m;
16
+ while ((m = kvRe.exec(meta)) !== null) result[m[1]] = m[2];
17
+ const remaining = meta.replace(kvRe, "");
18
+ const flagRe = /\b(\w+)\b/g;
19
+ while ((m = flagRe.exec(remaining)) !== null) if (!(m[1] in result)) result[m[1]] = "true";
20
+ return result;
21
+ }
22
+ /**
23
+ * Resolve a CSS path for Vite consumption.
24
+ *
25
+ * - Bare package specifiers (e.g. "@foo/bar/styles", "some-pkg/styles.css")
26
+ * are returned as-is so Vite resolves them from node_modules.
27
+ * - Relative or absolute paths are resolved against `hostRoot`.
28
+ */
29
+ function resolveCssImportPath(css, hostRoot) {
30
+ if (css.startsWith("@") || !css.startsWith(".") && !css.startsWith("/")) return css;
31
+ if (hostRoot) return resolve(hostRoot, css);
32
+ return css;
33
+ }
34
+ //#endregion
35
+ //#region src/preview-plugin.ts
36
+ const RESOLVED_REGISTRY_ID = "\0" + REGISTRY_MODULE_ID;
37
+ const RESOLVED_STANDALONE_CLIENT_ID = "\0" + STANDALONE_CLIENT_MODULE_ID;
38
+ /**
39
+ * Create the common preview hook functions for virtual module resolution,
40
+ * code generation, and JSX compilation.
41
+ *
42
+ * Shared between the standalone CLI previewer and framework-specific plugins.
43
+ */
44
+ function createPreviewHooks(options) {
45
+ const { blockRegistry, cssImport } = options;
46
+ function resolveId(id, importer) {
47
+ if (importer?.startsWith("\0virtual:mrp-preview-")) {
48
+ const blockId = importer.slice(("\0" + VIRTUAL_PREFIX).length).replace(/\.tsx$/, "");
49
+ const entry = blockRegistry.get(blockId);
50
+ if (entry?.sourceFile) return this.resolve(id, entry.sourceFile, { skipSelf: true });
51
+ }
52
+ const cleanId = id.startsWith("/") ? id.slice(1) : id;
53
+ if (cleanId === "virtual:mrp-preview-registry") return RESOLVED_REGISTRY_ID;
54
+ if (cleanId === "virtual:mrp-standalone-client") return RESOLVED_STANDALONE_CLIENT_ID;
55
+ if (cleanId.startsWith("virtual:mrp-preview-")) return "\0" + cleanId + ".tsx";
56
+ return null;
57
+ }
58
+ function load(id) {
59
+ if (id === RESOLVED_REGISTRY_ID) return `export const registry = {\n${[...blockRegistry.keys()].map((blockId) => ` ${JSON.stringify(blockId)}: () => import(${JSON.stringify(VIRTUAL_PREFIX + blockId)})`).join(",\n")}\n};\n`;
60
+ if (id === RESOLVED_STANDALONE_CLIENT_ID) return generateStandaloneClientCode();
61
+ if (!id.startsWith("\0virtual:mrp-preview-")) return;
62
+ const blockId = id.slice(("\0" + VIRTUAL_PREFIX).length).replace(/\.tsx$/, "");
63
+ const entry = blockRegistry.get(blockId);
64
+ if (!entry) return;
65
+ return generatePreviewModuleCode(blockId, entry, cssImport);
66
+ }
67
+ async function transform(code, id) {
68
+ if (id === RESOLVED_REGISTRY_ID || id === RESOLVED_STANDALONE_CLIENT_ID) return;
69
+ if (id.startsWith("\0virtual:mrp-preview-")) return transformWithEsbuild(code, id.slice(1), { jsx: "automatic" });
70
+ }
71
+ return {
72
+ resolveId,
73
+ load,
74
+ transform
75
+ };
76
+ }
77
+ function generateStandaloneClientCode() {
78
+ return `\
79
+ import { createElement } from "react";
80
+ import { createRoot } from "react-dom/client";
81
+ import { registry } from ${JSON.stringify(REGISTRY_MODULE_ID)};
82
+
83
+ var WRAP_STYLES = ${JSON.stringify(WRAP_STYLES)};
84
+ var ALIGN_STYLES = ${JSON.stringify(ALIGN_STYLES)};
85
+
86
+ var blockId = location.pathname.split("/").pop().replace(/\\.html$/, "");
87
+ var params = new URLSearchParams(location.search);
88
+
89
+ var themeParam = params.get("theme");
90
+ function applyTheme(theme) {
91
+ document.documentElement.setAttribute("data-theme", theme);
92
+ document.documentElement.classList.remove("light", "dark");
93
+ document.documentElement.classList.add(theme);
94
+ document.documentElement.style.colorScheme = theme;
95
+ var bg = theme === "dark" ? "#1a1a1a" : "#fff";
96
+ document.documentElement.style.backgroundColor = bg;
97
+ document.body.style.backgroundColor = bg;
98
+ }
99
+ if (themeParam === "dark" || themeParam === "light") {
100
+ applyTheme(themeParam);
101
+ }
102
+ window.addEventListener("message", function(e) {
103
+ if (e.data && e.data.type === "mrp-theme" && (e.data.theme === "dark" || e.data.theme === "light")) {
104
+ applyTheme(e.data.theme);
105
+ }
106
+ });
107
+
108
+ var wrapParam = params.get("wrap");
109
+ var alignParam = params.get("align");
110
+ var root = document.getElementById("root");
111
+ if (wrapParam && WRAP_STYLES[wrapParam]) root.style.cssText += ";" + WRAP_STYLES[wrapParam];
112
+ if (alignParam && ALIGN_STYLES[alignParam]) root.style.cssText += ";" + ALIGN_STYLES[alignParam];
113
+
114
+ registry[blockId]().then(function(mod) {
115
+ if (mod.css) {
116
+ var style = document.createElement("style");
117
+ style.textContent = mod.css;
118
+ document.head.appendChild(style);
119
+ }
120
+ createRoot(root).render(createElement(mod.default));
121
+
122
+ new ResizeObserver(function() {
123
+ window.parent.postMessage(
124
+ { type: "mrp-resize", blockId: blockId, height: root.scrollHeight },
125
+ "*"
126
+ );
127
+ }).observe(root);
128
+ });
129
+ `;
130
+ }
131
+ /**
132
+ * Create a complete preview Vite plugin from the common hooks.
133
+ * Suitable for consumers that don't need to extend the plugin (e.g. VitePress).
134
+ */
135
+ function createBasePreviewPlugin(name, options) {
136
+ const hooks = createPreviewHooks(options);
137
+ let isSsrBuild = false;
138
+ return [{
139
+ name,
140
+ enforce: "pre",
141
+ configResolved(config) {
142
+ isSsrBuild = !!config.build?.ssr;
143
+ },
144
+ resolveId: hooks.resolveId,
145
+ load(id) {
146
+ if (isSsrBuild) {
147
+ if (id === RESOLVED_REGISTRY_ID) return `export const registry = {};`;
148
+ if (id.startsWith("\0virtual:mrp-preview-")) return `export const css = ""; export default function Preview() { return null; }`;
149
+ }
150
+ return hooks.load(id);
151
+ },
152
+ transform: hooks.transform,
153
+ configureServer(server) {
154
+ if (options.skipStandaloneServer) return;
155
+ const standaloneHtml = generateStandaloneHtml(STANDALONE_CLIENT_MODULE_ID);
156
+ server.middlewares.use((req, res, next) => {
157
+ if (!req.url?.match(/^\/__preview\/([a-f0-9]+)(\?.*)?$/)) return next();
158
+ const registryMod = server.moduleGraph.getModuleById(RESOLVED_REGISTRY_ID);
159
+ if (registryMod) server.moduleGraph.invalidateModule(registryMod);
160
+ server.transformIndexHtml(req.url, standaloneHtml).then((transformed) => {
161
+ res.setHeader("Content-Type", "text/html");
162
+ res.end(transformed);
163
+ }).catch(next);
164
+ });
165
+ },
166
+ config() {
167
+ return {
168
+ resolve: { dedupe: ["react", "react-dom"] },
169
+ build: { rollupOptions: { onwarn(warning, defaultHandler) {
170
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes("\"use client\"")) return;
171
+ defaultHandler(warning);
172
+ } } },
173
+ ssr: { external: [
174
+ "react",
175
+ "react-dom",
176
+ "react-dom/client",
177
+ "react/jsx-runtime"
178
+ ] }
179
+ };
180
+ }
181
+ }];
182
+ }
183
+ /**
184
+ * Vite plugin that generates standalone preview HTML pages during build.
185
+ *
186
+ * Each preview block gets its own HTML page at `__preview/{blockId}.html`.
187
+ * Use alongside `createBasePreviewPlugin` for consumers like VitePress that
188
+ * need static preview pages in production.
189
+ */
190
+ function createPreviewBuildPlugin(options) {
191
+ const { blockRegistry } = options;
192
+ let resolvedConfig;
193
+ let isSsrBuild = false;
194
+ let standaloneChunkRefId;
195
+ return {
196
+ name: "mrp-preview-build",
197
+ configResolved(config) {
198
+ resolvedConfig = config;
199
+ isSsrBuild = !!config.build?.ssr;
200
+ },
201
+ async buildStart() {
202
+ if (isSsrBuild || resolvedConfig.command === "serve") return;
203
+ if (options.scanBlocks) await options.scanBlocks(resolvedConfig.root);
204
+ standaloneChunkRefId = this.emitFile({
205
+ type: "chunk",
206
+ id: STANDALONE_CLIENT_MODULE_ID,
207
+ name: "mrp-standalone-preview"
208
+ });
209
+ },
210
+ async generateBundle(_options, _bundle) {
211
+ if (isSsrBuild || !standaloneChunkRefId) return;
212
+ const blockIds = [...blockRegistry.keys()];
213
+ if (blockIds.length === 0) return;
214
+ const standaloneFileName = this.getFileName(standaloneChunkRefId);
215
+ for (const blockId of blockIds) {
216
+ const html = generateStandaloneHtml(standaloneFileName);
217
+ this.emitFile({
218
+ type: "asset",
219
+ fileName: `__preview/${blockId}.html`,
220
+ source: html
221
+ });
222
+ }
223
+ }
224
+ };
225
+ }
226
+ //#endregion
227
+ export { ALIGN_STYLES, REGISTRY_MODULE_ID, STANDALONE_CLIENT_MODULE_ID, VIRTUAL_PREFIX, WRAP_STYLES, createBasePreviewPlugin, createPreviewBuildPlugin, createPreviewHooks, generatePreviewModuleCode, generateStandaloneHtml, parseMeta, resolveCssImportPath, simpleHash };
@@ -0,0 +1,46 @@
1
+ //#region src/preview-utils.d.ts
2
+ interface PreviewBlockEntry {
3
+ code: string;
4
+ sourceFile: string;
5
+ wrap?: string;
6
+ height?: string;
7
+ standalone?: boolean;
8
+ }
9
+ declare function simpleHash(str: string): string;
10
+ /**
11
+ * Parse key="value" pairs and boolean flags from a meta string.
12
+ */
13
+ declare function parseMeta(meta: string): Record<string, string>;
14
+ /**
15
+ * Resolve a CSS path for Vite consumption.
16
+ *
17
+ * - Bare package specifiers (e.g. "@foo/bar/styles", "some-pkg/styles.css")
18
+ * are returned as-is so Vite resolves them from node_modules.
19
+ * - Relative or absolute paths are resolved against `hostRoot`.
20
+ */
21
+ declare function resolveCssImportPath(css: string, hostRoot?: string): string;
22
+ //#endregion
23
+ //#region src/preview-module.d.ts
24
+ declare const VIRTUAL_PREFIX = "virtual:mrp-preview-";
25
+ declare const REGISTRY_MODULE_ID = "virtual:mrp-preview-registry";
26
+ declare const STANDALONE_CLIENT_MODULE_ID = "virtual:mrp-standalone-client";
27
+ declare const WRAP_STYLES: Record<string, string>;
28
+ declare const ALIGN_STYLES: Record<string, string>;
29
+ /**
30
+ * Generate the standalone preview HTML page shell.
31
+ *
32
+ * Used by both dev-server middleware and production build to produce the
33
+ * `__preview/:blockId` pages. The `scriptSrc` parameter is the path to the
34
+ * standalone client entry (virtual module ID in dev, hashed filename in build).
35
+ */
36
+ declare function generateStandaloneHtml(scriptSrc: string): string;
37
+ /**
38
+ * Generate the virtual module code for a preview block.
39
+ *
40
+ * The module exports a React component as default and the CSS string as `css`.
41
+ * The host component (e.g. PreviewBlock) is responsible for mounting the
42
+ * component into an iframe container and injecting the CSS.
43
+ */
44
+ declare function generatePreviewModuleCode(_blockId: string, entry: PreviewBlockEntry, cssImport?: string): string;
45
+ //#endregion
46
+ export { WRAP_STYLES as a, PreviewBlockEntry as c, simpleHash as d, VIRTUAL_PREFIX as i, parseMeta as l, REGISTRY_MODULE_ID as n, generatePreviewModuleCode as o, STANDALONE_CLIENT_MODULE_ID as r, generateStandaloneHtml as s, ALIGN_STYLES as t, resolveCssImportPath as u };
@@ -0,0 +1,61 @@
1
+ //#region src/preview-module.ts
2
+ const VIRTUAL_PREFIX = "virtual:mrp-preview-";
3
+ const REGISTRY_MODULE_ID = "virtual:mrp-preview-registry";
4
+ const STANDALONE_CLIENT_MODULE_ID = "virtual:mrp-standalone-client";
5
+ const WRAP_STYLES = {
6
+ row: "flex-wrap:wrap;gap:8px",
7
+ column: "flex-direction:column;gap:8px"
8
+ };
9
+ const ALIGN_STYLES = {
10
+ center: "justify-content:center;align-items:center",
11
+ start: "justify-content:center;align-items:flex-start",
12
+ end: "justify-content:center;align-items:flex-end"
13
+ };
14
+ /**
15
+ * Generate the standalone preview HTML page shell.
16
+ *
17
+ * Used by both dev-server middleware and production build to produce the
18
+ * `__preview/:blockId` pages. The `scriptSrc` parameter is the path to the
19
+ * standalone client entry (virtual module ID in dev, hashed filename in build).
20
+ */
21
+ function generateStandaloneHtml(scriptSrc) {
22
+ return `<!doctype html>
23
+ <html lang="en">
24
+ <head>
25
+ <meta charset="UTF-8" />
26
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
27
+ <title>Preview</title>
28
+ <style>html,body{background-color:#fff}html.dark,html.dark body{background-color:#1a1a1a}</style>
29
+ <script>!function(){var t=new URLSearchParams(location.search).get("theme");if(t==="dark"||t==="light"){document.documentElement.style.colorScheme=t;document.documentElement.setAttribute("data-theme",t);document.documentElement.classList.add(t)}}()<\/script>
30
+ </head>
31
+ <body style="margin:0">
32
+ <div id="root" style="display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px"></div>
33
+ <script type="module" src="/${scriptSrc}"><\/script>
34
+ </body>
35
+ </html>`;
36
+ }
37
+ /**
38
+ * Generate the virtual module code for a preview block.
39
+ *
40
+ * The module exports a React component as default and the CSS string as `css`.
41
+ * The host component (e.g. PreviewBlock) is responsible for mounting the
42
+ * component into an iframe container and injecting the CSS.
43
+ */
44
+ function generatePreviewModuleCode(_blockId, entry, cssImport) {
45
+ const lines = entry.code.split("\n");
46
+ const blockImports = [];
47
+ const bodyLines = [];
48
+ for (const line of lines) if (line.trimStart().startsWith("import ")) blockImports.push(line);
49
+ else bodyLines.push(line);
50
+ const body = bodyLines.join("\n").trim();
51
+ const cssLines = [];
52
+ if (cssImport) cssLines.push(`import __mrp_css from ${JSON.stringify(cssImport + "?inline")};`, `export const css = __mrp_css;`);
53
+ else cssLines.push(`export const css = "";`);
54
+ return [
55
+ ...cssLines,
56
+ ...blockImports,
57
+ `export default function Preview() { return <>${body}</>; }`
58
+ ].join("\n");
59
+ }
60
+ //#endregion
61
+ export { WRAP_STYLES as a, VIRTUAL_PREFIX as i, REGISTRY_MODULE_ID as n, generatePreviewModuleCode as o, STANDALONE_CLIENT_MODULE_ID as r, generateStandaloneHtml as s, ALIGN_STYLES as t };
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@izumisy/vite-plugin-react-preview",
3
+ "version": "0.1.0",
4
+ "description": "Vite plugin for rendering React component previews in iframe",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.mts",
9
+ "default": "./dist/index.mjs"
10
+ },
11
+ "./dom": {
12
+ "types": "./dist/dom.d.mts",
13
+ "default": "./dist/dom.mjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist/**"
18
+ ],
19
+ "keywords": [],
20
+ "author": "",
21
+ "license": "ISC",
22
+ "dependencies": {
23
+ "vite": "^6.3.5"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22",
27
+ "tsdown": "^0.21.3",
28
+ "typescript": "~5.9.3",
29
+ "vitest": "^4.1.2"
30
+ },
31
+ "scripts": {
32
+ "dev": "tsdown --watch",
33
+ "build": "tsdown",
34
+ "type-check": "tsc --incremental",
35
+ "test": "vitest run"
36
+ }
37
+ }