From c8ed57e7117bb001f7bf3748d7f4d33875cb738a Mon Sep 17 00:00:00 2001
From: Kelly Mears <developers@tinypixel.dev>
Date: Wed, 16 Aug 2023 13:05:41 -0400
Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20improve(patch):=20@roots/bud-cl?=
 =?UTF-8?q?ient=20export=20mapping?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 sources/@roots/bud-client/package.json        | 41 ++++++++-
 .../src/hot/{client.ts => client/index.ts}    | 91 ++++++++-----------
 .../bud-client/src/hot/components/index.ts    |  4 +-
 .../{indicator.component.ts => component.ts}  |  2 +-
 ...{indicator.controller.ts => controller.ts} |  0
 .../src/hot/components/indicator/index.ts     |  4 +-
 .../{indicator.pulse.ts => pulse.ts}          |  0
 .../{overlay.component.ts => component.ts}    |  0
 .../{overlay.controller.ts => controller.ts}  |  0
 .../src/hot/components/overlay/index.ts       |  4 +-
 .../src/hot/{events.ts => events/index.ts}    |  0
 sources/@roots/bud-client/src/hot/index.ts    | 12 +--
 .../src/hot/{log.ts => log/index.ts}          |  0
 .../src/hot/{options.ts => options/index.ts}  |  0
 sources/@roots/bud-client/src/index.ts        |  4 +-
 sources/@roots/bud-client/src/lazy.ts         | 18 ++--
 .../@roots/bud-client/src/types/index.d.ts    |  2 +-
 sources/@roots/bud-client/test/client.test.ts | 21 ++---
 18 files changed, 108 insertions(+), 95 deletions(-)
 rename sources/@roots/bud-client/src/hot/{client.ts => client/index.ts} (65%)
 rename sources/@roots/bud-client/src/hot/components/indicator/{indicator.component.ts => component.ts} (98%)
 rename sources/@roots/bud-client/src/hot/components/indicator/{indicator.controller.ts => controller.ts} (100%)
 rename sources/@roots/bud-client/src/hot/components/indicator/{indicator.pulse.ts => pulse.ts} (100%)
 rename sources/@roots/bud-client/src/hot/components/overlay/{overlay.component.ts => component.ts} (100%)
 rename sources/@roots/bud-client/src/hot/components/overlay/{overlay.controller.ts => controller.ts} (100%)
 rename sources/@roots/bud-client/src/hot/{events.ts => events/index.ts} (100%)
 rename sources/@roots/bud-client/src/hot/{log.ts => log/index.ts} (100%)
 rename sources/@roots/bud-client/src/hot/{options.ts => options/index.ts} (100%)

diff --git a/sources/@roots/bud-client/package.json b/sources/@roots/bud-client/package.json
index d51b8ad60a..7e78336c43 100644
--- a/sources/@roots/bud-client/package.json
+++ b/sources/@roots/bud-client/package.json
@@ -46,15 +46,48 @@
   "type": "module",
   "exports": {
     ".": "./lib/index.js",
+    "./hot": "./lib/hot/index.js",
+    "./hot/client": "./lib/hot/client/index.js",
+    "./hot/components": "./lib/hot/components/index.js",
+    "./hot/components/indicator": "./lib/hot/components/indicator/index.js",
+    "./hot/components/overlay": "./lib/hot/components/overlay/index.js",
+    "./hot/events": "./lib/hot/events/index.js",
+    "./hot/log": "./lib/hot/log/index.js",
+    "./hot/options": "./lib/hot/options/index.js",
     "./dom-ready": "./lib/dom-ready.js",
     "./lazy": "./lib/lazy.js",
-    "./lib/*": "./lib/*"
+    "./lib/*": "./lib/*",
+    "./log": "./lib/log.js"
   },
   "typesVersions": {
     "*": {
       ".": [
         "./lib/index.d.ts"
       ],
+      "hot": [
+        "./lib/hot/index.d.ts"
+      ],
+      "hot/client": [
+        "./lib/hot/client/index.d.ts"
+      ],
+      "hot/components": [
+        "./lib/hot/components/index.d.ts"
+      ],
+      "hot/components/indicator": [
+        "./lib/hot/components/indicator/index.d.ts"
+      ],
+      "hot/components/overlay": [
+        "./lib/hot/components/overlay/index.d.ts"
+      ],
+      "hot/events": [
+        "./lib/hot/events/index.d.ts"
+      ],
+      "hot/log": [
+        "./lib/hot/log/index.d.ts"
+      ],
+      "hot/options": [
+        "./lib/hot/options/index.d.ts"
+      ],
       "dom-ready": [
         "./lib/dom-ready.d.ts"
       ],
@@ -63,6 +96,12 @@
       ],
       "lib/*": [
         "./lib/*"
+      ],
+      "log": [
+        "./lib/log.d.ts"
+      ],
+      "types": [
+        "./lib/types/index.d.ts"
       ]
     }
   },
diff --git a/sources/@roots/bud-client/src/hot/client.ts b/sources/@roots/bud-client/src/hot/client/index.ts
similarity index 65%
rename from sources/@roots/bud-client/src/hot/client.ts
rename to sources/@roots/bud-client/src/hot/client/index.ts
index 21beff6273..0c4efb18bf 100644
--- a/sources/@roots/bud-client/src/hot/client.ts
+++ b/sources/@roots/bud-client/src/hot/client/index.ts
@@ -2,15 +2,15 @@
 /* global __resourceQuery */
 /* global __webpack_hash__ */
 
-import * as components from './components/index.js'
-import {injectEvents} from './events.js'
-import {makeLogger} from './log.js'
-import * as clientOptions from './options.js'
+import * as components from '@roots/bud-client/hot/components'
+import {injectEvents} from '@roots/bud-client/hot/events'
+import {makeLogger} from '@roots/bud-client/hot/log'
+import * as clientOptions from '@roots/bud-client/hot/options'
 
 /**
  * Initializes bud.js HMR handling
  */
