mirror of
https://github.com/oven-sh/bun
synced 2026-02-26 03:27:23 +01:00
Compare commits
2 Commits
claude/fix
...
claude/con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4fb84aebd | ||
|
|
046070f9fd |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
.claude/settings.local.json
|
||||
.direnv
|
||||
.DS_Store
|
||||
.env
|
||||
.envrc
|
||||
|
||||
19
bench/snippets/blob-array-iteration.mjs
Normal file
19
bench/snippets/blob-array-iteration.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
import { bench, run } from "../runner.mjs";
|
||||
|
||||
const N100 = Array.from({ length: 100 }, (_, i) => `chunk-${i}`);
|
||||
const N1000 = Array.from({ length: 1000 }, (_, i) => `data-${i}`);
|
||||
const N10000 = Array.from({ length: 10000 }, (_, i) => `x${i}`);
|
||||
|
||||
bench("new Blob([100 strings])", () => new Blob(N100));
|
||||
bench("new Blob([1000 strings])", () => new Blob(N1000));
|
||||
bench("new Blob([10000 strings])", () => new Blob(N10000));
|
||||
|
||||
// Mixed: strings + buffers
|
||||
const mixed = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
mixed.push(`text-${i}`);
|
||||
mixed.push(new Uint8Array([i, i + 1, i + 2]));
|
||||
}
|
||||
bench("new Blob([100 strings + 100 buffers])", () => new Blob(mixed));
|
||||
|
||||
await run();
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
oven-sh/boringssl
|
||||
COMMIT
|
||||
4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac
|
||||
f1ffd9e83d4f5c28a9c70d73f9a4e6fcf310062f
|
||||
)
|
||||
|
||||
register_cmake_command(
|
||||
|
||||
@@ -131,7 +131,6 @@
|
||||
stdenv = pkgs.clangStdenv;
|
||||
}) {
|
||||
inherit packages;
|
||||
hardeningDisable = [ "fortify" ];
|
||||
|
||||
shellHook = ''
|
||||
# Set up build environment
|
||||
|
||||
28
src/bun.js/bindings/ContiguousArrayView.zig
Normal file
28
src/bun.js/bindings/ContiguousArrayView.zig
Normal file
@@ -0,0 +1,28 @@
|
||||
pub const ContiguousArrayView = struct {
|
||||
elements: [*]const JSValue,
|
||||
len: u32,
|
||||
i: u32 = 0,
|
||||
|
||||
pub fn init(value: JSValue, global: *JSGlobalObject) ?ContiguousArrayView {
|
||||
var length: u32 = 0;
|
||||
const ptr = Bun__JSArray__getContiguousVector(value, global, &length);
|
||||
if (ptr == null) return null;
|
||||
return .{ .elements = @ptrCast(ptr.?), .len = length };
|
||||
}
|
||||
|
||||
pub inline fn next(self: *ContiguousArrayView) ?JSValue {
|
||||
if (self.i >= self.len) return null;
|
||||
const val = self.elements[self.i];
|
||||
self.i += 1;
|
||||
if (val == .zero) return .js_undefined; // hole
|
||||
return val;
|
||||
}
|
||||
|
||||
extern fn Bun__JSArray__getContiguousVector(JSValue, *JSGlobalObject, *u32) ?[*]const JSValue;
|
||||
};
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSGlobalObject = jsc.JSGlobalObject;
|
||||
const JSValue = jsc.JSValue;
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "JavaScriptCore/JSArray.h"
|
||||
#include "JavaScriptCore/JSArrayBuffer.h"
|
||||
#include "JavaScriptCore/JSArrayInlines.h"
|
||||
#include "JavaScriptCore/JSGlobalObjectInlines.h"
|
||||
#include "JavaScriptCore/JSFunction.h"
|
||||
#include "JavaScriptCore/ErrorInstanceInlines.h"
|
||||
#include "JavaScriptCore/BigIntObject.h"
|
||||
@@ -6109,6 +6110,45 @@ CPP_DECL [[ZIG_EXPORT(nothrow)]] unsigned int Bun__CallFrame__getLineNumber(JSC:
|
||||
return lineColumn.line;
|
||||
}
|
||||
|
||||
extern "C" const JSC::EncodedJSValue* Bun__JSArray__getContiguousVector(
|
||||
JSC::EncodedJSValue encodedValue,
|
||||
JSC::JSGlobalObject* globalObject,
|
||||
uint32_t* outLength)
|
||||
{
|
||||
JSC::JSValue value = JSC::JSValue::decode(encodedValue);
|
||||
if (!value.isCell())
|
||||
return nullptr;
|
||||
|
||||
JSC::JSCell* cell = value.asCell();
|
||||
|
||||
if (!isJSArray(cell))
|
||||
return nullptr;
|
||||
|
||||
JSC::JSArray* array = jsCast<JSC::JSArray*>(cell);
|
||||
JSC::IndexingType indexing = array->indexingType();
|
||||
|
||||
// Only support Int32 and Contiguous shapes (not Double, ArrayStorage, etc.)
|
||||
if (!hasInt32(indexing) && !hasContiguous(indexing))
|
||||
return nullptr;
|
||||
|
||||
// Verify prototype chain is healthy and no indexed accessors are installed
|
||||
if (!array->canDoFastIndexedAccess())
|
||||
return nullptr;
|
||||
|
||||
ASSERT(!globalObject->isHavingABadTime());
|
||||
|
||||
JSC::Butterfly* butterfly = array->butterfly();
|
||||
uint32_t length = butterfly->publicLength();
|
||||
|
||||
ASSERT(length <= butterfly->vectorLength());
|
||||
|
||||
if (length == 0)
|
||||
return nullptr;
|
||||
|
||||
*outLength = length;
|
||||
return reinterpret_cast<const JSC::EncodedJSValue*>(butterfly->contiguous().data());
|
||||
}
|
||||
|
||||
extern "C" void JSC__ArrayBuffer__ref(JSC::ArrayBuffer* self) { self->ref(); }
|
||||
extern "C" void JSC__ArrayBuffer__deref(JSC::ArrayBuffer* self) { self->deref(); }
|
||||
extern "C" void JSC__ArrayBuffer__asBunArrayBuffer(JSC::ArrayBuffer* self, Bun__ArrayBuffer* out)
|
||||
|
||||
@@ -1901,7 +1901,7 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey,
|
||||
// ============================================================================
|
||||
// KDF
|
||||
|
||||
const EVP_MD* getDigestByName(const WTF::StringView name)
|
||||
const EVP_MD* getDigestByName(const WTF::StringView name, bool ignoreSHA512_224)
|
||||
{
|
||||
// Historically, "dss1" and "DSS1" were DSA aliases for SHA-1
|
||||
// exposed through the public API.
|
||||
@@ -1955,6 +1955,9 @@ const EVP_MD* getDigestByName(const WTF::StringView name)
|
||||
return EVP_sha512();
|
||||
}
|
||||
if (WTF::equalIgnoringASCIICase(moreBits, "/224"_s)) {
|
||||
if (ignoreSHA512_224) {
|
||||
return nullptr;
|
||||
}
|
||||
return EVP_sha512_224();
|
||||
}
|
||||
if (WTF::equalIgnoringASCIICase(moreBits, "/256"_s)) {
|
||||
@@ -1976,6 +1979,10 @@ const EVP_MD* getDigestByName(const WTF::StringView name)
|
||||
}
|
||||
}
|
||||
|
||||
if (ignoreSHA512_224 && WTF::equalIgnoringASCIICase(name, "sha512-224"_s)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// if (name == "ripemd160WithRSA"_s || name == "RSA-RIPEMD160"_s) {
|
||||
// return EVP_ripemd160();
|
||||
// }
|
||||
|
||||
@@ -1575,7 +1575,7 @@ Buffer<char> ExportChallenge(const char* input, size_t length);
|
||||
// ============================================================================
|
||||
// KDF
|
||||
|
||||
const EVP_MD* getDigestByName(const WTF::StringView name);
|
||||
const EVP_MD* getDigestByName(const WTF::StringView name, bool ignoreSHA512_224 = false);
|
||||
const EVP_CIPHER* getCipherByName(const WTF::StringView name);
|
||||
|
||||
// Verify that the specified HKDF output length is valid for the given digest.
|
||||
|
||||
@@ -251,7 +251,15 @@ JSC_DEFINE_HOST_FUNCTION(jsHashProtoFuncDigest, (JSC::JSGlobalObject * lexicalGl
|
||||
|
||||
// Only compute the digest if it hasn't been cached yet
|
||||
if (!hash->m_digest && len > 0) {
|
||||
auto data = hash->m_ctx.digestFinal(len);
|
||||
|
||||
const EVP_MD* md = hash->m_ctx.getDigest();
|
||||
uint32_t bufLen = len;
|
||||
if (md == EVP_sha512_224()) {
|
||||
// SHA-512/224 expects buffer length of length % 8. can be truncated afterwards
|
||||
bufLen = SHA512_224_DIGEST_BUFFER_LENGTH;
|
||||
}
|
||||
|
||||
auto data = hash->m_ctx.digestFinal(bufLen);
|
||||
if (!data) {
|
||||
throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "Failed to finalize digest"_s);
|
||||
return {};
|
||||
@@ -317,7 +325,7 @@ JSC_DEFINE_HOST_FUNCTION(constructHash, (JSC::JSGlobalObject * globalObject, JSC
|
||||
WTF::String algorithm = algorithmOrHashInstanceValue.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
md = ncrypto::getDigestByName(algorithm);
|
||||
md = ncrypto::getDigestByName(algorithm, true);
|
||||
if (!md) {
|
||||
zigHasher = ExternZigHash::getByName(zigGlobalObject, algorithm);
|
||||
}
|
||||
|
||||
@@ -518,32 +518,25 @@ static void processLine(const Char* lineStart, const Char* lineEnd, size_t colum
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate word lengths using WTF::find for space detection
|
||||
// Calculate word lengths
|
||||
Vector<size_t> wordLengths;
|
||||
auto lineSpan = std::span<const Char>(lineStart, lineEnd);
|
||||
size_t wordStartIdx = 0;
|
||||
while (wordStartIdx <= lineSpan.size()) {
|
||||
size_t spacePos = WTF::find(lineSpan, static_cast<Char>(' '), wordStartIdx);
|
||||
size_t wordEndIdx = (spacePos == WTF::notFound) ? lineSpan.size() : spacePos;
|
||||
|
||||
if (wordStartIdx < wordEndIdx) {
|
||||
wordLengths.append(stringWidth(lineSpan.data() + wordStartIdx,
|
||||
lineSpan.data() + wordEndIdx,
|
||||
options.ambiguousIsNarrow));
|
||||
} else {
|
||||
wordLengths.append(0);
|
||||
const Char* wordStart = lineStart;
|
||||
for (const Char* it = lineStart; it <= lineEnd; ++it) {
|
||||
if (it == lineEnd || *it == ' ') {
|
||||
if (wordStart < it) {
|
||||
wordLengths.append(stringWidth(wordStart, it, options.ambiguousIsNarrow));
|
||||
} else {
|
||||
wordLengths.append(0);
|
||||
}
|
||||
wordStart = it + 1;
|
||||
}
|
||||
|
||||
if (spacePos == WTF::notFound)
|
||||
break;
|
||||
wordStartIdx = wordEndIdx + 1;
|
||||
}
|
||||
|
||||
// Start with empty first row
|
||||
rows.append(Row<Char>());
|
||||
|
||||
// Process each word
|
||||
const Char* wordStart = lineStart;
|
||||
wordStart = lineStart;
|
||||
size_t wordIndex = 0;
|
||||
|
||||
for (const Char* it = lineStart; it <= lineEnd; ++it) {
|
||||
@@ -632,24 +625,17 @@ static WTF::String wrapAnsiImpl(std::span<const Char> input, size_t columns, con
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Normalize \r\n to \n using WTF::findNextNewline
|
||||
// Normalize \r\n to \n
|
||||
Vector<Char> normalized;
|
||||
normalized.reserveCapacity(input.size());
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < input.size()) {
|
||||
auto newline = WTF::findNextNewline(input, pos);
|
||||
if (newline.position == WTF::notFound) {
|
||||
// Append remaining content
|
||||
normalized.append(std::span { input.data() + pos, input.size() - pos });
|
||||
break;
|
||||
for (size_t i = 0; i < input.size(); ++i) {
|
||||
if (i + 1 < input.size() && input[i] == '\r' && input[i + 1] == '\n') {
|
||||
normalized.append(static_cast<Char>('\n'));
|
||||
i++; // Skip next char
|
||||
} else {
|
||||
normalized.append(input[i]);
|
||||
}
|
||||
// Append content before newline
|
||||
if (newline.position > pos)
|
||||
normalized.append(std::span { input.data() + pos, newline.position - pos });
|
||||
// Always append \n regardless of original (\r, \n, or \r\n)
|
||||
normalized.append(static_cast<Char>('\n'));
|
||||
pos = newline.position + newline.length;
|
||||
}
|
||||
|
||||
// Process each line separately
|
||||
|
||||
@@ -56,6 +56,7 @@ pub const DeferredError = @import("./bindings/DeferredError.zig").DeferredError;
|
||||
pub const GetterSetter = @import("./bindings/GetterSetter.zig").GetterSetter;
|
||||
pub const JSArray = @import("./bindings/JSArray.zig").JSArray;
|
||||
pub const JSArrayIterator = @import("./bindings/JSArrayIterator.zig").JSArrayIterator;
|
||||
pub const ContiguousArrayView = @import("./bindings/ContiguousArrayView.zig").ContiguousArrayView;
|
||||
pub const JSCell = @import("./bindings/JSCell.zig").JSCell;
|
||||
pub const JSFunction = @import("./bindings/JSFunction.zig").JSFunction;
|
||||
pub const JSGlobalObject = @import("./bindings/JSGlobalObject.zig").JSGlobalObject;
|
||||
|
||||
@@ -3967,74 +3967,138 @@ fn fromJSWithoutDeferGC(
|
||||
},
|
||||
|
||||
.Array, .DerivedArray => {
|
||||
var iter = try jsc.JSArrayIterator.init(current, global);
|
||||
try stack.ensureUnusedCapacity(iter.len);
|
||||
var any_arrays = false;
|
||||
while (try iter.next()) |item| {
|
||||
if (item.isUndefinedOrNull()) continue;
|
||||
if (jsc.ContiguousArrayView.init(current, global)) |view_init| {
|
||||
// Fast path: direct butterfly memory access
|
||||
var fast_view = view_init;
|
||||
try stack.ensureUnusedCapacity(fast_view.len);
|
||||
var any_arrays = false;
|
||||
while (fast_view.next()) |item| {
|
||||
if (item.isUndefinedOrNull()) continue;
|
||||
|
||||
// When it's a string or ArrayBuffer inside an array, we can avoid the extra push/pop
|
||||
// we only really want this for nested arrays
|
||||
// However, we must preserve the order
|
||||
// That means if there are any arrays
|
||||
// we have to restart the loop
|
||||
if (!any_arrays) {
|
||||
switch (item.jsTypeLoose()) {
|
||||
.NumberObject,
|
||||
.Cell,
|
||||
.String,
|
||||
.StringObject,
|
||||
.DerivedStringObject,
|
||||
=> {
|
||||
var sliced = try item.toSlice(global, bun.default_allocator);
|
||||
const allocator = sliced.allocator.get();
|
||||
could_have_non_ascii = could_have_non_ascii or !sliced.allocator.isWTFAllocator();
|
||||
joiner.push(sliced.slice(), allocator);
|
||||
continue;
|
||||
},
|
||||
.ArrayBuffer,
|
||||
.Int8Array,
|
||||
.Uint8Array,
|
||||
.Uint8ClampedArray,
|
||||
.Int16Array,
|
||||
.Uint16Array,
|
||||
.Int32Array,
|
||||
.Uint32Array,
|
||||
.Float16Array,
|
||||
.Float32Array,
|
||||
.Float64Array,
|
||||
.BigInt64Array,
|
||||
.BigUint64Array,
|
||||
.DataView,
|
||||
=> {
|
||||
could_have_non_ascii = true;
|
||||
var buf = item.asArrayBuffer(global).?;
|
||||
joiner.pushStatic(buf.byteSlice());
|
||||
continue;
|
||||
},
|
||||
.Array, .DerivedArray => {
|
||||
any_arrays = true;
|
||||
could_have_non_ascii = true;
|
||||
break;
|
||||
},
|
||||
|
||||
.DOMWrapper => {
|
||||
if (item.as(Blob)) |blob| {
|
||||
could_have_non_ascii = could_have_non_ascii or blob.charset != .all_ascii;
|
||||
joiner.pushStatic(blob.sharedView());
|
||||
continue;
|
||||
} else {
|
||||
const sliced = try current.toSliceClone(global);
|
||||
if (!any_arrays) {
|
||||
switch (item.jsTypeLoose()) {
|
||||
.NumberObject,
|
||||
.Cell,
|
||||
.String,
|
||||
.StringObject,
|
||||
.DerivedStringObject,
|
||||
=> {
|
||||
var sliced = try item.toSlice(global, bun.default_allocator);
|
||||
const allocator = sliced.allocator.get();
|
||||
could_have_non_ascii = could_have_non_ascii or allocator != null;
|
||||
could_have_non_ascii = could_have_non_ascii or !sliced.allocator.isWTFAllocator();
|
||||
joiner.push(sliced.slice(), allocator);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
continue;
|
||||
},
|
||||
.ArrayBuffer,
|
||||
.Int8Array,
|
||||
.Uint8Array,
|
||||
.Uint8ClampedArray,
|
||||
.Int16Array,
|
||||
.Uint16Array,
|
||||
.Int32Array,
|
||||
.Uint32Array,
|
||||
.Float16Array,
|
||||
.Float32Array,
|
||||
.Float64Array,
|
||||
.BigInt64Array,
|
||||
.BigUint64Array,
|
||||
.DataView,
|
||||
=> {
|
||||
could_have_non_ascii = true;
|
||||
var buf = item.asArrayBuffer(global).?;
|
||||
joiner.pushStatic(buf.byteSlice());
|
||||
continue;
|
||||
},
|
||||
.Array, .DerivedArray => {
|
||||
any_arrays = true;
|
||||
could_have_non_ascii = true;
|
||||
break;
|
||||
},
|
||||
|
||||
stack.appendAssumeCapacity(item);
|
||||
.DOMWrapper => {
|
||||
if (item.as(Blob)) |blob| {
|
||||
could_have_non_ascii = could_have_non_ascii or blob.charset != .all_ascii;
|
||||
joiner.pushStatic(blob.sharedView());
|
||||
continue;
|
||||
} else {
|
||||
const sliced = try current.toSliceClone(global);
|
||||
const allocator = sliced.allocator.get();
|
||||
could_have_non_ascii = could_have_non_ascii or allocator != null;
|
||||
joiner.push(sliced.slice(), allocator);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
stack.appendAssumeCapacity(item);
|
||||
}
|
||||
} else {
|
||||
// Slow path fallback: use indexed access
|
||||
var iter = try jsc.JSArrayIterator.init(current, global);
|
||||
try stack.ensureUnusedCapacity(iter.len);
|
||||
var any_arrays = false;
|
||||
while (try iter.next()) |item| {
|
||||
if (item.isUndefinedOrNull()) continue;
|
||||
|
||||
if (!any_arrays) {
|
||||
switch (item.jsTypeLoose()) {
|
||||
.NumberObject,
|
||||
.Cell,
|
||||
.String,
|
||||
.StringObject,
|
||||
.DerivedStringObject,
|
||||
=> {
|
||||
var sliced = try item.toSlice(global, bun.default_allocator);
|
||||
const allocator = sliced.allocator.get();
|
||||
could_have_non_ascii = could_have_non_ascii or !sliced.allocator.isWTFAllocator();
|
||||
joiner.push(sliced.slice(), allocator);
|
||||
continue;
|
||||
},
|
||||
.ArrayBuffer,
|
||||
.Int8Array,
|
||||
.Uint8Array,
|
||||
.Uint8ClampedArray,
|
||||
.Int16Array,
|
||||
.Uint16Array,
|
||||
.Int32Array,
|
||||
.Uint32Array,
|
||||
.Float16Array,
|
||||
.Float32Array,
|
||||
.Float64Array,
|
||||
.BigInt64Array,
|
||||
.BigUint64Array,
|
||||
.DataView,
|
||||
=> {
|
||||
could_have_non_ascii = true;
|
||||
var buf = item.asArrayBuffer(global).?;
|
||||
joiner.pushStatic(buf.byteSlice());
|
||||
continue;
|
||||
},
|
||||
.Array, .DerivedArray => {
|
||||
any_arrays = true;
|
||||
could_have_non_ascii = true;
|
||||
break;
|
||||
},
|
||||
|
||||
.DOMWrapper => {
|
||||
if (item.as(Blob)) |blob| {
|
||||
could_have_non_ascii = could_have_non_ascii or blob.charset != .all_ascii;
|
||||
joiner.pushStatic(blob.sharedView());
|
||||
continue;
|
||||
} else {
|
||||
const sliced = try current.toSliceClone(global);
|
||||
const allocator = sliced.allocator.get();
|
||||
could_have_non_ascii = could_have_non_ascii or allocator != null;
|
||||
joiner.push(sliced.slice(), allocator);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
stack.appendAssumeCapacity(item);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1988,10 +1988,6 @@ pub const BundleV2 = struct {
|
||||
if (transpiler.options.compile) {
|
||||
// Emitting DCE annotations is nonsensical in --compile.
|
||||
transpiler.options.emit_dce_annotations = false;
|
||||
// Default to production mode for --compile builds to enable dead code elimination
|
||||
// for conditional requires like React's process.env.NODE_ENV checks.
|
||||
// Users can override this with define: { 'process.env.NODE_ENV': '"development"' }
|
||||
try transpiler.env.map.put("NODE_ENV", "production");
|
||||
}
|
||||
|
||||
transpiler.configureLinker();
|
||||
@@ -2000,12 +1996,6 @@ pub const BundleV2 = struct {
|
||||
if (!transpiler.options.production) {
|
||||
try transpiler.options.conditions.appendSlice(&.{"development"});
|
||||
}
|
||||
|
||||
// Allow tsconfig.json overriding, but always set it to false for --compile builds.
|
||||
if (transpiler.options.compile) {
|
||||
transpiler.options.jsx.development = false;
|
||||
}
|
||||
|
||||
transpiler.resolver.env_loader = transpiler.env;
|
||||
transpiler.resolver.opts = transpiler.options;
|
||||
}
|
||||
|
||||
@@ -196,10 +196,7 @@ pub const BuildCommand = struct {
|
||||
this_transpiler.options.env.behavior = ctx.bundler_options.env_behavior;
|
||||
this_transpiler.options.env.prefix = ctx.bundler_options.env_prefix;
|
||||
|
||||
// Default to production mode for --compile builds to enable dead code elimination
|
||||
// for conditional requires like React's process.env.NODE_ENV checks.
|
||||
// Users can override this with --define 'process.env.NODE_ENV="development"'
|
||||
if (ctx.bundler_options.production or ctx.bundler_options.compile) {
|
||||
if (ctx.bundler_options.production) {
|
||||
try this_transpiler.env.map.put("NODE_ENV", "production");
|
||||
}
|
||||
|
||||
@@ -213,8 +210,8 @@ pub const BuildCommand = struct {
|
||||
this_transpiler.resolver.opts = this_transpiler.options;
|
||||
this_transpiler.resolver.env_loader = this_transpiler.env;
|
||||
|
||||
// Allow tsconfig.json overriding, but always set it to false if --production or --compile is passed.
|
||||
if (ctx.bundler_options.production or ctx.bundler_options.compile) {
|
||||
// Allow tsconfig.json overriding, but always set it to false if --production is passed.
|
||||
if (ctx.bundler_options.production) {
|
||||
this_transpiler.options.jsx.development = false;
|
||||
this_transpiler.resolver.opts.jsx.development = false;
|
||||
}
|
||||
|
||||
121
test/js/web/fetch/blob-array-fast-path.test.ts
Normal file
121
test/js/web/fetch/blob-array-fast-path.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test("basic string array", async () => {
|
||||
const blob = new Blob(["hello", " ", "world"]);
|
||||
expect(await blob.text()).toBe("hello world");
|
||||
});
|
||||
|
||||
test("large array (10000 elements)", async () => {
|
||||
const parts = Array.from({ length: 10000 }, (_, i) => `${i},`);
|
||||
const blob = new Blob(parts);
|
||||
const text = await blob.text();
|
||||
expect(text).toBe(parts.join(""));
|
||||
});
|
||||
|
||||
test("array with holes is handled", async () => {
|
||||
const arr = ["a", , "b", , "c"] as unknown as string[];
|
||||
const blob = new Blob(arr);
|
||||
// holes become undefined which are skipped
|
||||
expect(await blob.text()).toBe("abc");
|
||||
});
|
||||
|
||||
test("undefined and null elements are skipped", async () => {
|
||||
const blob = new Blob(["start", undefined as any, null as any, "end"]);
|
||||
expect(await blob.text()).toBe("startend");
|
||||
});
|
||||
|
||||
test("Proxy array is rejected", async () => {
|
||||
const arr = new Proxy(["a", "b", "c"], {
|
||||
get(target, prop) {
|
||||
return Reflect.get(target, prop);
|
||||
},
|
||||
});
|
||||
expect(() => new Blob(arr as any)).toThrow("new Blob() expects an Array");
|
||||
});
|
||||
|
||||
test("prototype getter causes slow path fallback", async () => {
|
||||
const arr = ["x", "y", "z"];
|
||||
Object.defineProperty(Array.prototype, "1000", {
|
||||
get() {
|
||||
return "intercepted";
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
try {
|
||||
const blob = new Blob(arr);
|
||||
expect(await blob.text()).toBe("xyz");
|
||||
} finally {
|
||||
delete (Array.prototype as any)["1000"];
|
||||
}
|
||||
});
|
||||
|
||||
test("nested arrays in blob parts", async () => {
|
||||
// Nested arrays are not valid BlobParts per spec; elements before
|
||||
// the nested array are processed inline
|
||||
const blob = new Blob(["before", ["a", "b"] as any, "after"]);
|
||||
expect(await blob.text()).toBe("before");
|
||||
});
|
||||
|
||||
test("mixed types: string + TypedArray + Blob", async () => {
|
||||
const innerBlob = new Blob(["inner"]);
|
||||
const arr = ["start-", new Uint8Array([65, 66, 67]), innerBlob, "-end"];
|
||||
const blob = new Blob(arr as any);
|
||||
expect(await blob.text()).toBe("start-ABCinner-end");
|
||||
});
|
||||
|
||||
test("toString side effects with custom objects", async () => {
|
||||
const order: number[] = [];
|
||||
const items = [1, 2, 3].map(n => ({
|
||||
toString() {
|
||||
order.push(n);
|
||||
return `item${n}`;
|
||||
},
|
||||
}));
|
||||
const blob = new Blob(items as any);
|
||||
// Objects with toString are processed via stack (LIFO order)
|
||||
expect(await blob.text()).toBe("item3item2item1");
|
||||
expect(order).toEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
test("empty array", async () => {
|
||||
const blob = new Blob([]);
|
||||
expect(blob.size).toBe(0);
|
||||
expect(await blob.text()).toBe("");
|
||||
});
|
||||
|
||||
test("DerivedArray (class extending Array)", async () => {
|
||||
class MyArray extends Array {
|
||||
constructor(...items: any[]) {
|
||||
super(...items);
|
||||
}
|
||||
}
|
||||
const arr = new MyArray("hello", " ", "derived");
|
||||
const blob = new Blob(arr);
|
||||
expect(await blob.text()).toBe("hello derived");
|
||||
});
|
||||
|
||||
test("COW (Copy-on-Write) array from literal", async () => {
|
||||
// Array literals may start as COW in JSC
|
||||
const blob = new Blob(["cow", "test"]);
|
||||
expect(await blob.text()).toBe("cowtest");
|
||||
});
|
||||
|
||||
test("frozen array works correctly", async () => {
|
||||
const arr = Object.freeze(["frozen", "-", "array"]);
|
||||
const blob = new Blob(arr as any);
|
||||
expect(await blob.text()).toBe("frozen-array");
|
||||
});
|
||||
|
||||
test("sparse array (ArrayStorage) uses slow path correctly", async () => {
|
||||
const arr: string[] = [];
|
||||
arr[0] = "first";
|
||||
arr[100] = "last";
|
||||
const blob = new Blob(arr);
|
||||
const text = await blob.text();
|
||||
expect(text).toBe("firstlast");
|
||||
});
|
||||
|
||||
test("single-element array optimization", async () => {
|
||||
const blob = new Blob(["only"]);
|
||||
expect(await blob.text()).toBe("only");
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, isWindows, tempDir } from "harness";
|
||||
import { join } from "path";
|
||||
|
||||
// https://github.com/oven-sh/bun/issues/26244
|
||||
// bun build --compile should default NODE_ENV to production for dead code elimination
|
||||
describe("Issue #26244", () => {
|
||||
test("--compile defaults NODE_ENV to production (CLI)", async () => {
|
||||
using dir = tempDir("compile-node-env-cli", {
|
||||
// This simulates React's conditional require pattern
|
||||
"index.js": `
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./prod.js');
|
||||
} else {
|
||||
module.exports = require('./dev.js');
|
||||
}
|
||||
`,
|
||||
"prod.js": `module.exports = { mode: "production" };`,
|
||||
// Note: dev.js intentionally not created to simulate Next.js standalone output
|
||||
// where development files are stripped
|
||||
});
|
||||
|
||||
const outfile = join(dir + "", isWindows ? "app.exe" : "app");
|
||||
|
||||
// This should succeed because NODE_ENV defaults to production,
|
||||
// enabling dead code elimination of the dev.js branch
|
||||
const buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "--compile", join(dir + "", "index.js"), "--outfile", outfile],
|
||||
cwd: dir + "",
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
|
||||
new Response(buildProc.stdout).text(),
|
||||
new Response(buildProc.stderr).text(),
|
||||
buildProc.exited,
|
||||
]);
|
||||
|
||||
// Build should succeed - the dead branch with dev.js should be eliminated
|
||||
expect(buildStderr).not.toContain("Could not resolve");
|
||||
expect(buildExitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("--compile defaults NODE_ENV to production (API)", async () => {
|
||||
using dir = tempDir("compile-node-env-api", {
|
||||
// This simulates React's conditional require pattern
|
||||
"index.js": `
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./prod.js');
|
||||
} else {
|
||||
module.exports = require('./dev.js');
|
||||
}
|
||||
`,
|
||||
"prod.js": `module.exports = { mode: "production" };`,
|
||||
// Note: dev.js intentionally not created to simulate Next.js standalone output
|
||||
// where development files are stripped
|
||||
});
|
||||
|
||||
const outfile = join(dir + "", isWindows ? "app.exe" : "app");
|
||||
|
||||
// This should succeed because NODE_ENV defaults to production,
|
||||
// enabling dead code elimination of the dev.js branch
|
||||
const result = await Bun.build({
|
||||
entrypoints: [join(dir + "", "index.js")],
|
||||
compile: {
|
||||
outfile,
|
||||
},
|
||||
});
|
||||
|
||||
// Build should succeed - the dead branch with dev.js should be eliminated
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.outputs.length).toBe(1);
|
||||
});
|
||||
|
||||
test("--compile with conditional require eliminates dead branch (CLI)", async () => {
|
||||
using dir = tempDir("compile-dead-code-cli", {
|
||||
"entry.js": `
|
||||
// This is the pattern used by React
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.log("Using production build");
|
||||
} else {
|
||||
// This branch references a non-existent file
|
||||
// and should be eliminated by dead code elimination
|
||||
require('./non-existent-dev-file.js');
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
const outfile = join(dir + "", isWindows ? "app.exe" : "app");
|
||||
|
||||
// Should succeed - the require('./non-existent-dev-file.js') should be
|
||||
// eliminated because NODE_ENV defaults to 'production'
|
||||
const buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "--compile", join(dir + "", "entry.js"), "--outfile", outfile],
|
||||
cwd: dir + "",
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
|
||||
new Response(buildProc.stdout).text(),
|
||||
new Response(buildProc.stderr).text(),
|
||||
buildProc.exited,
|
||||
]);
|
||||
|
||||
expect(buildStderr).not.toContain("Could not resolve");
|
||||
expect(buildExitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("--compile can override NODE_ENV with --define", async () => {
|
||||
using dir = tempDir("compile-define-override", {
|
||||
"entry.js": `console.log(process.env.NODE_ENV);`,
|
||||
});
|
||||
|
||||
const outfile = join(dir + "", isWindows ? "app.exe" : "app");
|
||||
|
||||
// Use CLI to test --define override
|
||||
const buildProc = Bun.spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"build",
|
||||
"--compile",
|
||||
join(dir + "", "entry.js"),
|
||||
"--outfile",
|
||||
outfile,
|
||||
"--define",
|
||||
'process.env.NODE_ENV="development"',
|
||||
],
|
||||
cwd: dir + "",
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
|
||||
new Response(buildProc.stdout).text(),
|
||||
new Response(buildProc.stderr).text(),
|
||||
buildProc.exited,
|
||||
]);
|
||||
|
||||
expect(buildExitCode).toBe(0);
|
||||
|
||||
// Run the compiled binary
|
||||
const runProc = Bun.spawn({
|
||||
cmd: [outfile],
|
||||
cwd: dir + "",
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(runProc.stdout).text(),
|
||||
new Response(runProc.stderr).text(),
|
||||
runProc.exited,
|
||||
]);
|
||||
|
||||
expect(stdout.trim()).toBe("development");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user