Download notebook with local Javascript

I’d like to download a notebook that contains some Javascript attached as a file. Attaching and executing a Javascript file into an Observable notebook is easy enough, as described in this post by @mbostock. When I download the notebook as code, however, the result does not work.

Here’s a simple example that loads a local copy of D3 and uses that to create and return a simple DIV:

I downloaded that notebook as code and posted it to my own server; the result looks like so:

1 Like

Hi @mcmcclur,

It looks like you might have found a bug in our current implementation of local FileAttachments. I’m looking into it now…

2 Likes

Thought had occurred to me. :slight_smile:

It’s probably worth mentioning that a workaround that works for me is to require the file from a fully qualified URL pointing to the file on my own server.

Thanks!

1 Like

Hi @mcmcclur,

You uncovered a small bug in the way that the Observable standard library’s implementation of FileAttachments was resolving URLs, which affected require()-ing local JavaScript files.

It’s fixed now, and if you try downloading your example notebook again, I hope you’ll find that everything is working as it should be.

Thanks for the report, and for the minimal test case notebook!

2 Likes

@jashkenas Yes, that does the trick - thanks!

Thanks for this!
Here are the files I get when unzipping the downloaded code from the example:
d3examp

Here is the code:
++++++++++++++++++++
// Simple local JS / Mark McClure / Observable
export default function define(runtime, observer) {
const main = runtime.module();
const fileAttachments = new Map([[“d3.v5.min.js”,new URL("./files/ce4450cc7608664358fdb193fb52fb7d447eb9f7e24ed88a7c12bf9721c2cbda96d042289188bce1cdf48a066a6520b4cdde04c1ba5ef6e25d711d0122c0bbe3",import.meta.url)]]);
main.builtin(“FileAttachment”, runtime.fileAttachments(name => fileAttachments.get(name)));
main.variable(observer()).define([“md”], function(md){return(
md# Simple local JS
)});
main.variable(observer(“div”)).define(“div”, [“d3”], function(d3)
{
let div = d3.create(‘div’).text(‘Hello from your local D3!’);
return div.node();
}
);
main.variable(observer(“d3”)).define(“d3”, [“require”,“FileAttachment”], async function(require,FileAttachment){return(
require(await FileAttachment(“d3.v5.min.js”).url())
)});
return main;
}
+++++++++++++++++++++++++++++++++++

I have some requests for those that are new as I was that I realise are probably too trivial to be worth implementing :slight_smile:

a) When I run it, I can’t make it work
div = RuntimeError: d3 could not be resolved
d3 = Error: File not found: d3.min.js

Where should the d3 library file be placed? I expect it to be alongside index.html and runtime.js but no. Oh and shouldn’t the error message say ‘d3.v5.min.js’ rather than just ‘d3.min.js’?
b) I presume the ‘new URL’ statement’s parameter is a random value? Maybe make it’s randomness clearer with a comment (or better, a function)?
c) Maybe make the code layout prettier?
d) Maybe consider including in the tar file the file attachment (d3.v5.min.js) the code required, when it is a call local to Observables?
e) If it’s not too radical, maybe highlight that a file attachment (‘lib’ parameter?) is an Observables-hosted library, rather than, say, a data file? A special form of attachment that is not included in the tar.
f) Could the PaxHeader be removed if it adds nothing?

Thanks again for all the marvellous stuff - totally get that this is all probably too trivial.
C.

Sounds like you might be opening the index.html file directly in your browser, which is then unable to read the JS files from the local filesystem. That’s why the README file in your downloaded folder suggests running from a local webserver.

Thanks for the comment - I am running it from a local webserver (this one)- the html finds the local js file OK, the Obs runtime takes care of that, I guess.
C.

Hmm… Taking a closer look at your post, you’ve got

Not sure what that’s for but it doesn’t look like the code in the index.html file. You should simply be opening that via your local server.

All the downloaded notebooks I’ve looked at are the same, and index.html file like this:


Simple local JS


    import define from "./index.js";
    import {Runtime, Library, Inspector} from "./runtime.js";

    const runtime = new Runtime();
    const main = runtime.module(define, Inspector.into(document.body));

    </script>

they all load index.js
index.js has one line in it that is different for each different notebook, named after the notebook itself, plus a version number:
export {default} from "./ecfd1c502129818a@13.js";

Here is the code in ecfd1c502129818a@13.js:
++++++++++++++++++++
// https://observablehq.com/d/ecfd1c502129818a@13
export default function define(runtime, observer) {
const main = runtime.module();
const fileAttachments = new Map([[“d3.v5.min.js”,new URL("./files/ce4450cc7608664358fdb193fb52fb7d447eb9f7e24ed88a7c12bf9721c2cbda96d042289188bce1cdf48a066a6520b4cdde04c1ba5ef6e25d711d0122c0bbe3",import.meta.url)]]);
main.builtin(“FileAttachment”, runtime.fileAttachments(name => fileAttachments.get(name)));
main.variable(observer()).define([“md”], function(md){return(
md # Simple local JS
)});
main.variable(observer(“div”)).define(“div”, [“d3”], function(d3)
{
let div = d3.create(‘div’).text(‘Hello from your local D3!’);
return div.node();
}
);
main.variable(observer(“d3”)).define(“d3”, [“require”,“FileAttachment”], async function(require,FileAttachment){return(
require(await FileAttachment(“d3.v5.min.js”).url())
)});
return main;
}
+++++++++++++++++++++++++++++++++++
I have some requests for those that are new as I was that I realise are probably too trivial to be worth implementing :slight_smile:

a) When I run it, I can’t make it work
div = RuntimeError: d3 could not be resolved
d3 = Error: File not found: d3.min.js

Where should the d3 library file be placed? I expect it to be alongside index.html and runtime.js but no. Oh and shouldn’t the error message say ‘d3.v5.min.js’ rather than just ‘d3.min.js’?
b) I presume the ‘new URL’ statement’s parameter is a random value? Maybe make it’s randomness clearer with a comment (or better, a function)?
c) Maybe make the code layout prettier?
d) Maybe consider including in the tar file the file attachment (d3.v5.min.js) the code required, when it is a call local to Observables?
e) If it’s not too radical, maybe highlight that a file attachment (‘lib’ parameter?) is an Observables-hosted library, rather than, say, a data file? A special form of attachment that is not included in the tar.
f) Could the PaxHeader be removed if it adds nothing?
that’s how the notebook-specific code is included. It’s what I included earlier, together with a carefully considered albeit trivial set of suggestions.
Hope that helps.

The PaxHeader file isn’t something we generate, so I’m not sure where that came from in your screenshot. Also, your screenshot suggests you are missing the files folder, which contains attached files, including d3.v5.min.js here.

Here’s what I see when I download the linked example:

That is so weird. I’ve been wondering about it. Here’s a download from the notebook Mark linked:

the text of one of the files:

146 path=./files/ce4450cc7458664378f8b913fb52fb7d447eb9f7e24ed88a7c12bf9721c2c3dd/d042282348bce1cdf48a066a6520b4cdde0fc1ba5ef6e25d711d0122c0bbe3

This is what I see (on MacOS) when downloading the code from that notebook


I think perhaps the PaxHeader files are from processing the tar files on Windows… https://stackoverflow.com/questions/34688392/paxheaders-in-tarball
Not sure if it is problematic or not.