@observablehq/notebook-kit 1.0.0

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.
Files changed (182) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +7 -0
  3. package/dist/bin/build.d.ts +2 -0
  4. package/dist/bin/build.js +63 -0
  5. package/dist/bin/download.d.ts +2 -0
  6. package/dist/bin/download.js +49 -0
  7. package/dist/bin/notebooks.d.ts +2 -0
  8. package/dist/bin/notebooks.js +33 -0
  9. package/dist/bin/preview.d.ts +2 -0
  10. package/dist/bin/preview.js +48 -0
  11. package/dist/package.json +73 -0
  12. package/dist/src/index.d.ts +5 -0
  13. package/dist/src/index.js +5 -0
  14. package/dist/src/javascript/assignments.d.ts +2 -0
  15. package/dist/src/javascript/assignments.js +37 -0
  16. package/dist/src/javascript/assignments.test.d.ts +1 -0
  17. package/dist/src/javascript/assignments.test.js +33 -0
  18. package/dist/src/javascript/awaits.d.ts +2 -0
  19. package/dist/src/javascript/awaits.js +23 -0
  20. package/dist/src/javascript/awaits.test.d.ts +1 -0
  21. package/dist/src/javascript/awaits.test.js +22 -0
  22. package/dist/src/javascript/declarations.d.ts +2 -0
  23. package/dist/src/javascript/declarations.js +45 -0
  24. package/dist/src/javascript/files.d.ts +3 -0
  25. package/dist/src/javascript/files.js +18 -0
  26. package/dist/src/javascript/globals.d.ts +1 -0
  27. package/dist/src/javascript/globals.js +86 -0
  28. package/dist/src/javascript/imports/jsr.d.ts +2 -0
  29. package/dist/src/javascript/imports/jsr.js +6 -0
  30. package/dist/src/javascript/imports/npm.d.ts +2 -0
  31. package/dist/src/javascript/imports/npm.js +47 -0
  32. package/dist/src/javascript/imports/npm.test.d.ts +1 -0
  33. package/dist/src/javascript/imports/npm.test.js +32 -0
  34. package/dist/src/javascript/imports/observable.d.ts +8 -0
  35. package/dist/src/javascript/imports/observable.js +64 -0
  36. package/dist/src/javascript/imports/observable.test.d.ts +1 -0
  37. package/dist/src/javascript/imports/observable.test.js +13 -0
  38. package/dist/src/javascript/imports.d.ts +21 -0
  39. package/dist/src/javascript/imports.js +146 -0
  40. package/dist/src/javascript/observable.d.ts +2 -0
  41. package/dist/src/javascript/observable.js +72 -0
  42. package/dist/src/javascript/parse.d.ts +11 -0
  43. package/dist/src/javascript/parse.js +53 -0
  44. package/dist/src/javascript/references.d.ts +8 -0
  45. package/dist/src/javascript/references.js +129 -0
  46. package/dist/src/javascript/references.test.d.ts +1 -0
  47. package/dist/src/javascript/references.test.js +38 -0
  48. package/dist/src/javascript/sourcemap.d.ts +21 -0
  49. package/dist/src/javascript/sourcemap.js +143 -0
  50. package/dist/src/javascript/sourcemap.test.d.ts +1 -0
  51. package/dist/src/javascript/sourcemap.test.js +88 -0
  52. package/dist/src/javascript/strings.d.ts +8 -0
  53. package/dist/src/javascript/strings.js +42 -0
  54. package/dist/src/javascript/strings.test.d.ts +1 -0
  55. package/dist/src/javascript/strings.test.js +31 -0
  56. package/dist/src/javascript/syntaxError.d.ts +2 -0
  57. package/dist/src/javascript/syntaxError.js +5 -0
  58. package/dist/src/javascript/template.d.ts +3 -0
  59. package/dist/src/javascript/template.js +141 -0
  60. package/dist/src/javascript/template.test.d.ts +1 -0
  61. package/dist/src/javascript/template.test.js +32 -0
  62. package/dist/src/javascript/transpile.d.ts +25 -0
  63. package/dist/src/javascript/transpile.js +42 -0
  64. package/dist/src/javascript/transpile.test.d.ts +1 -0
  65. package/dist/src/javascript/transpile.test.js +29 -0
  66. package/dist/src/javascript/walk.d.ts +5 -0
  67. package/dist/src/javascript/walk.js +13 -0
  68. package/dist/src/lib/notebook.d.ts +35 -0
  69. package/dist/src/lib/notebook.js +19 -0
  70. package/dist/src/lib/notebook.test.d.ts +1 -0
  71. package/dist/src/lib/notebook.test.js +26 -0
  72. package/dist/src/lib/serialize.d.ts +5 -0
  73. package/dist/src/lib/serialize.js +97 -0
  74. package/dist/src/lib/serialize.test.d.ts +1 -0
  75. package/dist/src/lib/serialize.test.js +125 -0
  76. package/dist/src/lib/text.d.ts +1 -0
  77. package/dist/src/lib/text.js +3 -0
  78. package/dist/src/runtime/define.d.ts +28 -0
  79. package/dist/src/runtime/define.js +62 -0
  80. package/dist/src/runtime/display.d.ts +16 -0
  81. package/dist/src/runtime/display.js +60 -0
  82. package/dist/src/runtime/index.d.ts +11 -0
  83. package/dist/src/runtime/index.js +14 -0
  84. package/dist/src/runtime/inspect.d.ts +3 -0
  85. package/dist/src/runtime/inspect.js +43 -0
  86. package/dist/src/runtime/stdlib/assets.d.ts +4 -0
  87. package/dist/src/runtime/stdlib/assets.js +110 -0
  88. package/dist/src/runtime/stdlib/assets.test.d.ts +1 -0
  89. package/dist/src/runtime/stdlib/assets.test.js +78 -0
  90. package/dist/src/runtime/stdlib/dom/canvas.d.ts +1 -0
  91. package/dist/src/runtime/stdlib/dom/canvas.js +6 -0
  92. package/dist/src/runtime/stdlib/dom/context2d.d.ts +1 -0
  93. package/dist/src/runtime/stdlib/dom/context2d.js +9 -0
  94. package/dist/src/runtime/stdlib/dom/index.d.ts +5 -0
  95. package/dist/src/runtime/stdlib/dom/index.js +5 -0
  96. package/dist/src/runtime/stdlib/dom/svg.d.ts +1 -0
  97. package/dist/src/runtime/stdlib/dom/svg.js +7 -0
  98. package/dist/src/runtime/stdlib/dom/text.d.ts +1 -0
  99. package/dist/src/runtime/stdlib/dom/text.js +3 -0
  100. package/dist/src/runtime/stdlib/dom/uid.d.ts +8 -0
  101. package/dist/src/runtime/stdlib/dom/uid.js +25 -0
  102. package/dist/src/runtime/stdlib/dot.d.ts +2 -0
  103. package/dist/src/runtime/stdlib/dot.js +34 -0
  104. package/dist/src/runtime/stdlib/duckdb.d.ts +24 -0
  105. package/dist/src/runtime/stdlib/duckdb.js +379 -0
  106. package/dist/src/runtime/stdlib/fileAttachment.d.ts +71 -0
  107. package/dist/src/runtime/stdlib/fileAttachment.js +199 -0
  108. package/dist/src/runtime/stdlib/generators/index.d.ts +5 -0
  109. package/dist/src/runtime/stdlib/generators/index.js +5 -0
  110. package/dist/src/runtime/stdlib/generators/input.d.ts +1 -0
  111. package/dist/src/runtime/stdlib/generators/input.js +45 -0
  112. package/dist/src/runtime/stdlib/generators/now.d.ts +1 -0
  113. package/dist/src/runtime/stdlib/generators/now.js +5 -0
  114. package/dist/src/runtime/stdlib/generators/observe.d.ts +1 -0
  115. package/dist/src/runtime/stdlib/generators/observe.js +31 -0
  116. package/dist/src/runtime/stdlib/generators/queue.d.ts +1 -0
  117. package/dist/src/runtime/stdlib/generators/queue.js +27 -0
  118. package/dist/src/runtime/stdlib/generators/width.d.ts +1 -0
  119. package/dist/src/runtime/stdlib/generators/width.js +13 -0
  120. package/dist/src/runtime/stdlib/highlight.d.ts +2 -0
  121. package/dist/src/runtime/stdlib/highlight.js +76 -0
  122. package/dist/src/runtime/stdlib/index.d.ts +56 -0
  123. package/dist/src/runtime/stdlib/index.js +23 -0
  124. package/dist/src/runtime/stdlib/inputs.css +15 -0
  125. package/dist/src/runtime/stdlib/inputs.d.ts +2 -0
  126. package/dist/src/runtime/stdlib/inputs.js +2 -0
  127. package/dist/src/runtime/stdlib/leaflet.d.ts +1 -0
  128. package/dist/src/runtime/stdlib/leaflet.js +7 -0
  129. package/dist/src/runtime/stdlib/mapboxgl.d.ts +1 -0
  130. package/dist/src/runtime/stdlib/mapboxgl.js +5 -0
  131. package/dist/src/runtime/stdlib/md.d.ts +5 -0
  132. package/dist/src/runtime/stdlib/md.js +72 -0
  133. package/dist/src/runtime/stdlib/mermaid.d.ts +2 -0
  134. package/dist/src/runtime/stdlib/mermaid.js +11 -0
  135. package/dist/src/runtime/stdlib/mutable.d.ts +8 -0
  136. package/dist/src/runtime/stdlib/mutable.js +30 -0
  137. package/dist/src/runtime/stdlib/observer.d.ts +16 -0
  138. package/dist/src/runtime/stdlib/observer.js +42 -0
  139. package/dist/src/runtime/stdlib/recommendedLibraries.d.ts +25 -0
  140. package/dist/src/runtime/stdlib/recommendedLibraries.js +26 -0
  141. package/dist/src/runtime/stdlib/require.d.ts +4 -0
  142. package/dist/src/runtime/stdlib/require.js +40 -0
  143. package/dist/src/runtime/stdlib/sampleDatasets.d.ts +12 -0
  144. package/dist/src/runtime/stdlib/sampleDatasets.js +31 -0
  145. package/dist/src/runtime/stdlib/sql.d.ts +5 -0
  146. package/dist/src/runtime/stdlib/sql.js +5 -0
  147. package/dist/src/runtime/stdlib/template.d.ts +7 -0
  148. package/dist/src/runtime/stdlib/template.js +2 -0
  149. package/dist/src/runtime/stdlib/tex.d.ts +7 -0
  150. package/dist/src/runtime/stdlib/tex.js +18 -0
  151. package/dist/src/runtime/stdlib/vega-lite.d.ts +1 -0
  152. package/dist/src/runtime/stdlib/vega-lite.js +4 -0
  153. package/dist/src/styles/abstract-dark.css +14 -0
  154. package/dist/src/styles/abstract-light.css +14 -0
  155. package/dist/src/styles/global.css +266 -0
  156. package/dist/src/styles/highlight.css +47 -0
  157. package/dist/src/styles/index.css +14 -0
  158. package/dist/src/styles/inspector.css +89 -0
  159. package/dist/src/styles/plot.css +7 -0
  160. package/dist/src/styles/syntax-dark.css +12 -0
  161. package/dist/src/styles/syntax-light.css +12 -0
  162. package/dist/src/styles/theme-air.css +7 -0
  163. package/dist/src/styles/theme-coffee.css +7 -0
  164. package/dist/src/styles/theme-cotton.css +7 -0
  165. package/dist/src/styles/theme-deep-space.css +16 -0
  166. package/dist/src/styles/theme-glacier.css +7 -0
  167. package/dist/src/styles/theme-ink.css +7 -0
  168. package/dist/src/styles/theme-midnight.css +7 -0
  169. package/dist/src/styles/theme-near-midnight.css +7 -0
  170. package/dist/src/styles/theme-ocean-floor.css +7 -0
  171. package/dist/src/styles/theme-parchment.css +7 -0
  172. package/dist/src/styles/theme-slate.css +7 -0
  173. package/dist/src/styles/theme-stark.css +16 -0
  174. package/dist/src/styles/theme-sun-faded.css +7 -0
  175. package/dist/src/templates/default.html +31 -0
  176. package/dist/src/vite/config.d.ts +2 -0
  177. package/dist/src/vite/config.js +30 -0
  178. package/dist/src/vite/index.d.ts +2 -0
  179. package/dist/src/vite/index.js +2 -0
  180. package/dist/src/vite/observable.d.ts +12 -0
  181. package/dist/src/vite/observable.js +176 -0
  182. package/package.json +73 -0
