diff --git a/packages/codegen/DESIGN.md b/packages/codegen/DESIGN.md index 1c424e4a7b..bee0885790 100644 --- a/packages/codegen/DESIGN.md +++ b/packages/codegen/DESIGN.md @@ -391,8 +391,11 @@ A plugin silently doing nothing is a failure mode worth guarding against. #### `unmap_writes` -Rewrites every mapped write into the plain one it becomes with no mapping to record - so -`writeWithMap(state, code, cat, node.start, node.end, node)` turns into `write(state, code, cat)` - +Runs in two modes, one per build flavour (sourcemaps / no sourcemaps), in both cases stripping arguments +nothing in that build reads. + +**Without source maps**, rewrites every mapped write into the plain one it becomes with no mapping to record - +so `writeWithMap(state, code, cat, node.start, node.end, node)` turns into `write(state, code, cat)` - and rewrites the import to match. `writeWithMapEnd` becomes `write` too, and every `NoLast` form becomes `writeNoLast`. `writeWithMapNamed` and `writeWithMapNamedPrivate` become `writeIdent` and `writePrivate`, which fix the category @@ -402,6 +405,10 @@ instead of taking it. `writePrivate` exists only for the rewrite to land on. They keep their names, but the arguments come off every call, and the parameters off their declarations - which, unlike the mapped writes, survive into the build. +**With source maps**, in release builds only, just the trailing `node` comes off - from every call, +and from every declaration. The mappings are real in these builds so the offsets stay, but `node` isn't read +except by debug asserts. Debug builds get neither mode - the `node` parameter stays for the debug asserts to read. + Without it, the offsets would still be read and held live across a call which ignores them. The three `markMap*` calls write nothing, so they have no plain form to become, and keep their names - diff --git a/packages/codegen/tsdown.config.ts b/packages/codegen/tsdown.config.ts index 5251d45d4b..30d619d68c 100644 --- a/packages/codegen/tsdown.config.ts +++ b/packages/codegen/tsdown.config.ts @@ -73,6 +73,9 @@ const minifyConfig = DEBUG // In builds without source maps, nothing reads the mapping arguments the mapped writes take, // so `unmap_writes` rewrites every mapped write call, and the imports which bring them in, // into the plain `write` / `writeNoLast` they become without the mapping arguments. +// +// In sourcemap release builds the same plugin removes only the trailing `node` argument, +// which nothing but the debug asserts those builds have lost ever read. const printerConfig = (name: string, { sourcemaps, ts }: { sourcemaps: boolean; ts: boolean }) => ({ ...commonConfig, minify: minifyConfig, @@ -89,7 +92,7 @@ const printerConfig = (name: string, { sourcemaps, ts }: { sourcemaps: boolean; // `const_functions` runs last, so the plugins before it still see function declarations. ...(ts ? [] : [stripTsPlugin()]), ...assertPlugins, - ...(sourcemaps ? [] : [unmapWritesPlugin()]), + unmapWritesPlugin(sourcemaps, DEBUG), constFunctionsPlugin, ], }); diff --git a/packages/codegen/tsdown_plugins/unmap_writes.ts b/packages/codegen/tsdown_plugins/unmap_writes.ts index 9fa3de1eeb..770d1a8275 100644 --- a/packages/codegen/tsdown_plugins/unmap_writes.ts +++ b/packages/codegen/tsdown_plugins/unmap_writes.ts @@ -49,6 +49,8 @@ const REWRITES = { markMapStart: { arity: 4, remove: 3, rename: null }, markMapAfter: { arity: 4, remove: 3, rename: null }, markMapAtStartOffset: { arity: 5, remove: 4, rename: null }, + markMapEnd: { arity: 4, remove: 3, rename: null }, + markMapNamed: { arity: 7, remove: 6, rename: null }, // `rename: null` to transform the function declarations, removing the dropped params printString: { arity: 5, remove: 3, rename: null }, printNonNegativeFloat: { arity: 5, remove: 3, rename: null }, @@ -59,11 +61,13 @@ const WRITE_MODULE = "./write.ts"; type MappedName = keyof typeof REWRITES; /** - * Create the plugin. + * Create the plugin for one build flavour. * + * @param sourcemaps - `true` to strip only `node`, `false` to unmap the writes entirely + * @param debug - `true` if is a debug build * @returns The plugin */ -export default function unmapWritesPlugin(): Plugin { +export default function unmapWritesPlugin(sourcemaps: boolean, debug: boolean): Plugin { return { name: "unmap-writes", transform: { @@ -71,6 +75,10 @@ export default function unmapWritesPlugin(): Plugin { filter: { id: /\/src-js\/print\/.+\.ts$/ }, handler(code, path, meta) { + // Don't alter anything in debug sourcemap builds. + // No functions are renamed and no arguments to those functions are removed. + if (sourcemaps && debug) return; + // Parse file const magicString = meta.magicString!; const { program, errors } = parseSync(path, code); @@ -92,7 +100,7 @@ export default function unmapWritesPlugin(): Plugin { const rewrite = REWRITES[name]; if (rewrite === undefined) return; - const { arity, remove, rename } = rewrite; + const { arity, rename } = rewrite; const args = node.arguments; if (args.length !== arity) { throw new Error( @@ -101,11 +109,14 @@ export default function unmapWritesPlugin(): Plugin { ); } - // Remove the dropped arguments, from the end of the last kept one to the end of the last + // Remove the dropped arguments, from the end of the last kept one to the end of the last. + // In sourcemaps builds, remove only the last argument (`node`). + const remove = sourcemaps ? 1 : rewrite.remove; magicString.remove(args[arity - remove - 1].end, args[arity - 1].end); - // If callee needs to be renamed, rename it, and record that it needs to be imported under new name - if (rename !== null) { + // If callee needs to be renamed, rename it, and record that it needs to be imported under new name. + // Nothing is renamed in sourcemap builds. + if (!sourcemaps && rename !== null) { magicString.overwrite(callee.start, callee.end, rename); neededImportReplacements.add(name); } @@ -120,7 +131,7 @@ export default function unmapWritesPlugin(): Plugin { const rewrite = REWRITES[name]; if (rewrite === undefined) return; - const { arity, remove } = rewrite; + const { arity } = rewrite; const { params } = node; if (params.length !== arity) { throw new Error( @@ -129,7 +140,9 @@ export default function unmapWritesPlugin(): Plugin { ); } - // Remove the dropped params, from the end of the last kept one to the end of the last + // Remove the dropped params, from the end of the last kept one to the end of the last. + // In sourcemaps builds, remove only the last param (`node`). + const remove = sourcemaps ? 1 : rewrite.remove; magicString.remove(params[arity - remove - 1].end, params[arity - 1].end); }, @@ -158,13 +171,17 @@ export default function unmapWritesPlugin(): Plugin { } const name = specifier.imported.name as MappedName; - const rewrite = REWRITES[name]; - if (rewrite !== undefined) { - const { rename } = rewrite; - if (rename !== null) { - specifierNames.add(rename); - importReplacements.push(name); - continue; + + // Only rename imports in no-sourcemap builds + if (!sourcemaps) { + const rewrite = REWRITES[name]; + if (rewrite !== undefined) { + const { rename } = rewrite; + if (rename !== null) { + specifierNames.add(rename); + importReplacements.push(name); + continue; + } } }