Skip to content

Commit

Permalink
refactor(bundle-source): Node.js conditions (#2373)
Browse files Browse the repository at this point in the history
## Description

The upcoming release was planned to introduce a `-t,--tags` flag to the
`bundle-source` command. I’ve found that the analogous flag in the
Node.js CLI is `-C,--condition` and elected to take this opportunity to
make these consistent, before releasing the new feature.

### Security Considerations

None.

### Scaling Considerations

None.

### Documentation Considerations

Usage docs update. Not yet documented in README.

### Testing Considerations

Covered with a CLI test.

### Compatibility Considerations

This feature is not yet released, so the change to `bundle-source` has
no compatibility concerns.

The change to `@endo/compartment-mapper` deprecates the `tags` option to
all functions and aliases `conditions` for backward-compatibility.

### Upgrade Considerations

None.
  • Loading branch information
kriskowal authored Jul 23, 2024
2 parents 4ae3915 + 7e516a0 commit 655a74c
Show file tree
Hide file tree
Showing 22 changed files with 159 additions and 148 deletions.
4 changes: 2 additions & 2 deletions packages/bundle-source/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
- Adds a `-f,--format` command flag to specify other module formats.
- Adds a new `endoScript` module format.
- Adds a no-cache, bundle-to-stdout mode.
- Adds a `-t,--tag` command flag to specify export/import conditions like
`"browser"`.
- Adds a `-C,--condition` command flag to specify export/import conditions like
`"development"` or `"browser"`.

# v3.2.1 (2024-03-20)

Expand Down
44 changes: 23 additions & 21 deletions packages/bundle-source/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const { Fail, quote: q } = assert;
* @property {number} bundleSize
* @property {boolean} noTransforms
* @property {ModuleFormat} format
* @property {string[]} tags
* @property {string[]} conditions
* @property {{ relative: string, absolute: string }} moduleSource
* @property {Array<{ relativePath: string, mtime: string, size: number }>} contents
*/
Expand Down Expand Up @@ -53,6 +53,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
* @param {object} [options]
* @param {boolean} [options.noTransforms]
* @param {string[]} [options.tags]
* @param {string[]} [options.conditions]
* @param {ModuleFormat} [options.format]
*/
const add = async (rootPath, targetName, log = defaultLog, options = {}) => {
Expand All @@ -61,10 +62,10 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
const {
noTransforms = false,
format = 'endoZipBase64',
tags = [],
conditions = [],
} = options;

tags.sort();
conditions.sort();

const statsByPath = new Map();

Expand Down Expand Up @@ -93,7 +94,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {

const bundle = await bundleSource(
rootPath,
{ ...bundleOptions, noTransforms, format },
{ ...bundleOptions, noTransforms, format, conditions },
{
...readPowers,
read: loggedRead,
Expand Down Expand Up @@ -123,7 +124,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
),
noTransforms,
format,
tags,
conditions,
};

await metaWr.atomicWriteText(JSON.stringify(meta, null, 2));
Expand Down Expand Up @@ -154,6 +155,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
* @param {boolean} [options.noTransforms]
* @param {ModuleFormat} [options.format]
* @param {string[]} [options.tags]
* @param {string[]} [options.conditions]
* @returns {Promise<BundleMeta>}
*/
const validate = async (
Expand All @@ -167,9 +169,9 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
const {
noTransforms: expectedNoTransforms,
format: expectedFormat,
tags: expectedTags = [],
conditions: expectedConditions = [],
} = options;
expectedTags.sort();
expectedConditions.sort();
if (!meta) {
const metaJson = await loadMetaText(targetName, log);
if (metaJson) {
Expand All @@ -194,15 +196,15 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
moduleSource: { absolute: moduleSource },
format = 'endoZipBase64',
noTransforms = false,
tags = [],
conditions = [],
} = meta;
tags.sort();
conditions.sort();
assert.equal(bundleFileName, toBundleName(targetName));
assert.equal(format, expectedFormat);
assert.equal(noTransforms, expectedNoTransforms);
assert.equal(tags.length, expectedTags.length);
tags.forEach((tag, index) => {
assert.equal(tag, expectedTags[index]);
assert.equal(conditions.length, expectedConditions.length);
conditions.forEach((tag, index) => {
assert.equal(tag, expectedConditions[index]);
});
if (rootOpt) {
moduleSource === cwd.neighbor(rootOpt).absolute() ||
Expand Down Expand Up @@ -250,7 +252,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
* @param {object} [options]
* @param {boolean} [options.noTransforms]
* @param {ModuleFormat} [options.format]
* @param {string[]} [options.tags]
* @param {string[]} [options.conditions]
* @returns {Promise<BundleMeta>}
*/
const validateOrAdd = async (
Expand All @@ -269,15 +271,15 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
meta = await validate(targetName, rootPath, log, meta, {
format: options.format,
noTransforms: options.noTransforms,
tags: options.tags,
conditions: options.conditions,
});
const {
bundleTime,
bundleSize,
contents,
noTransforms,
format = 'endoZipBase64',
tags = [],
conditions = [],
} = meta;
log(
`${wr}`,
Expand All @@ -291,8 +293,8 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
noTransforms ? 'w/o transforms' : 'with transforms',
'with format',
format,
'and tags',
JSON.stringify(tags),
'and conditions',
JSON.stringify(conditions),
);
} catch (invalid) {
meta = undefined;
Expand All @@ -309,7 +311,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
contents,
noTransforms,
format = 'endoZipBase64',
tags = [],
conditions = [],
} = meta;
log(
`${wr}`,
Expand All @@ -322,8 +324,8 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
noTransforms ? 'w/o transforms' : 'with transforms',
'with format',
format,
'and tags',
JSON.stringify(tags),
'and conditions',
JSON.stringify(conditions),
);
}

Expand All @@ -338,7 +340,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => {
* @param {object} [options]
* @param {boolean} [options.noTransforms]
* @param {ModuleFormat} [options.format]
* @param {string[]} [options.tags]
* @param {string[]} [options.conditions]
*/
const load = async (
rootPath,
Expand Down
12 changes: 6 additions & 6 deletions packages/bundle-source/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const USAGE = `\
bundle-source [-Tft] <entry.js>
bundle-source [-Tft] --cache-js|--cache-json <cache/> (<entry.js> <bundle-name>)*
-f,--format endoZipBase64*|nestedEvaluate|getExport
-t,--tag <tag> (browser, node, &c)
-C,--condition <condition> (browser, node, development, &c)
-T,--no-transforms`;

const options = /** @type {const} */ ({
Expand All @@ -33,9 +33,9 @@ const options = /** @type {const} */ ({
short: 'f',
multiple: false,
},
tag: {
condition: {
type: 'string',
short: 't',
short: 'C',
multiple: true,
},
// deprecated
Expand All @@ -58,7 +58,7 @@ export const main = async (args, { loadModule, pid, log }) => {
const {
values: {
format: moduleFormat = 'endoZipBase64',
tag: tags = [],
condition: conditions = [],
'no-transforms': noTransforms,
'cache-json': cacheJson,
'cache-js': cacheJs,
Expand Down Expand Up @@ -94,7 +94,7 @@ export const main = async (args, { loadModule, pid, log }) => {
const bundle = await bundleSource(entryPath, {
noTransforms,
format,
tags,
conditions,
});
process.stdout.write(JSON.stringify(bundle));
process.stdout.write('\n');
Expand Down Expand Up @@ -125,7 +125,7 @@ export const main = async (args, { loadModule, pid, log }) => {
await cache.validateOrAdd(bundleRoot, bundleName, undefined, {
noTransforms,
format,
tags,
conditions,
});
}
};
4 changes: 2 additions & 2 deletions packages/bundle-source/src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function bundleScript(
dev = false,
cacheSourceMaps = false,
noTransforms = false,
tags = [],
conditions = [],
commonDependencies,
} = options;
const powers = { ...readPowers, ...grantedPowers };
Expand Down Expand Up @@ -70,7 +70,7 @@ export async function bundleScript(

const source = await makeBundle(powers, entry, {
dev,
tags,
conditions,
commonDependencies,
parserForLanguage,
moduleTransforms,
Expand Down
2 changes: 1 addition & 1 deletion packages/bundle-source/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export {};
* @property {boolean} [noTransforms] - when true, generates a bundle with the
* original sources instead of SES-shim specific ESM and CJS. This may become
* default in a future major version.
* @property {string[]} [tags] - conditions for package.json conditional
* @property {string[]} [conditions] - conditions for package.json conditional
* exports and imports.
*/

Expand Down
6 changes: 3 additions & 3 deletions packages/bundle-source/src/zip-base64.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const readPowers = makeReadPowers({ fs, url, crypto });
* @param {boolean} [options.dev]
* @param {boolean} [options.cacheSourceMaps]
* @param {boolean} [options.noTransforms]
* @param {string[]} [options.tags]
* @param {string[]} [options.condditions]
* @param {Record<string, string>} [options.commonDependencies]
* @param {object} [grantedPowers]
* @param {(bytes: string | Uint8Array) => string} [grantedPowers.computeSha512]
Expand All @@ -40,7 +40,7 @@ export async function bundleZipBase64(
dev = false,
cacheSourceMaps = false,
noTransforms = false,
tags = [],
conditions = [],
commonDependencies,
} = options;
const powers = { ...readPowers, ...grantedPowers };
Expand Down Expand Up @@ -73,7 +73,7 @@ export async function bundleZipBase64(

const compartmentMap = await mapNodeModules(powers, entry, {
dev,
tags,
conditions,
commonDependencies,
});

Expand Down
6 changes: 3 additions & 3 deletions packages/bundle-source/test/tags-command.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ const bundleSource = async (...args) => {
return JSON.parse(bundleText);
};

test('bundle-source with --format and --tag', async t => {
test('bundle-source with --format and --condition', async t => {
const compartment = new Compartment();
{
const bundle = await bundleSource(
'--tag',
'--condition',
'b',
'--format',
'endoScript',
Expand All @@ -55,7 +55,7 @@ test('bundle-source with --format and --tag', async t => {
}
{
const bundle = await bundleSource(
'--tag',
'--condition',
'a',
'--format',
'endoScript',
Expand Down
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
Loading

0 comments on commit 655a74c

Please sign in to comment.