Observable Framework 1.7.0 GitHub️ 1.9k

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, MIME type, and modification time (represented as the number of milliseconds since UNIX epoch).

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 implicitly awaited 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. This also allows a content hash in the file name 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().

For missing files, file.lastModified is undefined. The file.mimeType is determined by checking the file extension against the mime-db media type database; it defaults to application/octet-stream.

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.dsv 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 of a file 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.

In addition to the above, you can get the resolved absolute URL of the file using file.href:

FileAttachment("volcano.json").href

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></video>

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);

Routing

Attached files live in the source root (typically src) alongside your Markdown pages. For example, say index.md has some JavaScript code that references FileAttachment("quakes.csv"):

.
├─ src
│  ├─ index.md
│  └─ quakes.csv
└─ ...

On build, any files referenced by FileAttachment will automatically be copied to the _file folder under the output root (dist), here resulting in:

.
├─ dist
│  ├─ _file
│  │  └─ quakes.e5f2eb94.csv
│  ├─ _observablehq
│  │  └─ ... # additional assets for serving the site
│  └─ index.html
└─ ...

FileAttachment references are automatically rewritten during build; for example, a reference to quakes.csv might be replaced with _file/quakes.e5f2eb94.csv. (As with imports, file names are given a content hash, here e5f2eb94, to improve performance.) Only the files you reference statically are copied to the output root (dist), so nothing extra or unused is included in the built site.

Imported local modules can use FileAttachment, too. In this case, the path to the file is relative to the importing module in the same fashion as import; this is accomplished by resolving relative paths at runtime with import.meta.url.

Some additional assets are automatically promoted to file attachments and copied to _file. For example, if you have a <link rel="stylesheet" href="style.css"> declared statically in a Markdown page, the style.css file will be copied to _file, too (and the file name given a content hash). The HTML elements eligible for file attachments are audio, img, link, picture, and video.