I agree that the syntax of static imports is preferable, and that it would be nice to support static imports for ES modules (as well to continue to support notebook imports, obviously).
This doesn’t directly address the issue, but we are hoping to add support for cell destructuring in the near future. This makes the dynamic import syntax less cumbersome in your example (note that the await
is implicit because Observable automatically awaits promises at cell boundaries):
{default: Delatin} = import("delatin")
Which would be equivalent to this static import syntax (outside of Observable):
import Delatin from "delatin"
Putting that aside, the problem currently is that we need some way to disambiguate between imports of notebooks and imports of ES modules. For example, if you say
import {legend} from "@d3/color-legend"
do you mean the legend
cell from the notebook @d3/color-legend
or the npm package of the same name? The latter doesn’t currently exist, but we don’t want a shared namespace between Observable notebook identifiers and npm package identifiers. So we use static vs. dynamic imports to disambiguate imports.
What the alternatives we should consider? We’d like to allow static imports of ES modules, but the simple approach would break compatibility with existing notebooks, and we’d like to avoid versioning the language (if at all possible). We could do something like
import Delatin from "es:delatin"
where the “es:” prefix indicates an ES module (as opposed to an Observable notebook), but that’s nonstandard, and especially weird when it’s a URL
import Delatin from "es:https://cdn.jsdelivr.net/npm/delatin"
Related, if we want to go all-in on ES imports, we’re going to need browser support for import maps and possibly some Observable support for configuring import maps. The challenge with ES imports currently is that all (nested) imports must be relative or absolute paths, not bare identifiers. This makes it difficult to use cross-package imports, such as d3-scale importing d3-interpolate. We currently use unpkg’s “very experimental” support for rewriting bare import specifiers, but that won’t be compatible with Observable’s future support for versioning pinning since unpkg currently rewrites the bare specifiers to the latest published package that satisfies the semver range in the associated package.json. require does not have this issue because the require function does the resolution, giving us a hook to control the behavior at runtime.
I hope this clarifies some of the challenges with your request. We’re all ears! Eager to hear your ideas.