Skip to content

Commit 4262531

Browse files
committed
fix ojs viewof and mutable imports
1 parent 2581d6c commit 4262531

File tree

3 files changed

+174
-5
lines changed

3 files changed

+174
-5
lines changed

src/javascript/__snapshots__/transpile.test.ts.snap

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,68 @@ await z;
101101
}
102102
`;
103103

104+
exports[`transpiles Observable JavaScript imports 1`] = `
105+
{
106+
"autodisplay": false,
107+
"body": "async (__ojs_runtime,__ojs_observer) => {
108+
const {figure, rotation, viewof$rotation} = await (import("https://api.observablehq.com/@rreusser/drawing-3d-objects-with-svg.js?v=4").then((_) => {
109+
const observers = {};
110+
const module = __ojs_runtime.module(_.default);
111+
const main = __ojs_runtime.module();
112+
if (!module.defines("figure")) throw new SyntaxError(\`export 'figure' not found\`);
113+
main.variable(observers.figure = __ojs_observer()).import("figure", module);
114+
if (!module.defines("rotation")) throw new SyntaxError(\`export 'rotation' not found\`);
115+
main.variable(observers.rotation = __ojs_observer()).import("rotation", module);
116+
if (!module.defines("viewof rotation")) throw new SyntaxError(\`export 'viewof rotation' not found\`);
117+
main.variable(observers.viewof$rotation = __ojs_observer()).import("viewof rotation", module);
118+
return observers;
119+
}));
120+
121+
return {figure,rotation,viewof$rotation};
122+
}",
123+
"inputs": [
124+
"__ojs_runtime",
125+
"__ojs_observer",
126+
],
127+
"outputs": [
128+
"figure",
129+
"rotation",
130+
"viewof$rotation",
131+
],
132+
}
133+
`;
134+
135+
exports[`transpiles Observable JavaScript imports 2`] = `
136+
{
137+
"autodisplay": false,
138+
"body": "async (__ojs_runtime,__ojs_observer) => {
139+
const {figure, rotation: rot, viewof$rotation: viewof$rot} = await (import("https://api.observablehq.com/@rreusser/drawing-3d-objects-with-svg.js?v=4").then((_) => {
140+
const observers = {};
141+
const module = __ojs_runtime.module(_.default);
142+
const main = __ojs_runtime.module();
143+
if (!module.defines("figure")) throw new SyntaxError(\`export 'figure' not found\`);
144+
main.variable(observers.figure = __ojs_observer()).import("figure", module);
145+
if (!module.defines("rotation")) throw new SyntaxError(\`export 'rotation' not found\`);
146+
main.variable(observers.rotation = __ojs_observer()).import("rotation", module);
147+
if (!module.defines("viewof rotation")) throw new SyntaxError(\`export 'viewof rotation' not found\`);
148+
main.variable(observers.viewof$rotation = __ojs_observer()).import("viewof rotation", module);
149+
return observers;
150+
}));
151+
152+
return {figure,rot,viewof$rot};
153+
}",
154+
"inputs": [
155+
"__ojs_runtime",
156+
"__ojs_observer",
157+
],
158+
"outputs": [
159+
"figure",
160+
"rot",
161+
"viewof$rot",
162+
],
163+
}
164+
`;
165+
104166
exports[`transpiles dynamic npm: imports 1`] = `
105167
{
106168
"autodisplay": false,
@@ -154,6 +216,56 @@ return (
154216
}
155217
`;
156218
219+
exports[`transpiles static imports with {type: 'observable'} 1`] = `
220+
{
221+
"autodisplay": false,
222+
"body": "async (__ojs_runtime,__ojs_observer) => {
223+
const {Scrubber} = await (import("https://api.observablehq.com/@mbostock/scrubber.js?v=4").then((_) => {
224+
const observers = {};
225+
const module = __ojs_runtime.module(_.default);
226+
const main = __ojs_runtime.module();
227+
if (!module.defines("Scrubber")) throw new SyntaxError(\`export 'Scrubber' not found\`);
228+
main.variable(observers.Scrubber = __ojs_observer()).import("Scrubber", module);
229+
return observers;
230+
}));
231+
232+
return {Scrubber};
233+
}",
234+
"inputs": [
235+
"__ojs_runtime",
236+
"__ojs_observer",
237+
],
238+
"outputs": [
239+
"Scrubber",
240+
],
241+
}
242+
`;
243+
244+
exports[`transpiles static imports with {type: 'observable'} 2`] = `
245+
{
246+
"autodisplay": false,
247+
"body": "async (__ojs_runtime,__ojs_observer) => {
248+
const {viewof$rotation} = await (import("https://api.observablehq.com/@rreusser/drawing-3d-objects-with-svg.js?v=4").then((_) => {
249+
const observers = {};
250+
const module = __ojs_runtime.module(_.default);
251+
const main = __ojs_runtime.module();
252+
if (!module.defines("viewof rotation")) throw new SyntaxError(\`export 'viewof rotation' not found\`);
253+
main.variable(observers.viewof$rotation = __ojs_observer()).import("viewof rotation", module);
254+
return observers;
255+
}));
256+
257+
return {viewof$rotation};
258+
}",
259+
"inputs": [
260+
"__ojs_runtime",
261+
"__ojs_observer",
262+
],
263+
"outputs": [
264+
"viewof$rotation",
265+
],
266+
}
267+
`;
268+
157269
exports[`transpiles static npm: imports 1`] = `
158270
{
159271
"autodisplay": false,
@@ -223,3 +335,28 @@ return {Scrubber};
223335
],
224336
}
225337
`;
338+
339+
exports[`transpiles static observable: imports 2`] = `
340+
{
341+
"autodisplay": false,
342+
"body": "async (__ojs_runtime,__ojs_observer) => {
343+
const {viewof$rotation} = await (import("https://api.observablehq.com/@rreusser/drawing-3d-objects-with-svg.js?v=4").then((_) => {
344+
const observers = {};
345+
const module = __ojs_runtime.module(_.default);
346+
const main = __ojs_runtime.module();
347+
if (!module.defines("viewof rotation")) throw new SyntaxError(\`export 'viewof rotation' not found\`);
348+
main.variable(observers.viewof$rotation = __ojs_observer()).import("viewof rotation", module);
349+
return observers;
350+
}));
351+
352+
return {viewof$rotation};
353+
}",
354+
"inputs": [
355+
"__ojs_runtime",
356+
"__ojs_observer",
357+
],
358+
"outputs": [
359+
"viewof$rotation",
360+
],
361+
}
362+
`;

src/javascript/observable.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {MutableExpression, ViewExpression, Visitors} from "@observablehq/parser";
22
import {parseCell} from "@observablehq/parser";
3-
import type {BlockStatement, Expression, Identifier, ImportDeclaration} from "acorn";
3+
import type {Identifier, ImportDeclaration, ImportSpecifier, Literal, Node} from "acorn";
44
import {rewriteFileExpressions} from "./files.js";
55
import {Sourcemap} from "./sourcemap.js";
66
import type {TranspiledJavaScript, TranspileOptions} from "./transpile.js";
@@ -15,12 +15,12 @@ export function transpileObservable(
1515
if (!cell.body) return transpileJavaScript(input);
1616
if (cell.tag) throw new Error("tagged ojs cells are not supported");
1717
const output = new Sourcemap(input).trim();
18+
rewriteSpecialReferences(output, cell.body);
1819
if (cell.body.type === "ImportDeclaration") {
1920
rewriteImportSource(output, cell.body);
2021
return transpileJavaScript(String(output));
2122
}
2223
if (options?.resolveFiles) rewriteFileExpressions(output, cell.body);
23-
rewriteSpecialReferences(output, cell.body);
2424
const inputs = Array.from(new Set(cell.references.map(asReference)));
2525
let start = "";
2626
let end = "";
@@ -43,25 +43,46 @@ export function transpileObservable(
4343
};
4444
}
4545

46+
/** Rewrite bare module specifiers to have the observable: protocol. */
4647
function rewriteImportSource(output: Sourcemap, body: ImportDeclaration): void {
4748
const specifier = body.source.value;
48-
if (typeof specifier === "string" && !/^\w+:/.test(specifier))
49+
if (typeof specifier === "string" && !/^\w+:/.test(specifier)) {
4950
output.insertLeft(body.source.start + 1, "observable:");
51+
}
5052
output.insertRight(body.end, ";");
5153
}
5254

53-
// Rewrite viewof x ↦ viewof$x, and mutable x ↦ mutable$x.value.
54-
function rewriteSpecialReferences(output: Sourcemap, body: Expression | BlockStatement): void {
55+
/** Rewrite viewof x ↦ viewof$x, and mutable x ↦ mutable$x.value. */
56+
function rewriteSpecialReferences(output: Sourcemap, body: Node): void {
5557
simple(body, {
5658
MutableExpression(node) {
5759
output.replaceLeft(node.start, node.end, `${asReference(node)}.value`);
5860
},
5961
ViewExpression(node) {
6062
output.replaceLeft(node.start, node.end, asReference(node));
63+
},
64+
ImportSpecifier(node) {
65+
const inode = node as ImportSpecifier & {view: boolean; mutable: boolean};
66+
const prefix = inode.view ? "viewof$" : inode.mutable ? "mutable$" : null;
67+
if (prefix) {
68+
const imported = asImportName(node.imported);
69+
output.replaceLeft(node.start, node.imported.start, prefix);
70+
if (node.imported === node.local) {
71+
output.insertLeft(node.start, `${imported},`);
72+
} else {
73+
const local = asImportName(node.local);
74+
output.insertLeft(node.start, `${imported} as ${local},`);
75+
output.insertLeft(node.local.start, prefix);
76+
}
77+
}
6178
}
6279
} as Visitors);
6380
}
6481

82+
function asImportName(ref: Identifier | Literal): string {
83+
return ref.type === "Identifier" ? ref.name : ref.raw!;
84+
}
85+
6586
function asReference(ref: Identifier | ViewExpression | MutableExpression): string {
6687
return ref.type === "ViewExpression"
6788
? `viewof$${ref.id.name}`

src/javascript/transpile.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ it("transpiles dynamic npm: imports", () => {
2626

2727
it("transpiles static observable: imports", () => {
2828
expect(transpile('import {Scrubber} from "observable:@mbostock/scrubber";', "js")).toMatchSnapshot();
29+
expect(transpile('import {viewof$rotation} from "observable:@rreusser/drawing-3d-objects-with-svg";', "js")).toMatchSnapshot();
30+
});
31+
32+
it("transpiles static imports with {type: 'observable'}", () => {
33+
expect(transpile('import {Scrubber} from "https://api.observablehq.com/@mbostock/scrubber.js?v=4" with {type: "observable"};', "js")).toMatchSnapshot();
34+
expect(transpile('import {viewof$rotation} from "https://api.observablehq.com/@rreusser/drawing-3d-objects-with-svg.js?v=4" with {type: "observable"};', "js")).toMatchSnapshot();
35+
});
36+
37+
it("transpiles Observable JavaScript imports", () => {
38+
expect(transpile('import {figure, viewof rotation} from "@rreusser/drawing-3d-objects-with-svg"', "ojs")).toMatchSnapshot();
39+
expect(transpile('import {figure, viewof rotation as rot} from "@rreusser/drawing-3d-objects-with-svg"', "ojs")).toMatchSnapshot();
2940
});
3041

3142
it("transpiles import.meta.resolve", () => {

0 commit comments

Comments
 (0)