Skip to content

Commit f3783e4

Browse files
mbostockFil
andauthored
embedding docs (#1692)
* embedding docs * Update docs/embeds.md Co-authored-by: Philippe Rivière <[email protected]> --------- Co-authored-by: Philippe Rivière <[email protected]>
1 parent f63c46e commit f3783e4

File tree

4 files changed

+65
-51
lines changed

4 files changed

+65
-51
lines changed

docs/chart.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import * as Plot from "npm:@observablehq/plot";
2-
import {FileAttachment} from "observablehq:stdlib";
2+
import {FileAttachment, resize} from "observablehq:stdlib";
33

44
export async function Chart() {
55
const gistemp = await FileAttachment("./lib/gistemp.csv").csv({typed: true});
6-
return Plot.plot({
7-
y: {grid: true},
8-
color: {scheme: "burd"},
9-
marks: [Plot.dot(gistemp, {x: "Date", y: "Anomaly", stroke: "Anomaly"}), Plot.ruleY([0])]
10-
});
6+
return resize((width) =>
7+
Plot.plot({
8+
width,
9+
y: {grid: true},
10+
color: {scheme: "burd"},
11+
marks: [Plot.dot(gistemp, {x: "Date", y: "Anomaly", stroke: "Anomaly"}), Plot.ruleY([0])]
12+
})
13+
);
1114
}

docs/config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ Whether to show the previous & next links in the footer; defaults to true. The p
161161

162162
## dynamicPaths <a href="https://github.com/observablehq/framework/releases/tag/v1.11.0" class="observablehq-version-badge" data-version="^1.11.0" title="Added in 1.11.0"></a>
163163

164-
The list of [parameterized pages](./params), [dynamic pages](./page-loaders), and [embedded modules](./embeds) to generate, either as a (synchronous) iterable of strings, or a function that returns an async iterable of strings if you wish to load the list of dynamic pages asynchronously.
164+
The list of [parameterized pages](./params), [dynamic pages](./page-loaders), and [exported modules and files](./embeds) to generate, either as a (synchronous) iterable of strings, or a function that returns an async iterable of strings if you wish to load the list of dynamic pages asynchronously.
165165

166166
## head
167167

docs/embeds.md

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,41 @@
1-
# Embedded analytics <a href="https://github.com/observablehq/framework/pull/1637" class="observablehq-version-badge" data-version="prerelease" title="Added in #1637"></a>
1+
---
2+
keywords: embedded analytics, embeds, iframe, exporting, exports
3+
---
24

3-
In addition to generating full-page apps, Framework can generate modules to embed analytics — such as individual charts or tables, or coordinated interactive views — in external applications. Embedded modules take full advantage of Framework’s polyglot, baked data architecture for instant page loads.
5+
# Embedding <a href="https://github.com/observablehq/framework/pull/1637" class="observablehq-version-badge" data-version="prerelease" title="Added in #1637"></a>
46

5-
Embedded modules are vanilla JavaScript, and behave identically when embedded in an external application as on a Framework page. As always, you can load data from a [data loader](./data-loaders) using [`FileAttachment`](./files), and you can [import](./imports) [self-hosted](./imports#self-hosting-of-npm-imports) local modules and libraries from npm; file and import resolutions are baked into the generated code at build time so that imported modules “just work”.
7+
In addition to standalone apps, you can use Framework to embed interactive views within other applications. Framework supports multiple approaches to embedding:
68

7-
Embedded modules are often written as component functions that return DOM elements. These functions can take options (or “props”), and typically load their own data. For example, below is a simple `chart.js` module that exports a `Chart` function that renders a scatterplot of global surface temperature data.
9+
- [exported modules](#exported-modules) for seamless integration and performance,
10+
- [exported files](#exported-files) for hotlinking images, data, and other assets, or
11+
- [iframe embeds](#iframe-embeds) for compatibility.
12+
13+
## Exported modules
14+
15+
Framework allows [JavaScript modules](./imports#local-imports) to be exported for use in another application. Exported modules are vanilla JavaScript and behave identically in an external web application as on a Framework page. As with local modules, exported modules can load data from a [static file](./files) or a [data loader](./data-loaders), [import](./imports) other local modules, and import libraries from [npm](./imports#npm-imports) or [JSR](./imports#jsr-imports).
16+
17+
Exported modules typically define **data components**: functions that render dynamic content, such as a chart or table, by returning a DOM element. Data components can take options (or “props”), and load any needed data using [`FileAttachment`](./files). For example, the `chart.js` module below exports a `Chart` data component that loads a CSV file and renders a responsive scatterplot of global surface temperature.
818

919
```js run=false
1020
import * as Plot from "npm:@observablehq/plot";
11-
import {FileAttachment} from "observablehq:stdlib";
21+
import {FileAttachment, resize} from "observablehq:stdlib";
1222

1323
export async function Chart() {
1424
const gistemp = await FileAttachment("./lib/gistemp.csv").csv({typed: true});
15-
return Plot.plot({
16-
y: {grid: true},
17-
color: {scheme: "burd"},
18-
marks: [
19-
Plot.dot(gistemp, {x: "Date", y: "Anomaly", stroke: "Anomaly"}),
20-
Plot.ruleY([0])
21-
]
22-
});
25+
return resize((width) =>
26+
Plot.plot({
27+
width,
28+
y: {grid: true},
29+
color: {scheme: "burd"},
30+
marks: [Plot.dot(gistemp, {x: "Date", y: "Anomaly", stroke: "Anomaly"}), Plot.ruleY([0])]
31+
})
32+
);
2333
}
2434
```
2535

26-
<div class="note">
27-
28-
When Framework builds your app, any transitive static imports are preloaded automatically when the embedded module is imported. This ensures optimal performance by avoiding long request chains.
29-
30-
</div>
36+
Data components benefit from Framework’s baked data architecture for instant loads. File and import resolutions are baked into exported modules at build time. Libraries from npm are [self-hosted](./imports#self-hosting-of-npm-imports) for stability, security, and performance. And transitive static imports are preloaded to avoid long request chains.
3137

32-
## Embedding modules
33-
34-
To allow a module to be embedded in an external application, declare the module’s path in your [config file](./config) using the [**dynamicPaths** option](./config#dynamic-paths). For example, to embed a single component named `chart.js`:
38+
To export a module, declare the module’s path in your [config file](./config) using the [**dynamicPaths** option](./config#dynamic-paths). For example, to export the module named `chart.js`:
3539

3640
```js run=false
3741
export default {
@@ -41,7 +45,7 @@ export default {
4145
};
4246
```
4347

44-
Or for [parameterized routes](./params), name the component `product-[id]/chart.js`, then load a list of product identifiers from a database with a SQL query:
48+
Or for [parameterized routes](./params), name the module `product-[id]/chart.js`, then load a list of product identifiers from a database with a SQL query:
4549

4650
```js run=false
4751
import postgres from "postgres";
@@ -57,7 +61,9 @@ export default {
5761
};
5862
```
5963

60-
An embedded component can be imported into a vanilla web application like so:
64+
### Importing exported modules
65+
66+
An exported module can then be imported into a vanilla web application like so:
6167

6268
```html run=false
6369
<script type="module">
@@ -69,15 +75,9 @@ document.body.append(await Chart());
6975
</script>
7076
```
7177

72-
<div class="note">
78+
<div class="warning" label="Coming soon">
7379

74-
The code above assumes the Framework app is called “my-app” and that it’s deployed to Observable Cloud in the workspace named “my-workspace”.
75-
76-
</div>
77-
78-
<div class="note">
79-
80-
If the external (host) application is on a different origin than the Framework app — for example, if the host application is on example.com and the Framework app is on app.example.com — then you will need to [enable CORS](https://enable-cors.org/) on app.example.com or use a proxy to forward requests from example.com to app.example.com for same-origin serving.
80+
Observable Cloud support for cross-origin resource sharing (CORS) is not yet generally available and is needed for exported modules. If you are interested in beta-testing this feature, please [email us](mailto:[email protected]). For public apps, you can use a third-party host supporting CORS such as GitHub Pages.
8181

8282
</div>
8383

@@ -103,28 +103,39 @@ export function EmbedChart() {
103103
104104
<div class="tip">
105105
106-
Since both dynamic import and the imported component are async, the code above is careful to clean up the effect and avoid race conditions.
106+
Some web tooling such as Vite and Webpack erroneously rewrite external dynamic imports. You may need to include a comment such as `import(/* @vite-ignore */ …)` or `import(/* webpackIgnore: true */ …)` to disable this behavior.
107107
108108
</div>
109109
110-
<div class="tip">
110+
## Exported files
111111
112-
You can alternatively embed Framework pages using [iframe embeds](https://observablehq.observablehq.cloud/framework-example-responsive-iframe/).
112+
In addition to modules, you can declare specific files to export using the [**dynamicPaths** config option](./config#dynamic-paths). Exported files are published under a stable URL that can be linked to and loaded from an external application. Exported files can be either [static files](./files) or generated dynamically by [data loaders](./data-loaders), and can use [parameterized routes](./params). For example, to export the file `/robots.txt`:
113113
114-
</div>
114+
```js run=false
115+
export default {
116+
dynamicPaths: [
117+
"/robots.txt"
118+
]
119+
};
120+
```
115121
116-
## Developing modules
122+
## Iframe embeds
117123
118-
To develop your component, you can import it into a Framework page like normal, giving you instant reactivity as you make changes to the component or its data.
124+
You can alternatively embed Framework pages using iframes. Pages that are intended to be embedded via iframe typically disable Framework’s built-in user interface using [Markdown front matter](./markdown#front-matter):
119125
120-
```js echo
121-
import {Chart} from "./chart.js";
126+
```yaml
127+
---
128+
sidebar: false
129+
header: false
130+
footer: false
131+
pager: false
132+
---
122133
```
123134
124-
To instantiate the imported component, simply call the function:
135+
For the page `/chart`, you can declare an iframe like so:
125136
126-
```js echo
127-
Chart()
137+
```html run=false
138+
<iframe scrolling="no" src="https://my-workspace.observablehq.cloud/my-app/chart"></iframe>
128139
```
129140
130-
A Framework page can serve as live documentation for your component: you can describe and demonstrate all the states and options for your component, and review the behavior visually.
141+
With a little bit of additional JavaScript, you can also implement [responsive iframe embeds](https://observablehq.observablehq.cloud/framework-example-responsive-iframe/) which resize automatically to fit the content of the page.

observablehq.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default {
3232
{name: "Themes", path: "/themes"},
3333
{name: "Page loaders", path: "/page-loaders"},
3434
{name: "Parameterized routes", path: "/params"},
35-
{name: "Embedded analytics", path: "/embeds"},
35+
{name: "Embedding", path: "/embeds"},
3636
{name: "Configuration", path: "/config"},
3737
{name: "Examples", path: "https://github.com/observablehq/framework/tree/main/examples"},
3838
{

0 commit comments

Comments
 (0)