Skip to content

Commit

Permalink
Merge branch 'main' into fil/event.target
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Nov 22, 2024
2 parents d8ab40b + 8efdcf4 commit 23cb090
Show file tree
Hide file tree
Showing 46 changed files with 121 additions and 55 deletions.
6 changes: 3 additions & 3 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,11 @@ footer: ({path}) => `<a href="https://github.com/example/test/blob/main/src${pat

The base path when serving the site. Currently this only affects the custom 404 page, if any.

## preserveIndex <a href="https://github.com/observablehq/framework/pulls/1784" class="observablehq-version-badge" data-version="prerelease" title="Added in #1784"></a>
## preserveIndex <a href="https://github.com/observablehq/framework/releases/tag/v1.13.0" class="observablehq-version-badge" data-version="^1.13.0" title="Added in 1.13.0"></a>

Whether page links should preserve `/index` for directories. Defaults to false. If true, a link to `/` will be formatted as `/index` if the **preserveExtension** option is false or `/index.html` if the **preserveExtension** option is true.

## preserveExtension <a href="https://github.com/observablehq/framework/pulls/1784" class="observablehq-version-badge" data-version="prerelease" title="Added in #1784"></a>
## preserveExtension <a href="https://github.com/observablehq/framework/releases/tag/v1.13.0" class="observablehq-version-badge" data-version="^1.13.0" title="Added in 1.13.0"></a>

Whether page links should preserve the `.html` extension. Defaults to false. If true, a link to `/foo` will be formatted as `/foo.html`.

Expand Down Expand Up @@ -301,7 +301,7 @@ export default {
};
```

## duckdb <a href="https://github.com/observablehq/framework/pull/1734" class="observablehq-version-badge" data-version="prerelease" title="Added in #1734"></a>
## duckdb <a href="https://github.com/observablehq/framework/releases/tag/v1.13.0" class="observablehq-version-badge" data-version="^1.13.0" title="Added in 1.13.0"></a>

