Add a flag to preserve comments during transpilation
What is the problem this feature would solve?
Comments like this one are currently stripped out:

I need them at runtime to parse from func.toString()
What is the feature you are proposing to solve the problem?
A flag to preserve comments during transpilation
What alternatives have you considered?
No response
I'd also like to use comments for annotations for subsequent build steps, such as https://github.com/terser/terser#annotations.
there probably should be an option to persist vite-ignore comments, too. Unsure how to avoid them otherwise
The opposite should be the default. Comments should be preserved by default, and removed only when instructed to be removed. Comments are there for a reason.
This is how other JavaScript runtimes handle comments, from here originally https://github.com/oven-sh/bun/issues/8727#issuecomment-2440431738
@paperdave Consider what this says:
$ bun build --help | grep no-bundle
--no-bundle Transpile file only, do not bundle
That "Transpile file only" language is important. There's no expectation that anything other than transpilations will occur.
bun build --no-bundle also removes shebang lines.
Just for completeness, Node.js' amaro when node --experimental-strip-types is used, to only transpile TypeScript source code to JavaScript, doesn't remove comments.
bun install amaro
bun build node_modules/amaro/dist/index.js --target=node --outfile=nodejs-amaro-bundle.js
strip-types.js
import * as amaro from "./nodejs-amaro-bundle.js";
import { readFileSync, writeFileSync } from "node:fs";
import * as process from "node:process";
const { load, transformSync } = amaro.default;
const url = new URL(process.argv.at(-1), import.meta.url);
let { code } = transformSync(readFileSync(url.pathname));
// TODO Remove space characters where types were @guest271314
// code = code.replace(/\s+(?=[,=()])/g, "").replace(/\s+(?=[{])/g, " ");
console.log(code);
writeFileSync(url.pathname.split("/").at(-1) + ".js", code);
bun run strip-types.js ../path/to/file.ts
or
node strip-types.js ../path/to/file.ts
though does leave multiple space characters where the types were defined
const runtime = navigator.userAgent;
const buffer = new ArrayBuffer(0, { maxByteLength: 1024 ** 2 });
const view = new DataView(buffer);
const encoder = new TextEncoder();
let readable ,
writable ,
exit = () => {};
// ...
function encodeMessage(message ) {
// ...
async function* getMessage() {
// ...
Deno transpiles and preserves comments when deno install --entrypoint ../path/to/file.ts
// Transpile TypeScript to JavaScript using Deno built-ins
// Usage: deno -A deno-ts-js-cache.js path/to/file.ts
// Write Deno's generated cache .ts.js file to stdout and current directory
// ts: Path to .ts script
const [ts] = Deno.args;
const url = new URL(import.meta.resolve(ts));
const { pathname } = url;
const filename = pathname.split("/").at(-1);
const decoder = new TextDecoder();
// Path to generated cache .ts.js script
const jsCache = `file${pathname}.js`;
// info: Get Deno cache and TypeScript cache subdirectories
const info = new Deno.Command(Deno.execPath(), {
args: [
"info",
"--json",
],
});
// Deno cache directory and generated TypeScript subdirectory
const { denoDir, typescriptCache } = JSON.parse(
decoder.decode((await info.output()).stdout),
);
// Cache
// https://docs.deno.com/runtime/fundamentals/modules/#vendoring-remote-modules
const command = await new Deno.Command(Deno.execPath(), {
args: ["install", "--entrypoint", "-r", ts],
}).output();
// Generated cache .ts.js file
const js = (await Deno.readTextFile(`${typescriptCache}/${jsCache}`))
.replace(/\/\/#\ssourceMappingURL=.+\n\/\/.+$/img, "");
console.log(js);
await Deno.writeTextFile(`${filename}.js`, js);
Deno.exit(0);
deno -A deno-ts-js-cache.js ../path/to/file.ts
#!/usr/bin/env -S /home/user/bin/bun run
// ...
// Resizable ArrayBuffer supported by tsc Version 5.7.0-dev.20241019
import process from "node:process";
const runtime = navigator.userAgent;
const buffer = new ArrayBuffer(0, {
maxByteLength: 1024 ** 2
});
const view = new DataView(buffer);
const encoder = new TextEncoder();
let readable, writable, exit = ()=>{};
if (runtime.startsWith("Deno")) {
// @ts-ignore Deno
({ readable } = Deno.stdin);
// @ts-ignore Deno
({ writable } = Deno.stdout);
// @ts-ignore Deno
({ exit } = Deno);
}
if (runtime.startsWith("Node")) {
readable = process.stdin;
writable = new WritableStream({
write (value) {
process.stdout.write(value);
}
}, new CountQueuingStrategy({
highWaterMark: Infinity
}));
({ exit } = process);
}
if (runtime.startsWith("Bun")) {
// @ts-ignore Bun
readable = Bun.file("/dev/stdin").stream();
writable = new WritableStream({
async write (value) {
// @ts-ignore Bun
await Bun.write(Bun.stdout, value);
}
}, new CountQueuingStrategy({
highWaterMark: Infinity
}));
({ exit } = process);
}
bun build --no-bundle does not preserve comments, including removing the shebang line
bun build ~/path/to/file.ts --no-bundle --outfile=filets.js
import process from "node:process";
const runtime = navigator.userAgent;
const buffer = new ArrayBuffer(0, { maxByteLength: 1024 ** 2 });
const view = new DataView(buffer);
const encoder = new TextEncoder;
let readable, writable, exit = () => {
};
if (runtime.startsWith("Deno")) {
({ readable } = Deno.stdin);
({ writable } = Deno.stdout);
({ exit } = Deno);
}
if (runtime.startsWith("Node")) {
readable = process.stdin;
writable = new WritableStream({
write(value) {
process.stdout.write(value);
}
}, new CountQueuingStrategy({ highWaterMark: 1 / 0 }));
({ exit } = process);
}
if (runtime.startsWith("Bun")) {
readable = Bun.file("/dev/stdin").stream();
writable = new WritableStream({
async write(value) {
await Bun.write(Bun.stdout, value);
}
}, new CountQueuingStrategy({ highWaterMark: 1 / 0 }));
({ exit } = process);
}
The opposite should be the default. Comments should be preserved by default, and removed only when instructed to be removed.
I agree with this, we are on the same page. The code that tracks comments doesn't exist. When it does, I think it should be active when --minify-whitespace is disabled.
I have had times where I had to flip between source code and bundled code to read comments, when this is not the case with any other bundler.
Looks like Node.js' amaro doesn't need to be since last week or so. transform does remove comments, strip doesn't, and doesn't format space characters where types were
node node-strip-types.js ../path/to/file.ts strip
node node-strip-types.js ../path/to/file.ts transform
// https://github.com/nodejs/node/commit/53b1050e6f692ee0330e1076e045b58aada0032d#diff-4e8f3cce79719e4a337f58575b20c998b093eb64164b847ca0eb9ba884d8a801R338
import { stripTypeScriptTypes } from "node:module";
import { readFileSync, writeFileSync } from "node:fs";
import { stdout } from "node:process";
const [, , sourceTs, mode = "transform", sourceUrl = sourceTs, sourceMap = false] = process.argv;
const code = readFileSync(sourceTs, "utf8");
const strippedCode = stripTypeScriptTypes(code, { mode, sourceUrl, sourceMap });
stdout.write(strippedCode);