Skip to content

Commit

Permalink
Add packages option to remove all dependencies from bundle (#12562)
Browse files Browse the repository at this point in the history
  • Loading branch information
zpix1 authored Jul 14, 2024
1 parent ae19489 commit 20235a0
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 2 deletions.
1 change: 1 addition & 0 deletions completions/bun.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ _bun_run_completion() {
'--external[Exclude module from transpilation (can use * wildcards). ex: -e react]:external' \
'-e[Exclude module from transpilation (can use * wildcards). ex: -e react]:external' \
'--loader[Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: js, jsx, ts, tsx, json, toml, text, file, wasm, napi]:loader' \
'--packages[Exclude dependencies from bundle, e.g. --packages external. Valid options: bundle, external]:packages' \
'-l[Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: js, jsx, ts, tsx, json, toml, text, file, wasm, napi]:loader' \
'--origin[Rewrite import URLs to start with --origin. Default: ""]:origin' \
'-u[Rewrite import URLs to start with --origin. Default: ""]:origin' \
Expand Down
19 changes: 19 additions & 0 deletions docs/bundler/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,25 @@ $ bun build ./index.tsx --outdir ./out --external '*'

{% /codetabs %}

### `packages`

Control whatever package dependencies are included to bundle or not. Possible values: `bundle` (default), `external`. Bun threats any import which path do not start with `.`, `..` or `/` as package.

{% codetabs group="a" %}

```ts#JavaScript
await Bun.build({
entrypoints: ['./index.ts'],
packages: 'external',
})
```

```bash#CLI
$ bun build ./index.ts --packages external
```

{% /codetabs %}

### `naming`

Customizes the generated file names. Defaults to `./[dir]/[name].[ext]`.
Expand Down
4 changes: 2 additions & 2 deletions docs/bundler/vs-esbuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot
---

- `--packages`
- n/a
- Not supported
- `--packages`
- No differences

---

Expand Down
1 change: 1 addition & 0 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,7 @@ declare module "bun" {
plugins?: BunPlugin[];
// manifest?: boolean; // whether to return manifest
external?: string[];
packages?: "bundle" | "external";
publicPath?: string;
define?: Record<string, string>;
// origin?: string; // e.g. http://mydomain.com
Expand Down
25 changes: 25 additions & 0 deletions src/api/schema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1684,6 +1684,9 @@ pub const Api = struct {
/// conditions
conditions: []const []const u8,

/// packages
packages: ?PackagesMode = null,

pub fn decode(reader: anytype) anyerror!TransformOptions {
var this = std.mem.zeroes(TransformOptions);

Expand Down Expand Up @@ -1771,6 +1774,9 @@ pub const Api = struct {
26 => {
this.conditions = try reader.readArray([]const u8);
},
27 => {
this.packages = try reader.readValue(PackagesMode);
},
else => {
return error.InvalidMessage;
},
Expand Down Expand Up @@ -1886,6 +1892,11 @@ pub const Api = struct {
try writer.writeArray([]const u8, conditions);
}

if (this.packages) |packages| {
try writer.writeFieldID(27);
try writer.writeValue([]const u8, packages);
}

try writer.endMessage();
}
};
Expand All @@ -1908,6 +1919,20 @@ pub const Api = struct {
}
};

pub const PackagesMode = enum(u8) {
/// bundle
bundle,

/// external
external,

_,

pub fn jsonStringify(self: @This(), writer: anytype) !void {
return try writer.write(@tagName(self));
}
};

pub const FileHandle = struct {
/// path
path: []const u8,
Expand Down
5 changes: 5 additions & 0 deletions src/bun.js/api/JSBundler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub const JSBundler = struct {
source_map: options.SourceMapOption = .none,
public_path: OwnedString = OwnedString.initEmpty(bun.default_allocator),
conditions: bun.StringSet = bun.StringSet.init(bun.default_allocator),
packages: options.PackagesOption = .bundle,

pub const List = bun.StringArrayHashMapUnmanaged(Config);

Expand Down Expand Up @@ -223,6 +224,10 @@ pub const JSBundler = struct {
}
}

if (try config.getOptionalEnum(globalThis, "packages", options.PackagesOption)) |packages| {
this.packages = packages;
}

if (try config.getOptionalEnum(globalThis, "format", options.Format)) |format| {
switch (format) {
.esm => {},
Expand Down
4 changes: 4 additions & 0 deletions src/bun.js/api/JSTranspiler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,10 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
}
}

if (try object.getOptionalEnum(globalThis, "packages", options.PackagesOption)) |packages| {
transpiler.transform.packages = packages.toAPI();
}

var tree_shaking: ?bool = null;
if (object.getOptional(globalThis, "treeShaking", bool) catch return transpiler) |treeShaking| {
tree_shaking = treeShaking;
Expand Down
1 change: 1 addition & 0 deletions src/bundler/bundle_v2.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,7 @@ pub const BundleV2 = struct {
bundler.options.minify_identifiers = config.minify.identifiers;
bundler.options.inlining = config.minify.syntax;
bundler.options.source_map = config.source_map;
bundler.options.packages = config.packages;
bundler.resolver.generation = generation;
bundler.options.code_splitting = config.code_splitting;

Expand Down
12 changes: 12 additions & 0 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ pub const Arguments = struct {
clap.parseParam("--splitting Enable code splitting") catch unreachable,
clap.parseParam("--public-path <STR> A prefix to be appended to any import paths in bundled code") catch unreachable,
clap.parseParam("-e, --external <STR>... Exclude module from transpilation (can use * wildcards). ex: -e react") catch unreachable,
clap.parseParam("--packages <STR> Add dependencies to bundle or keep them external. \"external\", \"bundle\" is supported. Defaults to \"bundle\".") catch unreachable,
clap.parseParam("--entry-naming <STR> Customize entry point filenames. Defaults to \"[dir]/[name].[ext]\"") catch unreachable,
clap.parseParam("--chunk-naming <STR> Customize chunk filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable,
clap.parseParam("--asset-naming <STR> Customize asset filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable,
Expand Down Expand Up @@ -696,6 +697,17 @@ pub const Arguments = struct {
opts.external = externals;
}

if (args.option("--packages")) |packages| {
if (strings.eqlComptime(packages, "bundle")) {
opts.packages = .bundle;
} else if (strings.eqlComptime(packages, "external")) {
opts.packages = .external;
} else {
Output.prettyErrorln("<r><red>error<r>: Invalid packages setting: \"{s}\"", .{packages});
Global.crash();
}
}

const TargetMatcher = strings.ExactSizeMatcher(8);
if (args.option("--target")) |_target| brk: {
if (comptime cmd == .BuildCommand) {
Expand Down
28 changes: 28 additions & 0 deletions src/options.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,31 @@ pub const SourceMapOption = enum {
});
};

pub const PackagesOption = enum {
bundle,
external,

pub fn fromApi(packages: ?Api.PackagesMode) PackagesOption {
return switch (packages orelse .bundle) {
.external => .external,
.bundle => .bundle,
else => .bundle,
};
}

pub fn toAPI(packages: ?PackagesOption) Api.PackagesMode {
return switch (packages orelse .bundle) {
.external => .external,
.bundle => .bundle,
};
}

pub const Map = bun.ComptimeStringMap(PackagesOption, .{
.{ "external", .external },
.{ "bundle", .bundle },
});
};

pub const OutputFormat = enum {
preserve,

Expand Down Expand Up @@ -1475,6 +1500,7 @@ pub const BundleOptions = struct {
tree_shaking: bool = false,
code_splitting: bool = false,
source_map: SourceMapOption = SourceMapOption.none,
packages: PackagesOption = PackagesOption.bundle,

disable_transpilation: bool = false,

Expand Down Expand Up @@ -1737,6 +1763,8 @@ pub const BundleOptions = struct {

opts.source_map = SourceMapOption.fromApi(transform.source_map orelse .none);

opts.packages = PackagesOption.fromApi(transform.packages orelse .bundle);

opts.tree_shaking = opts.target.isBun() or opts.production;
opts.inlining = opts.tree_shaking;
if (opts.inlining)
Expand Down
10 changes: 10 additions & 0 deletions src/resolver/resolver.zig
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,13 @@ pub fn ResolveWatcher(comptime Context: type, comptime onWatch: anytype) type {
};
}

fn isExternalModuleLike(import_path: string) bool {
if (strings.startsWith(import_path, ".") or strings.startsWith(import_path, "/") or strings.startsWith(import_path, "..")) {
return false;
}
return true;
}

pub const Resolver = struct {
const ThisResolver = @This();
opts: options.BundleOptions,
Expand Down Expand Up @@ -625,6 +632,9 @@ pub const Resolver = struct {
}

pub fn isExternalPattern(r: *ThisResolver, import_path: string) bool {
if (r.opts.packages == .external and isExternalModuleLike(import_path)) {
return true;
}
for (r.opts.external.patterns) |pattern| {
if (import_path.len >= pattern.prefix.len + pattern.suffix.len and (strings.startsWith(
import_path,
Expand Down
25 changes: 25 additions & 0 deletions test/bundler/bundler_edgecase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,31 @@ describe("bundler", () => {
target: "bun",
run: true,
});
itBundled("edgecase/PackageExternalDoNotBundleNodeModules", {
files: {
"/entry.ts": /* ts */ `
import { a } from "foo";
console.log(a);
`,
},
packages: "external",
target: "bun",
runtimeFiles: {
"/node_modules/foo/index.js": `export const a = "Hello World";`,
"/node_modules/foo/package.json": /* json */ `
{
"name": "foo",
"version": "2.0.0",
"main": "index.js"
}
`,
},
run: {
stdout: `
Hello World
`,
},
});

// TODO(@paperdave): test every case of this. I had already tested it manually, but it may break later
const requireTranspilationListESM = [
Expand Down
6 changes: 6 additions & 0 deletions test/bundler/expectBundled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ export interface BundlerTestInput {
extensionOrder?: string[];
/** Replaces "{{root}}" with the file root */
external?: string[];
/** Defaults to "bundle" */
packages?: "bundle" | "external";
/** Defaults to "esm" */
format?: "esm" | "cjs" | "iife";
globalName?: string;
Expand Down Expand Up @@ -400,6 +402,7 @@ function expectBundled(
entryPointsRaw,
env,
external,
packages,
files,
format,
globalName,
Expand Down Expand Up @@ -621,6 +624,7 @@ function expectBundled(
`--target=${target}`,
// `--format=${format}`,
external && external.map(x => ["--external", x]),
packages && ["--packages", packages],
conditions && conditions.map(x => ["--conditions", x]),
minifyIdentifiers && `--minify-identifiers`,
minifySyntax && `--minify-syntax`,
Expand Down Expand Up @@ -658,6 +662,7 @@ function expectBundled(
minifyWhitespace && `--minify-whitespace`,
globalName && `--global-name=${globalName}`,
external && external.map(x => `--external:${x}`),
packages && ["--packages", packages],
conditions && `--conditions=${conditions.join(",")}`,
inject && inject.map(x => `--inject:${path.join(root, x)}`),
define && Object.entries(define).map(([k, v]) => `--define:${k}=${v}`),
Expand Down Expand Up @@ -927,6 +932,7 @@ function expectBundled(
const buildConfig = {
entrypoints: [...entryPaths, ...(entryPointsRaw ?? [])],
external,
packages,
minify: {
whitespace: minifyWhitespace,
identifiers: minifyIdentifiers,
Expand Down

0 comments on commit 20235a0

Please sign in to comment.