@@ -8,10 +8,23 @@ import {
88import { HonoHttpServer } from './adapter' ;
99import { createHonoApp } from '@objectstack/hono' ;
1010import { serveStatic } from '@hono/node-server/serve-static' ;
11+ import * as fs from 'fs' ;
12+ import * as path from 'path' ;
13+
14+ export interface StaticMount {
15+ root : string ;
16+ path ?: string ;
17+ rewrite ?: boolean ;
18+ spa ?: boolean ;
19+ }
1120
1221export interface HonoPluginOptions {
1322 port ?: number ;
1423 staticRoot ?: string ;
24+ /**
25+ * Multiple static resource mounts
26+ */
27+ staticMounts ?: StaticMount [ ] ;
1528 /**
1629 * REST server configuration
1730 * Controls automatic endpoint generation and API behavior
@@ -112,31 +125,78 @@ export class HonoServerPlugin implements Plugin {
112125 }
113126
114127 // Configure Static Files & SPA Fallback
128+ const mounts : StaticMount [ ] = this . options . staticMounts || [ ] ;
129+
130+ // Backward compatibility for staticRoot
115131 if ( this . options . staticRoot ) {
132+ mounts . push ( {
133+ root : this . options . staticRoot ,
134+ path : '/' ,
135+ rewrite : false ,
136+ spa : this . options . spaFallback
137+ } ) ;
138+ }
139+
140+ if ( mounts . length > 0 ) {
116141 const rawApp = this . server . getRawApp ( ) ;
117- const staticRoot = this . options . staticRoot ;
118-
119- ctx . logger . debug ( 'Configuring static files' , { root : staticRoot , spa : this . options . spaFallback } ) ;
120142
121- // 1. Static Files
122- rawApp . get ( '/*' , serveStatic ( { root : staticRoot } ) ) ;
123-
124- // 2. SPA Fallback
125- if ( this . options . spaFallback ) {
126- rawApp . get ( '*' , async ( c , next ) => {
127- // Skip API paths
128- const config = this . options . restConfig || { } ;
129- const basePath = config . api ?. basePath || '/api' ;
130-
131- if ( c . req . path . startsWith ( basePath ) ) {
132- return next ( ) ;
143+ for ( const mount of mounts ) {
144+ const mountRoot = path . resolve ( process . cwd ( ) , mount . root ) ;
145+
146+ if ( ! fs . existsSync ( mountRoot ) ) {
147+ ctx . logger . warn ( `Static mount root not found: ${ mountRoot } . Skipping.` ) ;
148+ continue ;
149+ }
150+
151+ const mountPath = mount . path || '/' ;
152+ const normalizedPath = mountPath . startsWith ( '/' ) ? mountPath : `/${ mountPath } ` ;
153+ const routePattern = normalizedPath === '/' ? '/*' : `${ normalizedPath . replace ( / \/ $ / , '' ) } /*` ;
154+
155+ // Routes to register: both /mount and /mount/*
156+ const routes = normalizedPath === '/' ? [ routePattern ] : [ normalizedPath , routePattern ] ;
157+
158+ ctx . logger . debug ( 'Mounting static files' , {
159+ to : routes ,
160+ from : mountRoot ,
161+ rewrite : mount . rewrite ,
162+ spa : mount . spa
163+ } ) ;
164+
165+ routes . forEach ( route => {
166+ // 1. Serve Static Files
167+ rawApp . get (
168+ route ,
169+ serveStatic ( {
170+ root : mount . root ,
171+ rewriteRequestPath : ( reqPath ) => {
172+ if ( mount . rewrite && normalizedPath !== '/' ) {
173+ // /console/assets/style.css -> /assets/style.css
174+ if ( reqPath . startsWith ( normalizedPath ) ) {
175+ return reqPath . substring ( normalizedPath . length ) || '/' ;
176+ }
177+ }
178+ return reqPath ;
179+ }
180+ } )
181+ ) ;
182+
183+ // 2. SPA Fallback (Scoped)
184+ if ( mount . spa ) {
185+ rawApp . get ( route , async ( c , next ) => {
186+ // Skip if API path check
187+ const config = this . options . restConfig || { } ;
188+ const basePath = config . api ?. basePath || '/api' ;
189+
190+ if ( c . req . path . startsWith ( basePath ) ) {
191+ return next ( ) ;
192+ }
193+
194+ return serveStatic ( {
195+ root : mount . root ,
196+ rewriteRequestPath : ( ) => 'index.html'
197+ } ) ( c , next ) ;
198+ } ) ;
133199 }
134-
135- // Fallback to index.html
136- return serveStatic ( {
137- root : staticRoot ,
138- rewriteRequestPath : ( ) => 'index.html'
139- } ) ( c , next ) ;
140200 } ) ;
141201 }
142202 }
0 commit comments