@samitouri / QOSami-HFS / commits / ba2618c9

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'