Observable Framework 1.4.0 GitHub️ 1.7k

JavaScript: Imports

You can load a library using an import statement. For example:

import confetti from "npm:canvas-confetti";
Thanks to reactivity, imports can live in a JavaScript block anywhere on the page. But by convention, imports are commonly placed at the top of source files for readability.

You can use an imported library anywhere on the page.

Inputs.button("Throw confetti!", {reduce: () => confetti()})

npm imports

An npm: specifier, as shown above, denotes that the imported library will be loaded from npm rather than from node_modules. You don’t have to install imported libraries beforehand — just import, and the cloud shall provide. (This convention is also used by Deno.)

Under the hood, npm: imports are powered by jsDelivr’s esm.run CDN. The import above is thus equivalent to:

import confetti from "https://cdn.jsdelivr.net/npm/canvas-confetti/+esm";
We plan on supporting importing from node_modules using bare module specifiers (e.g., import confetti from "canvas-confetti"). If you’d like this feature, please upvote #360.

To load a specific version of a library, add a semver range:

import confetti from "npm:canvas-confetti@1";

To load a specific entry point, add a slash and the desired path:

import confetti from "npm:canvas-confetti@1/dist/confetti.module.mjs";

If you’re having difficulty getting an import working, it may help to browse the package and see what files are available as well as what’s exported in the package.json. You can browse the contents of a published module via jsDelivr; for example, for canvas-confetti see https://cdn.jsdelivr.net/npm/canvas-confetti/.

Self-hosting of npm imports

Framework automatically downloads npm: imports from jsDelivr during preview and build. This improves performance and security of your built site by removing external code dependencies. It also improves performance during local preview by only downloading libraries once.

Downloads from npm are cached in .observablehq/cache/_npm within your source root. You can clear the cache and restart the server to re-fetch the latest versions of libraries from npm.

Self-hosting of npm: imports applies to static imports, dynamic imports, and import resolutions (import.meta.resolve), provided the specifier is a static string literal. For example to load D3:

import * as d3 from "npm:d3";
const d3 = await import("npm:d3");

In both cases above, the latest version of d3 is resolved and downloaded from jsDelivr as an ES module, including all of its transitive dependencies.

You can load a library from a CDN at runtime by importing a URL. However, we recommend self-hosted npm: to improve performance and security, and to improve reliability by letting you control when you upgrade.

Transitive static imports are also registered as module preloads (using <link rel="modulepreload">), such that the requests happen in parallel and as early as possible, rather than being chained. This dramatically improves performance on page load. Framework also preloads npm: imports for FileAttachment methods, such as d3-dsv for CSV.

Import resolutions allow you to download files from npm. These files are automatically downloaded for self-hosting, too. For example, to load U.S. county geometry:

const data = await fetch(import.meta.resolve("npm:us-atlas/counties-albers-10m.json")).then((r) => r.json());

Framework automatically downloads files as needed for recommended libraries, and resolves import resolutions in transitive static and dynamic imports. For example, DuckDB needs WebAssembly bundles, and KaTeX needs a stylesheet and fonts. For any dependencies that are not statically analyzable (such as fetch calls or dynamic arguments to import) you can call import.meta.resolve to register additional files to download from npm.

Local imports

In addition to npm, you can import JavaScript from local modules. This is useful for organizing your code: you can move JavaScript out of Markdown and create components and helpers that can be imported across multiple pages. This also means you can write unit tests for your code, and share code with any other web applications.

For example, if this is foo.js:

export const foo = 42;

Then you can say

import {foo} from "./foo.js";

and the imported value of foo is: .

Observable Framework automatically watches imported local modules during preview, so any changes to these files will instantly update in the browser via hot module replacement.

While there is reactivity across JavaScript code blocks in Markdown, there’s no reactivity within a JavaScript module. However, you can write async functions and generator functions to define reactive variables. And you can import the Observable standard library into local modules, so you can reference files and use other standard library features.

Module preloads

During build, Observable Framework will resolve the current exact version of the imported library from npm. Importing npm:canvas-confetti is thus equivalent to:

import confetti from "https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/+esm";

Version resolution locks the version of imported libraries so you don’t have to worry about new releases breaking your built site in the future. At the same time, you’ll conveniently get the latest version of libraries during local development and the next time you build.

In addition to resolving versions of directly-imported modules, Observable Framework recursively resolves dependencies, too! All transitively imported modules are automatically preloaded, greatly improving page load speed because the browser requests all imported modules in parallel.

<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/+esm">
Framework automatically downloads npm: imports from npm during preview and build, making the built site entirely self-contained. If you prefer not to self-host a module, and instead load it from an external server at runtime, import a full URL instead of using the npm: protocol.

Implicit imports

For convenience, Observable Framework provides recommended libraries by default in Markdown. These implicit imports are only evaluated if you reference the corresponding symbol and hence don’t add overhead if you don’t use them; for example, D3 won’t be loaded unless you have an unbound reference to d3.

Click on any of the imported symbols below to learn more.

import {FileAttachment} from "npm:@observablehq/stdlib";
import {Generators} from "npm:@observablehq/stdlib";
import {Mutable} from "npm:@observablehq/stdlib";
import dot from "npm:@observablehq/dot";
import * as duckdb from "npm:@duckdb/duckdb-wasm";
import {DuckDBClient} from "npm:@observablehq/duckdb";
import * as Inputs from "npm:@observablehq/inputs";
import mapboxgl from "npm:mapbox-gl";
import mermaid from "npm:@observablehq/mermaid";
import * as Plot from "npm:@observablehq/plot";
import SQLite from "npm:@observablehq/sqlite";
import {SQLiteDatabaseClient} from "npm:@observablehq/sqlite";
import tex from "npm:@observablehq/tex";
import * as Arrow from "npm:apache-arrow";
import * as aq from "npm:arquero";
import * as d3 from "npm:d3";
import * as htl from "npm:htl";
import {html} from "npm:htl";
import {svg} from "npm:htl";
import * as L from "npm:leaflet";
import _ from "npm:lodash";
import * as topojson from "npm:topojson-client";

Require

If you’re familiar with Observable notebooks, you may have noticed that we don’t mention require above. We recommend that you avoid require as the underlying Asynchronous Module Definition (AMD) convention has been made obsolete by standard imports in JavaScript, and AMD tends to be implemented inconsistently by libraries.

If you really need require, you can import it from d3-require:

import {require} from "npm:d3-require";

Then you can call require like so:

const d3 = require("d3@5");
We recommend that you use import instead of require: it’s the modern standard, more reliable, more forward-looking, and faster.