mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 06:12:08 +00:00
Compare commits
4 Commits
claude/fix
...
claude/min
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
410f4d5d49 | ||
|
|
eaad2e22e8 | ||
|
|
eba6db8d5f | ||
|
|
fdab33294a |
@@ -56,6 +56,7 @@ pub fn buildCommand(ctx: bun.cli.Command.Context) !void {
|
||||
b.resolver.env_loader = b.env;
|
||||
b.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
|
||||
b.options.minify_whitespace = ctx.bundler_options.minify_whitespace;
|
||||
b.options.minify_internal_exports = ctx.bundler_options.minify_internal_exports;
|
||||
b.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
|
||||
b.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
|
||||
b.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace;
|
||||
|
||||
@@ -62,6 +62,7 @@ pub const Run = struct {
|
||||
|
||||
b.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
|
||||
b.options.minify_whitespace = ctx.bundler_options.minify_whitespace;
|
||||
b.options.minify_internal_exports = ctx.bundler_options.minify_internal_exports;
|
||||
b.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
|
||||
b.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
|
||||
b.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace;
|
||||
@@ -210,6 +211,7 @@ pub const Run = struct {
|
||||
|
||||
b.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
|
||||
b.options.minify_whitespace = ctx.bundler_options.minify_whitespace;
|
||||
b.options.minify_internal_exports = ctx.bundler_options.minify_internal_exports;
|
||||
b.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
|
||||
b.resolver.opts.minify_identifiers = ctx.bundler_options.minify_identifiers;
|
||||
b.resolver.opts.minify_whitespace = ctx.bundler_options.minify_whitespace;
|
||||
|
||||
@@ -463,6 +463,9 @@ pub const JSBundler = struct {
|
||||
if (try minify.getBooleanLoose(globalThis, "keepNames")) |keep_names| {
|
||||
this.minify.keep_names = keep_names;
|
||||
}
|
||||
if (try minify.getBooleanLoose(globalThis, "internalExports")) |internal_exports| {
|
||||
this.minify.internal_exports = internal_exports;
|
||||
}
|
||||
} else {
|
||||
return globalThis.throwInvalidArguments("Expected minify to be a boolean or an object", .{});
|
||||
}
|
||||
@@ -743,6 +746,7 @@ pub const JSBundler = struct {
|
||||
identifiers: bool = false,
|
||||
syntax: bool = false,
|
||||
keep_names: bool = false,
|
||||
internal_exports: bool = false,
|
||||
};
|
||||
|
||||
pub const Serve = struct {
|
||||
|
||||
@@ -64,6 +64,7 @@ pub const LinkerContext = struct {
|
||||
minify_whitespace: bool = false,
|
||||
minify_syntax: bool = false,
|
||||
minify_identifiers: bool = false,
|
||||
minify_internal_exports: bool = false,
|
||||
banner: []const u8 = "",
|
||||
footer: []const u8 = "",
|
||||
css_chunking: bool = false,
|
||||
|
||||
@@ -918,6 +918,7 @@ pub const BundleV2 = struct {
|
||||
|
||||
this.linker.options.minify_syntax = transpiler.options.minify_syntax;
|
||||
this.linker.options.minify_identifiers = transpiler.options.minify_identifiers;
|
||||
this.linker.options.minify_internal_exports = transpiler.options.minify_internal_exports;
|
||||
this.linker.options.minify_whitespace = transpiler.options.minify_whitespace;
|
||||
this.linker.options.emit_dce_annotations = transpiler.options.emit_dce_annotations;
|
||||
this.linker.options.ignore_dce_annotations = transpiler.options.ignore_dce_annotations;
|
||||
@@ -1893,6 +1894,7 @@ pub const BundleV2 = struct {
|
||||
transpiler.options.minify_syntax = config.minify.syntax;
|
||||
transpiler.options.minify_whitespace = config.minify.whitespace;
|
||||
transpiler.options.minify_identifiers = config.minify.identifiers;
|
||||
transpiler.options.minify_internal_exports = config.minify.internal_exports;
|
||||
transpiler.options.keep_names = config.minify.keep_names;
|
||||
transpiler.options.inlining = config.minify.syntax;
|
||||
transpiler.options.source_map = config.source_map;
|
||||
|
||||
@@ -19,6 +19,25 @@ pub fn renameSymbolsInChunk(
|
||||
renamer.computeReservedNamesForScope(&all_module_scopes[source_index], &c.graph.symbols, &reserved_names, allocator);
|
||||
}
|
||||
|
||||
// When minify_internal_exports is enabled, we need to preserve export names from entry points
|
||||
// but allow minification of internal exports from non-entry-point files
|
||||
if (c.options.minify_internal_exports and c.options.minify_identifiers and chunk.isEntryPoint()) {
|
||||
const entry_point_source_index = chunk.entry_point.source_index;
|
||||
const resolved_exports = c.graph.meta.items(.resolved_exports)[entry_point_source_index];
|
||||
|
||||
// resolved_exports contains the complete mapping of export names to their final symbols
|
||||
// This includes direct exports, re-exports via "export *", and "export { x } from"
|
||||
var iter = resolved_exports.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
const export_data = entry.value_ptr.*;
|
||||
// Follow the ref to get the actual symbol (handles symbol merging/aliasing)
|
||||
const export_ref = c.graph.symbols.follow(export_data.data.import_ref);
|
||||
if (c.graph.symbols.get(export_ref)) |symbol| {
|
||||
symbol.must_not_be_renamed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sorted_imports_from_other_chunks: std.ArrayList(StableRef) = brk: {
|
||||
var list = std.ArrayList(StableRef).init(allocator);
|
||||
var count: u32 = 0;
|
||||
|
||||
@@ -430,6 +430,7 @@ pub const Command = struct {
|
||||
minify_syntax: bool = false,
|
||||
minify_whitespace: bool = false,
|
||||
minify_identifiers: bool = false,
|
||||
minify_internal_exports: bool = false,
|
||||
keep_names: bool = false,
|
||||
ignore_dce_annotations: bool = false,
|
||||
emit_dce_annotations: bool = true,
|
||||
|
||||
@@ -170,6 +170,7 @@ pub const build_only_params = [_]ParamType{
|
||||
clap.parseParam("--minify-syntax Minify syntax and inline data") catch unreachable,
|
||||
clap.parseParam("--minify-whitespace Minify whitespace") catch unreachable,
|
||||
clap.parseParam("--minify-identifiers Minify identifiers") catch unreachable,
|
||||
clap.parseParam("--minify-internal-exports Minify internal export names that are not re-exported by entry points") catch unreachable,
|
||||
clap.parseParam("--keep-names Preserve original function and class names when minifying") catch unreachable,
|
||||
clap.parseParam("--css-chunking Chunk CSS files together to reduce duplicated CSS loaded in a browser. Only has an effect when multiple entrypoints import CSS") catch unreachable,
|
||||
clap.parseParam("--dump-environment-variables") catch unreachable,
|
||||
@@ -858,6 +859,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
ctx.bundler_options.minify_syntax = minify_flag or args.flag("--minify-syntax");
|
||||
ctx.bundler_options.minify_whitespace = minify_flag or args.flag("--minify-whitespace");
|
||||
ctx.bundler_options.minify_identifiers = minify_flag or args.flag("--minify-identifiers");
|
||||
ctx.bundler_options.minify_internal_exports = args.flag("--minify-internal-exports");
|
||||
ctx.bundler_options.keep_names = args.flag("--keep-names");
|
||||
|
||||
ctx.bundler_options.css_chunking = args.flag("--css-chunking");
|
||||
|
||||
@@ -75,6 +75,7 @@ pub const BuildCommand = struct {
|
||||
this_transpiler.options.minify_syntax = ctx.bundler_options.minify_syntax;
|
||||
this_transpiler.options.minify_whitespace = ctx.bundler_options.minify_whitespace;
|
||||
this_transpiler.options.minify_identifiers = ctx.bundler_options.minify_identifiers;
|
||||
this_transpiler.options.minify_internal_exports = ctx.bundler_options.minify_internal_exports;
|
||||
this_transpiler.options.keep_names = ctx.bundler_options.keep_names;
|
||||
this_transpiler.options.emit_dce_annotations = ctx.bundler_options.emit_dce_annotations;
|
||||
this_transpiler.options.ignore_dce_annotations = ctx.bundler_options.ignore_dce_annotations;
|
||||
|
||||
@@ -1796,6 +1796,7 @@ pub const BundleOptions = struct {
|
||||
minify_whitespace: bool = false,
|
||||
minify_syntax: bool = false,
|
||||
minify_identifiers: bool = false,
|
||||
minify_internal_exports: bool = false,
|
||||
keep_names: bool = false,
|
||||
dead_code_elimination: bool = true,
|
||||
css_chunking: bool,
|
||||
|
||||
@@ -1109,15 +1109,15 @@ describe("bundler", () => {
|
||||
"/entry.js": /* js */ `
|
||||
// Test all equality operators with typeof undefined
|
||||
console.log(typeof x !== 'undefined');
|
||||
console.log(typeof x != 'undefined');
|
||||
console.log(typeof x != 'undefined');
|
||||
console.log('undefined' !== typeof x);
|
||||
console.log('undefined' != typeof x);
|
||||
|
||||
|
||||
console.log(typeof x === 'undefined');
|
||||
console.log(typeof x == 'undefined');
|
||||
console.log('undefined' === typeof x);
|
||||
console.log('undefined' == typeof x);
|
||||
|
||||
|
||||
// These should not be optimized
|
||||
console.log(typeof x === 'string');
|
||||
console.log(x === 'undefined');
|
||||
@@ -1135,4 +1135,270 @@ describe("bundler", () => {
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsBasic", {
|
||||
files: {
|
||||
"/entry.ts": /* ts */ `
|
||||
import * as baz from "./baz";
|
||||
export * from "./bar";
|
||||
export const a = 123;
|
||||
export const b = baz.qux + 1;
|
||||
`,
|
||||
"/bar.ts": /* ts */ `
|
||||
export const barExport1 = "bar1";
|
||||
export const barExport2 = "bar2";
|
||||
`,
|
||||
"/baz.ts": /* ts */ `
|
||||
export const qux = 456;
|
||||
export const internalExport = "internal";
|
||||
`,
|
||||
},
|
||||
minifyIdentifiers: true,
|
||||
minifyInternalExports: true,
|
||||
onAfterBundle(api) {
|
||||
const code = api.readFile("/out.js");
|
||||
// Entry point exports and re-exports should NOT be minified
|
||||
// The export names should appear in the export statement
|
||||
expect(code).toMatch(/export\s*\{[^}]*\ba\b[^}]*\}/);
|
||||
expect(code).toMatch(/export\s*\{[^}]*\bb\b[^}]*\}/);
|
||||
expect(code).toMatch(/export\s*\{[^}]*\bbarExport1\b[^}]*\}/);
|
||||
expect(code).toMatch(/export\s*\{[^}]*\bbarExport2\b[^}]*\}/);
|
||||
|
||||
// The actual variable names should also not be minified for entry point exports
|
||||
expect(code).toContain("var a =");
|
||||
expect(code).toContain("var b =");
|
||||
expect(code).toContain("var barExport1 =");
|
||||
expect(code).toContain("var barExport2 =");
|
||||
|
||||
// Internal exports from baz that are not re-exported should be minified
|
||||
expect(code).not.toContain("qux");
|
||||
expect(code).not.toContain("internalExport");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsWithoutFlag", {
|
||||
files: {
|
||||
"/entry.ts": /* ts */ `
|
||||
import * as baz from "./baz";
|
||||
export * from "./bar";
|
||||
export const a = 123;
|
||||
export const b = baz.qux + 1;
|
||||
`,
|
||||
"/bar.ts": /* ts */ `
|
||||
export const barExport1 = "bar1";
|
||||
export const barExport2 = "bar2";
|
||||
`,
|
||||
"/baz.ts": /* ts */ `
|
||||
export const qux = 456;
|
||||
export const internalExport = "internal";
|
||||
`,
|
||||
},
|
||||
minifyIdentifiers: true,
|
||||
// minifyInternalExports is NOT set - exports can be minified
|
||||
onAfterBundle(api) {
|
||||
const code = api.readFile("/out.js");
|
||||
// Without minifyInternalExports, variable names can be minified
|
||||
// but export names are preserved via aliasing (e.g., "x as barExport1")
|
||||
// Check that the export names still appear (in the export statement)
|
||||
expect(code).toMatch(/export\s*\{[^}]*\bbarExport1\b[^}]*\}/);
|
||||
expect(code).toMatch(/export\s*\{[^}]*\bbarExport2\b[^}]*\}/);
|
||||
|
||||
// Variables from imported modules (bar.ts) should be minified
|
||||
expect(code).not.toContain("var barExport1 =");
|
||||
expect(code).not.toContain("var barExport2 =");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsNamedReexports", {
|
||||
files: {
|
||||
"/entry.ts": /* ts */ `
|
||||
export { namedExport } from "./lib";
|
||||
import { internalHelper } from "./lib";
|
||||
console.log(internalHelper);
|
||||
`,
|
||||
"/lib.ts": /* ts */ `
|
||||
export const namedExport = "public";
|
||||
export const internalHelper = "helper";
|
||||
export const unused = "unused";
|
||||
`,
|
||||
},
|
||||
minifyIdentifiers: true,
|
||||
minifyInternalExports: true,
|
||||
onAfterBundle(api) {
|
||||
const code = api.readFile("/out.js");
|
||||
// namedExport should NOT be minified - check both export statement and variable
|
||||
expect(code).toMatch(/export\s*\{[^}]*\bnamedExport\b[^}]*\}/);
|
||||
expect(code).toContain("var namedExport =");
|
||||
|
||||
// internalHelper and unused should be minified
|
||||
expect(code).not.toContain("internalHelper");
|
||||
expect(code).not.toContain("unused");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsMultipleEntryPoints", {
|
||||
files: {
|
||||
"/entry1.ts": /* ts */ `
|
||||
export { shared1 } from "./shared";
|
||||
import { helper } from "./shared";
|
||||
console.log(helper);
|
||||
`,
|
||||
"/entry2.ts": /* ts */ `
|
||||
export { shared2 } from "./shared";
|
||||
import { helper } from "./shared";
|
||||
console.log(helper);
|
||||
`,
|
||||
"/shared.ts": /* ts */ `
|
||||
export const shared1 = "s1";
|
||||
export const shared2 = "s2";
|
||||
export const helper = "help";
|
||||
export const unused = "u";
|
||||
`,
|
||||
},
|
||||
entryPoints: ["/entry1.ts", "/entry2.ts"],
|
||||
minifyIdentifiers: true,
|
||||
minifyInternalExports: true,
|
||||
onAfterBundle(api) {
|
||||
const code1 = api.readFile("/out/entry1.js");
|
||||
const code2 = api.readFile("/out/entry2.js");
|
||||
|
||||
// Each entry point should preserve its own exports
|
||||
expect(code1).toContain("shared1");
|
||||
expect(code2).toContain("shared2");
|
||||
|
||||
// Helper is used but not exported, should be minified in both
|
||||
expect(code1).not.toContain("helper");
|
||||
expect(code2).not.toContain("helper");
|
||||
|
||||
// Unused should be minified (or removed by tree-shaking)
|
||||
expect(code1).not.toContain("unused");
|
||||
expect(code2).not.toContain("unused");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsCommonJS", {
|
||||
files: {
|
||||
"/entry.js": /* js */ `
|
||||
const lib = require("./lib");
|
||||
module.exports = { publicAPI: lib.publicExport };
|
||||
`,
|
||||
"/lib.js": /* js */ `
|
||||
exports.publicExport = "public";
|
||||
exports.internalExport = "internal";
|
||||
`,
|
||||
},
|
||||
minifyIdentifiers: true,
|
||||
minifyInternalExports: true,
|
||||
format: "cjs",
|
||||
onAfterBundle(api) {
|
||||
const code = api.readFile("/out.js");
|
||||
// With CommonJS, object property names are not minified
|
||||
// (this is standard minifier behavior for dynamic property access)
|
||||
expect(code).toContain("publicAPI");
|
||||
// But variable names should still be minified
|
||||
expect(code).not.toMatch(/var\s+lib\s*=/);
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsMixedESMCJS", {
|
||||
files: {
|
||||
"/entry.ts": /* ts */ `
|
||||
import { esmExport } from "./esm";
|
||||
const cjsModule = require("./cjs");
|
||||
export { esmExport };
|
||||
export const combined = esmExport + cjsModule.value;
|
||||
`,
|
||||
"/esm.ts": /* ts */ `
|
||||
export const esmExport = "esm";
|
||||
export const esmInternal = "internal";
|
||||
`,
|
||||
"/cjs.js": /* js */ `
|
||||
exports.value = 123;
|
||||
exports.internalValue = 456;
|
||||
`,
|
||||
},
|
||||
minifyIdentifiers: true,
|
||||
minifyInternalExports: true,
|
||||
onAfterBundle(api) {
|
||||
const code = api.readFile("/out.js");
|
||||
// Entry point exports should be preserved
|
||||
expect(code).toContain("esmExport");
|
||||
expect(code).toContain("combined");
|
||||
// Internal ESM exports should be minified
|
||||
expect(code).not.toContain("esmInternal");
|
||||
// Note: CommonJS property names are not minified (standard behavior)
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsDefaultExport", {
|
||||
files: {
|
||||
"/entry.ts": /* ts */ `
|
||||
export { default as myDefault } from "./lib";
|
||||
export { namedExport } from "./lib";
|
||||
`,
|
||||
"/lib.ts": /* ts */ `
|
||||
export default function defaultFunc() { return 42; }
|
||||
export const namedExport = "named";
|
||||
export const internalExport = "internal";
|
||||
`,
|
||||
},
|
||||
minifyIdentifiers: true,
|
||||
minifyInternalExports: true,
|
||||
onAfterBundle(api) {
|
||||
const code = api.readFile("/out.js");
|
||||
// Re-exported default and named exports should be preserved
|
||||
expect(code).toContain("myDefault");
|
||||
expect(code).toContain("namedExport");
|
||||
// Internal exports should be minified
|
||||
expect(code).not.toContain("internalExport");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsAPIBackend", {
|
||||
files: {
|
||||
"/entry.ts": /* ts */ `
|
||||
import * as internal from "./internal";
|
||||
export const publicAPI = internal.helper();
|
||||
`,
|
||||
"/internal.ts": /* ts */ `
|
||||
export function helper() { return "help"; }
|
||||
export function unused() { return "unused"; }
|
||||
`,
|
||||
},
|
||||
minifyIdentifiers: true,
|
||||
minifyInternalExports: true,
|
||||
backend: "api",
|
||||
onAfterBundle(api) {
|
||||
const code = api.readFile("/out.js");
|
||||
// Entry point export should be preserved
|
||||
expect(code).toContain("publicAPI");
|
||||
// Internal helper function name should be minified
|
||||
expect(code).not.toContain("helper");
|
||||
expect(code).not.toContain("unused");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("minify/InternalExportsCLIBackend", {
|
||||
files: {
|
||||
"/entry.ts": /* ts */ `
|
||||
import * as internal from "./internal";
|
||||
export const publicAPI = internal.helper();
|
||||
`,
|
||||
"/internal.ts": /* ts */ `
|
||||
export function helper() { return "help"; }
|
||||
export function unused() { return "unused"; }
|
||||
`,
|
||||
},
|
||||
minifyIdentifiers: true,
|
||||
minifyInternalExports: true,
|
||||
backend: "cli",
|
||||
onAfterBundle(api) {
|
||||
const code = api.readFile("/out.js");
|
||||
// Entry point export should be preserved
|
||||
expect(code).toContain("publicAPI");
|
||||
// Internal helper function name should be minified
|
||||
expect(code).not.toContain("helper");
|
||||
expect(code).not.toContain("unused");
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -208,6 +208,7 @@ export interface BundlerTestInput {
|
||||
minifySyntax?: boolean;
|
||||
targetFromAPI?: "TargetWasConfigured";
|
||||
minifyWhitespace?: boolean;
|
||||
minifyInternalExports?: boolean;
|
||||
splitting?: boolean;
|
||||
serverComponents?: boolean;
|
||||
treeShaking?: boolean;
|
||||
@@ -460,6 +461,7 @@ function expectBundled(
|
||||
minifyIdentifiers,
|
||||
minifySyntax,
|
||||
minifyWhitespace,
|
||||
minifyInternalExports,
|
||||
onAfterBundle,
|
||||
outdir,
|
||||
dotenv,
|
||||
@@ -729,6 +731,7 @@ function expectBundled(
|
||||
minifyIdentifiers && `--minify-identifiers`,
|
||||
minifySyntax && `--minify-syntax`,
|
||||
minifyWhitespace && `--minify-whitespace`,
|
||||
minifyInternalExports && `--minify-internal-exports`,
|
||||
drop?.length && drop.map(x => ["--drop=" + x]),
|
||||
globalName && `--global-name=${globalName}`,
|
||||
jsx.runtime && ["--jsx-runtime", jsx.runtime],
|
||||
@@ -769,6 +772,7 @@ function expectBundled(
|
||||
minifyIdentifiers && `--minify-identifiers`,
|
||||
minifySyntax && `--minify-syntax`,
|
||||
minifyWhitespace && `--minify-whitespace`,
|
||||
minifyInternalExports && `--minify-internal-exports`,
|
||||
globalName && `--global-name=${globalName}`,
|
||||
external && external.map(x => `--external:${x}`),
|
||||
packages && ["--packages", packages],
|
||||
@@ -1075,6 +1079,7 @@ function expectBundled(
|
||||
identifiers: minifyIdentifiers,
|
||||
syntax: minifySyntax,
|
||||
keepNames: keepNames,
|
||||
internalExports: minifyInternalExports,
|
||||
},
|
||||
naming: {
|
||||
entry: useOutFile ? path.basename(outfile!) : entryNaming,
|
||||
|
||||
Reference in New Issue
Block a user