Skip to content

Commit

Permalink
Merge branch 'main' into nektro-patch-47166
Browse files Browse the repository at this point in the history
  • Loading branch information
nektro authored Apr 24, 2024
2 parents 0d2814e + 2abe6e7 commit fdb318e
Show file tree
Hide file tree
Showing 43 changed files with 1,778 additions and 239 deletions.
7 changes: 7 additions & 0 deletions docs/bundler/loaders.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ TOML files can be directly imported. Bun will parse them with its fast native TO
```ts
import config from "./bunfig.toml";
config.logLevel; // => "debug"

// via import attribute:
// import myCustomTOML from './my.config' with {type: "toml"};
```

During bundling, the parsed TOML is inlined into the bundle as a JavaScript object.
Expand Down Expand Up @@ -122,6 +125,10 @@ Text files can be directly imported. The file is read and returned as a string.
```ts
import contents from "./file.txt";
console.log(contents); // => "Hello, world!"

// To import an html file as text
// The "type' attribute can be used to override the default loader.
import html from "./index.html" with { type: "text" };
```

When referenced during a build, the contents are into the bundle as a string.
Expand Down
145 changes: 145 additions & 0 deletions docs/guides/runtime/define-constant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
name: Define and replace static globals & constants
---

The `--define` flag lets you declare statically-analyzable constants and globals. It replace all usages of an identifier or property in a JavaScript or TypeScript file with a constant value. This feature is supported at runtime and also in `bun build`. This is sort of similar to `#define` in C/C++, except for JavaScript.

```ts
bun --define:process.env.NODE_ENV="'production'" src/index.ts # Runtime
bun build --define:process.env.NODE_ENV="'production'" src/index.ts # Build
```

---

These statically-known values are used by Bun for dead code elimination and other optimizations.

```ts
if (process.env.NODE_ENV === "production") {
console.log("Production mode");
} else {
console.log("Development mode");
}
```

---

Before the code reaches the JavaScript engine, Bun replaces `process.env.NODE_ENV` with `"production"`.

```ts
if ("production" === "production") {
console.log("Production mode");
} else {
console.log("Development mode");
}
```

---

It doesn't stop there. Bun's optimizing transpiler is smart enough to do some basic constant folding.

Since `"production" === "production"` is always `true`, Bun replaces the entire expression with the `true` value.

```ts
if (true) {
console.log("Production mode");
} else {
console.log("Development mode");
}
```

---

And finally, Bun detects the `else` branch is not reachable, and eliminates it.

```ts
console.log("Production mode");
```

---

## What types of values are supported?

Values can be strings, identifiers, properties, or JSON.

### Replace global identifiers

To make all usages of `window` be `undefined`, you can use the following command.

```sh
bun --define window="undefined" src/index.ts
```

This can be useful when Server-Side Rendering (SSR) or when you want to make sure that the code doesn't depend on the `window` object.

```js
if (typeof window !== "undefined") {
console.log("Client-side code");
} else {
console.log("Server-side code");
}
```

You can also set the value to be another identifier. For example, to make all usages of `global` be `globalThis`, you can use the following command.

```sh
bun --define global="globalThis" src/index.ts
```

`global` is a global object in Node.js, but not in web browsers. So, you can use this to fix some cases where the code assumes that `global` is available.

### Replace values with JSON

`--define` can also be used to replace values with JSON objects and arrays.

To replace all usages of `AWS` with the JSON object `{"ACCESS_KEY":"abc","SECRET_KEY":"def"}`, you can use the following command.

```sh
# JSON
bun --define:AWS='{"ACCESS_KEY":"abc","SECRET_KEY":"def"}' src/index.ts
```

Those will be transformed into the equivalent JavaScript code.

From:

```ts
console.log(AWS.ACCESS_KEY); // => "abc"
```

To:

```ts
console.log("abc");
```

### Replace values with other properties

You can also pass properties to the `--define` flag.

For example, to replace all usages of `console.write` with `console.log`, you can use the following command (requires Bun v1.1.5 or later)

```sh
bun --define:console.write=console.log src/index.ts
```

That transforms the following input:

```ts
console.write("Hello, world!");
```

Into the following output:

```ts
console.log("Hello, world!");
```

## How is this different than setting a variable?

You can also set `process.env.NODE_ENV` to `"production"` in your code, but that won't help with dead code elimination. In JavaScript, property accesses can have side effects. Getters & setters can be functions, and even dynamically defined (due to prototype chains and Proxy). Even if you set `process.env.NODE_ENV` to `"production"`, on the next line, it is not safe for static analysis tools to assume that `process.env.NODE_ENV`is`"production"`.

