Skip to content

Control deep imports #8679

@kriskowal

Description

@kriskowal

What is the Problem Being Solved?

Deep imports are import statements that reach deeper than the entry module for a package. For example, import { E, Far } from "@endo/far" is a shallow import and import "@endo/lockdown/commit-debug.js" is a deep import.

Agoric makes extensive use of deep imports that even reach into the src subdirectory of a package, as in "@agoric/zoe/src/contractSupport/index.js". We otherwise prefer to avoid deep imports that reach into the src tree, since the layout under src should not be a public API. Elsewhere, we’ve settled on a pattern where deep imports can occur but are limited to being in the top-level of the package, like "@endo/lockdown/commit.js" or "@endo/base64/encode.js".

Node 16+ and Endo respect the "exports" property in package.json which expresses the entirely of the importable surface of the package and forbids all other deep imports. To maintain a compatibility bridge between older versions of Node.js and tools that do not yet recognize the existence of "exports" we arrived at a pattern of use where we would only use "exports" entries with identical keys and values. We also learned that some tools need an entry for "package.json".

{
  "main": "./src/index.js",
  "exports": {
    ".": "./src/index.js",
    "./package.json": "./package.json"
  }
}

We have made some regrettable design choices for the naming of deep imports up to this point, but to maintain backward compatibility, we need to preserve working import expressions.

Description of the Design

To that end, I propose that we introduce an "exports" directive to every public package of Agoric SDK and populate it with an entry for every existing use of a deep import. For example:

{
  "name": "zoe",
  "exports": {
    "./src/contractSupport/index.js": "./src/contractSupport/index.js",
    "./package.json": "./package.json"
  }
}

Making these entry-points explicit will prevent future unintended creep of the public API of our packages. Then, we can being the process of normalizing our design philosophy for intentional package exports.

For example, we can then add a top-level contract-support.js to @agoric/zoe:

export * from './src/contractSupport/index.js';

And make this alias public for all future use:

{
  "name": "zoe",
  "exports": {
    "./contract-support.js": "./contract-support.js",
    "./src/contractSupport/index.js": "./src/contractSupport/index.js",
    "./package.json": "./package.json"
  }
}

Security Considerations

Scaling Considerations

Test Plan

Upgrade Considerations

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions