diff --git a/README.md b/README.md
index 0e3bc06bc..34dabd75a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Observable CLI
+# Observable Framework
- [Documentation](https://cli.observablehq.com/)
- [Issues](https://github.com/observablehq/cli/issues)
diff --git a/docs/components.md b/docs/components.md
index 67722f1d5..1c0b7eae7 100644
--- a/docs/components.md
+++ b/docs/components.md
@@ -2,7 +2,7 @@
You don’t have to start from scratch: components are reusable pieces of code (functions, themes, snippets, etc.) that make it quicker to update page layout and appearance, and add common page content.
-The Observable CLI offers three flavors of components: [layout helpers](#layout-helpers), [Observable Plot snippets](#observable-plot-snippets), and [Observable Inputs](#observable-inputs).
+Observable Framework offers three flavors of components: [layout helpers](#layout-helpers), [Observable Plot snippets](#observable-plot-snippets), and [Observable Inputs](#observable-inputs).
## Layout helpers
@@ -140,4 +140,4 @@ The [radio input](./inputs/radio) prompts a user to select a penguin species:
const pickSpecies = view(Inputs.radio(["Adelie", "Chinstrap", "Gentoo"], {value: "Gentoo", label: "Penguin species:"}))
```
-The value of `pickSpecies` (="${pickSpecies}") can then be accessed elsewhere in the page, as a parameter in other computations, and to create interactive charts, tables or text with [inline expressions](./javascript#inline-expressions).
+The value of `pickSpecies` (="${pickSpecies}") can then be accessed elsewhere in the page, as a parameter in other computations, and to create interactive charts, tables or text with [inline expressions](./javascript#inline-expressions).
diff --git a/docs/contributing.md b/docs/contributing.md
index 5a5d77c63..19c6a2a16 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -1,6 +1,6 @@
# Contributing
-If you’d like to contribute to the Observable CLI, here’s how. First clone the [git repo](https://github.com/observablehq/cli) and run [Yarn (1.x)](https://classic.yarnpkg.com/lang/en/docs/install/) to install dependencies:
+If you’d like to contribute to Observable Framework, here’s how. First clone the [git repo](https://github.com/observablehq/cli) and run [Yarn (1.x)](https://classic.yarnpkg.com/lang/en/docs/install/) to install dependencies:
```sh
git clone git@github.com:observablehq/cli.git
@@ -30,7 +30,7 @@ This creates the `dist` folder. View the site using your preferred web server, s
http-server dist
```
-This documentation site is built on GitHub using the Observable CLI; see the [deploy workflow](https://github.com/observablehq/cli/blob/main/.github/workflows/deploy.yml). Please open a pull request if you’d like to contribute to the documentation or to CLI features. Contributors are expected to follow our [code of conduct](https://github.com/observablehq/.github/blob/master/CODE_OF_CONDUCT.md). 🙏
+This documentation site is built on GitHub using Observable Framework; see the [deploy workflow](https://github.com/observablehq/cli/blob/main/.github/workflows/deploy.yml). Please open a pull request if you’d like to contribute. Contributors are expected to follow our [code of conduct](https://github.com/observablehq/.github/blob/master/CODE_OF_CONDUCT.md). 🙏
A test coverage report can be generated with [c8](https://github.com/bcoe/c8), in text and lcov formats, to help you identify which lines of code are not (yet!) covered by tests. Just run:
@@ -42,7 +42,7 @@ yarn test:coverage
These instructions are intended for Observable staff.
-To release a new version of the CLI, first update the [package.json](https://github.com/observablehq/cli/blob/main/package.json) file by following the standard process for committing code changes:
+To release a new version, first update the [package.json](https://github.com/observablehq/cli/blob/main/package.json) file by following the standard process for committing code changes:
1. Create a new branch.
2. Edit the `version` field in the [package.json](https://github.com/observablehq/cli/blob/main/package.json) file as desired.
diff --git a/docs/data/forecast.json.js b/docs/data/forecast.json.js
new file mode 100644
index 000000000..2f33a6d67
--- /dev/null
+++ b/docs/data/forecast.json.js
@@ -0,0 +1,13 @@
+const longitude = -122.47;
+const latitude = 37.80;
+
+async function json(url) {
+ const response = await fetch(url);
+ if (!response.ok) throw new Error(`fetch failed: ${response.status}`);
+ return await response.json();
+}
+
+const station = await json(`https://api.weather.gov/points/${latitude},${longitude}`);
+const forecast = await json(station.properties.forecastHourly);
+
+process.stdout.write(JSON.stringify(forecast));
diff --git a/docs/data/forecast.json.py b/docs/data/forecast.json.py
new file mode 100644
index 000000000..395facb3f
--- /dev/null
+++ b/docs/data/forecast.json.py
@@ -0,0 +1,11 @@
+import json
+import requests
+import sys
+
+longitude = -122.47
+latitude = 37.80
+
+station = requests.get(f"https://api.weather.gov/points/{latitude},{longitude}").json()
+forecast = requests.get(station["properties"]["forecastHourly"]).json()
+
+json.dump(forecast, sys.stdout)
diff --git a/docs/getting-started.md b/docs/getting-started.md
index ccc3aaebb..6bff064a1 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -1,153 +1,571 @@
+
+
# Getting started
-The Observable CLI is a Node.js application. As the name suggests, the CLI lives on the command line; the instructions below are intended to run in your [terminal](https://support.apple.com/guide/terminal/open-or-quit-terminal-apd5265185d-f365-44cb-8b09-71a064a42125/mac). You’ll need to install [Node.js 20.6 or later](https://nodejs.org/) before you can install the CLI.
+Welcome! This tutorial will guide your first steps with Observable Framework by way of a hands-on exercise creating a dashboard of local weather. 🌦️
+
+Observable Framework — or “Framework” for short — is an open-source system for building data apps, dashboards, and reports that combines the power of JavaScript on the front-end for interactive graphics with any language you want on the back-end for data preparation and analysis.
+
+Framework is three things in one:
+
+- a **local development server** that you use to preview projects locally during development, with instant updates as you save changes,
+- a **static site generator** that compiles Markdown, JavaScript, and other sources and static assets — alongside data snapshots generated by loaders — into a static site that you can host anywhere, and
+- a **command-line interface** to Observable so that you can quickly and securely share your site with whomever you like (your boss, your team, the world).
-## Installing
+We’ll touch on each of these parts in this tutorial. It’ll go something like this:
-We recommend starting with our default project template which currently requires Yarn 1.x. If you already have Yarn installed, you can check the version like so:
+```js
+const digraph = dot`digraph {
+ rankdir=LR
-```sh
-yarn --version
+ create -> edit -> preview -> build -> deploy -> view
+ preview -> edit
+
+ subgraph cluster_develop {
+ label = "develop"
+ color = "gray"
+ edit
+ preview
+ }
+
+ subgraph cluster_publish {
+ label = "publish"
+ color = "gray"
+ build
+ deploy
+ }
+}`;
```
-Otherwise, or if some other version of Yarn is installed, you can install Yarn via npm:
+
+ ${digraph}
+ An overview of developing with Framework.
+
+
+First you’ll setup your local development environment by [**creating**](#1.-create) a project. Next you’ll [**develop**](#2.-develop): an iterative process where you save changes to source files in your editor while previewing the result in your browser. When you’re ready to share, it’s time to [**publish**](#3.-publish): you can either build a static site for self-hosting or deploy directly to Observable. Lastly, you can invite people to view your project!
+
+These are just first steps. You can continue to develop projects after publishing, and republish as needed. You can also setup continuous deployment to publish your site automatically on commit or on schedule. We’ll cover these [next steps](#next-steps) briefly below.
+
+## 1. Create
+
+Framework includes a helper script (`observable create`) for creating new projects. After a few quick prompts — where to create the project, your preferred package manager, *etc.* — it will stamp out a fresh project from a template.
+
+
+
Framework is a Node.js application published to npm. You must have Node.js 20.6 or later installed before you can install Framework. Framework is a command-line interface (CLI) and runs in the terminal.
The commands above will not work until Framework is published to npm and made publicly available. Until then, you’ll need to install Yarn 1.x and run this instead:
+
yarn global add https://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@github.com/observablehq/cli
+
$(yarn global bin)/observable create
+
+You can run the above command anywhere, but you may want to `cd` to your `~/Development` directory first (or wherever you do local development).
+
+The first prompt asks where to create your new project. Enter `./hello-framework` to create a directory named `hello-framework` within the current directory. Or just hit Enter, as this is conveniently the default. (The `./` is implied, so `./hello-framework` is equivalent to `hello-framework`. You can create a project in a different directory by entering a path that starts with `../` or `~/` or `/`.)
+
+
┌ observable create
+│
+◆ Where to create your project?
+│ ./hello-framework
+└
+
+Next you’ll enter the project’s title. A project’s title appears in the sidebar as well as on all pages. You can hit Enter here to accept the default title derived from the directory name.
+
+
┌ observable create
+│
+◇ Where to create your project?
+│./hello-framework
+│
+◆ What to title your project?
+│Hello Framework
+└
+
+Next, decide whether you want sample files in your new project. These files demonstrate common techniques and are handy for learning — you can edit the code and see what happens. But if you’d prefer a more minimal starter project with less to delete later, you can omit them. They’re not needed for this tutorial.
+
+
┌ observable create
+│
+◇ Where to create your project?
+│./hello-framework
+│
+◇ What to title your project?
+│Hello Framework
+│
+◆ Include sample files to help you get started?
+│● Yes, include sample files (recommended)
+│○ No, create an empty project
+└
+
+If you use npm or Yarn as your preferred package manager, declare your allegiance now. The package manager you used to launch `observable create` will be selected by default, so you can just hit Enter again to continue. If you prefer a different package manager (say pnpm), choose `No`; you can always install dependencies after the project is created.
+
+
┌ observable create
+│
+◇ Where to create your project?
+│./hello-framework
+│
+◇ What to title your project?
+│Hello Framework
+│
+◇ Include sample files to help you get started?
+│Yes, include sample files
+│
+◆ Install dependencies?
+│○ Yes, via npm
+│● Yes, via yarn (recommended)
+│○ No
+└
+
+If you’ll continue developing your project after you finish this tutorial and want source control, answer `Yes` to initialize a git repository. Or say `No` — you can always do it later by running `git init`.
+
+
┌ observable create
+│
+◇ Where to create your project?
+│./hello-framework
+│
+◇ What to title your project?
+│Hello Framework
+│
+◇ Include sample files to help you get started?
+│Yes, include sample files
+│
+◇ Install dependencies?
+│Yes, via yarn
+│
+◆ Initialize a git repository?
+│● Yes / ○ No
+└
+
+And that’s it! After some downloading, copying, and installing, your new project is ready to go. 🎉
+
+
┌ observable create
+│
+◇ Where to create your project?
+│./hello-framework
+│
+◇ What to title your project?
+│Hello Framework
+│
+◇ Include sample files to help you get started?
+│Yes, include sample files
+│
+◇ Install dependencies?
+│Yes, via yarn
+│
+◇ Initialize a git repository?
+│Yes
+│
+◇ Installed! 🎉
+│
+◇ Next steps…
+│
+│cd ./hello-framework
+│yarn dev
+│
+└ Problems? https://cli.observablehq.com/getting-started
+
+## 2. Develop
+
+Next, `cd` into your new project folder.
+
+
cd hello-framework
+
+Framework’s local development server lets you preview your site in the browser as you make rapid changes. The preview server generates pages on-the-fly: as you edit files in your editor, changes are instantly streamed to your browser.
+
+
You can work offline with the preview server, but you must be connected to the internet to import libraries from npm. In the future, we intend to support self-hosting imported libraries; please upvote #20 and #360 if you are interested in this feature.
If port 3000 is in use, the preview server will choose the next available port, so your actual port may vary. To specify port 4321 (and similarly for any other port), use --port 4321.
+
For security, the preview server is by default only accessible on your local machine using the loopback address 127.0.0.1. To allow remote connections, use --host 0.0.0.0.
+
+
+Now visit in your browser, which should look like:
+
+
+
+ The default home page (docs/index.md) after creating a new project.
+
+
+### Test live preview
+
+Live preview means that as you save changes, your in-browser preview updates instantly. Live preview applies to Markdown pages, imported JavaScript modules (so-called *hot module replacement*), data loaders, and file attachments. This feature is implemented by the preview server watching files and pushing changes to the browser over a socket.
+
+To experience live preview, open docs/index.md in your preferred text editor — below we show Visual Studio Code — and position your browser window so that you can see your editor and browser side-by-side. If you then replace the text “Hello, Observable Framework” with “Hi, Mom!” and save, you should see:
+
+
+
+ No seriously — hi, Mom! Thanks for supporting me all these years.
+
+
+
If you don’t see an update after saving, try reloading. The preview socket may disconnect if you’re idle. Please upvote #50 if you run into this issue.
+
+### Create a new page
+
+Now let’s add a page for our weather dashboard. Create a new file `docs/weather.md` and paste in the following snippet:
+
+````md run=false
+# Weather report
+
+```js
+1 + 2
+```
+````
+
+To see the new page in the sidebar, you must restart the preview server. In the terminal, use Control-C (⌃C) to kill the preview server. Then use up arrow (↑) to re-run the command to start the preview server (`npm run dev` or `yarn dev`). Lastly, reload your browser. A bit of rigamarole, but you won’t have to do it often… 😓
+
+If you click on the **Weather report** link in the sidebar, it’ll take you to , where you should see:
+
+
+
+ The humble beginnings of a local weather dashboard.
+
+
+
The sidebar is hidden by default in narrow windows. If you don’t see the sidebar, you can show it by making the window wider, or using Command-B (⌘B) or Option-B (⌥B) on Firefox and non-macOS, or clicking the right-pointing arrow ↦ on the left edge of the window.
+
+As evidenced by the code 1 + 2 rendered as 3, JavaScript fenced code blocks (```js) are *live*: the code runs in the browser. Try replacing 2 with Math.random(), and the code will re-run automatically on save. In a bit, we’ll write code to render a chart. We can also use code to debug as we develop, say to inspect data.
+
+### Data loader
+
+Next, let’s load some data. The [National Weather Service (NWS)](https://www.weather.gov/documentation/services-web-api) provides an excellent and free API for local weather data within the United States. We’ll use the `/points/{latitude},{longitude}` endpoint to get metadata for the closest grid point to the given location, and then fetch the corresponding hourly forecast.
+
+Create a new file docs/data/forecast.json.js and paste in the following snippet:
+
+
const longitude = ${html`${longitude.toFixed(2)}`};
+const latitude = ${html`${latitude.toFixed(2)}`};
+
+async function json(url) {
+ const response = await fetch(url);
+ if (!response.ok) throw new Error(`fetch failed: ${response.status}`);
+ return await response.json();
+}
+
+const station = await json(`https://api.weather.gov/points/${latitude},${longitude}`);
+const forecast = await json(station.properties.forecastHourly);
+
+process.stdout.write(JSON.stringify(forecast));
+
+```js
+const location = view(Locator([-122.47, 37.8]));
+
+function Locator(initialValue) {
+ const form = html``;
+ form.b.onclick = async event => {
+ form.value = await new Promise((resolve, reject) => {
+ navigator.geolocation.getCurrentPosition(
+ ({coords: {longitude, latitude}}) => {
+ form.o.value = "Located!";
+ resolve([longitude, latitude]);
+ },
+ (error) => {
+ form.o.value = "Error!";
+ reject(error);
+ }
+ );
+ form.o.value = "Locating…";
+ });
+ form.dispatchEvent(new CustomEvent("input", {bubbles: true}));
+ };
+ form.value = initialValue;
+ return form;
+}
+```
-```sh
-npm install --global yarn
+```js
+const [longitude, latitude] = location;
```
-See the [Yarn 1.x installation instructions](https://classic.yarnpkg.com/docs/install) for details.
+To personalize this code snippet to your current location, edit the longitude and latitude values above, or click the **Locate me** button above.
+
+
NWS does not provide forecasts for points outside the United States, so if you specify such a location the API will return an error and the data loader will fail.
+
+
If you would rather write your data loader in Python, R, or some other language, take a peek at the next steps below before continuing.
+
+Your data loader should look like this:
+
+
+
+ A JavaScript data loader for fetching a local forecast from weather.gov.
+
+
+If you like, you can run your data loader manually in the terminal:
+
+
node docs/data/forecast.json.js
-Once Yarn is installed, you can install `observablehq-create`, our project template. This package won’t be made publicly available until the Observable CLI is released, so the command below uses an access token to download it from our private repo. Please do not share this token with anyone outside the Early Access program.
+If this barfs a bunch of JSON in the terminal, it’s working as intended. 😅 Normally you don’t run data loaders by hand — Framework runs them automatically, as needed — but data loaders are “just” programs so you can run them manually if you want. Conversely, any executable or shell script that runs on your machine and outputs something to stdout can be a data loader!
-```sh
-yarn global add https://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@github.com/observablehq/create
+### File attachments
+
+Framework uses [file-based routing](./routing) not just for pages but for data loaders as well: the data loader forecast.json.js serves the file forecast.json. To load this file from docs/weather.md we use the relative path ./data/forecast.json. In effect, data loaders are simply a naming convention for generating “static” files — a big advantage of which is that you can edit a data loader and the changes immediately propagate to the live preview without needing a reload.
+
+To load a file in JavaScript, use the built-in [`FileAttachment`](./javascript/files). In `weather.md`, replace the contents of the JavaScript code block (the parts inside the triple backticks ```) with the following code:
+
+```js run=false
+const forecast = FileAttachment("./data/forecast.json").json();
```
-Once installed, create a new project with the following command:
+
FileAttachment is a special function that can only be passed a static string literal as an argument. This restriction enables static analysis, allowing Framework to determine which data loaders to run on build and improving security by only including referenced files in the published site.
-```sh
-observablehq-create
+You can now reference the variable `forecast` from other code. For example, you can add another code block that displays the `forecast` data.
+
+```js run=false
+display(forecast);
```
-If Yarn doesn’t install onto your `$PATH`, instead try:
+This looks like:
+
+
+
+ Using FileAttachment to load data.
+
+
+The built-in [`display`](./javascript/display) function displays the specified value, a bit like `console.log` in the browser’s console. As you may have noticed above with 1 + 2, `display` is called implicitly when a code block contains an expression.
-```sh
-$(yarn global bin)/observablehq-create
+For convenience, here’s a copy of the data so you can explore it here:
+
+```js
+forecast
```
-After answering a few questions, this command will create a new project folder in the current working directory.
+This is a GeoJSON `Feature` object of a `Polygon` geometry representing the grid square. The `properties` object within contains the hourly forecast data. You can display it on a map with Leaflet, if you like.
-## Project structure
+
+
+ This grid point covers the south end of the Golden Gate Bridge.
+
-A typical project looks like this:
+```js
+const map = L.map(document.querySelector("#map"));
+const tile = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map);
+const geo = L.geoJSON().addData(forecast).addTo(map);
+map.fitBounds(geo.getBounds(), {padding: [50, 50]});
+invalidation.then(() => map.remove());
+```
+```js
+const forecast = FileAttachment("./data/forecast.json").json();
```
-.
-├─ docs
-│ ├─ .observablehq
-│ │ └─ cache
-│ ├─ components
-│ │ └─ dotmap.js
-│ ├─ data
-│ │ └─ quakes.csv.ts
-│ ├─ quakes.md
-│ └─ index.md
-├─ .gitignore
-├─ README.md
-├─ observablehq.config.ts
-├─ yarn.lock
-└─ package.json
+
+### Plots
+
+Now let’s add a chart using Observable Plot. Framework includes a variety of recommended libraries by default, including `Plot`, and you can always import more from npm. Replace the `display(forecast)` code block with the following code:
+
+```js run=false
+Plot.plot({
+ title: "Hourly temperature forecast",
+ x: {type: "utc", ticks: "day", label: null},
+ y: {grid: true, inset: 10, label: "Degrees (F)"},
+ marks: [
+ Plot.lineY(forecast.properties.periods, {
+ x: "startTime",
+ y: "temperature",
+ z: null, // varying color, not series
+ stroke: "temperature",
+ curve: "step-after"
+ })
+ ]
+})
```
-#### `docs`
+
Because this is JSON data, startTime is a string rather than a Date. Setting the type of the x scale to utc tells Plot to interpret these values as temporal rather than ordinal.
-This is the “source root” — where your source files live. It doesn’t have to be named `docs`, but that’s the default; you can change it using the **root** [config option](./config). Pages go here. Each page is a Markdown file. The Observable CLI uses [file-based routing](./routing), which means that the name of the file controls where the page is served. You can create as many pages as you like. Use folders to organize your pages.
+You should now see:
-#### `docs/.observablehq/cache`
+
+
+ Using Plot to make a chart.
+
-This is where the [data loader](./loaders) cache lives. You don’t typically have to worry about this since it’s autogenerated when the first data loader is referenced. You can `rm -rf docs/.observablehq/cache` to clean the cache and force data loaders to re-run.
+
Try editing forecast.json.js to change the longitude and latitude to a different location! After you save, Framework will run the data loader again and push the new data to the client to update the chart. For example, to see the current forecast at the White House:
const longitude = -77.04;
+const latitude = 38.90;
-#### `docs/.observablehq/deploy.json`
+As before, the code block contains an expression (a call to `Plot.plot`) and hence `display` is called implicitly. And since this expression evaluates to a DOM element (a `` containing an `