aboutsummaryrefslogtreecommitdiff
path: root/src/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'src/javascript')
-rw-r--r--src/javascript/jsc/api/server.zig139
-rw-r--r--src/javascript/jsc/api/transpiler.zig243
-rw-r--r--src/javascript/jsc/bindings/bindings.zig9
3 files changed, 320 insertions, 71 deletions
diff --git a/src/javascript/jsc/api/server.zig b/src/javascript/jsc/api/server.zig
index 9f38045f0..a76ddf9ab 100644
--- a/src/javascript/jsc/api/server.zig
+++ b/src/javascript/jsc/api/server.zig
@@ -254,7 +254,7 @@ pub const ServerConfig = struct {
args.development = false;
}
- const PORT_ENV = .{ "PORT", "BUN_PORT" };
+ const PORT_ENV = .{ "PORT", "BUN_PORT", "NODE_PORT" };
inline for (PORT_ENV) |PORT| {
if (env.get(PORT)) |port| {
@@ -428,6 +428,74 @@ pub const ServerConfig = struct {
}
};
+pub fn NewRequestContextStackAllocator(comptime RequestContext: type, comptime count: usize) type {
+ // Pre-allocate up to 2048 requests
+ // use a bitset to track which ones are used
+ return struct {
+ buf: [count]RequestContext = undefined,
+ unused: Set = undefined,
+ fallback_allocator: std.mem.Allocator = undefined,
+
+ pub const Set = std.bit_set.ArrayBitSet(usize, count);
+
+ pub fn get(this: *@This()) std.mem.Allocator {
+ this.unused = Set.initFull();
+ return std.mem.Allocator.init(this, alloc, resize, free);
+ }
+
+ fn alloc(self: *@This(), a: usize, b: u29, c: u29, d: usize) ![]u8 {
+ if (self.unused.findFirstSet()) |i| {
+ self.unused.unset(i);
+ return std.mem.asBytes(&self.buf[i]);
+ }
+
+ return try self.fallback_allocator.rawAlloc(a, b, c, d);
+ }
+
+ fn resize(
+ _: *@This(),
+ _: []u8,
+ _: u29,
+ _: usize,
+ _: u29,
+ _: usize,
+ ) ?usize {
+ unreachable;
+ }
+
+ fn sliceContainsSlice(container: []u8, slice: []u8) bool {
+ return @ptrToInt(slice.ptr) >= @ptrToInt(container.ptr) and
+ (@ptrToInt(slice.ptr) + slice.len) <= (@ptrToInt(container.ptr) + container.len);
+ }
+
+ fn free(
+ self: *@This(),
+ buf: []u8,
+ buf_align: u29,
+ return_address: usize,
+ ) void {
+ _ = buf_align;
+ _ = return_address;
+ const bytes = std.mem.asBytes(&self.buf);
+ if (sliceContainsSlice(bytes, buf)) {
+ const index = if (bytes[0..buf.len].ptr != buf.ptr)
+ (@ptrToInt(buf.ptr) - @ptrToInt(bytes)) / @sizeOf(RequestContext)
+ else
+ @as(usize, 0);
+
+ if (comptime Environment.allow_assert) {
+ std.debug.assert(@intToPtr(*RequestContext, @ptrToInt(buf.ptr)) == &self.buf[index]);
+ std.debug.assert(!self.unused.isSet(index));
+ }
+
+ self.unused.set(index);
+ } else {
+ self.fallback_allocator.rawFree(buf, buf_align, return_address);
+ }
+ }
+ };
+}
+
// This is defined separately partially to work-around an LLVM debugger bug.
fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comptime ThisServer: type) type {
return struct {
@@ -436,6 +504,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
pub threadlocal var pool: ?*RequestContext.RequestContextStackAllocator = null;
pub threadlocal var pool_allocator: std.mem.Allocator = undefined;
+ pub const RequestContextStackAllocator = NewRequestContextStackAllocator(RequestContext, 2048);
+
server: *ThisServer,
resp: *App.Response,
/// thread-local default heap allocator
@@ -459,76 +529,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
sendfile: SendfileContext = undefined,
request_js_object: JSC.C.JSObjectRef = null,
request_body_buf: std.ArrayListUnmanaged(u8) = .{},
+
/// Used either for temporary blob data or fallback
/// When the response body is a temporary value
response_buf_owned: std.ArrayListUnmanaged(u8) = .{},
- // Pre-allocate up to 2048 requests
- // use a bitset to track which ones are used
- pub const RequestContextStackAllocator = struct {
- buf: [2048]RequestContext = undefined,
- unused: Set = undefined,
- fallback_allocator: std.mem.Allocator = undefined,
-
- pub const Set = std.bit_set.ArrayBitSet(usize, 2048);
-
- pub fn get(this: *@This()) std.mem.Allocator {
- this.unused = Set.initFull();
- return std.mem.Allocator.init(this, alloc, resize, free);
- }
-
- fn alloc(self: *@This(), a: usize, b: u29, c: u29, d: usize) ![]u8 {
- if (self.unused.findFirstSet()) |i| {
- self.unused.unset(i);
- return std.mem.asBytes(&self.buf[i]);
- }
-
- return try self.fallback_allocator.rawAlloc(a, b, c, d);
- }
-
- fn resize(
- _: *@This(),
- _: []u8,
- _: u29,
- _: usize,
- _: u29,
- _: usize,
- ) ?usize {
- unreachable;
- }
-
- fn sliceContainsSlice(container: []u8, slice: []u8) bool {
- return @ptrToInt(slice.ptr) >= @ptrToInt(container.ptr) and
- (@ptrToInt(slice.ptr) + slice.len) <= (@ptrToInt(container.ptr) + container.len);
- }
-
- fn free(
- self: *@This(),
- buf: []u8,
- buf_align: u29,
- return_address: usize,
- ) void {
- _ = buf_align;
- _ = return_address;
- const bytes = std.mem.asBytes(&self.buf);
- if (sliceContainsSlice(bytes, buf)) {
- const index = if (bytes[0..buf.len].ptr != buf.ptr)
- (@ptrToInt(buf.ptr) - @ptrToInt(bytes)) / @sizeOf(RequestContext)
- else
- @as(usize, 0);
-
- if (comptime Environment.allow_assert) {
- std.debug.assert(@intToPtr(*RequestContext, @ptrToInt(buf.ptr)) == &self.buf[index]);
- std.debug.assert(!self.unused.isSet(index));
- }
-
- self.unused.set(index);
- } else {
- self.fallback_allocator.rawFree(buf, buf_align, return_address);
- }
- }
- };
-
// TODO: support builtin compression
const can_sendfile = !ssl_enabled;
diff --git a/src/javascript/jsc/api/transpiler.zig b/src/javascript/jsc/api/transpiler.zig
index e7c78ac78..9b4fce4de 100644
--- a/src/javascript/jsc/api/transpiler.zig
+++ b/src/javascript/jsc/api/transpiler.zig
@@ -41,6 +41,8 @@ const JSPrinter = @import("../../../js_printer.zig");
const ScanPassResult = JSParser.ScanPassResult;
const Mimalloc = @import("../../../mimalloc_arena.zig");
const Runtime = @import("../../../runtime.zig").Runtime;
+const JSLexer = @import("../../../js_lexer.zig");
+const Expr = JSAst.Expr;
bundler: Bundler.Bundler,
arena: std.heap.ArenaAllocator,
@@ -64,6 +66,12 @@ pub const Class = NewClass(
.transformSync = .{
.rfn = transformSync,
},
+ // .resolve = .{
+ // .rfn = resolve,
+ // },
+ // .buildSync = .{
+ // .rfn = buildSync,
+ // },
.finalize = finalize,
},
.{},
@@ -82,7 +90,6 @@ const default_transform_options: Api.TransformOptions = brk: {
opts.disable_hmr = true;
opts.platform = Api.Platform.browser;
opts.serve = false;
-
break :brk opts;
};
@@ -94,8 +101,9 @@ const TranspilerOptions = struct {
tsconfig_buf: []const u8 = "",
macros_buf: []const u8 = "",
log: logger.Log,
- pending_tasks: u32 = 0,
runtime: Runtime.Features = Runtime.Features{ .top_level_await = true },
+ tree_shaking: bool = false,
+ trim_unused_imports: ?bool = null,
};
// Mimalloc gets unstable if we try to move this to a different thread
@@ -114,6 +122,7 @@ pub const TransformTask = struct {
tsconfig: ?*TSConfigJSON = null,
loader: Loader,
global: *JSGlobalObject,
+ replace_exports: Runtime.Features.ReplaceableExport.Map = .{},
pub const AsyncTransformTask = JSC.ConcurrentPromiseTask(TransformTask);
pub const AsyncTransformEventLoopTask = AsyncTransformTask.EventLoopTask;
@@ -129,6 +138,7 @@ pub const TransformTask = struct {
.tsconfig = transpiler.transpiler_options.tsconfig,
.log = logger.Log.init(bun.default_allocator),
.loader = loader,
+ .replace_exports = transpiler.transpiler_options.runtime.replace_exports,
};
transform_task.bundler = transpiler.bundler;
transform_task.bundler.linker.resolver = &transform_task.bundler.resolver;
@@ -170,6 +180,7 @@ pub const TransformTask = struct {
.jsx = jsx,
.path = source.path,
.virtual_source = &source,
+ .replace_exports = this.replace_exports,
// .allocator = this.
};
@@ -265,7 +276,63 @@ pub const TransformTask = struct {
}
};
-fn transformOptionsFromJSC(ctx: JSC.C.JSContextRef, temp_allocator: std.mem.Allocator, args: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) TranspilerOptions {
+fn exportReplacementValue(value: JSValue, globalThis: *JSGlobalObject) ?JSAst.Expr {
+ if (value.isBoolean()) {
+ return Expr{
+ .data = .{
+ .e_boolean = .{
+ .value = value.toBoolean(),
+ },
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isNumber()) {
+ return Expr{
+ .data = .{
+ .e_number = .{ .value = value.asNumber() },
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isNull()) {
+ return Expr{
+ .data = .{
+ .e_null = .{},
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isUndefined()) {
+ return Expr{
+ .data = .{
+ .e_undefined = .{},
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ if (value.isString()) {
+ var str = JSAst.E.String{
+ .utf8 = std.fmt.allocPrint(bun.default_allocator, "{}", .{value.getZigString(globalThis)}) catch unreachable,
+ };
+ var out = bun.default_allocator.create(JSAst.E.String) catch unreachable;
+ out.* = str;
+ return Expr{
+ .data = .{
+ .e_string = out,
+ },
+ .loc = logger.Loc.Empty,
+ };
+ }
+
+ return null;
+}
+
+fn transformOptionsFromJSC(ctx: JSC.C.JSContextRef, temp_allocator: std.mem.Allocator, args: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) !TranspilerOptions {
var globalThis = ctx.ptr();
const object = args.next() orelse return TranspilerOptions{ .log = logger.Log.init(temp_allocator) };
if (object.isUndefinedOrNull()) return TranspilerOptions{ .log = logger.Log.init(temp_allocator) };
@@ -498,6 +565,168 @@ fn transformOptionsFromJSC(ctx: JSC.C.JSContextRef, temp_allocator: std.mem.Allo
}
}
+ var tree_shaking: ?bool = null;
+ if (object.get(globalThis, "treeShaking")) |treeShaking| {
+ tree_shaking = treeShaking.toBoolean();
+ }
+
+ var trim_unused_imports: ?bool = null;
+ if (object.get(globalThis, "trimUnusedImports")) |trimUnusedImports| {
+ trim_unused_imports = trimUnusedImports.toBoolean();
+ }
+
+ if (object.getTruthy(globalThis, "exports")) |exports| {
+ if (!exports.isObject()) {
+ JSC.throwInvalidArguments("exports must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var replacements = Runtime.Features.ReplaceableExport.Map{};
+ errdefer replacements.clearAndFree(bun.default_allocator);
+
+ if (exports.getTruthy(globalThis, "eliminate")) |eliminate| {
+ if (!eliminate.jsType().isArray()) {
+ JSC.throwInvalidArguments("exports.eliminate must be an array", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var total_name_buf_len: u32 = 0;
+ var string_count: u32 = 0;
+ var iter = JSC.JSArrayIterator.init(eliminate, globalThis);
+ {
+ var length_iter = iter;
+ while (length_iter.next()) |value| {
+ if (value.isString()) {
+ const length = value.getLengthOfArray(globalThis);
+ string_count += @as(u32, @boolToInt(length > 0));
+ total_name_buf_len += length;
+ }
+ }
+ }
+
+ if (total_name_buf_len > 0) {
+ var buf = try std.ArrayListUnmanaged(u8).initCapacity(bun.default_allocator, total_name_buf_len);
+ try replacements.ensureUnusedCapacity(bun.default_allocator, string_count);
+ {
+ var length_iter = iter;
+ while (length_iter.next()) |value| {
+ if (!value.isString()) continue;
+ var str = value.getZigString(globalThis);
+ if (str.len == 0) continue;
+ const name = std.fmt.bufPrint(buf.items.ptr[buf.items.len..buf.capacity], "{}", .{str}) catch {
+ JSC.throwInvalidArguments("Error reading exports.eliminate. TODO: utf-16", .{}, ctx, exception);
+ return transpiler;
+ };
+ buf.items.len += name.len;
+ if (name.len > 0) {
+ replacements.putAssumeCapacity(name, .{ .delete = .{} });
+ }
+ }
+ }
+ }
+ }
+
+ if (exports.getTruthy(globalThis, "replace")) |replace| {
+ if (!replace.isObject()) {
+ JSC.throwInvalidArguments("replace must be an object", .{}, ctx, exception);
+ return transpiler;
+ }
+
+ var total_name_buf_len: usize = 0;
+
+ var array = js.JSObjectCopyPropertyNames(ctx, replace.asObjectRef());
+ defer js.JSPropertyNameArrayRelease(array);
+ const property_names_count = @intCast(u32, js.JSPropertyNameArrayGetCount(array));
+ var iter = JSC.JSPropertyNameIterator{
+ .array = array,
+ .count = @intCast(u32, property_names_count),
+ };
+
+ {
+ var key_iter = iter;
+ while (key_iter.next()) |item| {
+ total_name_buf_len += JSC.C.JSStringGetLength(item);
+ }
+ }
+
+ if (total_name_buf_len > 0) {
+ var total_name_buf = try std.ArrayList(u8).initCapacity(bun.default_allocator, total_name_buf_len);
+ errdefer total_name_buf.clearAndFree();
+
+ try replacements.ensureUnusedCapacity(bun.default_allocator, property_names_count);
+ defer {
+ if (exception.* != null) {
+ total_name_buf.clearAndFree();
+ replacements.clearAndFree(bun.default_allocator);
+ }
+ }
+
+ while (iter.next()) |item| {
+ const start = total_name_buf.items.len;
+ total_name_buf.items.len += @maximum(
+ // this returns a null terminated string
+ JSC.C.JSStringGetUTF8CString(item, total_name_buf.items.ptr + start, total_name_buf.capacity - start),
+ 1,
+ ) - 1;
+ JSC.C.JSStringRelease(item);
+ const key = total_name_buf.items[start..total_name_buf.items.len];
+ // if somehow the string is empty, skip it
+ if (key.len == 0)
+ continue;
+
+ const value = replace.get(globalThis, key).?;
+ if (value.isEmpty()) continue;
+
+ if (!JSLexer.isIdentifier(key)) {
+ JSC.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{key}, ctx, exception);
+ total_name_buf.deinit();
+ return transpiler;
+ }
+
+ var entry = replacements.getOrPutAssumeCapacity(key);
+
+ if (exportReplacementValue(value, globalThis)) |expr| {
+ entry.value_ptr.* = .{ .replace = expr };
+ continue;
+ }
+
+ if (value.isObject() and value.getLengthOfArray(ctx.ptr()) == 2) {
+ const replacementValue = JSC.JSObject.getIndex(value, globalThis, 1);
+ if (exportReplacementValue(replacementValue, globalThis)) |to_replace| {
+ const replacementKey = JSC.JSObject.getIndex(value, globalThis, 0);
+ var slice = (try replacementKey.toSlice(globalThis, bun.default_allocator).cloneIfNeeded());
+ var replacement_name = slice.slice();
+
+ if (!JSLexer.isIdentifier(replacement_name)) {
+ JSC.throwInvalidArguments("\"{s}\" is not a valid ECMAScript identifier", .{replacement_name}, ctx, exception);
+ total_name_buf.deinit();
+ slice.deinit();
+ return transpiler;
+ }
+
+ entry.value_ptr.* = .{
+ .inject = .{
+ .name = replacement_name,
+ .value = to_replace,
+ },
+ };
+ continue;
+ }
+ }
+
+ JSC.throwInvalidArguments("exports.replace values can only be string, null, undefined, number or boolean", .{}, ctx, exception);
+ return transpiler;
+ }
+ }
+ }
+
+ tree_shaking = tree_shaking orelse (replacements.count() > 0);
+ transpiler.runtime.replace_exports = replacements;
+ }
+
+ transpiler.tree_shaking = tree_shaking orelse false;
+ transpiler.trim_unused_imports = trim_unused_imports orelse transpiler.tree_shaking;
+
return transpiler;
}
@@ -511,7 +740,10 @@ pub fn constructor(
var args = JSC.Node.ArgumentsSlice.init(@ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]);
defer temp.deinit();
const transpiler_options: TranspilerOptions = if (arguments.len > 0)
- transformOptionsFromJSC(ctx, temp.allocator(), &args, exception)
+ transformOptionsFromJSC(ctx, temp.allocator(), &args, exception) catch {
+ JSC.throwInvalidArguments("Failed to create transpiler", .{}, ctx, exception);
+ return null;
+ }
else
TranspilerOptions{ .log = logger.Log.init(getAllocator(ctx)) };
@@ -561,6 +793,8 @@ pub fn constructor(
bundler.options.macro_remap = transpiler_options.macro_map;
}
+ bundler.options.tree_shaking = transpiler_options.tree_shaking;
+ bundler.options.trim_unused_imports = transpiler_options.trim_unused_imports;
bundler.options.allow_runtime = transpiler_options.runtime.allow_runtime;
bundler.options.auto_import_jsx = transpiler_options.runtime.auto_import_jsx;
bundler.options.hot_module_reloading = transpiler_options.runtime.hot_module_reloading;
@@ -612,6 +846,7 @@ fn getParseResult(this: *Transpiler, allocator: std.mem.Allocator, code: []const
.jsx = jsx,
.path = source.path,
.virtual_source = &source,
+ .replace_exports = this.transpiler_options.runtime.replace_exports,
// .allocator = this.
};
diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig
index 35f565bc2..3cec43447 100644
--- a/src/javascript/jsc/bindings/bindings.zig
+++ b/src/javascript/jsc/bindings/bindings.zig
@@ -119,6 +119,15 @@ pub const ZigString = extern struct {
return Slice{ .allocator = allocator, .ptr = duped.ptr, .len = this.len, .allocated = true };
}
+ pub fn cloneIfNeeded(this: Slice) !Slice {
+ if (this.allocated) {
+ return this;
+ }
+
+ var duped = try this.allocator.dupe(u8, this.ptr[0..this.len]);
+ return Slice{ .allocator = this.allocator, .ptr = duped.ptr, .len = this.len, .allocated = true };
+ }
+
pub fn cloneZ(this: Slice, allocator: std.mem.Allocator) !Slice {
if (this.allocated or this.len == 0) {
return this;