diff --git a/lua/nvim-tree/actions/finders/find-file.lua b/lua/nvim-tree/actions/finders/find-file.lua
index cfc51c0dd19..fcfd24941ff 100644
--- a/lua/nvim-tree/actions/finders/find-file.lua
+++ b/lua/nvim-tree/actions/finders/find-file.lua
@@ -2,7 +2,6 @@ local log = require "nvim-tree.log"
 local view = require "nvim-tree.view"
 local utils = require "nvim-tree.utils"
 local renderer = require "nvim-tree.renderer"
-local reload = require "nvim-tree.explorer.reload"
 local core = require "nvim-tree.core"
 local Iterator = require "nvim-tree.iterators.node-iterator"
 
@@ -13,7 +12,8 @@ local running = {}
 ---Find a path in the tree, expand it and focus it
 ---@param path string relative or absolute
 function M.fn(path)
-  if not core.get_explorer() or not view.is_visible() then
+  local explorer = core.get_explorer()
+  if not explorer or not view.is_visible() then
     return
   end
 
@@ -32,7 +32,7 @@ function M.fn(path)
 
   -- refresh the contents of all parents, expanding groups as needed
   if utils.get_node_from_path(path_real) == nil then
-    reload.refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h"))
+    explorer:refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h"))
   end
 
   local line = core.get_nodes_starting_line()
diff --git a/lua/nvim-tree/actions/reloaders.lua b/lua/nvim-tree/actions/reloaders.lua
index 626b280db7f..02126b515d3 100644
--- a/lua/nvim-tree/actions/reloaders.lua
+++ b/lua/nvim-tree/actions/reloaders.lua
@@ -1,21 +1,22 @@
 local git = require "nvim-tree.git"
 local view = require "nvim-tree.view"
 local renderer = require "nvim-tree.renderer"
-local explorer_module = require "nvim-tree.explorer"
 local core = require "nvim-tree.core"
 local explorer_node = require "nvim-tree.explorer.node"
 local Iterator = require "nvim-tree.iterators.node-iterator"
 
 local M = {}
 
----@param node Explorer|nil
+---@param explorer Explorer|nil
 ---@param projects table
-local function refresh_nodes(node, projects)
-  Iterator.builder({ node })
+local function refresh_nodes(explorer, projects)
+  Iterator.builder({ explorer })
     :applier(function(n)
       if n.nodes then
         local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path)
