React is awesome but have you tried fresh?
ashish

ashish @asheeshh

About: student and solo dev by passion who likes writing occasionally

Location:
Under a Blanket
Joined:
Oct 6, 2021

React is awesome but have you tried fresh?

Publish Date: Jun 30 '22
306 41

Why Fresh?

Let me start by asking you a simple question -- "Have you used react?" or have you used any JS framework or library for creating web apps like Vue, Svelte, Lit, NextJS or anything like that. You know what's the one thing that's common in all of them? They are built on top of Nodejs, have lots of boilerplate code and install tons of other libraries and use that evil node_modules folder.

What if I said, there's a framework which doesn't have any of these issues, has minimum boilerplate, no node_modules and is built on top of deno. I'm talking about fresh here. It's a web framework built on top of deno, and recently got out of beta and is getting decent amount of attention from the JS ecosystem. The creators of fresh call it "The next gen web framework", sounds cool right!

Some features that make fresh stand out --

  • Just-in-time rendering on the edge.
  • Island based client hydration for maximum interactivity.
  • Zero runtime overhead: no JS is shipped to the client by default.
  • No build step.
  • No configuration necessary.
  • TypeScript support out of the box.

In this blog, I'll walk you through the basics of the framework and we will be building the classic ToDo app as always, so let's start!

Prerequisite: you should have deno installed on your machine you can install it using this.

Create the App

Let's start by scaffolding our project, to create a new fresh project and run it you need to run these command in your terminal. Make sure you use twind when you are prompted for it by fresh.

deno run -A -r https://fresh.deno.dev todo-app
cd todo-app
deno task start
Enter fullscreen mode Exit fullscreen mode

This will start your app on localhost:8000, with a basic counter app.

ss1

Let's understand the basic concepts now, a fresh project has a total of 8 components, I'll be covering only the routes/ and islands/ folder in this blog, you can learn about all of them here.

  • routes/: This folder contains all of the routes in your project. The names of each file in this folder correspond to the path where that page will be accessed. Code inside of this folder is never directly shipped to the client
  • islands/: This folder contains all of the interactive islands in your project. The name of each file corresponds to the name of the island defined in that file. Code inside of this folder can be run from both client and server.

In simpler terms, to add interactivity and reactivity to your project, you need to use islands and to create pages/routes you need to use routes.

Let's start by creating a new todo route in the app where we will build our todo app. Add a file named todo.tsx inside routes folder with the below content. We will be using twind to style the app, so having a basic knowledge of tailwind would be good.

// routes/todo.tsx

/** @jsx h */
import { h } from "preact";
import { tw } from "@twind";

