Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Preserve formatting #2444

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
"atLeast": 0
},
"resolutions": {
"@babel/types": "7.24.0",
"@lerna/version@npm:5.6.2": "patch:@lerna/version@npm%3A5.6.2#~/.yarn/patches/@lerna-version-npm-5.6.2-ce2d9cb2f5.patch",
"@lerna/conventional-commits@npm:5.6.2": "patch:@lerna/conventional-commits@npm%3A5.6.2#~/.yarn/patches/@lerna-conventional-commits-npm-5.6.2-a373ba4bc0.patch"
}
Expand Down
10 changes: 10 additions & 0 deletions packages/bundle-source/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
User-visible changes to `@endo/bundle-source`:

# Next release

- Replaces the implementation for the `nestedEvaluate` and `getExport`
formats with one based on Endo's Compartment Mapper instead of Rollup,
in order to obviate the need to reconcile source map transforms between
Rollup and the underlying Babel generator.
As a consequence, we no longer generate a source map for the bundle, but
Babel ensures that we preserve line and column numbers between the original
source and the bundled source.

# v3.5.0 (2024-11-13)

- Adds support for TypeScript type erasure using
Expand Down
91 changes: 78 additions & 13 deletions packages/bundle-source/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,31 @@ not exist.
## getExport moduleFormat

The most primitive `moduleFormat` is the `"getExport"` format.
It generates source like:
It generates a script where the completion value (last expression evaluated)
is a function that accepts an optional `sourceUrlPrefix`.

```js
function getExport() {
let exports = {};
const module = { exports };
// CommonJS source translated from the inputs.
...
return module.exports;
}
cosnt { source } = await bundleSource('program.js', { format: 'getExport' });
const exports = eval(source)();
```

To evaluate it and obtain the resulting module namespace, you need to endow
a `require` function to resolve external imports.
The `getExport` format can import host modules with a CommonJS `require`
function, if there is one in scope.
One can be endowed using a Hardened JavaScript `Compartment`.

```js
const compartment = new Comaprtment({
globals: { require },
__options__: true, // until SES and XS implementations converge
});
const exports = compartment.evaluate(source);
```

> :warning: The `getExport` format was previously implemented using
> [Rollup](https://rollupjs.org/) and is implemented with
> `@endo/compartment-mapper/bundle.js` starting with version 4 of
> `@endo/bundle-source`.
> See `nestedEvaluate` below for compatibility caveats.

## nestedEvaluate moduleFormat

Expand All @@ -145,9 +156,63 @@ to evaluate submodules in the same context as the parent function.
The advantage of this format is that it helps preserve the filenames within
the bundle in the event of any stack traces.

Also, the toplevel `getExport(filePrefix = "/bundled-source")` accepts an
optional `filePrefix` argument (which is prepended to relative paths for the
bundled files) in order to help give context to stack traces.
The completion value of a `nestedEvaluate` bundle is a function that accepts
the `sourceUrlPrefix` for every module in the bundle, which will appear in stack
traces and assist debuggers to find a matching source file.

```js
cosnt { source } = await bundleSource('program.js', { format: 'nestedEvaluate' });
const compartment = new Comaprtment({
globals: {
require,
nestedEvaluate: source => compartment.evaluate(source),
},
__options__: true, // until SES and XS implementations converge
});
const exports = compartment.evaluate(source)('bundled-sources/.../');
```

> :warning: The `nestedEvaluate` format was previously implemented using
> [Rollup](https://rollupjs.org/) and is implemented with
> `@endo/compartment-mapper/bundle.js` starting with version 4 of
> `@endo/bundle-source`.
> Their behaviors are not identical.
>
> 1. Version 3 used different heuristics than Node.js 18 for inferring whether
> a module was in CommonJS format or ESM format. Version 4 does not guess,
> but relies on the `"type": "module"` directive in `package.json` to indicate
> that a `.js` extension implies ESM format, or respects the explicit `.cjs`
> and `.mjs` extensions.
> 2. Version 3 supports [live
> bindings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#imported_values_can_only_be_modified_by_the_exporter)
> and Version 4 does not.
> 3. Version 3 can import any package that is discoverable by walking parent directories
> until the dependency or devDependeny is found in a `node_modules` directory.
> Version 4 requires that the dependent package explicitly note the dependency
> in `package.json`.
> 4. Version 3 and 4 generate different text.
> Any treatment of that text that is sensitive to the exact shape of the
> text is fragile and may break even between minor and patch versions.

## endoScript moduleFormat

The `ses` shim uses the `endoScript` format to generate its distribution bundles,
suitable for injecting in a web page with a `<script>` tag.
For this format, extract the `source` from the generated JSON envelope and place
it in a file you embed in a web page, an Agoric
[Core Eval](https://docs.agoric.com/guides/coreeval/) script, or evaluate
anywhere that accepts scripts.

```js
const { source } = await bundleSource('program.js', { format: 'endoScript' });
const compartment = new Compartment();
compartment.evaluate(source);
```

Unlike, `getExport` and `nestedEvaluate`, the `dev` option to `bundleSource` is
required for any bundle that imports `devDependencies`.
The `endoScript` format does not support importing host modules with CommonJS
`require`.

## endoZipBase64 moduleFormat

Expand Down
2 changes: 1 addition & 1 deletion packages/bundle-source/demo/dir1/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { encourage, makeError } from './encourage.js';
import more from './sub/more.js';
import more from './sub/more.cjs';

export default function makeEncourager() {
return harden({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global exports require */
const things = require('./things.js');
const things = require('./things.cjs');

exports.more = `have more ${things.description}`;
6 changes: 5 additions & 1 deletion packages/bundle-source/demo/external-fs.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
import 'fs';
import * as fs from 'fs';
import { readFileSync } from 'fs';

assert(fs[Symbol.toStringTag] === 'Module');
assert(typeof readFileSync === 'function');
6 changes: 1 addition & 5 deletions packages/bundle-source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@
"@endo/init": "workspace:^",
"@endo/promise-kit": "workspace:^",
"@endo/where": "workspace:^",
"@rollup/plugin-commonjs": "^19.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"acorn": "^8.2.4",
"rollup": "^2.79.1",
"ts-blank-space": "^0.4.1"
},
"devDependencies": {
Expand All @@ -45,6 +40,7 @@
"ava": "^6.1.3",
"c8": "^7.14.0",
"eslint": "^8.57.0",
"ses": "workspace:^",
"typescript": "~5.6.3"
},
"keywords": [],
Expand Down
24 changes: 3 additions & 21 deletions packages/bundle-source/src/bundle-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,11 @@ const bundleSource = async (
const { bundleZipBase64 } = await import('./zip-base64.js');
return bundleZipBase64(startFilename, options, powers);
}
case 'getExport':
case 'nestedEvaluate':
case 'endoScript': {
const { bundleScript } = await import('./script.js');
return bundleScript(startFilename, options, powers);
}
case 'getExport': {
const { bundleNestedEvaluateAndGetExports } = await import(
'./nested-evaluate-and-get-exports.js'
);
return bundleNestedEvaluateAndGetExports(
startFilename,
moduleFormat,
powers,
);
}
case 'nestedEvaluate': {
const { bundleNestedEvaluateAndGetExports } = await import(
'./nested-evaluate-and-get-exports.js'
);
return bundleNestedEvaluateAndGetExports(
startFilename,
moduleFormat,
powers,
);
return bundleScript(startFilename, moduleFormat, options, powers);
}
default:
if (!SUPPORTED_FORMATS.includes(moduleFormat)) {
Expand Down
Loading
Loading