@observablehq/notebook-kit 2.1.5 → 2.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/observablehq/notebook-kit.git"
7
7
  },
8
- "version": "2.1.5",
8
+ "version": "2.1.7",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "test": "vitest",
@@ -1,8 +1,15 @@
1
- import type { ImportDeclaration } from "acorn";
1
+ import type { ImportWithDeclaration } from "@observablehq/parser";
2
+ import type { ImportDeclaration, ImportSpecifier } from "acorn";
3
+ import type { Identifier } from "acorn";
4
+ import type { NamedImportSpecifier } from "../imports.js";
2
5
  /** If specifier is an observable: protocol import, resolves it. */
3
6
  export declare function resolveObservableImport(specifier: string): string;
4
7
  export declare function isObservableImport(node: ImportDeclaration, specifier: string): boolean;
5
8
  /** Note: mutates inputs! */
6
- export declare function renderObservableImport(source: string, node: ImportDeclaration, inputs: string[]): string;
9
+ export declare function renderObservableImport(source: string, node: ImportDeclaration | ImportWithDeclaration, inputs: string[]): string;
10
+ export declare function toSpecialIdentifier(identifier: Identifier, type: "viewof" | "mutable"): Identifier;
11
+ export declare function toSpecialImportSpecifier(specifier: ImportSpecifier, type: "viewof" | "mutable"): ImportSpecifier;
12
+ export declare function isViewImport(node: NamedImportSpecifier): node is ImportSpecifier;
13
+ export declare function isMutableImport(node: NamedImportSpecifier): node is ImportSpecifier;
7
14
  /** Turns e.g. "viewof$foo" into "viewof foo", and "$$" into "$". */
8
15
  export declare function dedollar(input: string): string;
@@ -1,4 +1,4 @@
1
- import { getImportedName, getLocalName } from "../imports.js";
1
+ import { flatMapImportSpecifiers, getImportedName, getLocalName } from "../imports.js";
2
2
  const CODE_DOLLAR = 36;
3
3
  /** If specifier is an observable: protocol import, resolves it. */
4
4
  export function resolveObservableImport(specifier) {
@@ -21,20 +21,60 @@ export function renderObservableImport(source, node, inputs) {
21
21
  if (!inputs.includes("@variable"))
22
22
  inputs.push("@variable");
23
23
  return `(import(${JSON.stringify(source)}).then((_) => {
24
- const module = __variable._module._runtime.module(_.default);
25
- const outputs = new Map(Array.from(__variable._outputs, (v) => [v._name, v]));${node.specifiers
26
- .map((specifier) => {
27
- if (specifier.type === "ImportNamespaceSpecifier")
28
- throw new SyntaxError("observable namespace imports are not supported");
29
- const iname = dedollar(getImportedName(specifier));
30
- const lname = getLocalName(specifier);
24
+ const module = __variable._module._runtime.module(_.default)${"injections" in node ? `.derive([${renderObservableInjections(node)}], __variable._module)` : ""};
25
+ const outputs = new Map(Array.from(__variable._outputs, (v) => [v._name, v]));${flatMapImportSpecifiers(node, (specifier) => {
26
+ const i = dedollar(getImportedName(specifier));
27
+ const l = getLocalName(specifier);
31
28
  return `
32
- outputs.get(${JSON.stringify(lname)})?.import(${JSON.stringify(iname)}${iname === lname ? "" : `, ${JSON.stringify(lname)}`}, module);`;
33
- })
34
- .join("")}
29
+ outputs.get(${JSON.stringify(l)})?.import(${JSON.stringify(i)}${i === l ? "" : `, ${JSON.stringify(l)}`}, module);`;
30
+ }).join("")}
35
31
  return {};
