Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v5: hono/build #3659

Open
EdamAme-x opened this issue Nov 12, 2024 · 23 comments
Open

v5: hono/build #3659

EdamAme-x opened this issue Nov 12, 2024 · 23 comments
Labels
enhancement New feature or request. v5

Comments

@EdamAme-x
Copy link
Contributor

EdamAme-x commented Nov 12, 2024

I'm creating a new router named "PreparedRouter" using AOT.
This issue is a thread to solicit opinions on concerns.

@yusukebe
Copy link
Member

yusukebe commented Nov 12, 2024

Quick response.

Rather than AOT, it would be nice to have a build script or bundler that writes out an optimal application "file" that includes the hono. Then, it will work in Cloudflare Workers and other environments where eval and Function are not available.

@yusukebe
Copy link
Member

yusukebe commented Nov 12, 2024

I forgot, but we discussed this issue - creating a build script or bundler - in this project Issue or PR with @usualoma.

@EdamAme-x
Copy link
Contributor Author

EdamAme-x commented Nov 12, 2024

I have seen PreparedRegExpRouter in the past.
I've seen the whole discussion, but it seems difficult to interfere with the build.

And you as said, it currently depends on Function, so it won't work with Cloudflare workers, etc.
However, I think it will help speed things up in VPS and other environments.

@MathurAditya724
Copy link
Contributor

I’ve also been considering a feature like this for Hono, and I agree with @yusukebe on incorporating a build script. I recall we previously discussed this topic while comparing the speeds of Hono and Elysia with AOT compilation.

Rather than embedding this directly into the router, integrating it into something like Honox might be more effective. The code/logic is already modularized with nested routes, allowing us to implement a script that merges these routes into a single, optimized Hono app. This approach could enhance performance across different runtimes.

The key question now is which specific optimizations we could include in the build script to maximize efficiency. Thoughts?

@EdamAme-x
Copy link
Contributor Author

EdamAme-x commented Nov 12, 2024

Even if we choice to build, we need the logic of code generation for optimization, so we will work on PreparedRouter.
Also, the following may be of use.

new PreparedRouter({
  preparedMatch: () => {
    ...
  } // precompiled
})

It should be able to be utilized when build.

@ryuapp
Copy link
Contributor

ryuapp commented Nov 12, 2024

I don't know if that's possible, but if we can make good use of Parcel's macros, the prepared router will seem to make sense.
https://parceljs.org/features/macros/

The macros also work with Vite etc.
https://github.com/devongovett/unplugin-parcel-macros

@yusukebe
Copy link
Member

But, IMO, we don't have to implement the PreparedRouter or build a script or bundler (just for a "router") because our routers are already fast enough. If we have a faster router, it may not affect us in the real world.

@usualoma
Copy link
Member

I'm also interested in this area, and it's worth considering if there is a good approach. I'm also interested in macros.

The reasons I considered this with "honox" before are as follows.

  • Going through the build process
  • Routing being determined statically

It isn't easy to prepare for apps that don't use static routing (use variables). I think that apps that use variables are in the minority, but I think it's inevitable that there will be conditions that determine whether or not they can be applied.

There are currently hono, hono/quick, and hono/tiny, and they meet most requirements. So, I think the difficult part is whether or not we can prepare something that makes sense to add to them.

@yusukebe
Copy link
Member

What do you think about anything other than routing?

For example, define a schema for the incoming JSON. After building the app, an optimized JSON parser is written in the build script. The parsing is then much faster than normally using JSON.parse.

@MathurAditya724
Copy link
Contributor

some initial ideas -

  • Combine all chained validators into a single, unified validator during the build process.
  • Implement a handler that pre-generates responses at build time for faster delivery, taking a similar approach to static site generation.
  • Move toward compiling all components at build time for improved efficiency.

@yusukebe
Copy link
Member

Haha, topics are getting bigger! But I have the most fun when we talk about these things.

@nakasyou
Copy link
Contributor

For example, define a schema for the incoming JSON. After building the app, an optimized JSON parser is written in the build script. The parsing is then much faster than normally using JSON.parse.

It is why elysia is fast. I want Hono to implement it.

And my personal opinion, build-time building is better than run-time building. Outputting code can reduce build size.

@yusukebe
Copy link
Member

It is why elysia is fast.

