Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

import FileAttachment #325

Merged
merged 4 commits into from
Dec 6, 2023
Merged

import FileAttachment #325

merged 4 commits into from
Dec 6, 2023

Conversation

mbostock
Copy link
Member

@mbostock mbostock commented Dec 5, 2023

This allows you to import and use FileAttachment from ES modules. For example:

import {FileAttachment} from "npm:@observablehq/stdlib";

export const data = FileAttachment("data.csv").csv({typed: true});

Under the hood, this works by rewriting the call to FileAttachment to pass in import.meta.url; this allows the FileAttachment constructor to resolve the path to the file relative to the importing module in the same fashion as import.

Since you can use FileAttachment in ES modules now, I’ve also removed the rewriting of fetch; fetch is now unadulterated, and calls to fetch won’t be statically analyzed and turned into file attachments. The motivation here is to encourage people to standardize on FileAttachment since FileAttachment enforces static analysis; unlike fetch, FileAttachment throws a syntax error if you pass arguments that are not statically analyzable. In addition, while we can rewrite direct calls to fetch, we can’t rewrite indirect calls to fetch such as d3.json and d3.csv, so it’s better to be more explicit and limit automatic resolution to FileAttachment.

Fixes #15.

@mbostock mbostock force-pushed the mbostock/imported-file branch 9 times, most recently from f83cf7f to 87df0cf Compare December 6, 2023 01:20
@mbostock mbostock force-pushed the mbostock/imported-file branch from 87df0cf to 7e8e722 Compare December 6, 2023 01:26
@mbostock mbostock marked this pull request as ready for review December 6, 2023 01:30
@mbostock mbostock requested review from Fil, trebor and cinxmo December 6, 2023 01:30
@@ -2,7 +2,13 @@

TK Should this be called “working with data”?

You can load files the built-in `FileAttachment` function or the standard [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) API. We recommend `FileAttachment` because it supports many common data formats, including CSV, TSV, JSON, Apache Arrow, and SQLite. For example, here’s how to load a CSV file:
You can load files the built-in `FileAttachment` function. This is available by default in Markdown, but you can import it like so:
Copy link
Contributor

@cinxmo cinxmo Dec 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
You can load files the built-in `FileAttachment` function. This is available by default in Markdown, but you can import it like so:
You can load files with the built-in `FileAttachment` function. This is available by default in Markdown, but you can import it like so:

going to continue reviewing tomorrow

@Fil
Copy link
Contributor

Fil commented Dec 6, 2023

Nice! Is there any chance that this could also work in data loaders? This way a loader (say, one that does analysis in ts) could use the product of another loader (one that downloads a dataset).

Currently the import fails with ERR_UNSUPPORTED_ESM_URL_SCHEME, which is a generic problem already discussed in #288.

@mbostock
Copy link
Member Author

mbostock commented Dec 6, 2023

Maybe @Fil. I think I had a Slack thread where I showed how to get the npm: protocol imports working in a data loader. But getting the import working is sort of beside the point — the API for chaining loaders isn’t really about FileAttachment since data loaders can be written in any language. Instead a data loader needs to be able to fetch a file from the preview server (and we need an equivalent server during build). Maybe we set an environment variable which data loaders can read to know the address of the preview server. We’d also need to detect and error on circular dependencies ideally.

Future work though. Is there an issue already for chaining loaders?

@Fil
Copy link
Contributor

Fil commented Dec 6, 2023

Now there is one: #332

@@ -20,9 +18,11 @@ export interface DatabaseReference {
}

export interface FileReference {
/** The relative path from the source root to the file. */
name: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name is still the relative path from the page to the file?

docs/
├── earthquakes.csv
└── sub
    └── index.md

and in docs/sub/index.md we have FileAttachment("../earthquakes.csv"), name is "../earthquakes.csv" vs "./earthquakes.csv"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can double check but there is a difference between Feature.name, FileAttachment.name in generated code, and FileReference.name. It’s all a bit hard to keep straight.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right! 👍 Sorry, I was confused. I’ve updated the comment.

const {imports, fetches} = findImports(body, root, sourcePath);
const features = findFeatures(body, root, sourcePath, references, input);
const references = findReferences(body);
findAssignments(body, references, input);
Copy link
Contributor

@cinxmo cinxmo Dec 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have good test coverage for transpileJavaScript but it would be nice to have specific unit tests for the find<...> functions. I can work that if it's helpful nvm there are comprehensive test cases at the end

@@ -28,16 +28,173 @@ describe("parseLocalImports(root, paths)", () => {
});
it("ignores missing files", () => {
assert.deepStrictEqual(
parseLocalImports("test/input/imports", ["static-import.js", "does-not-exist.js"]).imports.sort(compareImport),
parseLocalImports("test/input/imports", ["static-import.js", "does-not-exist.js"]).imports.sort(order),
[
{name: "bar.js", type: "local"},
{name: "does-not-exist.js", type: "local"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this test case - is {name: "does-not-exist.js", type: "local"} supposed to be included?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we don’t check for existence, just a valid path.

@mbostock mbostock mentioned this pull request Dec 6, 2023
@mbostock mbostock enabled auto-merge (squash) December 6, 2023 15:57
@mbostock mbostock merged commit 23cf7a3 into main Dec 6, 2023
1 check passed
@mbostock mbostock deleted the mbostock/imported-file branch December 6, 2023 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support FileAttachment in local ES modules
3 participants