@firebuzz/design-mode 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/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # @firebuzz/design-mode
2
+
3
+ Design mode overlay and utilities for Firebuzz landing page templates. Enables visual editing of React components in development mode using React Fiber's `_debugSource`.
4
+
5
+ ## Features
6
+
7
+ - **Visual Element Selection**: Click to select any element in your template
8
+ - **Runtime Element Tracking**: Uses React Fiber internals (no build-time modifications needed)
9
+ - **Client-side Tailwind Generation**: Generates CSS for Tailwind classes at runtime
10
+ - **Theme Customization**: Live preview of theme changes (colors, fonts, etc.)
11
+ - **Element Editing**: Edit className, text content, images, and links
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @firebuzz/design-mode
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### 1. Add the Vite Plugin
22
+
23
+ In your `vite.config.ts`:
24
+
25
+ ```typescript
26
+ import { defineConfig } from "vite";
27
+ import react from "@vitejs/plugin-react-swc";
28
+ import { firebuzzDesignMode } from "@firebuzz/design-mode";
29
+
30
+ export default defineConfig({
31
+ plugins: [
32
+ react(),
33
+ firebuzzDesignMode(),
34
+ ],
35
+ });
36
+ ```
37
+
38
+ ### 2. Create Design Mode Directory
39
+
40
+ The plugin expects a Tailwind config JSON to be generated at `./src/design-mode/tailwind.config.json`. The directory will be created automatically.
41
+
42
+ ### 3. Enable Design Mode
43
+
44
+ Design mode is automatically enabled in development (`NODE_ENV=development`). To disable it, set:
45
+
46
+ ```bash
47
+ VITE_DESIGN_MODE=false
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ The plugin accepts optional configuration:
53
+
54
+ ```typescript
55
+ firebuzzDesignMode({
56
+ // Path to your tailwind.config.js (default: "./tailwind.config.js")
57
+ tailwindConfigPath: "./tailwind.config.js",
58
+
59
+ // Output path for generated JSON (default: "./src/design-mode/tailwind.config.json")
60
+ outputPath: "./src/design-mode/tailwind.config.json",
61
+
62
+ // Custom overlay script path (default: uses package's overlay)
63
+ overlayPath: "@firebuzz/design-mode/overlay",
64
+ })
65
+ ```
66
+
67
+ ## How It Works
68
+
69
+ 1. **Vite Plugin**: Generates Tailwind config JSON at build time and injects the overlay script
70
+ 2. **Overlay Script**: Listens for postMessage events from parent window and enables element selection
71
+ 3. **Tailwind Generator**: Generates CSS for Tailwind classes at runtime as they're applied
72
+ 4. **React Fiber Integration**: Tracks element source locations using React's internal `_debugSource` property
73
+
74
+ ## Message Protocol
75
+
76
+ The overlay communicates with the parent window using postMessage:
77
+
78
+ ### Messages to Overlay (from parent)
79
+
80
+ - `ENABLE_DESIGN_MODE`: Enable design mode overlay
81
+ - `DISABLE_DESIGN_MODE`: Disable design mode overlay
82
+ - `FB_SELECT_ELEMENT`: Programmatically select an element
83
+ - `FB_DESELECT_ELEMENT`: Deselect the current element
84
+ - `FB_UPDATE_ELEMENT`: Update element properties (className, textContent, etc.)
85
+ - `FB_UPDATE_THEME`: Update theme CSS variables
86
+ - `FB_GET_ALL_ELEMENTS_STATE`: Request current state of all elements
87
+
88
+ ### Messages from Overlay (to parent)
89
+
90
+ - `FB_ELEMENT_SELECTED`: User selected an element (includes source location and properties)
91
+ - `FB_ALL_ELEMENTS_STATE`: Response with all elements' current state
92
+
93
+ ## TypeScript
94
+
95
+ The package includes full TypeScript definitions:
96
+
97
+ ```typescript
98
+ import type {
99
+ DesignModeMessage,
100
+ ElementSelectedMessage,
101
+ ElementData,
102
+ } from "@firebuzz/design-mode";
103
+ ```
104
+
105
+ ## Development
106
+
107
+ This package is part of the Firebuzz monorepo and is designed to work with Vite-based React templates.
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,87 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /**
4
+ * Vite plugin that enables design mode features for Firebuzz templates
5
+ * - Generates Tailwind config JSON for client-side use
6
+ * - Injects overlay script for element selection
7
+ *
8
+ * NOTE: This plugin does NOT modify JSX/TSX files.
9
+ * Element tracking is done at runtime using React Fiber's _debugSource.
10
+ *
11
+ * @param options - Plugin configuration options
12
+ * @param options.tailwindConfigPath - Path to tailwind.config.js (default: "./tailwind.config.js")
13
+ * @param options.outputPath - Path to output tailwind.config.json (default: "./src/design-mode/tailwind.config.json")
14
+ * @param options.overlayPath - Path to overlay script (default: uses package's overlay.ts)
15
+ */
16
+ declare function firebuzzDesignMode(options?: {
17
+ tailwindConfigPath?: string;
18
+ outputPath?: string;
19
+ overlayPath?: string;
20
+ }): Plugin;
21
+
22
+ /**
23
+ * TypeScript type definitions for Firebuzz Design Mode
24
+ */
25
+ interface SourceLocation {
26
+ fileName: string;
27
+ lineNumber: number;
28
+ columnNumber: number;
29
+ }
30
+ interface ElementUpdates {
31
+ className?: string;
32
+ textContent?: string;
33
+ src?: string;
34
+ alt?: string;
35
+ href?: string;
36
+ target?: string;
37
+ rel?: string;
38
+ }
39
+ interface ThemeVariables {
40
+ lightVariables?: Record<string, string>;
41
+ darkVariables?: Record<string, string>;
42
+ }
43
+ interface DesignModeMessage {
44
+ type: "ENABLE_DESIGN_MODE" | "DISABLE_DESIGN_MODE" | "FB_UPDATE_ELEMENT" | "FB_GET_ALL_ELEMENTS_STATE" | "FB_UPDATE_THEME" | "FB_DESELECT_ELEMENT" | "FB_SELECT_ELEMENT";
45
+ enabled?: boolean;
46
+ sourceFile?: string;
47
+ sourceLine?: number;
48
+ sourceColumn?: number;
49
+ updates?: ElementUpdates;
50
+ theme?: ThemeVariables;
51
+ }
52
+ interface ElementData {
53
+ sourceFile: string;
54
+ sourceLine: number;
55
+ sourceColumn: number;
56
+ tagName: string;
57
+ className: string;
58
+ textContent: string | null;
59
+ src?: string;
60
+ alt?: string;
61
+ href?: string;
62
+ target?: string;
63
+ rel?: string;
64
+ computedStyles: Record<string, string>;
65
+ }
66
+ interface ElementSelectedMessage {
67
+ type: "FB_ELEMENT_SELECTED";
68
+ data: ElementData;
69
+ }
70
+ interface AllElementsStateMessage {
71
+ type: "FB_ALL_ELEMENTS_STATE";
72
+ data: Array<{
73
+ sourceFile: string;
74
+ sourceLine: number;
75
+ sourceColumn: number;
76
+ className: string;
77
+ textContent: string | null;
78
+ src?: string;
79
+ alt?: string;
80
+ href?: string;
81
+ target?: string;
82
+ rel?: string;
83
+ }>;
84
+ }
85
+ type DesignModeMessageType = DesignModeMessage | ElementSelectedMessage | AllElementsStateMessage;
86
+
87
+ export { type AllElementsStateMessage, type DesignModeMessage, type DesignModeMessageType, type ElementData, type ElementSelectedMessage, type ElementUpdates, type SourceLocation, type ThemeVariables, firebuzzDesignMode };
@@ -0,0 +1,87 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /**
4
+ * Vite plugin that enables design mode features for Firebuzz templates
5
+ * - Generates Tailwind config JSON for client-side use
6
+ * - Injects overlay script for element selection
7
+ *
8
+ * NOTE: This plugin does NOT modify JSX/TSX files.
9
+ * Element tracking is done at runtime using React Fiber's _debugSource.
10
+ *
11
+ * @param options - Plugin configuration options
12
+ * @param options.tailwindConfigPath - Path to tailwind.config.js (default: "./tailwind.config.js")
13
+ * @param options.outputPath - Path to output tailwind.config.json (default: "./src/design-mode/tailwind.config.json")
14
+ * @param options.overlayPath - Path to overlay script (default: uses package's overlay.ts)
15
+ */
16
+ declare function firebuzzDesignMode(options?: {
17
+ tailwindConfigPath?: string;
18
+ outputPath?: string;
19
+ overlayPath?: string;
20
+ }): Plugin;
21
+
22
+ /**
23
+ * TypeScript type definitions for Firebuzz Design Mode
24
+ */
25
+ interface SourceLocation {
26
+ fileName: string;
27
+ lineNumber: number;
28
+ columnNumber: number;
29
+ }
30
+ interface ElementUpdates {
31
+ className?: string;
32
+ textContent?: string;
33
+ src?: string;
34
+ alt?: string;
35
+ href?: string;
36
+ target?: string;
37
+ rel?: string;
38
+ }
39
+ interface ThemeVariables {
40
+ lightVariables?: Record<string, string>;
41
+ darkVariables?: Record<string, string>;
42
+ }
43
+ interface DesignModeMessage {
44
+ type: "ENABLE_DESIGN_MODE" | "DISABLE_DESIGN_MODE" | "FB_UPDATE_ELEMENT" | "FB_GET_ALL_ELEMENTS_STATE" | "FB_UPDATE_THEME" | "FB_DESELECT_ELEMENT" | "FB_SELECT_ELEMENT";
45
+ enabled?: boolean;
46
+ sourceFile?: string;
47
+ sourceLine?: number;
48
+ sourceColumn?: number;
49
+ updates?: ElementUpdates;
50
+ theme?: ThemeVariables;
51
+ }
52
+ interface ElementData {
53
+ sourceFile: string;
54
+ sourceLine: number;
55
+ sourceColumn: number;
56
+ tagName: string;
57
+ className: string;
58
+ textContent: string | null;
59
+ src?: string;
60
+ alt?: string;
61
+ href?: string;
62
+ target?: string;
63
+ rel?: string;
64
+ computedStyles: Record<string, string>;
65
+ }
66
+ interface ElementSelectedMessage {
67
+ type: "FB_ELEMENT_SELECTED";
68
+ data: ElementData;
69
+ }
70
+ interface AllElementsStateMessage {
71
+ type: "FB_ALL_ELEMENTS_STATE";
72
+ data: Array<{
73
+ sourceFile: string;
74
+ sourceLine: number;
75
+ sourceColumn: number;
76
+ className: string;
77
+ textContent: string | null;
78
+ src?: string;
79
+ alt?: string;
80
+ href?: string;
81
+ target?: string;
82
+ rel?: string;
83
+ }>;
84
+ }
85
+ type DesignModeMessageType = DesignModeMessage | ElementSelectedMessage | AllElementsStateMessage;
86
+
87
+ export { type AllElementsStateMessage, type DesignModeMessage, type DesignModeMessageType, type ElementData, type ElementSelectedMessage, type ElementUpdates, type SourceLocation, type ThemeVariables, firebuzzDesignMode };
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ 'use strict';var d=require('fs/promises'),i=require('path'),f=require('esbuild');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var d__default=/*#__PURE__*/_interopDefault(d);var i__default=/*#__PURE__*/_interopDefault(i);var f__namespace=/*#__PURE__*/_interopNamespace(f);function m(n){let r=process.env.NODE_ENV==="development"&&process.env.VITE_DESIGN_MODE!=="false",o=process.cwd(),a=i__default.default.resolve(o,n?.tailwindConfigPath||"./tailwind.config.js"),s=i__default.default.resolve(o,n?.outputPath||"./src/design-mode/tailwind.config.json"),l=i__default.default.resolve(o,"./.fb-tailwind.config.js");n?.overlayPath||"@firebuzz/design-mode/overlay";return {name:"vite-plugin-firebuzz-design-mode",enforce:"pre",async buildStart(){if(r)try{await u();}catch(e){console.warn("[Firebuzz Design Mode] Could not generate Tailwind config:",e);}},configureServer(e){if(r)try{e.watcher.add(a),e.watcher.on("change",async t=>{i__default.default.normalize(t)===i__default.default.normalize(a)&&await u();});}catch(t){console.warn("[Firebuzz Design Mode] Could not watch Tailwind config:",t);}},transformIndexHtml(e){if(!r)return e;let c=`
2
+ <script type="module">
3
+ // Load Tailwind config and make it available globally
4
+ const response = await fetch('${s.replace(o,"")}');
5
+ const config = await response.json();
6
+ window.__FIREBUZZ_TAILWIND_CONFIG__ = config;
7
+
8
+ // Then load the overlay
9
+ await import('/@fs${i__default.default.resolve(__dirname,"overlay.js").replace(/\\/g,"/")}');
10
+ </script>`;return e.replace("</body>",`${c}</body>`)}};async function u(){try{await f__namespace.build({entryPoints:[a],outfile:l,bundle:!0,format:"esm",banner:{js:'import { createRequire } from "module"; const require = createRequire(import.meta.url);'}});let e=await import(`${l}?update=${Date.now()}`);if(!e?.default)throw new Error("Invalid Tailwind config structure");let{default:t}=await import('tailwindcss/resolveConfig.js'),c=t(e.default),g=i__default.default.dirname(s);await d__default.default.mkdir(g,{recursive:!0}),await d__default.default.writeFile(s,JSON.stringify(c,null,2)),await d__default.default.unlink(l).catch(()=>{}),console.log("[Firebuzz Design Mode] Generated Tailwind config JSON successfully");}catch(e){throw console.error("[Firebuzz Design Mode] Error generating config:",e),e}}}exports.firebuzzDesignMode=m;//# sourceMappingURL=index.js.map
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin.ts"],"names":["firebuzzDesignMode","options","isDesignModeEnabled","projectRoot","tailwindConfigFile","path","tailwindJsonOutfile","tailwindIntermediateFile","generateTailwindConfig","error","server","changedPath","html","scripts","f","userConfig","resolveConfig","resolvedConfig","outputDir","fs"],"mappings":"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","file":"index.js","sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport * as esbuild from \"esbuild\";\nimport type { Plugin } from \"vite\";\n\n/**\n * Vite plugin that enables design mode features for Firebuzz templates\n * - Generates Tailwind config JSON for client-side use\n * - Injects overlay script for element selection\n *\n * NOTE: This plugin does NOT modify JSX/TSX files.\n * Element tracking is done at runtime using React Fiber's _debugSource.\n *\n * @param options - Plugin configuration options\n * @param options.tailwindConfigPath - Path to tailwind.config.js (default: \"./tailwind.config.js\")\n * @param options.outputPath - Path to output tailwind.config.json (default: \"./src/design-mode/tailwind.config.json\")\n * @param options.overlayPath - Path to overlay script (default: uses package's overlay.ts)\n */\nexport function firebuzzDesignMode(options?: {\n\ttailwindConfigPath?: string;\n\toutputPath?: string;\n\toverlayPath?: string;\n}): Plugin {\n\t// Only enable in development, never in production builds\n\tconst isDesignModeEnabled =\n\t\tprocess.env.NODE_ENV === \"development\" &&\n\t\tprocess.env.VITE_DESIGN_MODE !== \"false\";\n\n\tconst projectRoot = process.cwd();\n\tconst tailwindConfigFile = path.resolve(\n\t\tprojectRoot,\n\t\toptions?.tailwindConfigPath || \"./tailwind.config.js\",\n\t);\n\tconst tailwindJsonOutfile = path.resolve(\n\t\tprojectRoot,\n\t\toptions?.outputPath || \"./src/design-mode/tailwind.config.json\",\n\t);\n\tconst tailwindIntermediateFile = path.resolve(\n\t\tprojectRoot,\n\t\t\"./.fb-tailwind.config.js\",\n\t);\n\n\t// Use package's overlay script if not specified\n\tconst overlayScriptPath = options?.overlayPath || \"@firebuzz/design-mode/overlay\";\n\n\treturn {\n\t\tname: \"vite-plugin-firebuzz-design-mode\",\n\t\tenforce: \"pre\",\n\n\t\tasync buildStart() {\n\t\t\tif (!isDesignModeEnabled) return;\n\n\t\t\t// Generate Tailwind config JSON for client-side use\n\t\t\ttry {\n\t\t\t\tawait generateTailwindConfig();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[Firebuzz Design Mode] Could not generate Tailwind config:\",\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tconfigureServer(server) {\n\t\t\tif (!isDesignModeEnabled) return;\n\n\t\t\t// Watch Tailwind config for changes\n\t\t\ttry {\n\t\t\t\tserver.watcher.add(tailwindConfigFile);\n\t\t\t\tserver.watcher.on(\"change\", async (changedPath) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tpath.normalize(changedPath) === path.normalize(tailwindConfigFile)\n\t\t\t\t\t) {\n\t\t\t\t\t\tawait generateTailwindConfig();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[Firebuzz Design Mode] Could not watch Tailwind config:\",\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\ttransformIndexHtml(html) {\n\t\t\tif (!isDesignModeEnabled) return html;\n\n\t\t\t// Inject Tailwind config as a global variable first\n\t\t\tconst configJsonPath = tailwindJsonOutfile.replace(projectRoot, \"\");\n\n\t\t\t// Inject the overlay script before </body>\n\t\t\tconst scripts = `\n<script type=\"module\">\n\t// Load Tailwind config and make it available globally\n\tconst response = await fetch('${configJsonPath}');\n\tconst config = await response.json();\n\twindow.__FIREBUZZ_TAILWIND_CONFIG__ = config;\n\n\t// Then load the overlay\n\tawait import('/@fs${path.resolve(__dirname, 'overlay.js').replace(/\\\\/g, '/')}');\n</script>`;\n\n\t\t\treturn html.replace(\"</body>\", `${scripts}</body>`);\n\t\t},\n\t};\n\n\tasync function generateTailwindConfig() {\n\t\ttry {\n\t\t\t// Bundle Tailwind config using esbuild\n\t\t\tawait esbuild.build({\n\t\t\t\tentryPoints: [tailwindConfigFile],\n\t\t\t\toutfile: tailwindIntermediateFile,\n\t\t\t\tbundle: true,\n\t\t\t\tformat: \"esm\",\n\t\t\t\tbanner: {\n\t\t\t\t\tjs: 'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);',\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Import and resolve the config\n\t\t\tconst userConfig = await import(\n\t\t\t\t`${tailwindIntermediateFile}?update=${Date.now()}`\n\t\t\t);\n\n\t\t\tif (!userConfig?.default) {\n\t\t\t\tthrow new Error(\"Invalid Tailwind config structure\");\n\t\t\t}\n\n\t\t\t// Dynamically import tailwindcss/resolveConfig\n\t\t\tconst { default: resolveConfig } = await import(\n\t\t\t\t\"tailwindcss/resolveConfig.js\"\n\t\t\t);\n\t\t\tconst resolvedConfig = resolveConfig(userConfig.default);\n\n\t\t\t// Ensure output directory exists\n\t\t\tconst outputDir = path.dirname(tailwindJsonOutfile);\n\t\t\tawait fs.mkdir(outputDir, { recursive: true });\n\n\t\t\t// Write resolved config to JSON\n\t\t\tawait fs.writeFile(\n\t\t\t\ttailwindJsonOutfile,\n\t\t\t\tJSON.stringify(resolvedConfig, null, 2),\n\t\t\t);\n\n\t\t\t// Clean up intermediate file\n\t\t\tawait fs.unlink(tailwindIntermediateFile).catch(() => {});\n\n\t\t\tconsole.log(\n\t\t\t\t\"[Firebuzz Design Mode] Generated Tailwind config JSON successfully\",\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Firebuzz Design Mode] Error generating config:\", error);\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,11 @@
1
+ import d from'fs/promises';import i from'path';import*as f from'esbuild';function m(n){let r=process.env.NODE_ENV==="development"&&process.env.VITE_DESIGN_MODE!=="false",o=process.cwd(),a=i.resolve(o,n?.tailwindConfigPath||"./tailwind.config.js"),s=i.resolve(o,n?.outputPath||"./src/design-mode/tailwind.config.json"),l=i.resolve(o,"./.fb-tailwind.config.js");n?.overlayPath||"@firebuzz/design-mode/overlay";return {name:"vite-plugin-firebuzz-design-mode",enforce:"pre",async buildStart(){if(r)try{await u();}catch(e){console.warn("[Firebuzz Design Mode] Could not generate Tailwind config:",e);}},configureServer(e){if(r)try{e.watcher.add(a),e.watcher.on("change",async t=>{i.normalize(t)===i.normalize(a)&&await u();});}catch(t){console.warn("[Firebuzz Design Mode] Could not watch Tailwind config:",t);}},transformIndexHtml(e){if(!r)return e;let c=`
2
+ <script type="module">
3
+ // Load Tailwind config and make it available globally
4
+ const response = await fetch('${s.replace(o,"")}');
5
+ const config = await response.json();
6
+ window.__FIREBUZZ_TAILWIND_CONFIG__ = config;
7
+
8
+ // Then load the overlay
9
+ await import('/@fs${i.resolve(__dirname,"overlay.js").replace(/\\/g,"/")}');
10
+ </script>`;return e.replace("</body>",`${c}</body>`)}};async function u(){try{await f.build({entryPoints:[a],outfile:l,bundle:!0,format:"esm",banner:{js:'import { createRequire } from "module"; const require = createRequire(import.meta.url);'}});let e=await import(`${l}?update=${Date.now()}`);if(!e?.default)throw new Error("Invalid Tailwind config structure");let{default:t}=await import('tailwindcss/resolveConfig.js'),c=t(e.default),g=i.dirname(s);await d.mkdir(g,{recursive:!0}),await d.writeFile(s,JSON.stringify(c,null,2)),await d.unlink(l).catch(()=>{}),console.log("[Firebuzz Design Mode] Generated Tailwind config JSON successfully");}catch(e){throw console.error("[Firebuzz Design Mode] Error generating config:",e),e}}}export{m as firebuzzDesignMode};//# sourceMappingURL=index.mjs.map
11
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin.ts"],"names":["firebuzzDesignMode","options","isDesignModeEnabled","projectRoot","tailwindConfigFile","path","tailwindJsonOutfile","tailwindIntermediateFile","generateTailwindConfig","error","server","changedPath","html","scripts","userConfig","resolveConfig","resolvedConfig","outputDir","fs"],"mappings":"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","file":"index.mjs","sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport * as esbuild from \"esbuild\";\nimport type { Plugin } from \"vite\";\n\n/**\n * Vite plugin that enables design mode features for Firebuzz templates\n * - Generates Tailwind config JSON for client-side use\n * - Injects overlay script for element selection\n *\n * NOTE: This plugin does NOT modify JSX/TSX files.\n * Element tracking is done at runtime using React Fiber's _debugSource.\n *\n * @param options - Plugin configuration options\n * @param options.tailwindConfigPath - Path to tailwind.config.js (default: \"./tailwind.config.js\")\n * @param options.outputPath - Path to output tailwind.config.json (default: \"./src/design-mode/tailwind.config.json\")\n * @param options.overlayPath - Path to overlay script (default: uses package's overlay.ts)\n */\nexport function firebuzzDesignMode(options?: {\n\ttailwindConfigPath?: string;\n\toutputPath?: string;\n\toverlayPath?: string;\n}): Plugin {\n\t// Only enable in development, never in production builds\n\tconst isDesignModeEnabled =\n\t\tprocess.env.NODE_ENV === \"development\" &&\n\t\tprocess.env.VITE_DESIGN_MODE !== \"false\";\n\n\tconst projectRoot = process.cwd();\n\tconst tailwindConfigFile = path.resolve(\n\t\tprojectRoot,\n\t\toptions?.tailwindConfigPath || \"./tailwind.config.js\",\n\t);\n\tconst tailwindJsonOutfile = path.resolve(\n\t\tprojectRoot,\n\t\toptions?.outputPath || \"./src/design-mode/tailwind.config.json\",\n\t);\n\tconst tailwindIntermediateFile = path.resolve(\n\t\tprojectRoot,\n\t\t\"./.fb-tailwind.config.js\",\n\t);\n\n\t// Use package's overlay script if not specified\n\tconst overlayScriptPath = options?.overlayPath || \"@firebuzz/design-mode/overlay\";\n\n\treturn {\n\t\tname: \"vite-plugin-firebuzz-design-mode\",\n\t\tenforce: \"pre\",\n\n\t\tasync buildStart() {\n\t\t\tif (!isDesignModeEnabled) return;\n\n\t\t\t// Generate Tailwind config JSON for client-side use\n\t\t\ttry {\n\t\t\t\tawait generateTailwindConfig();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[Firebuzz Design Mode] Could not generate Tailwind config:\",\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\tconfigureServer(server) {\n\t\t\tif (!isDesignModeEnabled) return;\n\n\t\t\t// Watch Tailwind config for changes\n\t\t\ttry {\n\t\t\t\tserver.watcher.add(tailwindConfigFile);\n\t\t\t\tserver.watcher.on(\"change\", async (changedPath) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\tpath.normalize(changedPath) === path.normalize(tailwindConfigFile)\n\t\t\t\t\t) {\n\t\t\t\t\t\tawait generateTailwindConfig();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[Firebuzz Design Mode] Could not watch Tailwind config:\",\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\ttransformIndexHtml(html) {\n\t\t\tif (!isDesignModeEnabled) return html;\n\n\t\t\t// Inject Tailwind config as a global variable first\n\t\t\tconst configJsonPath = tailwindJsonOutfile.replace(projectRoot, \"\");\n\n\t\t\t// Inject the overlay script before </body>\n\t\t\tconst scripts = `\n<script type=\"module\">\n\t// Load Tailwind config and make it available globally\n\tconst response = await fetch('${configJsonPath}');\n\tconst config = await response.json();\n\twindow.__FIREBUZZ_TAILWIND_CONFIG__ = config;\n\n\t// Then load the overlay\n\tawait import('/@fs${path.resolve(__dirname, 'overlay.js').replace(/\\\\/g, '/')}');\n</script>`;\n\n\t\t\treturn html.replace(\"</body>\", `${scripts}</body>`);\n\t\t},\n\t};\n\n\tasync function generateTailwindConfig() {\n\t\ttry {\n\t\t\t// Bundle Tailwind config using esbuild\n\t\t\tawait esbuild.build({\n\t\t\t\tentryPoints: [tailwindConfigFile],\n\t\t\t\toutfile: tailwindIntermediateFile,\n\t\t\t\tbundle: true,\n\t\t\t\tformat: \"esm\",\n\t\t\t\tbanner: {\n\t\t\t\t\tjs: 'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);',\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Import and resolve the config\n\t\t\tconst userConfig = await import(\n\t\t\t\t`${tailwindIntermediateFile}?update=${Date.now()}`\n\t\t\t);\n\n\t\t\tif (!userConfig?.default) {\n\t\t\t\tthrow new Error(\"Invalid Tailwind config structure\");\n\t\t\t}\n\n\t\t\t// Dynamically import tailwindcss/resolveConfig\n\t\t\tconst { default: resolveConfig } = await import(\n\t\t\t\t\"tailwindcss/resolveConfig.js\"\n\t\t\t);\n\t\t\tconst resolvedConfig = resolveConfig(userConfig.default);\n\n\t\t\t// Ensure output directory exists\n\t\t\tconst outputDir = path.dirname(tailwindJsonOutfile);\n\t\t\tawait fs.mkdir(outputDir, { recursive: true });\n\n\t\t\t// Write resolved config to JSON\n\t\t\tawait fs.writeFile(\n\t\t\t\ttailwindJsonOutfile,\n\t\t\t\tJSON.stringify(resolvedConfig, null, 2),\n\t\t\t);\n\n\t\t\t// Clean up intermediate file\n\t\t\tawait fs.unlink(tailwindIntermediateFile).catch(() => {});\n\n\t\t\tconsole.log(\n\t\t\t\t\"[Firebuzz Design Mode] Generated Tailwind config JSON successfully\",\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Firebuzz Design Mode] Error generating config:\", error);\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@firebuzz/design-mode",
3
+ "version": "0.1.0",
4
+ "description": "Design mode overlay and utilities for Firebuzz landing page templates",
5
+ "author": "Firebuzz Team",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rm -rf dist",
26
+ "prepublishOnly": "pnpm build"
27
+ },
28
+ "peerDependencies": {
29
+ "react": ">=18.0.0",
30
+ "tailwindcss": "^3.0.0",
31
+ "vite": "^6.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/babel__generator": "^7.27.0",
35
+ "@types/node": "^22.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.7.2"
38
+ },
39
+ "dependencies": {
40
+ "esbuild": "^0.25.11"
41
+ },
42
+ "keywords": [
43
+ "design-mode",
44
+ "visual-editor",
45
+ "firebuzz",
46
+ "landing-pages",
47
+ "vite",
48
+ "tailwind"
49
+ ],
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/firebuzz/firebuzz.git",
53
+ "directory": "packages/design-mode"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
58
+ }