Yes, exactly (though I don't want to fight with Elysia).

@EdamAme-x
Copy link
Contributor Author

EdamAme-x commented Nov 15, 2024

hono/build is interesting for me.
It has the potential to become a hot topic for v5.

@nakasyou
Copy link
Contributor

In the future it may depend external package such as TypeScript compiler API and babel. So I think it should be in hono/middleware or other repo, and create @hono/build.

@yusukebe
Copy link
Member

In the future it may depend external package such as TypeScript compiler API and babel. So I think it should be in hono/middleware or other repo, and create @hono/build.

+1

@EdamAme-x EdamAme-x changed the title feat(router): Introduce PreparedRouter hono/build Nov 18, 2024
@EdamAme-x
Copy link
Contributor Author

Most of the work has been completed. I will only write a brief description when school is over.

@EdamAme-x EdamAme-x changed the title hono/build v5: @hono/build Dec 17, 2024
@EdamAme-x EdamAme-x changed the title v5: @hono/build v5: hono/build Dec 17, 2024
@EdamAme-x
Copy link
Contributor Author

EdamAme-x commented Dec 17, 2024

Precompiled Prepared Router (Example)

new function(){let t=function t(e,n,r,u,l,[i]){let o=l[n];if(o){let a=o[e]||o.ALL;if(a)return[a]}let f=u[n],p=f&&(f[e]||f.ALL)||[];Object.create(null);let c=n.split("/");if("GET"===e&&"bar"===c[1]&&3===c.length&&!0==!!c[2]){let h=new r;h.id=c[2],p.push([i,h,1])}return p.length>1&&p.sort((t,e)=>t[2]-e[2]),[p.map(([t,e])=>[t,e])]},e=Object.create(null),n=(()=>{let t=function(){};return t.prototype=e,t})(),r={"/baz/baz":{ALL:[]}},u={"/foo":{GET:[]}},l=[];return{name:"PreparedRouter",add:function(t,n,i){t in(r[n]||e)?r[n][t].push([i,e]):t in(u[n]||e)?u[n][t].push([i,e]):l.push(i)},match:function(e,i){return t(e,i,n,r,u,l)}}};

image
(this is old)

router.add('GET', '/foo', 'foo')
router.add('GET', '/bar/:id', 'bar + :id')
router.add('ALL', '/baz/baz', 'baz + baz')
  • Generated Matcher
function anonymous(method, path, createParams, staticHandlers, preparedHandlers, {
        handler1,
        handler2,
        handler3,
        handler4,
        handler5,
        handler6,
        handler7
    }) {

        const preparedMethods = preparedHandlers[path];
        const preparedResult = preparedMethods?.[method]

        if (preparedResult) {
            return preparedResult;
        }

        const matchResult = [];

        const emptyParams = new createParams();
        const pathParts = path.split('/');

        if (method === 'GET') {
            if (!!pathParts[2] === true) {

                if (pathParts[1] === 'event') {

                    if (pathParts.length === 3) {

                        const params = new createParams();
                        params.id = pathParts[2];

                        matchResult.push({
                            handler: handler3,
                            params: params,
                            order: 5
                        })

                    } else if (pathParts.length === 4) {

                        if (pathParts[3] === 'comments') {

                            const params = new createParams();
                            params.id = pathParts[2];

                            matchResult.push({
                                handler: handler4,
                                params: params,
                                order: 6
                            })

                        }

                    }

                } else if (pathParts[1] === 'map') {

                    if (pathParts.length === 4) {

                        if (pathParts[3] === 'events') {

                            const params = new createParams();
                            params.location = pathParts[2];

                            matchResult.push({
                                handler: handler6,
                                params: params,
                                order: 8
                            })

                        }

                    }

                }

            }

            if (pathParts[1] === 'user') {

                if (pathParts[2] === 'lookup') {

                    if (pathParts.length === 5) {

                        if (!!pathParts[4] === true) {

                            if (pathParts[3] === 'username') {

                                const params = new createParams();
                                params.username = pathParts[4];

                                matchResult.push({
                                    handler: handler1,
                                    params: params,
                                    order: 3
                                })

                            } else if (pathParts[3] === 'email') {

                                const params = new createParams();
                                params.address = pathParts[4];

                                matchResult.push({
                                    handler: handler2,
                                    params: params,
                                    order: 4
                                })

                            }

                        }

                    }

                }

            } else if (pathParts[1] === 'static') {

                if (pathParts.length >= 2) {

                    matchResult.push({
                        handler: handler7,
                        params: emptyParams,
                        order: 11
                    })

                }

            }

        } else if (method === 'POST') {
            if (pathParts[1] === 'event') {

                if (!!pathParts[2] === true) {

                    if (pathParts.length === 4) {

                        if (pathParts[3] === 'comment') {

                            const params = new createParams();
                            params.id = pathParts[2];

                            matchResult.push({
                                handler: handler5,
                                params: params,
                                order: 7
                            })

                        }

                    }

                }

            }

        }

        if (matchResult.length > 1) {
            matchResult.sort((a, b) => a.order - b.order);
        }

        return [matchResult.map(({
            handler,
            params
        }) => [handler, params])];
    };

Prepared Router, like Trie Router, supports all path formats
and the speed depends on the format of the application path, it is a little inferior to RegExp Router, but only by a factor of 1.1 ~ 1.3 and very fast.. (not precompiled and measured in benchmarks/routers and this may be improved in the future.)

Initialization is as fast as Linear Router when precompiled, and the post-compiled code is as minimal as PatternRouter.

And it generates matchers as smartly as SmartRouter ( Really!! ).

By introducing the concept of precompilation, Prepared Router can be used in environments where AOT is not available, such as Cloudflare Workers.

I will make the slides about this later.

summary for all together
  Hono RegExpRouter
   1.21x faster than Memoirist
   1.29x faster than Hono PreparedRouter
   1.41x faster than koa-tree-router
   1.57x faster than @medley/router
   1.72x faster than rou3
   1.87x faster than radix3
   2.39x faster than trek-router
   3.04x faster than find-my-way
   4.04x faster than Hono PatternRouter
   5.11x faster than koa-router
   12.22x faster than Hono TrieRouter
   16.09x faster than express (WARNING: includes handling)
(Deno v2)
// 1
summary for all together
  Memoirist
   1.09x faster than Hono RegExpRouter
   1.23x faster than Hono PreparedRouter
   1.26x faster than @medley/router
   2.13x faster than radix3
   2.28x faster than rou3
   2.9x faster than find-my-way
   3.01x faster than koa-tree-router
   4.06x faster than Hono PatternRouter
   4.7x faster than trek-router
   5.28x faster than koa-router
   10.92x faster than Hono TrieRouter
   14.45x faster than express (WARNING: includes handling)
// 2
summary for all together
  Memoirist
   1.14x faster than Hono RegExpRouter
   1.3x faster than @medley/router
   1.4x faster than Hono PreparedRouter
   2.11x faster than radix3
   2.35x faster than rou3
   2.45x faster than koa-tree-router
   2.95x faster than find-my-way
   4.06x faster than trek-router
   4.22x faster than Hono PatternRouter
   5.55x faster than koa-router
   10.22x faster than Hono TrieRouter
   12.91x faster than express (WARNING: includes handling)
// 3
summary for all together
  Memoirist
   1.07x faster than Hono PreparedRouter
   1.11x faster than Hono RegExpRouter
   1.33x faster than @medley/router
   2.08x faster than radix3
   2.36x faster than rou3
   2.85x faster than koa-tree-router
   2.91x faster than find-my-way
   3.96x faster than Hono PatternRouter
   4.35x faster than trek-router
   5.25x faster than koa-router
   11.09x faster than Hono TrieRouter
   12.83x faster than express (WARNING: includes handling)
(Bun)
// includes precompiled
summary for all together
  Memoirist
   1.02x faster than Hono PreparedRouter (precompiled)
   1.08x faster than Hono RegExpRouter
   1.24x faster than @medley/router
   1.57x faster than Hono PreparedRouter
   2.03x faster than radix3
   2.24x faster than rou3
   2.93x faster than find-my-way
   2.95x faster than koa-tree-router
   4.03x faster than trek-router
   4.32x faster than Hono PatternRouter
   5.13x faster than koa-router
   11.8x faster than Hono TrieRouter
   12.49x faster than express (WARNING: includes handling)
(Bun)

When precompile is performed, it speeds up the process, perhaps due to optimization.

@EdamAme-x
Copy link
Contributor Author

EdamAme-x commented Dec 18, 2024

Since then, several optimizations have been made,
Benchmark results often show that the speed of a precompiled Prepared Router exceeds that of a RegExp Router.

@EdamAme-x
Copy link
Contributor Author

EdamAme-x commented Dec 19, 2024

Prepared Router may be the fastest.

summary for all together
  Hono PreparedRouter (precompiled)
   1.05x faster than Memoirist
   1.09x faster than Hono PreparedRouter
   1.12x faster than Hono RegExpRouter
   1.37x faster than @medley/router
   2.05x faster than radix3
   2.09x faster than rou3
   2.34x faster than koa-tree-router
   3.24x faster than find-my-way
   4.34x faster than Hono PatternRouter
   4.56x faster than trek-router
   5.43x faster than koa-router
   10.23x faster than Hono TrieRouter
   10.7x faster than express (WARNING: includes handling)
   24.23x faster than Hono LinearRouter

Fastest in Bun.
Node and Deno marked about 1.5x.

@EdamAme-x
Copy link
Contributor Author

EdamAme-x commented Dec 19, 2024

I think we've gone fast spped enough, so we can move on to the next step.
This is what I am assuming.

const app = new Hono({
  router: new PreparedRouter()
})

after build

const app = new Hono({
  router: new (function() {...})()
})

I think it is fast enough without building on runtimes other than edge.
Cloudflare Workers and others don't allow I/O operations at the top level, but that's preferable.
We can do a dry run at build time to collect route information and precompile.

@EdamAme-x
Copy link
Contributor Author

I would like to hear your opinions.

@EdamAme-x
Copy link
Contributor Author

@yusukebe yusukebe added enhancement New feature or request. v5 labels Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request. v5
Projects
None yet
Development

No branches or pull requests

6 participants