asg017 / unofficial-observablehq-compiler

An unofficial compiler for Observable notebook syntax
https://www.npmjs.com/package/@alex.garcia/unofficial-observablehq-compiler
112 stars 23 forks source link

More Compiler options #30

Open asg017 opened 3 years ago

asg017 commented 3 years ago

The PR at #29 is already quite lengthy, so thought I would break out this idea into in a separate issue for a future release.

1) Make the export default prefix optional.

So you can just compile to a function define(runtime, observer) {...} (good for compiling for environments that dont have ES modules

2) bundle imported defines into same file

Instead of:

import define1 from "https://example.com/define1.js"
import define2 from "https://example.com/define2.js"
import define3 from "https://example.com/define3.js"

export default function define(runtime, observer() {
  ...
}

Have an option where remote definitions can be dynamically fetched and bundled with the ES module source, eliminating the need for static imports. example:

//  from "https://example.com/define1.js"
function define1 (runtime, observer) {
 ...
}
// from "https://example.com/define2.js"
function define2 (runtime, observer) {
 ...
}
// from "https://example.com/define3.js"
function define3 (runtime, observer) {
 ...
}

export default function define(runtime, observer() {
  ...
}

Nested/recursive definitions may be fun to work out...

3) export cell definitions

Brought up in #28. Export the cell definitions for defined cells.

import define1 from "https://api.observablehq.com/@d3/bar-chart.js?v=3";

export function a() {
  return 1;
}

export function b() {
  return 2;
}

export function c(a, b) {
  return a + b;
}

export default function define(runtime, observer) {
  const main = runtime.module();

  main.variable(observer("a")).define("a", a);
  main.variable(observer("b")).define("a", b);
  main.variable(observer("c")).define("c", ["a", "b"], c);
  const child1 = runtime
    .module(define1)
    .derive([{ name: "c", alias: "height" }], main);
  main.import("d3", "d3", child1);
  main.import("chart", "chart", child1);
  return main;
}

3 potential issues:

  1. If a cell name is define, would that clash with the default exported define? I guess we don't have to give a name to the default exported define function in this case
  2. probably can't use this and the potential bundled option from above
  3. The function signatures wouldn't be deterministic. For example:
x = a + b + c + d

would give:

export function x(a,b,c,d) {
  return a + b + c + d;
}

But, if x is given like:

x = d + c +b + a

As the compiler currently stands, it would give out:

export function x(d,c,b,a) {
  return d + c + b + a;
}

It would be nice to have deterministic function signatures for these exported functions, and not determined by which cell is referenced first in the definition. One way could be:

export function a() {
  return 1;
}

export function b() {
  return 2;
}

export function c({a, b}) {
  return a + b;
}

export function x({a,b,c,d}) {
  return a + b + c + d;
}

export default function define(runtime, observer) {
  const main = runtime.module();

  main.variable(observer("a")).define("a", a);
  main.variable(observer("b")).define("a", b);
  main.variable(observer("c")).define("c", ["a", "b"], function(a,b) {
   return c({a, b});
  });
  main.variable(observer("c")).define("x", ["a", "b", "c", "d"], function(a,b, c, d) {
   return x({a, b, c, d});
  });
  return main;
}