Published
Edited
May 5, 2022
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// TODO - change the server to instead "replace" node values like how 'import with' works. The notebook can define the imports as named cells with a null / mock value, allowing them to be executed and tested in the browser. And the "real" nodejs application can resolve the value of those cells using the actual imports / inputs.
main = use_express(async context => {
const { app, puppeteer } = context;
const browser = await puppeteer.launch({ args: chrome_args });
const notebooks = new Map();

redirectHttpToHttps(context);
middleware(context);
favicon(context);

app.get('/notebook.stats', serveNotebookStats({ notebooks }));

app.all(route, serveNotebooks({ browser, notebooks, ...context }));
})
Insert cell
Insert cell
serveNotebooks = context => async (req, res) => {
const {
body,
params: { notebook = default_notebook, cell = default_cell }
} = req;
try {
const { error, html, json, status } = await runNotebook({
notebook: `${user}/${notebook}`,
cell,
args: { body },
...context
});
if (error) res.status(status || 500).json({ error });
else if (typeof json === 'number') res.sendStatus(json);
else res.status(status || 200).send(html || json);
} catch (error) {
console.error(error);
res.status(500).json({ error: error.message });
}
}
Insert cell
Insert cell
async function runNotebook(context) {
const { notebook, cell, args } = context;
try {
const page = await loadNotebook(context);
if (!page) return { status: 404 };
const handle = await page.waitForFunction(
async ({ cell, args }) => {
if (!window.app) return false;
let value = await window.app.get(cell);
if (!value) return false;
try {
while (typeof value === 'function') value = await value(args);
if (!value) return {};
if (typeof value === 'string') return { html: value };
if (value.outerHTML) return { html: value.outerHTML };
return { json: value };
} catch (error) {
return { error: error.message || error };
}
},
{ timeout },
{ cell, args }
);
return await handle.jsonValue();
} catch (error) {
if (error.name !== 'TimeoutError') throw error;
return { error: error.message, status: 408 };
}
}
Insert cell
Insert cell
async function loadNotebook(context) {
const { browser, notebooks, notebook } = context;
const url = `https://api.observablehq.com/${notebook}.js?v=3`;
const etag = await getNotebookEtag({ url, ...context });
if (!etag) return null;
const cache = notebooks.get(notebook);
if (cache) {
if (cache.etag === etag) return cache.promise;
cache.promise.then(page => page.close()).catch(console.error.bind(console));
}
const promise = new Promise(async (resolve, reject) => {
try {
const page = await browser.newPage();
await page.addScriptTag({
content: notebookScript({ url }),
type: 'module'
});
resolve(page);
} catch (error) {
reject(error);
}
});
notebooks.set(notebook, { promise, etag });
return promise;
}
Insert cell
Insert cell
notebookScript = ({ url }) => `
import { Runtime } from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
import define from "${url}";
window.app = new Map();
new Runtime().module(define, name => ({
fulfilled(value) { window.app.set(name, value); },
rejected(error) { window.app.set(name, () => { throw error; }); }
}));`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
hello_world = () => `<p>Hello World! ${new Date()}</p>`
Insert cell
web = hello_world
Insert cell
example1 = function() {
return html`<p>Dynamic Text! ${new Date()}`;
}
Insert cell
example2 = html`<p>Static Text!`
Insert cell
example3 = md`# More static text
And stuff.`
Insert cell
example4 = 442
Insert cell
example5 = ({
data: 'static'
})
Insert cell
example6 = () => ({ data: 'dynamic', date: new Date().toString() })
Insert cell
example7 = input => ({ data: input, date: new Date().toString() })
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more