Skip to content

Commit

Permalink
refactor(compartment-mapper): Alias conditions to tags
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Jul 23, 2024
1 parent 4ae3915 commit 3f7aa66
Show file tree
Hide file tree
Showing 15 changed files with 119 additions and 110 deletions.
2 changes: 2 additions & 0 deletions packages/compartment-mapper/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ User-visible changes to the compartment mapper:
The new `import-lite.js` does not entrain `node-modules.js` and composes
with potential alternative package discovery, storage, and locks.
- Adds JSON module support to `makeBundle`.
- Aliases and deprecates `tags` in favor of `conditions` to align with Node.js
terminology.

# 0.9.0 (2023-08-07)

Expand Down
41 changes: 15 additions & 26 deletions packages/compartment-mapper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,27 +164,15 @@ in a parent directory, under `node_modules`.
The `main`, `browser`, and `exports` properties determine the modules each
package exports to other compartments.
The `exports` property describes [package entry points] and can be influenced
by build _tags_.
Currently, the only tag supported by the compartment mapper is `import`, which
indicates that the module map should use ESM modules over CommonJS modules or
other variants.
> TODO
>
> A future version may reveal other tags like `browser` to prepare an
> application for use in a web client.
> For this case, the compartment mapper would prepare a JSON manifest like an
> `importmap` (if not precisely an `importmap`).
> The "compartment map" would be consistent except when the dependency graph
> changes so updates could be automated with a `postinstall` script.
> Preparing a web application for production would follow a process similar to
> creating an archive, but with the `browser` build tag.
The `browser` and `require` tags are well-known but not yet supported.
The `browser` tag will apply for compartment maps generated for use on the web.
The `require` tag is a fallback for environments that do not support ESM and
will never apply.
The `exports` property describes [package entry points][] and can be influenced
by build _conditions_.
Currently, the only conditions supported by the compartment mapper are
`import`, `browser`, and `endo`.
The `imports` condition indicates that the module map should use ESM modules
over CommonJS modules or other variants, and `endo`.
The `browser` condition also draws in the `browser` property from
`package.json` instead of `main`.
The `endo` condition only indicates that this tool is in use.
If no `exports` apply to the root of the compartment namespace (`"."`),
the `main` property serves as a default.
Expand Down Expand Up @@ -284,7 +272,7 @@ Node.js platform.
> For browser applications, the compartment mapper would use the translator
> modules in two modes.
> During development, the compartment mapper would be able to load the
> translator in the client, with the `browser` tag.
> translator in the client, with the `browser` condition.
> The compartment mapper would also be able to run the translator in a separate
> non-browser compartment during bundling, so the translator can be excluded
> from the production application and archived applications.
Expand Down Expand Up @@ -392,18 +380,19 @@ The compartment map shape:
// CompartmentMap describes how to prepare compartments
// to run an application.
type CompartmentMap = {
tags: Tags,
conditions: Conditions,
entry: Entry,
compartments: Record<CompartmentName, Compartment>,
realms: Record<RealmName, Realm>, // TODO
};
// Tags are the build tags for the compartment.
// Conditions influence which modules are selected
// to represent the implementation of various modules.
// These may include terms like "browser", meaning
// each compartment uses the implementation of each
// module suitable for use in a browser environment.
type Tags = Array<Tag>;
type Tag = string;
type Conditions = Array<Condition>;
type Condition = string;
// Entry is a reference to the module that is the module to initially import.
type Entry = CompartmentModule;
Expand Down
2 changes: 1 addition & 1 deletion packages/compartment-mapper/src/archive-lite.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export const makeArchiveCompartmentMap = (compartmentMap, sources) => {
const archiveSources = renameSources(sources, compartmentRenames);

const archiveCompartmentMap = {
tags: [],
conditions: [],
entry: {
compartment: archiveEntryCompartmentName,
module: entryModuleSpecifier,
Expand Down
16 changes: 8 additions & 8 deletions packages/compartment-mapper/src/archive.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ export const makeAndHashArchive = async (
* @returns {Promise<Uint8Array>}
*/
export const makeArchive = async (powers, moduleLocation, options = {}) => {
const { dev, tags, commonDependencies, policy } = options;
const { dev, tags, conditions = tags, commonDependencies, policy } = options;

const compartmentMap = await mapNodeModules(powers, moduleLocation, {
dev,
tags,
conditions,
commonDependencies,
policy,
});
Expand All @@ -95,11 +95,11 @@ export const makeArchive = async (powers, moduleLocation, options = {}) => {
* @returns {Promise<Uint8Array>}
*/
export const mapLocation = async (powers, moduleLocation, options = {}) => {
const { dev, tags, commonDependencies, policy } = options;
const { dev, tags, conditions = tags, commonDependencies, policy } = options;

const compartmentMap = await mapNodeModules(powers, moduleLocation, {
dev,
tags,
conditions,
commonDependencies,
policy,
});
Expand All @@ -114,11 +114,11 @@ export const mapLocation = async (powers, moduleLocation, options = {}) => {
* @returns {Promise<string>}
*/
export const hashLocation = async (powers, moduleLocation, options = {}) => {
const { dev, tags, commonDependencies, policy } = options;
const { dev, tags, conditions = tags, commonDependencies, policy } = options;

const compartmentMap = await mapNodeModules(powers, moduleLocation, {
dev,
tags,
conditions,
commonDependencies,
policy,
});
Expand All @@ -140,10 +140,10 @@ export const writeArchive = async (
moduleLocation,
options = {},
) => {
const { dev, tags, commonDependencies, policy } = options;
const { dev, tags, conditions = tags, commonDependencies, policy } = options;
const compartmentMap = await mapNodeModules(readPowers, moduleLocation, {
dev,
tags,
conditions,
commonDependencies,
policy,
});
Expand Down
7 changes: 4 additions & 3 deletions packages/compartment-mapper/src/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,15 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
const {
moduleTransforms,
dev,
tags: tagsOption,
tags,
conditions: conditionsOption = tags,
searchSuffixes,
commonDependencies,
sourceMapHook = undefined,
parserForLanguage: parserForLanguageOption = {},
languageForExtension: languageForExtensionOption = {},
} = options || {};
const tags = new Set(tagsOption);
const conditions = new Set(conditionsOption);

const parserForLanguage = Object.freeze(
Object.assign(
Expand All @@ -258,7 +259,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
const compartmentMap = await compartmentMapForNodeModules(
read,
packageLocation,
tags,
conditions,
packageDescriptor,
moduleSpecifier,
{ dev, commonDependencies },
Expand Down
2 changes: 1 addition & 1 deletion packages/compartment-mapper/src/capture-lite.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const captureCompartmentMap = (compartmentMap, sources) => {
const captureSources = renameSources(sources, compartmentRenames);

const captureCompartmentMap = {
tags: [],
conditions: [],
entry: {
compartment: captureEntryCompartmentName,
module: entryModuleSpecifier,
Expand Down
24 changes: 15 additions & 9 deletions packages/compartment-mapper/src/compartment-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,20 @@ const assertEmptyObject = (object, message) => {
};

/**
* @param {unknown} tags
* @param {unknown} conditions
* @param {string} url
*/
const assertTags = (tags, url) => {
if (tags === undefined) return;
const assertConditions = (conditions, url) => {
if (conditions === undefined) return;
assert(
Array.isArray(tags),
`tags must be an array, got ${tags} in ${q(url)}`,
Array.isArray(conditions),
`conditions must be an array, got ${conditions} in ${q(url)}`,
);
for (const [index, value] of enumerate(tags)) {
for (const [index, value] of enumerate(conditions)) {
assert.typeof(
value,
'string',
`tags[${index}] must be a string, got ${value} in ${q(url)}`,
`conditions[${index}] must be a string, got ${value} in ${q(url)}`,
);
}
};
Expand Down Expand Up @@ -480,14 +480,20 @@ export const assertCompartmentMap = (
url,
)}`,
);
const { tags, entry, compartments, ...extra } = Object(compartmentMap);
const {
tags,
conditions = tags,
entry,
compartments,
...extra
} = Object(compartmentMap);
assertEmptyObject(
extra,
`Compartment map must not have extra properties, got ${q(
Object.keys(extra),
)} in ${q(url)}`,
);
assertTags(tags, url);
assertConditions(conditions, url);
assertEntry(entry, url);
assertCompartments(compartments, url);
};
4 changes: 2 additions & 2 deletions packages/compartment-mapper/src/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ export const loadLocation = async (
moduleLocation,
options = {},
) => {
const { dev, tags, commonDependencies, policy } = options;
const { dev, tags, conditions = tags, commonDependencies, policy } = options;
const compartmentMap = await mapNodeModules(readPowers, moduleLocation, {
dev,
tags,
conditions,
commonDependencies,
policy,
});
Expand Down
34 changes: 17 additions & 17 deletions packages/compartment-mapper/src/infer-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ function* interpretBrowserField(name, browser, main = 'index.js') {
/**
* @param {string} name - the name of the referrer package.
* @param {object} exports - the `exports` field from a package.json.
* @param {Set<string>} tags - build tags about the target environment
* @param {Set<string>} conditions - build conditions about the target environment
* for selecting relevant exports, e.g., "browser" or "node".
* @yields {[string, string]}
* @returns {Generator<[string, string]>}
*/
function* interpretExports(name, exports, tags) {
function* interpretExports(name, exports, conditions) {
if (isArray(exports)) {
for (const section of exports) {
const results = [...interpretExports(name, section, tags)];
const results = [...interpretExports(name, section, conditions)];
if (results.length > 0) {
yield* results;
break;
Expand All @@ -84,12 +84,12 @@ function* interpretExports(name, exports, tags) {
for (const [key, value] of entries(exports)) {
if (key.startsWith('./') || key === '.') {
if (name === '.') {
yield* interpretExports(key, value, tags);
yield* interpretExports(key, value, conditions);
} else {
yield* interpretExports(join(name, key), value, tags);
yield* interpretExports(join(name, key), value, conditions);
}
} else if (tags.has(key)) {
yield* interpretExports(name, value, tags);
} else if (conditions.has(key)) {
yield* interpretExports(name, value, conditions);
// Take only the first matching tag.
break;
}
Expand All @@ -110,20 +110,20 @@ function* interpretExports(name, exports, tags) {
* @param {string} packageDescriptor.main
* @param {string} [packageDescriptor.module]
* @param {object} [packageDescriptor.exports]
* @param {Set<string>} tags - build tags about the target environment
* @param {Set<string>} conditions - build conditions about the target environment
* for selecting relevant exports, e.g., "browser" or "node".
* @param {LanguageForExtension} types - an object to populate
* with any recognized module's type, if implied by a tag.
* @yields {[string, string]}
*/
export const inferExportsEntries = function* inferExportsEntries(
{ main, module, exports },
tags,
conditions,
types,
) {
// From lowest to highest precedence, such that later entries override former
// entries.
if (module !== undefined && tags.has('import')) {
if (module !== undefined && conditions.has('import')) {
// In this one case, the key "module" has carried a hint that the
// referenced module is an ECMASCript module, and that hint may be
// necessary to override whatever type might be inferred from the module
Expand All @@ -135,7 +135,7 @@ export const inferExportsEntries = function* inferExportsEntries(
yield ['.', relativize(main)];
}
if (exports !== undefined) {
yield* interpretExports('.', exports, tags);
yield* interpretExports('.', exports, conditions);
}
// TODO Otherwise, glob 'files' for all '.js', '.cjs', and '.mjs' entry
// modules, taking care to exclude node_modules.
Expand All @@ -151,28 +151,28 @@ export const inferExportsEntries = function* inferExportsEntries(
* package's module map, like `./index.js`.
*
* @param {object} descriptor - the parsed body of a package.json file.
* @param {Set<string>} tags - build tags about the target environment
* @param {Set<string>} conditions - build conditions about the target environment
* for selecting relevant exports, e.g., "browser" or "node".
* @param {LanguageForExtension} types - an object to populate
* with any recognized module's type, if implied by a tag.
* @returns {Record<string, string>}
*/
export const inferExports = (descriptor, tags, types) =>
fromEntries(inferExportsEntries(descriptor, tags, types));
export const inferExports = (descriptor, conditions, types) =>
fromEntries(inferExportsEntries(descriptor, conditions, types));

export const inferExportsAndAliases = (
descriptor,
externalAliases,
internalAliases,
tags,
conditions,
types,
) => {
const { name, type, main, module, exports, browser } = descriptor;

// collect externalAliases from exports and main/module
assign(
externalAliases,
fromEntries(inferExportsEntries(descriptor, tags, types)),
fromEntries(inferExportsEntries(descriptor, conditions, types)),
);

// expose default module as package root
Expand All @@ -188,7 +188,7 @@ export const inferExportsAndAliases = (
}

// if present, allow "browser" field to populate moduleMap
if (tags.has('browser') && browser !== undefined) {
if (conditions.has('browser') && browser !== undefined) {
for (const [specifier, target] of interpretBrowserField(
name,
browser,
Expand Down
Loading

0 comments on commit 3f7aa66

Please sign in to comment.