mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 13:51:47 +00:00
Compare commits
3 Commits
claude/fix
...
claude/ref
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0676364be0 | ||
|
|
b40b6c2c40 | ||
|
|
ed26bb8231 |
317
REFACTORING_STATUS.md
Normal file
317
REFACTORING_STATUS.md
Normal file
@@ -0,0 +1,317 @@
|
||||
# SourceProvider Refactoring - Implementation Status
|
||||
|
||||
## Overview
|
||||
Refactoring `ResolvedSource` and `ZigSourceProvider` to eliminate complexity, reduce memory usage, and clarify ownership.
|
||||
|
||||
**Goal**: Reduce from 12-field struct to focused 5-field types with type-safe unions.
|
||||
|
||||
## Phase 1: Infrastructure ✅ COMPLETE
|
||||
|
||||
### Completed Work
|
||||
All new infrastructure files have been created and the build passes with existing tests:
|
||||
|
||||
1. **Created New Zig Types**:
|
||||
- `src/bun.js/bindings/TranspiledSource.zig` - Minimal 5-field POD struct for transpiled code
|
||||
- `src/bun.js/bindings/SpecialModule.zig` - Handle special cases (exports objects, custom extensions)
|
||||
- `src/bun.js/bindings/ModuleResult.zig` - Tagged union return type
|
||||
|
||||
2. **Created New C++ SourceProvider**:
|
||||
- `src/bun.js/bindings/BunSourceProvider.h` - Simplified SourceProvider header
|
||||
- `src/bun.js/bindings/BunSourceProvider.cpp` - Implementation with `Bun__createSourceProvider` C bridge
|
||||
|
||||
3. **Verification**:
|
||||
- Build succeeds: `bun bd` ✅
|
||||
- Tests pass: `bun bd test test/regression/issue23966.test.ts` ✅
|
||||
- All new code coexists with old code during migration
|
||||
|
||||
### Key Implementation Details
|
||||
|
||||
**TranspiledSource** (5 fields vs 12 in ResolvedSource):
|
||||
```zig
|
||||
pub const TranspiledSource = extern struct {
|
||||
source_code: bun.String,
|
||||
source_url: bun.String,
|
||||
bytecode_cache: ?[*]u8,
|
||||
bytecode_cache_len: usize,
|
||||
flags: Flags,
|
||||
};
|
||||
```
|
||||
|
||||
**ModuleResult** (Tagged union for type safety):
|
||||
```zig
|
||||
pub const ModuleResult = extern struct {
|
||||
tag: Tag, // transpiled | special | builtin
|
||||
value: extern union {
|
||||
transpiled: TranspiledSource,
|
||||
special: SpecialModule,
|
||||
builtin_id: u32,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**BunSourceProvider C Bridge**:
|
||||
```cpp
|
||||
extern "C" JSC::SourceProvider* Bun__createSourceProvider(
|
||||
Zig::GlobalObject* globalObject,
|
||||
const TranspiledSource* source
|
||||
)
|
||||
```
|
||||
|
||||
## Phase 2: Update Zig Side - 🚧 TODO
|
||||
|
||||
### Functions to Modify
|
||||
|
||||
Based on codebase exploration, the following functions need updates:
|
||||
|
||||
#### 1. `transpileSourceCode()` - `/workspace/bun/src/bun.js/ModuleLoader.zig:820-1539`
|
||||
|
||||
**Current signature**:
|
||||
```zig
|
||||
pub fn transpileSourceCode(
|
||||
jsc_vm: *VirtualMachine,
|
||||
specifier: string,
|
||||
referrer: string,
|
||||
input_specifier: String,
|
||||
path: Fs.Path,
|
||||
loader: options.Loader,
|
||||
module_type: options.ModuleType,
|
||||
log: *logger.Log,
|
||||
virtual_source: ?*const logger.Source,
|
||||
promise_ptr: ?*?*jsc.JSInternalPromise,
|
||||
source_code_printer: *js_printer.BufferPrinter,
|
||||
globalObject: ?*JSGlobalObject,
|
||||
comptime flags: FetchFlags,
|
||||
) !ResolvedSource // ← Change to !ModuleResult
|
||||
```
|
||||
|
||||
**Changes needed**:
|
||||
- Change return type from `!ResolvedSource` to `!ModuleResult`
|
||||
- Update all return statements to use ModuleResult tagged unions:
|
||||
- Normal transpilation → `ModuleResult{ .tag = .transpiled, .value = .{ .transpiled = TranspiledSource{...} } }`
|
||||
- JSON/TOML/YAML → `ModuleResult{ .tag = .special, .value = .{ .special = SpecialModule{...} } }`
|
||||
- HTML/assets → `ModuleResult{ .tag = .special, .value = .{ .special = SpecialModule{...} } }`
|
||||
|
||||
**Key return paths to update** (based on loader type):
|
||||
- Lines 1076-1083: JSON files → `.special` with `.exports_object`
|
||||
- Lines 1099-1117: JSONC/TOML/YAML → `.special` with `.exports_object`
|
||||
- Lines 1288-1304: Normal transpilation → `.transpiled`
|
||||
- Lines 1390-1430: SQLite → `.transpiled` (generated code)
|
||||
- Lines 1432-1455: HTML → `.special` with `.export_default_object`
|
||||
- Lines 1457-1537: Static assets → `.special` with `.export_default_object`
|
||||
|
||||
#### 2. `fetchBuiltinModule()` - Location TBD
|
||||
|
||||
**Changes needed**:
|
||||
- Return `ModuleResult{ .tag = .builtin, .value = .{ .builtin_id = @intFromEnum(hardcoded) } }`
|
||||
- Instead of returning full module source, just return the builtin ID
|
||||
- C++ will look up the module in InternalModuleRegistry
|
||||
|
||||
#### 3. `TranspilerJob` - `/workspace/bun/src/bun.js/ModuleLoader.zig:2215-2605`
|
||||
|
||||
**Current structure**:
|
||||
```zig
|
||||
pub const TranspilerJob = struct {
|
||||
// ...
|
||||
resolved_source: ResolvedSource = ResolvedSource{}, // ← Change to ModuleResult
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
**Changes needed in `run()` (worker thread - line 2312)**:
|
||||
- Change `this.resolved_source` type from `ResolvedSource` to `ModuleResult`
|
||||
- Update assignments (lines 2598-2603) to create `ModuleResult.transpiled`:
|
||||
```zig
|
||||
this.resolved_source = ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = source_code,
|
||||
.source_url = bun.String.empty, // Set on main thread
|
||||
.flags = .{
|
||||
.is_commonjs = parse_result.ast.has_commonjs_export_names,
|
||||
},
|
||||
}},
|
||||
};
|
||||
```
|
||||
|
||||
**Changes needed in `runFromJSThread()` (main thread - line 2267)**:
|
||||
- Update to pass `ModuleResult*` instead of `ResolvedSource*`
|
||||
- Set source_url on main thread if needed
|
||||
|
||||
#### 4. `AsyncModule` - `/workspace/bun/src/bun.js/ModuleLoader.zig:69+`
|
||||
|
||||
**Functions to update**:
|
||||
|
||||
**`resumeLoadingModule()` - Returns transpilation result**:
|
||||
- Change return type from `!ResolvedSource` to `!ModuleResult`
|
||||
- Update return statement to create `ModuleResult.transpiled`
|
||||
|
||||
**`fulfill()` - Accepts result and fulfills promise**:
|
||||
- Change signature from accepting `ResolvedSource*` to `ModuleResult*`
|
||||
- Pass to C++ `Bun__onFulfillAsyncModule` with new type
|
||||
|
||||
### Call Sites to Update
|
||||
|
||||
All callers of these functions need updates:
|
||||
- Search for `transpileSourceCode` calls
|
||||
- Search for `AsyncModule.fulfill` calls
|
||||
- Search for `TranspilerJob` usage
|
||||
|
||||
## Phase 3: Update C++ Side - 🚧 TODO
|
||||
|
||||
### Functions to Modify
|
||||
|
||||
#### 1. `Bun__onFulfillAsyncModule` - `/workspace/bun/src/bun.js/bindings/ModuleLoader.cpp`
|
||||
|
||||
**Current signature**:
|
||||
```cpp
|
||||
extern "C" void Bun__onFulfillAsyncModule(
|
||||
Zig::GlobalObject* globalObject,
|
||||
JSC::EncodedJSValue encodedPromiseValue,
|
||||
ErrorableResolvedSource* result, // ← Change to ModuleResult*
|
||||
BunString* specifier,
|
||||
BunString* referrer)
|
||||
```
|
||||
|
||||
**Changes needed**:
|
||||
```cpp
|
||||
switch (result->tag) {
|
||||
case ModuleResult::Tag::transpiled: {
|
||||
auto* provider = Bun__createSourceProvider(globalObject, &result->value.transpiled);
|
||||
// Handle CommonJS vs ESM...
|
||||
break;
|
||||
}
|
||||
case ModuleResult::Tag::special: {
|
||||
// Should not reach AsyncModule path (synchronous)
|
||||
throwTypeError(...);
|
||||
break;
|
||||
}
|
||||
case ModuleResult::Tag::builtin: {
|
||||
// Should not reach AsyncModule path
|
||||
throwTypeError(...);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `fetchESMSourceCode` - `/workspace/bun/src/bun.js/bindings/ModuleLoader.cpp`
|
||||
|
||||
**Changes needed**:
|
||||
```cpp
|
||||
template<bool allowPromise>
|
||||
static JSValue fetchESMSourceCode(
|
||||
Zig::GlobalObject* globalObject,
|
||||
JSC::JSString* specifierJS,
|
||||
ModuleResult* result, // ← Changed type
|
||||
BunString* specifier,
|
||||
BunString* referrer,
|
||||
BunString* typeAttribute)
|
||||
{
|
||||
switch (result->tag) {
|
||||
case ModuleResult::Tag::transpiled:
|
||||
// Use Bun__createSourceProvider
|
||||
break;
|
||||
case ModuleResult::Tag::special:
|
||||
// Handle exports_object, export_default_object, custom_extension
|
||||
break;
|
||||
case ModuleResult::Tag::builtin:
|
||||
// Return builtin module from InternalModuleRegistry
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. `fetchCommonJSModule` - Similar switch-based handling
|
||||
|
||||
#### 4. `Bun__transpileFile` - Update to return `ModuleResult*`
|
||||
|
||||
All the wrapper functions that call into Zig need their return types updated.
|
||||
|
||||
## Phase 4: Cleanup - 🚧 TODO
|
||||
|
||||
### Files to Remove/Modify
|
||||
|
||||
1. **Delete**:
|
||||
- Most of `src/bun.js/bindings/ZigSourceProvider.cpp` (keep only helper functions like `toSourceOrigin`)
|
||||
- `ResolvedSourceCodeHolder` class from ModuleLoader.cpp
|
||||
|
||||
2. **Update**:
|
||||
- `src/bun.js/bindings/ResolvedSource.zig` → Remove or mark deprecated
|
||||
- `src/bun.js/bindings/ZigSourceProvider.h` → Slim down or remove
|
||||
- Remove all references to old types throughout codebase
|
||||
|
||||
3. **Fields to remove from structs**:
|
||||
- `allocator` - Not needed, ownership is clear
|
||||
- `source_code_needs_deref` - SourceProvider handles it
|
||||
- `cjs_custom_extension_index` - Moved to SpecialModule
|
||||
- `jsvalue_for_export` - Moved to SpecialModule
|
||||
|
||||
## Phase 5: Testing & Verification - 🚧 TODO
|
||||
|
||||
### Test Areas
|
||||
|
||||
1. **Normal module loading**: `bun bd test test/js/bun/http/serve.test.ts`
|
||||
2. **CommonJS**: `bun bd test test/js/node/fs.test.ts`
|
||||
3. **Module resolution**: `bun bd test test/js/bun/resolve/`
|
||||
4. **Plugins**: `bun bd test test/js/bun/plugin/`
|
||||
5. **Bytecode**: `bun bd test test/bundler/bundler_compile.test.ts`
|
||||
6. **Coverage**: `bun bd test test/js/bun/test/coverage.test.ts`
|
||||
7. **Full test suite**: `bun bd test`
|
||||
|
||||
### Performance Checks
|
||||
|
||||
- Benchmark before/after with `@babel/standalone` load time
|
||||
- Memory usage analysis
|
||||
- Profile hot paths
|
||||
|
||||
### Memory Safety
|
||||
|
||||
- Run with ASAN/valgrind
|
||||
- Check ref-counts carefully
|
||||
- Test long-running processes
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Key Insights from Codebase Exploration
|
||||
|
||||
1. **transpileSourceCode is 720 lines** (line 820-1539 in ModuleLoader.zig)
|
||||
- Main switch on `loader` type with 8+ different code paths
|
||||
- Returns different tags based on file type (JSON, TOML, HTML, etc.)
|
||||
|
||||
2. **TranspilerJob runs on worker threads**
|
||||
- `run()` executes on worker, stores result in `resolved_source` field
|
||||
- `runFromJSThread()` executes on main thread, calls `AsyncModule.fulfill()`
|
||||
- Uses object pooling for performance
|
||||
|
||||
3. **AsyncModule handles package downloads**
|
||||
- Waits for package manager to download dependencies
|
||||
- Then calls `resumeLoadingModule()` to finish transpilation
|
||||
- Finally calls `fulfill()` to resolve the JS promise
|
||||
|
||||
### Migration Strategy
|
||||
|
||||
The plan explicitly calls for **backward compatibility during migration**:
|
||||
- Old code (ResolvedSource) and new code (ModuleResult) coexist
|
||||
- Phase 1 creates new infrastructure ✅ DONE
|
||||
- Phases 2-3 migrate to use new types
|
||||
- Phase 4 removes old code once everything is migrated
|
||||
|
||||
This allows for incremental testing and reduces risk.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Phase 2**: Update Zig-side functions to return/use ModuleResult
|
||||
2. **Phase 3**: Update C++-side functions to handle ModuleResult
|
||||
3. **Phase 4**: Remove old ResolvedSource code
|
||||
4. **Phase 5**: Comprehensive testing
|
||||
|
||||
## Git Branch
|
||||
|
||||
- Branch: `claude/refactor-source-provider`
|
||||
- Pushed to: `origin/claude/refactor-source-provider`
|
||||
- Current status: Phase 1 complete, ready for Phase 2
|
||||
|
||||
## References
|
||||
|
||||
- Original plan: `/tmp/PLAN.md`
|
||||
- Main implementation file: `src/bun.js/ModuleLoader.zig` (2600+ lines)
|
||||
- C++ module loader: `src/bun.js/bindings/ModuleLoader.cpp`
|
||||
@@ -426,7 +426,7 @@ pub const AsyncModule = struct {
|
||||
var errorable: jsc.ErrorableResolvedSource = undefined;
|
||||
this.poll_ref.unref(jsc_vm);
|
||||
outer: {
|
||||
errorable = jsc.ErrorableResolvedSource.ok(this.resumeLoadingModule(&log) catch |err| {
|
||||
const module_result = this.resumeLoadingModule(&log) catch |err| {
|
||||
switch (err) {
|
||||
error.JSError => {
|
||||
errorable = .err(error.JSError, this.globalThis.takeError(error.JSError));
|
||||
@@ -444,7 +444,9 @@ pub const AsyncModule = struct {
|
||||
break :outer;
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
const resolved_source = moduleResultToResolvedSource(module_result, bun.String.init(this.specifier));
|
||||
errorable = jsc.ErrorableResolvedSource.ok(resolved_source);
|
||||
}
|
||||
|
||||
var spec = bun.String.init(ZigString.init(this.specifier).withEncoding());
|
||||
@@ -463,7 +465,7 @@ pub const AsyncModule = struct {
|
||||
pub fn fulfill(
|
||||
globalThis: *JSGlobalObject,
|
||||
promise: JSValue,
|
||||
resolved_source: *ResolvedSource,
|
||||
module_result: *ModuleResult,
|
||||
err: ?anyerror,
|
||||
specifier_: bun.String,
|
||||
referrer_: bun.String,
|
||||
@@ -480,6 +482,9 @@ pub const AsyncModule = struct {
|
||||
scope.deinit();
|
||||
}
|
||||
|
||||
// Convert ModuleResult to ResolvedSource for C++ until C++ is updated
|
||||
var resolved_source = moduleResultToResolvedSource(module_result.*, specifier);
|
||||
|
||||
var errorable: jsc.ErrorableResolvedSource = undefined;
|
||||
if (err) |e| {
|
||||
defer {
|
||||
@@ -502,7 +507,7 @@ pub const AsyncModule = struct {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
errorable = jsc.ErrorableResolvedSource.ok(resolved_source.*);
|
||||
errorable = jsc.ErrorableResolvedSource.ok(resolved_source);
|
||||
}
|
||||
log.deinit();
|
||||
|
||||
@@ -704,7 +709,7 @@ pub const AsyncModule = struct {
|
||||
promise.rejectAsHandled(globalThis, error_instance);
|
||||
}
|
||||
|
||||
pub fn resumeLoadingModule(this: *AsyncModule, log: *logger.Log) !ResolvedSource {
|
||||
pub fn resumeLoadingModule(this: *AsyncModule, log: *logger.Log) !ModuleResult {
|
||||
debug("resumeLoadingModule: {s}", .{this.specifier});
|
||||
var parse_result = this.parse_result;
|
||||
const path = this.path;
|
||||
@@ -755,7 +760,7 @@ pub const AsyncModule = struct {
|
||||
}
|
||||
|
||||
if (jsc_vm.isWatcherEnabled()) {
|
||||
var resolved_source = jsc_vm.refCountedResolvedSource(printer.ctx.written, bun.String.init(specifier), path.text, null, false);
|
||||
const resolved_source = jsc_vm.refCountedResolvedSource(printer.ctx.written, bun.String.init(specifier), path.text, null, false);
|
||||
|
||||
if (parse_result.input_fd) |fd_| {
|
||||
if (std.fs.path.isAbsolute(path.text) and !strings.contains(path.text, "node_modules")) {
|
||||
@@ -771,17 +776,33 @@ pub const AsyncModule = struct {
|
||||
}
|
||||
}
|
||||
|
||||
resolved_source.is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs;
|
||||
const is_commonjs = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs;
|
||||
|
||||
return resolved_source;
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{
|
||||
.transpiled = .{
|
||||
.source_code = resolved_source.source_code,
|
||||
.source_url = resolved_source.source_url,
|
||||
.flags = .{
|
||||
.is_commonjs = is_commonjs,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.cloneLatin1(printer.ctx.getWritten()),
|
||||
.specifier = String.init(specifier),
|
||||
.source_url = String.init(path.text),
|
||||
.is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs,
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{
|
||||
.transpiled = .{
|
||||
.source_code = bun.String.cloneLatin1(printer.ctx.getWritten()),
|
||||
.source_url = String.init(path.text),
|
||||
.flags = .{
|
||||
.is_commonjs = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -831,17 +852,18 @@ pub fn transpileSourceCode(
|
||||
source_code_printer: *js_printer.BufferPrinter,
|
||||
globalObject: ?*JSGlobalObject,
|
||||
comptime flags: FetchFlags,
|
||||
) !ResolvedSource {
|
||||
) !ModuleResult {
|
||||
const disable_transpilying = comptime flags.disableTranspiling();
|
||||
|
||||
if (comptime disable_transpilying) {
|
||||
if (!(loader.isJavaScriptLike() or loader == .toml or loader == .yaml or loader == .text or loader == .json or loader == .jsonc)) {
|
||||
// Don't print "export default <file path>"
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.empty,
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = bun.String.empty,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
} },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1074,59 +1096,63 @@ pub fn transpileSourceCode(
|
||||
}
|
||||
|
||||
if (loader == .json) {
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.cloneUTF8(source.contents),
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.tag = ResolvedSource.Tag.json_for_object_loader,
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = bun.String.cloneUTF8(source.contents),
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
if (comptime disable_transpilying) {
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = switch (comptime flags) {
|
||||
.print_source_and_clone => bun.String.init(jsc_vm.allocator.dupe(u8, source.contents) catch unreachable),
|
||||
.print_source => bun.String.init(source.contents),
|
||||
else => @compileError("unreachable"),
|
||||
},
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = switch (comptime flags) {
|
||||
.print_source_and_clone => bun.String.init(jsc_vm.allocator.dupe(u8, source.contents) catch unreachable),
|
||||
.print_source => bun.String.init(source.contents),
|
||||
else => @compileError("unreachable"),
|
||||
},
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
if (loader == .json or loader == .jsonc or loader == .toml or loader == .yaml) {
|
||||
if (parse_result.empty) {
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.jsvalue_for_export = JSValue.createEmptyObject(jsc_vm.global, 0),
|
||||
.tag = .exports_object,
|
||||
return ModuleResult{
|
||||
.tag = .special,
|
||||
.value = .{ .special = SpecialModule{
|
||||
.tag = .exports_object,
|
||||
.jsvalue = JSValue.createEmptyObject(jsc_vm.global, 0),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.jsvalue_for_export = parse_result.ast.parts.at(0).stmts[0].data.s_expr.value.toJS(allocator, globalObject orelse jsc_vm.global) catch |e| panic("Unexpected JS error: {s}", .{@errorName(e)}),
|
||||
.tag = .exports_object,
|
||||
return ModuleResult{
|
||||
.tag = .special,
|
||||
.value = .{ .special = SpecialModule{
|
||||
.tag = .exports_object,
|
||||
.jsvalue = parse_result.ast.parts.at(0).stmts[0].data.s_expr.value.toJS(allocator, globalObject orelse jsc_vm.global) catch |e| panic("Unexpected JS error: {s}", .{@errorName(e)}),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
if (parse_result.already_bundled != .none) {
|
||||
const bytecode_slice = parse_result.already_bundled.bytecodeSlice();
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.cloneLatin1(source.contents),
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.already_bundled = true,
|
||||
.bytecode_cache = if (bytecode_slice.len > 0) bytecode_slice.ptr else null,
|
||||
.bytecode_cache_size = bytecode_slice.len,
|
||||
.is_commonjs_module = parse_result.already_bundled.isCommonJS(),
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = bun.String.cloneLatin1(source.contents),
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.bytecode_cache = if (bytecode_slice.len > 0) bytecode_slice.ptr else null,
|
||||
.bytecode_cache_len = bytecode_slice.len,
|
||||
.flags = .{
|
||||
.is_commonjs = parse_result.already_bundled.isCommonJS(),
|
||||
.is_already_bundled = true,
|
||||
},
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1136,13 +1162,13 @@ pub fn transpileSourceCode(
|
||||
break :brk strings.eqlComptime(ext, ".cjs") or strings.eqlComptime(ext, ".cts");
|
||||
};
|
||||
if (was_cjs) {
|
||||
return .{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.static("(function(){})"),
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.is_commonjs_module = true,
|
||||
.tag = .javascript,
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = bun.String.static("(function(){})"),
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.flags = .{ .is_commonjs = true },
|
||||
} },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1157,37 +1183,21 @@ pub fn transpileSourceCode(
|
||||
dumpSourceString(jsc_vm, specifier, entry.output_code.byteSlice());
|
||||
}
|
||||
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = switch (entry.output_code) {
|
||||
.string => entry.output_code.string,
|
||||
.utf8 => brk: {
|
||||
const result = bun.String.cloneUTF8(entry.output_code.utf8);
|
||||
cache.output_code_allocator.free(entry.output_code.utf8);
|
||||
entry.output_code.utf8 = "";
|
||||
break :brk result;
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = switch (entry.output_code) {
|
||||
.string => entry.output_code.string,
|
||||
.utf8 => brk: {
|
||||
const result = bun.String.cloneUTF8(entry.output_code.utf8);
|
||||
cache.output_code_allocator.free(entry.output_code.utf8);
|
||||
entry.output_code.utf8 = "";
|
||||
break :brk result;
|
||||
},
|
||||
},
|
||||
},
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.is_commonjs_module = entry.metadata.module_type == .cjs,
|
||||
.tag = brk: {
|
||||
if (entry.metadata.module_type == .cjs and source.path.isFile()) {
|
||||
const actual_package_json: *PackageJSON = package_json orelse brk2: {
|
||||
// this should already be cached virtually always so it's fine to do this
|
||||
const dir_info = (jsc_vm.transpiler.resolver.readDirInfo(source.path.name.dir) catch null) orelse
|
||||
break :brk .javascript;
|
||||
|
||||
break :brk2 dir_info.package_json orelse dir_info.enclosing_package_json;
|
||||
} orelse break :brk .javascript;
|
||||
|
||||
if (actual_package_json.module_type == .esm) {
|
||||
break :brk ResolvedSource.Tag.package_json_type_module;
|
||||
}
|
||||
}
|
||||
|
||||
break :brk ResolvedSource.Tag.javascript;
|
||||
},
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.flags = .{ .is_commonjs = entry.metadata.module_type == .cjs },
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1267,40 +1277,32 @@ pub fn transpileSourceCode(
|
||||
if (jsc_vm.isWatcherEnabled()) {
|
||||
var resolved_source = jsc_vm.refCountedResolvedSource(printer.ctx.written, input_specifier, path.text, null, false);
|
||||
resolved_source.is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs;
|
||||
return resolved_source;
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = resolved_source.source_code,
|
||||
.source_url = resolved_source.source_url,
|
||||
.flags = .{ .is_commonjs = resolved_source.is_commonjs_module },
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
// Pass along package.json type "module" if set.
|
||||
const tag: ResolvedSource.Tag = switch (loader) {
|
||||
.json, .jsonc => .json_for_object_loader,
|
||||
.js, .jsx, .ts, .tsx => brk: {
|
||||
const module_type_ = if (package_json) |pkg| pkg.module_type else module_type;
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = brk: {
|
||||
const written = printer.ctx.getWritten();
|
||||
const result = cache.output_code orelse bun.String.cloneLatin1(written);
|
||||
|
||||
break :brk switch (module_type_) {
|
||||
.esm => .package_json_type_module,
|
||||
.cjs => .package_json_type_commonjs,
|
||||
else => .javascript,
|
||||
};
|
||||
},
|
||||
else => .javascript,
|
||||
};
|
||||
if (written.len > 1024 * 1024 * 2 or jsc_vm.smol) {
|
||||
printer.ctx.buffer.deinit();
|
||||
}
|
||||
|
||||
return .{
|
||||
.allocator = null,
|
||||
.source_code = brk: {
|
||||
const written = printer.ctx.getWritten();
|
||||
const result = cache.output_code orelse bun.String.cloneLatin1(written);
|
||||
|
||||
if (written.len > 1024 * 1024 * 2 or jsc_vm.smol) {
|
||||
printer.ctx.buffer.deinit();
|
||||
}
|
||||
|
||||
break :brk result;
|
||||
},
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs,
|
||||
.tag = tag,
|
||||
break :brk result;
|
||||
},
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.flags = .{ .is_commonjs = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs },
|
||||
} },
|
||||
};
|
||||
},
|
||||
// provideFetch() should be called
|
||||
@@ -1361,12 +1363,12 @@ pub fn transpileSourceCode(
|
||||
);
|
||||
}
|
||||
}
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.static(@embedFile("../js/wasi-runner.js")),
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.tag = .esm,
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = bun.String.static(@embedFile("../js/wasi-runner.js")),
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1420,23 +1422,23 @@ pub fn transpileSourceCode(
|
||||
;
|
||||
};
|
||||
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.cloneUTF8(sqlite_module_source_code_string),
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.tag = .esm,
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = bun.String.cloneUTF8(sqlite_module_source_code_string),
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
} },
|
||||
};
|
||||
},
|
||||
|
||||
.html => {
|
||||
if (flags.disableTranspiling()) {
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.empty,
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.tag = .esm,
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = bun.String.empty,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1445,23 +1447,23 @@ pub fn transpileSourceCode(
|
||||
}
|
||||
|
||||
const html_bundle = try jsc.API.HTMLBundle.init(globalObject.?, path.text);
|
||||
return ResolvedSource{
|
||||
.allocator = &jsc_vm.allocator,
|
||||
.jsvalue_for_export = html_bundle.toJS(globalObject.?),
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.tag = .export_default_object,
|
||||
return ModuleResult{
|
||||
.tag = .special,
|
||||
.value = .{ .special = SpecialModule{
|
||||
.tag = .export_default_object,
|
||||
.jsvalue = html_bundle.toJS(globalObject.?),
|
||||
} },
|
||||
};
|
||||
},
|
||||
|
||||
else => {
|
||||
if (flags.disableTranspiling()) {
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.empty,
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.tag = .esm,
|
||||
return ModuleResult{
|
||||
.tag = .transpiled,
|
||||
.value = .{ .transpiled = TranspiledSource{
|
||||
.source_code = bun.String.empty,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
} },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1527,17 +1529,50 @@ pub fn transpileSourceCode(
|
||||
break :brk try bun.String.createUTF8ForJS(globalObject.?, path.text);
|
||||
};
|
||||
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.jsvalue_for_export = value,
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.tag = .export_default_object,
|
||||
return ModuleResult{
|
||||
.tag = .special,
|
||||
.value = .{ .special = SpecialModule{
|
||||
.tag = .export_default_object,
|
||||
.jsvalue = value,
|
||||
} },
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to convert ModuleResult to ResolvedSource
|
||||
/// This is needed for callers that still expect ResolvedSource
|
||||
pub fn moduleResultToResolvedSource(
|
||||
result: ModuleResult,
|
||||
specifier: bun.String,
|
||||
) ResolvedSource {
|
||||
return switch (result.tag) {
|
||||
.transpiled => ResolvedSource{
|
||||
.specifier = specifier,
|
||||
.source_code = result.value.transpiled.source_code,
|
||||
.source_url = result.value.transpiled.source_url,
|
||||
.is_commonjs_module = result.value.transpiled.flags.is_commonjs,
|
||||
.already_bundled = result.value.transpiled.flags.is_already_bundled,
|
||||
.bytecode_cache = result.value.transpiled.bytecode_cache,
|
||||
.bytecode_cache_size = result.value.transpiled.bytecode_cache_len,
|
||||
.tag = .javascript,
|
||||
.source_code_needs_deref = true,
|
||||
},
|
||||
.special => ResolvedSource{
|
||||
.specifier = specifier,
|
||||
.source_code = bun.String.empty,
|
||||
.source_url = bun.String.empty,
|
||||
.jsvalue_for_export = result.value.special.jsvalue,
|
||||
.tag = switch (result.value.special.tag) {
|
||||
.exports_object => .exports_object,
|
||||
.export_default_object => .export_default_object,
|
||||
.custom_extension => .common_js_custom_extension,
|
||||
},
|
||||
},
|
||||
.builtin => unreachable, // transpileSourceCode should never return .builtin
|
||||
};
|
||||
}
|
||||
|
||||
pub export fn Bun__resolveAndFetchBuiltinModule(
|
||||
jsc_vm: *VirtualMachine,
|
||||
specifier: *bun.String,
|
||||
@@ -1796,38 +1831,39 @@ pub export fn Bun__transpileFile(
|
||||
defer jsc_vm.module_loader.resetArena(jsc_vm);
|
||||
|
||||
var promise: ?*jsc.JSInternalPromise = null;
|
||||
const module_result = ModuleLoader.transpileSourceCode(
|
||||
jsc_vm,
|
||||
lr.specifier,
|
||||
referrer_slice.slice(),
|
||||
specifier_ptr.*,
|
||||
lr.path,
|
||||
synchronous_loader,
|
||||
module_type,
|
||||
&log,
|
||||
lr.virtual_source,
|
||||
if (allow_promise) &promise else null,
|
||||
VirtualMachine.source_code_printer.?,
|
||||
globalObject,
|
||||
FetchFlags.transpile,
|
||||
) catch |err| {
|
||||
switch (err) {
|
||||
error.AsyncModule => {
|
||||
bun.assert(promise != null);
|
||||
return promise;
|
||||
},
|
||||
error.PluginError => return null,
|
||||
error.JSError => {
|
||||
ret.* = jsc.ErrorableResolvedSource.err(error.JSError, globalObject.takeError(error.JSError));
|
||||
return null;
|
||||
},
|
||||
else => {
|
||||
VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer.*, &log, ret, err);
|
||||
return null;
|
||||
},
|
||||
}
|
||||
};
|
||||
ret.* = jsc.ErrorableResolvedSource.ok(
|
||||
ModuleLoader.transpileSourceCode(
|
||||
jsc_vm,
|
||||
lr.specifier,
|
||||
referrer_slice.slice(),
|
||||
specifier_ptr.*,
|
||||
lr.path,
|
||||
synchronous_loader,
|
||||
module_type,
|
||||
&log,
|
||||
lr.virtual_source,
|
||||
if (allow_promise) &promise else null,
|
||||
VirtualMachine.source_code_printer.?,
|
||||
globalObject,
|
||||
FetchFlags.transpile,
|
||||
) catch |err| {
|
||||
switch (err) {
|
||||
error.AsyncModule => {
|
||||
bun.assert(promise != null);
|
||||
return promise;
|
||||
},
|
||||
error.PluginError => return null,
|
||||
error.JSError => {
|
||||
ret.* = jsc.ErrorableResolvedSource.err(error.JSError, globalObject.takeError(error.JSError));
|
||||
return null;
|
||||
},
|
||||
else => {
|
||||
VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer.*, &log, ret, err);
|
||||
return null;
|
||||
},
|
||||
}
|
||||
},
|
||||
ModuleLoader.moduleResultToResolvedSource(module_result, specifier_ptr.*),
|
||||
);
|
||||
return promise;
|
||||
}
|
||||
@@ -1977,34 +2013,35 @@ export fn Bun__transpileVirtualModule(
|
||||
defer log.deinit();
|
||||
defer jsc_vm.module_loader.resetArena(jsc_vm);
|
||||
|
||||
const module_result = ModuleLoader.transpileSourceCode(
|
||||
jsc_vm,
|
||||
specifier_slice.slice(),
|
||||
referrer_slice.slice(),
|
||||
specifier_ptr.*,
|
||||
path,
|
||||
loader,
|
||||
.unknown,
|
||||
&log,
|
||||
&virtual_source,
|
||||
null,
|
||||
VirtualMachine.source_code_printer.?,
|
||||
globalObject,
|
||||
FetchFlags.transpile,
|
||||
) catch |err| {
|
||||
switch (err) {
|
||||
error.PluginError => return true,
|
||||
error.JSError => {
|
||||
ret.* = jsc.ErrorableResolvedSource.err(error.JSError, globalObject.takeError(error.JSError));
|
||||
return true;
|
||||
},
|
||||
else => {
|
||||
VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer_ptr.*, &log, ret, err);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
};
|
||||
ret.* = jsc.ErrorableResolvedSource.ok(
|
||||
ModuleLoader.transpileSourceCode(
|
||||
jsc_vm,
|
||||
specifier_slice.slice(),
|
||||
referrer_slice.slice(),
|
||||
specifier_ptr.*,
|
||||
path,
|
||||
loader,
|
||||
.unknown,
|
||||
&log,
|
||||
&virtual_source,
|
||||
null,
|
||||
VirtualMachine.source_code_printer.?,
|
||||
globalObject,
|
||||
FetchFlags.transpile,
|
||||
) catch |err| {
|
||||
switch (err) {
|
||||
error.PluginError => return true,
|
||||
error.JSError => {
|
||||
ret.* = jsc.ErrorableResolvedSource.err(error.JSError, globalObject.takeError(error.JSError));
|
||||
return true;
|
||||
},
|
||||
else => {
|
||||
VirtualMachine.processFetchLog(globalObject, specifier_ptr.*, referrer_ptr.*, &log, ret, err);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
},
|
||||
ModuleLoader.moduleResultToResolvedSource(module_result, specifier_ptr.*),
|
||||
);
|
||||
analytics.Features.virtual_modules += 1;
|
||||
return true;
|
||||
@@ -2179,17 +2216,7 @@ pub const RuntimeTranspilerStore = struct {
|
||||
|
||||
// NOTE: DirInfo should already be cached since module loading happens
|
||||
// after module resolution, so this should be cheap
|
||||
var resolved_source = ResolvedSource{};
|
||||
if (package_json) |pkg| {
|
||||
switch (pkg.module_type) {
|
||||
.cjs => {
|
||||
resolved_source.tag = .package_json_type_commonjs;
|
||||
resolved_source.is_commonjs_module = true;
|
||||
},
|
||||
.esm => resolved_source.tag = .package_json_type_module,
|
||||
.unknown => {},
|
||||
}
|
||||
}
|
||||
const package_json_module_type: ModuleType = if (package_json) |pkg| pkg.module_type else .unknown;
|
||||
|
||||
job.* = TranspilerJob{
|
||||
.non_threadsafe_input_specifier = input_specifier,
|
||||
@@ -2204,7 +2231,7 @@ pub const RuntimeTranspilerStore = struct {
|
||||
.fetcher = TranspilerJob.Fetcher{
|
||||
.file = {},
|
||||
},
|
||||
.resolved_source = resolved_source,
|
||||
.package_json_module_type = package_json_module_type,
|
||||
};
|
||||
if (comptime Environment.allow_assert)
|
||||
debug("transpile({s}, {s}, async)", .{ path.text, @tagName(job.loader) });
|
||||
@@ -2225,7 +2252,8 @@ pub const RuntimeTranspilerStore = struct {
|
||||
generation_number: u32 = 0,
|
||||
log: logger.Log,
|
||||
parse_error: ?anyerror = null,
|
||||
resolved_source: ResolvedSource = ResolvedSource{},
|
||||
module_result: ModuleResult = .{ .tag = .transpiled, .value = .{ .transpiled = .{} } },
|
||||
package_json_module_type: ModuleType = .unknown,
|
||||
work_task: jsc.WorkPoolTask = .{ .callback = runFromWorkerThread },
|
||||
next: ?*TranspilerJob = null,
|
||||
|
||||
@@ -2274,7 +2302,7 @@ pub const RuntimeTranspilerStore = struct {
|
||||
this.non_threadsafe_referrer = String.empty;
|
||||
var log = this.log;
|
||||
this.log = logger.Log.init(bun.default_allocator);
|
||||
var resolved_source = this.resolved_source;
|
||||
var module_result = this.module_result;
|
||||
const specifier = brk: {
|
||||
if (this.parse_error != null) {
|
||||
break :brk bun.String.cloneUTF8(this.path.text);
|
||||
@@ -2283,10 +2311,8 @@ pub const RuntimeTranspilerStore = struct {
|
||||
const out = this.non_threadsafe_input_specifier;
|
||||
this.non_threadsafe_input_specifier = String.empty;
|
||||
|
||||
bun.debugAssert(resolved_source.source_url.isEmpty());
|
||||
bun.debugAssert(resolved_source.specifier.isEmpty());
|
||||
resolved_source.source_url = out.createIfDifferent(this.path.text);
|
||||
resolved_source.specifier = out.dupeRef();
|
||||
bun.debugAssert(module_result.value.transpiled.source_url.isEmpty());
|
||||
module_result.value.transpiled.source_url = out.createIfDifferent(this.path.text);
|
||||
break :brk out;
|
||||
};
|
||||
|
||||
@@ -2297,7 +2323,7 @@ pub const RuntimeTranspilerStore = struct {
|
||||
|
||||
_ = vm.transpiler_store.store.put(this);
|
||||
|
||||
try ModuleLoader.AsyncModule.fulfill(globalThis, promise, &resolved_source, parse_error, specifier, referrer, &log);
|
||||
try ModuleLoader.AsyncModule.fulfill(globalThis, promise, &module_result, parse_error, specifier, referrer, &log);
|
||||
}
|
||||
|
||||
pub fn schedule(this: *TranspilerJob) void {
|
||||
@@ -2391,11 +2417,7 @@ pub const RuntimeTranspilerStore = struct {
|
||||
vm.main_hash == hash and
|
||||
strings.eqlLong(vm.main, path.text, false);
|
||||
|
||||
const module_type: ModuleType = switch (this.resolved_source.tag) {
|
||||
.package_json_type_commonjs => .cjs,
|
||||
.package_json_type_module => .esm,
|
||||
else => .unknown,
|
||||
};
|
||||
const module_type: ModuleType = this.package_json_module_type;
|
||||
|
||||
var parse_options = Transpiler.ParseOptions{
|
||||
.allocator = allocator,
|
||||
@@ -2494,19 +2516,24 @@ pub const RuntimeTranspilerStore = struct {
|
||||
dumpSourceString(vm, specifier, entry.output_code.byteSlice());
|
||||
}
|
||||
|
||||
this.resolved_source = ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = switch (entry.output_code) {
|
||||
.string => entry.output_code.string,
|
||||
.utf8 => brk: {
|
||||
const result = bun.String.cloneUTF8(entry.output_code.utf8);
|
||||
cache.output_code_allocator.free(entry.output_code.utf8);
|
||||
entry.output_code.utf8 = "";
|
||||
break :brk result;
|
||||
this.module_result = .{
|
||||
.tag = .transpiled,
|
||||
.value = .{
|
||||
.transpiled = .{
|
||||
.source_code = switch (entry.output_code) {
|
||||
.string => entry.output_code.string,
|
||||
.utf8 => brk: {
|
||||
const result = bun.String.cloneUTF8(entry.output_code.utf8);
|
||||
cache.output_code_allocator.free(entry.output_code.utf8);
|
||||
entry.output_code.utf8 = "";
|
||||
break :brk result;
|
||||
},
|
||||
},
|
||||
.flags = .{
|
||||
.is_commonjs = entry.metadata.module_type == .cjs,
|
||||
},
|
||||
},
|
||||
},
|
||||
.is_commonjs_module = entry.metadata.module_type == .cjs,
|
||||
.tag = this.resolved_source.tag,
|
||||
};
|
||||
|
||||
return;
|
||||
@@ -2514,16 +2541,22 @@ pub const RuntimeTranspilerStore = struct {
|
||||
|
||||
if (parse_result.already_bundled != .none) {
|
||||
const bytecode_slice = parse_result.already_bundled.bytecodeSlice();
|
||||
this.resolved_source = ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.cloneLatin1(parse_result.source.contents),
|
||||
.already_bundled = true,
|
||||
.bytecode_cache = if (bytecode_slice.len > 0) bytecode_slice.ptr else null,
|
||||
.bytecode_cache_size = bytecode_slice.len,
|
||||
.is_commonjs_module = parse_result.already_bundled.isCommonJS(),
|
||||
.tag = this.resolved_source.tag,
|
||||
const source_code = bun.String.cloneLatin1(parse_result.source.contents);
|
||||
source_code.ensureHash();
|
||||
this.module_result = .{
|
||||
.tag = .transpiled,
|
||||
.value = .{
|
||||
.transpiled = .{
|
||||
.source_code = source_code,
|
||||
.bytecode_cache = if (bytecode_slice.len > 0) bytecode_slice.ptr else null,
|
||||
.bytecode_cache_len = bytecode_slice.len,
|
||||
.flags = .{
|
||||
.is_commonjs = parse_result.already_bundled.isCommonJS(),
|
||||
.is_already_bundled = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
this.resolved_source.source_code.ensureHash();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2595,11 +2628,16 @@ pub const RuntimeTranspilerStore = struct {
|
||||
|
||||
break :brk result;
|
||||
};
|
||||
this.resolved_source = ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = source_code,
|
||||
.is_commonjs_module = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs,
|
||||
.tag = this.resolved_source.tag,
|
||||
this.module_result = .{
|
||||
.tag = .transpiled,
|
||||
.value = .{
|
||||
.transpiled = .{
|
||||
.source_code = source_code,
|
||||
.flags = .{
|
||||
.is_commonjs = parse_result.ast.has_commonjs_export_names or parse_result.ast.exports_kind == .cjs,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -3063,6 +3101,9 @@ const Dependency = @import("../install/dependency.zig");
|
||||
const Fs = @import("../fs.zig");
|
||||
const Runtime = @import("../runtime.zig");
|
||||
const node_module_module = @import("./bindings/NodeModuleModule.zig");
|
||||
const TranspiledSource = @import("./bindings/TranspiledSource.zig").TranspiledSource;
|
||||
const SpecialModule = @import("./bindings/SpecialModule.zig").SpecialModule;
|
||||
const ModuleResult = @import("./bindings/ModuleResult.zig").ModuleResult;
|
||||
const std = @import("std");
|
||||
const panic = std.debug.panic;
|
||||
|
||||
|
||||
@@ -1545,7 +1545,7 @@ pub fn fetchWithoutOnLoadPlugins(
|
||||
defer if (flags != .print_source) jsc_vm.module_loader.resetArena(jsc_vm);
|
||||
errdefer if (flags == .print_source) jsc_vm.module_loader.resetArena(jsc_vm);
|
||||
|
||||
return try ModuleLoader.transpileSourceCode(
|
||||
const module_result = try ModuleLoader.transpileSourceCode(
|
||||
jsc_vm,
|
||||
lr.specifier,
|
||||
referrer_clone.slice(),
|
||||
@@ -1560,6 +1560,7 @@ pub fn fetchWithoutOnLoadPlugins(
|
||||
globalObject,
|
||||
flags,
|
||||
);
|
||||
return ModuleLoader.moduleResultToResolvedSource(module_result, _specifier);
|
||||
}
|
||||
|
||||
pub const ResolveFunctionResult = struct {
|
||||
|
||||
135
src/bun.js/bindings/BunSourceProvider.cpp
Normal file
135
src/bun.js/bindings/BunSourceProvider.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "root.h"
|
||||
#include "helpers.h"
|
||||
#include "BunSourceProvider.h"
|
||||
#include "ZigSourceProvider.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
|
||||
#include <JavaScriptCore/BytecodeCacheError.h>
|
||||
#include <JavaScriptCore/Completion.h>
|
||||
#include <wtf/Scope.h>
|
||||
#include <wtf/text/StringHash.h>
|
||||
#include <wtf/Assertions.h>
|
||||
#include <sys/stat.h>
|
||||
#include <JavaScriptCore/SourceCodeKey.h>
|
||||
#include <mimalloc.h>
|
||||
#include <JavaScriptCore/CodeCache.h>
|
||||
|
||||
// Import the TranspiledSource struct from Zig
|
||||
extern "C" {
|
||||
struct TranspiledSource {
|
||||
BunString source_code;
|
||||
BunString source_url;
|
||||
uint8_t* bytecode_cache;
|
||||
size_t bytecode_cache_len;
|
||||
struct {
|
||||
bool is_commonjs : 1;
|
||||
bool is_already_bundled : 1;
|
||||
uint32_t _padding : 30;
|
||||
} flags;
|
||||
};
|
||||
}
|
||||
|
||||
namespace Zig {
|
||||
|
||||
using SourceOrigin = JSC::SourceOrigin;
|
||||
using SourceProviderSourceType = JSC::SourceProviderSourceType;
|
||||
|
||||
extern "C" void Bun__addSourceProviderSourceMap(void* bun_vm, JSC::SourceProvider* opaque_source_provider, BunString* specifier);
|
||||
extern "C" void Bun__removeSourceProviderSourceMap(void* bun_vm, JSC::SourceProvider* opaque_source_provider, BunString* specifier);
|
||||
|
||||
BunSourceProvider::BunSourceProvider(
|
||||
Zig::GlobalObject* globalObject,
|
||||
Ref<WTF::StringImpl>&& source,
|
||||
const JSC::SourceOrigin& origin,
|
||||
WTF::String&& sourceURL,
|
||||
RefPtr<JSC::CachedBytecode>&& bytecode,
|
||||
JSC::SourceProviderSourceType sourceType)
|
||||
: Base(origin, WTFMove(sourceURL), WTF::String(), JSC::SourceTaintedOrigin::Untainted, WTF::TextPosition(), sourceType)
|
||||
, m_source(WTFMove(source))
|
||||
, m_cachedBytecode(WTFMove(bytecode))
|
||||
, m_globalObject(globalObject)
|
||||
{
|
||||
}
|
||||
|
||||
Ref<BunSourceProvider> BunSourceProvider::create(
|
||||
Zig::GlobalObject* globalObject,
|
||||
Ref<WTF::StringImpl>&& source,
|
||||
const JSC::SourceOrigin& origin,
|
||||
WTF::String&& sourceURL,
|
||||
RefPtr<JSC::CachedBytecode>&& bytecode,
|
||||
JSC::SourceProviderSourceType sourceType)
|
||||
{
|
||||
return adoptRef(*new BunSourceProvider(
|
||||
globalObject,
|
||||
WTFMove(source),
|
||||
origin,
|
||||
WTFMove(sourceURL),
|
||||
WTFMove(bytecode),
|
||||
sourceType));
|
||||
}
|
||||
|
||||
BunSourceProvider::~BunSourceProvider()
|
||||
{
|
||||
// Sourcemap cleanup is handled separately
|
||||
}
|
||||
|
||||
StringView BunSourceProvider::source() const
|
||||
{
|
||||
return StringView(m_source.get());
|
||||
}
|
||||
|
||||
unsigned BunSourceProvider::hash() const
|
||||
{
|
||||
if (m_hash == 0) {
|
||||
m_hash = m_source->hash();
|
||||
}
|
||||
return m_hash;
|
||||
}
|
||||
|
||||
// C bridge function to create BunSourceProvider from TranspiledSource
|
||||
extern "C" JSC::SourceProvider* Bun__createSourceProvider(
|
||||
Zig::GlobalObject* globalObject,
|
||||
const TranspiledSource* source)
|
||||
{
|
||||
auto sourceString = source->source_code.toWTFString(BunString::ZeroCopy);
|
||||
auto sourceURL = source->source_url.toWTFString(BunString::ZeroCopy);
|
||||
|
||||
bool isCommonJS = source->flags.is_commonjs;
|
||||
auto sourceType = isCommonJS ?
|
||||
JSC::SourceProviderSourceType::Program :
|
||||
JSC::SourceProviderSourceType::Module;
|
||||
|
||||
// Handle bytecode if present
|
||||
RefPtr<JSC::CachedBytecode> bytecode;
|
||||
if (source->bytecode_cache) {
|
||||
bytecode = JSC::CachedBytecode::create(
|
||||
std::span<uint8_t>(source->bytecode_cache, source->bytecode_cache_len),
|
||||
[](const void* ptr) { mi_free(const_cast<void*>(ptr)); },
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
auto provider = BunSourceProvider::create(
|
||||
globalObject,
|
||||
sourceString.isNull() ? Ref<WTF::StringImpl>(*StringImpl::empty()) : Ref<WTF::StringImpl>(*sourceString.impl()),
|
||||
toSourceOrigin(sourceURL, false),
|
||||
WTFMove(sourceURL),
|
||||
WTFMove(bytecode),
|
||||
sourceType
|
||||
);
|
||||
|
||||
// Register sourcemap if needed
|
||||
if (source->flags.is_already_bundled) {
|
||||
BunString sourceUrlCopy = source->source_url;
|
||||
Bun__addSourceProviderSourceMap(
|
||||
globalObject->bunVM(),
|
||||
provider.ptr(),
|
||||
&sourceUrlCopy
|
||||
);
|
||||
}
|
||||
|
||||
// Transfer ownership to caller
|
||||
return &provider.leakRef();
|
||||
}
|
||||
|
||||
} // namespace Zig
|
||||
61
src/bun.js/bindings/BunSourceProvider.h
Normal file
61
src/bun.js/bindings/BunSourceProvider.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "headers.h"
|
||||
#include "root.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace JSC {
|
||||
class SourceProvider;
|
||||
class SourceOrigin;
|
||||
} // namespace JSC
|
||||
|
||||
#include <JavaScriptCore/CachedBytecode.h>
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
#include <JavaScriptCore/SourceProvider.h>
|
||||
|
||||
// Forward declarations
|
||||
struct TranspiledSource;
|
||||
|
||||
namespace Zig {
|
||||
|
||||
class GlobalObject;
|
||||
|
||||
class BunSourceProvider final : public JSC::SourceProvider {
|
||||
WTF_DEPRECATED_MAKE_FAST_ALLOCATED(BunSourceProvider);
|
||||
using Base = JSC::SourceProvider;
|
||||
|
||||
public:
|
||||
static Ref<BunSourceProvider> create(
|
||||
Zig::GlobalObject* globalObject,
|
||||
Ref<WTF::StringImpl>&& source,
|
||||
const JSC::SourceOrigin& origin,
|
||||
WTF::String&& sourceURL,
|
||||
RefPtr<JSC::CachedBytecode>&& bytecode,
|
||||
JSC::SourceProviderSourceType sourceType);
|
||||
|
||||
~BunSourceProvider() final;
|
||||
|
||||
// Required overrides
|
||||
StringView source() const final;
|
||||
unsigned hash() const final;
|
||||
RefPtr<JSC::CachedBytecode> cachedBytecode() const final
|
||||
{
|
||||
return m_cachedBytecode.copyRef();
|
||||
};
|
||||
|
||||
private:
|
||||
BunSourceProvider(
|
||||
Zig::GlobalObject* globalObject,
|
||||
Ref<WTF::StringImpl>&& source,
|
||||
const JSC::SourceOrigin& origin,
|
||||
WTF::String&& sourceURL,
|
||||
RefPtr<JSC::CachedBytecode>&& bytecode,
|
||||
JSC::SourceProviderSourceType sourceType);
|
||||
|
||||
// Simplified members (vs ZigSourceProvider)
|
||||
Ref<WTF::StringImpl> m_source;
|
||||
RefPtr<JSC::CachedBytecode> m_cachedBytecode;
|
||||
Zig::GlobalObject* m_globalObject; // For sourcemap cleanup only
|
||||
mutable unsigned m_hash = 0;
|
||||
};
|
||||
|
||||
} // namespace Zig
|
||||
@@ -41,6 +41,66 @@
|
||||
|
||||
#include "BunProcess.h"
|
||||
|
||||
// ModuleResult structure definitions - match Zig side
|
||||
extern "C" {
|
||||
|
||||
struct TranspiledSourceFlags {
|
||||
bool is_commonjs : 1;
|
||||
bool is_already_bundled : 1;
|
||||
uint32_t _padding : 30;
|
||||
};
|
||||
|
||||
struct TranspiledSource {
|
||||
BunString source_code;
|
||||
BunString source_url;
|
||||
uint8_t* bytecode_cache;
|
||||
size_t bytecode_cache_len;
|
||||
TranspiledSourceFlags flags;
|
||||
};
|
||||
|
||||
enum class SpecialModuleTag : uint8_t {
|
||||
exports_object,
|
||||
export_default_object,
|
||||
custom_extension,
|
||||
};
|
||||
|
||||
struct SpecialModule {
|
||||
SpecialModuleTag tag;
|
||||
JSC::EncodedJSValue jsvalue;
|
||||
};
|
||||
|
||||
enum class ModuleResultTag : uint8_t {
|
||||
transpiled,
|
||||
special,
|
||||
builtin,
|
||||
};
|
||||
|
||||
struct ModuleResult {
|
||||
ModuleResultTag tag;
|
||||
union {
|
||||
TranspiledSource transpiled;
|
||||
SpecialModule special;
|
||||
uint32_t builtin_id;
|
||||
} value;
|
||||
};
|
||||
|
||||
struct ErrorableModuleResult {
|
||||
bool success;
|
||||
union {
|
||||
ModuleResult value;
|
||||
struct {
|
||||
JSC::EncodedJSValue value;
|
||||
} err;
|
||||
} result;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// C bridge function to create BunSourceProvider from TranspiledSource
|
||||
extern "C" JSC::SourceProvider* Bun__createSourceProvider(
|
||||
Zig::GlobalObject* globalObject,
|
||||
const TranspiledSource* source);
|
||||
|
||||
namespace Bun {
|
||||
using namespace JSC;
|
||||
using namespace Zig;
|
||||
@@ -457,17 +517,16 @@ static JSValue handleVirtualModuleResult(
|
||||
extern "C" void Bun__onFulfillAsyncModule(
|
||||
Zig::GlobalObject* globalObject,
|
||||
JSC::EncodedJSValue encodedPromiseValue,
|
||||
ErrorableResolvedSource* res,
|
||||
ErrorableModuleResult* result,
|
||||
BunString* specifier,
|
||||
BunString* referrer)
|
||||
{
|
||||
ResolvedSourceCodeHolder sourceCodeHolder(res);
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
JSC::JSInternalPromise* promise = jsCast<JSC::JSInternalPromise*>(JSC::JSValue::decode(encodedPromiseValue));
|
||||
|
||||
if (!res->success) {
|
||||
RELEASE_AND_RETURN(scope, promise->reject(globalObject, JSValue::decode(res->result.err.value)));
|
||||
if (!result->success) {
|
||||
RELEASE_AND_RETURN(scope, promise->reject(globalObject, JSValue::decode(result->result.err.value)));
|
||||
}
|
||||
|
||||
auto specifierValue = Bun::toJS(globalObject, *specifier);
|
||||
@@ -492,25 +551,70 @@ extern "C" void Bun__onFulfillAsyncModule(
|
||||
}
|
||||
}
|
||||
|
||||
if (res->result.value.isCommonJSModule) {
|
||||
auto created = Bun::createCommonJSModule(jsCast<Zig::GlobalObject*>(globalObject), specifierValue, res->result.value);
|
||||
EXCEPTION_ASSERT(created.has_value() == !scope.exception());
|
||||
if (created.has_value()) {
|
||||
JSSourceCode* code = JSSourceCode::create(vm, WTFMove(created.value()));
|
||||
promise->resolve(globalObject, code);
|
||||
scope.assertNoExceptionExceptTermination();
|
||||
} else {
|
||||
auto* exception = scope.exception();
|
||||
if (!vm.isTerminationException(exception)) {
|
||||
scope.clearException();
|
||||
promise->reject(globalObject, exception);
|
||||
// Handle ModuleResult based on its tag
|
||||
switch (result->result.value.tag) {
|
||||
case ModuleResultTag::transpiled: {
|
||||
auto& transpiled = result->result.value.value.transpiled;
|
||||
|
||||
// Create source provider using new C bridge
|
||||
// The function returns a leaked ref that we need to adopt
|
||||
Ref<JSC::SourceProvider> provider = adoptRef(*Bun__createSourceProvider(globalObject, &transpiled));
|
||||
|
||||
// Check if it's CommonJS vs ESM based on flags
|
||||
if (transpiled.flags.is_commonjs) {
|
||||
// For CommonJS, we need to create a CommonJS module wrapper
|
||||
// This uses the existing createCommonJSModule path which expects ResolvedSource
|
||||
// We'll need to construct a temporary ResolvedSource for now
|
||||
// TODO: Update createCommonJSModule to accept TranspiledSource directly
|
||||
ResolvedSource tempRes = {};
|
||||
tempRes.source_code = transpiled.source_code;
|
||||
tempRes.source_url = transpiled.source_url;
|
||||
tempRes.isCommonJSModule = true;
|
||||
tempRes.already_bundled = transpiled.flags.is_already_bundled;
|
||||
tempRes.bytecode_cache = transpiled.bytecode_cache;
|
||||
tempRes.bytecode_cache_size = transpiled.bytecode_cache_len;
|
||||
tempRes.needsDeref = false; // Ownership transferred via ModuleResult
|
||||
|
||||
auto created = Bun::createCommonJSModule(jsCast<Zig::GlobalObject*>(globalObject), specifierValue, tempRes);
|
||||
EXCEPTION_ASSERT(created.has_value() == !scope.exception());
|
||||
if (created.has_value()) {
|
||||
JSSourceCode* code = JSSourceCode::create(vm, WTFMove(created.value()));
|
||||
promise->resolve(globalObject, code);
|
||||
scope.assertNoExceptionExceptTermination();
|
||||
} else {
|
||||
auto* exception = scope.exception();
|
||||
if (!vm.isTerminationException(exception)) {
|
||||
scope.clearException();
|
||||
promise->reject(globalObject, exception);
|
||||
scope.assertNoExceptionExceptTermination();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ESM module - use provider directly
|
||||
promise->resolve(globalObject, JSC::JSSourceCode::create(vm, JSC::SourceCode(WTFMove(provider))));
|
||||
scope.assertNoExceptionExceptTermination();
|
||||
}
|
||||
} else {
|
||||
auto&& provider = Zig::SourceProvider::create(jsDynamicCast<Zig::GlobalObject*>(globalObject), res->result.value);
|
||||
promise->resolve(globalObject, JSC::JSSourceCode::create(vm, JSC::SourceCode(provider)));
|
||||
break;
|
||||
}
|
||||
|
||||
case ModuleResultTag::special: {
|
||||
// Special modules (exports_object, export_default_object, custom_extension)
|
||||
// should not reach async module fulfillment - they're handled synchronously
|
||||
auto* exception = JSC::createTypeError(globalObject, "Special modules cannot be used in async import"_s);
|
||||
scope.clearException();
|
||||
promise->reject(globalObject, exception);
|
||||
scope.assertNoExceptionExceptTermination();
|
||||
break;
|
||||
}
|
||||
|
||||
case ModuleResultTag::builtin: {
|
||||
// Builtin modules should not reach async module fulfillment
|
||||
auto* exception = JSC::createTypeError(globalObject, "Builtin modules cannot be used in async import"_s);
|
||||
scope.clearException();
|
||||
promise->reject(globalObject, exception);
|
||||
scope.assertNoExceptionExceptTermination();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// the module has since been deleted from the registry.
|
||||
|
||||
18
src/bun.js/bindings/ModuleResult.zig
Normal file
18
src/bun.js/bindings/ModuleResult.zig
Normal file
@@ -0,0 +1,18 @@
|
||||
const TranspiledSource = @import("./TranspiledSource.zig").TranspiledSource;
|
||||
const SpecialModule = @import("./SpecialModule.zig").SpecialModule;
|
||||
|
||||
/// Tagged union return type from transpiler
|
||||
pub const ModuleResult = extern struct {
|
||||
tag: Tag,
|
||||
value: extern union {
|
||||
transpiled: TranspiledSource,
|
||||
special: SpecialModule,
|
||||
builtin_id: u32,
|
||||
},
|
||||
|
||||
pub const Tag = enum(u8) {
|
||||
transpiled,
|
||||
special,
|
||||
builtin,
|
||||
};
|
||||
};
|
||||
19
src/bun.js/bindings/SpecialModule.zig
Normal file
19
src/bun.js/bindings/SpecialModule.zig
Normal file
@@ -0,0 +1,19 @@
|
||||
const bun = @import("bun");
|
||||
const jsc = bun.jsc;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
/// For special cases that need JSValue handling
|
||||
/// Main thread only (contains JSValue)
|
||||
pub const SpecialModule = extern struct {
|
||||
tag: Tag,
|
||||
jsvalue: JSValue,
|
||||
|
||||
pub const Tag = enum(u8) {
|
||||
/// Return exports object directly
|
||||
exports_object,
|
||||
/// Return default export only
|
||||
export_default_object,
|
||||
/// Call custom require.extensions handler
|
||||
custom_extension,
|
||||
};
|
||||
};
|
||||
26
src/bun.js/bindings/TranspiledSource.zig
Normal file
26
src/bun.js/bindings/TranspiledSource.zig
Normal file
@@ -0,0 +1,26 @@
|
||||
const bun = @import("bun");
|
||||
|
||||
/// Minimal POD struct for transpiled source code
|
||||
/// Can be safely created on worker threads
|
||||
/// Ownership transfers to C++ on return
|
||||
pub const TranspiledSource = extern struct {
|
||||
/// Transpiled source code (Latin1 or UTF16)
|
||||
/// Ownership transfers to C++ on return
|
||||
source_code: bun.String = bun.String.empty,
|
||||
|
||||
/// Module specifier for debugging/sourcemaps
|
||||
source_url: bun.String = bun.String.empty,
|
||||
|
||||
/// Optional bytecode cache (for bun build --compile)
|
||||
bytecode_cache: ?[*]u8 = null,
|
||||
bytecode_cache_len: usize = 0,
|
||||
|
||||
/// Packed flags
|
||||
flags: Flags = .{},
|
||||
|
||||
pub const Flags = packed struct(u32) {
|
||||
is_commonjs: bool = false,
|
||||
is_already_bundled: bool = false,
|
||||
_padding: u30 = 0,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user