export default function Todo() {
    return (
        <div class={tw`w-screen h-screen flex flex-col justify-center items-center`}>
            <h1>hello world</h1>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

This syntax is very similar to react as we are using jsx, tw is being used to style the elements using twind, you can learn more about it from twind's site if you want to. Now, if you did everything correctly, going to localhost:8000/todo will give you a page which looks like this -

ss2

Now, let's start by building our todo component inside the islands/ folder. Create a new file named TodoComponent.tsx inside inslands folder and put the following code inside it.

// islands/TodoComponent.tsx

/** @jsx h */
import { h } from "preact";
import { useState } from "preact/hooks";
import { IS_BROWSER } from "$fresh/runtime.ts";
import { tw } from "@twind";

export default function TodoComponent() {
  const [todoEl, setTodoEL] = useState("");
  const [todos, setTodos] = useState([]);
  const btn = tw
    `px-2 py-1 border-gray-200 border-2 hover:bg-gray-200 focus:outline-none`;

  return (
    <div class={tw`h-2/3 w-1/2 flex flex-col justify-center ites-center gap-3`}>
      <div class={tw`flex gap-3 h-[10%] w-full`}>
        <input
          type="text"
          class={tw
            `flex-grow-1 outline-none focus:outline-none border-gray-200 border-2 p-2`}
          placeholder="Enter new ToDo"
          onChange={(e: any) => {
            setTodoEL(e.target.value);
          }}
        >
        </input>
        <button
          class={btn}
          onClick={() => {
            if (todoEl) {
              setTodos([...todos, todoEl]);
              setTodoEL("");
            }
          }}
          disabled={!IS_BROWSER}
        ></button>
      </div>
      <ul class={tw`flex flex-col gap-2 overflow-y-scroll min-h-[90%]`}>
        {todos.map((todo, index) => (
          <li class={tw`flex gap-2`} key={todo}>
            <p class={tw`flex-grow-1`}>{todo}</p>
            <button
              class={btn}
              onClick={() => {
                setTodos(todos.filter((todo, i) => i !== index));
              }}
              disabled={!IS_BROWSER}
            ></button>
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

It's a basic todo app code, which you can understand easily.
We have 2 states one for the current todo element and other for our list of todos, we render a flex container with two containers inside it, the first one has an input box and a button to add todos. We are using onChange on input element to update our todoEl state and a onClick in the add todo button which adds the todoEl to the array after making sure it's not null.
The second part has a ul element which maps our todos array to create li elements with todo as their text and a button to remove the todo element using the index of todo.

Now we need to add this island to our todo route. We can do that like this --

routes/todo.tsx

/** @jsx h */
import { h } from "preact";
import { tw } from "@twind";
import TodoComponent from "../islands/TodoComponent.tsx";

export default function Todo() {
  return (
    <div
      class={tw`w-screen h-screen flex flex-col justify-center items-center`}
    >
      <TodoComponent />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now, if you open localhost:8000/todo you'll see something like this if you followed the tutorial correctly-

ss3

You can try playing with the app to see if it works, and it will work! You can also try to add a button for marking the todo as done if you want to as an exercise.

Conclusion

This was a basic intro about fresh framework, you can learn more about it by reading the docs.

Thanks for reading, Happy Coding!

Buy me a pizza 🍕

Comments 41 total

  • Aurélien Delogu
    Aurélien DeloguJun 30, 2022

    Is the page refreshed at each action? Like is it refreshed when you add a task in the to-do list?

    • ashish
      ashish Jun 30, 2022

      no it doesnt

      • Aurélien Delogu
        Aurélien DeloguJun 30, 2022

        So there's JavaScript embedded inside the page, isn't it ?

        • ashish
          ashish Jun 30, 2022

          yes i think, according to what i have read, fresh uses just in time rendering, and also uses partial hydration through islands, and i don't think refreshing on adding a new todo is an expected behavior as i'm not using forms, right?

          • Aurélien Delogu
            Aurélien DeloguJun 30, 2022

            Mmmh ok. I read the documentation a bit. So it seems JS is sent to the client when islands are used, is that it ? Because without it I cannot see how all of this could work ahah.

            But that's cool, Fresh seems really equivalent to Phoenix (an Elixir framework). Those hybrid frameworks are the future IMHO.

            • ashish
              ashish Jun 30, 2022

              yes js is sent to the client when its needed, and not all components are hydrated, only the islands are hydrated i.e. provide interactivity

  • tq-bit
    tq-bitJun 30, 2022

    Fresh looks pretty cool. Out of curiousity: Could you find some tooling that helps with Deno imports? VSCode's intellisense alone doesn't cut it and shows me one error after the other when I try and fiddle around with Deno

    • ashish
      ashish Jun 30, 2022
      1. Install the official deno extension for vsc.

      2. create a .vscode folder in the project dir with settings.json file, put the following content inside it -

      {
        "deno.enable": true,
        "deno.lint": true
      }
      
      Enter fullscreen mode Exit fullscreen mode

      this is the basic config, you can learn more here.

      1. for finding packages, use deno.land/x
    • bherbruck
      bherbruckJul 1, 2022

      If you have docker installed, use the official deno devcontainer, it has everything set up

  • Alexandr Rešetňak
    Alexandr RešetňakJun 30, 2022

    Just saw a video from Fireship about Fresh and there is already an article. Great community! Will definitely try Fresh. And well written article! Thanks.

  • Andrew Baisden
    Andrew BaisdenJul 1, 2022

    Cool, this is a new one for me.

  • АнонимJul 1, 2022

    [deleted]

    • ashish
      ashish Jul 1, 2022

      I don't understand what you mean by "doesn't have a build", fresh does build the app, github.com/denoland/fresh/blob/mai...

      about bundle size, I'm not really sure as I didn't check but it shouldn't be high according to me.

      there are some benefits, being able to run the whole app on edge is a benefit in itself, not to mention all the benefits of using deno over node count in too, overall it's a great experience using fresh and I have a lot of expectations from this framework personally!

      • АнонимJul 4, 2022

        [deleted]

        • ashish
          ashish Jul 4, 2022

          its a relatively new tech, I'm currently trying to learn more about it myself so no need to worry 👍

  • bherbruck
    bherbruckJul 1, 2022

    Did you get twind autocompletion working in vscode? I couldn't get the plugin working in a deno environment

    • ashish
      ashish Jul 2, 2022

      Doesn't work for me too, I have opened an issue in the repository regarding this, hopefully they'll fix it soon

    • ashish
      ashish Jul 3, 2022

      someone in issues helped me out, its working perfectly fine now, see this: github.com/tw-in-js/vscode-twind-i...

  • Yongchang He
    Yongchang HeJul 2, 2022

    Thank you for sharing!

  • Dao Shen
    Dao ShenJul 2, 2022

    Kudos for using Deno! <3

    React is not awesome. It encourages people to embed content and the structure for content in code. It looks like fresh is doing the same thing. No. For anything other than toys and one-offs, doing this results in brittle systems to maintain.

    Reactive architecture however, now this is awesome. Really really awesome. Learn this from React and fresh, and then when we are done playing, we'll be ready for systems that both scale and can be maintained and grown over time.

    All the best!

    • ashish
      ashish Jul 2, 2022

      I agree with the second paragraph, couldn't have said it any better, but about the first paragraph - if you don't mind can you elaborate what do you mean by "embedding content in code", I think I'm missing something here.

      • Jan Wedel
        Jan WedelJul 2, 2022

        I think it’s about having logic, structure and styling in one file. I have never used react, but it always looked very weird to me. I agree with that in large applications, I would never ever want this. When I am changing code, I’ll do it in a ts file, when I change structure, I’ll do it in an html file and when I style something, I’ll do it in a css file. Very simple. That’s how browsers work and it’s a very sensible separation of concerns.

        • ashish
          ashish Jul 3, 2022

          makes somewhat sense, though it's hard to structure your project when it's really big as most of the frameworks don't work the way you want them to and seeing how css-in-js is being used by more people, but I don't hate the way of structuring projects using folders like pages, components, styles too. It's not exactly what you want but is somewhat efficient and readable.

          • imonem
            imonemJul 3, 2022

            I think having everything in one file is for the purpose of the tutorial and not intended to be for scaling production. I use sass for larger projects. However if it's a quick prototype then it's bootstrap or two for the sake of speed

            • ashish
              ashish Jul 3, 2022

              absolutely, I just wanted to show the basics of the framework, fresh is still very new to be used in production but I have high hopes from it.

  • panfan
    panfanJul 3, 2022

    how to import icons

    • ashish
      ashish Jul 3, 2022

      As many npm modules dont yet support deno, things like react-icons may not work, though you can use simple-icons for sure with an image tag.

      Other than that I'd personally prefer getting the svg paths of the icons and then exporting them as jsx components, for example. see this --
      github.com/denoland/fresh/blob/mai...

      • panfan
        panfanJul 3, 2022

        got it.
        do u have try to deploy on vercel?

        • ashish
          ashish Jul 3, 2022

          i didn't try it, i'd suggest deploying on deno deploy though, runs on edge and has faster deployment

    • Zenobius Jiricek
      Zenobius JiricekMar 26, 2023

      iconify. thank me later.

  • Largenty
    LargentyJul 3, 2022

    Can we use preact-router with it ?

  • Ayush Kumar Shaw
    Ayush Kumar ShawJul 3, 2022

    Thanks for the post Ashish. Nice way of a starter introduction to Fresh. Once again, thanks.

    • ashish
      ashish Jul 3, 2022

      thanks for reading!

  • Rogério Luiz Aques de Amorim
    Rogério Luiz Aques de AmorimJul 4, 2022

    I saw the documentation and wondered how can I share state between islands. I can't stick one island inside another island to pass the state by props. For me it seems a big downpoint for fresh. Hope they figure it out.

    • ashish
      ashish Jul 5, 2022

      you can use non interactive "components" inside islands though, but not being able to call an island inside another is surely a thing I hope they work on.

  • Nick
    NickJul 13, 2022

    Great article, thanks for sharing

  • Tony Brown
    Tony BrownAug 23, 2022

    Thanks for the write up. This looks like a cool framework

  • Zenobius Jiricek
    Zenobius JiricekMar 26, 2023

    don't manually download engine binaries...

    use asdf and asdf-deno, then drop a .tool-versions file in your project root

    git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3
    echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
    
    Enter fullscreen mode Exit fullscreen mode

    new terminal

    asdf plugin add deno
    asdf list-all deno
    # note down the version you want
    asdf install deno 1.32.0
    asdf global deno 1.32.0
    # makes 1.32.0 default for your user when not in a project
    asdf local deno 1.32.0
    cat .tool-versions
    asdf reshim
    
    asdf current
    
    Enter fullscreen mode Exit fullscreen mode

    When moving between projects, there's nothing to do. the ~/.asdf/shims/deno inspects your local .tool-versions

    when pulling a project down, you should run asdf install when you see the .tool-versions file is modified.

    I've now just taught you how to setup any other tool for your project and ensure that when you share the project or come back to it in (insert time frame where your brain turns to mush) minutes, you'll have added a degree of reproducability.

Add comment