36
32
  }))`;
37
33
  }
34
+ function renderObservableInjections(node) {
35
+ return node.injections
36
+ .map((node) => {
37
+ if (node.imported.type !== "Identifier")
38
+ throw new SyntaxError("unexpected import specifier");
39
+ const imported = node.imported.name;
40
+ const local = node.local.name;
41
+ let injection;
42
+ if (imported === local) {
43
+ injection = `"${local}"`;
44
+ if (node.view)
45
+ injection += `, "viewof ${local}"`;
46
+ if (node.mutable)
47
+ injection += `, "mutable ${local}"`;
48
+ }
49
+ else {
50
+ injection = `{name: "${imported}", alias: "${local}"}`;
51
+ if (node.view)
52
+ injection += `, {name: "viewof ${imported}", alias: "viewof ${local}"}`;
53
+ if (node.mutable)
54
+ injection += `, {name: "mutable ${imported}", alias: "mutable ${local}"}`;
55
+ }
56
+ return injection;
57
+ })
58
+ .join(", ");
59
+ }
60
+ export function toSpecialIdentifier(identifier, type) {
61
+ return { ...identifier, name: `${type}$${identifier.name}` };
62
+ }
63
+ export function toSpecialImportSpecifier(specifier, type) {
64
+ if (specifier.imported.type !== "Identifier")
65
+ throw new SyntaxError("unexpected import specifier");
66
+ return {
67
+ ...specifier,
68
+ imported: toSpecialIdentifier(specifier.imported, type),
69
+ local: toSpecialIdentifier(specifier.local, type)
70
+ };
71
+ }
72
+ export function isViewImport(node) {
73
+ return "view" in node && !!node.view;
74
+ }
75
+ export function isMutableImport(node) {
76
+ return "mutable" in node && !!node.mutable;
77
+ }
38
78
  /** Turns e.g. "viewof$foo" into "viewof foo", and "$$" into "$". */
39
79
  export function dedollar(input) {
40
80
  const start = 0;
@@ -1,7 +1,8 @@
1
- import type { CallExpression, MemberExpression, Node } from "acorn";
2
- import type { ImportDefaultSpecifier, ImportSpecifier } from "acorn";
1
+ import type { CallExpression, ImportDeclaration, MemberExpression, Node } from "acorn";
2
+ import type { ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier } from "acorn";
3
3
  import type { Sourcemap } from "./sourcemap.js";
4
- type NamedImportSpecifier = ImportSpecifier | ImportDefaultSpecifier;
4
+ export type NamedImportSpecifier = ImportSpecifier | ImportDefaultSpecifier;
5
+ export type AnyImportSpecifier = ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier;
5
6
  /** Throws a syntax error if any export declarations are found. */
6
7
  export declare function checkExports(body: Node, { input }: {
7
8
  input: string;
@@ -21,4 +22,4 @@ export declare function rewriteImportExpressions(output: Sourcemap, body: Node,
21
22
  export declare function rewriteImportDeclarations(output: Sourcemap, body: Node, inputs: string[], { resolveLocalImports }?: RewriteImportOptions): void;
22
23
  export declare function getLocalName(node: NamedImportSpecifier): string;
23
24
  export declare function getImportedName(node: NamedImportSpecifier): string;
24
- export {};
25
+ export declare function flatMapImportSpecifiers<T>(node: ImportDeclaration, f: (node: NamedImportSpecifier) => T): T[];
@@ -1,7 +1,8 @@
1
1
  import { resolveJsrImport } from "./imports/jsr.js";
2
2
  import { resolveNpmImport } from "./imports/npm.js";
3
- import { resolveObservableImport } from "./imports/observable.js";
4
- import { isObservableImport, renderObservableImport } from "./imports/observable.js";
3
+ import { isObservableImport, isMutableImport, isViewImport } from "./imports/observable.js";
4
+ import { resolveObservableImport, renderObservableImport } from "./imports/observable.js";
5
+ import { toSpecialImportSpecifier } from "./imports/observable.js";
5
6
  import { getStringLiteralValue, isStringLiteral } from "./strings.js";
6
7
  import { syntaxError } from "./syntaxError.js";
7
8
  import { simple } from "./walk.js";
@@ -138,9 +139,20 @@ function isNamedSpecifier(node) {
138
139
  }
139
140
  function rewriteImportSpecifiers(node) {
140
141
  return node.specifiers.some(isNamedSpecifier)
141
- ? `{${node.specifiers.filter(isNamedSpecifier).map(rewriteImportSpecifier).join(", ")}}`
142
+ ? `{${flatMapImportSpecifiers(node, rewriteImportSpecifier).join(", ")}}`
142
143
  : (node.specifiers.find(isNamespaceSpecifier)?.local.name ?? "{}");
143
144
  }
145
+ export function flatMapImportSpecifiers(node, f) {
146
+ return node.specifiers.flatMap((node) => {
147
+ if (node.type === "ImportNamespaceSpecifier")
148
+ throw new SyntaxError("unexpected namespace specifier");
149
+ return isViewImport(node)
150
+ ? [f(node), f(toSpecialImportSpecifier(node, "viewof"))]
151
+ : isMutableImport(node)
152
+ ? [f(node), f(toSpecialImportSpecifier(node, "mutable"))]
153
+ : f(node);
154
+ });
155
+ }
144
156
  function rewriteImportSpecifier(node) {
145
157
  return getImportedName(node) === getLocalName(node)
146
158
  ? getLocalName(node)
@@ -1,5 +1,6 @@
1
1
  import { parseCell } from "@observablehq/parser";
2
2
  import { rewriteFileExpressions } from "./files.js";
3
+ import { flatMapImportSpecifiers, rewriteImportDeclarations } from "./imports.js";
3
4
  import { Sourcemap } from "./sourcemap.js";
4
5
  import { transpileJavaScript } from "./transpile.js";
5
6
  import { simple } from "./walk.js";
@@ -7,14 +8,12 @@ export function transpileObservable(input, options) {
7
8
  const cell = parseCell(input);
8
9
  if (!cell.body)
9
10
  return transpileJavaScript(input);
11
+ if (isImportCell(cell))
12
+ return transpileObservableImport(input, cell, options);
10
13
  if (cell.tag)
11
14
  throw new Error("tagged ojs cells are not supported");
12
15
  const output = new Sourcemap(input).trim();
13
16
  rewriteSpecialReferences(output, cell.body);
14
- if (cell.body.type === "ImportDeclaration") {
15
- rewriteImportSource(output, cell.body);
16
- return transpileJavaScript(String(output));
17
- }
18
17
  if (options?.resolveFiles)
19
18
  rewriteFileExpressions(output, cell.body);
20
19
  const inputs = Array.from(new Set(cell.references.map(asReference)));
@@ -42,13 +41,46 @@ export function transpileObservable(input, options) {
42
41
  secrets: new Set(cell.secrets.keys())
43
42
  };
44
43
  }
45
- /** Rewrite bare module specifiers to have the observable: protocol. */
46
- function rewriteImportSource(output, body) {
47
- const specifier = body.source.value;
48
- if (typeof specifier === "string" && !/^\w+:/.test(specifier)) {
49
- output.insertLeft(body.source.start + 1, "observable:");
44
+ function isImportCell(cell) {
45
+ return cell.body.type === "ImportDeclaration";
46
+ }
47
+ function transpileObservableImport(input, cell, options) {
48
+ const output = new Sourcemap(input).trim();
49
+ const inputs = ["@variable"];
50
+ const declarations = flatMapImportSpecifiers(cell.body, (s) => s.local);
51
+ const outputs = Array.from(new Set(declarations.map(asDeclaration)));
52
+ transformObservableImport(cell.body);
53
+ rewriteImportDeclarations(output, cell.body, inputs, options);
54
+ output.insertLeft(0, `async (__variable) => {\n`);
55
+ if (outputs.length > 0)
56
+ output.insertRight(input.length, `\nreturn {${outputs}};`);
57
+ output.insertRight(input.length, "\n}");
58
+ const body = String(output);
59
+ return {
60
+ body,
61
+ inputs,
62
+ outputs,
63
+ autodisplay: false,
64
+ files: new Set(),
65
+ secrets: new Set(),
66
+ databases: new Set()
67
+ };
68
+ }
69
+ /** Mutates the given import declaration to be an Observable import. */
70
+ function transformObservableImport(body) {
71
+ const source = body.source.value;
72
+ if (typeof source === "string" && !/^\w+:/.test(source)) {
73
+ body.source.value = `observable:${source}`;
50
74
  }
51
- output.insertRight(body.end, ";");
75
+ body.attributes = [
76
+ {
77
+ type: "ImportAttribute",
78
+ key: { type: "Literal", value: "type", start: 0, end: 0 },
79
+ value: { type: "Literal", value: "observable", start: 0, end: 0 },
80
+ start: 0,
81
+ end: 0
82
+ }
83
+ ];
52
84
  }
53
85
  /** Rewrite viewof x ↦ viewof$x, and mutable x ↦ mutable$x.value. */
54
86
  function rewriteSpecialReferences(output, body) {
@@ -58,28 +90,9 @@ function rewriteSpecialReferences(output, body) {
58
90
  },
59
91
  ViewExpression(node) {
60
92
  output.replaceLeft(node.start, node.end, asReference(node));
61
- },
62
- ImportSpecifier(node) {
63
- const inode = node;
64
- const prefix = inode.view ? "viewof$" : inode.mutable ? "mutable$" : null;
65
- if (prefix) {
66
- const imported = asImportName(node.imported);
67
- output.replaceLeft(node.start, node.imported.start, prefix);
68
- if (node.imported === node.local) {
69
- output.insertLeft(node.start, `${imported},`);
70
- }
71
- else {
72
- const local = asImportName(node.local);
73
- output.insertLeft(node.start, `${imported} as ${local},`);
74
- output.insertLeft(node.local.start, prefix);
75
- }
76
- }
77
93
  }
78
94
  });
79
95
  }
80
- function asImportName(ref) {
81
- return ref.type === "Identifier" ? ref.name : ref.raw;
82
- }
83
96
  function asReference(ref) {
84
97
  return ref.type === "ViewExpression"
85
98
  ? `viewof$${ref.id.name}`
@@ -84,6 +84,7 @@
84
84
  }
85
85
 
86
86
  .observablehq--cell {
87
+ position: relative;
87
88
  margin: 17px 0;
88
89
  min-height: 1px; /* prevent margin collapse when empty */
89
90
  }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/observablehq/notebook-kit.git"
7
7
  },
8
- "version": "2.1.5",
8
+ "version": "2.1.7",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "test": "vitest",