@@ -0,0 +1,47 @@
1
+ /** Note: specifier here does not start with `npm:`. */
2
+ function parseNpmSpecifier(specifier) {
3
+ const parts = specifier.split("/");
4
+ const namerange = specifier.startsWith("@")
5
+ ? [parts.shift(), parts.shift()].join("/")
6
+ : parts.shift();
7
+ const ranged = namerange.indexOf("@", 1);
8
+ const name = ranged > 0 ? namerange.slice(0, ranged) : namerange;
9
+ const range = ranged > 0 ? namerange.slice(ranged) : "";
10
+ const path = parts.length > 0 ? `/${parts.join("/")}` : "";
11
+ return { name, range, path };
12
+ }
13
+ /** If specifier is an npm: protocol import, resolves it. */
14
+ export function resolveNpmImport(specifier) {
15
+ if (!specifier.startsWith("npm:"))
16
+ return specifier;
17
+ const { name, range, path } = parseNpmSpecifier(specifier.slice(4));
18
+ return `https://cdn.jsdelivr.net/npm/${name}${range || getDefaultRange(name)}${path
19
+ ? !/(\.\w+|\/|\/\+esm)$/.test(path) // if not file, directory, or /+esm
20
+ ? `${path}/+esm` // then append /+esm
21
+ : path // otherwise keep as-is
22
+ : getDefaultPath(name)}`;
23
+ }
24
+ function getDefaultRange(name) {
25
+ switch (name) {
26
+ case "@duckdb/duckdb-wasm":
27
+ return "@1.29.0"; // https://github.com/duckdb/duckdb-wasm/issues/1994
28
+ default:
29
+ return "";
30
+ }
31
+ }
32
+ function getDefaultPath(name) {
33
+ switch (name) {
34
+ case "mermaid":
35
+ return "/dist/mermaid.esm.min.mjs/+esm";
36
+ case "echarts":
37
+ return "/dist/echarts.esm.min.js/+esm";
38
+ case "jquery-ui":
39
+ return "/dist/jquery-ui.js/+esm";
40
+ case "deck.gl":
41
+ return "/dist.min.js/+esm";
42
+ case "react-dom":
43
+ return "/client/+esm";
44
+ default:
45
+ return "/+esm";
46
+ }
47
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import { assert, test } from "vitest";
2
+ import { resolveNpmImport } from "./npm.js";
3
+ test("adds /+esm as expected", () => {
4
+ assert.strictEqual(resolveNpmImport("npm:prettier"), "https://cdn.jsdelivr.net/npm/prettier/+esm" // defaults to /+esm
5
+ );
6
+ assert.strictEqual(resolveNpmImport("npm:prettier/"), "https://cdn.jsdelivr.net/npm/prettier/" // no /+esm because trailing slash
7
+ );
8
+ assert.strictEqual(resolveNpmImport("npm:prettier/+esm"), "https://cdn.jsdelivr.net/npm/prettier/+esm" // preserve existing /+esm
9
+ );
10
+ assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn/+esm");
11
+ assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn/+esm"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn/+esm" // preserve existing /+esm
12
+ );
13
+ assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn/"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn/" // no /+esm because trailing slash
14
+ );
15
+ assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn.js"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn.js" // no /+esm because file extension
16
+ );
17
+ assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn.js/+esm"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn.js/+esm" // preserve existing /+esm
18
+ );
19
+ assert.strictEqual(resolveNpmImport("npm:prettier/plugins/estree"), "https://cdn.jsdelivr.net/npm/prettier/plugins/estree/+esm");
20
+ });
21
+ test("sets the default version of @duckdb/duckdb-wasm", () => {
22
+ assert.strictEqual(resolveNpmImport("npm:@duckdb/duckdb-wasm"), "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/+esm");
23
+ assert.strictEqual(resolveNpmImport("npm:@duckdb/duckdb-wasm/+esm"), "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/+esm");
24
+ assert.strictEqual(resolveNpmImport("npm:@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm"), "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/dist/duckdb-mvp.wasm");
25
+ });
26
+ test("sets the default path for various libraries", () => {
27
+ assert.strictEqual(resolveNpmImport("npm:mermaid"), "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs/+esm");
28
+ assert.strictEqual(resolveNpmImport("npm:echarts"), "https://cdn.jsdelivr.net/npm/echarts/dist/echarts.esm.min.js/+esm");
29
+ assert.strictEqual(resolveNpmImport("npm:jquery-ui"), "https://cdn.jsdelivr.net/npm/jquery-ui/dist/jquery-ui.js/+esm");
30
+ assert.strictEqual(resolveNpmImport("npm:deck.gl"), "https://cdn.jsdelivr.net/npm/deck.gl/dist.min.js/+esm");
31
+ assert.strictEqual(resolveNpmImport("npm:react-dom"), "https://cdn.jsdelivr.net/npm/react-dom/client/+esm");
32
+ });
@@ -0,0 +1,8 @@
1
+ import type { ImportDeclaration } from "acorn";
2
+ /** If specifier is an observable: protocol import, resolves it. */
3
+ export declare function resolveObservableImport(specifier: string): string;
4
+ export declare function isObservableImport(node: ImportDeclaration, specifier: string): boolean;
5
+ /** Note: mutates inputs! */
6
+ export declare function renderObservableImport(source: string, node: ImportDeclaration, inputs: string[]): string;
7
+ /** Turns e.g. "viewof$foo" into "viewof foo", and "$$" into "$". */
8
+ export declare function dedollar(input: string): string;
@@ -0,0 +1,64 @@
1
+ import { getImportedName } from "../imports.js";
2
+ const CODE_DOLLAR = 36;
3
+ /** If specifier is an observable: protocol import, resolves it. */
4
+ export function resolveObservableImport(specifier) {
5
+ if (!specifier.startsWith("observable:"))
6
+ return specifier;
7
+ return `https://api.observablehq.com/${specifier.slice("observable:".length)}.js?v=4`;
8
+ }
9
+ export function isObservableImport(node, specifier) {
10
+ const type = node.attributes?.find((a) => getImportAttributeKey(a) === "type")?.value;
11
+ return type ? type.value === "observable" : specifier.startsWith("observable:");
12
+ }
13
+ function getImportAttributeKey(node) {
14
+ return node.key.type === "Identifier" ? node.key.name : String(node.key.value);
15
+ }
16
+ /** Note: mutates inputs! */
17
+ export function renderObservableImport(source, node, inputs) {
18
+ if (!inputs.includes("__ojs_runtime"))
19
+ inputs.push("__ojs_runtime");
20
+ if (!inputs.includes("__ojs_observer"))
21
+ inputs.push("__ojs_observer");
22
+ return `(import(${JSON.stringify(source)}).then((_) => {
23
+ const observers = {};
24
+ const module = __ojs_runtime.module(_.default);
25
+ const main = __ojs_runtime.module();${node.specifiers
26
+ .map((specifier) => {
27
+ if (specifier.type === "ImportNamespaceSpecifier")
28
+ throw new SyntaxError("observable namespace imports are not supported");
29
+ const iname = getImportedName(specifier);
30
+ const vname = dedollar(iname);
31
+ return `
32
+ if (!module.defines("${vname}")) throw new SyntaxError(\`export '${vname}' not found\`);
33
+ main.variable(observers.${iname} = __ojs_observer()).import("${vname}", module);`;
34
+ })
35
+ .join("")}
36
+ return observers;
37
+ }))`;
38
+ }
39
+ /** Turns e.g. "viewof$foo" into "viewof foo", and "$$" into "$". */
40
+ export function dedollar(input) {
41
+ const start = 0;
42
+ const end = input.length;
43
+ let dollars = 0;
44
+ for (let i = start; i < end; ++i) {
45
+ switch (input.charCodeAt(i)) {
46
+ case CODE_DOLLAR: {
47
+ ++dollars;
48
+ break;
49
+ }
50
+ default: {
51
+ if (dollars > 0) {
52
+ input = `${input.slice(0, i - 1)}${dollars === 1 ? " " : ""}${input.slice(i)}`;
53
+ dollars = 0;
54
+ }
55
+ break;
56
+ }
57
+ }
58
+ }
59
+ if (dollars > 0) {
60
+ input = `${input.slice(0, end - 1)}${dollars === 1 ? " " : ""}`;
61
+ dollars = 0;
62
+ }
63
+ return input;
64
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { assert, test } from "vitest";
2
+ import { dedollar } from "./observable.js";
3
+ test("unescapes dollar signs in imported symbols", () => {
4
+ assert.strictEqual(dedollar("viewof$foo"), "viewof foo");
5
+ assert.strictEqual(dedollar("viewof$"), "viewof ");
6
+ assert.strictEqual(dedollar("$viewof"), " viewof");
7
+ assert.strictEqual(dedollar("viewof$$foo"), "viewof$foo");
8
+ assert.strictEqual(dedollar("viewof$$$foo"), "viewof$$foo");
9
+ assert.strictEqual(dedollar("$"), " ");
10
+ assert.strictEqual(dedollar("$$"), "$");
11
+ assert.strictEqual(dedollar("$$$"), "$$");
12
+ assert.strictEqual(dedollar("$$_"), "$_");
13
+ });
@@ -0,0 +1,21 @@
1
+ import type { CallExpression, MemberExpression, Node } from "acorn";
2
+ import type { ImportDefaultSpecifier, ImportSpecifier } from "acorn";
3
+ import type { Sourcemap } from "./sourcemap.js";
4
+ type NamedImportSpecifier = ImportSpecifier | ImportDefaultSpecifier;
5
+ /** Throws a syntax error if any export declarations are found. */
6
+ export declare function checkExports(body: Node, input: string): void;
7
+ /** Returns true if the body includes an import declaration. */
8
+ export declare function hasImportDeclaration(body: Node): boolean;
9
+ /** Returns true if the given node is a import.meta.resolve(…) call. */
10
+ export declare function isImportMetaResolve(node: CallExpression): boolean;
11
+ /** Returns true if the given node is a import.meta.url expression. */
12
+ export declare function isImportMetaUrl(node: MemberExpression): boolean;
13
+ export type RewriteImportOptions = {
14
+ /** If true, resolve local imports relative to document.baseURI. */
15
+ resolveLocalImports?: boolean;
16
+ };
17
+ export declare function rewriteImportExpressions(output: Sourcemap, body: Node, { resolveLocalImports }?: RewriteImportOptions): void;
18
+ /** Note: mutates inputs! */
19
+ export declare function rewriteImportDeclarations(output: Sourcemap, body: Node, inputs: string[], { resolveLocalImports }?: RewriteImportOptions): void;
20
+ export declare function getImportedName(node: NamedImportSpecifier): string;
21
+ export {};
@@ -0,0 +1,146 @@
1
+ import { resolveJsrImport } from "./imports/jsr.js";
2
+ import { resolveNpmImport } from "./imports/npm.js";
3
+ import { resolveObservableImport } from "./imports/observable.js";
4
+ import { isObservableImport, renderObservableImport } from "./imports/observable.js";
5
+ import { getStringLiteralValue, isStringLiteral } from "./strings.js";
6
+ import { syntaxError } from "./syntaxError.js";
7
+ import { simple } from "./walk.js";
8
+ /** Throws a syntax error if any export declarations are found. */
9
+ export function checkExports(body, input) {
10
+ function checkExport(child) {
11
+ throw syntaxError("Unexpected token 'export'", child, input);
12
+ }
13
+ simple(body, {
14
+ ExportAllDeclaration: checkExport,
15
+ ExportNamedDeclaration: checkExport
16
+ });
17
+ }
18
+ /** Returns true if the body includes an import declaration. */
19
+ export function hasImportDeclaration(body) {
20
+ let has = false;
21
+ simple(body, {
22
+ ImportDeclaration() {
23
+ has = true;
24
+ }
25
+ });
26
+ return has;
27
+ }
28
+ /** Returns true if the given node is a import.meta.resolve(…) call. */
29
+ export function isImportMetaResolve(node) {
30
+ return (node.callee.type === "MemberExpression" &&
31
+ node.callee.object.type === "MetaProperty" &&
32
+ node.callee.object.meta.name === "import" &&
33
+ node.callee.object.property.name === "meta" &&
34
+ node.callee.property.type === "Identifier" &&
35
+ node.callee.property.name === "resolve" &&
36
+ node.arguments.length > 0);
37
+ }
38
+ /** Returns true if the given node is a import.meta.url expression. */
39
+ export function isImportMetaUrl(node) {
40
+ return (node.object.type === "MetaProperty" &&
41
+ node.object.meta.name === "import" &&
42
+ node.object.property.name === "meta" &&
43
+ node.property.type === "Identifier" &&
44
+ node.property.name === "url");
45
+ }
46
+ function resolveImport(specifier) {
47
+ return resolveObservableImport(resolveJsrImport(resolveNpmImport(specifier)));
48
+ }
49
+ export function rewriteImportExpressions(output, body, { resolveLocalImports } = {}) {
50
+ function rewriteImportSource(source, node = source) {
51
+ const value = getStringLiteralValue(source);
52
+ const resolution = resolveImport(value);
53
+ output.replaceLeft(node.start, node.end, resolveLocalImports && isLocalImport(resolution)
54
+ ? `new URL(${JSON.stringify(resolution)}, document.baseURI).href`
55
+ : JSON.stringify(resolution));
56
+ }
57
+ simple(body, {
58
+ ImportExpression(node) {
59
+ const source = node.source;
60
+ if (isStringLiteral(source)) {
61
+ rewriteImportSource(source);
62
+ }
63
+ },
64
+ MemberExpression(node) {
65
+ if (resolveLocalImports && isImportMetaUrl(node)) {
66
+ output.replaceLeft(node.start, node.end, "document.baseURI");
67
+ }
68
+ },
69
+ CallExpression(node) {
70
+ const source = node.arguments[0];
71
+ if (isImportMetaResolve(node) && isStringLiteral(source)) {
72
+ rewriteImportSource(source, node);
73
+ }
74
+ }
75
+ });
76
+ }
77
+ /** Note: mutates inputs! */
78
+ export function rewriteImportDeclarations(output, body, inputs, { resolveLocalImports } = {}) {
79
+ const declarations = [];
80
+ simple(body, {
81
+ ImportDeclaration(node) {
82
+ if (isStringLiteral(node.source)) {
83
+ declarations.push([node, node.source]);
84
+ }
85
+ }
86
+ });
87
+ const specifiers = [];
88
+ const imports = [];
89
+ for (const [node, source] of declarations) {
90
+ output.delete(node.start, node.end + +(output.input[node.end] === "\n"));
91
+ specifiers.push(rewriteImportSpecifiers(node));
92
+ const value = getStringLiteralValue(source);
93
+ const resolution = resolveImport(value);
94
+ imports.push(isObservableImport(node, value)
95
+ ? renderObservableImport(resolution, node, inputs)
96
+ : renderImport(resolveLocalImports && isLocalImport(resolution)
97
+ ? `new URL(${JSON.stringify(resolution)}, document.baseURI)`
98
+ : JSON.stringify(resolution), node, output.input));
99
+ }
100
+ if (declarations.length > 1) {
101
+ output.insertLeft(0, `const [${specifiers.join(", ")}] = await Promise.all([${imports.join(", ")}]);\n`);
102
+ }
103
+ else if (declarations.length === 1) {
104
+ output.insertLeft(0, `const ${specifiers[0]} = await ${imports[0]};\n`);
105
+ }
106
+ }
107
+ function renderImport(source, node, input) {
108
+ const names = node.specifiers.filter(isNamedSpecifier).map(getImportedName);
109
+ return `import(${source}${node.attributes.length > 0
110
+ ? `, {with: {${input.slice(node.attributes[0].start, node.attributes[node.attributes.length - 1].end)}}}`
111
+ : ""})${names.length > 0
112
+ ? `.then((module) => {${names.map((name) => `
113
+ if (!("${name}" in module)) throw new SyntaxError(\`export '${name}' not found\`);`).join("")}
114
+ return module;
115
+ })`
116
+ : ""}`;
117
+ }
118
+ function getLocalName(node) {
119
+ return node.local.name;
120
+ }
121
+ export function getImportedName(node) {
122
+ return node.type === "ImportDefaultSpecifier"
123
+ ? "default"
124
+ : node.imported.type === "Identifier"
125
+ ? node.imported.name
126
+ : node.imported.raw;
127
+ }
128
+ function isLocalImport(source) {
129
+ return ["./", "../", "/"].some((prefix) => source.startsWith(prefix));
130
+ }
131
+ function isNamespaceSpecifier(node) {
132
+ return node.type === "ImportNamespaceSpecifier";
133
+ }
134
+ function isNamedSpecifier(node) {
135
+ return node.type !== "ImportNamespaceSpecifier";
136
+ }
137
+ function rewriteImportSpecifiers(node) {
138
+ return node.specifiers.some(isNamedSpecifier)
139
+ ? `{${node.specifiers.filter(isNamedSpecifier).map(rewriteImportSpecifier).join(", ")}}`
140
+ : (node.specifiers.find(isNamespaceSpecifier)?.local.name ?? "{}");
141
+ }
142
+ function rewriteImportSpecifier(node) {
143
+ return getImportedName(node) === getLocalName(node)
144
+ ? getLocalName(node)
145
+ : `${getImportedName(node)}: ${getLocalName(node)}`;
146
+ }
@@ -0,0 +1,2 @@
1
+ import type { TranspiledJavaScript, TranspileOptions } from "./transpile.js";
2
+ export declare function transpileObservable(input: string, options?: TranspileOptions): TranspiledJavaScript;
@@ -0,0 +1,72 @@
1
+ import { parseCell } from "@observablehq/parser";
2
+ import { rewriteFileExpressions } from "./files.js";
3
+ import { Sourcemap } from "./sourcemap.js";
4
+ import { transpileJavaScript } from "./transpile.js";
5
+ import { simple } from "./walk.js";
6
+ export function transpileObservable(input, options) {
7
+ const cell = parseCell(input);
8
+ if (!cell.body)
9
+ return transpileJavaScript(input);
10
+ if (cell.tag)
11
+ throw new Error("tagged ojs cells are not supported");
12
+ const output = new Sourcemap(input).trim();
13
+ if (cell.body.type === "ImportDeclaration") {
14
+ rewriteImportSource(output, cell.body);
15
+ return transpileJavaScript(String(output));
16
+ }
17
+ if (options?.resolveFiles)
18
+ rewriteFileExpressions(output, cell.body);
19
+ rewriteSpecialReferences(output, cell.body);
20
+ const inputs = Array.from(new Set(cell.references.map(asReference)));
21
+ let start = "";
22
+ let end = "";
23
+ start += `${cell.async ? "async " : ""}function${cell.generator ? "*" : ""}`;
24
+ if (cell.id)
25
+ start += ` ${asReference(cell.id)}`;
26
+ start += `(${inputs})`;
27
+ if (cell.body.type !== "BlockStatement") {
28
+ start += "{return(";
29
+ end += ")}";
30
+ }
31
+ output.replaceLeft(0, cell.body.start, `${start}\n`);
32
+ output.replaceRight(cell.body.end, input.length, `\n${end}`);
33
+ return {
34
+ body: String(output),
35
+ inputs,
36
+ output: cell.id ? asDeclaration(cell.id) : undefined,
37
+ autodisplay: true,
38
+ automutable: cell.id?.type === "MutableExpression",
39
+ autoview: cell.id?.type === "ViewExpression"
40
+ };
41
+ }
42
+ function rewriteImportSource(output, body) {
43
+ const specifier = body.source.value;
44
+ if (typeof specifier === "string" && !/^\w+:/.test(specifier))
45
+ output.insertLeft(body.source.start + 1, "observable:");
46
+ output.insertRight(body.end, ";");
47
+ }
48
+ // Rewrite viewof x ↦ viewof$x, and mutable x ↦ mutable$x.value.
49
+ function rewriteSpecialReferences(output, body) {
50
+ simple(body, {
51
+ MutableExpression(node) {
52
+ output.replaceLeft(node.start, node.end, `${asReference(node)}.value`);
53
+ },
54
+ ViewExpression(node) {
55
+ output.replaceLeft(node.start, node.end, asReference(node));
56
+ }
57
+ });
58
+ }
59
+ function asReference(ref) {
60
+ return ref.type === "ViewExpression"
61
+ ? `viewof$${ref.id.name}`
62
+ : ref.type === "MutableExpression"
63
+ ? `mutable$${ref.id.name}`
64
+ : ref.name;
65
+ }
66
+ function asDeclaration(ref) {
67
+ return ref.type === "ViewExpression"
68
+ ? `viewof$${ref.id.name}`
69
+ : ref.type === "MutableExpression"
70
+ ? `mutable ${ref.id.name}` // "initial x", really
71
+ : ref.name;
72
+ }
@@ -0,0 +1,11 @@
1
+ import type { Expression, Identifier, Options, Program } from "acorn";
2
+ export declare const acornOptions: Options;
3
+ export interface JavaScriptCell {
4
+ body: Program | Expression;
5
+ declarations: Identifier[] | null;
6
+ references: Identifier[];
7
+ expression: boolean;
8
+ async: boolean;
9
+ }
10
+ export declare function maybeParseJavaScript(input: string): JavaScriptCell | undefined;
11
+ export declare function parseJavaScript(input: string): JavaScriptCell;
@@ -0,0 +1,53 @@
1
+ import { Parser, tokTypes } from "acorn";
2
+ import { checkExports } from "./imports.js";
3
+ import { findReferences } from "./references.js";
4
+ import { checkAssignments } from "./assignments.js";
5
+ import { findDeclarations } from "./declarations.js";
6
+ import { findAwaits } from "./awaits.js";
7
+ export const acornOptions = {
8
+ ecmaVersion: "latest",
9
+ sourceType: "module"
10
+ };
11
+ export function maybeParseJavaScript(input) {
12
+ try {
13
+ return parseJavaScript(input);
14
+ }
15
+ catch (error) {
16
+ if (!(error instanceof SyntaxError))
17
+ throw error;
18
+ return;
19
+ }
20
+ }
21
+ export function parseJavaScript(input) {
22
+ let expression = maybeParseExpression(input); // first attempt to parse as expression
23
+ if (expression?.type === "ClassExpression" && expression.id)
24
+ expression = null; // treat named class as program
25
+ if (expression?.type === "FunctionExpression" && expression.id)
26
+ expression = null; // treat named function as program
27
+ const body = expression ?? parseProgram(input); // otherwise parse as a program
28
+ checkExports(body, input);
29
+ const references = findReferences(body);
30
+ checkAssignments(body, references, input);
31
+ return {
32
+ body,
33
+ declarations: expression ? null : findDeclarations(body, input),
34
+ references,
35
+ expression: !!expression,
36
+ async: findAwaits(body).length > 0
37
+ };
38
+ }
39
+ function parseProgram(input) {
40
+ return Parser.parse(input, acornOptions);
41
+ }
42
+ function maybeParseExpression(input) {
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ const parser = new Parser(acornOptions, input, 0); // private constructor
45
+ parser.nextToken();
46
+ try {
47
+ const node = parser.parseExpression();
48
+ return parser.type === tokTypes.eof ? node : null;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
@@ -0,0 +1,8 @@
1
+ import type { Identifier, Node } from "acorn";
2
+ export declare function findReferences(node: Node, { globals, filterReference, filterDeclaration }?: {
3
+ globals?: Set<string>;
4
+ filterReference?: (identifier: Identifier) => boolean;
5
+ filterDeclaration?: (identifier: {
6
+ name: string;
7
+ }) => boolean;
8
+ }): Identifier[];
@@ -0,0 +1,129 @@
1
+ import { defaultGlobals } from "./globals.js";
2
+ import { ancestor } from "./walk.js";
3
+ function isScope(node) {
4
+ return (node.type === "FunctionExpression" ||
5
+ node.type === "FunctionDeclaration" ||
6
+ node.type === "ArrowFunctionExpression" ||
7
+ node.type === "Program");
8
+ }
9
+ // prettier-ignore
10
+ function isBlockScope(node) {
11
+ return (node.type === "BlockStatement" ||
12
+ node.type === "SwitchStatement" ||
13
+ node.type === "ForInStatement" ||
14
+ node.type === "ForOfStatement" ||
15
+ node.type === "ForStatement" ||
16
+ isScope(node));
17
+ }
18
+ export function findReferences(node, { globals = defaultGlobals, filterReference = (identifier) => !globals.has(identifier.name), filterDeclaration = () => true } = {}) {
19
+ const locals = new Map();
20
+ const references = [];
21
+ function hasLocal(node, name) {
22
+ const l = locals.get(node);
23
+ return l ? l.has(name) : false;
24
+ }
25
+ function declareLocal(node, id) {
26
+ if (!filterDeclaration(id))
27
+ return;
28
+ const l = locals.get(node);
29
+ if (l)
30
+ l.add(id.name);
31
+ else
32
+ locals.set(node, new Set([id.name]));
33
+ }
34
+ function declareClass(node) {
35
+ if (node.id)
36
+ declareLocal(node, node.id);
37
+ }
38
+ function declareFunction(node) {
39
+ node.params.forEach((param) => declarePattern(param, node));
40
+ if (node.id)
41
+ declareLocal(node, node.id);
42
+ if (node.type !== "ArrowFunctionExpression")
43
+ declareLocal(node, { name: "arguments" });
44
+ }
45
+ function declareCatchClause(node) {
46
+ if (node.param)
47
+ declarePattern(node.param, node);
48
+ }
49
+ function declarePattern(node, parent) {
50
+ switch (node.type) {
51
+ case "Identifier":
52
+ declareLocal(parent, node);
53
+ break;
54
+ case "ObjectPattern":
55
+ node.properties.forEach((node) => declarePattern(node.type === "Property" ? node.value : node, parent));
56
+ break;
57
+ case "ArrayPattern":
58
+ node.elements.forEach((node) => node && declarePattern(node, parent));
59
+ break;
60
+ case "RestElement":
61
+ declarePattern(node.argument, parent);
62
+ break;
63
+ case "AssignmentPattern":
64
+ declarePattern(node.left, parent);
65
+ break;
66
+ }
67
+ }
68
+ ancestor(node, {
69
+ VariableDeclaration(node, _state, parents) {
70
+ let parent = null;
71
+ for (let i = parents.length - 1; i >= 0 && parent === null; --i) {
72
+ if (node.kind === "var" ? isScope(parents[i]) : isBlockScope(parents[i])) {
73
+ parent = parents[i];
74
+ }
75
+ }
76
+ node.declarations.forEach((declaration) => declarePattern(declaration.id, parent));
77
+ },
78
+ FunctionDeclaration(node, _state, parents) {
79
+ let parent = null;
80
+ for (let i = parents.length - 2; i >= 0 && parent === null; --i) {
81
+ if (isScope(parents[i])) {
82
+ parent = parents[i];
83
+ }
84
+ }
85
+ if (node.id)
86
+ declareLocal(parent, node.id);
87
+ declareFunction(node);
88
+ },
89
+ FunctionExpression: declareFunction,
90
+ ArrowFunctionExpression: declareFunction,
91
+ ClassDeclaration(node, _state, parents) {
92
+ let parent = null;
93
+ for (let i = parents.length - 2; i >= 0 && parent === null; --i) {
94
+ if (isScope(parents[i])) {
95
+ parent = parents[i];
96
+ }
97
+ }
98
+ if (node.id)
99
+ declareLocal(parent, node.id);
100
+ },
101
+ ClassExpression: declareClass,
102
+ CatchClause: declareCatchClause,
103
+ ImportDeclaration(node, _state, [root]) {
104
+ node.specifiers.forEach((specifier) => declareLocal(root, specifier.local));
105
+ }
106
+ });
107
+ function identifier(node, _state, parents) {
108
+ const name = node.name;
109
+ if (name === "undefined")
110
+ return;
111
+ for (let i = parents.length - 2; i >= 0; --i) {
112
+ if (hasLocal(parents[i], name)) {
113
+ return;
114
+ }
115
+ }
116
+ if (filterReference(node)) {
117
+ references.push(node);
118
+ }
119
+ }
120
+ ancestor(node, {
121
+ Pattern(node, _state, parents) {
122
+ if (node.type === "Identifier") {
123
+ identifier(node, _state, parents);
124
+ }
125
+ },
126
+ Identifier: identifier
127
+ });
128
+ return references;
129
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { expect, test } from "vitest";
2
+ import { parseJavaScript } from "./parse.js";
3
+ function find(input) {
4
+ return parseJavaScript(input).references;
5
+ }
6
+ test("finds references in expressions", () => {
7
+ expect(find(`foo + bar`)).toMatchSnapshot();
8
+ });
9
+ test("finds references in statements", () => {
10
+ expect(find(`foo + bar;`)).toMatchSnapshot();
11
+ });
12
+ test("finds multiple references", () => {
13
+ expect(find(`const a = b + c;\nconst d = c - b;`)).toMatchSnapshot();
14
+ });
15
+ test("doesn’t consider the identifier a reference", () => {
16
+ expect(find(`function foo() { return bar; }`)).toMatchSnapshot();
17
+ });
18
+ test("local variables can mask references", () => {
19
+ expect(find(`{ let foo; foo + bar; }`)).toMatchSnapshot();
20
+ });
21
+ test("local variables can not mask references", () => {
22
+ expect(find(`{ foo + bar; { let foo; } }`)).toMatchSnapshot();
23
+ });
24
+ test("function parameters can mask references", () => {
25
+ expect(find(`foo => foo + bar`)).toMatchSnapshot();
26
+ });
27
+ test("function rest parameters can mask references", () => {
28
+ expect(find(`(...foo) => foo + bar`)).toMatchSnapshot();
29
+ });
30
+ test("destructured variables can mask references", () => {
31
+ expect(find(`{ let {foo} = null; foo + bar; }`)).toMatchSnapshot();
32
+ });
33
+ test("destructured rest variables can mask references", () => {
34
+ expect(find(`{ let {...foo} = null; foo + bar; }`)).toMatchSnapshot();
35
+ });
36
+ test("ignores globals", () => {
37
+ expect(find(`window + bar`)).toMatchSnapshot();
38
+ });