bun-dev-server 1.0.1 → 1.0.2

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.
@@ -8,13 +8,18 @@ import { type BunDevServerConfig } from "./bunServeConfig";
8
8
  * @returns A p-queue instance configured for throttled builds
9
9
  */
10
10
  export declare function getThrottledBuildQueue(serverConfig: BunDevServerConfig): pqueue;
11
+ export interface BuildEnvPaths {
12
+ buildDestination: string;
13
+ serveDestination: string;
14
+ watchDestination: string;
15
+ }
11
16
  /**
12
17
  * Build and notify clients about the build result
13
18
  * @param importerMeta - The ImportMeta object from the caller
14
19
  * @param finalConfig - The final server configuration
15
- * @param destinationPath - The absolute path to the output directory
20
+ * @param buildDestination - The absolute path to the output directory
16
21
  * @param buildCfg - The build configuration
17
22
  * @param bunServer - The Bun server instance
18
23
  * @param event - The file change event that triggered the build
19
24
  */
20
- export declare function cleanBuildAndNotify(importerMeta: ImportMeta, finalConfig: BunDevServerConfig, destinationPath: string, buildCfg: BuildConfig, bunServer: Server<any>, event: FileChangeInfo<string>): Promise<void>;
25
+ export declare function cleanBuildAndNotify(importerMeta: ImportMeta, finalConfig: BunDevServerConfig, paths: BuildEnvPaths, buildCfg: BuildConfig, bunServer: Server<any>, event: FileChangeInfo<string>): Promise<void>;
@@ -3,7 +3,9 @@ import type { FileChangeInfo } from "fs/promises";
3
3
  export interface BuildEnv {
4
4
  importerMeta: ImportMeta;
5
5
  finalConfig: BunDevServerConfig;
6
- destinationPath: string;
6
+ buildDestination: string;
7
+ serveDestination: string;
8
+ watchDestination: string;
7
9
  buildCfg: BuildConfig;
8
10
  bunServer: Server<any>;
9
11
  event: FileChangeInfo<any>;
@@ -98,6 +100,13 @@ export interface BunDevServerConfig extends Partial<BunServeConfig> {
98
100
  * Whether to clean the `servePath` before building a new batch.
99
101
  */
100
102
  cleanServePath?: boolean;
103
+ /**
104
+ * Whether to clean the `outdir` before building a new batch.
105
+ *
106
+ * If `outdir` is under `servePath` and `cleanServePath` is true,
107
+ * then `outdir` will be cleaned regardless.
108
+ */
109
+ cleanBuildPath?: boolean;
101
110
  /**
102
111
  * The EJS template used for the output of the `/` path on the server.
103
112
  * When supplying your own template, the properties that are provided during rendering are `files` and `dirs` both are arrays.
@@ -5,8 +5,9 @@ import { type BuildConfig } from "bun";
5
5
  import { type BunDevServerConfig } from "./bunServeConfig";
6
6
  export interface PreparedConfig {
7
7
  finalConfig: BunDevServerConfig;
8
- destinationPath: string;
9
- srcWatch: string;
8
+ serveDestination: string;
9
+ buildDestination: string;
10
+ watchDestination: string;
10
11
  buildCfg: BuildConfig;
11
12
  }
12
13
  /**
@@ -1,12 +1,13 @@
1
1
  import { type BuildConfig, type Server } from "bun";
2
2
  import { type BunDevServerConfig } from "./bunServeConfig";
3
+ import { type BuildEnvPaths } from "./buildManager";
3
4
  /**
4
5
  * Start watching a directory for changes and trigger builds
5
6
  * @param srcWatch - The absolute path to watch for changes
6
7
  * @param importMeta - The ImportMeta object from the caller
7
8
  * @param finalConfig - The final server configuration
8
- * @param destinationPath - The absolute path to the output directory
9
+ * @param buildDestination - The absolute path to the output directory
9
10
  * @param buildCfg - The build configuration
10
11
  * @param bunServer - The Bun server instance
11
12
  */
12
- export declare function startFileWatcher(srcWatch: string, importMeta: ImportMeta, finalConfig: BunDevServerConfig, destinationPath: string, buildCfg: BuildConfig, bunServer: Server<any>): Promise<void>;
13
+ export declare function startFileWatcher(srcWatch: string, importMeta: ImportMeta, finalConfig: BunDevServerConfig, paths: BuildEnvPaths, buildCfg: BuildConfig, bunServer: Server<any>): Promise<void>;
package/dist/index.js CHANGED
@@ -1044,6 +1044,15 @@ function withCORSHeaders(response, request) {
1044
1044
  // src/utils/filesystem.ts
1045
1045
  var {$ } = globalThis.Bun;
1046
1046
  import { access, constants } from "fs/promises";
1047
+ import { mkdir } from "fs/promises";
1048
+ async function ensureDestinationDirectory(destinationPath) {
1049
+ try {
1050
+ await mkdir(destinationPath, { recursive: true });
1051
+ } catch (e) {
1052
+ console.error("Unable to create directory", e);
1053
+ throw e;
1054
+ }
1055
+ }
1047
1056
  async function cleanDirectory(dst) {
1048
1057
  const { stderr, exitCode } = await $`rm -rf ${dst}/*`.nothrow();
1049
1058
  if (exitCode !== 0) {
@@ -1496,13 +1505,6 @@ class PQueue extends import__.default {
1496
1505
  this.#concurrency = newConcurrency;
1497
1506
  this.#processQueue();
1498
1507
  }
1499
- async#throwOnAbort(signal) {
1500
- return new Promise((_resolve, reject) => {
1501
- signal.addEventListener("abort", () => {
1502
- reject(signal.reason);
1503
- }, { once: true });
1504
- });
1505
- }
1506
1508
  setPriority(id, priority) {
1507
1509
  if (typeof priority !== "number" || !Number.isFinite(priority)) {
1508
1510
  throw new TypeError(`Expected \`priority\` to be a finite number, got \`${priority}\` (${typeof priority})`);
@@ -1525,6 +1527,7 @@ class PQueue extends import__.default {
1525
1527
  startTime: Date.now(),
1526
1528
  timeout: options.timeout
1527
1529
  });
1530
+ let eventListener;
1528
1531
  try {
1529
1532
  try {
1530
1533
  options.signal?.throwIfAborted();
@@ -1543,7 +1546,13 @@ class PQueue extends import__.default {
1543
1546
  });
1544
1547
  }
1545
1548
  if (options.signal) {
1546
- operation = Promise.race([operation, this.#throwOnAbort(options.signal)]);
1549
+ const { signal } = options;
1550
+ operation = Promise.race([operation, new Promise((_resolve, reject2) => {
1551
+ eventListener = () => {
1552
+ reject2(signal.reason);
1553
+ };
1554
+ signal.addEventListener("abort", eventListener, { once: true });
1555
+ })]);
1547
1556
  }
1548
1557
  const result = await operation;
1549
1558
  resolve2(result);
@@ -1552,6 +1561,9 @@ class PQueue extends import__.default {
1552
1561
  reject(error);
1553
1562
  this.emit("error", error);
1554
1563
  } finally {
1564
+ if (eventListener) {
1565
+ options.signal?.removeEventListener("abort", eventListener);
1566
+ }
1555
1567
  this.#runningTasks.delete(taskSymbol);
1556
1568
  queueMicrotask(() => {
1557
1569
  this.#next();
@@ -1729,7 +1741,7 @@ function writeManifest(output, outdir, withHash = false, manifestName = "bun_ser
1729
1741
  for (const ep of entryPoints) {
1730
1742
  const basePathUrl = pathToFileURL(outdir);
1731
1743
  const epUrl = pathToFileURL(ep.path);
1732
- const relativePath = epUrl.href.replace(`${basePathUrl.href}/`, "");
1744
+ const relativePath = epUrl.href.replace(`${basePathUrl.href}/`, "./");
1733
1745
  const hashedImport = `${relativePath}${withHash ? `?${ep.hash}` : ``}`;
1734
1746
  epTable.push(hashedImport);
1735
1747
  }
@@ -1746,30 +1758,25 @@ function getThrottledBuildQueue(serverConfig) {
1746
1758
  carryoverConcurrencyCount: true
1747
1759
  });
1748
1760
  }
1749
- async function cleanBuildAndNotify(importerMeta, finalConfig, destinationPath, buildCfg, bunServer, event) {
1761
+ async function cleanBuildAndNotify(importerMeta, finalConfig, paths, buildCfg, bunServer, event) {
1750
1762
  const buildEnv = {
1751
1763
  importerMeta,
1752
1764
  finalConfig,
1753
- destinationPath,
1765
+ ...paths,
1754
1766
  buildCfg,
1755
1767
  bunServer,
1756
1768
  event
1757
1769
  };
1758
1770
  await finalConfig.beforeBuild?.(buildEnv);
1759
1771
  try {
1760
- if (buildCfg.outdir) {
1761
- const outdirExists = await checkObjectExists(buildCfg.outdir);
1762
- if (!outdirExists) {
1763
- return;
1764
- }
1765
- }
1772
+ await ensureDestinationDirectory(paths.buildDestination);
1766
1773
  const output = await build(buildCfg);
1767
1774
  publishOutputLogs(bunServer, output, finalConfig, event);
1768
1775
  if (finalConfig.createIndexHTML) {
1769
- publishIndexHTML(destinationPath, finalConfig.serveIndexHtmlEjs, output, event);
1776
+ publishIndexHTML(paths, finalConfig.serveIndexHtmlEjs, output, event);
1770
1777
  }
1771
1778
  if (finalConfig.writeManifest) {
1772
- writeManifest(output, destinationPath, finalConfig.manifestWithHash, finalConfig.manifestName);
1779
+ writeManifest(output, paths.serveDestination, finalConfig.manifestWithHash, finalConfig.manifestName);
1773
1780
  }
1774
1781
  await finalConfig.afterBuild?.(output, buildEnv);
1775
1782
  if (finalConfig.reloadOnChange && !finalConfig.waitForTSCSuccessBeforeReload) {
@@ -1816,23 +1823,23 @@ function publishOutputLogs(bunServer, output, config, event) {
1816
1823
  bunServer.publish("message", JSON.stringify({ type: "output", message: outTable }));
1817
1824
  }
1818
1825
  }
1819
- function publishIndexHTML(destinationPath, template, output, _event) {
1826
+ function publishIndexHTML(paths, template, output, _event) {
1820
1827
  const eps = output.outputs.filter((o) => o.kind === "entry-point");
1821
1828
  const hashedImports = [];
1822
1829
  const cssFiles = [];
1823
- const basePathUrl = Bun.pathToFileURL(destinationPath);
1830
+ const basePathUrl = Bun.pathToFileURL(paths.serveDestination);
1824
1831
  for (const ep of eps) {
1825
1832
  const epUrl = Bun.pathToFileURL(ep.path);
1826
- const hashedImport = `${epUrl.href.replace(basePathUrl.href, "")}?${ep.hash}`;
1833
+ const hashedImport = `${epUrl.href.replace(`${basePathUrl.href}/`, "./")}?${ep.hash}`;
1827
1834
  hashedImports.push(hashedImport);
1828
1835
  }
1829
1836
  const cssOutputs = output.outputs.filter((o) => o.path.endsWith(".css"));
1830
1837
  for (const cssFile of cssOutputs) {
1831
1838
  const cssUrl = Bun.pathToFileURL(cssFile.path);
1832
- const hashedCss = `${cssUrl.href.replace(basePathUrl.href, "")}?${cssFile.hash}`;
1839
+ const hashedCss = `${cssUrl.href.replace(`${basePathUrl.href}/`, "./")}?${cssFile.hash}`;
1833
1840
  cssFiles.push(hashedCss);
1834
1841
  }
1835
- Bun.write(destinationPath + "/index.html", import_ejs2.render(template, { hashedImports, cssFiles }));
1842
+ Bun.write(paths.buildDestination + "/index.html", import_ejs2.render(template, { hashedImports, cssFiles }));
1836
1843
  }
1837
1844
  function printPrettyBuildOutput(outTable) {
1838
1845
  if (outTable.length === 0)
@@ -1851,10 +1858,10 @@ function printPrettyBuildOutput(outTable) {
1851
1858
  }
1852
1859
 
1853
1860
  // src/fileWatcher.ts
1854
- async function startFileWatcher(srcWatch, importMeta, finalConfig, destinationPath, buildCfg, bunServer) {
1861
+ async function startFileWatcher(srcWatch, importMeta, finalConfig, paths, buildCfg, bunServer) {
1855
1862
  const queue = getThrottledBuildQueue(finalConfig);
1856
1863
  await queue.add(async () => {
1857
- await cleanBuildAndNotify(importMeta, finalConfig, destinationPath, buildCfg, bunServer, { filename: "Initial", eventType: "change" });
1864
+ await cleanBuildAndNotify(importMeta, finalConfig, paths, buildCfg, bunServer, { filename: "Initial", eventType: "change" });
1858
1865
  });
1859
1866
  const watcher = watch(srcWatch, { recursive: true });
1860
1867
  try {
@@ -1867,7 +1874,7 @@ async function startFileWatcher(srcWatch, importMeta, finalConfig, destinationPa
1867
1874
  queue.clear();
1868
1875
  }
1869
1876
  queue.add(async () => {
1870
- await cleanBuildAndNotify(importMeta, finalConfig, destinationPath, buildCfg, bunServer, event);
1877
+ await cleanBuildAndNotify(importMeta, finalConfig, paths, buildCfg, bunServer, event);
1871
1878
  });
1872
1879
  } catch (e) {
1873
1880
  console.error("Error while processing file change", e);
@@ -1881,8 +1888,6 @@ async function startFileWatcher(srcWatch, importMeta, finalConfig, destinationPa
1881
1888
  }
1882
1889
 
1883
1890
  // src/configManager.ts
1884
- var {$: $3 } = globalThis.Bun;
1885
- import { readdir as readdir2 } from "fs/promises";
1886
1891
  import { resolve as resolve2 } from "path";
1887
1892
 
1888
1893
  // src/templates/output.ejs
@@ -2043,15 +2048,16 @@ async function prepareConfiguration(serverConfig, importMeta) {
2043
2048
  if (!finalConfig.watchDir) {
2044
2049
  throw new Error("watchDir must be set");
2045
2050
  }
2046
- const servePart = finalConfig.buildConfig.outdir ?? finalConfig.servePath ?? "./dist";
2051
+ const defaultBuildDir = finalConfig.buildConfig.outdir ?? "./dist";
2052
+ const servePart = finalConfig.servePath ?? defaultBuildDir;
2047
2053
  const serveDestination = resolve2(importMeta.dir, servePart);
2048
2054
  const watchDestination = resolve2(importMeta.dir, finalConfig.watchDir);
2055
+ const buildDestination = resolve2(importMeta.dir, defaultBuildDir);
2049
2056
  const allEntries = serverConfig.buildConfig.entrypoints.splice(0, serverConfig.buildConfig.entrypoints.length);
2050
2057
  const resolvedEntries = allEntries.map((e) => resolve2(importMeta.dir, e));
2051
2058
  serverConfig.buildConfig.entrypoints = resolvedEntries;
2052
- const destinationPath = serveDestination;
2053
- const srcWatch = watchDestination;
2054
- await ensureDestinationDirectory(destinationPath);
2059
+ await ensureDestinationDirectory(buildDestination);
2060
+ await ensureDestinationDirectory(serveDestination);
2055
2061
  const buncfg = {
2056
2062
  port: finalConfig.port,
2057
2063
  tls: finalConfig.tls,
@@ -2060,7 +2066,7 @@ async function prepareConfiguration(serverConfig, importMeta) {
2060
2066
  };
2061
2067
  const buildCfg = {
2062
2068
  ...serverConfig.buildConfig,
2063
- outdir: destinationPath
2069
+ outdir: buildDestination
2064
2070
  };
2065
2071
  if (finalConfig.hotReload === "footer") {
2066
2072
  if (!buildCfg.footer) {
@@ -2076,44 +2082,36 @@ async function prepareConfiguration(serverConfig, importMeta) {
2076
2082
  }
2077
2083
  const userBeforeBuild = finalConfig.beforeBuild;
2078
2084
  finalConfig.beforeBuild = async (env) => {
2079
- if (serverConfig.cleanServePath) {
2080
- await cleanDirectory(env.destinationPath);
2085
+ const buildDirOverOfServe = serveDestination.indexOf(buildDestination) !== -1;
2086
+ const serveDirOverOfBuild = buildDestination.indexOf(serveDestination) !== -1;
2087
+ if (serverConfig.cleanBuildPath && buildDirOverOfServe) {
2088
+ await cleanDirectory(env.buildDestination);
2089
+ }
2090
+ if (serverConfig.cleanServePath && serveDirOverOfBuild) {
2091
+ await cleanDirectory(env.serveDestination);
2081
2092
  }
2082
2093
  await userBeforeBuild?.(env);
2083
2094
  };
2084
2095
  return {
2085
2096
  finalConfig,
2086
- destinationPath,
2087
- srcWatch,
2097
+ serveDestination,
2098
+ buildDestination,
2099
+ watchDestination,
2088
2100
  buildCfg
2089
2101
  };
2090
2102
  }
2091
- async function ensureDestinationDirectory(destinationPath) {
2092
- try {
2093
- await readdir2(destinationPath);
2094
- } catch (e) {
2095
- if (e.code === "ENOENT") {
2096
- console.log("Directory not found, creating it...");
2097
- try {
2098
- await $3`mkdir ${destinationPath}`;
2099
- } catch (e2) {
2100
- console.error("Unable to create directory", e2);
2101
- }
2102
- } else {
2103
- throw e;
2104
- }
2105
- }
2106
- }
2107
2103
 
2108
2104
  // src/server.ts
2109
2105
  async function startBunDevServer(serverConfig, importMeta) {
2110
- const { finalConfig, destinationPath, srcWatch, buildCfg } = await prepareConfiguration(serverConfig, importMeta);
2106
+ const { finalConfig, buildCfg, ...paths } = await prepareConfiguration(serverConfig, importMeta);
2111
2107
  const protocol = finalConfig.tls ? "https" : "http";
2112
2108
  const serverUrl = `${protocol}://localhost:${finalConfig.port}`;
2113
2109
  const isTestMode = process.env.BUN_TEST === "true" || typeof Bun !== "undefined" && Bun.main.includes("test");
2114
2110
  if (!isTestMode) {
2115
2111
  console.log(import_picocolors3.default.bold(import_picocolors3.default.green("\uD83D\uDE80 Server running at")) + " " + import_picocolors3.default.cyan(import_picocolors3.default.underline(serverUrl)));
2112
+ console.log(import_picocolors3.default.bold(import_picocolors3.default.green("\uD83D\uDCC1 Serving files from")) + " " + import_picocolors3.default.cyan(import_picocolors3.default.underline(paths.serveDestination)));
2116
2113
  }
2114
+ await ensureDestinationDirectory(paths.serveDestination);
2117
2115
  let bunServer;
2118
2116
  try {
2119
2117
  bunServer = Bun.serve({
@@ -2142,7 +2140,7 @@ async function startBunDevServer(serverConfig, importMeta) {
2142
2140
  }
2143
2141
  const url = new URL(req.url);
2144
2142
  let requestPath = url.pathname;
2145
- return handlePathRequest(requestPath, req, finalConfig, destinationPath);
2143
+ return handlePathRequest(requestPath, req, finalConfig, paths.serveDestination);
2146
2144
  },
2147
2145
  websocket: {
2148
2146
  open(ws) {
@@ -2160,7 +2158,7 @@ async function startBunDevServer(serverConfig, importMeta) {
2160
2158
  throw error;
2161
2159
  }
2162
2160
  if (bunServer) {
2163
- await startFileWatcher(srcWatch, importMeta, finalConfig, destinationPath, buildCfg, bunServer);
2161
+ await startFileWatcher(paths.watchDestination, importMeta, finalConfig, paths, buildCfg, bunServer);
2164
2162
  }
2165
2163
  }
2166
2164
  export {
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Ensure the destination directory exists, create if it doesn't
3
+ * @param destinationPath - The absolute path to the destination directory
4
+ */
5
+ export declare function ensureDestinationDirectory(destinationPath: string): Promise<void>;
1
6
  /**
2
7
  * Clean a directory by removing all its contents
3
8
  * @param dst - The absolute path to the directory to clean
package/package.json CHANGED
@@ -24,7 +24,7 @@
24
24
  "exports": {
25
25
  ".": "./dist/index.js"
26
26
  },
27
- "version": "1.0.1",
27
+ "version": "1.0.2",
28
28
  "module": "index.ts",
29
29
  "type": "module",
30
30
  "license": "MIT",
@@ -35,7 +35,7 @@
35
35
  "test:coverage": "bun test --coverage"
36
36
  },
37
37
  "devDependencies": {
38
- "@types/bun": "^1.3.0"
38
+ "@types/bun": "^1.3.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "typescript": "^5.9.3"
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@types/ejs": "^3.1.5",
45
45
  "ejs": "^3.1.10",
46
- "p-queue": "^9.0.0",
46
+ "p-queue": "^9.0.1",
47
47
  "picocolors": "^1.1.1"
48
48
  }
49
49
  }