From ab8b416597a05d0d3d28940169a777a09cc7113c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Mon, 15 Jun 2026 15:29:31 +0200 Subject: [PATCH 1/5] only set the display and view builtins in js and ts cells --- .../__snapshots__/transpile.test.ts.snap | 25 +++++++++++++++++++ src/javascript/transpile.test.ts | 9 +++++++ src/javascript/transpile.ts | 3 +++ src/runtime/define.ts | 6 +++-- src/vite/observable.ts | 3 ++- 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/javascript/__snapshots__/transpile.test.ts.snap b/src/javascript/__snapshots__/transpile.test.ts.snap index 4461a86..959a77f 100644 --- a/src/javascript/__snapshots__/transpile.test.ts.snap +++ b/src/javascript/__snapshots__/transpile.test.ts.snap @@ -9,6 +9,7 @@ return ( ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -26,6 +27,7 @@ x + y ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "x", @@ -46,6 +48,7 @@ await z ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "z", @@ -65,6 +68,7 @@ display(1), display(2) ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "display", @@ -83,6 +87,7 @@ const x = 1, y = 2; return {x,y}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -101,6 +106,7 @@ exports[`transpiles JavaScript programs 2`] = ` x + y; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "x", @@ -119,6 +125,7 @@ exports[`transpiles JavaScript programs 3`] = ` await z; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "z", @@ -567,6 +574,7 @@ const d3 = await import("https://cdn.jsdelivr.net/npm/d3/+esm"); return {d3}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -584,6 +592,7 @@ exports[`transpiles empty cells 1`] = ` }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -665,6 +674,7 @@ return ( ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -682,6 +692,7 @@ new URL("./test", document.baseURI).href ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -699,6 +710,7 @@ return ( ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -821,6 +833,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -847,6 +860,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3@1 return {color}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -873,6 +887,7 @@ const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubbe return {Scrubber}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -899,6 +914,7 @@ const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubbe return {Scrubber}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -925,6 +941,7 @@ const {viewof$rotation} = await (import("https://api.observablehq.com/@rreusser/ return {viewof$rotation}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -946,6 +963,7 @@ const d3 = await import("https://cdn.jsdelivr.net/npm/d3/+esm"); return {d3}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -968,6 +986,7 @@ const {default: _} = await import("https://cdn.jsdelivr.net/npm/lodash/+esm").th return {_}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -986,6 +1005,7 @@ const [{}, {}] = await Promise.all([import("https://cdn.jsdelivr.net/npm/d3/+esm }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -1008,6 +1028,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -1034,6 +1055,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3@1 return {color}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -1060,6 +1082,7 @@ const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubbe return {Scrubber}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -1086,6 +1109,7 @@ const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubbe return {Scrubber}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -1112,6 +1136,7 @@ const {viewof$rotation} = await (import("https://api.observablehq.com/@rreusser/ return {viewof$rotation}; }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", diff --git a/src/javascript/transpile.test.ts b/src/javascript/transpile.test.ts index 5378085..dd11238 100644 --- a/src/javascript/transpile.test.ts +++ b/src/javascript/transpile.test.ts @@ -53,6 +53,15 @@ it("transpiles static imports with {type: 'observable'}", () => { expect(transpile('import {viewof$rotation} from "https://api.observablehq.com/@rreusser/drawing-3d-objects-with-svg.js?v=4" with {type: "observable"};', "js")).toMatchSnapshot(); }); +it("introduces the display and view builtins for JavaScript and TypeScript only", () => { + expect(transpile("display(1)", "js").displayBuiltins).toBe(true); + expect(transpile("display(1)", "ts").displayBuiltins).toBe(true); + expect(transpile("display(1)", "ojs").displayBuiltins).toBeUndefined(); + expect(transpile("viewof x = 1", "ojs").displayBuiltins).toBeUndefined(); + expect(transpile("Hello ${display}", "md").displayBuiltins).toBeUndefined(); + expect(transpile("

${display}", "html").displayBuiltins).toBeUndefined(); +}); + it("transpiles Observable JavaScript imports", () => { expect(transpile('import {color} from "2d6bf7be248d66f3"', "ojs")).toMatchSnapshot(); expect(transpile('import {color} from "2d6bf7be248d66f3@173"', "ojs")).toMatchSnapshot(); diff --git a/src/javascript/transpile.ts b/src/javascript/transpile.ts index eda766c..bc99f1b 100644 --- a/src/javascript/transpile.ts +++ b/src/javascript/transpile.ts @@ -20,6 +20,8 @@ export type TranspiledJavaScript = { output?: string; /** whether to implicitly display the body value (e.g., an expression) */ autodisplay?: boolean; + /** whether to introduce the display and view builtins (js and ts cells only) */ + displayBuiltins?: boolean; /** whether to implicitly derive a view; requires viewof output */ autoview?: boolean; /** whether to implicitly derive a mutable; requires mutable output */ @@ -70,6 +72,7 @@ export function transpile( ? transpileJavaScript(transpileTemplate(cell), options) : transpileJavaScript(input, options); if (transpiled.output === undefined) transpiled.output = cell.output; + if (mode === "js" || mode === "ts") transpiled.displayBuiltins = true; if (cell.hidden) transpiled.autodisplay = false; else if (mode !== "js" && mode !== "ts" && mode !== "ojs") { transpiled.autodisplay = !!input; diff --git a/src/runtime/define.ts b/src/runtime/define.ts index 89d07df..20da8c0 100644 --- a/src/runtime/define.ts +++ b/src/runtime/define.ts @@ -22,6 +22,8 @@ export type Definition = { output?: string; /** whether to display this cell’s singular output automatically */ autodisplay?: boolean; + /** whether to introduce the display and view builtins (js and ts cells only) */ + displayBuiltins?: boolean; /** whether this cell’s singular output is a view */ autoview?: boolean; /** whether this cell’s singular output is a mutable */ @@ -31,12 +33,12 @@ export type Definition = { }; export function define(main: Module, state: DefineState, definition: Definition, observer = observe): void { - const {id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable} = definition; + const {id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable, displayBuiltins = false} = definition; const variables = state.variables; const v = main.variable(observer(state, definition), {shadow: {}}); const vid = output ?? (outputs.length ? `cell ${id}` : null); state.autoclear = true; - if (inputs.includes("display") || inputs.includes("view")) { + if (displayBuiltins && (inputs.includes("display") || inputs.includes("view"))) { let displayVersion = -1; // the variable._version of currently-displayed values const vd = new (v.constructor as typeof Variable)(2, v._module); vd.define( diff --git a/src/vite/observable.ts b/src/vite/observable.ts index 99038a6..3fc6aec 100644 --- a/src/vite/observable.ts +++ b/src/vite/observable.ts @@ -226,7 +226,8 @@ define( assets: ${assets.size > 0 ? "assets" : "undefined"}, autodisplay: ${transpiled.autodisplay}, autoview: ${transpiled.autoview}, - automutable: ${transpiled.automutable} + automutable: ${transpiled.automutable}, + displayBuiltins: ${transpiled.displayBuiltins ?? false} } );`; }) From e28679a2dbd5a79fa8a5f0682f09d672d4626f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Mon, 15 Jun 2026 16:45:50 +0200 Subject: [PATCH 2/5] apply to all cell modes except ojs --- .../__snapshots__/transpile.test.ts.snap | 25 +++++++++++++++++++ src/javascript/transpile.test.ts | 10 ++++---- src/javascript/transpile.ts | 4 +-- src/runtime/define.ts | 2 +- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/javascript/__snapshots__/transpile.test.ts.snap b/src/javascript/__snapshots__/transpile.test.ts.snap index 959a77f..08fc311 100644 --- a/src/javascript/__snapshots__/transpile.test.ts.snap +++ b/src/javascript/__snapshots__/transpile.test.ts.snap @@ -150,6 +150,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -176,6 +177,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -202,6 +204,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -228,6 +231,7 @@ const {color: red} = await (import("https://api.observablehq.com/d/2d6bf7be248d6 return {red}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -255,6 +259,7 @@ const {color, viewof$color} = await (import("https://api.observablehq.com/d/2d6b return {color,viewof$color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -282,6 +287,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -308,6 +314,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -334,6 +341,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -360,6 +368,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -386,6 +395,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -412,6 +422,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -438,6 +449,7 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3@1 return {color}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -465,6 +477,7 @@ const {foo, mutable$foo} = await (import("https://api.observablehq.com/d/2d6bf7b return {foo,mutable$foo}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -493,6 +506,7 @@ const {foo: bar, mutable$foo: mutable$bar} = await (import("https://api.observab return {bar,mutable$bar}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -522,6 +536,7 @@ const {figure, rotation, viewof$rotation} = await (import("https://api.observabl return {figure,rotation,viewof$rotation}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -552,6 +567,7 @@ const {figure, rotation: rot, viewof$rotation: viewof$rot} = await (import("http return {figure,rot,viewof$rot}; }", "databases": Set {}, + "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -609,6 +625,7 @@ exports[`transpiles empty cells 2`] = ` }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -625,6 +642,7 @@ exports[`transpiles empty cells 3`] = ` }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -641,6 +659,7 @@ exports[`transpiles empty cells 4`] = ` }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -657,6 +676,7 @@ exports[`transpiles empty cells 5`] = ` }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -729,6 +749,7 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -749,6 +770,7 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -769,6 +791,7 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -789,6 +812,7 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -809,6 +833,7 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, + "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", diff --git a/src/javascript/transpile.test.ts b/src/javascript/transpile.test.ts index dd11238..78603cf 100644 --- a/src/javascript/transpile.test.ts +++ b/src/javascript/transpile.test.ts @@ -53,13 +53,13 @@ it("transpiles static imports with {type: 'observable'}", () => { expect(transpile('import {viewof$rotation} from "https://api.observablehq.com/@rreusser/drawing-3d-objects-with-svg.js?v=4" with {type: "observable"};', "js")).toMatchSnapshot(); }); -it("introduces the display and view builtins for JavaScript and TypeScript only", () => { +it("introduces the display and view builtins for every mode except Observable JavaScript", () => { expect(transpile("display(1)", "js").displayBuiltins).toBe(true); expect(transpile("display(1)", "ts").displayBuiltins).toBe(true); - expect(transpile("display(1)", "ojs").displayBuiltins).toBeUndefined(); - expect(transpile("viewof x = 1", "ojs").displayBuiltins).toBeUndefined(); - expect(transpile("Hello ${display}", "md").displayBuiltins).toBeUndefined(); - expect(transpile("

${display}", "html").displayBuiltins).toBeUndefined(); + expect(transpile("Hello ${display}", "md").displayBuiltins).toBe(true); + expect(transpile("

${display}", "html").displayBuiltins).toBe(true); + expect(transpile("display(1)", "ojs").displayBuiltins).toBe(false); + expect(transpile("viewof x = 1", "ojs").displayBuiltins).toBe(false); }); it("transpiles Observable JavaScript imports", () => { diff --git a/src/javascript/transpile.ts b/src/javascript/transpile.ts index bc99f1b..0700d90 100644 --- a/src/javascript/transpile.ts +++ b/src/javascript/transpile.ts @@ -20,7 +20,7 @@ export type TranspiledJavaScript = { output?: string; /** whether to implicitly display the body value (e.g., an expression) */ autodisplay?: boolean; - /** whether to introduce the display and view builtins (js and ts cells only) */ + /** whether to introduce the display and view builtins (every mode except ojs) */ displayBuiltins?: boolean; /** whether to implicitly derive a view; requires viewof output */ autoview?: boolean; @@ -72,7 +72,7 @@ export function transpile( ? transpileJavaScript(transpileTemplate(cell), options) : transpileJavaScript(input, options); if (transpiled.output === undefined) transpiled.output = cell.output; - if (mode === "js" || mode === "ts") transpiled.displayBuiltins = true; + transpiled.displayBuiltins ??= mode !== "ojs"; if (cell.hidden) transpiled.autodisplay = false; else if (mode !== "js" && mode !== "ts" && mode !== "ojs") { transpiled.autodisplay = !!input; diff --git a/src/runtime/define.ts b/src/runtime/define.ts index 20da8c0..5c6438a 100644 --- a/src/runtime/define.ts +++ b/src/runtime/define.ts @@ -22,7 +22,7 @@ export type Definition = { output?: string; /** whether to display this cell’s singular output automatically */ autodisplay?: boolean; - /** whether to introduce the display and view builtins (js and ts cells only) */ + /** whether to introduce the display and view builtins; defaults to false */ displayBuiltins?: boolean; /** whether this cell’s singular output is a view */ autoview?: boolean; From bd1e32cfca707f121d16dc36fe804d68e51c4fa0 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 15 Jun 2026 17:32:41 -0600 Subject: [PATCH 3/5] limit change to define --- .../__snapshots__/transpile.test.ts.snap | 50 ------------------- src/javascript/transpile.test.ts | 9 ---- src/javascript/transpile.ts | 3 -- src/runtime/define.ts | 14 +++--- 4 files changed, 7 insertions(+), 69 deletions(-) diff --git a/src/javascript/__snapshots__/transpile.test.ts.snap b/src/javascript/__snapshots__/transpile.test.ts.snap index 08fc311..4461a86 100644 --- a/src/javascript/__snapshots__/transpile.test.ts.snap +++ b/src/javascript/__snapshots__/transpile.test.ts.snap @@ -9,7 +9,6 @@ return ( ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -27,7 +26,6 @@ x + y ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "x", @@ -48,7 +46,6 @@ await z ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "z", @@ -68,7 +65,6 @@ display(1), display(2) ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "display", @@ -87,7 +83,6 @@ const x = 1, y = 2; return {x,y}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -106,7 +101,6 @@ exports[`transpiles JavaScript programs 2`] = ` x + y; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "x", @@ -125,7 +119,6 @@ exports[`transpiles JavaScript programs 3`] = ` await z; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "z", @@ -150,7 +143,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -177,7 +169,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -204,7 +195,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -231,7 +221,6 @@ const {color: red} = await (import("https://api.observablehq.com/d/2d6bf7be248d6 return {red}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -259,7 +248,6 @@ const {color, viewof$color} = await (import("https://api.observablehq.com/d/2d6b return {color,viewof$color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -287,7 +275,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -314,7 +301,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -341,7 +327,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -368,7 +353,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -395,7 +379,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -422,7 +405,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -449,7 +431,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3@1 return {color}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -477,7 +458,6 @@ const {foo, mutable$foo} = await (import("https://api.observablehq.com/d/2d6bf7b return {foo,mutable$foo}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -506,7 +486,6 @@ const {foo: bar, mutable$foo: mutable$bar} = await (import("https://api.observab return {bar,mutable$bar}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -536,7 +515,6 @@ const {figure, rotation, viewof$rotation} = await (import("https://api.observabl return {figure,rotation,viewof$rotation}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -567,7 +545,6 @@ const {figure, rotation: rot, viewof$rotation: viewof$rot} = await (import("http return {figure,rot,viewof$rot}; }", "databases": Set {}, - "displayBuiltins": false, "files": Set {}, "inputs": [ "@variable", @@ -590,7 +567,6 @@ const d3 = await import("https://cdn.jsdelivr.net/npm/d3/+esm"); return {d3}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -608,7 +584,6 @@ exports[`transpiles empty cells 1`] = ` }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -625,7 +600,6 @@ exports[`transpiles empty cells 2`] = ` }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -642,7 +616,6 @@ exports[`transpiles empty cells 3`] = ` }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -659,7 +632,6 @@ exports[`transpiles empty cells 4`] = ` }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -676,7 +648,6 @@ exports[`transpiles empty cells 5`] = ` }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -694,7 +665,6 @@ return ( ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -712,7 +682,6 @@ new URL("./test", document.baseURI).href ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -730,7 +699,6 @@ return ( ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -749,7 +717,6 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -770,7 +737,6 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -791,7 +757,6 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -812,7 +777,6 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -833,7 +797,6 @@ Interpreter("node", {id: -1, format: "buffer"}).run(\`process.stdout.write(\\\`N ) }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "Interpreter", @@ -858,7 +821,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -885,7 +847,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3@1 return {color}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -912,7 +873,6 @@ const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubbe return {Scrubber}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -939,7 +899,6 @@ const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubbe return {Scrubber}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -966,7 +925,6 @@ const {viewof$rotation} = await (import("https://api.observablehq.com/@rreusser/ return {viewof$rotation}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -988,7 +946,6 @@ const d3 = await import("https://cdn.jsdelivr.net/npm/d3/+esm"); return {d3}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -1011,7 +968,6 @@ const {default: _} = await import("https://cdn.jsdelivr.net/npm/lodash/+esm").th return {_}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -1030,7 +986,6 @@ const [{}, {}] = await Promise.all([import("https://cdn.jsdelivr.net/npm/d3/+esm }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [], "output": undefined, @@ -1053,7 +1008,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3.j return {color}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -1080,7 +1034,6 @@ const {color} = await (import("https://api.observablehq.com/d/2d6bf7be248d66f3@1 return {color}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -1107,7 +1060,6 @@ const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubbe return {Scrubber}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -1134,7 +1086,6 @@ const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubbe return {Scrubber}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", @@ -1161,7 +1112,6 @@ const {viewof$rotation} = await (import("https://api.observablehq.com/@rreusser/ return {viewof$rotation}; }", "databases": Set {}, - "displayBuiltins": true, "files": Set {}, "inputs": [ "@variable", diff --git a/src/javascript/transpile.test.ts b/src/javascript/transpile.test.ts index 78603cf..5378085 100644 --- a/src/javascript/transpile.test.ts +++ b/src/javascript/transpile.test.ts @@ -53,15 +53,6 @@ it("transpiles static imports with {type: 'observable'}", () => { expect(transpile('import {viewof$rotation} from "https://api.observablehq.com/@rreusser/drawing-3d-objects-with-svg.js?v=4" with {type: "observable"};', "js")).toMatchSnapshot(); }); -it("introduces the display and view builtins for every mode except Observable JavaScript", () => { - expect(transpile("display(1)", "js").displayBuiltins).toBe(true); - expect(transpile("display(1)", "ts").displayBuiltins).toBe(true); - expect(transpile("Hello ${display}", "md").displayBuiltins).toBe(true); - expect(transpile("

${display}", "html").displayBuiltins).toBe(true); - expect(transpile("display(1)", "ojs").displayBuiltins).toBe(false); - expect(transpile("viewof x = 1", "ojs").displayBuiltins).toBe(false); -}); - it("transpiles Observable JavaScript imports", () => { expect(transpile('import {color} from "2d6bf7be248d66f3"', "ojs")).toMatchSnapshot(); expect(transpile('import {color} from "2d6bf7be248d66f3@173"', "ojs")).toMatchSnapshot(); diff --git a/src/javascript/transpile.ts b/src/javascript/transpile.ts index 0700d90..eda766c 100644 --- a/src/javascript/transpile.ts +++ b/src/javascript/transpile.ts @@ -20,8 +20,6 @@ export type TranspiledJavaScript = { output?: string; /** whether to implicitly display the body value (e.g., an expression) */ autodisplay?: boolean; - /** whether to introduce the display and view builtins (every mode except ojs) */ - displayBuiltins?: boolean; /** whether to implicitly derive a view; requires viewof output */ autoview?: boolean; /** whether to implicitly derive a mutable; requires mutable output */ @@ -72,7 +70,6 @@ export function transpile( ? transpileJavaScript(transpileTemplate(cell), options) : transpileJavaScript(input, options); if (transpiled.output === undefined) transpiled.output = cell.output; - transpiled.displayBuiltins ??= mode !== "ojs"; if (cell.hidden) transpiled.autodisplay = false; else if (mode !== "js" && mode !== "ts" && mode !== "ojs") { transpiled.autodisplay = !!input; diff --git a/src/runtime/define.ts b/src/runtime/define.ts index 5c6438a..dce0c03 100644 --- a/src/runtime/define.ts +++ b/src/runtime/define.ts @@ -22,23 +22,23 @@ export type Definition = { output?: string; /** whether to display this cell’s singular output automatically */ autodisplay?: boolean; - /** whether to introduce the display and view builtins; defaults to false */ - displayBuiltins?: boolean; /** whether this cell’s singular output is a view */ autoview?: boolean; /** whether this cell’s singular output is a mutable */ automutable?: boolean; + /** whether to define the display and view builtins; defaults to false */ + display?: boolean; /** an asset mapping to apply to any autodisplayed assets (e.g., images and videos) */ assets?: Map; }; export function define(main: Module, state: DefineState, definition: Definition, observer = observe): void { - const {id, body, inputs = [], outputs = [], output, autodisplay, autoview, automutable, displayBuiltins = false} = definition; + const {id, body, inputs = [], outputs = [], output} = definition; const variables = state.variables; const v = main.variable(observer(state, definition), {shadow: {}}); const vid = output ?? (outputs.length ? `cell ${id}` : null); state.autoclear = true; - if (displayBuiltins && (inputs.includes("display") || inputs.includes("view"))) { + if (definition.display && (inputs.includes("display") || inputs.includes("view"))) { let displayVersion = -1; // the variable._version of currently-displayed values const vd = new (v.constructor as typeof Variable)(2, v._module); vd.define( @@ -62,15 +62,15 @@ export function define(main: Module, state: DefineState, definition: Definition, vv.define(["display"], (display) => (value: unknown) => input(display(value))); v._shadow.set("view", vv); } - } else if (!autodisplay) { + } else if (!definition.autodisplay) { clear(state); } variables.push(v.define(vid, inputs, body)); if (output != null) { - if (autoview) { + if (definition.autoview) { const o = unprefix(output, "viewof$"); variables.push(main.define(o, [output], input)); - } else if (automutable) { + } else if (definition.automutable) { const o = unprefix(output, "mutable "); const x = `cell ${id}`; v.define(o, [x], ([mutable]) => mutable); // observe live value From 951f4a5989810ceb531167c0bbb1483ae9cf8fa0 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 15 Jun 2026 17:34:22 -0600 Subject: [PATCH 4/5] set display option --- src/vite/observable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vite/observable.ts b/src/vite/observable.ts index 3fc6aec..a9d78b6 100644 --- a/src/vite/observable.ts +++ b/src/vite/observable.ts @@ -223,11 +223,11 @@ define( inputs: ${JSON.stringify(transpiled.inputs)}, outputs: ${JSON.stringify(transpiled.outputs)}, output: ${JSON.stringify(transpiled.output)}, + display: ${cell.mode === "js" || cell.mode === "ts"}, assets: ${assets.size > 0 ? "assets" : "undefined"}, autodisplay: ${transpiled.autodisplay}, autoview: ${transpiled.autoview}, - automutable: ${transpiled.automutable}, - displayBuiltins: ${transpiled.displayBuiltins ?? false} + automutable: ${transpiled.automutable} } );`; }) From 46efe710ef96e6564135df0f875f33abd7a6084c Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 15 Jun 2026 17:38:54 -0600 Subject: [PATCH 5/5] default to true for backwards compatibility --- src/runtime/define.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/runtime/define.ts b/src/runtime/define.ts index dce0c03..fdcac19 100644 --- a/src/runtime/define.ts +++ b/src/runtime/define.ts @@ -26,19 +26,25 @@ export type Definition = { autoview?: boolean; /** whether this cell’s singular output is a mutable */ automutable?: boolean; - /** whether to define the display and view builtins; defaults to false */ + /** whether to define the display and view builtins; defaults to true */ display?: boolean; /** an asset mapping to apply to any autodisplayed assets (e.g., images and videos) */ assets?: Map; }; -export function define(main: Module, state: DefineState, definition: Definition, observer = observe): void { +export function define( + main: Module, + state: DefineState, + definition: Definition, + observer = observe +): void { const {id, body, inputs = [], outputs = [], output} = definition; + const {autodisplay, autoview, automutable, display: defineDisplay = true} = definition; const variables = state.variables; const v = main.variable(observer(state, definition), {shadow: {}}); const vid = output ?? (outputs.length ? `cell ${id}` : null); state.autoclear = true; - if (definition.display && (inputs.includes("display") || inputs.includes("view"))) { + if (defineDisplay && (inputs.includes("display") || inputs.includes("view"))) { let displayVersion = -1; // the variable._version of currently-displayed values const vd = new (v.constructor as typeof Variable)(2, v._module); vd.define( @@ -62,15 +68,15 @@ export function define(main: Module, state: DefineState, definition: Definition, vv.define(["display"], (display) => (value: unknown) => input(display(value))); v._shadow.set("view", vv); } - } else if (!definition.autodisplay) { + } else if (!autodisplay) { clear(state); } variables.push(v.define(vid, inputs, body)); if (output != null) { - if (definition.autoview) { + if (autoview) { const o = unprefix(output, "viewof$"); variables.push(main.define(o, [output], input)); - } else if (definition.automutable) { + } else if (automutable) { const o = unprefix(output, "mutable "); const x = `cell ${id}`; v.define(o, [x], ([mutable]) => mutable); // observe live value