JavaScript: Files
Load files — whether static or generated dynamically by a data loader — using the built-in FileAttachment function. This is available by default in Markdown, but you can import it explicitly like so:
import {FileAttachment} from "npm:@observablehq/stdlib";
The FileAttachment function takes a path and returns a file handle. This handle exposes the file’s name and MIME type.
FileAttachment("volcano.json")
Like a local import, the path is relative to the calling code’s source file: either the page’s Markdown file or the imported local JavaScript module. (To load a remote file, use fetch, or use a data loader to download the file at build time.)
Calling FileAttachment doesn’t actually load the file; the contents are only loaded when you invoke a file contents method. For example, to load a JSON file:
const volcano = FileAttachment("volcano.json").json();
The value of volcano above is a promise. In other code blocks, the promise is resolved implicitly and hence you can refer to the resolved value directly.
volcano
Static analysis
The FileAttachment function can only be passed a static string literal; constructing a dynamic path such as FileAttachment("my" + "file.csv") is invalid syntax. Static analysis is used to invoke data loaders at build time, and ensures that only referenced files are included in the generated output during build. In the future #260, it will also allow content hashes for cache breaking during deploy.
If you have multiple files, you can enumerate them explicitly like so:
const frames = [
FileAttachment("frame1.png"),
FileAttachment("frame2.png"),
FileAttachment("frame3.png"),
FileAttachment("frame4.png"),
FileAttachment("frame5.png"),
FileAttachment("frame6.png"),
FileAttachment("frame7.png"),
FileAttachment("frame8.png"),
FileAttachment("frame9.png")
];
None of the files in frames above are loaded until a content method is invoked, for example by saying frames[0].image().
Supported formats
FileAttachment supports a variety of methods for loading file contents:
The contents often dictate the appropriate method — for example, an Apache Arrow file is almost always read with file.arrow. When multiple methods are valid, choose based on your needs. For example, you can load a CSV file using file.text to implement parsing yourself instead of using D3.
In addition to the above, you can get the resolved relative path to the file using file.url. This returns a promise to a string:
FileAttachment("volcano.json").url()
See file-based routing for additional details.
Basic formats
The following common basic formats are supported natively.
Text
To load a humble text file, use file.text:
const hello = FileAttachment("hello.txt").text();
hello
By default, file.text expects the file to be encoded in UTF-8. To use a different encoding, pass the desired encoding name to file.text.
const pryvit = FileAttachment("pryvit.txt").text("utf-16be");
pryvit
JSON
To load a JSON (JavaScript Object Notation) file, use file.json
FileAttachment("volcano.json").json()
A common gotcha with JSON is that it has no built-in date type; dates are therefore typically represented as ISO 8601 strings, or as a number of milliseconds or seconds since UNIX epoch.
Media
To display an image, you can use a static image in Markdown such as <img src="horse.jpg"> or . Likewise, you can use a video or audio element. Per file-based routing, static references to these files are automatically detected and therefore these files will be included in the built output.
<video src="horse.mp4" autoplay muted loop controls>
If you want to manipulate an image in JavaScript, use file.image. For example, below we load an image and invert the RGB channel values.
const canvas = document.querySelector("#horse-canvas");
const context = canvas.getContext("2d");
const horse = await FileAttachment("horse.jpg").image();
context.drawImage(horse, 0, 0, canvas.width, canvas.height);
const data = context.getImageData(0, 0, canvas.width, canvas.height);
for (let j = 0, k = 0; j < canvas.height; ++j) {
for (let i = 0; i < canvas.width; ++i, k += 4) {
data.data[k + 0] = 255 - data.data[k + 0];
data.data[k + 1] = 255 - data.data[k + 1];
data.data[k + 2] = 255 - data.data[k + 2];
}
}
context.putImageData(data, 0, 0);
(The images above are from Eadweard Muybridge’s studies of animal locomotion.)
Markup
The file.xml method reads an XML file and returns a promise to a Document; it takes a single argument with the file’s MIME-type, which defaults to "application/xml". The file.html method similarly reads an HTML file; it is equivalent to file.xml("text/html").
Binary formats
Load binary data using file.blob to get a Blob, or file.arrayBuffer to get an ArrayBuffer. For example, to read Exif image metadata with ExifReader:
import ExifReader from "npm:exifreader";
const buffer = await FileAttachment("horse.jpg").arrayBuffer();
const tags = ExifReader.load(buffer);
display(tags);
To read a file incrementally, get a ReadableStream with file.stream. For example, to count the number of bytes in a file:
const stream = await FileAttachment("horse.jpg").stream();
const reader = stream.getReader();
let total = 0;
while (true) {
const {done, value} = await reader.read();
if (done) break;
total += value.length;
}
display(total);