From fa48cac2ee6842723aaf1e55f05fe56002dba56c Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Mon, 19 Aug 2024 16:59:34 -0700 Subject: [PATCH] feat(bundle-source): --no-comment --- packages/bundle-source/NEWS.md | 7 ++++++ packages/bundle-source/README.md | 8 +++++++ packages/bundle-source/cache.js | 23 ++++++++++++++++--- packages/bundle-source/demo/meaningful.js | 4 ++++ packages/bundle-source/src/endo.js | 9 +++++++- packages/bundle-source/src/main.js | 16 ++++++++++++- packages/bundle-source/src/script.js | 4 ++++ packages/bundle-source/src/types.js | 3 +++ packages/bundle-source/src/zip-base64.js | 3 +++ .../test/endo-script-format.tests.js | 18 +++++++++++++-- 10 files changed, 88 insertions(+), 7 deletions(-) diff --git a/packages/bundle-source/NEWS.md b/packages/bundle-source/NEWS.md index d93df151e8..6f08b4c70f 100644 --- a/packages/bundle-source/NEWS.md +++ b/packages/bundle-source/NEWS.md @@ -1,5 +1,12 @@ User-visible changes to `@endo/bundle-source`: +# Next version + +- Adds support for `--no-comment` (`-X`) that blanks out the interior of + comments in `endoScript` and `endoZipBase64` formats to reduce bundle size + without compromising cursor position correspondence between source and bundle + code. + # v3.3.0 (2024-07-30) - Adds support for `--no-transforms` (`-T`) which generates bundles with diff --git a/packages/bundle-source/README.md b/packages/bundle-source/README.md index 766b94f9e2..60feb1174a 100644 --- a/packages/bundle-source/README.md +++ b/packages/bundle-source/README.md @@ -42,6 +42,14 @@ entry instead of `main` in `package.json`, if not overridden by explicit The `development` condition additionally implies that the bundle may import `devDependencies` from the package containing the entry module. +## Comment Elision + +The `--no-comment` (`-X`) flag with `--format` `endoScript` or `endoZipBase64` +(default) causes the bundler to blank out the interior of comments, without +compromising line or column number cursor advancement. +This can reduce bundle size without harming the debug experience any more than +other transforms. + ## Source maps With the `moduleFormat` of `endoZipBase64`, the bundler can generate source diff --git a/packages/bundle-source/cache.js b/packages/bundle-source/cache.js index 7a3640264e..d2e92b0ddb 100644 --- a/packages/bundle-source/cache.js +++ b/packages/bundle-source/cache.js @@ -19,6 +19,7 @@ const { Fail, quote: q } = assert; * @property {string} bundleTime ISO format * @property {number} bundleSize * @property {boolean} noTransforms + * @property {boolean} elideComments * @property {ModuleFormat} format * @property {string[]} conditions * @property {{ relative: string, absolute: string }} moduleSource @@ -52,6 +53,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { * @param {Logger} [log] * @param {object} [options] * @param {boolean} [options.noTransforms] + * @param {boolean} [options.elideComments] * @param {string[]} [options.conditions] * @param {ModuleFormat} [options.format] */ @@ -60,6 +62,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { const { noTransforms = false, + elideComments = false, format = 'endoZipBase64', conditions = [], } = options; @@ -93,7 +96,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { const bundle = await bundleSource( rootPath, - { ...bundleOptions, noTransforms, format, conditions }, + { ...bundleOptions, noTransforms, elideComments, format, conditions }, { ...readPowers, read: loggedRead, @@ -122,6 +125,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { }), ), noTransforms, + elideComments, format, conditions, }; @@ -152,6 +156,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { * @param {BundleMeta} [meta] * @param {object} [options] * @param {boolean} [options.noTransforms] + * @param {boolean} [options.elideComments] * @param {ModuleFormat} [options.format] * @param {string[]} [options.conditions] * @returns {Promise} @@ -166,6 +171,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { await null; const { noTransforms: expectedNoTransforms = false, + elideComments: expectedElideComments = false, format: expectedFormat = DEFAULT_MODULE_FORMAT, conditions: expectedConditions = [], } = options; @@ -194,12 +200,14 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { moduleSource: { absolute: moduleSource }, format = 'endoZipBase64', noTransforms = false, + elideComments = false, conditions = [], } = meta; conditions.sort(); assert.equal(bundleFileName, toBundleName(targetName)); assert.equal(format, expectedFormat); assert.equal(noTransforms, expectedNoTransforms); + assert.equal(elideComments, expectedElideComments); assert.equal(conditions.length, expectedConditions.length); conditions.forEach((tag, index) => { assert.equal(tag, expectedConditions[index]); @@ -249,6 +257,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { * @param {Logger} [log] * @param {object} [options] * @param {boolean} [options.noTransforms] + * @param {boolean} [options.elideComments] * @param {ModuleFormat} [options.format] * @param {string[]} [options.conditions] * @returns {Promise} @@ -269,6 +278,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { meta = await validate(targetName, rootPath, log, meta, { format: options.format, noTransforms: options.noTransforms, + elideComments: options.elideComments, conditions: options.conditions, }); const { @@ -276,6 +286,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { bundleSize, contents, noTransforms, + elideComments, format = 'endoZipBase64', conditions = [], } = meta; @@ -288,7 +299,9 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { bundleTime, 'with size', bundleSize, - noTransforms ? 'w/o transforms' : 'with transforms', + noTransforms + ? 'w/o transforms' + : 'with transforms' + (elideComments ? ' and comments elided' : ''), 'with format', format, 'and conditions', @@ -308,6 +321,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { bundleTime, contents, noTransforms, + elideComments, format = 'endoZipBase64', conditions = [], } = meta; @@ -319,7 +333,9 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { bundleFileName, 'at', bundleTime, - noTransforms ? 'w/o transforms' : 'with transforms', + noTransforms + ? 'w/o transforms' + : 'with transforms' + (elideComments ? ' and comments elided' : ''), 'with format', format, 'and conditions', @@ -337,6 +353,7 @@ export const makeBundleCache = (wr, cwd, readPowers, opts) => { * @param {Logger} [log] * @param {object} [options] * @param {boolean} [options.noTransforms] + * @param {boolean} [options.elideComments] * @param {ModuleFormat} [options.format] * @param {string[]} [options.conditions] */ diff --git a/packages/bundle-source/demo/meaningful.js b/packages/bundle-source/demo/meaningful.js index 5d3808dcf6..dafdca33d4 100644 --- a/packages/bundle-source/demo/meaningful.js +++ b/packages/bundle-source/demo/meaningful.js @@ -1,3 +1,7 @@ import json from './meaning.json'; +/** + * This comment will be blanked with the elideComments / --no-comment feature + * enabled. + */ export const meaning = json.meaning; diff --git a/packages/bundle-source/src/endo.js b/packages/bundle-source/src/endo.js index 6f9c8f5d5b..0608087f72 100644 --- a/packages/bundle-source/src/endo.js +++ b/packages/bundle-source/src/endo.js @@ -12,8 +12,14 @@ const textDecoder = new TextDecoder(); export const makeBundlingKit = ( { pathResolve, userInfo, computeSha512, platform, env }, - { cacheSourceMaps, noTransforms, commonDependencies, dev }, + { cacheSourceMaps, elideComments, noTransforms, commonDependencies, dev }, ) => { + if (noTransforms && elideComments) { + throw new Error( + 'bundleSource endoZipBase64 cannot elideComments with noTransforms', + ); + } + const sourceMapJobs = new Set(); let writeSourceMap = Function.prototype; if (cacheSourceMaps) { @@ -112,6 +118,7 @@ export const makeBundlingKit = ( sourceType: babelSourceType, sourceMap, sourceMapUrl: new URL(specifier, location).href, + elideComments, })); const objectBytes = textEncoder.encode(object); return { bytes: objectBytes, parser, sourceMap }; diff --git a/packages/bundle-source/src/main.js b/packages/bundle-source/src/main.js index c8b54a7205..f70d61e5e8 100644 --- a/packages/bundle-source/src/main.js +++ b/packages/bundle-source/src/main.js @@ -13,7 +13,8 @@ bundle-source [-Tft] --cache-js|--cache-json ( ) -f,--format endoZipBase64*|nestedEvaluate|getExport -C,--condition (browser, node, development, &c) -C development (to access devDependencies) - -T,--no-transforms`; + -T,--no-transforms + -X,--no-comment`; const options = /** @type {const} */ ({ 'no-transforms': { @@ -21,6 +22,11 @@ const options = /** @type {const} */ ({ short: 'T', multiple: false, }, + 'no-comment': { + type: 'boolean', + short: 'X', + multiple: false, + }, 'cache-js': { type: 'string', multiple: false, @@ -61,6 +67,7 @@ export const main = async (args, { loadModule, pid, log }) => { format: moduleFormat = 'endoZipBase64', condition: conditions = [], 'no-transforms': noTransforms, + 'no-comment': elideComments, 'cache-json': cacheJson, 'cache-js': cacheJs, // deprecated @@ -69,6 +76,11 @@ export const main = async (args, { loadModule, pid, log }) => { positionals, } = parseArgs({ args, options, allowPositionals: true }); + if (noTransforms && elideComments) { + throw Error( + `Cannot elide comments without transforms (-T,--no-transforms + -X,--no-comment)`, + ); + } if (!SUPPORTED_FORMATS.includes(moduleFormat)) { throw Error(`Unsupported format: ${moduleFormat}\n\n${USAGE}`); } @@ -94,6 +106,7 @@ export const main = async (args, { loadModule, pid, log }) => { const [entryPath] = positionals; const bundle = await bundleSource(entryPath, { noTransforms, + elideComments, format, conditions, }); @@ -125,6 +138,7 @@ export const main = async (args, { loadModule, pid, log }) => { // eslint-disable-next-line no-await-in-loop await cache.validateOrAdd(bundleRoot, bundleName, undefined, { noTransforms, + elideComments, format, conditions, }); diff --git a/packages/bundle-source/src/script.js b/packages/bundle-source/src/script.js index 3e08e8347b..c8f633a5b8 100644 --- a/packages/bundle-source/src/script.js +++ b/packages/bundle-source/src/script.js @@ -20,6 +20,7 @@ const readPowers = makeReadPowers({ fs, url, crypto }); * @param {boolean} [options.dev] * @param {boolean} [options.cacheSourceMaps] * @param {boolean} [options.noTransforms] + * @param {boolean} [options.elideComments] * @param {Record} [options.commonDependencies] * @param {object} [grantedPowers] * @param {(bytes: string | Uint8Array) => string} [grantedPowers.computeSha512] @@ -37,9 +38,11 @@ export async function bundleScript( dev = false, cacheSourceMaps = false, noTransforms = false, + elideComments = false, conditions = [], commonDependencies, } = options; + const powers = { ...readPowers, ...grantedPowers }; const { computeSha512, @@ -63,6 +66,7 @@ export async function bundleScript( { cacheSourceMaps, noTransforms, + elideComments, commonDependencies, dev, }, diff --git a/packages/bundle-source/src/types.js b/packages/bundle-source/src/types.js index 087e0adab7..617c17f20f 100644 --- a/packages/bundle-source/src/types.js +++ b/packages/bundle-source/src/types.js @@ -67,6 +67,9 @@ export {}; * @property {T} [format] * @property {boolean} [dev] - development mode, for test bundles that need * access to devDependencies of the entry package. + * @property {boolean} [elideComments] - when true for the `endoScript` and + * `endoZipBase64` format, replaces the interior of comments with blank space + * that advances the cursor the same number of lines and columns. * @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. diff --git a/packages/bundle-source/src/zip-base64.js b/packages/bundle-source/src/zip-base64.js index d097e9d63b..6f5de55fa8 100644 --- a/packages/bundle-source/src/zip-base64.js +++ b/packages/bundle-source/src/zip-base64.js @@ -22,6 +22,7 @@ const readPowers = makeReadPowers({ fs, url, crypto }); * @param {boolean} [options.dev] * @param {boolean} [options.cacheSourceMaps] * @param {boolean} [options.noTransforms] + * @param {boolean} [options.elideComments] * @param {string[]} [options.conditions] * @param {Record} [options.commonDependencies] * @param {object} [grantedPowers] @@ -40,6 +41,7 @@ export async function bundleZipBase64( dev = false, cacheSourceMaps = false, noTransforms = false, + elideComments = false, conditions = [], commonDependencies, } = options; @@ -66,6 +68,7 @@ export async function bundleZipBase64( { cacheSourceMaps, noTransforms, + elideComments, commonDependencies, dev, }, diff --git a/packages/bundle-source/test/endo-script-format.tests.js b/packages/bundle-source/test/endo-script-format.tests.js index c1146bbeb5..d0462a994f 100644 --- a/packages/bundle-source/test/endo-script-format.tests.js +++ b/packages/bundle-source/test/endo-script-format.tests.js @@ -4,16 +4,30 @@ import test from '@endo/ses-ava/prepare-endo.js'; import * as url from 'url'; import bundleSource from '../src/index.js'; -test('endo script format', async t => { +const generate = async (options = {}) => { const entryPath = url.fileURLToPath( new URL(`../demo/meaning.js`, import.meta.url), ); - const bundle = await bundleSource(entryPath, { + return bundleSource(entryPath, { format: 'endoScript', + ...options, }); +}; + +test('endo script format', async t => { + const bundle = await generate(); t.is(bundle.moduleFormat, 'endoScript'); const { source } = bundle; const compartment = new Compartment(); const ns = compartment.evaluate(source); t.is(ns.meaning, 42); }); + +test('endo script format is smaller with blank comments', async t => { + const bigBundle = await generate(); + const smallBundle = await generate({ elideComments: true }); + const compartment = new Compartment(); + const ns = compartment.evaluate(smallBundle.source); + t.is(ns.meaning, 42); + t.assert(smallBundle.source.length < bigBundle.source.length); +});