mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 13:51:47 +00:00
Compare commits
13 Commits
claude/fix
...
claude/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d4fcd0164 | ||
|
|
b1128d1d19 | ||
|
|
16f38848bf | ||
|
|
3418180e8e | ||
|
|
c6849740a7 | ||
|
|
c9c8c9abfd | ||
|
|
53a6d40c4b | ||
|
|
3b75ea5790 | ||
|
|
76d3175da6 | ||
|
|
dd439a925b | ||
|
|
eecb9fb68d | ||
|
|
27250445ed | ||
|
|
f2a4f2527c |
@@ -390,7 +390,64 @@ $ 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`
|
||||
|
||||
|
||||
22
packages/bun-types/bun.d.ts
vendored
22
packages/bun-types/bun.d.ts
vendored
@@ -1873,6 +1873,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.
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,7 @@ pub const JSBundler = struct {
|
||||
bytecode: bool = false,
|
||||
banner: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
footer: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
global_name: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
css_chunking: bool = false,
|
||||
drop: bun.StringSet = bun.StringSet.init(bun.default_allocator),
|
||||
has_any_on_before_parse: bool = false,
|
||||
@@ -331,6 +332,14 @@ pub const JSBundler = struct {
|
||||
try this.footer.appendSliceExact(slice.slice());
|
||||
}
|
||||
|
||||
if (try config.getOptional(globalThis, "globalName", ZigString.Slice)) |slice| {
|
||||
defer slice.deinit();
|
||||
try this.global_name.appendSliceExact(slice.slice());
|
||||
} else if (try config.getOptional(globalThis, "global_name", ZigString.Slice)) |slice| {
|
||||
defer slice.deinit();
|
||||
try this.global_name.appendSliceExact(slice.slice());
|
||||
}
|
||||
|
||||
if (try config.getTruthy(globalThis, "sourcemap")) |source_map_js| {
|
||||
if (source_map_js.isBoolean()) {
|
||||
if (source_map_js == .true) {
|
||||
@@ -725,6 +734,7 @@ pub const JSBundler = struct {
|
||||
self.conditions.deinit();
|
||||
self.drop.deinit();
|
||||
self.banner.deinit();
|
||||
self.global_name.deinit();
|
||||
if (self.compile) |*compile| {
|
||||
compile.deinit();
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ pub const LinkerContext = struct {
|
||||
pub const LinkerOptions = struct {
|
||||
generate_bytecode_cache: bool = false,
|
||||
output_format: options.Format = .esm,
|
||||
global_name: []const u8 = "",
|
||||
ignore_dce_annotations: bool = false,
|
||||
emit_dce_annotations: bool = true,
|
||||
tree_shaking: bool = true,
|
||||
|
||||
@@ -916,6 +916,7 @@ pub const BundleV2 = struct {
|
||||
this.linker.options.ignore_dce_annotations = transpiler.options.ignore_dce_annotations;
|
||||
this.linker.options.banner = transpiler.options.banner;
|
||||
this.linker.options.footer = transpiler.options.footer;
|
||||
this.linker.options.global_name = transpiler.options.global_name;
|
||||
this.linker.options.css_chunking = transpiler.options.css_chunking;
|
||||
this.linker.options.source_maps = transpiler.options.source_map;
|
||||
this.linker.options.tree_shaking = transpiler.options.tree_shaking;
|
||||
@@ -1866,6 +1867,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 = if (config.format == .iife) config.global_name.slice() else "";
|
||||
|
||||
if (transpiler.options.compile) {
|
||||
// Emitting DCE annotations is nonsensical in --compile.
|
||||
|
||||
@@ -193,9 +193,137 @@ pub fn postProcessJSChunk(ctx: GenerateChunkCtx, worker: *ThreadPool.Worker, chu
|
||||
},
|
||||
.iife => {
|
||||
// Bun does not do arrow function lowering. So the wrapper can be an arrow.
|
||||
const start = if (c.options.minify_whitespace) "(()=>{" else "(() => {\n";
|
||||
j.pushStatic(start);
|
||||
line_offset.advance(start);
|
||||
if (c.options.global_name.len > 0) {
|
||||
// Parse the global name and generate the proper prefix like esbuild
|
||||
const space = if (c.options.minify_whitespace) "" else " ";
|
||||
const join = if (c.options.minify_whitespace) ";" else ";\n";
|
||||
|
||||
// Find the first dot to split the global name
|
||||
const first_dot = std.mem.indexOfScalar(u8, c.options.global_name, '.');
|
||||
|
||||
if (first_dot) |dot_index| {
|
||||
// Has dot expression: e.g., "window.MyLib" or "globalThis.my.lib"
|
||||
const first_part = c.options.global_name[0..dot_index];
|
||||
const rest = c.options.global_name[dot_index + 1 ..];
|
||||
|
||||
// Generate: var window;
|
||||
j.pushStatic("var ");
|
||||
j.pushStatic(first_part);
|
||||
j.pushStatic(join);
|
||||
line_offset.advance("var ");
|
||||
line_offset.advance(first_part);
|
||||
line_offset.advance(join);
|
||||
|
||||
// For simplicity, handle only 2-part names properly for now
|
||||
// e.g., "window.MyLib" -> "(window ||= {}).MyLib = "
|
||||
const second_dot = std.mem.indexOfScalar(u8, rest, '.');
|
||||
if (second_dot == null) {
|
||||
// Simple 2-part case: window.MyLib
|
||||
j.pushStatic("(");
|
||||
j.pushStatic(first_part);
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("||=");
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("{}).");
|
||||
j.pushStatic(rest);
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("=");
|
||||
j.pushStatic(space);
|
||||
|
||||
line_offset.advance("(");
|
||||
line_offset.advance(first_part);
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("||=");
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("{}).");
|
||||
line_offset.advance(rest);
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("=");
|
||||
line_offset.advance(space);
|
||||
} else {
|
||||
// Multi-part case (3+ parts) - generate nested nullish coalescing
|
||||
// Example: "globalThis.my.lib" -> "((globalThis ||= {}).my ||= {}).lib = "
|
||||
|
||||
// Split rest into individual parts
|
||||
var iter = std.mem.tokenizeScalar(u8, rest, '.');
|
||||
var rest_parts = std.ArrayList([]const u8).init(worker.allocator);
|
||||
defer rest_parts.deinit();
|
||||
|
||||
while (iter.next()) |part| {
|
||||
rest_parts.append(part) catch {};
|
||||
}
|
||||
|
||||
// Total parts = 1 (first_part) + rest_parts.len
|
||||
// We need (total_parts - 1) opening parens = rest_parts.len opening parens
|
||||
var i: usize = 0;
|
||||
while (i < rest_parts.items.len) : (i += 1) {
|
||||
j.pushStatic("(");
|
||||
line_offset.advance("(");
|
||||
}
|
||||
|
||||
// Start with first part
|
||||
j.pushStatic(first_part);
|
||||
line_offset.advance(first_part);
|
||||
|
||||
// Process all parts except the last
|
||||
for (rest_parts.items[0 .. rest_parts.items.len - 1]) |part| {
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("||=");
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("{}).");
|
||||
j.pushStatic(part);
|
||||
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("||=");
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("{}).");
|
||||
line_offset.advance(part);
|
||||
}
|
||||
|
||||
// Last part
|
||||
const last_part = rest_parts.items[rest_parts.items.len - 1];
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("||=");
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("{}).");
|
||||
j.pushStatic(last_part);
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("=");
|
||||
j.pushStatic(space);
|
||||
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("||=");
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("{}).");
|
||||
line_offset.advance(last_part);
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("=");
|
||||
line_offset.advance(space);
|
||||
}
|
||||
|
||||
j.pushStatic(if (c.options.minify_whitespace) "(()=>{" else "(() => {\n");
|
||||
line_offset.advance(if (c.options.minify_whitespace) "(()=>{" else "(() => {\n");
|
||||
} else {
|
||||
// Simple identifier: e.g., "MyLib"
|
||||
j.pushStatic("var ");
|
||||
j.pushStatic(c.options.global_name);
|
||||
j.pushStatic(space);
|
||||
j.pushStatic("=");
|
||||
j.pushStatic(space);
|
||||
j.pushStatic(if (c.options.minify_whitespace) "(()=>{" else "(() => {\n");
|
||||
|
||||
line_offset.advance("var ");
|
||||
line_offset.advance(c.options.global_name);
|
||||
line_offset.advance(space);
|
||||
line_offset.advance("=");
|
||||
line_offset.advance(space);
|
||||
line_offset.advance(if (c.options.minify_whitespace) "(()=>{" else "(() => {\n");
|
||||
}
|
||||
} else {
|
||||
const start = if (c.options.minify_whitespace) "(()=>{" else "(() => {\n";
|
||||
j.pushStatic(start);
|
||||
line_offset.advance(start);
|
||||
}
|
||||
},
|
||||
else => {}, // no wrapper
|
||||
}
|
||||
@@ -747,8 +875,82 @@ pub fn generateEntryPointTailJS(
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: iife
|
||||
.iife => {},
|
||||
.iife => {
|
||||
// When globalName is specified, we need to return the exports object
|
||||
if (c.options.global_name.len > 0) {
|
||||
switch (flags.wrap) {
|
||||
.cjs => {
|
||||
// "return require_foo();"
|
||||
stmts.append(
|
||||
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,
|
||||
),
|
||||
},
|
||||
Logger.Loc.Empty,
|
||||
),
|
||||
) catch unreachable;
|
||||
},
|
||||
.esm => {
|
||||
// "init_foo(); return exports_entry;"
|
||||
if (ast.wrapper_ref.isValid()) {
|
||||
stmts.append(
|
||||
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,
|
||||
),
|
||||
},
|
||||
Logger.Loc.Empty,
|
||||
),
|
||||
) catch unreachable;
|
||||
}
|
||||
|
||||
// Return the exports object if it has exports
|
||||
if (ast.exports_ref.isValid()) {
|
||||
stmts.append(
|
||||
Stmt.allocate(
|
||||
allocator,
|
||||
S.Return,
|
||||
S.Return{
|
||||
.value = Expr.initIdentifier(ast.exports_ref, Logger.Loc.Empty),
|
||||
},
|
||||
Logger.Loc.Empty,
|
||||
),
|
||||
) catch unreachable;
|
||||
}
|
||||
},
|
||||
else => {
|
||||
// For other cases, try to return the exports object if available
|
||||
if (ast.exports_ref.isValid()) {
|
||||
stmts.append(
|
||||
Stmt.allocate(
|
||||
allocator,
|
||||
S.Return,
|
||||
S.Return{
|
||||
.value = Expr.initIdentifier(ast.exports_ref, Logger.Loc.Empty),
|
||||
},
|
||||
Logger.Loc.Empty,
|
||||
),
|
||||
) catch unreachable;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.internal_bake_dev => {
|
||||
// nothing needs to be done here, as the exports are already
|
||||
|
||||
@@ -421,6 +421,7 @@ pub const Command = struct {
|
||||
ignore_dce_annotations: bool = false,
|
||||
emit_dce_annotations: bool = true,
|
||||
output_format: options.Format = .esm,
|
||||
global_name: []const u8 = "",
|
||||
bytecode: bool = false,
|
||||
banner: []const u8 = "",
|
||||
footer: []const u8 = "",
|
||||
|
||||
@@ -167,6 +167,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 (IIFE only; must be a valid JS identifier)") 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,
|
||||
@@ -1028,6 +1029,29 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
}
|
||||
}
|
||||
|
||||
if (args.option("--global-name")) |global_name| {
|
||||
// --global-name is only valid with --format=iife
|
||||
if (ctx.bundler_options.output_format != .iife) {
|
||||
Output.errGeneric("--global-name can only be used with --format=iife", .{});
|
||||
Global.exit(1);
|
||||
}
|
||||
|
||||
// Validate that the provided name is a valid JavaScript identifier or dot expression
|
||||
const global_name_parser = @import("../js_parser/global_name.zig");
|
||||
const parsed = global_name_parser.parseGlobalName(ctx.allocator, global_name) catch {
|
||||
Output.errGeneric("--global-name must be a valid JavaScript identifier or dot expression, got: {s}", .{global_name});
|
||||
Global.exit(1);
|
||||
};
|
||||
|
||||
if (parsed == null) {
|
||||
Output.errGeneric("--global-name must be a valid JavaScript identifier or dot expression, got: {s}", .{global_name});
|
||||
Global.exit(1);
|
||||
}
|
||||
|
||||
// We can store the original string and parse it later when needed
|
||||
ctx.bundler_options.global_name = global_name;
|
||||
}
|
||||
|
||||
if (args.flag("--splitting")) {
|
||||
ctx.bundler_options.code_splitting = true;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ pub const BuildCommand = struct {
|
||||
|
||||
this_transpiler.options.banner = ctx.bundler_options.banner;
|
||||
this_transpiler.options.footer = ctx.bundler_options.footer;
|
||||
this_transpiler.options.global_name = ctx.bundler_options.global_name;
|
||||
this_transpiler.options.drop = ctx.args.drop;
|
||||
|
||||
this_transpiler.options.css_chunking = ctx.bundler_options.css_chunking;
|
||||
|
||||
163
src/js_parser/global_name.zig
Normal file
163
src/js_parser/global_name.zig
Normal file
@@ -0,0 +1,163 @@
|
||||
pub const ParsedGlobalName = struct {
|
||||
/// List of identifiers in the global name path
|
||||
/// e.g., "window.MyLib.v1" -> ["window", "MyLib", "v1"]
|
||||
parts: []const []const u8,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn deinit(self: *ParsedGlobalName) void {
|
||||
for (self.parts) |part| {
|
||||
self.allocator.free(part);
|
||||
}
|
||||
self.allocator.free(self.parts);
|
||||
}
|
||||
|
||||
/// Generate the variable declaration part
|
||||
/// e.g., "window.MyLib.v1" -> "var window;"
|
||||
pub fn generateVarDeclaration(self: ParsedGlobalName, writer: anytype, minify: bool) !void {
|
||||
if (self.parts.len > 0) {
|
||||
try writer.writeAll("var ");
|
||||
try writer.writeAll(self.parts[0]);
|
||||
try writer.writeAll(if (minify) ";" else ";\n");
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the assignment expression
|
||||
/// e.g., "window.MyLib.v1" -> "(((window ||= {}).MyLib ||= {}).v1 = "
|
||||
pub fn generateAssignment(self: ParsedGlobalName, writer: anytype, minify: bool) !void {
|
||||
if (self.parts.len == 0) return;
|
||||
|
||||
if (self.parts.len == 1) {
|
||||
// Simple case: just "globalName"
|
||||
try writer.writeAll(self.parts[0]);
|
||||
if (minify) {
|
||||
try writer.writeAll("=");
|
||||
} else {
|
||||
try writer.writeAll(" = ");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Complex case: "a.b.c" -> "(((a ||= {}).b ||= {}).c = "
|
||||
for (self.parts, 0..) |part, i| {
|
||||
if (i < self.parts.len - 1) {
|
||||
if (i == 0) {
|
||||
try writer.writeAll("(");
|
||||
try writer.writeAll(part);
|
||||
if (minify) {
|
||||
try writer.writeAll("||={})");
|
||||
} else {
|
||||
try writer.writeAll(" ||= {})");
|
||||
}
|
||||
} else {
|
||||
try writer.writeAll(".");
|
||||
try writer.writeAll(part);
|
||||
if (minify) {
|
||||
try writer.writeAll("||={})");
|
||||
} else {
|
||||
try writer.writeAll(" ||= {})");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Last part
|
||||
try writer.writeAll(".");
|
||||
try writer.writeAll(part);
|
||||
if (minify) {
|
||||
try writer.writeAll("=");
|
||||
} else {
|
||||
try writer.writeAll(" = ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Parse a global name that may contain dot expressions
|
||||
/// e.g., "myLib", "window.myLib", "globalThis.my.lib"
|
||||
/// Returns null if the global name is invalid
|
||||
pub fn parseGlobalName(allocator: std.mem.Allocator, text: []const u8) !?ParsedGlobalName {
|
||||
if (text.len == 0) return null;
|
||||
|
||||
var parts = std.ArrayList([]const u8).init(allocator);
|
||||
defer parts.deinit();
|
||||
|
||||
var iter = std.mem.tokenizeScalar(u8, text, '.');
|
||||
|
||||
while (iter.next()) |part| {
|
||||
// Each part must be a valid identifier
|
||||
if (!js_lexer.isIdentifier(part)) {
|
||||
// Clean up allocated parts
|
||||
for (parts.items) |p| {
|
||||
allocator.free(p);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const part_copy = try allocator.dupe(u8, part);
|
||||
try parts.append(part_copy);
|
||||
}
|
||||
|
||||
if (parts.items.len == 0) return null;
|
||||
|
||||
return ParsedGlobalName{
|
||||
.parts = try parts.toOwnedSlice(),
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
test "parseGlobalName" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
// Simple identifier
|
||||
{
|
||||
var parsed = try parseGlobalName(allocator, "myLib");
|
||||
defer if (parsed) |*p| p.deinit();
|
||||
try std.testing.expect(parsed != null);
|
||||
try std.testing.expectEqual(@as(usize, 1), parsed.?.parts.len);
|
||||
try std.testing.expectEqualStrings("myLib", parsed.?.parts[0]);
|
||||
}
|
||||
|
||||
// Dot expression
|
||||
{
|
||||
var parsed = try parseGlobalName(allocator, "window.myLib");
|
||||
defer if (parsed) |*p| p.deinit();
|
||||
try std.testing.expect(parsed != null);
|
||||
try std.testing.expectEqual(@as(usize, 2), parsed.?.parts.len);
|
||||
try std.testing.expectEqualStrings("window", parsed.?.parts[0]);
|
||||
try std.testing.expectEqualStrings("myLib", parsed.?.parts[1]);
|
||||
}
|
||||
|
||||
// Nested dot expression
|
||||
{
|
||||
var parsed = try parseGlobalName(allocator, "globalThis.my.lib.v1");
|
||||
defer if (parsed) |*p| p.deinit();
|
||||
try std.testing.expect(parsed != null);
|
||||
try std.testing.expectEqual(@as(usize, 4), parsed.?.parts.len);
|
||||
try std.testing.expectEqualStrings("globalThis", parsed.?.parts[0]);
|
||||
try std.testing.expectEqualStrings("my", parsed.?.parts[1]);
|
||||
try std.testing.expectEqualStrings("lib", parsed.?.parts[2]);
|
||||
try std.testing.expectEqualStrings("v1", parsed.?.parts[3]);
|
||||
}
|
||||
|
||||
// Invalid: starts with number
|
||||
{
|
||||
const parsed = try parseGlobalName(allocator, "123invalid");
|
||||
try std.testing.expect(parsed == null);
|
||||
}
|
||||
|
||||
// Invalid: contains invalid identifier
|
||||
{
|
||||
const parsed = try parseGlobalName(allocator, "window.123");
|
||||
try std.testing.expect(parsed == null);
|
||||
}
|
||||
|
||||
// Invalid: empty parts
|
||||
{
|
||||
const parsed = try parseGlobalName(allocator, "window..lib");
|
||||
try std.testing.expect(parsed == null);
|
||||
}
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("../bun.zig");
|
||||
const js_lexer = bun.js_lexer;
|
||||
@@ -1708,6 +1708,7 @@ pub const PackagesOption = enum {
|
||||
pub const BundleOptions = struct {
|
||||
footer: string = "",
|
||||
banner: string = "",
|
||||
global_name: string = "",
|
||||
define: *defines.Define,
|
||||
drop: []const []const u8 = &.{},
|
||||
loaders: Loader.HashTable,
|
||||
@@ -2112,6 +2113,7 @@ pub fn openOutputDir(output_dir: string) !std.fs.Dir {
|
||||
pub const TransformOptions = struct {
|
||||
footer: string = "",
|
||||
banner: string = "",
|
||||
global_name: string = "",
|
||||
define: bun.StringHashMap(string),
|
||||
loader: Loader = Loader.js,
|
||||
resolve_dir: string = "/",
|
||||
|
||||
@@ -1063,7 +1063,6 @@ describe("bundler", () => {
|
||||
},
|
||||
});
|
||||
itBundled("importstar/ReExportStarExternalIIFE", {
|
||||
todo: true,
|
||||
files: {
|
||||
"/entry.js": `export * from "foo"`,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user