## How is this different than find-and-replace or string replacement?

The `--define` flag operates on the AST (Abstract Syntax Tree) level, not on the text level. It happens during the transpilation process, which means it can be used in optimizations like dead code elimination.

String replacement tools tend to have escaping issues and replace unintended parts of the code.
15 changes: 15 additions & 0 deletions docs/guides/runtime/import-html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
name: Import HTML file as text
---

To import a `.html` file in Bun as a text file, use the `type: "text"` attribute in the import statement.

```ts
import html from "./file.html" with { type: "text" };

console.log(html); // <!DOCTYPE html><html><head>...
```

This can also be used with hot module reloading and/or watch mode to force Bun to reload whenever the `./file.html` file changes.

This feature was added in Bun v1.1.5.
19 changes: 18 additions & 1 deletion src/bun.js/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3866,8 +3866,25 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderImportModule(JSGlobalObject* j
resolvedIdentifier = JSC::Identifier::fromString(vm, makeString(resolved.result.value.toWTFString(BunString::ZeroCopy), Zig::toString(queryString)));
}

// This gets passed through the "parameters" argument to moduleLoaderFetch.
// Therefore, we modify it in place.
if (parameters && parameters.isObject()) {
auto* object = parameters.toObject(globalObject);
if (auto withObject = object->getIfPropertyExists(globalObject, vm.propertyNames->withKeyword)) {
if (withObject.isObject()) {
auto* with = jsCast<JSObject*>(withObject);
if (auto type = with->getIfPropertyExists(globalObject, vm.propertyNames->type)) {
if (type.isString()) {
const auto typeString = type.toWTFString(globalObject);
parameters = JSC::JSScriptFetchParameters::create(vm, ScriptFetchParameters::create(typeString));
}
}
}
}
}

auto result = JSC::importModule(globalObject, resolvedIdentifier,
JSC::jsUndefined(), parameters, JSC::jsUndefined());
JSC::jsUndefined(), parameters, jsUndefined());
RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope));

return result;
Expand Down
8 changes: 8 additions & 0 deletions src/bun.js/module_loader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2254,6 +2254,14 @@ pub const ModuleLoader = struct {
if (type_attribute) |attribute| {
if (attribute.eqlComptime("sqlite")) {
loader = .sqlite;
} else if (attribute.eqlComptime("text")) {
loader = .text;
} else if (attribute.eqlComptime("json")) {
loader = .json;
} else if (attribute.eqlComptime("toml")) {
loader = .toml;
} else if (attribute.eqlComptime("file")) {
loader = .file;
}
}

Expand Down
9 changes: 3 additions & 6 deletions src/bun.js/node/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -450,12 +450,7 @@ pub const StringOrBuffer = union(enum) {
}
}

