Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: honojs/hono
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.0.3
Choose a base ref
...
head repository: honojs/hono
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.0.4
Choose a head ref
  • 9 commits
  • 18 files changed
  • 4 contributors

Commits on Feb 16, 2024

  1. Copy the full SHA
    e49c140 View commit details

Commits on Feb 17, 2024

  1. refactor(jsx): shorten use hook a bit (#2231)

    * refactor(jsx): shorten use hook a bit
    
    * chore: denoify
    usualoma authored Feb 17, 2024
    Copy the full SHA
    7cf7432 View commit details
  2. fix(jsx/dom): fix finding element to insert before (#2230)

    * fix(jsx/dom): fix finding element to insert before
    
    * chore: denoify
    usualoma authored Feb 17, 2024
    Copy the full SHA
    e355089 View commit details
  3. feat(jsx): support HtmlEscapedString in html tag function (#2233)

    * feat(jsx): support HtmlEscapedString in html tag function
    
    * chore: denoify
    usualoma authored Feb 17, 2024
    Copy the full SHA
    86d1221 View commit details
  4. Copy the full SHA
    e2e2c14 View commit details
  5. fix(jwt): import cookie helper correctly (#2238)

    * fix(jwt): import cookie helper correctly
    
    * denoify
    yusukebe authored Feb 17, 2024
    Copy the full SHA
    1dd07d0 View commit details
  6. fix(ssg): path of already extention (#2236)

    * fix(ssg): path of already extention
    
    * adding test
    watany-dev authored Feb 17, 2024
    Copy the full SHA
    d67babf View commit details
  7. fix(validator): use the cached content (#2234)

    * fix(validator): use the cached content
    
    * denoify
    yusukebe authored Feb 17, 2024
    Copy the full SHA
    e2f1d14 View commit details
  8. v4.0.4

    yusukebe committed Feb 17, 2024
    Copy the full SHA
    f87373e View commit details
19 changes: 11 additions & 8 deletions deno_dist/helper/html/index.ts
Original file line number Diff line number Diff line change
@@ -19,17 +19,20 @@ export const html = (
const child = children[i] as any
if (typeof child === 'string') {
escapeToBuffer(child, buffer)
} else if (typeof child === 'number') {
;(buffer[0] as string) += child
} else if (typeof child === 'boolean' || child === null || child === undefined) {
continue
} else if (
(typeof child === 'object' && (child as HtmlEscaped).isEscaped) ||
typeof child === 'number'
) {
const tmp = child.toString()
if (tmp instanceof Promise) {
buffer.unshift('', tmp)
} else if (typeof child === 'object' && (child as HtmlEscaped).isEscaped) {
if ((child as HtmlEscapedString).callbacks) {
buffer.unshift('', child)
} else {
buffer[0] += tmp
const tmp = child.toString()
if (tmp instanceof Promise) {
buffer.unshift('', tmp)
} else {
buffer[0] += tmp
}
}
} else if (child instanceof Promise) {
buffer.unshift('', child)
5 changes: 5 additions & 0 deletions deno_dist/helper/ssg/index.ts
Original file line number Diff line number Diff line change
@@ -31,6 +31,11 @@ export interface ToSSGResult {
}

const generateFilePath = (routePath: string, outDir: string, mimeType: string) => {
const hasExtension = /\.[^\/]+$/.test(routePath)
if (hasExtension) {
return joinPaths(outDir, routePath)
}

const extension = determineExtension(mimeType)
if (routePath === '/') {
return joinPaths(outDir, `index.${extension}`)
32 changes: 22 additions & 10 deletions deno_dist/jsx/dom/render.ts
Original file line number Diff line number Diff line change
@@ -240,23 +240,35 @@ const applyNode = (node: Node, container: Container) => {
}
}

const findChildNodeIndex = (
childNodes: NodeListOf<ChildNode>,
child: ChildNode | null | undefined
): number | undefined => {
if (!child) {
return
}

for (let i = 0, len = childNodes.length; i < len; i++) {
if (childNodes[i] === child) {
return i
}
}

return
}

const applyNodeObject = (node: NodeObject, container: Container) => {
const next: Node[] = []
const remove: Node[] = []
const callbacks: EffectData[] = []
getNextChildren(node, container, next, remove, callbacks)
let offset = container.childNodes.length
const insertBefore = findInsertBefore(node.nN) || next.find((n) => n.e)?.e
if (insertBefore) {
for (let i = 0; i < offset; i++) {
if (container.childNodes[i] === insertBefore) {
offset = i
break
}
}
}

const childNodes = container.childNodes
let offset =
findChildNodeIndex(childNodes, findInsertBefore(node.nN)) ??
findChildNodeIndex(childNodes, next.find((n) => n.e)?.e) ??
childNodes.length

for (let i = 0, len = next.length; i < len; i++, offset++) {
const child = next[i]

18 changes: 10 additions & 8 deletions deno_dist/jsx/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -274,9 +274,10 @@ export const use = <T>(promise: Promise<T>): T => {
}
return cachedRes[0] as T
}
promise
.then((res) => resolvedPromiseValueMap.set(promise, [res]))
.catch((e) => resolvedPromiseValueMap.set(promise, [undefined, e]))
promise.then(
(res) => resolvedPromiseValueMap.set(promise, [res]),
(e) => resolvedPromiseValueMap.set(promise, [undefined, e])
)

const buildData = buildDataStack.at(-1) as [unknown, NodeObject]
if (!buildData) {
@@ -287,13 +288,14 @@ export const use = <T>(promise: Promise<T>): T => {
const promiseArray = (node[DOM_STASH][1][STASH_USE] ||= [])
const hookIndex = node[DOM_STASH][0]++

promise
.then((res) => {
promise.then(
(res) => {
promiseArray[hookIndex] = [res]
})
.catch((e) => {
},
(e) => {
promiseArray[hookIndex] = [undefined, e]
})
}
)

const res = promiseArray[hookIndex]
if (res) {
2 changes: 1 addition & 1 deletion deno_dist/middleware/jwt/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Context } from '../../context.ts'
import { getCookie } from '../../helper.ts'
import { getCookie } from '../../helper/cookie/index.ts'
import { HTTPException } from '../../http-exception.ts'
import type { MiddlewareHandler } from '../../types.ts'
import { Jwt } from '../../utils/jwt/index.ts'
11 changes: 9 additions & 2 deletions deno_dist/validator/validator.ts
Original file line number Diff line number Diff line change
@@ -67,9 +67,12 @@ export const validator = <
const message = `Invalid HTTP header: Content-Type=${contentType}`
throw new HTTPException(400, { message })
}
if (c.req.bodyCache.json) {
value = await c.req.bodyCache.json
break
}
/**
* Get the arrayBuffer first, create JSON object via Response,
* and cache the arrayBuffer in the c.req.bodyCache.
* Get the arrayBuffer, create JSON object via Response, and cache the arrayBuffer in the c.req.bodyCache if there is no cached content in c.req.bodyCache.json.
*/
try {
const arrayBuffer = c.req.bodyCache.arrayBuffer ?? (await c.req.raw.arrayBuffer())
@@ -82,6 +85,10 @@ export const validator = <
}
break
case 'form': {
if (c.req.bodyCache.formData) {
value = c.req.bodyCache.formData
break
}
try {
const contentType = c.req.header('Content-Type')
if (contentType) {
2 changes: 1 addition & 1 deletion docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@ If you want to do it, create the issue about your middleware.
## Local Development

```
git clone git@github.com:honojs/hono.git && cd hono/.devcontainer && yarn install
git clone git@github.com:honojs/hono.git && cd hono/.devcontainer && yarn install --frozen-lockfile
docker compose up -d --build
docker compose exec hono bash
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hono",
"version": "4.0.3",
"version": "4.0.4",
"description": "Ultrafast web framework for the Edges",
"main": "dist/cjs/index.js",
"type": "module",
@@ -499,7 +499,7 @@
"cloudflare",
"workers",
"fastly",
"compute@edge",
"compute",
"deno",
"bun",
"lambda",
22 changes: 22 additions & 0 deletions src/helper/html/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { resolveCallback, HtmlEscapedCallbackPhase } from '../../utils/html'
import { html, raw } from '.'

describe('Tagged Template Literals', () => {
@@ -45,6 +46,27 @@ describe('Tagged Template Literals', () => {
expect((await res).toString()).toBe('<p>I\'m John "Johnny" Smith.</p>')
})
})

describe('HtmlEscapedString', () => {
it('Should preserve callbacks', async () => {
const name = raw('Hono', [
({ buffer }) => {
if (buffer) {
buffer[0] = buffer[0].replace('Hono', 'Hono!')
}
return undefined
},
])
const res = html`<p>I'm ${name}.</p>`
expect(res).toBeInstanceOf(Promise)
// eslint-disable-next-line quotes
expect((await res).toString()).toBe("<p>I'm Hono.</p>")
expect(await resolveCallback(await res, HtmlEscapedCallbackPhase.Stringify, false, {})).toBe(
// eslint-disable-next-line quotes
"<p>I'm Hono!.</p>"
)
})
})
})

describe('raw', () => {
19 changes: 11 additions & 8 deletions src/helper/html/index.ts
Original file line number Diff line number Diff line change
@@ -19,17 +19,20 @@ export const html = (
const child = children[i] as any
if (typeof child === 'string') {
escapeToBuffer(child, buffer)
} else if (typeof child === 'number') {
;(buffer[0] as string) += child
} else if (typeof child === 'boolean' || child === null || child === undefined) {
continue
} else if (
(typeof child === 'object' && (child as HtmlEscaped).isEscaped) ||
typeof child === 'number'
) {
const tmp = child.toString()
if (tmp instanceof Promise) {
buffer.unshift('', tmp)
} else if (typeof child === 'object' && (child as HtmlEscaped).isEscaped) {
if ((child as HtmlEscapedString).callbacks) {
buffer.unshift('', child)
} else {
buffer[0] += tmp
const tmp = child.toString()
if (tmp instanceof Promise) {
buffer.unshift('', tmp)
} else {
buffer[0] += tmp
}
}
} else if (child instanceof Promise) {
buffer.unshift('', child)
13 changes: 13 additions & 0 deletions src/helper/ssg/index.test.tsx
Original file line number Diff line number Diff line change
@@ -295,6 +295,10 @@ describe('saveContentToFiles function', () => {
['/', { content: 'Home Page', mimeType: 'text/html' }],
['/about', { content: 'About Page', mimeType: 'text/html' }],
['/about/', { content: 'About Page', mimeType: 'text/html' }],
['/bravo/index.html', { content: 'About Page', mimeType: 'text/html' }],
['/bravo/index.tar.gz', { content: 'About Page', mimeType: 'application/gzip' }],
['/bravo.text/index.html', { content: 'About Page', mimeType: 'text/html' }],
['/bravo.text/', { content: 'Bravo Page', mimeType: 'text/html' }],
])
})

@@ -304,6 +308,10 @@ describe('saveContentToFiles function', () => {
expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', 'Home Page')
expect(fsMock.writeFile).toHaveBeenCalledWith('static/about.html', 'About Page')
expect(fsMock.writeFile).toHaveBeenCalledWith('static/about/index.html', 'About Page')
expect(fsMock.writeFile).toHaveBeenCalledWith('static/bravo/index.html', 'About Page')
expect(fsMock.writeFile).toHaveBeenCalledWith('static/bravo/index.tar.gz', 'About Page')
expect(fsMock.writeFile).toHaveBeenCalledWith('static/bravo.text/index.html', 'About Page')
expect(fsMock.writeFile).toHaveBeenCalledWith('static/bravo.text/index.html', 'Bravo Page')
})

it('should correctly create directories if they do not exist', async () => {
@@ -322,6 +330,11 @@ describe('saveContentToFiles function', () => {
'File write error'
)
})
it('check extensions', async () => {
await saveContentToFiles(htmlMap, fsMock, './static')

expect(fsMock.mkdir).toHaveBeenCalledWith('static', { recursive: true })
})
})

describe('Dynamic route handling', () => {
5 changes: 5 additions & 0 deletions src/helper/ssg/index.ts
Original file line number Diff line number Diff line change
@@ -31,6 +31,11 @@ export interface ToSSGResult {
}

const generateFilePath = (routePath: string, outDir: string, mimeType: string) => {
const hasExtension = /\.[^\/]+$/.test(routePath)
if (hasExtension) {
return joinPaths(outDir, routePath)
}

const extension = determineExtension(mimeType)
if (routePath === '/') {
return joinPaths(outDir, `index.${extension}`)
38 changes: 29 additions & 9 deletions src/jsx/dom/index.test.tsx
Original file line number Diff line number Diff line change
@@ -6,14 +6,9 @@ import type { FC } from '..'
import { jsx, Fragment } from '..'
import type { RefObject } from '../hooks'
import { useState, useEffect, useLayoutEffect, useCallback, useRef, useMemo } from '../hooks'
import type { NodeObject } from './render'
import { memo, isValidElement, cloneElement } from '.'
import { render, cloneElement as cloneElementForDom } from '.'

const getContainer = (element: JSX.Element): DocumentFragment | HTMLElement | undefined => {
return (element as unknown as NodeObject).c
}

describe('DOM', () => {
beforeAll(() => {
global.requestAnimationFrame = (cb) => setTimeout(cb)
@@ -154,10 +149,9 @@ describe('DOM', () => {
}
const app = <App />
render(app, root)
const container = getContainer(app) as HTMLElement
expect(root.innerHTML).toBe('<div>0</div>')

const insertBeforeSpy = vi.spyOn(container, 'insertBefore')
const insertBeforeSpy = vi.spyOn(dom.window.Node.prototype, 'insertBefore')
setCount(1)
await Promise.resolve()
expect(root.innerHTML).toBe('<div>1</div>')
@@ -215,15 +209,41 @@ describe('DOM', () => {
</>
)
render(app, root)
const container = getContainer(app) as HTMLElement
expect(root.innerHTML).toBe('<div>0</div><div>Footer</div>')

const insertBeforeSpy = vi.spyOn(container, 'insertBefore')
const insertBeforeSpy = vi.spyOn(dom.window.Node.prototype, 'insertBefore')
setCount(1)
await Promise.resolve()
expect(root.innerHTML).toBe('<div>1</div><div>Footer</div>')
expect(insertBeforeSpy).not.toHaveBeenCalled()
})

it('should not call insertBefore for unchanged complex dom tree', async () => {
let setCount: (count: number) => void = () => {}
const App = () => {
const [count, _setCount] = useState(0)
setCount = _setCount
return (
<form>
<div>
<label>label</label>
<input />
</div>
<p>{count}</p>
</form>
)
}
const app = <App />

render(app, root)
expect(root.innerHTML).toBe('<form><div><label>label</label><input></div><p>0</p></form>')

const insertBeforeSpy = vi.spyOn(dom.window.Node.prototype, 'insertBefore')
setCount(1)
await Promise.resolve()
expect(root.innerHTML).toBe('<form><div><label>label</label><input></div><p>1</p></form>')
expect(insertBeforeSpy).not.toHaveBeenCalled()
})
})

describe('Event', () => {
Loading