bun-dev-server 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,12 @@ export interface BunDevServerConfig extends Partial<BunDevServerSocketConfig> {
3
3
  port: number;
4
4
  buildConfig: BuildConfig;
5
5
  watchDir: string;
6
+ /**
7
+ * The delay in milliseconds to wait before starting a new build once a file has changed.
8
+ * Used in debounce function, in case many changes happen in file system at once
9
+ * @default 1000
10
+ */
11
+ watchDelay?: number;
6
12
  enableTSC?: boolean;
7
13
  writeManifest?: boolean;
8
14
  manifestName?: string;
package/dist/index.js CHANGED
@@ -846,6 +846,84 @@ var require_picocolors = __commonJS((exports, module) => {
846
846
  module.exports.createColors = createColors;
847
847
  });
848
848
 
849
+ // node_modules/debounce/index.js
850
+ var require_debounce = __commonJS((exports, module) => {
851
+ function debounce(function_, wait = 100, options = {}) {
852
+ if (typeof function_ !== "function") {
853
+ throw new TypeError(`Expected the first parameter to be a function, got \`${typeof function_}\`.`);
854
+ }
855
+ if (wait < 0) {
856
+ throw new RangeError("`wait` must not be negative.");
857
+ }
858
+ const { immediate } = typeof options === "boolean" ? { immediate: options } : options;
859
+ let storedContext;
860
+ let storedArguments;
861
+ let timeoutId;
862
+ let timestamp;
863
+ let result;
864
+ function run() {
865
+ const callContext = storedContext;
866
+ const callArguments = storedArguments;
867
+ storedContext = undefined;
868
+ storedArguments = undefined;
869
+ result = function_.apply(callContext, callArguments);
870
+ return result;
871
+ }
872
+ function later() {
873
+ const last = Date.now() - timestamp;
874
+ if (last < wait && last >= 0) {
875
+ timeoutId = setTimeout(later, wait - last);
876
+ } else {
877
+ timeoutId = undefined;
878
+ if (!immediate) {
879
+ result = run();
880
+ }
881
+ }
882
+ }
883
+ const debounced = function(...arguments_) {
884
+ if (storedContext && this !== storedContext && Object.getPrototypeOf(this) === Object.getPrototypeOf(storedContext)) {
885
+ throw new Error("Debounced method called with different contexts of the same prototype.");
886
+ }
887
+ storedContext = this;
888
+ storedArguments = arguments_;
889
+ timestamp = Date.now();
890
+ const callNow = immediate && !timeoutId;
891
+ if (!timeoutId) {
892
+ timeoutId = setTimeout(later, wait);
893
+ }
894
+ if (callNow) {
895
+ result = run();
896
+ }
897
+ return result;
898
+ };
899
+ Object.defineProperty(debounced, "isPending", {
900
+ get() {
901
+ return timeoutId !== undefined;
902
+ }
903
+ });
904
+ debounced.clear = () => {
905
+ if (!timeoutId) {
906
+ return;
907
+ }
908
+ clearTimeout(timeoutId);
909
+ timeoutId = undefined;
910
+ };
911
+ debounced.flush = () => {
912
+ if (!timeoutId) {
913
+ return;
914
+ }
915
+ debounced.trigger();
916
+ };
917
+ debounced.trigger = () => {
918
+ result = run();
919
+ debounced.clear();
920
+ };
921
+ return debounced;
922
+ }
923
+ exports.debounce = debounce;
924
+ module.exports = debounce;
925
+ });
926
+
849
927
  // src/server.ts
850
928
  var import_ejs = __toESM(require_ejs(), 1);
851
929
  var Bun = globalThis.Bun;
@@ -892,26 +970,36 @@ import { watch, readdir, exists, readFile as readFile2 } from "fs/promises";
892
970
 
893
971
  // src/bunClientHmr.ts
894
972
  function hotReload() {
895
- if (window.BUN_HMR_INITED) {
973
+ if (!window.BUN_DEV_SERVER) {
974
+ window.BUN_DEV_SERVER = [];
975
+ }
976
+ const devServer = "[Bun Dev Server]";
977
+ const connectAddress = "[REPLACE_ENDPOINT]";
978
+ let foundServer = window.BUN_DEV_SERVER.find((server) => server.url === connectAddress);
979
+ if (!foundServer) {
980
+ foundServer = { url: connectAddress, socket: null };
981
+ }
982
+ if (foundServer.socket) {
896
983
  return;
897
984
  }
898
- window.BUN_HMR_INITED = true;
899
- const hmrSock = new WebSocket("[REPLACE_ENDPOINT]");
900
- hmrSock.addEventListener("error", (err) => {
901
- console.error("HMR ERROR", err);
902
- });
903
- hmrSock.addEventListener("message", (msg) => {
985
+ console.log(devServer, "Connecting to Bun Dev Server at", connectAddress);
986
+ foundServer.socket = new WebSocket(connectAddress);
987
+ window.BUN_DEV_SERVER.push(foundServer);
988
+ function errorHandler(err) {
989
+ console.error(devServer, "ERROR", err);
990
+ }
991
+ function messageHandler(msg) {
904
992
  let parsed = msg.data;
905
993
  try {
906
994
  parsed = JSON.parse(msg.data);
907
995
  } catch (e) {
908
996
  }
909
997
  if (parsed?.type === "message") {
910
- console.log(parsed.message);
998
+ console.log(devServer, parsed.message);
911
999
  return;
912
1000
  }
913
1001
  if (parsed?.type === "output") {
914
- console.table(parsed.message);
1002
+ console.table(devServer, parsed.message);
915
1003
  return;
916
1004
  }
917
1005
  if (parsed?.type === "reload") {
@@ -932,7 +1020,26 @@ function hotReload() {
932
1020
  }
933
1021
  return;
934
1022
  }
935
- });
1023
+ }
1024
+ function closeHandler(ev) {
1025
+ console.warn(devServer, "Connection closed. Will retry in 5 seconds...");
1026
+ foundServer.socket?.removeEventListener("error", errorHandler);
1027
+ foundServer.socket?.removeEventListener("message", messageHandler);
1028
+ foundServer.socket?.removeEventListener("open", messageHandler);
1029
+ foundServer.socket?.removeEventListener("close", closeHandler);
1030
+ foundServer.socket = null;
1031
+ setTimeout(function() {
1032
+ console.log(devServer, "Attempting to reconnect...");
1033
+ hotReload();
1034
+ }, 5000);
1035
+ }
1036
+ function openHandler(ev) {
1037
+ console.log(devServer, "Connected to Bun Dev Server");
1038
+ }
1039
+ foundServer.socket.addEventListener("error", errorHandler);
1040
+ foundServer.socket.addEventListener("message", messageHandler);
1041
+ foundServer.socket.addEventListener("close", closeHandler);
1042
+ foundServer.socket.addEventListener("open", openHandler);
936
1043
  }
937
1044
  var DEFAULT_HMR_PATH = "/hmr-ws";
938
1045
  function bunHotReload(bunServerConfig) {
@@ -1013,14 +1120,19 @@ async function performTSC(finalConfig) {
1013
1120
  const tsc = await $`tsc`.nothrow().quiet();
1014
1121
  if (tsc.exitCode === 0) {
1015
1122
  console.log(import_picocolors.default.bgGreen("\u2714 [SUCCESS]"), "TSC check passed");
1123
+ return true;
1016
1124
  } else {
1017
1125
  console.log(import_picocolors.default.bgRed("\u2718 [ERROR]"), `\r
1018
1126
  ${tsc.stdout.toString()}`);
1127
+ return false;
1019
1128
  }
1020
1129
  }
1130
+ return true;
1021
1131
  }
1022
1132
 
1023
1133
  // src/server.ts
1134
+ var import_debounce = __toESM(require_debounce(), 1);
1135
+ var watchDelay = 1000;
1024
1136
  async function startBunDevServer(serverConfig) {
1025
1137
  const defaultConfig = {
1026
1138
  port: 3000,
@@ -1029,20 +1141,23 @@ async function startBunDevServer(serverConfig) {
1029
1141
  serveOutputHtml: indexHTMLTemplate_default
1030
1142
  };
1031
1143
  const finalConfig = { ...defaultConfig, ...serverConfig };
1144
+ if (finalConfig.watchDelay) {
1145
+ watchDelay = finalConfig.watchDelay;
1146
+ }
1032
1147
  if (!finalConfig.watchDir) {
1033
1148
  throw new Error("watchDir must be set");
1034
1149
  }
1035
1150
  const serveDestination = finalConfig.buildConfig.outdir ?? finalConfig.servePath ?? "dist";
1036
1151
  const bunDestinationPath = Bun.pathToFileURL(serveDestination);
1037
1152
  const bunWatchDirPath = Bun.pathToFileURL(finalConfig.watchDir);
1038
- const dst = process.platform === "win32" ? bunDestinationPath.pathname.substring(1) : bunDestinationPath.pathname;
1153
+ const destinationPath = process.platform === "win32" ? bunDestinationPath.pathname.substring(1) : bunDestinationPath.pathname;
1039
1154
  const srcWatch = process.platform === "win32" ? bunWatchDirPath.pathname.substring(1) : bunWatchDirPath.pathname;
1040
1155
  try {
1041
- await readdir(dst);
1156
+ await readdir(destinationPath);
1042
1157
  } catch (e) {
1043
1158
  if (e.code === "ENOENT") {
1044
1159
  console.log("Directory not found, creating it...");
1045
- await $2`mkdir ${dst}`;
1160
+ await $2`mkdir ${destinationPath}`;
1046
1161
  } else {
1047
1162
  throw e;
1048
1163
  }
@@ -1050,7 +1165,7 @@ async function startBunDevServer(serverConfig) {
1050
1165
  const buncfg = { port: finalConfig.port, tls: finalConfig.tls, websocketPath: finalConfig.websocketPath };
1051
1166
  const buildCfg = {
1052
1167
  ...serverConfig.buildConfig,
1053
- outdir: dst
1168
+ outdir: destinationPath
1054
1169
  };
1055
1170
  if (finalConfig.hotReload === "footer") {
1056
1171
  if (!buildCfg.footer) {
@@ -1065,7 +1180,7 @@ async function startBunDevServer(serverConfig) {
1065
1180
  buildCfg.plugins.push(bunHotReloadPlugin(buncfg));
1066
1181
  }
1067
1182
  if (serverConfig.cleanServePath) {
1068
- await cleanDirectory(dst);
1183
+ await cleanDirectory(destinationPath);
1069
1184
  }
1070
1185
  console.log("Starting Bun Dev Server on port", finalConfig.port);
1071
1186
  const bunServer = Bun.serve({
@@ -1090,11 +1205,11 @@ async function startBunDevServer(serverConfig) {
1090
1205
  }
1091
1206
  const url = new URL(req.url);
1092
1207
  const requestPath = url.pathname;
1093
- const objThere = await exists(dst + requestPath);
1208
+ const objThere = await exists(destinationPath + requestPath);
1094
1209
  let isDirectory = false;
1095
1210
  if (objThere) {
1096
1211
  try {
1097
- await readFile2(dst + requestPath);
1212
+ await readFile2(destinationPath + requestPath);
1098
1213
  } catch (e) {
1099
1214
  if (e.code === "EISDIR") {
1100
1215
  isDirectory = true;
@@ -1109,7 +1224,7 @@ async function startBunDevServer(serverConfig) {
1109
1224
  }
1110
1225
  if (!isDirectory) {
1111
1226
  try {
1112
- const fl = Bun.file(dst + requestPath);
1227
+ const fl = Bun.file(destinationPath + requestPath);
1113
1228
  const response = new Response(fl);
1114
1229
  augumentHeaders(req, response);
1115
1230
  return response;
@@ -1124,7 +1239,7 @@ async function startBunDevServer(serverConfig) {
1124
1239
  }
1125
1240
  }
1126
1241
  try {
1127
- const allEntries = await readdir(dst + requestPath, {
1242
+ const allEntries = await readdir(destinationPath + requestPath, {
1128
1243
  withFileTypes: true
1129
1244
  });
1130
1245
  const dirs = allEntries.filter((entry) => entry.isDirectory()).map((entry) => {
@@ -1158,55 +1273,52 @@ async function startBunDevServer(serverConfig) {
1158
1273
  sendPings: true
1159
1274
  }
1160
1275
  });
1161
- const output = await Bun.build(buildCfg);
1162
- publishOutputLogs(output, { filename: "Initial", eventType: "change" });
1163
- publishIndexHTML(output, { filename: "Initial", eventType: "change" });
1164
- if (finalConfig.writeManifest) {
1165
- writeManifest(output, dst, finalConfig.manifestWithHash, finalConfig.manifestName);
1166
- }
1167
- await performTSC(finalConfig);
1276
+ debouncedbuildAndNotify(finalConfig, destinationPath, buildCfg, bunServer, { filename: "Initial", eventType: "change" });
1168
1277
  const watcher = watch(srcWatch, { recursive: true });
1169
1278
  for await (const event of watcher) {
1170
- if (finalConfig.cleanServePath) {
1171
- await cleanDirectory(dst);
1172
- }
1173
- const output2 = await Bun.build(buildCfg);
1174
- publishOutputLogs(output2, event);
1175
- publishIndexHTML(output2, event);
1176
- if (finalConfig.writeManifest) {
1177
- writeManifest(output2, dst, finalConfig.manifestWithHash, finalConfig.manifestName);
1178
- }
1179
- await performTSC(finalConfig);
1180
- if (finalConfig.reloadOnChange) {
1181
- bunServer.publish("message", JSON.stringify({ type: "reload" }));
1182
- }
1279
+ debouncedbuildAndNotify(finalConfig, destinationPath, buildCfg, bunServer, event);
1183
1280
  }
1184
- function publishOutputLogs(output2, event) {
1185
- output2.logs.forEach(console.log);
1186
- bunServer.publish("message", JSON.stringify({ type: "message", message: `[Bun HMR] ${event.filename} ${event.eventType}` }));
1187
- const outTable = output2.outputs.filter((o) => o.kind !== "sourcemap").map((o) => {
1188
- const a = Bun.pathToFileURL(o.path);
1189
- const fileName = a.href.substring(a.href.lastIndexOf("/") + 1);
1190
- return {
1191
- name: fileName,
1192
- path: o.path,
1193
- size: convertBytes(o.size)
1194
- };
1195
- });
1196
- console.table(outTable);
1197
- bunServer.publish("message", JSON.stringify({ type: "output", message: outTable }));
1281
+ }
1282
+ var debouncedbuildAndNotify = import_debounce.default(async (finalConfig, destinationPath, buildCfg, bunServer, event) => {
1283
+ if (finalConfig.cleanServePath) {
1284
+ await cleanDirectory(destinationPath);
1198
1285
  }
1199
- function publishIndexHTML(output2, event) {
1200
- const eps = output2.outputs.filter((o) => o.kind === "entry-point");
1201
- const hashedImports = [];
1202
- for (const ep of eps) {
1203
- const basePathUrl = Bun.pathToFileURL(dst);
1204
- const epUrl = Bun.pathToFileURL(ep.path);
1205
- const hashedImport = `${epUrl.href.replace(basePathUrl.href, "")}?${ep.hash}`;
1206
- hashedImports.push(hashedImport);
1207
- }
1208
- Bun.write(dst + "/index.html", import_ejs.render(finalConfig.serveOutputHtml, { hashedImports }));
1286
+ const output = await Bun.build(buildCfg);
1287
+ publishOutputLogs(bunServer, output, event);
1288
+ publishIndexHTML(destinationPath, finalConfig.serveOutputHtml, output, event);
1289
+ if (finalConfig.writeManifest) {
1290
+ writeManifest(output, destinationPath, finalConfig.manifestWithHash, finalConfig.manifestName);
1291
+ }
1292
+ const tscSuccess = await performTSC(finalConfig);
1293
+ if (finalConfig.reloadOnChange && tscSuccess) {
1294
+ bunServer.publish("message", JSON.stringify({ type: "reload" }));
1295
+ }
1296
+ }, watchDelay, { immediate: true });
1297
+ function publishOutputLogs(bunServer, output, event) {
1298
+ output.logs.forEach(console.log);
1299
+ bunServer.publish("message", JSON.stringify({ type: "message", message: `[Bun HMR] ${event.filename} ${event.eventType}` }));
1300
+ const outTable = output.outputs.filter((o) => o.kind !== "sourcemap").map((o) => {
1301
+ const a = Bun.pathToFileURL(o.path);
1302
+ const fileName = a.href.substring(a.href.lastIndexOf("/") + 1);
1303
+ return {
1304
+ name: fileName,
1305
+ path: o.path,
1306
+ size: convertBytes(o.size)
1307
+ };
1308
+ });
1309
+ console.table(outTable);
1310
+ bunServer.publish("message", JSON.stringify({ type: "output", message: outTable }));
1311
+ }
1312
+ function publishIndexHTML(destinationPath, template, output, _event) {
1313
+ const eps = output.outputs.filter((o) => o.kind === "entry-point");
1314
+ const hashedImports = [];
1315
+ for (const ep of eps) {
1316
+ const basePathUrl = Bun.pathToFileURL(destinationPath);
1317
+ const epUrl = Bun.pathToFileURL(ep.path);
1318
+ const hashedImport = `${epUrl.href.replace(basePathUrl.href, "")}?${ep.hash}`;
1319
+ hashedImports.push(hashedImport);
1209
1320
  }
1321
+ Bun.write(destinationPath + "/index.html", import_ejs.render(template, { hashedImports }));
1210
1322
  }
1211
1323
  function augumentHeaders(request, response) {
1212
1324
  response.headers.set("Access-Control-Allow-Origin", request.headers.get("origin") ?? "*");
@@ -1,2 +1,2 @@
1
1
  import type { BunDevServerConfig } from "./bunServeConfig";
2
- export declare function performTSC(finalConfig: BunDevServerConfig): Promise<void>;
2
+ export declare function performTSC(finalConfig: BunDevServerConfig): Promise<boolean>;
package/package.json CHANGED
@@ -9,6 +9,8 @@
9
9
  "keywords": [
10
10
  "bun",
11
11
  "devserver",
12
+ "dev",
13
+ "server",
12
14
  "hmr",
13
15
  "reload",
14
16
  "hot"
@@ -19,7 +21,7 @@
19
21
  "exports": {
20
22
  ".": "./dist/index.js"
21
23
  },
22
- "version": "0.4.0",
24
+ "version": "0.5.0",
23
25
  "module": "index.ts",
24
26
  "type": "module",
25
27
  "license": "MIT",
@@ -27,14 +29,14 @@
27
29
  "serve": "bun --hot ./serve.ts"
28
30
  },
29
31
  "devDependencies": {
30
- "@types/bun": "latest",
31
- "bun": "^1.1.36"
32
+ "@types/bun": "latest"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "typescript": "^5.7.2"
35
36
  },
36
37
  "dependencies": {
37
38
  "@types/ejs": "^3.1.5",
39
+ "debounce": "^2.2.0",
38
40
  "ejs": "^3.1.10",
39
41
  "picocolors": "^1.1.1"
40
42
  }