The **duckdb** option configures [self-hosting](./lib/duckdb#self-hosting-of-extensions) and loading of [DuckDB extensions](./lib/duckdb#extensions) for use in [SQL code blocks](./sql) and the `sql` and `DuckDBClient` built-ins. For example, a geospatial data app might enable the [`spatial`](https://duckdb.org/docs/extensions/spatial/overview.html) and [`h3`](https://duckdb.org/community_extensions/extensions/h3.html) extensions like so:

Expand Down
6 changes: 4 additions & 2 deletions docs/embeds.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ In addition to standalone apps, you can use Framework to embed interactive views
- [exported files](#exported-files) for hotlinking images, data, and other assets, or
- [iframe embeds](#iframe-embeds) for compatibility.

You can deploy to Observable Cloud for [additional features](https://observablehq.com/documentation/data-apps/embeds)<a href="https://github.com/observablehq/framework/releases/tag/v1.13.0" class="observablehq-version-badge" data-version="^1.13.0" title="Added in v1.13.0"></a> including secure private embedding on approved domains and analytics to see which exports are used.

## Exported modules

Framework allows [JavaScript modules](./imports#local-imports) to be exported for use in another application. Exported modules are vanilla JavaScript and behave identically in an external web application as on a Framework page. As with local modules, exported modules can load data from a [static file](./files) or a [data loader](./data-loaders), [import](./imports) other local modules, and import libraries from [npm](./imports#npm-imports) or [JSR](./imports#jsr-imports).
Expand Down Expand Up @@ -75,9 +77,9 @@ document.body.append(await Chart());
</script>
```

<div class="warning" label="Coming soon">
<div class="tip">

Observable Cloud support for cross-origin resource sharing (CORS) is not yet generally available and is needed for exported modules. If you are interested in beta-testing this feature, please [email us](mailto:support@observablehq.com). For public apps, you can use a third-party host supporting CORS such as GitHub Pages.
Observable Cloud supports [cross-origin resource sharing](https://observablehq.com/documentation/data-apps/embeds#cors) (CORS), which is needed for exported modules.

</div>

Expand Down
6 changes: 3 additions & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ This command will ask you a series of questions in order to initialize your new
<span class="muted">│</span> <span class="muted">Yes, include sample files</span>
<span class="muted">│</span>
<span class="green">◇</span> Install dependencies?
<span class="muted">│</span> <span class="muted">Yes, via yarn</span>
<span class="muted">│</span> <span class="muted">Yes, via npm</span>
<span class="muted">│</span>
<span class="green">◇</span> Initialize a git repository?
<span class="muted">│</span> <span class="muted">Yes</span>
Expand All @@ -122,7 +122,7 @@ This command will ask you a series of questions in order to initialize your new
<span class="green">◇</span> Next steps… <span class="muted">──────────╮</span>
<span class="muted">│</span> <span class="muted">│</span>
<span class="muted">│</span> <span class="focus">cd hello-framework</span> <span class="muted">│</span>
<span class="muted">│</span> <span class="focus">yarn dev</span> <span class="muted">│</span>
<span class="muted">│</span> <span class="focus">npm run dev</span> <span class="muted">│</span>
<span class="muted">│</span> <span class="muted">│</span>
<span class="muted">├────────────────────────╯</span>
<span class="muted">│</span>
Expand All @@ -148,7 +148,7 @@ Or with Yarn:

You should see something like this:

<pre data-copy="none"><b class="green">Observable Framework</b> v1.12.0
<pre data-copy="none"><b class="green">Observable Framework</b> v1.13.0
↳ <u><a href="http://127.0.0.1:3000/" style="color: inherit;">http://127.0.0.1:3000/</a></u></pre>

<div class="note">
Expand Down
4 changes: 2 additions & 2 deletions docs/lib/duckdb.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const sql = DuckDBClient.sql({quakes: `https://earthquake.usgs.gov/earthquakes/f
SELECT * FROM quakes ORDER BY updated DESC;
```

## Extensions <a href="https://github.com/observablehq/framework/pull/1734" class="observablehq-version-badge" data-version="prerelease" title="Added in #1734"></a>
## Extensions <a href="https://github.com/observablehq/framework/releases/tag/v1.13.0" class="observablehq-version-badge" data-version="^1.13.0" title="Added in 1.13.0"></a>

[DuckDB extensions](https://duckdb.org/docs/extensions/overview.html) extend DuckDB’s functionality, adding support for additional file formats, new types, and domain-specific functions. For example, the [`json` extension](https://duckdb.org/docs/data/json/overview.html) provides a `read_json` method for reading JSON files:

Expand Down Expand Up @@ -195,7 +195,7 @@ In the future, we’d like to allow DuckDB to be configured globally (beyond jus

## Versioning

Framework currently uses [DuckDB-Wasm 1.29.0](https://github.com/duckdb/duckdb-wasm/releases/tag/v1.29.0) <a href="https://github.com/observablehq/framework/pull/1734" class="observablehq-version-badge" data-version="prerelease" title="Added in #1734"></a>, which aligns with [DuckDB 1.1.1](https://github.com/duckdb/duckdb/releases/tag/v1.1.1). You can load a different version of DuckDB-Wasm by importing `npm:@duckdb/duckdb-wasm` directly, for example:
Framework currently uses [DuckDB-Wasm 1.29.0](https://github.com/duckdb/duckdb-wasm/releases/tag/v1.29.0) <a href="https://github.com/observablehq/framework/releases/tag/v1.13.0" class="observablehq-version-badge" data-version="^1.13.0" title="Added in 1.13.0"></a>, which aligns with [DuckDB 1.1.1](https://github.com/duckdb/duckdb/releases/tag/v1.1.1). You can load a different version of DuckDB-Wasm by importing `npm:@duckdb/duckdb-wasm` directly, for example:

```js run=false
import * as duckdb from "npm:@duckdb/[email protected]";
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@observablehq/framework",
"license": "ISC",
"version": "1.12.0",
"version": "1.13.0",
"type": "module",
"publishConfig": {
"access": "public"
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ function readPages(root: string, md: MarkdownIt): Page[] {
return pages;
}

let currentDate: Date | null = null;
export let currentDate: Date | null = null;

/** For testing only! */
export function setCurrentDate(date: Date | null): void {
Expand Down
3 changes: 2 additions & 1 deletion src/javascript/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {extname, join} from "node:path/posix";
import type {Program} from "acorn";
import type {TransformOptions} from "esbuild";
import {transform, transformSync} from "esbuild";
import {currentDate} from "../config.js";
import {resolveJsrImport} from "../jsr.js";
import {resolveNodeImport} from "../node.js";
import {resolveNpmImport} from "../npm.js";
Expand Down Expand Up @@ -199,7 +200,7 @@ export function getFileInfo(root: string, path: string): FileInfo | undefined {
const stat = statSync(key);
if (!stat.isFile()) return; // ignore non-files
accessSync(key, constants.R_OK); // verify that file is readable
mtimeMs = Math.floor(stat.mtimeMs);
mtimeMs = Math.floor((currentDate ?? stat.mtimeMs) as number);
size = stat.size;
} catch {
fileInfoCache.delete(key); // delete stale entry
Expand Down
2 changes: 1 addition & 1 deletion src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export class LoaderResolver {
getOutputFileHash(name: string): string {
const info = this.getOutputInfo(name);
if (!info) throw new Error(`output file not found: ${name}`);
return info.hash;
return createHash("sha256").update(info.hash).update(String(info.mtimeMs)).digest("hex");
}

getSourceInfo(name: string): FileInfo | undefined {
Expand Down
12 changes: 7 additions & 5 deletions test/build-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import assert from "node:assert";
import {existsSync, readdirSync, statSync} from "node:fs";
import {mkdir, mkdtemp, open, readFile, rename, rm, unlink, writeFile} from "node:fs/promises";
import os from "node:os";
import {extname} from "node:path/posix";
import {join, normalize, relative} from "node:path/posix";
import {PassThrough} from "node:stream";
import {ascending, difference} from "d3-array";
import {ascending, difference, sort} from "d3-array";
import type {BuildManifest} from "../src/build.js";
import {FileBuildEffects, build} from "../src/build.js";
import {normalizeConfig, readConfig, setCurrentDate} from "../src/config.js";
Expand Down Expand Up @@ -32,6 +33,7 @@ const failureTests = ["missing-file", "missing-import"];

describe("build", () => {
before(() => setCurrentDate(new Date("2024-01-10T16:00:00")));
after(() => setCurrentDate(null));
mockJsDelivr();
mockJsr();
mockDuckDB();
Expand Down Expand Up @@ -75,7 +77,10 @@ describe("build", () => {
// renumber the hashes so they are sequential. This way we don’t have to
// update the test snapshots whenever Framework’s client code changes. We
// make an exception for minisearch.json because to test the content.
for (const path of findFiles(join(outputDir, "_observablehq"))) {
for (const path of sort(
findFiles(join(outputDir, "_observablehq")),
(a, b) => ascending(extname(a) === ".css", extname(b) === ".css") || ascending(a, b)
)) {
const match = /^((.+)\.[0-9a-f]{8})\.(\w+)$/.exec(path);
if (!match) throw new Error(`no hash found: ${path}`);
const [, key, name, ext] = match;
Expand Down Expand Up @@ -233,9 +238,6 @@ class TestEffects extends FileBuildEffects {
contents = contents.replace(/^(\s*<script>\{).*(\}<\/script>)$/gm, "$1/* redacted init script */$2");
contents = contents.replace(/(registerFile\(.*,"lastModified":)\d+(,"size":\d+.*\))/gm, "$1/* ts */1706742000000$2"); // prettier-ignore
}
if (typeof contents === "string" && outputPath.endsWith(".js")) {
contents = contents.replace(/(FileAttachment\(.*,"lastModified":)\d+(,"size":\d+.*\))/gm, "$1/* ts */1706742000000$2"); // prettier-ignore
}
return super.writeFile(outputPath, contents);
}
}
Expand Down
1 change: 1 addition & 0 deletions test/config-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const DUCKDB_DEFAULTS: DuckDBConfig = {

describe("readConfig(undefined, root)", () => {
before(() => setCurrentDate(new Date("2024-01-10T16:00:00")));
after(() => setCurrentDate(null));
it("imports the config file at the specified root", async () => {
const {md, loaders, paths, normalizePath, ...config} = await readConfig(undefined, "test/input/build/config");
assert(md instanceof MarkdownIt);
Expand Down
1 change: 1 addition & 0 deletions test/deploy-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ const DEPLOY_CONFIG: DeployConfig & {projectId: string; projectSlug: string; wor

describe("deploy", () => {
before(() => setCurrentDate(new Date("2024-01-10T16:00:00")));
after(() => setCurrentDate(null));
mockObservableApi();
mockJsDelivr();

Expand Down
3 changes: 3 additions & 0 deletions test/input/build/params2/[code]/analytics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {FileAttachment} from "npm:@observablehq/stdlib";

FileAttachment("data.json");
1 change: 1 addition & 0 deletions test/input/build/params2/[code]/data.json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
process.stdout.write(JSON.stringify({a: 1}));
5 changes: 5 additions & 0 deletions test/input/build/params2/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# test

```js
import "/code/analytics.js"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {test} from "./test.a9a4ef0e.js";

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import {FileAttachment} from "../_observablehq/stdlib.00000003.js";

export const test = FileAttachment({"name":"../test.txt","mimeType":"text/plain","path":"../_file/test.f2ca1bb6.txt","lastModified":/* ts */1706742000000,"size":5}, import.meta.url).text();
export const test = FileAttachment({"name":"../test.txt","mimeType":"text/plain","path":"../_file/test.f2ca1bb6.txt","lastModified":1704931200000,"size":5}, import.meta.url).text();
6 changes: 3 additions & 3 deletions test/output/build/data-loaders/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<link rel="modulepreload" href="./_observablehq/client.00000001.js">
<link rel="modulepreload" href="./_observablehq/runtime.00000002.js">
<link rel="modulepreload" href="./_observablehq/stdlib.00000003.js">
<link rel="modulepreload" href="./_import/import-test.e7269c4e.js">
<link rel="modulepreload" href="./_import/test.86a60bc6.js">
<link rel="modulepreload" href="./_import/import-test.3349a02d.js">
<link rel="modulepreload" href="./_import/test.a9a4ef0e.js">
<script type="module">

import {define} from "./_observablehq/client.00000001.js";
Expand All @@ -23,7 +23,7 @@
registerFile("./test.txt", {"name":"./test.txt","mimeType":"text/plain","path":"./_file/test.f2ca1bb6.txt","lastModified":/* ts */1706742000000,"size":5});

define({id: "05e74070", inputs: ["display"], outputs: ["test"], body: async (display) => {
const {test} = await import("./_import/import-test.e7269c4e.js");
const {test} = await import("./_import/import-test.3349a02d.js");

display(await test);
return {test};
Expand Down
12 changes: 6 additions & 6 deletions test/output/build/duckdb/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
<title>test DuckDB</title>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&amp;display=swap" crossorigin>
<link rel="preload" as="style" href="./_observablehq/theme-air,near-midnight.00000004.css">
<link rel="preload" as="style" href="./_observablehq/stdlib/inputs.00000007.css">
<link rel="preload" as="style" href="./_observablehq/theme-air,near-midnight.00000007.css">
<link rel="preload" as="style" href="./_observablehq/stdlib/inputs.00000006.css">
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&amp;display=swap" crossorigin>
<link rel="stylesheet" type="text/css" href="./_observablehq/theme-air,near-midnight.00000004.css">
<link rel="stylesheet" type="text/css" href="./_observablehq/stdlib/inputs.00000007.css">
<link rel="stylesheet" type="text/css" href="./_observablehq/theme-air,near-midnight.00000007.css">
<link rel="stylesheet" type="text/css" href="./_observablehq/stdlib/inputs.00000006.css">
<link rel="modulepreload" href="./_observablehq/client.00000001.js">
<link rel="modulepreload" href="./_observablehq/runtime.00000002.js">
<link rel="modulepreload" href="./_observablehq/stdlib.00000003.js">
<link rel="modulepreload" href="./_observablehq/stdlib/duckdb.00000005.js">
<link rel="modulepreload" href="./_observablehq/stdlib/inputs.00000006.js">
<link rel="modulepreload" href="./_observablehq/stdlib/duckdb.00000004.js">
<link rel="modulepreload" href="./_observablehq/stdlib/inputs.00000005.js">
<link rel="modulepreload" href="./_npm/@duckdb/[email protected]/cd372fb8.js">
<link rel="modulepreload" href="./_npm/[email protected]/cd372fb8.js">
<link rel="modulepreload" href="./_npm/[email protected]/cd372fb8.js">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {FileAttachment} from "../_observablehq/stdlib.00000003.js";
import * as Plot from "../_npm/@observablehq/[email protected]/cd372fb8.js";

export async function Chart() {
const gistemp = await FileAttachment({"name":"../lib/gistemp.csv","mimeType":"text/csv","path":"../_file/lib/gistemp.1cf298b1.csv","lastModified":/* ts */1706742000000,"size":97}, import.meta.url).csv({typed: true});
const gistemp = await FileAttachment({"name":"../lib/gistemp.csv","mimeType":"text/csv","path":"../_file/lib/gistemp.1cf298b1.csv","lastModified":1704931200000,"size":97}, import.meta.url).csv({typed: true});
return Plot.plot({
y: {grid: true},
color: {scheme: "burd"},
Expand Down
2 changes: 1 addition & 1 deletion test/output/build/embed/chart.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import "./_observablehq/stdlib.00000003.js";
import "./_npm/@observablehq/[email protected]/cd372fb8.js";
import "./_npm/[email protected]/cd372fb8.js";
export * from "./_import/chart.2ce91e05.js";
export * from "./_import/chart.4140747c.js";

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {FileAttachment} from "../_observablehq/stdlib.00000003.js";
export {fooCsvData, fooJsonData} from "./foo/foo.666599bc.js";
export const topJsonData = await FileAttachment({"name":"../top-data.json","mimeType":"application/json","path":"../_file/top-data.67358ed8.json","lastModified":/* ts */1706742000000,"size":10}, import.meta.url).json();
export const topCsvData = await FileAttachment({"name":"../top-data.csv","mimeType":"text/csv","path":"../_file/top-data.24ef4634.csv","lastModified":/* ts */1706742000000,"size":72}, import.meta.url).text();
export {fooCsvData, fooJsonData} from "./foo/foo.0cc12e18.js";
export const topJsonData = await FileAttachment({"name":"../top-data.json","mimeType":"application/json","path":"../_file/top-data.67358ed8.json","lastModified":1704931200000,"size":10}, import.meta.url).json();
export const topCsvData = await FileAttachment({"name":"../top-data.csv","mimeType":"text/csv","path":"../_file/top-data.24ef4634.csv","lastModified":1704931200000,"size":72}, import.meta.url).text();
4 changes: 2 additions & 2 deletions test/output/build/fetches/foo.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<link rel="modulepreload" href="./_observablehq/client.00000001.js">
<link rel="modulepreload" href="./_observablehq/runtime.00000002.js">
<link rel="modulepreload" href="./_observablehq/stdlib.00000003.js">
<link rel="modulepreload" href="./_import/foo/foo.666599bc.js">
<link rel="modulepreload" href="./_import/foo/foo.0cc12e18.js">
<script type="module">

import {define} from "./_observablehq/client.00000001.js";
Expand All @@ -23,7 +23,7 @@
registerFile("./foo/foo-data.json", {"name":"./foo/foo-data.json","mimeType":"application/json","path":"./_file/foo/foo-data.67358ed8.json","lastModified":/* ts */1706742000000,"size":10});

define({id: "47a695da", inputs: ["display"], outputs: ["fooJsonData","fooCsvData"], body: async (display) => {
const {fooJsonData, fooCsvData} = await import("./_import/foo/foo.666599bc.js");
const {fooJsonData, fooCsvData} = await import("./_import/foo/foo.0cc12e18.js");

display(fooJsonData);
display(fooCsvData);
Expand Down
Loading

0 comments on commit 23cb090

Please sign in to comment.