pub fn fromJSMaybeAsync(
global: *JSC.JSGlobalObject,
allocator: std.mem.Allocator,
value: JSC.JSValue,
is_async: bool,
) ?StringOrBuffer {
pub fn fromJSMaybeAsync(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, is_async: bool) ?StringOrBuffer {
return switch (value.jsType()) {
JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject, JSC.JSValue.JSType.Object => {
const str = bun.String.tryFromJS(value, global) orelse return null;
Expand Down Expand Up @@ -495,9 +490,11 @@ pub const StringOrBuffer = union(enum) {
else => null,
};
}

pub fn fromJS(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue) ?StringOrBuffer {
return fromJSMaybeAsync(global, allocator, value, false);
}

pub fn fromJSWithEncoding(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, encoding: Encoding) ?StringOrBuffer {
return fromJSWithEncodingMaybeAsync(global, allocator, value, encoding, false);
}
Expand Down
2 changes: 1 addition & 1 deletion src/bundler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1597,7 +1597,7 @@ pub const Bundler = struct {
},
// TODO: use lazy export AST
.text => {
const expr = js_ast.Expr.init(js_ast.E.String, js_ast.E.String{
const expr = js_ast.Expr.init(js_ast.E.UTF8String, js_ast.E.UTF8String{
.data = source.contents,
}, logger.Loc.Empty);
const stmt = js_ast.Stmt.alloc(js_ast.S.ExportDefault, js_ast.S.ExportDefault{
Expand Down
8 changes: 5 additions & 3 deletions src/bundler/bundle_v2.zig
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,10 @@ pub const BundleV2 = struct {
.index = source_index,
},
.loader = loader,
.side_effects = _resolver.SideEffects.has_side_effects,
.side_effects = switch (loader) {
.text, .json, .toml, .file => _resolver.SideEffects.no_side_effects__pure_data,
else => _resolver.SideEffects.has_side_effects,
},
}) catch @panic("Ran out of memory");
var task = this.graph.allocator.create(ParseTask) catch @panic("Ran out of memory");
task.* = ParseTask.init(&resolve_result, source_index, this);
Expand Down Expand Up @@ -2587,9 +2590,8 @@ pub const ParseTask = struct {
return JSAst.init((try js_parser.newLazyExportAST(allocator, bundler.options.define, opts, log, root, &source, "")).?);
},
.text => {
const root = Expr.init(E.String, E.String{
const root = Expr.init(E.UTF8String, E.UTF8String{
.data = source.contents,
.prefer_template = true,
}, Logger.Loc{ .start = 0 });
return JSAst.init((try js_parser.newLazyExportAST(allocator, bundler.options.define, opts, log, root, &source, "")).?);
},
Expand Down
39 changes: 33 additions & 6 deletions src/cli/filter_arg.zig
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,28 @@ pub fn getCandidatePackagePatterns(allocator: std.mem.Allocator, log: *bun.logge

pub const FilterSet = struct {
allocator: std.mem.Allocator,

// TODO: Pattern should be
// union (enum) { name: []const u32, path: []const u32, any_name: void }
filters: []Pattern,
has_name_filters: bool = false,
match_all: bool = false,

pub fn matches(this: *const FilterSet, path: []const u8, name: []const u8) bool {
if (this.match_all) {
// allow empty name if there are any filters which are a relative path
// --filter="*" --filter="./bar" script
if (name.len > 0) {
return true;
}
}

if (this.has_name_filters) {
return this.matchesPathName(path, name);
}

return this.matchesPath(path);
}

const Pattern = struct {
codepoints: []u32,
Expand All @@ -125,8 +145,14 @@ pub const FilterSet = struct {
pub fn init(allocator: std.mem.Allocator, filters: []const []const u8, cwd: []const u8) !FilterSet {
var buf: bun.PathBuffer = undefined;
// TODO fixed buffer allocator with fallback?
var self = FilterSet{ .allocator = allocator, .filters = try allocator.alloc(Pattern, filters.len) };
for (self.filters, filters) |*pattern, filter_utf8_| {
var list = try std.ArrayList(Pattern).initCapacity(allocator, filters.len);
var self = FilterSet{ .allocator = allocator, .filters = &.{} };
for (filters) |filter_utf8_| {
if (strings.eqlComptime(filter_utf8_, "*") or strings.eqlComptime(filter_utf8_, "**")) {
self.match_all = true;
continue;
}

var filter_utf8 = filter_utf8_;
const is_path = filter_utf8.len > 0 and filter_utf8[0] == '.';
if (is_path) {
Expand All @@ -143,11 +169,12 @@ pub const FilterSet = struct {
try filter_utf32.append(self.allocator, cursor.c);
}
self.has_name_filters = self.has_name_filters or !is_path;
pattern.* = Pattern{
try list.append(.{
.codepoints = filter_utf32.items,
.kind = if (is_path) .path else .name,
};
});
}
self.filters = list.items;
return self;
}

Expand All @@ -159,7 +186,7 @@ pub const FilterSet = struct {
self.allocator.free(self.filters);
}

pub fn matchesPath(self: *FilterSet, path: []const u8) bool {
pub fn matchesPath(self: *const FilterSet, path: []const u8) bool {
for (self.filters) |filter| {
if (Glob.matchImpl(filter.codepoints, path)) {
return true;
Expand All @@ -168,7 +195,7 @@ pub const FilterSet = struct {
return false;
}

pub fn matchesPathName(self: *FilterSet, path: []const u8, name: []const u8) bool {
pub fn matchesPathName(self: *const FilterSet, path: []const u8, name: []const u8) bool {
for (self.filters) |filter| {
const target = switch (filter.kind) {
.name => name,
Expand Down
9 changes: 3 additions & 6 deletions src/cli/filter_run.zig
Original file line number Diff line number Diff line change
Expand Up @@ -470,14 +470,11 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
continue;
};

const matches = if (filter_instance.has_name_filters)
filter_instance.matchesPathName(path, pkgjson.name)
else
filter_instance.matchesPath(path);
const pkgscripts = pkgjson.scripts orelse continue;

if (!matches) continue;
if (!filter_instance.matches(path, pkgjson.name))
continue;

const pkgscripts = pkgjson.scripts orelse continue;
const PATH = try RunCommand.configurePathForRunWithPackageJsonDir(ctx, dirpath, &this_bundler, null, dirpath, ctx.debug.run_in_bun);

for (&[3][]const u8{ pre_script_name, script_name, post_script_name }) |name| {
Expand Down
Loading

0 comments on commit fdb318e

Please sign in to comment.