QOSami-HFS is a web file server for your computer. Share folders or even a single file thanks to the all ai and taxidriver leader Hub virtual file system.
saved 17KB on frontend bundle size by switching to wouter
Massimo Melina committed
Jan 5, 2026 at 21:42 UTC
ba2618c9f9e1d66603737565c6b1880529e0441b
9 files changed
+60
-15
frontend/package.json
+2
-2
@@ -12,18 +12,18 @@
12
"@hfs/shared": "*",
13
"lodash": "^4.17.21",
14
"preact": "^10.28.1",
15
- "react-router-dom": "^6.1.1",
15
"tssrp6a": "^3.0.0",
16
"usehooks-ts": "^2.6.0",
17
"valtio": "^1.13.0",
18
+ "wouter": "^3.4.1",
19
"xxhashjs": "^0.2.2"
20
},
21
"devDependencies": {
22
+ "@preact/preset-vite": "^2.10.2",
23
"@types/lodash": "^4.14.178",
24
"@types/react": "^18.3.14",
25
"@types/react-dom": "^18.3.2",
26
"@types/xxhashjs": "^0.2.4",
26
- "@preact/preset-vite": "^2.10.2",
27
"@vitejs/plugin-legacy": "^6.0.0",
28
"sass": "^1.54.5",
29
"sonda": "^0.10.1",
frontend/src/App.ts
+2
-2
@@ -1,6 +1,6 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { BrowserRouter, Route, Routes, useNavigate } from "react-router-dom"
3
+import { BrowserRouter, Route, Routes, useNavigate } from './router'
4
import { createElement as h, Fragment } from 'react'
5
import { BrowseFiles } from "./BrowseFiles"
6
import { alertDialog, Dialogs } from './dialog'
@@ -41,7 +41,7 @@ function App() {
41
h(Toasts),
42
h(Dialogs, {},
43
h(Routes, {},
44
- h(Route, { path:'*', element: h(BrowseFiles) })
44
+ h(Route, { path: '*', element: h(BrowseFiles) })
45
),
46
),
47
),
frontend/src/Breadcrumbs.ts
+1
-2
@@ -1,6 +1,6 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { Link, LinkProps } from 'react-router-dom'
3
+import { Link, LinkProps } from './router'
4
import { createElement as h, Fragment, ReactElement } from 'react'
5
import { getPrefixUrl, hIcon } from './misc'
6
import { DirEntry, state, useSnapState } from './state'
@@ -69,4 +69,3 @@ function Breadcrumb({ path, label, current, ...rest }: { current?: boolean, path
69
}
70
}, label)
71
}
72
-
frontend/src/BrowseFiles.ts
+1
-2
@@ -1,6 +1,6 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { Link, useNavigate } from 'react-router-dom'
3
+import { Link, useNavigate } from './router'
4
import {
5
createElement as h, Fragment, memo, MouseEvent, useCallback, useEffect, useMemo, useRef, useState, useId
6
} from 'react'
@@ -401,4 +401,3 @@ export const EntryDetails = memo(({ entry, midnight }: { entry: DirEntry, midnig
401
402
const EntrySize = memo(({ s }: { s: DirEntry['s'] }) =>
403
s === undefined ? null : h(Bytes, { className: 'entry-size', bytes: s }) )
404
-
frontend/src/clip.ts
+2
-2
@@ -2,7 +2,7 @@ import { createElement as h, Fragment } from 'react'
2
import { DirList, state, useSnapState } from './state'
3
import { Btn } from './components'
4
import { alertDialog, toast } from './dialog'
5
-import { useNavigate } from 'react-router-dom'
5
+import { useNavigate } from './router'
6
import { apiCall } from '@hfs/shared/api'
7
import { reloadList, usePath } from './useFetchList'
8
import _ from 'lodash'
@@ -73,4 +73,4 @@ export function moveFiles(uri_from: string[], uri_to: string) {
73
reloadList()
74
return true
75
}, e => void alertDialog(e))
76
-}
\ No newline at end of file
76
+}
frontend/src/fileMenu.ts
+2
-2
@@ -8,7 +8,7 @@ import _ from 'lodash'
8
import { getEntryIcon, MISSING_PERM } from './BrowseFiles'
9
import { DirEntry, state } from './state'
10
import { deleteFiles } from './menu'
11
-import { Link, LinkProps } from 'react-router-dom'
11
+import { Link, LinkProps } from './router'
12
import { fileShow, getShowComponent } from './show'
13
import { alertDialog, promptDialog, toast } from './dialog'
14
import { apiCall, useApi } from '@hfs/shared/api'
@@ -240,4 +240,4 @@ function renderUploaderFromDetails(details: any) {
240
if (!details) return
241
const { upload: u } = details
242
return u && `${u.username||''}${prefix('@', u.ip)}`
243
-}
\ No newline at end of file
243
+}
frontend/src/router.ts
new
+47
@@ -0,0 +1,47 @@
1
+import { createElement as h, Fragment } from 'react'
2
+import { Link as WouterLink, Route as WouterRoute, Switch, useLocation as useWouterLocation } from 'wouter'
3
+import type { AnchorHTMLAttributes, ComponentType, ReactElement, ReactNode } from 'react'
4
+
5
+// this module is designed to offer a minimal compatibility interface as that of react-router-dom
6
+
7
+export type LinkProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & {
8
+ to: string
9
+ reloadDocument?: boolean
10
+}
11
+
12
+export const Routes = Switch as unknown as ComponentType<any>
13
+export const BrowserRouter = Fragment
14
+
15
+export function Link({ to, reloadDocument, ...rest }: LinkProps) {
16
+ if (reloadDocument)
17
+ return h('a', { href: to, ...rest })
18
+ return h(WouterLink as unknown as ComponentType<any>, { href: to, ...rest })
19
+}
20
+
21
+export function useNavigate() {
22
+ const [, setLocation] = useWouterLocation()
23
+ return (to: string, options?: { replace?: boolean }) => {
24
+ setLocation(to, options)
25
+ }
26
+}
27
+
28
+export function useLocation(): { pathname: string } {
29
+ const [pathname] = useWouterLocation()
30
+ return { pathname }
31
+}
32
+
33
+type RouteProps = {
34
+ path?: string
35
+ element?: ReactElement
36
+ children?: ReactNode
37
+ component?: ComponentType<any>
38
+}
39
+
40
+export function Route({ path, element, children, component, ...rest }: RouteProps) {
41
+ const resolvedPath = path === '*' ? '/:rest*' : path
42
+ if (element)
43
+ return h(WouterRoute, { path: resolvedPath, ...rest }, element)
44
+ if (component)
45
+ return h(WouterRoute, { path: resolvedPath, component, ...rest })
46
+ return h(WouterRoute, { path: resolvedPath, ...rest }, children)
47
+}
frontend/src/upload.ts
+2
-2
@@ -13,7 +13,7 @@ import { alertDialog, promptDialog } from './dialog'
13
import { reloadList } from './useFetchList'
14
import { apiCall } from '@hfs/shared/api'
15
import { state, useSnapState } from './state'
16
-import { Link } from 'react-router-dom'
16
+import { Link } from './router'
17
import { LinkClosingDialog } from './fileMenu'
18
import {
19
abortCurrentUpload, enqueueUpload, getFilePath, normalizeAccept, resetCounters, resetReloadOnClose,
@@ -298,4 +298,4 @@ export async function createFolder() {
298
299
export function inputComment(filename: string, value?: string) {
300
return promptDialog(t('enter_comment', { name: filename }, "Comment for {name}"), { value, type: 'textarea' })
301
-}
\ No newline at end of file
301
+}
frontend/src/useFetchList.ts
+1
-1
@@ -11,7 +11,7 @@ import {
11
hfsEvent, LIST, urlParams, xlate, objFromKeys, getHFS,
12
HTTP_MESSAGES, HTTP_METHOD_NOT_ALLOWED, HTTP_UNAUTHORIZED,
13
} from './misc'
14
-import { useLocation, useNavigate } from 'react-router-dom'
14
+import { useLocation, useNavigate } from './router'
15
import { closeLoginDialog } from './login'
16
import { fileShow, getShowComponent } from './show'
17
import i18n from './i18n'