-
Notifications
You must be signed in to change notification settings - Fork 647
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
The road to Fresh 2.0 #2363
Comments
If |
Are there any plans to use vite? Would be nice to fix #978 before 2.0 as well. |
@CAYdenberg The head API is the least fleshed out from the points. It should definitely be possible to set any tag you want. @fazil47 No plans to switch to vite at the moment. Good point, #978 will be resolved before 2.0 . |
Are there any plans/ideas to support functions as props in islands, even if with some limitations? I personally find that the hardest thing to live with, using Fresh. It limits code re-usability considerably. For example, it's not uncommon to build something like:
I understand why it's done (because of the SSR), but there should be some way around it, even if it's disabling SSR for those components automatically, I believe, and it would make it a lot more easy to migrate existing code and patterns. |
No, that doesn't fit into the 2.0 release schedule. Serializing functions would require a complex machinery of tracking all accessed variables which is very error prone. I don't have an idea of how to solve that at the moment, so this requires further research before we can think of tackling it. |
My suggestion would be to not serialize that component, just let it be completely rendered in the client, as I don't think it would be possible to serialize any function reliably. |
If I'm understanding @BrunoBernardino 's point, the limitation he's experiencing is around code reusability. In other words, the ability to use a component as an island or not-an-island, depending on the context. In other words, for components that accept a function as an optional prop, it would be nice if there were a way to declare them as an island (or not-an-island) other than what folder they are in. |
@CAYdenberg that's exactly it! I'd like to create "dumb" components in |
Well, if it helps, components that are children of islands can receive functions as props. So, your |
@CAYdenberg I need to try that, as that should solve my problem! I'll report back once I've had the chance to do so. |
Nice points @BrunoBernardino @CAYdenberg ... also, I'd like to bring up proper support for HMR to the table, would that fit into 2.0? |
Proper HMR support in Fresh requires some changes in Deno CLI. It won't land as part of Fresh 2.0 but rather in the following release 2.1. That's the current plan. |
@CAYdenberg that totally solved my problem, thank you so much! |
Regarding the middleware, I have done an innovative step when I invented middleware trees for my framework. It's a much smoother thing than what express did, as it lets you combine middleware (and thus, plugins) in a much more flexible way. If you feel like reading more about it, https://grammy.dev/advanced/middleware is the place to go. If, however, you don't want to take inspiration from that, please ignore me. Keep up the great work! |
Exciting stuff! Any ideas on projected release timeline yet? |
@KnorpelSenf thanks for sharing, that was interesting read! In our case we take routing into account which allows us to effectively flatten the tree into a flat list of middlewares + routes. This saves us a bit of book keeping and seems to work very well so far. @lionel-rowe I'm making good progress at the moment and the hope for it is to have it out in a couple of weeks, unless I run into unforeseen challenges. I definitely want to get this into user's hands as soon as possible. |
@marvinhagemeister there's no book-keeping required for such an implementation. It's surprisingly trivial to do this, i.e. 10-20 lines of code (depending on how you count). We did it like this: https://github.com/grammyjs/grammY/blob/38efc3c7729eef7133446451bdd7056ad4b09004/src/composer.ts#L196 What I'm rather concerned about is that a nested middleware system prevents some performance optimizations. While you could technically implement fast routing per node, it isn't really possible to directly jump to the right handler deep down in the middleware tree. In my case, the functionality is worth that, but HTTP routing can be optimised a lot better, so the opportunity cost is different for fresh. You might not want to go with middleware trees because of this. I'll leave it up to you :) |
@KnorpelSenf The implementation you have there is quite elegant! I like it! Agree about the concerns of nested middlewares potentially preventing performance optimizations. The way it currently works is that internally middlewares and routes are stored like this: [
* [...middlewares]
GET / [...routeMiddlewares]
GET /foo/bar [...routeMiddlewares]
POST /api/foobar [...routeMiddlewares]
] This means that an incoming request needs to do two steps:
Apart from the route matching, this system conceptually just walks over a flat array until one of the middlewares returns a response. |
Great work and exciting times ahead. Are there any plans on supporting css modules, css in js and such with 2.0? |
@aakashsigdel CSS modules require transpilation and I've been getting pushback when proposing to transpile the server code in Fresh or landing CSS modules right in Deno itself. So no progress on that front. When it comes to CSS-in-JS something like styled-components will never be a got fit with Fresh as it turns every component into a client component effectively. The good news though is that with 2.0 we're in a much better position to potentially add all those transpilation stuff, even for server code. |
Would it be possible to extract template rendering/islands into a plugin? It'd be awesome if you could mix/match frameworks similar to Astro, but with the ergonomics of Fresh |
@Twitch0125 The new architecture certainly makes this easier as rendering isn't spread out throughout the entire code base like it is in Fresh 1.x . That said there are no plans to support multiple frameworks at the moment. The worry is that supporting more than one framework requires more maintenance effort, likely more than we have the bandwidth for at the moment. |
Fresh 2.0 promises to bring great improvements. When do you think it will be released? |
@FabianMendoza7 soon™ |
Without having read the Fresh source, yet. Would be great if you could somehow make this loosely connected to Fresh, as it could be really useful for developing SPAs with Preact, Deno and Esbuild. |
Agree, I've been pushing for solutions that are not specific to Fresh for that internally. Something that any developer using Deno would profit off, not just Fresh. |
Awesome! when can i use the 2.0 version? |
I think it is useful to specify islands freely so that we can place the islands component close to the route components where they are used, but alpha.16's |
@Xuhv That's already supported in both Fresh 1.x and 2.x . Any folder inside the
|
I'd be happy to give a hand with the doc of Fresh 2; is there a thread to track or anything that help me get into it ? |
I would also like to help with docs and beta testing for 2.0 if the team is willing. 🔬 |
I am trying out @fresh/core@^2.0.0-alpha.18. |
For comparison with a few other frameworks I could think of:
Personally, with such core and well recognized concepts that are a in frequent use, and with everyone's editor showing |
Thank you for the listing. I guess that shows though, that there is not a complete consensus about the usage of
Imo we should not design apis based on what would likely be best formatted but what is the most consistent with other apis. I think modern tools (autocompletion) and the js language (destructuring e.g. |
I advocate for |
Fresh itself always used export async function handler(
req: Request,
ctx: FreshContext<State>,
) {
ctx.state.data = "myData";
const resp = await ctx.next();
resp.headers.set("server", "fresh server");
return resp;
} The code snippets on deno.com use that too: And on top of that this has been the convention in |
Yes but variable names can be any name, it is not the same as a property name of an object.
I would argue that established web api names should be weighted more than framework conventions. FetchEvent also uses |
I do think it's a bit weird for a modern framework to use Previously, it was just a function argument and had an explicit type that fully spells it out, avoiding the ambiguity. (req: Request) => {…} Whereas now, it's an actual property name and it may be destructured from the context object. Using ({ req }: FreshContext) => {…} Personally, I also think reduces readability because "req" is not a word. It would also not be announced correctly for developers who rely on screen-readers. If we're looking at other frameworks, Astro, SvelteKit and Remix all use |
There will never be consensus. Some teams avoid abbreviations, some don't. It also seems fairly even in a rough code search 🙂
I agree to a degree. I would not argument for making abbreviations to any property just to make it format better. In this case it's something that will already be encountered in the Fresh docs; It's not keystrokes I want to save, but being able to see and scan more code without "noise". Longer property/parameter names just makes it more likely one line can become 2-4 just to format nicely. It's often the stuff after that is more relevant too, e.g.
Even if parameters can be named anything, I think it's still relevant as it shows a shared convention in the ecosystem, both for library and application code.
I don't think what the platform does factors in heavily here; the example is just camelCased Deno and this project seems to lean into abbreviations as long as they easily deduced; |
My two cents are: in Fresh 1.x, since export const handler = (req: Request, ctx: FreshContext<State>) => {
console.log(req);
} in Fresh 2.x, since its no longer an argument, but a property of export const handler = (ctx: FreshContext<State>) => {
console.log(ctx.request);
} Not wishing to delay the 2.0 release, but if this change gonna happen, then I figured this is the right time for it to happen (still in time?). |
List addition: Astro uses Astro.request and Svelte uses |
@timreichen Yes, I mentioned Astro, Svelte and Remix in my comment above. |
Apart from the req vs request drama, any updates on when will Fresh 2.0 will be released? Will the incoming release of Deno 2.0 be timed with the release of Fresh 2.0 aswell, or it has nothing to do with it and will be released separetly? Are there any specified dates? |
no specific date, Marvin is currently focusing on the release of deno 2.0, but fresh 2.0 has released alpha version in jsr and i think it can be used for production |
Will Fresh v2 Plugin interface drops the ability to add Routes component ( aka PluginRoute) ? I can see in the drafted API const plugin: PluginMiddleware = {
routes: [
{ path: "no-leading-slash-here", component: SimpleRoute },
], In the current main/canary (v2 alpha) I see Based on the new plugin interface, it seems this change is intentional, and I understand the goal of simplifying the API. I just want to ensure I fully grasp the direction here. From what I gather, this approach might discourage plugins that enable certain routes meant to integrate directly within the app's layout. For example, a blog plugin configured to appear at |
Now that Deno 2 has full Node compat, is there any chance to be able to use anything but Preact using Vite? |
@nrako I have not tested this code, but I think that a plugin might be called with // @example/my-fresh-plugin/mod.ts
import { fsRoutes } from 'fresh';
export async function myFreshPlugin(app: App) {
await fsRoutes(app, {
dir: import.meta.dirname,
loadRoute: (path) => import(`./routes/${path}`)
});
} // main.ts
import { App } from 'fresh';
import { myFreshPlugin } from 'jsr:@example/my-fresh-plugin';
const app = new App();
await myFreshPlugin (app); The It's also possible that a plugin can return a import { App } from 'fresh';
import { appFreshPlugin } from 'jsr:@example/app-fresh-plugin';
const app = new App();
app.mountApp('/plugin-path', await appFreshPlugin()); Without docs, I'm guessing a bit. But to me, the base primitives we get from |
I think I'm coming around to adding some sort of layout API everywhere instead of moving that off to its own plugin. There are lots of good use cases where you want a plugin route to integrate into an existing layout which can be done right now, but is a bit low-level. Originally, I had pictured a world where layouts would be just plain components, so you'd do: app.get("/foo", ctx => {
return ctx.render((
<App>
<Layout>
<h1>this is a heading inside a layout</h1>
</Layout>
</App>
);
}); Whilst components are composable on its own, it gets complicated when you're trying to compose routes from plugins. That's where the current API falls a bit short. Having something like |
@marvinhagemeister, @csvn, thanks for sharing your insights!
Exactly, this is indeed possible in v1. I understand and can anticipate the complexity that arises when trying to compose routes using plugins. The simplicity of a Hono & express style API for the v2 Plugin API approach, has a lot of merits. It could indeed be the right direction, even if it involves a breaking change—it might prove better for the long-term. That’s the key consideration I was trying to understand. But indeed, the ability to add routes at any path through a plugin while inheriting layouts from the app's file system (
However, I'm not totally getting how To clarify my thoughts, let’s imagine an application with the following file system structure and plugin usage:
// main.ts
freshBlogPlugin(app, { path: '/team/blog' }) For the plugin API I could imagine something like that: app.get(
options.path, // "/team/blog",
ctx => {
return ctx.render(
<p>this is inside the app wrapper and the layouts, thus '_app.tsx' and 'team/_layout.tsx' </p>,
{
skipAppWrapper: false,
skipInheritedLayouts: false,
}
);
}
); |
Yup, something like that would be great. It would require the |
That might be true. However, since this feature doesn’t seem to be available in v1, I’m not entirely sure about its necessity. I can’t think of a scenario where, as a Fresh user, I would need a plugin to inject a layout at a specific path. |
Will async components block the response stream or render tree? I presumed an async component would insert a placeholder or something in the html that, on settling of the component render, would transfer that html back into the placeholder on hydration, browser side. |
The road to Fresh 2.0
tl;dr: Fresh will become massively simpler and be distributed via JSR with the upcoming 2.0 release. Express/Hono-like API and true async components and much more.
Back to Fresh!
Since yesterday, I've been going back to working on Fresh again. During the past few months I helped with shipping https://jsr.io/ which meant that Fresh sat on the back burner for me. Admittedly, I needed a bit a break from Fresh as well, and JSR came along at the right time.
Working on JSR allowed me to put myself into the user's seat and get first hands experiences with Fresh as a user, which was nice change! It also uncovered a lot of things where I feel we can make Fresh much better.
With JSR beeing out of the door, the next task for me is to move Fresh over from
deno.land/x
to JSR. Given that this is a bit of a breaking change, @lucacasonato and I were wondering what a potential Fresh 2.0 release could look like.We've never done a breaking Fresh release before, but now, after having worked for nearly a year on Fresh, the timing feels right. It allows us to revisit all the ideas in Fresh and part ways with those that didn't pan out.
We spent yesterday morning going through the most common problems people shared on Discord and in our issue tracker to get a good picture of where Fresh is at. The overall theme we want to have for Fresh 2, is to be massively simpler than before.
A new plugin API
It became pretty clear that many issues relate to the "clunkyness" of the current plugin API. It's the most common way to extend Fresh itself with capabilities that it doesn't include out of the box. Whilst it received many cool new features over time, it quite never felt elegant as it should be. This is no surprise given that nothing in Fresh itself makes use of it.
Back in July of last year there was some explorations on making Fresh's API more express/Hono-like #1487 and yesterday we realized that this is the perfect solution for nearly all the problems encountered with the current plugin API. Here is what we're picturing it to look like:
A Fresh plugin would change from the complex object it is today, to being nothing more than a standard JavaScript function:
The beauty about this kind of API is that many features in Fresh are a just standard middleware, and the more complex ones like our file system router and middleware handler would just call methods on
app
. The internals of Fresh will be implemented the exact same way as any other plugin or middleware. This eliminates the mismatch of the current plugin API and the Fresh internals of today.Simpler middleware signature
The current middleware signature in Fresh has two function arguments:
In most of our code we noticed that we rarely need to access the
Request
object. So most of our middlewares just skip over it:It's a minor thing, but it's a bit annoying that you always have to sorta step over the
req
argument. With Fresh 2.0 we're planning to move it onto the context object.Same arguments for sync and async routes
Orignally, I've modelled async routes after the middleware signature. That's why they require two arguments:
With the benefit of hindsight, I consider this a mistake. What naturally happens is that folks tend to start out with a synchronous route and add the
async
keyword to make it asynchronoues. But this doesn't work in Fresh 1.x.So yeah, we'll correct that. In Fresh 2.0 this will work as expected.
True async server-side components
In truth, the async route components in Fresh 1.x are a bit of a beautiful lie. Internally, they are not components, but a plain function that happens to return JSX. We take the returned JSX and pass it to Preact to render and that's all
there is to it. The downside of that approach is that hooks don't work inside the async funciton, because it's not executed in a component context. In fact async routes in Fresh 1.x are called way before the actual rendering starts.
You might be wondering why we went with this approach and the main reason was that Preact didn't support rendering async components at that time. This was the quickest way to get something that gives you most of the benefits of async components with some tradeoffs into Fresh.
The good news is that Preact recently added support for rendering async components on the server itself. This means we can finally drop our workaround and support rendering async components natively.
This isn't restricted to just route components either. With the exception of islands or components used inside islands, any component on the server can be async in Fresh 2. Components in islands cannot be async, because islands are also rendered in the browser and Preact does not support async components there. It's unlikely that it will in the near future either as rendering in the client is much more complex, given that it mostly has to deal with updates which are not a thing on the server.
Simpler error responses
Fresh 1.x allows you two define a
_500.tsx
and a_404.tsx
template at the top of your routes folder. This allows you to render a different template when an error occurred. But this has a problem: What if you want to show an error page when a different status code other than500
or404
is thrown?We could always add support for more error templates in Fresh, but ultimately, the heart of the issue is that Fresh does the branching instead of the developer using Fresh.
So in Fresh 2.0 both templates will be merged into one
_error.tsx
template.And then inside the
_error.tsx
template you can branch and render different content based on the error code if you desire:With only one error template to deal with, this makes it a lot easier to allow them to be put anywhere. So will be able to use different error templates for different parts of your app.
Adding
<head>
elements from HandlersWhilst not ready for the initial Fresh 2.0 release, we plan to get the guts of Fresh ready for streaming rendering where both the server and client can load data in parallel. Again, this will not be part of the initial 2.0 release, but might land later in the year. But before we can even explore that path, some changes in regards to how Fresh works are required.
With streaming the
<head>
portion of an HTML document will be flushed as early as possible to the browser. This breaks the current<Head>
component which allows you to add additional elements into the<head>
-tag from anywhere else in your component tree. By the team the<Head>
component is called, the actual<head>
of the document has long been flushed already to the browser. There is no remaining head on the server we could patch anymore.So with Fresh 2.0 we'll remove the
<Head>
component in favour of a new way of adding elements from a route handler.EDIT: This API is the least fleshed out of the ones listed here and might change. The main goal we have is to get rid of the
<Head>
component.Appendix
These are the breaking changes we have planned for Fresh 2. Despite some of them being breaking changes updating a Fresh 1.x project to Fresh 2 should be fairly smooth. Although this is quite a long list of bigger features, I spent yesterday and today hacking on it and got way further than I anticipated. Most of the features are already implemented in a branch, but lots of things are still in flux. It's lacking quite a bit of polish too. There will likely be some bigger changes landed in the coming weeks in the
main
branch.It's too early yet to be tried out, but there will be an update soon. Once the actual release date approaches we'll also work on adding an extensive migration document. Given that it's early days and I just started back working on Fresh
yesterday, these things don't exist yet. The details of some of the features listed here and how they will be implemented might change, but I think the rough outline is pretty solid already.
I'm pretty excited about this release because it fixes many long standing issues and massively improves the overall API. Playing around with early prototypes it makes it much more fun to use too. I hope you are similarly excited about this release.
The text was updated successfully, but these errors were encountered: