Repository: honojs/hono
File: deno_dist/helper/ssg/index.ts (TypeScript)

Code before:
}

interface RouteContent {
  content: string | ArrayBuffer
  mimeType: string
}

const fetchRouteContent = async <E extends Env = Env, S extends Schema = {}>(
  app: Hono<E, S>,
  path: string,
  baseURL: string
): Promise<RouteContent | null> => {
  try {
    const url = new URL(path, baseURL).toString()
    const response = await app.fetch(new Request(url))
    const mimeType = response.headers.get('Content-Type')?.split(';')[0] || 'text/plain'
    const content = await parseResponseContent(response)
    return { content, mimeType }
  } catch (error) {
    console.error(`Error fetching content for path: ${path}`, error)
    return null
  }
}

const isDynamicRoute = (path: string): boolean => {
  return /:\w+|\*/.test(path)
}

/**
 * @experimental
 * `saveContentToFiles` is an experimental feature.
 * The API might be changed.
 */
export const saveContentToFiles = async (
  htmlMap: Map<string, { content: string | ArrayBuffer; mimeType: string }>,
  fsModule: FileSystemModule,
  outDir: string
): Promise<string[]> => {
  const files: string[] = []

  for (const [routePath, { content, mimeType }] of htmlMap) {
    const filePath = generateFilePath(routePath, outDir, mimeType)
    const dirPath = dirname(filePath)

    await fsModule.mkdir(dirPath, { recursive: true })
    if (typeof content === 'string') {
      await fsModule.writeFile(filePath, content)
    } else if (content instanceof ArrayBuffer) {
      await fsModule.writeFile(filePath, Buffer.from(content))
    }
    files.push(filePath)

Review comment (bug):
The determination of the named path parameters here is incorrect.

`/foo:bar` is not a dynamic route. You can see the spec here:

https://github.com/honojs/hono/blob/a00fc566e93374acd4c8999443f4b48f79da05b9/src/utils/url.ts#L55

So you have to split the path with `/` and determine if there is a `:` at the beginning of it or not.

Code after:

  return htmlMap
}

const isDynamicRoute = (path: string): boolean => {
  return path.split('/').some((segment) => segment.startsWith(':') || segment.includes('*'))
}

/**
 * @experimental
 * `saveContentToFiles` is an experimental feature.
 * The API might be changed.
 */
export const saveContentToFiles = async (
  htmlMap: Map<string, { content: string | ArrayBuffer; mimeType: string }>,
  fsModule: FileSystemModule,
  outDir: string
): Promise<string[]> => {
  const files: string[] = []

  for (const [routePath, { content, mimeType }] of htmlMap) {
    const filePath = generateFilePath(routePath, outDir, mimeType)
    const dirPath = dirname(filePath)

    await fsModule.mkdir(dirPath, { recursive: true })
    if (typeof content === 'string') {
      await fsModule.writeFile(filePath, content)
    } else if (content instanceof ArrayBuffer) {
      await fsModule.writeFile(filePath, Buffer.from(content))
    }
    files.push(filePath)
  }

  return files
}

/**
 * @experimental
 * `ToSSGInterface` is an experimental feature.
 * The API might be changed.
 */
export interface ToSSGInterface<
  E extends Env = Env,
  S extends Schema = {},
  BasePath extends string = '/'
> {
  (
    app: Hono<E, S, BasePath>,
    fsModule: FileSystemModule,
    options?: { dir?: string }
  ): Promise<ToSSGResult>