Notebooks running in Deno

Hey folks,

I was able to import notebooks into Deno runtime and execute them using code like below:

const notebook = "@username/notebookname";

// import runtime
window.setImmediate = func => setTimeout(func, 0);
const { Runtime } = await import(
  "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js"
);

// import notebook
const { default: define } = await import(`https://api.observablehq.com/${notebook}.js?v=3&d=${Date.now()}`);

// invoke notebook
const handler = await new Runtime().module(define).value("Deno");
return await handler(Deno);

I needed to define the “setImmediate” on the window as something in the runtime references it though it “shouldn’t”. But a minor fix to make. Surprisingly works very well!!

I added the cache-busting datetime to the import URL to ensure the latest version was always loaded when executed. Note that if you recycle the process you can use Deno flags to tell it to reload imports from the observable API endpoint if you wish. But that does require recycling the process.

The code looks for a cell in the notebook named “Deno” and calls it as a function, passing-in the the single global “Deno” object to it. This makes for a nice hook in the notebook and you can using dynamic import syntax to load any Deno dependencies that you wish!

I was looking at writing a web server / cron job that constantly refreshed notebook versions. However, there is no way to garbage collect the stale notebook modules from memory without recycling the process, whether running in Node or Deno. Deno at least allows the URL imports. But it looks like we will still need to use subprocesses to run the notebooks so we can recycle them so the stale versions don’t fill the memory of the main process.

@tomlarkworthy has built a similar service with webcode.run and we’ve exchanged snippets of code for node and using puppeteer to run the notebooks. Just wanted to share a blurb about using Deno runtime and it works very well!

Below is some server code if you want to get started with running a Deno web server with the source code in an observable notebook:

Deno = async (Deno) => {
  const { Application, Router } = await import("https://deno.land/x/oak/mod.ts");

  const app = new Application();

  const router = new Router();

  router.get("/test", (context) => {
    context.response.body = `Hello world from notebook! ${Date.now()}`;
  });

  app.use(router.routes());

  app.addEventListener("listen", ({ hostname, port, secure }) => {
    console.log(
      `Listening on ${secure ? "https://" : "http://"}${hostname}:${port}`
    );
  });

  await app.listen({ port: 8000 });
}
4 Likes

This is great and something I wanted to test out. Deno could be quite attractive as a notebook runner in a few different situations.

Related: Proposal: Module Loader API · Issue #8327 · denoland/deno · GitHub

Added more info here: obs.run / Bryant Richardson / Observable