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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more