Observable Framework 1.12.0-alpha.4 GitHub️ 2.4k

Embedded analytics

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.

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 using FileAttachment, and you can import self-hosted 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”.

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.

import {FileAttachment} from "npm:@observablehq/stdlib";
import * as Plot from "npm:@observablehq/plot";

export async function Chart() {
  const gistemp = await FileAttachment("./lib/gistemp.csv").csv({typed: true});
  return Plot.plot({
    y: {grid: true},
    color: {scheme: "burd"},
    marks: [
      Plot.dot(gistemp, {x: "Date", y: "Anomaly", stroke: "Anomaly"}),
      Plot.ruleY([0])
    ]
  });
}

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.

Embedding modules

To allow a module to be embedded in an external application, declare the module’s path in your config file using the dynamicPaths option. For example, to embed a single component named chart.js:

export default {
  dynamicPaths: [
    "/chart.js"
  ]
};

Or for parameterized routes, name the component product-[id]/chart.js, then load a list of product identifiers from a database with a SQL query:

import postgres from "postgres";

const sql = postgres(); // Note: uses psql environment variables

export default {
  async *dynamicPaths() {
    for await (const {id} of sql`SELECT id FROM products`.cursor()) {
      yield `/product-${id}/chart.js`;
    }
  }
};

An embedded component can be imported into a vanilla web application like so:

<script type="module">

import {Chart} from "https://my-workspace.observablehq.cloud/my-app/chart.js";

document.body.append(await Chart());

</script>

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”.

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 on app.example.com or use a proxy to forward requests from example.com to app.example.com for same-origin serving.

In React, you can do something similar using dynamic import and useEffect and useRef hooks (see this example live on StackBlitz):

import {useEffect, useRef} from "react";

export function EmbedChart() {
  const ref = useRef(null);

  useEffect(() => {
    let parent = ref.current, child;
    import("https://my-workspace.observablehq.cloud/my-app/chart.js")
      .then(({Chart}) => Chart())
      .then((chart) => parent?.append((child = chart)));
    return () => ((parent = null), child?.remove());
  }, []);

  return <div ref={ref} />;
}

Since both dynamic import and the imported component are async, the code above is careful to clean up the effect and avoid race conditions.

You can alternatively embed Framework pages using iframe embeds.

Developing modules

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.

import {Chart} from "./chart.js";

To instantiate the imported component, simply call the function:

Chart()

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.