Skip to content

Commit 8336f1e

Browse files
committed
Simplify readme
1 parent 123c283 commit 8336f1e

File tree

1 file changed

+33
-76
lines changed

1 file changed

+33
-76
lines changed

Readme.md

Lines changed: 33 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default defineConfig({
2424
plugins: [
2525
chatGPTWidgetPlugin({
2626
widgetsDir: "web/chatgpt-widgets", // default: 'web/chatgpt-widgets'
27-
baseUrl: "https://example.com", // required because the chatgpt iframe is sandboxed and absolute URL links are required
27+
baseUrl: "https://example.com", // if not using a vite `base`, this is required because the chatgpt iframe is sandboxed and absolute URL links are required
2828
}),
2929
],
3030
build: {
@@ -38,10 +38,21 @@ export default defineConfig({
3838
Create React components in your widgets directory:
3939

4040
```tsx
41-
// web/chatgpt-widgets/MyWidget.tsx
42-
export default function MyWidget() {
41+
// in web/chatgpt-widgets/Hello.tsx
42+
export default function Hello() {
4343
return <div>Hello from ChatGPT Widget!</div>;
4444
}
45+
46+
// in web/chatgpt-widgets/ListFoobars.tsx
47+
export default function ListFoobars() {
48+
return (
49+
<ul>
50+
{window.openai.tool_output.foobars.map((foobar) => (
51+
<li>{foobar}</li>
52+
))}
53+
</ul>
54+
);
55+
}
4556
```
4657

4758
#### Optional: Root Layout Component
@@ -52,15 +63,10 @@ You can optionally create a root layout component that will wrap all widgets. If
5263
// web/chatgpt-widgets/root.tsx
5364
export default function RootLayout({ children }: { children: React.ReactNode }) {
5465
return (
55-
<div className="root-layout">
56-
<header>
57-
<h1>Common Header</h1>
58-
</header>
59-
<main>{children}</main>
60-
<footer>
61-
<p>Common Footer</p>
62-
</footer>
63-
</div>
66+
<SomeProvider>
67+
<header>Common Header</header>
68+
{children}
69+
</SomeProvider>
6470
);
6571
}
6672
```
@@ -69,24 +75,29 @@ The root layout component:
6975

7076
- Must accept a `children` prop
7177
- Will automatically wrap every widget component
72-
- Is not exposed as a widget itself
7378
- Is optional - if not present, widgets render without a wrapper
7479

7580
### 3. Serve Widgets in Your Application
7681

77-
#### Development Mode (with Vite Dev Server)
82+
After setting up the plugin and writing some widgets, you need to expose the widget HTML snippets generated by this plugin as MCP server resources. In development, the HTML snippets will be generated by Vite dynamically, and in production, they'll be built by your Vite build process and read off the disk.
7883

7984
```typescript
8085
import { getWidgets } from "vite-plugin-chatgpt-widgets";
8186

82-
// Pass the Vite dev server instance from wherever you can get it
87+
// In development, pass the Vite dev server instance from wherever you can get it
8388
const widgets = await getWidgets("web/chatgpt-widgets", viteDevServer);
8489

90+
// In production, pass a path to the vite manifest, where we'll load precompiled versions from
91+
const widgets = await getWidgets("web/chatgpt-widgets", {
92+
manifestPath: "dist/.vite/manifest.json",
93+
});
94+
8595
// Register each widget on an MCP server as a resource for exposure to ChatGPT
8696
for (const widget of widgets) {
8797
const resourceName = `widget-${widget.name.toLowerCase()}`;
8898
const resourceUri = `ui://widget/${widget.name}.html`;
8999

100+
// assuming you are using @modelcontextprotocol/sdk, will be similar for other MCP implementations
90101
mcpServer.registerResource(
91102
resourceName,
92103
resourceUri,
@@ -109,45 +120,23 @@ for (const widget of widgets) {
109120
}
110121
```
111122

112-
#### Production Mode (reading from build manifest)
113-
114-
```typescript
115-
import { getWidgetHTML } from "vite-plugin-chatgpt-widgets";
116-
117-
// Pass production options (or omit for defaults)
118-
const widgets = await getWidgets("web/chatgpt-widgets", {
119-
manifestPath: "dist/.vite/manifest.json",
120-
baseUrl: "https://example.com", // required for sandbox-safe iframe links (see below)
121-
});
122-
123-
for (const widget of widgets) {
124-
// ...
125-
}
126-
```
127-
128-
### Sandboxed iFrames & Fully Qualified URLs
123+
### Sandboxed iframes require fully qualified URLs
129124

130-
**⚠️ Required:** When serving widgets in sandboxed iframes like ChatGPT's UI, asset links **must** be fully qualified URLs with protocol and domain. The plugin enforces this requirement and will throw an error if an absolute base URL is not configured.
125+
When serving widgets in sandboxed iframes like ChatGPT's UI, asset links **must** be fully qualified URLs with protocol and domain. The user's browser loads up your widget on an OpenAI controlled domain, so asset loads must refer directly back to your hosting provider. The plugin enforces this requirement and will throw an error if an absolute base URL is not configured.
131126

132127
You must configure an absolute base URL in one of these ways:
133128

134-
1. **Vite's `base` config is an absolute URL**: If you've already configured Vite with `base: "https://example.com/"`, the plugin will use it automatically.
129+
1. Vite's `base` config: If you've already configured Vite with `base: "https://example.com/"`, the plugin will use it automatically.
135130

136131
```typescript
137132
// Option 1: In Vite config (affects both dev and build)
138133
export default defineConfig({
139134
plugins: [chatGPTWidgetPlugin({})],
140135
base: "https://example.com",
141136
});
142-
143-
// and when calling getWidgets in production
144-
const widgets = await getWidgets("web/chatgpt-widgets", {
145-
manifestPath: "dist/.vite/manifest.json",
146-
baseUrl: "https://example.com",
147-
});
148137
```
149138

150-
2. **Provide `baseUrl` option**: If Vite's `base` is relative (or not set), provide the `baseUrl` option to the plugin or the production build configuration:
139+
2. `baseUrl` option to this plugin:: If Vite's `base` is not set, or must be relative,, provide the `baseUrl` option to this plugin directly:
151140

152141
```typescript
153142
// Option 1: In Vite config (affects both dev and build)
@@ -159,26 +148,8 @@ export default defineConfig({
159148
],
160149
base: "/",
161150
});
162-
163-
// Option 2: When calling getWidgets in production
164-
const widgets = await getWidgets("web/chatgpt-widgets", {
165-
manifestPath: "dist/.vite/manifest.json",
166-
baseUrl: "https://example.com",
167-
});
168151
```
169152

170-
**How it works:**
171-
172-
- The plugin transforms relative asset URLs like `/assets/widget-abc123.js` to `https://example.com/assets/widget-abc123.js`
173-
- Only the entry `<script>` and `<link>` tags in the HTML are transformed
174-
- ES module imports within JavaScript files remain relative (correct behavior - the browser resolves them relative to the parent module's URL)
175-
176-
**Validation:**
177-
178-
- At build time: The plugin validates that either Vite's `base` or the plugin's `baseUrl` option is an absolute URL
179-
- At runtime: `getWidgets()` and `getWidgetHTML()` validate that an absolute base URL is available from either Vite's config or the provided `baseUrl` option
180-
- If validation fails, an error is thrown with clear instructions on how to fix it
181-
182153
## How It Works
183154

184155
The plugin creates virtual modules for each widget component:
@@ -209,20 +180,6 @@ Get the HTML content for a widget.
209180
**Parameters:**
210181

211182
- `widgetsDir` (string): The path to the directory on disk with your widget components
212-
- `viteHandle` (ViteDevServer | ProductionViteBuild): A reference to a Vite context we can use for getting widget content.
213-
- In dev: Pass the Vite dev server instance
214-
- In prod: Pass an object with:
215-
- `manifestPath` (string): Path to the Vite manifest.json file (e.g., `"dist/.vite/manifest.json"`)
216-
- `baseUrl` (string, optional): Base URL for assets if Vite's `base` is not absolute
217-
218-
## Architecture
219-
220-
The plugin and helpers run in different contexts:
221-
222-
- **Plugin context**: Runs during Vite build, creates virtual modules and adds them as entrypoints
223-
- **Application context**: The helper functions run in your app (e.g., MCP server) to serve the widgets
224-
225-
They communicate via:
226-
227-
- **Dev mode**: Direct access to Vite's dev server plugin container
228-
- **Production**: Vite's `manifest.json` file maps virtual module IDs to built file paths
183+
- `viteHandle` (DevelopmentViteBuild | ProductionViteBuild): A reference to a Vite context we can use for getting widget content.
184+
- In dev: Pass an object like `{ devServer: ViteDevServer }` to give the Vite dev server to use to build HTML
185+
- In prod: Pass an object like `{ mainfest: "some/path/to/.vite/manifest.json" }` to list all the entrypoints built by the vite build process

0 commit comments

Comments
 (0)