-export const client = async (
+export const initializeClient = async (
   queryString: string,
   webpackHot: __WebpackModuleApi.Hot,
 ) => {
@@ -34,20 +34,18 @@ export const client = async (
   /* Setup logger */
   const logger = makeLogger(options)
 
-  if (typeof window.bud === `undefined`) {
-    window.bud = {
-      controllers: [],
-      current: {},
-      hmr: {},
-      listeners: {},
-    }
-  }
-
-  if (!window.bud.current[options.name]) {
-    window.bud.current[options.name] = null
+  window.bud = {
+    ...window.bud ?? {},
+    controllers: [...window.bud?.controllers ?? []],
+    current: {
+      ...window.bud?.current ?? {},
+      [options.name]: window.bud?.current?.[options.name] ?? null,
+    },
+    hmr: {...window.bud?.hmr ?? {}},
+    listeners: {...window.bud?.listeners ?? {}},
   }
 
-  const isStale = (hash?: string) => {
+  const isStale = (hash?: string): boolean => {
     if (hash) window.bud.current[options.name] = hash
     return __webpack_hash__ === window.bud.current[options.name]
   }
@@ -61,7 +59,25 @@ export const client = async (
 
       requestAnimationFrame(async function whenReady() {
         if (webpackHot.status() === `ready`) {
-          await update()
+          await webpackHot
+            .apply({
+              ignoreDeclined: true,
+              ignoreErrored: true,
+              ignoreUnaccepted: true,
+              onDeclined: onUnacceptedOrDeclined,
+              onErrored: (error: any) => {
+                window.bud.controllers.map(
+                  controller =>
+                    controller?.update({
+                      errors: [error],
+                    }),
+                )
+              },
+              onUnaccepted: onUnacceptedOrDeclined,
+            })
+            .catch(logger.error)
+
+          if (!isStale()) await check()
         } else {
           requestAnimationFrame(whenReady)
         }
@@ -75,46 +91,12 @@ export const client = async (
   const onUnacceptedOrDeclined = (
     info: __WebpackModuleApi.HotNotifierInfo,
   ) => {
-    console.warn(`[${options.name}] ${info.type}`, info)
+    logger.warn(info.type, info)
     options.reload && window.location.reload()
   }
 
-  /**
-   * Webpack HMR error handler
-   */
-  const onErrored = (error: any) => {
-    window.bud.controllers.map(
-      controller =>
-        controller?.update({
-          errors: [error],
-        }),
-    )
-  }
-
-  /**
-   * Webpack HMR update handler
-   */
-  const update = async () => {
-    try {
-      await webpackHot.apply({
-        ignoreDeclined: true,
-        ignoreErrored: true,
-        ignoreUnaccepted: true,
-        onDeclined: onUnacceptedOrDeclined,
-        onErrored,
-        onUnaccepted: onUnacceptedOrDeclined,
-      })
-
-      if (!isStale()) await check()
-    } catch (error) {
-      logger.error(error)
-    }
-  }
-
   /* Instantiate indicator, overlay */
-  try {
-    await components.make(options)
-  } catch (error) {}
+  await components.make(options).catch(err => {})
 
   /* Instantiate eventSource */
   const events = injectEvents(EventSource).make(options)
@@ -127,6 +109,7 @@ export const client = async (
         return window.location.reload()
 
       if (payload.name !== options.name) return
+
       window.bud.controllers.map(controller => controller?.update(payload))
 
       if (payload.errors?.length > 0) return
diff --git a/sources/@roots/bud-client/src/hot/components/index.ts b/sources/@roots/bud-client/src/hot/components/index.ts
index a12ae1bfe1..b3d91ebe34 100644
--- a/sources/@roots/bud-client/src/hot/components/index.ts
+++ b/sources/@roots/bud-client/src/hot/components/index.ts
@@ -1,5 +1,5 @@
-import * as Indicator from './indicator/index.js'
-import * as Overlay from './overlay/index.js'
+import * as Indicator from '@roots/bud-client/hot/components/indicator'
+import * as Overlay from '@roots/bud-client/hot/components/overlay'
 
 export const make: (
   options: Options,
diff --git a/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts b/sources/@roots/bud-client/src/hot/components/indicator/component.ts
similarity index 98%
rename from sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts
rename to sources/@roots/bud-client/src/hot/components/indicator/component.ts
index 6d88aa919f..ae4eb5ab2b 100644
--- a/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts
+++ b/sources/@roots/bud-client/src/hot/components/indicator/component.ts
@@ -1,4 +1,4 @@
-import {pulse} from './indicator.pulse.js'
+import {pulse} from './pulse.js'
 
 /**
  * Indicator web component
diff --git a/sources/@roots/bud-client/src/hot/components/indicator/indicator.controller.ts b/sources/@roots/bud-client/src/hot/components/indicator/controller.ts
similarity index 100%
rename from sources/@roots/bud-client/src/hot/components/indicator/indicator.controller.ts
rename to sources/@roots/bud-client/src/hot/components/indicator/controller.ts
diff --git a/sources/@roots/bud-client/src/hot/components/indicator/index.ts b/sources/@roots/bud-client/src/hot/components/indicator/index.ts
index e5a3766c6a..1ebeff1144 100644
--- a/sources/@roots/bud-client/src/hot/components/indicator/index.ts
+++ b/sources/@roots/bud-client/src/hot/components/indicator/index.ts
@@ -1,5 +1,5 @@
-import {Component} from './indicator.component.js'
-import {Controller} from './indicator.controller.js'
+import {Component} from './component.js'
+import {Controller} from './controller.js'
 
 export const make = () => {
   if (customElements.get(`bud-activity-indicator`)) return
diff --git a/sources/@roots/bud-client/src/hot/components/indicator/indicator.pulse.ts b/sources/@roots/bud-client/src/hot/components/indicator/pulse.ts
similarity index 100%
rename from sources/@roots/bud-client/src/hot/components/indicator/indicator.pulse.ts
rename to sources/@roots/bud-client/src/hot/components/indicator/pulse.ts
diff --git a/sources/@roots/bud-client/src/hot/components/overlay/overlay.component.ts b/sources/@roots/bud-client/src/hot/components/overlay/component.ts
similarity index 100%
rename from sources/@roots/bud-client/src/hot/components/overlay/overlay.component.ts
rename to sources/@roots/bud-client/src/hot/components/overlay/component.ts
diff --git a/sources/@roots/bud-client/src/hot/components/overlay/overlay.controller.ts b/sources/@roots/bud-client/src/hot/components/overlay/controller.ts
similarity index 100%
rename from sources/@roots/bud-client/src/hot/components/overlay/overlay.controller.ts
rename to sources/@roots/bud-client/src/hot/components/overlay/controller.ts
diff --git a/sources/@roots/bud-client/src/hot/components/overlay/index.ts b/sources/@roots/bud-client/src/hot/components/overlay/index.ts
index 05c2f5a13c..b5e112f011 100644
--- a/sources/@roots/bud-client/src/hot/components/overlay/index.ts
+++ b/sources/@roots/bud-client/src/hot/components/overlay/index.ts
@@ -1,5 +1,5 @@
-import {Component} from './overlay.component.js'
-import {Controller} from './overlay.controller.js'
+import {Component} from './component.js'
+import {Controller} from './controller.js'
 
 export const make = (): {
   update: (data: Payload) => void
diff --git a/sources/@roots/bud-client/src/hot/events.ts b/sources/@roots/bud-client/src/hot/events/index.ts
similarity index 100%
rename from sources/@roots/bud-client/src/hot/events.ts
rename to sources/@roots/bud-client/src/hot/events/index.ts
diff --git a/sources/@roots/bud-client/src/hot/index.ts b/sources/@roots/bud-client/src/hot/index.ts
index d175bbb30a..5b13792c02 100644
--- a/sources/@roots/bud-client/src/hot/index.ts
+++ b/sources/@roots/bud-client/src/hot/index.ts
@@ -2,15 +2,7 @@
 /* global __resourceQuery */
 /* global module */
 
-import {client} from './client.js'
-
-/**
- * Client entrypoint
- */
 ;(async function () {
-  try {
-    await client(__resourceQuery, import.meta.webpackHot)
-  } catch (err) {
-    console.error(err)
-  }
+  const {initializeClient} = await import(`@roots/bud-client/hot/client`)
+  await initializeClient(__resourceQuery, import.meta.webpackHot).catch(console.error)
 })()
diff --git a/sources/@roots/bud-client/src/hot/log.ts b/sources/@roots/bud-client/src/hot/log/index.ts
similarity index 100%
rename from sources/@roots/bud-client/src/hot/log.ts
rename to sources/@roots/bud-client/src/hot/log/index.ts
diff --git a/sources/@roots/bud-client/src/hot/options.ts b/sources/@roots/bud-client/src/hot/options/index.ts
similarity index 100%
rename from sources/@roots/bud-client/src/hot/options.ts
rename to sources/@roots/bud-client/src/hot/options/index.ts
diff --git a/sources/@roots/bud-client/src/index.ts b/sources/@roots/bud-client/src/index.ts
index 8fcdc8afe7..60b6c1ea07 100644
--- a/sources/@roots/bud-client/src/index.ts
+++ b/sources/@roots/bud-client/src/index.ts
@@ -2,15 +2,13 @@
 // Licensed under the MIT license.
 
 /**
- * `@roots/bud` client scripts
+ * @roots/bud-client
  *
  * You should not import this root module.
  * Import the components from the submodules instead.
  *
  * @see https://bud.js.org
  * @see https://github.com/roots/bud
- *
- *  @packageDocumentation
  */
 
 import domReady from '@roots/bud-client/dom-ready'
diff --git a/sources/@roots/bud-client/src/lazy.ts b/sources/@roots/bud-client/src/lazy.ts
index 77d8ec4636..5f7cc67381 100644
--- a/sources/@roots/bud-client/src/lazy.ts
+++ b/sources/@roots/bud-client/src/lazy.ts
@@ -9,7 +9,7 @@
  */
 interface lazy {
   <T = any>(
-    module: Promise<{default: T}>,
+    module: Promise<T>,
     handler: (module: T) => Promise<unknown> | unknown,
     errorHandler?: (error: unknown) => unknown,
   ): Promise<unknown>
@@ -25,16 +25,18 @@ const defaultErrorHandler = (error: unknown) => {
 }
 
 const lazy: lazy = async function lazy<T = any>(
-  module: Promise<{default: T}>,
-  handler: (module: T) => Promise<unknown> | unknown,
-  errorHandler?: (error: unknown) => unknown,
+  module: Promise<T>,
+  onImport: (module: T) => Promise<unknown> | unknown,
+  onError?: (error: unknown) => unknown,
 ) {
   try {
-    const {default: request} = await module
-    return await handler(request)
+    const request = await module
+    if (!request) throw new Error(`module not found: ${module}`)
+    const result = await onImport(request)
+    return result
   } catch (error: unknown) {
-    const handle = errorHandler ? errorHandler : defaultErrorHandler
-    handle(error)
+    const errorFn = onError ? onError : defaultErrorHandler
+    errorFn(error)
   }
 }
 
diff --git a/sources/@roots/bud-client/src/types/index.d.ts b/sources/@roots/bud-client/src/types/index.d.ts
index ce08c8ad86..d6c50c718b 100644
--- a/sources/@roots/bud-client/src/types/index.d.ts
+++ b/sources/@roots/bud-client/src/types/index.d.ts
@@ -49,6 +49,6 @@ declare var bud: {
 
 declare module global {
   interface Window {
-    bud: typeof bud
+    bud?: typeof bud
   }
 }
diff --git a/sources/@roots/bud-client/test/client.test.ts b/sources/@roots/bud-client/test/client.test.ts
index c9cdfb19b4..2b6b882016 100644
--- a/sources/@roots/bud-client/test/client.test.ts
+++ b/sources/@roots/bud-client/test/client.test.ts
@@ -1,14 +1,13 @@
-import '../src/types/index.d.ts'
-
-/* eslint-disable no-console */
 /**
  * @vitest-environment jsdom
  */
+
+import {initializeClient} from '@roots/bud-client/hot/client'
+import {injectEvents} from '@roots/bud-client/hot/events'
+import * as options from '@roots/bud-client/hot/options'
 import {describe, expect, it, vi} from 'vitest'
 
-import {client} from '../src/hot/client.js'
-import {injectEvents} from '../src/hot/events.js'
-import * as options from '../src/hot/options.js'
+import '../src/types/index.d.ts'
 
 // @ts-ignore
 global.EventSource = class Events {
@@ -27,21 +26,21 @@ const webpackHotMock = {
 
 describe(`@roots/bud-client`, () => {
   it(`should be a fn module`, () => {
-    expect(client).toBeInstanceOf(Function)
+    expect(initializeClient).toBeInstanceOf(Function)
   })
 
   it(`should add window.bud`, async () => {
-    await client(`?name=test`, webpackHotMock)
+    await initializeClient(`?name=test`, webpackHotMock)
     expect(window.bud).toBeDefined()
   })
 
   it(`should add window.bud.hmr as an instance of EventSource`, async () => {
-    await client(`?name=test`, webpackHotMock)
+    await initializeClient(`?name=test`, webpackHotMock)
     expect(window.bud?.hmr?.test).toBeInstanceOf(EventSource)
   })
 
   it(`should set clientOptions`, async () => {
-    await client(`?name=test`, webpackHotMock)
+    await initializeClient(`?name=test`, webpackHotMock)
     expect(options.data).toEqual(
       expect.objectContaining({
         debug: true,
@@ -57,7 +56,7 @@ describe(`@roots/bud-client`, () => {
   })
 
   it(`should call listener from onmessage`, async () => {
-    await client(`?name=test`, webpackHotMock)
+    await initializeClient(`?name=test`, webpackHotMock)
     const events = Events.make(options.data)
 
     const listenerMock = vi.fn(async () => {})

From c0a0045276e9241af3a2849da3a8e07df31d8725 Mon Sep 17 00:00:00 2001
From: Kelly Mears <developers@tinypixel.dev>
Date: Thu, 17 Aug 2023 11:33:43 -0400
Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20improve(patch):=20set=20strict?=
 =?UTF-8?q?=20mode=20for=20@roots/bud-client?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 sources/@roots/bud-client/package.json        | 14 ++---
 .../@roots/bud-client/src/hot/client/index.ts | 59 ++++++++++---------
 .../bud-client/src/hot/components/index.ts    | 12 ++--
 .../src/hot/components/indicator/component.ts | 32 +++++-----
 .../hot/components/indicator/controller.ts    |  2 +-
 .../src/hot/components/overlay/component.ts   | 11 ++--
 .../src/hot/components/overlay/controller.ts  |  6 +-
 .../src/hot/components/overlay/index.ts       |  2 +-
 .../@roots/bud-client/src/hot/events/index.ts | 10 +++-
 sources/@roots/bud-client/src/hot/index.ts    |  4 +-
 .../bud-client/src/hot/options/index.ts       | 19 +++---
 sources/@roots/bud-client/src/index.ts        |  6 +-
 .../@roots/bud-client/src/intercept/index.ts  | 50 +++++++---------
 .../src/intercept/proxy-click-interceptor.ts  | 11 +++-
 .../@roots/bud-client/src/types/index.d.ts    |  2 +-
 sources/@roots/bud-client/tsconfig.json       |  1 +
 yarn.lock                                     |  6 +-
 17 files changed, 124 insertions(+), 123 deletions(-)

diff --git a/sources/@roots/bud-client/package.json b/sources/@roots/bud-client/package.json
index 7e78336c43..8e2bc2af3d 100644
--- a/sources/@roots/bud-client/package.json
+++ b/sources/@roots/bud-client/package.json
@@ -46,6 +46,7 @@
   "type": "module",
   "exports": {
     ".": "./lib/index.js",
+    "./dom-ready": "./lib/dom-ready.js",
     "./hot": "./lib/hot/index.js",
     "./hot/client": "./lib/hot/client/index.js",
     "./hot/components": "./lib/hot/components/index.js",
@@ -54,16 +55,17 @@
     "./hot/events": "./lib/hot/events/index.js",
     "./hot/log": "./lib/hot/log/index.js",
     "./hot/options": "./lib/hot/options/index.js",
-    "./dom-ready": "./lib/dom-ready.js",
     "./lazy": "./lib/lazy.js",
-    "./lib/*": "./lib/*",
-    "./log": "./lib/log.js"
+    "./lib/*": "./lib/*"
   },
   "typesVersions": {
     "*": {
       ".": [
         "./lib/index.d.ts"
       ],
+      "dom-ready": [
+        "./lib/dom-ready.d.ts"
+      ],
       "hot": [
         "./lib/hot/index.d.ts"
       ],
@@ -88,18 +90,12 @@
       "hot/options": [
         "./lib/hot/options/index.d.ts"
       ],
-      "dom-ready": [
-        "./lib/dom-ready.d.ts"
-      ],
       "lazy": [
         "./lib/lazy.d.ts"
       ],
       "lib/*": [
         "./lib/*"
       ],
-      "log": [
-        "./lib/log.d.ts"
-      ],
       "types": [
         "./lib/types/index.d.ts"
       ]
diff --git a/sources/@roots/bud-client/src/hot/client/index.ts b/sources/@roots/bud-client/src/hot/client/index.ts
index 0c4efb18bf..05ec742b5c 100644
--- a/sources/@roots/bud-client/src/hot/client/index.ts
+++ b/sources/@roots/bud-client/src/hot/client/index.ts
@@ -21,35 +21,45 @@ export const initializeClient = async (
     )
     return false
   }
-  /* Guard: webpackHot api availability */
-  if (!webpackHot) {
-    console.error(
-      `[bud] hot module reload requires the webpack hot api to be available`,
-    )
-    return false
-  }
 
   /* Set client options from URL params */
   const options = clientOptions.setFromParameters(queryString)
   /* Setup logger */
   const logger = makeLogger(options)
 
+  /**
+   * Setup window.bud
+   */
   window.bud = {
-    ...window.bud ?? {},
-    controllers: [...window.bud?.controllers ?? []],
+    ...(window.bud ?? {}),
+    controllers: window.bud?.controllers ?? [],
     current: {
-      ...window.bud?.current ?? {},
+      ...(window.bud?.current ?? {}),
       [options.name]: window.bud?.current?.[options.name] ?? null,
     },
-    hmr: {...window.bud?.hmr ?? {}},
-    listeners: {...window.bud?.listeners ?? {}},
+    hmr: window.bud?.hmr ?? {},
+    listeners: window.bud?.listeners ?? {},
   }
 
+  /**
+   * Is update stale?
+   */
   const isStale = (hash?: string): boolean => {
+    if (!window.bud.current) return false
     if (hash) window.bud.current[options.name] = hash
     return __webpack_hash__ === window.bud.current[options.name]
   }
 
+  /**
+   * Unaccepted & declined module handler
+   */
+  const onUnacceptedOrDeclined = (
+    info: __WebpackModuleApi.HotNotifierInfo,
+  ) => {
+    logger.warn(info.type, info)
+    options.reload && window.location.reload()
+  }
+
   /**
    * Webpack HMR check handler
    */
@@ -66,11 +76,8 @@ export const initializeClient = async (
               ignoreUnaccepted: true,
               onDeclined: onUnacceptedOrDeclined,
               onErrored: (error: any) => {
-                window.bud.controllers.map(
-                  controller =>
-                    controller?.update({
-                      errors: [error],
-                    }),
+                window.bud.controllers?.map(
+                  c => c?.update({errors: [error]}),
                 )
               },
               onUnaccepted: onUnacceptedOrDeclined,
@@ -85,22 +92,16 @@ export const initializeClient = async (
     }
   }
 
-  /**
-   * Webpack HMR unaccepted module handler
-   */
-  const onUnacceptedOrDeclined = (
-    info: __WebpackModuleApi.HotNotifierInfo,
-  ) => {
-    logger.warn(info.type, info)
-    options.reload && window.location.reload()
-  }
-
   /* Instantiate indicator, overlay */
   await components.make(options).catch(err => {})
 
   /* Instantiate eventSource */
   const events = injectEvents(EventSource).make(options)
 
+  if (!window.bud.listeners) {
+    window.bud.listeners = {}
+  }
+
   if (!window.bud.listeners?.[options.name]) {
     window.bud.listeners[options.name] = async payload => {
       if (!payload) return
@@ -110,9 +111,9 @@ export const initializeClient = async (
 
       if (payload.name !== options.name) return
 
-      window.bud.controllers.map(controller => controller?.update(payload))
+      window.bud.controllers?.map(controller => controller?.update(payload))
 
-      if (payload.errors?.length > 0) return
+      if (typeof payload.errors !== `undefined` && payload.errors.length > 0) return
 
       if (payload.action === `built` || payload.action === `sync`) {
         if (isStale(payload.hash)) return
diff --git a/sources/@roots/bud-client/src/hot/components/index.ts b/sources/@roots/bud-client/src/hot/components/index.ts
index b3d91ebe34..4d54605529 100644
--- a/sources/@roots/bud-client/src/hot/components/index.ts
+++ b/sources/@roots/bud-client/src/hot/components/index.ts
@@ -5,17 +5,19 @@ export const make: (
   options: Options,
 ) => Promise<Array<Controller>> = async options => {
   if (options.indicator && !customElements.get(`bud-activity-indicator`)) {
-    maybePushController(Indicator.make())
+    const indicator = Indicator.make()
+    if (indicator) maybePushController(indicator)
   }
 
   if (options.overlay && !customElements.get(`bud-error`)) {
-    maybePushController(Overlay.make())
+    const overlay = Overlay.make()
+    if (overlay) maybePushController(overlay)
   }
 
-  return window.bud.controllers
+  return window.bud.controllers ?? []
 }
 
-const maybePushController = (controller: Controller | undefined) => {
+const maybePushController = (controller: any) => {
   if (!controller) return
-  window.bud.controllers.push(controller)
+  window.bud.controllers?.push(controller)
 }
diff --git a/sources/@roots/bud-client/src/hot/components/indicator/component.ts b/sources/@roots/bud-client/src/hot/components/indicator/component.ts
index ae4eb5ab2b..d10246df23 100644
--- a/sources/@roots/bud-client/src/hot/components/indicator/component.ts
+++ b/sources/@roots/bud-client/src/hot/components/indicator/component.ts
@@ -17,7 +17,7 @@ export class Component extends HTMLElement {
   /**
    * Timer
    */
-  public hideTimeout: NodeJS.Timer
+  public declare hideTimeout: NodeJS.Timer
 
   /**
    * Component name
@@ -27,7 +27,7 @@ export class Component extends HTMLElement {
   /**
    * Has component rendered
    */
-  public rendered: boolean
+  public declare rendered: boolean
 
   /**
    * Class constructor
@@ -78,7 +78,7 @@ export class Component extends HTMLElement {
    */
   public hide() {
     this.hideTimeout = setTimeout(() => {
-      this.shadowRoot.querySelector(this.selector).classList.remove(`show`)
+      this.shadowRoot?.querySelector(this.selector)?.classList.remove(`show`)
     }, 2000)
   }
 
@@ -89,9 +89,9 @@ export class Component extends HTMLElement {
     this.show()
 
     this.shadowRoot
-      .querySelector(this.selector)
-      .classList.remove(`warning`, `success`, `pending`)
-    this.shadowRoot.querySelector(this.selector).classList.add(`error`)
+      ?.querySelector(this.selector)
+      ?.classList.remove(`warning`, `success`, `pending`)
+    this.shadowRoot?.querySelector(this.selector)?.classList.add(`error`)
   }
 
   /**
@@ -101,10 +101,10 @@ export class Component extends HTMLElement {
     this.show()
 
     this.shadowRoot
-      .querySelector(this.selector)
-      .classList.remove(`error`, `warning`, `success`)
+      ?.querySelector(this.selector)
+      ?.classList.remove(`error`, `warning`, `success`)
 
-    this.shadowRoot.querySelector(this.selector).classList.add(`pending`)
+    this.shadowRoot?.querySelector(this.selector)?.classList.add(`pending`)
 
     this.hide()
   }
@@ -116,10 +116,10 @@ export class Component extends HTMLElement {
     this.show()
 
     this.shadowRoot
-      .querySelector(this.selector)
-      .classList.remove(`error`, `warning`, `pending`)
+      ?.querySelector(this.selector)
+      ?.classList.remove(`error`, `warning`, `pending`)
 
-    this.shadowRoot.querySelector(this.selector).classList.add(`success`)
+    this.shadowRoot?.querySelector(this.selector)?.classList.add(`success`)
 
     this.hide()
   }
@@ -131,10 +131,10 @@ export class Component extends HTMLElement {
     this.show()
 
     this.shadowRoot
-      .querySelector(this.selector)
-      .classList.remove(`error`, `success`, `pending`)
+      ?.querySelector(this.selector)
+      ?.classList.remove(`error`, `success`, `pending`)
 
-    this.shadowRoot.querySelector(this.selector).classList.add(`warning`)
+    this.shadowRoot?.querySelector(this.selector)?.classList.add(`warning`)
   }
 
   /**
@@ -196,6 +196,6 @@ export class Component extends HTMLElement {
    */
   public show() {
     this.hideTimeout && clearTimeout(this.hideTimeout)
-    this.shadowRoot.querySelector(this.selector).classList.add(`show`)
+    this.shadowRoot?.querySelector(this.selector)?.classList.add(`show`)
   }
 }
diff --git a/sources/@roots/bud-client/src/hot/components/indicator/controller.ts b/sources/@roots/bud-client/src/hot/components/indicator/controller.ts
index dcd568ce00..15cd573c3a 100644
--- a/sources/@roots/bud-client/src/hot/components/indicator/controller.ts
+++ b/sources/@roots/bud-client/src/hot/components/indicator/controller.ts
@@ -15,7 +15,7 @@ export class Controller {
   /**
    * Timer handler
    */
-  public timer: NodeJS.Timeout
+  public declare timer: NodeJS.Timeout
 
   /**
    * Initialization
diff --git a/sources/@roots/bud-client/src/hot/components/overlay/component.ts b/sources/@roots/bud-client/src/hot/components/overlay/component.ts
index a9d4fc689c..37ae4bcc77 100644
--- a/sources/@roots/bud-client/src/hot/components/overlay/component.ts
+++ b/sources/@roots/bud-client/src/hot/components/overlay/component.ts
@@ -27,10 +27,13 @@ export class Component extends HTMLElement {
     if (this.getAttribute(`message`)) {
       document.body.style.overflow = `hidden`
 
-      this.shadowRoot.querySelector(`.overlay`).classList.add(`visible`)
+      this.shadowRoot?.querySelector(`.overlay`)?.classList.add(`visible`)
 
-      this.shadowRoot.querySelector(`.messages`).innerHTML =
-        this.getAttribute(`message`)
+      const message = this.getAttribute(`message`)
+      const messages = this.shadowRoot?.querySelector(`.messages`)
+
+      if (messages && `innerHTML` in messages && typeof message === `string`)
+        messages.innerHTML = message
 
       return
     }
@@ -39,7 +42,7 @@ export class Component extends HTMLElement {
       document.body.style.overflow = this.documentBodyStyle.overflow
     }
 
-    this.shadowRoot.querySelector(`.overlay`).classList.remove(`visible`)
+    this.shadowRoot?.querySelector(`.overlay`)?.classList.remove(`visible`)
   }
 
   public connectedCallback() {
diff --git a/sources/@roots/bud-client/src/hot/components/overlay/controller.ts b/sources/@roots/bud-client/src/hot/components/overlay/controller.ts
index 4fc2dd31f2..8e69c474d0 100644
--- a/sources/@roots/bud-client/src/hot/components/overlay/controller.ts
+++ b/sources/@roots/bud-client/src/hot/components/overlay/controller.ts
@@ -18,7 +18,7 @@ export class Controller {
   /**
    * HMR update
    */
-  public payload: Payload
+  public declare payload: Payload
 
   /**
    * Class constructor
@@ -39,7 +39,7 @@ export class Controller {
   /**
    * Formatted error message
    */
-  public get message(): string {
+  public get message(): string | undefined {
     return this.payload.errors?.reduce((a, c) => {
       const msg = c?.message ?? c?.error ?? c
       if (!msg) return a
@@ -65,7 +65,7 @@ export class Controller {
 
     this.element.setAttribute(`message`, this.message ?? ``)
 
-    if (this.payload.errors?.length > 0) {
+    if (this.payload.errors && this.payload.errors?.length > 0) {
       return this.createError()
     }
 
diff --git a/sources/@roots/bud-client/src/hot/components/overlay/index.ts b/sources/@roots/bud-client/src/hot/components/overlay/index.ts
index b5e112f011..f23ba3434f 100644
--- a/sources/@roots/bud-client/src/hot/components/overlay/index.ts
+++ b/sources/@roots/bud-client/src/hot/components/overlay/index.ts
@@ -3,7 +3,7 @@ import {Controller} from './controller.js'
 
 export const make = (): {
   update: (data: Payload) => void
-} => {
+} | undefined => {
   if (customElements.get(`bud-error`)) return
 
   customElements.define(`bud-error`, Component)
diff --git a/sources/@roots/bud-client/src/hot/events/index.ts b/sources/@roots/bud-client/src/hot/events/index.ts
index a4682a6f00..04cbb34344 100644
--- a/sources/@roots/bud-client/src/hot/events/index.ts
+++ b/sources/@roots/bud-client/src/hot/events/index.ts
@@ -14,12 +14,12 @@ export const injectEvents = (eventSource: EventSourceFactory) => {
     /**
      * Registered listeners
      */
-    public listeners: Set<Listener> = new Set<Listener>()
+    public listeners: Set<Listener> = new Set<Listener>([])
 
     /**
      * EventSource `onmessage` handler
      */
-    public override onmessage = async function (payload: MessageEvent) {
+    public override onmessage = async function (this: any, payload: MessageEvent) {
       if (!payload?.data || payload.data == `\uD83D\uDC93`) {
         return
       }
@@ -65,7 +65,11 @@ export const injectEvents = (eventSource: EventSourceFactory) => {
     public static make(
       options: Partial<Options> & {name: string; path: string},
     ): Events {
-      if (typeof window.bud.hmr[options.name] === `undefined`)
+      if (typeof window.bud.hmr === `undefined`) {
+        window.bud.hmr = {}
+      }
+
+      if (typeof window.bud.hmr?.[options.name] === `undefined`)
         Object.assign(window.bud.hmr, {
           [options.name]: new Events(options),
         })
diff --git a/sources/@roots/bud-client/src/hot/index.ts b/sources/@roots/bud-client/src/hot/index.ts
index 5b13792c02..4b8af1b4ea 100644
--- a/sources/@roots/bud-client/src/hot/index.ts
+++ b/sources/@roots/bud-client/src/hot/index.ts
@@ -2,7 +2,9 @@
 /* global __resourceQuery */
 /* global module */
 
+import {initializeClient} from '@roots/bud-client/hot/client'
+
 ;(async function () {
-  const {initializeClient} = await import(`@roots/bud-client/hot/client`)
+  if (!import.meta.webpackHot) return
   await initializeClient(__resourceQuery, import.meta.webpackHot).catch(console.error)
 })()
diff --git a/sources/@roots/bud-client/src/hot/options/index.ts b/sources/@roots/bud-client/src/hot/options/index.ts
index d95ed8712b..20023272ee 100644
--- a/sources/@roots/bud-client/src/hot/options/index.ts
+++ b/sources/@roots/bud-client/src/hot/options/index.ts
@@ -1,7 +1,7 @@
 /**
  * Client options
  */
-let data: Options = {
+const data: Record<string, any> = {
   debug: true,
   indicator: true,
   log: true,
@@ -15,23 +15,20 @@ let data: Options = {
 /**
  * Get client option
  */
-const get = (name?: string, key?: string) =>
-  key ? data[name][key] : data[name]
-
+const get = (name: `${keyof Options & string}`) => data[name]
+const set = (name: `${keyof Options & string}`, value: any) => data[name] = value
 /**
  * Set client data based on URL parameters
  */
 const setFromParameters = (query: string): Options => {
-  let parsedParams: Partial<Options> = {}
-
-  new window.URLSearchParams(query).forEach((value, key) => {
-    parsedParams[key] =
+  new window.URLSearchParams(query).forEach((value: any, key: any) => {
+    data[key] =
       value === `true` ? true : value === `false` ? false : value
   })
 
-  data[parsedParams.name] = {...data, ...parsedParams}
+  data[data.name] = {...data, ...data}
 
-  return data[parsedParams.name]
+  return data[data.name]
 }
 
-export {data, get, setFromParameters}
+export {data, get, set, setFromParameters}
diff --git a/sources/@roots/bud-client/src/index.ts b/sources/@roots/bud-client/src/index.ts
index 60b6c1ea07..5f97b3cdab 100644
--- a/sources/@roots/bud-client/src/index.ts
+++ b/sources/@roots/bud-client/src/index.ts
@@ -11,7 +11,5 @@
  * @see https://github.com/roots/bud
  */
 
-import domReady from '@roots/bud-client/dom-ready'
-import lazy from '@roots/bud-client/lazy'
-
-export {domReady, lazy}
+export {default as domReady} from '@roots/bud-client/dom-ready'
+export {default as lazy} from '@roots/bud-client/lazy'
diff --git a/sources/@roots/bud-client/src/intercept/index.ts b/sources/@roots/bud-client/src/intercept/index.ts
index d3d0d933a7..61b7024aa8 100644
--- a/sources/@roots/bud-client/src/intercept/index.ts
+++ b/sources/@roots/bud-client/src/intercept/index.ts
@@ -1,33 +1,25 @@
-type Target = HTMLAnchorElement | HTMLFormElement | HTMLLinkElement
-type ElementTuple = [HTMLCollectionOf<Target>, any]
+const intercept = (search: string, replace: string) => {
+  setInterval(
+    () =>
+      [
+        ...document.getElementsByTagName(`a`),
+        ...document.getElementsByTagName(`link`),
+      ].forEach(element => {
+        if (element.hasAttribute(`__bud_processed`)) return
+        if (!element.hasAttribute(`href`)) return
+        if (!element.getAttribute(`href`)?.startsWith(search)) return
 
-const intercept = (...args: [string, string]) => {
-  args.every(arg => typeof arg === `string`) &&
-    setInterval(
-      () =>
-        [
-          [document.getElementsByTagName(`a`), `href`],
-          [document.getElementsByTagName(`link`), `href`],
-        ]
-          .map(
-            ([elements, attribute]: ElementTuple): [
-              Array<Target>,
-              any,
-            ] => [Array.from(elements), attribute],
-          )
-          .forEach(([elements, attribute]: [Array<Target>, any]) =>
-            elements
-              .filter(el => el.hasAttribute(attribute))
-              .filter(el => !el.hasAttribute(`__bud_processed`))
-              .filter(el => el.getAttribute(attribute).startsWith(args[0]))
-              .map(el => {
-                const value = el.getAttribute(attribute).replace(...args)
-                el.setAttribute(attribute, value)
-                el.toggleAttribute(`__bud_processed`)
-              }),
-          ),
-      1000,
-    )
+        const value = element
+          .getAttribute(`href`)
+          ?.replace(search, replace)
+
+        if (!value) return
+
+        element.setAttribute(`href`, value)
+        element.toggleAttribute(`__bud_processed`)
+      }),
+    1000,
+  )
 }
 
 export default intercept
diff --git a/sources/@roots/bud-client/src/intercept/proxy-click-interceptor.ts b/sources/@roots/bud-client/src/intercept/proxy-click-interceptor.ts
index 1bda3623a6..484eb03152 100644
--- a/sources/@roots/bud-client/src/intercept/proxy-click-interceptor.ts
+++ b/sources/@roots/bud-client/src/intercept/proxy-click-interceptor.ts
@@ -7,10 +7,15 @@ window.requestAnimationFrame(async function ready() {
   if (!__resourceQuery) return
 
   const params = new URLSearchParams(__resourceQuery)
-  if (!params || !params.has(`search`) || !params.has(`replace`)) return
+  if (!params) return
 
-  const search = decodeURI(params.get(`search`))
-  const replace = decodeURI(params.get(`replace`))
+  const searchUri = params.get(`search`)
+  if (!searchUri) return
+  const search = decodeURI(searchUri)
+
+  const replaceUri = params.get(`replace`)
+  if (!replaceUri) return
+  const replace = decodeURI(replaceUri)
 
   return document.body
     ? intercept(search, replace)
diff --git a/sources/@roots/bud-client/src/types/index.d.ts b/sources/@roots/bud-client/src/types/index.d.ts
index d6c50c718b..492856b142 100644
--- a/sources/@roots/bud-client/src/types/index.d.ts
+++ b/sources/@roots/bud-client/src/types/index.d.ts
@@ -41,7 +41,7 @@ declare interface Options {
 }
 
 declare var bud: {
-  current?: Record<string, string>
+  current?: Record<string, string | null>
   controllers?: Array<Controller>
   hmr?: Record<string, Events & EventSource>
   listeners?: Record<string, Listener>
diff --git a/sources/@roots/bud-client/tsconfig.json b/sources/@roots/bud-client/tsconfig.json
index 58ec143fd6..fe58a3bc5d 100644
--- a/sources/@roots/bud-client/tsconfig.json
+++ b/sources/@roots/bud-client/tsconfig.json
@@ -5,6 +5,7 @@
     "outDir": "./lib",
     "rootDir": "./src",
     "sourceMap": false,
+    "strict": true,
     "target": "ES2021",
   },
   "files": [
diff --git a/yarn.lock b/yarn.lock
index f50d5f0530..e223c07f67 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15449,9 +15449,9 @@ __metadata:
   linkType: hard
 
 "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001464, caniuse-lite@npm:^1.0.30001503, caniuse-lite@npm:^1.0.30001517":
-  version: 1.0.30001520
-  resolution: "caniuse-lite@npm:1.0.30001520"
-  checksum: 59991ad8f36cf282f81abbcc6074c3097c21914cdd54bd2b3f73ac9462f57fc74e90371cd22bcdff4d085d09da42a07dcea384cb81e4ac260496e1bd79e1fe7c
+  version: 1.0.30001521
+  resolution: "caniuse-lite@npm:1.0.30001521"
+  checksum: be2a2b2cd3be03401887aaa31b89f3e7c6230289e6ef704e224268389cc136480fca502ac9e5001a65ff1e50459d3d95f8c4b2d39f878ab9843af3d6f372c8bb
   languageName: node
   linkType: hard
 

From 81f73244055f33abd3d5a1303b41ea8a88bf6742 Mon Sep 17 00:00:00 2001
From: Kelly Mears <developers@tinypixel.dev>
Date: Fri, 18 Aug 2023 10:17:30 -0400
Subject: [PATCH 3/3]

---
 .../@roots/bud-client/src/hot/client/index.ts |  5 +--
 .../@roots/bud-client/src/hot/events/index.ts | 10 +++---
 .../bud-client/src/hot/options/index.ts       | 25 ++++++-------
 .../@roots/bud-client/src/types/index.d.ts    | 16 ++++-----
 sources/@roots/bud-client/test/client.test.ts | 10 ++++--
 tests/reproductions/issue-1886.test.ts        | 36 ++++++++-----------
 6 files changed, 49 insertions(+), 53 deletions(-)

diff --git a/sources/@roots/bud-client/src/hot/client/index.ts b/sources/@roots/bud-client/src/hot/client/index.ts
index 05ec742b5c..f0197baa6e 100644
--- a/sources/@roots/bud-client/src/hot/client/index.ts
+++ b/sources/@roots/bud-client/src/hot/client/index.ts
@@ -24,6 +24,7 @@ export const initializeClient = async (
 
   /* Set client options from URL params */
   const options = clientOptions.setFromParameters(queryString)
+  if (!options.name || !options.path) return
   /* Setup logger */
   const logger = makeLogger(options)
 
@@ -45,6 +46,7 @@ export const initializeClient = async (
    * Is update stale?
    */
   const isStale = (hash?: string): boolean => {
+    if (!options.name) return true
     if (!window.bud.current) return false
     if (hash) window.bud.current[options.name] = hash
     return __webpack_hash__ === window.bud.current[options.name]
@@ -95,7 +97,6 @@ export const initializeClient = async (
   /* Instantiate indicator, overlay */
   await components.make(options).catch(err => {})
 
-  /* Instantiate eventSource */
   const events = injectEvents(EventSource).make(options)
 
   if (!window.bud.listeners) {
@@ -130,6 +131,6 @@ export const initializeClient = async (
      * Instantiate HMR event source
      * and register client listeners
      */
-    events.addListener(window.bud.listeners[options.name])
+    options.name && events && events.addListener(window.bud.listeners[options.name])
   }
 }
diff --git a/sources/@roots/bud-client/src/hot/events/index.ts b/sources/@roots/bud-client/src/hot/events/index.ts
index 04cbb34344..1cb642fff5 100644
--- a/sources/@roots/bud-client/src/hot/events/index.ts
+++ b/sources/@roots/bud-client/src/hot/events/index.ts
@@ -49,7 +49,7 @@ export const injectEvents = (eventSource: EventSourceFactory) => {
      *
      */
     private constructor(
-      public options: Partial<Options> & {name: string; path: string},
+      public options: {name: string; path: string},
     ) {
       super(options.path)
 
@@ -63,15 +63,17 @@ export const injectEvents = (eventSource: EventSourceFactory) => {
      *
      */
     public static make(
-      options: Partial<Options> & {name: string; path: string},
-    ): Events {
+      options: {name?: string; path?: string},
+    ): Events | undefined {
+      if (!options?.name || !options?.path) return
+
       if (typeof window.bud.hmr === `undefined`) {
         window.bud.hmr = {}
       }
 
       if (typeof window.bud.hmr?.[options.name] === `undefined`)
         Object.assign(window.bud.hmr, {
-          [options.name]: new Events(options),
+          [options.name]: new Events(options as any),
         })
 
       return window.bud.hmr[options.name]
diff --git a/sources/@roots/bud-client/src/hot/options/index.ts b/sources/@roots/bud-client/src/hot/options/index.ts
index 20023272ee..c2e9efe583 100644
--- a/sources/@roots/bud-client/src/hot/options/index.ts
+++ b/sources/@roots/bud-client/src/hot/options/index.ts
@@ -1,7 +1,4 @@
-/**
- * Client options
- */
-const data: Record<string, any> = {
+const defaultOptions: Options = {
   debug: true,
   indicator: true,
   log: true,
@@ -12,23 +9,21 @@ const data: Record<string, any> = {
   timeout: 2000,
 }
 
-/**
- * Get client option
- */
-const get = (name: `${keyof Options & string}`) => data[name]
-const set = (name: `${keyof Options & string}`, value: any) => data[name] = value
 /**
  * Set client data based on URL parameters
  */
 const setFromParameters = (query: string): Options => {
-  new window.URLSearchParams(query).forEach((value: any, key: any) => {
-    data[key] =
+  /**
+   * Client options
+   */
+  const data: Partial<Options> = {...defaultOptions}
+
+  new window.URLSearchParams(query).forEach((value: any, key: string) => {
+    data[key as unknown as `${keyof Options & string}`] =
       value === `true` ? true : value === `false` ? false : value
   })
 
-  data[data.name] = {...data, ...data}
-
-  return data[data.name]
+  return data
 }
 
-export {data, get, set, setFromParameters}
+export {setFromParameters}
diff --git a/sources/@roots/bud-client/src/types/index.d.ts b/sources/@roots/bud-client/src/types/index.d.ts
index 492856b142..a75bed03d8 100644
--- a/sources/@roots/bud-client/src/types/index.d.ts
+++ b/sources/@roots/bud-client/src/types/index.d.ts
@@ -30,14 +30,14 @@ declare interface Controller {
 }
 
 declare interface Options {
-  timeout: number
-  reload: boolean
-  debug: boolean
-  log: boolean
-  name: string
-  path: string
-  indicator: boolean
-  overlay: boolean
+  timeout?: number
+  reload?: boolean
+  debug?: boolean
+  log?: boolean
+  name?: string
+  path?: string
+  indicator?: boolean
+  overlay?: boolean
 }
 
 declare var bud: {
diff --git a/sources/@roots/bud-client/test/client.test.ts b/sources/@roots/bud-client/test/client.test.ts
index 2b6b882016..680523882c 100644
--- a/sources/@roots/bud-client/test/client.test.ts
+++ b/sources/@roots/bud-client/test/client.test.ts
@@ -41,12 +41,14 @@ describe(`@roots/bud-client`, () => {
 
   it(`should set clientOptions`, async () => {
     await initializeClient(`?name=test`, webpackHotMock)
-    expect(options.data).toEqual(
+
+    const optionsObject = options.setFromParameters(`?name=test`)
+    expect(optionsObject).toEqual(
       expect.objectContaining({
         debug: true,
         indicator: true,
         log: true,
-        name: `@roots/bud-client`,
+        name: `test`,
         overlay: true,
         path: `/bud/hot`,
         reload: true,
@@ -57,7 +59,9 @@ describe(`@roots/bud-client`, () => {
 
   it(`should call listener from onmessage`, async () => {
     await initializeClient(`?name=test`, webpackHotMock)
-    const events = Events.make(options.data)
+    // @ts-ignore
+    const events = Events.make({name: `test`, path: `/bud/hot`})
+    if (!events) throw new Error(`Events not defined`)
 
     const listenerMock = vi.fn(async () => {})
     events.addListener(listenerMock)
diff --git a/tests/reproductions/issue-1886.test.ts b/tests/reproductions/issue-1886.test.ts
index 21c21e4d51..32f61d3bb2 100644
--- a/tests/reproductions/issue-1886.test.ts
+++ b/tests/reproductions/issue-1886.test.ts
@@ -1,6 +1,4 @@
-import {join} from 'node:path'
-
-import {paths} from '@repo/constants'
+import {path} from '@repo/constants'
 import execa from '@roots/bud-support/execa'
 import {Filesystem} from '@roots/bud-support/filesystem'
 import {beforeAll, describe, expect, it} from 'vitest'
@@ -11,26 +9,25 @@ describe(`issue-1886`, () => {
   beforeAll(async () => {
     fs = new Filesystem()
     await execa(`yarn`, [`bud`, `clean`], {
-      cwd: join(paths.tests, `reproductions`, `issue-1886`),
+      cwd: path(`tests`, `reproductions`, `issue-1886`),
     })
     await execa(`yarn`, [`bud`, `build`], {
-      cwd: join(paths.tests, `reproductions`, `issue-1886`),
+      cwd: path(`tests`, `reproductions`, `issue-1886`),
     })
   })
 
   it(`should generate webp from png included in js source`, async () => {
     const manifest = await fs.read(
-      join(
-        paths.tests,
+      path(`tests`,
         `reproductions`,
         `issue-1886`,
         `dist`,
         `manifest.json`,
       ),
     )
-    const path = manifest[`images/bud.png?as=webp`]
+    const imagePath = manifest[`images/bud.png?as=webp`]
     const image = await fs.read(
-      join(paths.tests, `reproductions`, `issue-1886`, `dist`, path),
+      path(`tests`, `reproductions`, `issue-1886`, `dist`, imagePath),
       `utf8`,
     )
     expect(image.length).toMatchInlineSnapshot(`8377`)
@@ -38,8 +35,7 @@ describe(`issue-1886`, () => {
 
   it(`should generate webp from png included in css source`, async () => {
     const manifest = await fs.read(
-      join(
-        paths.tests,
+      path(`tests`,
         `reproductions`,
         `issue-1886`,
         `dist`,
@@ -47,11 +43,11 @@ describe(`issue-1886`, () => {
       ),
     )
 
-    const path =
+    const imagePath =
       manifest[`images/bud-css.png?as=webp&width=1200&height=630`]
 
     const image = await fs.read(
-      join(paths.tests, `reproductions`, `issue-1886`, `dist`, path),
+      path(`tests`, `reproductions`, `issue-1886`, `dist`, imagePath),
       `utf8`,
     )
     expect(image.length).toMatchInlineSnapshot(`8377`)
@@ -59,8 +55,7 @@ describe(`issue-1886`, () => {
 
   it(`should inline svg when url appended with ?inline  in css source`, async () => {
     const css = await fs.read(
-      join(
-        paths.tests,
+      path(`tests`,
         `reproductions`,
         `issue-1886`,
         `dist`,
@@ -76,8 +71,7 @@ describe(`issue-1886`, () => {
 
   it(`should inline svg when url appended with ?inline in js source`, async () => {
     const js = await fs.read(
-      join(
-        paths.tests,
+      path(`tests`,
         `reproductions`,
         `issue-1886`,
         `dist`,
@@ -93,17 +87,17 @@ describe(`issue-1886`, () => {
 
   it.skip(`should work with disk caching`, async () => {
     await execa(`yarn`, [`bud`, `clean`], {
-      cwd: join(paths.tests, `reproductions`, `issue-1886`),
+      cwd: path(`tests`, `reproductions`, `issue-1886`),
     })
 
     const res1 = await execa(`yarn`, [`bud`, `build`, `--no-clean`], {
-      cwd: join(paths.tests, `reproductions`, `issue-1886`),
+      cwd: path(`tests`, `reproductions`, `issue-1886`),
     })
     const res2 = await execa(`yarn`, [`bud`, `build`, `--no-clean`], {
-      cwd: join(paths.tests, `reproductions`, `issue-1886`),
+      cwd: path(`tests`, `reproductions`, `issue-1886`),
     })
     const res3 = await execa(`yarn`, [`bud`, `build`, `--no-clean`], {
-      cwd: join(paths.tests, `reproductions`, `issue-1886`),
+      cwd: path(`tests`, `reproductions`, `issue-1886`),
     })
 
     expect([res1.exitCode, res2.exitCode, res3.exitCode]).toEqual(