-        explorer_module.reload(n, projects[toplevel] or {})
+        if explorer then
+          explorer:reload(n, projects[toplevel] or {})
+        end
       end
     end)
     :recursor(function(n)
diff --git a/lua/nvim-tree/core.lua b/lua/nvim-tree/core.lua
index 50072d40bb5..a255233dab3 100644
--- a/lua/nvim-tree/core.lua
+++ b/lua/nvim-tree/core.lua
@@ -16,7 +16,7 @@ function M.init(foldername)
   if TreeExplorer then
     TreeExplorer:destroy()
   end
-  TreeExplorer = explorer.Explorer.new(foldername)
+  TreeExplorer = explorer:new(foldername)
   if not first_init_done then
     events._dispatch_ready()
     first_init_done = true
diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua
index 89b02e96e68..f6cdbcaaaa0 100644
--- a/lua/nvim-tree/explorer/init.lua
+++ b/lua/nvim-tree/explorer/init.lua
@@ -1,17 +1,23 @@
+local builders = require "nvim-tree.explorer.node-builders"
 local git = require "nvim-tree.git"
+local log = require "nvim-tree.log"
 local notify = require "nvim-tree.notify"
+local utils = require "nvim-tree.utils"
 local watch = require "nvim-tree.explorer.watch"
 local explorer_node = require "nvim-tree.explorer.node"
+
+local NodeIterator = require "nvim-tree.iterators.node-iterator"
+local Watcher = require "nvim-tree.watcher"
+
 local Filters = require "nvim-tree.explorer.filters"
 local Marks = {} -- circular dependencies
 local LiveFilter = require "nvim-tree.explorer.live-filter"
 local Sorters = require "nvim-tree.explorer.sorters"
 local Clipboard = {} -- circular dependencies
 
-local M = {}
+local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
 
-M.explore = require("nvim-tree.explorer.explore").explore
-M.reload = require("nvim-tree.explorer.reload").reload
+local config
 
 ---@class Explorer
 ---@field absolute_path string
@@ -22,13 +28,13 @@ M.reload = require("nvim-tree.explorer.reload").reload
 ---@field sorters Sorter
 ---@field marks Marks
 ---@field clipboard Clipboard
-
 local Explorer = {}
-Explorer.__index = Explorer
+
+Explorer.explore = require("nvim-tree.explorer.explore").explore
 
 ---@param path string|nil
 ---@return Explorer|nil
-function Explorer.new(path)
+function Explorer:new(path)
   local err
 
   if path then
@@ -42,27 +48,24 @@ function Explorer.new(path)
   end
 
   ---@class Explorer
-  local explorer = setmetatable({
+  local o = setmetatable({
     absolute_path = path,
     nodes = {},
     open = true,
-    sorters = Sorters:new(M.config),
+    sorters = Sorters:new(config),
   }, Explorer)
-  explorer.watcher = watch.create_watcher(explorer)
-  explorer.filters = Filters:new(M.config, explorer)
-  explorer.live_filter = LiveFilter:new(M.config, explorer)
-  explorer.marks = Marks:new(M.config, explorer)
-  explorer.clipboard = Clipboard:new(M.config, explorer)
-  explorer:_load(explorer)
-  return explorer
-end
+  setmetatable(o, self)
+  self.__index = self
 
----@private
----@param node Node
-function Explorer:_load(node)
-  local cwd = node.link_to or node.absolute_path
-  local git_status = git.load_project_status(cwd)
-  M.explore(node, git_status, self)
+  o.watcher = watch.create_watcher(o)
+  o.filters = Filters:new(config, o)
+  o.live_filter = LiveFilter:new(config, o)
+  o.marks = Marks:new(config, o)
+  o.clipboard = Clipboard:new(config, o)
+
+  o:_load(o)
+
+  return o
 end
 
 ---@param node Node
@@ -82,17 +85,253 @@ function Explorer:destroy()
   iterate(self)
 end
 
-function M.setup(opts)
-  M.config = opts
+---@param node Node
+---@param git_status table|nil
+function Explorer:reload(node, git_status)
+  local cwd = node.link_to or node.absolute_path
+  local handle = vim.loop.fs_scandir(cwd)
+  if not handle then
+    return
+  end
+
+  local profile = log.profile_start("reload %s", node.absolute_path)
+
+  local filter_status = self.filters:prepare(git_status)
+
+  if node.group_next then
+    node.nodes = { node.group_next }
+    node.group_next = nil
+  end
+
+  local remain_childs = {}
+
+  local node_ignored = explorer_node.is_git_ignored(node)
+  ---@type table<string, Node>
+  local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
+
+  -- To reset we must 'zero' everything that we use
+  node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
+    git = 0,
+    buf = 0,
+    dotfile = 0,
+    custom = 0,
+    bookmark = 0,
+  })
+
+  while true do
+    local name, t = vim.loop.fs_scandir_next(handle)
+    if not name then
+      break
+    end
+
+    local abs = utils.path_join { cwd, name }
+    ---@type uv.fs_stat.result|nil
+    local stat = vim.loop.fs_stat(abs)
+
+    local filter_reason = self.filters:should_filter_as_reason(abs, stat, filter_status)
+    if filter_reason == FILTER_REASON.none then
+      remain_childs[abs] = true
+
+      -- Recreate node if type changes.
+      if nodes_by_path[abs] then
+        local n = nodes_by_path[abs]
+
+        if n.type ~= t then
+          utils.array_remove(node.nodes, n)
+          explorer_node.node_destroy(n)
+          nodes_by_path[abs] = nil
+        end
+      end
+
+      if not nodes_by_path[abs] then
+        local new_child = nil
+        if t == "directory" and vim.loop.fs_access(abs, "R") and Watcher.is_fs_event_capable(abs) then
+          new_child = builders.folder(node, abs, name, stat)
+        elseif t == "file" then
+          new_child = builders.file(node, abs, name, stat)
+        elseif t == "link" then
+          local link = builders.link(node, abs, name, stat)
+          if link.link_to ~= nil then
+            new_child = link
+          end
+        end
+        if new_child then
+          table.insert(node.nodes, new_child)
+          nodes_by_path[abs] = new_child
+        end
+      else
+        local n = nodes_by_path[abs]
+        if n then
+          n.executable = builders.is_executable(abs) or false
+          n.fs_stat = stat
+        end
+      end
+    else
+      for reason, value in pairs(FILTER_REASON) do
+        if filter_reason == value then
+          node.hidden_stats[reason] = node.hidden_stats[reason] + 1
+        end
+      end
+    end
+  end
+
+  node.nodes = vim.tbl_map(
+    self:update_status(nodes_by_path, node_ignored, git_status),
+    vim.tbl_filter(function(n)
+      if remain_childs[n.absolute_path] then
+        return remain_childs[n.absolute_path]
+      else
+        explorer_node.node_destroy(n)
+        return false
+      end
+    end, node.nodes)
+  )
+
+  local is_root = not node.parent
+  local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
+  if config.group_empty and not is_root and child_folder_only then
+    node.group_next = child_folder_only
+    local ns = self:reload(child_folder_only, git_status)
+    node.nodes = ns or {}
+    log.profile_end(profile)
+    return ns
+  end
+
+  self.sorters:sort(node.nodes)
+  self.live_filter:apply_filter(node)
+  log.profile_end(profile)
+  return node.nodes
+end
+
+---TODO #2837 #2871 move this and similar to node
+---Refresh contents and git status for a single node
+---@param node Node
+---@param callback function
+function Explorer:refresh_node(node, callback)
+  if type(node) ~= "table" then
+    callback()
+  end
+
+  local parent_node = utils.get_parent_of_group(node)
+
+  self:reload_and_get_git_project(node.absolute_path, function(toplevel, project)
+    self:reload(parent_node, project)
+
+    self:update_parent_statuses(parent_node, project, toplevel)
+
+    callback()
+  end)
+end
+
+---Refresh contents of all nodes to a path: actual directory and links.
+---Groups will be expanded if needed.
+---@param path string absolute path
+function Explorer:refresh_parent_nodes_for_path(path)
+  local profile = log.profile_start("refresh_parent_nodes_for_path %s", path)
+
+  -- collect parent nodes from the top down
+  local parent_nodes = {}
+  NodeIterator.builder({ self })
+    :recursor(function(node)
+      return node.nodes
+    end)
+    :applier(function(node)
+      local abs_contains = node.absolute_path and path:find(node.absolute_path, 1, true) == 1
+      local link_contains = node.link_to and path:find(node.link_to, 1, true) == 1
+      if abs_contains or link_contains then
+        table.insert(parent_nodes, node)
+      end
+    end)
+    :iterate()
+
+  -- refresh in order; this will expand groups as needed
+  for _, node in ipairs(parent_nodes) do
+    local toplevel = git.get_toplevel(node.absolute_path)
+    local project = git.get_project(toplevel) or {}
+
+    self:reload(node, project)
+    self:update_parent_statuses(node, project, toplevel)
+  end
+
+  log.profile_end(profile)
+end
+
+---@private
+---@param node Node
+function Explorer:_load(node)
+  local cwd = node.link_to or node.absolute_path
+  local git_status = git.load_project_status(cwd)
+  Explorer.explore(node, git_status, self)
+end
+
+function Explorer.setup(opts)
+  config = opts
   require("nvim-tree.explorer.node").setup(opts)
   require("nvim-tree.explorer.explore").setup(opts)
-  require("nvim-tree.explorer.reload").setup(opts)
   require("nvim-tree.explorer.watch").setup(opts)
 
   Marks = require "nvim-tree.marks"
   Clipboard = require "nvim-tree.actions.fs.clipboard"
 end
 
-M.Explorer = Explorer
+---@private
+---@param nodes_by_path table
+---@param node_ignored boolean
+---@param status table|nil
+---@return fun(node: Node): table
+function Explorer:update_status(nodes_by_path, node_ignored, status)
+  return function(node)
+    if nodes_by_path[node.absolute_path] then
+      explorer_node.update_git_status(node, node_ignored, status)
+    end
+    return node
+  end
+end
+
+---TODO #2837 #2871 move this and similar to node
+---@private
+---@param path string
+---@param callback fun(toplevel: string|nil, project: table|nil)
+function Explorer:reload_and_get_git_project(path, callback)
+  local toplevel = git.get_toplevel(path)
+
+  git.reload_project(toplevel, path, function()
+    callback(toplevel, git.get_project(toplevel) or {})
+  end)
+end
+
+---TODO #2837 #2871 move this and similar to node
+---@private
+---@param node Node
+---@param project table|nil
+---@param root string|nil
+function Explorer:update_parent_statuses(node, project, root)
+  while project and node do
+    -- step up to the containing project
+    if node.absolute_path == root then
+      -- stop at the top of the tree
+      if not node.parent then
+        break
+      end
+
+      root = git.get_toplevel(node.parent.absolute_path)
+
+      -- stop when no more projects
+      if not root then
+        break
+      end
+
+      -- update the containing project
+      project = git.get_project(root)
+      git.reload_project(root, node.absolute_path, nil)
+    end
+
+    -- update status
+    explorer_node.update_git_status(node, explorer_node.is_git_ignored(node.parent), project)
+
+    -- maybe parent
+    node = node.parent
+  end
+end
 
-return M
+return Explorer
diff --git a/lua/nvim-tree/explorer/reload.lua b/lua/nvim-tree/explorer/reload.lua
deleted file mode 100644
index ff0f75990aa..00000000000
--- a/lua/nvim-tree/explorer/reload.lua
+++ /dev/null
@@ -1,251 +0,0 @@
-local utils = require "nvim-tree.utils"
-local builders = require "nvim-tree.explorer.node-builders"
-local explorer_node = require "nvim-tree.explorer.node"
-local git = require "nvim-tree.git"
-local log = require "nvim-tree.log"
-
-local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
-local NodeIterator = require "nvim-tree.iterators.node-iterator"
-local Watcher = require "nvim-tree.watcher"
-
-local M = {}
-
----@param nodes_by_path table
----@param node_ignored boolean
----@param status table
----@return fun(node: Node): table
-local function update_status(nodes_by_path, node_ignored, status)
-  return function(node)
-    if nodes_by_path[node.absolute_path] then
-      explorer_node.update_git_status(node, node_ignored, status)
-    end
-    return node
-  end
-end
-
----@param path string
----@param callback fun(toplevel: string|nil, project: table|nil)
-local function reload_and_get_git_project(path, callback)
-  local toplevel = git.get_toplevel(path)
-
-  git.reload_project(toplevel, path, function()
-    callback(toplevel, git.get_project(toplevel) or {})
-  end)
-end
-
----@param node Node
----@param project table|nil
----@param root string|nil
-local function update_parent_statuses(node, project, root)
-  while project and node do
-    -- step up to the containing project
-    if node.absolute_path == root then
-      -- stop at the top of the tree
-      if not node.parent then
-        break
-      end
-
-      root = git.get_toplevel(node.parent.absolute_path)
-
-      -- stop when no more projects
-      if not root then
-        break
-      end
-
-      -- update the containing project
-      project = git.get_project(root)
-      git.reload_project(root, node.absolute_path, nil)
-    end
-
-    -- update status
-    explorer_node.update_git_status(node, explorer_node.is_git_ignored(node.parent), project)
-
-    -- maybe parent
-    node = node.parent
-  end
-end
-
----@param node Node
----@param git_status table
-function M.reload(node, git_status)
-  local explorer = require("nvim-tree.core").get_explorer()
-  if not explorer then
-    return
-  end
-  local cwd = node.link_to or node.absolute_path
-  local handle = vim.loop.fs_scandir(cwd)
-  if not handle then
-    return
-  end
-
-  local profile = log.profile_start("reload %s", node.absolute_path)
-
-  local filter_status = explorer.filters:prepare(git_status)
-
-  if node.group_next then
-    node.nodes = { node.group_next }
-    node.group_next = nil
-  end
-
-  local remain_childs = {}
-
-  local node_ignored = explorer_node.is_git_ignored(node)
-  ---@type table<string, Node>
-  local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
-
-  -- To reset we must 'zero' everything that we use
-  node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
-    git = 0,
-    buf = 0,
-    dotfile = 0,
-    custom = 0,
-    bookmark = 0,
-  })
-
-  while true do
-    local name, t = vim.loop.fs_scandir_next(handle)
-    if not name then
-      break
-    end
-
-    local abs = utils.path_join { cwd, name }
-    ---@type uv.fs_stat.result|nil
-    local stat = vim.loop.fs_stat(abs)
-
-    local filter_reason = explorer.filters:should_filter_as_reason(abs, stat, filter_status)
-    if filter_reason == FILTER_REASON.none then
-      remain_childs[abs] = true
-
-      -- Recreate node if type changes.
-      if nodes_by_path[abs] then
-        local n = nodes_by_path[abs]
-
-        if n.type ~= t then
-          utils.array_remove(node.nodes, n)
-          explorer_node.node_destroy(n)
-          nodes_by_path[abs] = nil
-        end
-      end
-
-      if not nodes_by_path[abs] then
-        local new_child = nil
-        if t == "directory" and vim.loop.fs_access(abs, "R") and Watcher.is_fs_event_capable(abs) then
-          new_child = builders.folder(node, abs, name, stat)
-        elseif t == "file" then
-          new_child = builders.file(node, abs, name, stat)
-        elseif t == "link" then
-          local link = builders.link(node, abs, name, stat)
-          if link.link_to ~= nil then
-            new_child = link
-          end
-        end
-        if new_child then
-          table.insert(node.nodes, new_child)
-          nodes_by_path[abs] = new_child
-        end
-      else
-        local n = nodes_by_path[abs]
-        if n then
-          n.executable = builders.is_executable(abs) or false
-          n.fs_stat = stat
-        end
-      end
-    else
-      for reason, value in pairs(FILTER_REASON) do
-        if filter_reason == value then
-          node.hidden_stats[reason] = node.hidden_stats[reason] + 1
-        end
-      end
-    end
-  end
-
-  node.nodes = vim.tbl_map(
-    update_status(nodes_by_path, node_ignored, git_status),
-    vim.tbl_filter(function(n)
-      if remain_childs[n.absolute_path] then
-        return remain_childs[n.absolute_path]
-      else
-        explorer_node.node_destroy(n)
-        return false
-      end
-    end, node.nodes)
-  )
-
-  local is_root = not node.parent
-  local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
-  if M.config.group_empty and not is_root and child_folder_only then
-    node.group_next = child_folder_only
-    local ns = M.reload(child_folder_only, git_status)
-    node.nodes = ns or {}
-    log.profile_end(profile)
-    return ns
-  end
-
-  explorer.sorters:sort(node.nodes)
-  explorer.live_filter:apply_filter(node)
-  log.profile_end(profile)
-  return node.nodes
-end
-
----Refresh contents and git status for a single node
----@param node Node
----@param callback function
-function M.refresh_node(node, callback)
-  if type(node) ~= "table" then
-    callback()
-  end
-
-  local parent_node = utils.get_parent_of_group(node)
-
-  reload_and_get_git_project(node.absolute_path, function(toplevel, project)
-    require("nvim-tree.explorer.reload").reload(parent_node, project)
-
-    update_parent_statuses(parent_node, project, toplevel)
-
-    callback()
-  end)
-end
-
----Refresh contents of all nodes to a path: actual directory and links.
----Groups will be expanded if needed.
----@param path string absolute path
-function M.refresh_parent_nodes_for_path(path)
-  local explorer = require("nvim-tree.core").get_explorer()
-  if not explorer then
-    return
-  end
-
-  local profile = log.profile_start("refresh_parent_nodes_for_path %s", path)
-
-  -- collect parent nodes from the top down
-  local parent_nodes = {}
-  NodeIterator.builder({ explorer })
-    :recursor(function(node)
-      return node.nodes
-    end)
-    :applier(function(node)
-      local abs_contains = node.absolute_path and path:find(node.absolute_path, 1, true) == 1
-      local link_contains = node.link_to and path:find(node.link_to, 1, true) == 1
-      if abs_contains or link_contains then
-        table.insert(parent_nodes, node)
-      end
-    end)
-    :iterate()
-
-  -- refresh in order; this will expand groups as needed
-  for _, node in ipairs(parent_nodes) do
-    local toplevel = git.get_toplevel(node.absolute_path)
-    local project = git.get_project(toplevel) or {}
-
-    M.reload(node, project)
-    update_parent_statuses(node, project, toplevel)
-  end
-
-  log.profile_end(profile)
-end
-
-function M.setup(opts)
-  M.config = opts.renderer
-end
-
-return M
diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua
index 4995d31ebe9..90ccedb20de 100644
--- a/lua/nvim-tree/explorer/watch.lua
+++ b/lua/nvim-tree/explorer/watch.lua
@@ -76,9 +76,12 @@ function M.create_watcher(node)
       else
         log.line("watcher", "node event executing refresh '%s'", node.absolute_path)
       end
-      require("nvim-tree.explorer.reload").refresh_node(node, function()
-        require("nvim-tree.renderer").draw()
-      end)
+      local explorer = require("nvim-tree.core").get_explorer()
+      if explorer then
+        explorer:refresh_node(node, function()
+          require("nvim-tree.renderer").draw()
+        end)
+      end
     end)
   end