Runtime and dependencies

Hi, I have trouble importing a notebook as a module, maybe due to a dependency imported twice.

Please see the description of the problem/bug here: https://observablehq.com/@severo/notebook-3

Am I using Runtime incorrectly, or is there some problem with the notebook modules exported by ObservableHQ?

See https://observablehq.com/d/e29ea83d4aa3afe8 for a simpler situation where the problem occurs.

Note: one of the imported modules contains a duplicate key (expand the notebook2 value):

  id: "0afc32928f110b49"
  variables: 
    Array(2) [
      0: Object {
        name: "str0"
        value: ƒ()
      }
      1: Object {
        name: "str0"
        value: ƒ()
      }
    ]

and removing it solves the problem (see the notebook2_noDuplicate cell).

See the module file generated by Observable at: https://api.observablehq.com/d/8e5e6d7c80d7e3dd.js. It contains the duplicate variable.

Curiously, all of the cells in your second example seem to be working for me, but I do see the problem in your first example.

If you change the final cell in the first example notebook to this:

new Promise((resolve, reject) => {
  Runtime.load(notebook2, variable => {
    if (variable.name === "viewof ch2") return { fulfilled: resolve, rejected: e => reject(e) };
  });
})

Then you can see that indeed, the error is that checkbox is somehow defined twice.

Oops! I had a wrong explanation here before! Sorry about that.

P.S. By the way, you are importing a relatively old version of the observable runtime; I believe the recommended import is this:

Runtime = (await import("https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js")).Runtime

This also works:

Runtime = (await require('@observablehq/runtime@4')).Runtime

(This doesn’t change the error though.)

Thanks, I:

I hope it will make it easier to keep investigating.

So far as I can tell, the issue is that the backend service that generates the module files at api.observablehq.com/d/notebook.js can create duplicate variables inside the module arrays when variables are imported multiple times. Here’s a function which deduplicates variables in general imported notebook modules (it automates what you did manually in your fix notebook):

function dedup({id, modules}) {
  return {id, modules:modules.map(({id, variables}) => {
    const set = new Set();
    const newVariables = [];
    for (const v of variables) {
      if(!set.has(v.name)) {
        newVariables.push(v);
        set.add(v.name);
      }
    }
    return {id, variables:newVariables};
  })};
}

(Just use e.g. dedup(notebook2) in place of notebook2).

Note that these modules served by the API are written for the V1 runtime, which may become unsupported at some point. A more future-proof solution is to switch to using the V3 runtime like this:

notebook2v3 = (await import("https://api.observablehq.com/d/8e5e6d7c80d7e3dd.js?v=3")).default
new Promise((resolve, reject) => {
  (new Runtime()).module(notebook2v3, name => {
    if (name === "str2") {
      return { fulfilled: resolve, rejected: reject };
    }
  });
})

This also avoids the duplicate variable issue.

Now, if you want to play around with a really convoluted solution that stays in the V1 runtime world, you can try this:

import {fromV1, toV1, utoa} from '@bryangingechen/fromv1'
mm = {
  const fV1 = fromV1(await import('https://api.observablehq.com/d/8e5e6d7c80d7e3dd.js'));
  const nbObj = await toV1(({id:'xx', version:'0', nodes:fV1}));
  const mm = await import(`data:text/javascript;base64,${utoa(nbObj)}`);
  return mm;
}
new Promise((resolve, reject) => {
  Runtime.load(mm.default, variable => {
    if (variable.name === "str2") {
      return { fulfilled: resolve, rejected: reject };
    }
  });
})

This uses the following two functions I wrote some time back:

  • fromV1 reverse-engineers a V1 module to generate cell definitions
  • toV1 creates a V1 module from cell definitions (and luckily, my hacked-together implementation does remove duplicates from imports)

See the fromv1 notebook and its parent for more behind the scenes details.

1 Like

Thanks a lot!

I successfully applied your solution with API v3 at:

I had come across this issue since several months ago. This really confused me so I ended up with a series of notebook narrowing down the cause, proposing a workaround (?) but not thoroughly tested. I had personally switched to v3 module format soon.

2 Likes