Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6385,6 +6385,10 @@ declare module "bun" {
}

interface OnLoadResultSourceCode {
/** Absolute file paths whose changes invalidate this module in the development server. */
watchFiles?: string[] | undefined;
/** Absolute directories whose immediate entry changes invalidate this module. Not recursive. */
watchDirs?: string[] | undefined;
/**
* The source code of the module
*/
Expand All @@ -6396,6 +6400,10 @@ declare module "bun" {
}

interface OnLoadResultObject {
/** Absolute file paths whose changes invalidate this module in the development server. */
watchFiles?: string[] | undefined;
/** Absolute directories whose immediate entry changes invalidate this module. Not recursive. */
watchDirs?: string[] | undefined;
/**
* The object to use as the module
* @example
Expand Down
28 changes: 24 additions & 4 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,8 @@ pub mod bv2_impl {
pub struct LoadSuccess {
pub source_code: Box<[u8]>,
pub loader: Loader,
pub watch_files: Vec<Box<[u8]>>,
pub watch_dirs: Vec<Box<[u8]>>,
}
/// Mirrors `JSBundler.Load.Value` (`union(enum)`).
#[derive(Default)]
Expand Down Expand Up @@ -5053,6 +5055,16 @@ pub mod bv2_impl {
jsc_api::JSBundler::LoadValue::NoMatch => {
let source =
&this.graph.input_files.items_source()[load.source_index.get() as usize];
if let Some(dev) = this.dev_server {
dev.replace_plugin_watch_dependencies(
source.path.key_for_incremental_graph(),
load.bake_graph(),
!source.path.is_file(),
&[],
&[],
)
.expect("OOM");
}
// If it's a file namespace, we should run it through the parser like normal.
// The file could be on disk.
if source.path.is_file() {
Expand Down Expand Up @@ -5085,7 +5097,18 @@ pub mod bv2_impl {
this.decrement_scan_counter();
}
jsc_api::JSBundler::LoadValue::Success(code) => {
// `code`: LoadSuccess { source_code, loader }
if let Some(dev) = this.dev_server {
let source = &this.graph.input_files.items_source()
[load.source_index.get() as usize];
dev.replace_plugin_watch_dependencies(
source.path.key_for_incremental_graph(),
load.bake_graph(),
!source.path.is_file(),
&code.watch_files,
&code.watch_dirs,
)
.expect("OOM");
}
// When a plugin returns a file loader, we always need to populate additional_files
let should_copy_for_bundling = code.loader.should_copy_for_bundling();
if should_copy_for_bundling {
Expand Down Expand Up @@ -5132,9 +5155,6 @@ pub mod bv2_impl {
break 'add_watchers;
}

// TODO: support explicit watchFiles array. this is not done
// right now because DevServer requires a table to map
// watched files and dirs to their respective dependants.
let fd = if bun_watcher::REQUIRES_FILE_DESCRIPTORS {
let mut buf = bun_paths::path_buffer_pool::get();
// On kqueue platforms paths are already
Expand Down
1 change: 1 addition & 0 deletions src/bundler/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ bun_dispatch::link_interface! {
fn handle_parse_task_failure(err: crate::Error, graph: bake_types::Graph, abs_path: &[u8], log: *const bun_ast::Log, bv2: *mut bundle_v2::BundleV2<'_>) -> Result<(), crate::Error>;
fn put_or_overwrite_asset(path: *const (), contents: &[u8], content_hash: u64) -> Result<(), crate::Error>;
fn track_resolution_failure(import_source: &[u8], specifier: &[u8], renderer: bake_types::Graph, loader: bun_ast::Loader) -> Result<(), crate::Error>;
fn replace_plugin_watch_dependencies(module: &[u8], graph: bake_types::Graph, is_virtual: bool, files: &[Box<[u8]>], dirs: &[Box<[u8]>]) -> Result<(), crate::Error>;
fn is_file_cached(abs_path: &[u8], side: bake_types::Graph) -> Option<bake_types::CacheEntry>;
fn asset_hash(abs_path: &[u8]) -> Option<u64>;
fn current_bundle_start_data() -> *mut ();
Expand Down
1 change: 1 addition & 0 deletions src/js/builtins/BunBuiltinNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ using namespace JSC;
macro(onClose) \
macro(onDrain) \
macro(onEndCallbacks) \
macro(onStartCallbacks) \
macro(originalColumn) \
macro(originalLine) \
macro(overridableRequire) \
Expand Down
64 changes: 61 additions & 3 deletions src/js/builtins/BundlerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ interface BundlerPlugin {
onLoad: Map<string, [RegExp, OnLoadCallback][]>;
onResolve: Map<string, [RegExp, OnResolveCallback][]>;
$onEndCallbacks: Array<(build: Bun.BuildOutput) => void | Promise<void>> | undefined;
$onStartCallbacks: Array<() => void | Promise<void>> | undefined;
/** Binding to `JSBundlerPlugin__onLoadAsync` */
onLoadAsync(
internalID,
sourceCode: string | Uint8Array | ArrayBuffer | DataView | null,
loaderKey: number | null,
watchFiles?: string[],
watchDirs?: string[],
): void;
/** Binding to `JSBundlerPlugin__onResolveAsync` */
onResolveAsync(internalID, a, b, c): void;
Expand Down Expand Up @@ -62,13 +65,18 @@ export function loadAndResolvePluginsForServe(
plugins: Array<string | BunPlugin>,
bunfig_folder: string,
runSetupFn: typeof runSetupFunction,
isDevServer: boolean,
reactFastRefresh: boolean,
jsxDevelopment: boolean,
) {
// Same config as created in HTMLBundle.init
let config: SetupConfig = {
experimentalCss: true,
experimentalHtml: true,
target: "browser",
root: bunfig_folder,
reactFastRefresh,
jsx: { development: jsxDevelopment },
};

class InvalidBundlerPluginError extends TypeError {
Expand Down Expand Up @@ -111,6 +119,7 @@ export function loadAndResolvePluginsForServe(
onstart_promises_array,
i === plugins.length - 1,
false,
isDevServer,
]);
}
if (onstart_promises_array !== undefined) {
Expand All @@ -121,6 +130,30 @@ export function loadAndResolvePluginsForServe(
return promiseResult;
}

export function runOnStartCallbacks(this: BundlerPlugin): Promise<void> | void {
const callbacks = this.$onStartCallbacks;
if (!callbacks) {
return;
}
const promises: Promise<void>[] = [];
for (const callback of callbacks) {
try {
$arrayPush(promises, Promise.$resolve(callback()));
} catch (error) {
$arrayPush(promises, Promise.$reject(error));
}
}
if (promises.length > 0) {
return Promise.allSettled(promises).then(results => {
for (const result of results) {
if (result.status === "rejected") {
throw result.reason;
}
}
});
}
}

export function runOnEndCallbacks(
this: BundlerPlugin,
promise: Promise<Bun.BuildOutput>,
Expand Down Expand Up @@ -166,7 +199,8 @@ export function runOnEndCallbacks(
* The `setup` function may define `onLoad`, `onResolve`, `onBeforeParse`, `onStart` callbacks.
* Those callbacks will be added to the `BundlerPlugin` via cpp functions like `.addFilter()` (see JSBundlerPlugin.cpp)
*
* Any `onStart()` callbacks are invoked here and their promises appended to the `promises` array parameter.
* Dev server `onStart()` callbacks are retained for the native bundle preparation barrier.
* Other `onStart()` callbacks are invoked here and their promises appended to the `promises` array parameter.
* The `promises` parameter remains undefined if no `onStart()` callbacks are defined.
*/
export function runSetupFunction(
Expand All @@ -176,6 +210,7 @@ export function runSetupFunction(
promises: Array<Promise<any>> | undefined,
is_last: boolean,
isBake: boolean,
isDevServer: boolean = false,
): Promise<Promise<any>[] | undefined> | Promise<any>[] | undefined {
this.promises = promises;
var onLoadPlugins = new Map<string, [filter: RegExp, callback: OnLoadCallback][]>();
Expand Down Expand Up @@ -267,13 +302,19 @@ export function runSetupFunction(

const self = this;
function onStart(this: PluginBuilderExt, callback): PluginBuilderExt {
if (isBake) {
if (isBake && !isDevServer) {
throw new TypeError("onStart() is not supported in Bake yet");
}
if (!$isCallable(callback)) {
throw new TypeError("callback must be a function");
}

if (isDevServer) {
self.$onStartCallbacks ??= [];
$arrayPush(self.$onStartCallbacks, callback);
return this;
}

const ret = callback();
if ($isPromise(ret)) {
if ($peekPromiseStatus(ret) != 1) {
Expand Down Expand Up @@ -597,7 +638,24 @@ export function runOnLoadPlugins(
throw new TypeError(`Loader ${loader} is not supported.`);
}

this.onLoadAsync(internalID, contents as any, chosenLoader);
const { watchFiles, watchDirs } = result;
for (const [name, paths] of [
["watchFiles", watchFiles],
["watchDirs", watchDirs],
]) {
if (paths === undefined) {
continue;
}
if (!$isJSArray(paths)) {
throw new TypeError(`onLoad plugins '${name}' field must be an array`);
}
for (const watchPath of paths) {
if (typeof watchPath !== "string" || !require("node:path").isAbsolute(watchPath)) {
throw new TypeError(`onLoad plugins '${name}' entries must be absolute paths`);
}
}
}
this.onLoadAsync(internalID, contents as any, chosenLoader, watchFiles, watchDirs);
return null;
}
}
Expand Down
29 changes: 25 additions & 4 deletions src/jsc/bindings/JSBundlerPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ extern "C" void OnBeforeParseResult__reset(OnBeforeParseResult* result);

/// These are native callbacks to be run after their associated JS version is run
extern "C" void JSBundlerPlugin__addError(void*, void*, JSC::EncodedJSValue, uint8_t);
extern "C" void JSBundlerPlugin__onLoadAsync(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue);
extern "C" void JSBundlerPlugin__onLoadAsync(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue);
extern "C" void JSBundlerPlugin__onResolveAsync(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue);
extern "C" JSC::EncodedJSValue JSBundlerPlugin__onDefer(void*, JSC::JSGlobalObject*);

Expand Down Expand Up @@ -475,7 +475,9 @@ JSC_DEFINE_HOST_FUNCTION(jsBundlerPluginFunction_onLoadAsync, (JSC::JSGlobalObje
UNWRAP_BUNDLER_PLUGIN(callFrame),
thisObject->plugin.config,
JSValue::encode(callFrame->argument(1)),
JSValue::encode(callFrame->argument(2)));
JSValue::encode(callFrame->argument(2)),
JSValue::encode(callFrame->argument(3)),
JSValue::encode(callFrame->argument(4)));
}

return JSC::JSValue::encode(JSC::jsUndefined());
Expand Down Expand Up @@ -560,6 +562,7 @@ void JSBundlerPlugin::finishCreation(JSC::VM& vm)
this->putDirect(vm, Identifier::fromString(vm, String("onLoad"_s)), jsUndefined(), 0);
this->putDirect(vm, Identifier::fromString(vm, String("onResolve"_s)), jsUndefined(), 0);
this->putDirect(vm, Identifier::fromString(vm, String("onEndCallbacks"_s)), jsUndefined(), 0);
this->putDirect(vm, Identifier::fromString(vm, String("onStartCallbacks"_s)), jsUndefined(), 0);
this->putDirect(vm, Identifier::fromString(vm, String("promises"_s)), jsUndefined(), 0);
Bun::reifyStaticPropertyTable(vm, JSBundlerPlugin::info(), JSBundlerPluginHashTable, *this);
}
Expand Down Expand Up @@ -650,7 +653,7 @@ extern "C" Bun::JSBundlerPlugin* JSBundlerPlugin__create(Zig::GlobalObject* glob
target);
}

extern "C" JSC::EncodedJSValue JSBundlerPlugin__loadAndResolvePluginsForServe(Bun::JSBundlerPlugin* plugin, JSC::EncodedJSValue encodedPlugins, JSC::EncodedJSValue encodedBunfigFolder)
extern "C" JSC::EncodedJSValue JSBundlerPlugin__loadAndResolvePluginsForServe(Bun::JSBundlerPlugin* plugin, JSC::EncodedJSValue encodedPlugins, JSC::EncodedJSValue encodedBunfigFolder, bool isDevServer, bool reactFastRefresh, bool jsxDevelopment)
{
auto& vm = plugin->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Expand All @@ -667,6 +670,9 @@ extern "C" JSC::EncodedJSValue JSBundlerPlugin__loadAndResolvePluginsForServe(Bu
arguments.append(JSValue::decode(encodedPlugins));
arguments.append(JSValue::decode(encodedBunfigFolder));
arguments.append(runSetupFn);
arguments.append(jsBoolean(isDevServer));
arguments.append(jsBoolean(reactFastRefresh));
arguments.append(jsBoolean(jsxDevelopment));

RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::profiledCall(plugin->globalObject(), ProfilingReason::API, loadAndResolvePluginsForServeBuiltinFn, callData, plugin, arguments)));
}
Expand All @@ -677,7 +683,8 @@ extern "C" JSC::EncodedJSValue JSBundlerPlugin__runSetupFunction(
JSC::EncodedJSValue encodedConfig,
JSC::EncodedJSValue encodedOnstartPromisesArray,
JSC::EncodedJSValue encodedIsLast,
JSC::EncodedJSValue encodedIsBake)
JSC::EncodedJSValue encodedIsBake,
bool isDevServer)
{
auto& vm = plugin->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Expand All @@ -696,6 +703,7 @@ extern "C" JSC::EncodedJSValue JSBundlerPlugin__runSetupFunction(
arguments.append(JSValue::decode(encodedOnstartPromisesArray));
arguments.append(JSValue::decode(encodedIsLast));
arguments.append(JSValue::decode(encodedIsBake));
arguments.append(jsBoolean(isDevServer));
// setup() can be any callable, and a callable Proxy isn't a JSFunction.
auto* setupObject = JSValue::decode(encodedSetupFunction).getObject();
auto* lexicalGlobalObject = setupObject ? setupObject->globalObject() : plugin->globalObject();
Expand Down Expand Up @@ -749,6 +757,19 @@ extern "C" void JSBundlerPlugin__freezeFilters(Bun::JSBundlerPlugin* plugin)
plugin->plugin.filtersFrozen = true;
}

extern "C" JSC::EncodedJSValue JSBundlerPlugin__runOnStartCallbacks(Bun::JSBundlerPlugin* plugin)
{
auto& vm = plugin->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* globalObject = plugin->globalObject();
auto* callback = JSC::JSFunction::create(vm, globalObject,
WebCore::bundlerPluginRunOnStartCallbacksCodeGenerator(vm), globalObject);
MarkedArgumentBuffer arguments;
auto result = JSC::profiledCall(globalObject, ProfilingReason::API, callback, JSC::getCallData(callback), plugin, arguments);
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(result);
}

extern "C" JSC::EncodedJSValue JSBundlerPlugin__runOnEndCallbacks(Bun::JSBundlerPlugin* plugin, JSC::EncodedJSValue encodedBuildPromise, JSC::EncodedJSValue encodedBuildResult, JSC::EncodedJSValue encodedRejection)
{
auto& vm = plugin->vm();
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/JSBundlerPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#include <wtf/HashMap.h>

typedef void (*JSBundlerPluginAddErrorCallback)(void*, void*, JSC::EncodedJSValue, uint8_t);
typedef void (*JSBundlerPluginOnLoadAsyncCallback)(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue);
typedef void (*JSBundlerPluginOnLoadAsyncCallback)(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue);
typedef void (*JSBundlerPluginOnResolveAsyncCallback)(void*, void*, JSC::EncodedJSValue, JSC::EncodedJSValue, JSC::EncodedJSValue);
typedef void (*JSBundlerPluginNativeOnBeforeParseCallback)(const OnBeforeParseArguments*, OnBeforeParseResult*);

Expand Down
4 changes: 4 additions & 0 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4495,6 +4495,10 @@ GlobalObject::PromiseFunctions GlobalObject::promiseHandlerID(Zig::FFIFunction h
return GlobalObject::PromiseFunctions::BunServe__Plugins__onResolve;
} else if (handler == BunServe__onRejectPlugins) {
return GlobalObject::PromiseFunctions::BunServe__Plugins__onReject;
} else if (handler == BunDevServer__onStartResolved) {
return GlobalObject::PromiseFunctions::BunDevServer__onStartResolved;
} else if (handler == BunDevServer__onStartRejected) {
return GlobalObject::PromiseFunctions::BunDevServer__onStartRejected;
} else if (handler == Bun__HTTPRequestContext__onReject) {
return GlobalObject::PromiseFunctions::Bun__HTTPRequestContext__onReject;
} else if (handler == Bun__HTTPRequestContext__onRejectStream) {
Expand Down
2 changes: 2 additions & 0 deletions src/jsc/bindings/ZigGlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ class GlobalObject : public Bun::GlobalScope {
enum class PromiseFunctions : uint8_t {
BunServe__Plugins__onResolve,
BunServe__Plugins__onReject,
BunDevServer__onStartResolved,
BunDevServer__onStartRejected,
Bun__HTTPRequestContext__onReject,
Bun__HTTPRequestContext__onRejectStream,
Bun__HTTPRequestContext__onResolve,
Expand Down
2 changes: 2 additions & 0 deletions src/jsc/bindings/headers.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading