feat(compartment-mapper): Support reflexive imports
In Node.js ESM, a package may import its own name. This is particularly
useful for creating tests and testing examples of usage from a
third-party perspective, and is necessary for feature parity.
Blocks
MODIFY
packages/compartment-mapper/NEWS.md
packages/compartment-mapper/NEWS.md
User-visible changes to the compartment mapper:
+# Next release
+
+- Adds support for reflexive import specifiers, so modules in package named
+ `@example/example` may import `@example/example` from their own modules.
+ This is necessary for parity with Node.js.
+
# 0.5.0 (2021-07-22)
- The calling convention between SES and StaticModuleRecords has changed and

MODIFY
packages/compartment-mapper/src/node-modules.js
packages/compartment-mapper/src/node-modules.js
/**
* @typedef {Object} Node
* @property {string} label
+ * @property {string} name
* @property {boolean} explicit
* @property {Record<string, string>} exports
* @property {Record<string, string>} dependencies - from module name to
@@ -266,6 +267,7 @@ const graphPackage = async (
const types = {};
Object.assign(result, {
+ name,
label: `${name}${version ? `-v${version}` : ''}`,
explicit: exports !== undefined,
exports: inferExports(packageDescriptor, tags, types),
@@ -411,13 +413,18 @@ const translateGraph = (
// package and is a complete list of every external module that the
// corresponding compartment can import.
for (const packageLocation of keys(graph).sort()) {
- const { label, dependencies, parsers, types } = graph[packageLocation];
+ const { name, label, dependencies, parsers, types } = graph[
+ packageLocation
+ ];
/** @type {Record<string, ModuleDescriptor>} */
const modules = {};
/** @type {Record<string, ScopeDescriptor>} */
const scopes = {};
- for (const dependencyName of keys(dependencies).sort()) {
- const packageLocation = dependencies[dependencyName];
+ /**
+ * @param {string} dependencyName
+ * @param {string} packageLocation
+ */
+ const digest = (dependencyName, packageLocation) => {
const { exports, explicit } = graph[packageLocation];
for (const exportName of keys(exports).sort()) {
const module = exports[exportName];
@@ -431,6 +438,13 @@ const translateGraph = (
compartment: packageLocation,
};
}
+ };
+ // Support reflexive package imports.
+ digest(name, entryPackageLocation);
+ // Support external package imports.
+ for (const dependencyName of keys(dependencies).sort()) {
+ const packageLocation = dependencies[dependencyName];
+ digest(dependencyName, packageLocation);
}
compartments[packageLocation] = {
label,

MODIFY
packages/compartment-mapper/test/fixtures-0/node_modules/app/main.js
packages/compartment-mapper/test/fixtures-0/node_modules/app/main.js
@@ -28,3 +28,10 @@ export const receivedGlobalLexical = globalLexical;
if (!Object.isFrozen(globalThis)) {
throw new Error('The global object must be frozen in all compartments');
}
+
+// reflexivity
+import * as app from 'app';
+import * as appMain from 'app/main.js';
+if (app !== appMain) {
+ throw new Error('Import aliases for the reflexive module name should produce identical namespace objects');
+}

feat(compartment-mapper): Support reflexive imports (#861)

In Node.js ESM, a package may import its own name. This is particularly
useful for creating tests and testing examples of usage from a
third-party perspective, and is necessary for feature parity.

Blocks https://github.com/Agoric/agoric-sdk/pull/3273