Observable Framework 1.0.0 GitHub

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:

method return type
file.arrayBuffer ArrayBuffer
file.arrow Table
file.blob Blob
file.csv Array
file.html Document
file.image HTMLImageElement
file.json Array, Object, etc.
file.parquet Table
file.sqlite SQLiteDatabaseClient
file.stream ReadableStream
file.text string
file.tsv Array
file.xlsx Workbook
file.xml Document
file.zip ZipArchive

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 ![horse](horse.jpg). 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);