bundler: implement globalName support for IIFE format

Fixes bundler test by implementing globalName option for IIFE format.
When globalName is specified with IIFE format, the bundled output is
wrapped in a variable assignment that exposes exports as a global.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2025-08-30 05:37:28 +00:00
parent 27250445ed
commit eecb9fb68d
6 changed files with 119 additions and 34 deletions

View File

@@ -390,7 +390,62 @@ $ bun build ./index.tsx --outdir ./out --format cjs
#### `format: "iife"` - IIFE
TODO: document IIFE once we support globalNames.
Wraps the bundle in an Immediately Invoked Function Expression (IIFE). This format is useful for creating bundles that can be directly included in HTML `<script>` tags without polluting the global namespace.
{% codetabs group="a" %}
```ts#JavaScript
await Bun.build({
entrypoints: ['./index.tsx'],
outdir: './out',
format: "iife",
})
```
```bash#CLI
$ bun build ./index.tsx --outdir ./out --format iife
```
{% /codetabs %}
By default, the IIFE format creates a self-contained bundle:
```js
(() => {
// Your bundled code here
// No global variables are created
})();
```
#### `globalName`
To expose the bundle's exports as a global variable, use the `globalName` option:
{% codetabs group="a" %}
```ts#JavaScript
await Bun.build({
entrypoints: ['./index.tsx'],
outdir: './out',
format: "iife",
globalName: "MyLibrary",
})
```
```bash#CLI
$ bun build ./index.tsx --outdir ./out --format iife --global-name MyLibrary
```
{% /codetabs %}
This creates a bundle that assigns the exports to a global variable:
```js
var MyLibrary = (() => {
// Your bundled code here
return exports; // The module's exports are returned
})();
```
The `globalName` must be a valid JavaScript identifier. This feature is only available when `format` is set to `"iife"`.
### `splitting`

View File

@@ -1835,6 +1835,28 @@ declare module "bun" {
*/
footer?: string;
/**
* Global variable name for IIFE format bundles.
*
* When using `format: "iife"` with a `globalName`, the bundle will be
* wrapped in an IIFE and the exported values will be assigned to a global
* variable with the specified name.
*
* @example
* ```ts
* await Bun.build({
* entrypoints: ['./src/library.ts'],
* format: 'iife',
* globalName: 'MyLibrary',
* outfile: './dist/library.js'
* });
* ```
*
* The `globalName` must be a valid JavaScript identifier.
* This option is only meaningful when `format` is set to `"iife"`.
*/
globalName?: string;
/**
* Drop function calls to matching property accesses.
*/

View File

@@ -1792,7 +1792,7 @@ pub const BundleV2 = struct {
transpiler.options.css_chunking = config.css_chunking;
transpiler.options.banner = config.banner.slice();
transpiler.options.footer = config.footer.slice();
transpiler.options.global_name = config.global_name.slice();
transpiler.options.global_name = if (config.format == .iife) config.global_name.slice() else "";
transpiler.configureLinker();
try transpiler.configureDefines();

View File

@@ -194,12 +194,21 @@ pub fn postProcessJSChunk(ctx: GenerateChunkCtx, worker: *ThreadPool.Worker, chu
.iife => {
// Bun does not do arrow function lowering. So the wrapper can be an arrow.
if (c.options.global_name.len > 0) {
const global_name_prefix = if (c.options.minify_whitespace)
std.fmt.allocPrint(worker.allocator, "var {s}=(()=>{{", .{c.options.global_name}) catch bun.outOfMemory()
else
std.fmt.allocPrint(worker.allocator, "var {s} = (() => {{\n", .{c.options.global_name}) catch bun.outOfMemory();
j.push(global_name_prefix, worker.allocator);
line_offset.advance(global_name_prefix);
if (c.options.minify_whitespace) {
j.pushStatic("var ");
j.pushStatic(c.options.global_name);
j.pushStatic("=(()=>{");
line_offset.advance("var ");
line_offset.advance(c.options.global_name);
line_offset.advance("=(()=>{");
} else {
j.pushStatic("var ");
j.pushStatic(c.options.global_name);
j.pushStatic(" = (() => {\n");
line_offset.advance("var ");
line_offset.advance(c.options.global_name);
line_offset.advance(" = (() => {\n");
}
} else {
const start = if (c.options.minify_whitespace) "(()=>{" else "(() => {\n";
j.pushStatic(start);
@@ -346,21 +355,14 @@ pub fn postProcessJSChunk(ctx: GenerateChunkCtx, worker: *ThreadPool.Worker, chu
switch (output_format) {
.iife => {
if (c.options.global_name.len > 0) {
const without_newline = "})();";
const with_newline = if (newline_before_comment)
without_newline ++ "\n"
else
without_newline;
j.pushStatic(with_newline);
} else {
const without_newline = "})();";
const with_newline = if (newline_before_comment)
without_newline ++ "\n"
else
without_newline;
j.pushStatic(with_newline);
}
const without_newline = "})();";
const with_newline = if (newline_before_comment)
without_newline ++ "\n"
else
without_newline;
j.pushStatic(with_newline);
},
.internal_bake_dev => {
{
@@ -770,12 +772,13 @@ pub fn generateEntryPointTailJS(
.cjs => {
// "return require_foo();"
stmts.append(
Stmt.alloc(
Stmt.allocate(
allocator,
S.Return,
.{
S.Return{
.value = Expr.init(
E.Call,
.{
E.Call{
.target = Expr.initIdentifier(ast.wrapper_ref, Logger.Loc.Empty),
},
Logger.Loc.Empty,
@@ -789,12 +792,13 @@ pub fn generateEntryPointTailJS(
// "init_foo(); return exports_entry;"
if (ast.wrapper_ref.isValid()) {
stmts.append(
Stmt.alloc(
Stmt.allocate(
allocator,
S.SExpr,
.{
S.SExpr{
.value = Expr.init(
E.Call,
.{
E.Call{
.target = Expr.initIdentifier(ast.wrapper_ref, Logger.Loc.Empty),
},
Logger.Loc.Empty,
@@ -808,9 +812,10 @@ pub fn generateEntryPointTailJS(
// Return the exports object if it has exports
if (ast.exports_ref.isValid()) {
stmts.append(
Stmt.alloc(
Stmt.allocate(
allocator,
S.Return,
.{
S.Return{
.value = Expr.initIdentifier(ast.exports_ref, Logger.Loc.Empty),
},
Logger.Loc.Empty,
@@ -822,9 +827,10 @@ pub fn generateEntryPointTailJS(
// For other cases, try to return the exports object if available
if (ast.exports_ref.isValid()) {
stmts.append(
Stmt.alloc(
Stmt.allocate(
allocator,
S.Return,
.{
S.Return{
.value = Expr.initIdentifier(ast.exports_ref, Logger.Loc.Empty),
},
Logger.Loc.Empty,

View File

@@ -165,7 +165,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("--global-name <STR> Global variable name for IIFE bundles") catch unreachable,
clap.parseParam("--global-name <STR> Global variable name for IIFE bundles (IIFE only; must be a valid JS identifier)") 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,
clap.parseParam("--conditions <STR>... Pass custom conditions to resolve") catch unreachable,

2
test_entry.js Normal file
View File

@@ -0,0 +1,2 @@
export const foo = 123;
export const bar = "hello";