Is it possible to use Material UI components in a Deno Fresh app?

813 Views Asked by At

I've been working with Material UI for quite a bit and I've liked it quite a lot.

I've also liked Deno and its framework Fresh. The main downside for Fresh is that I have not found any documentation to integrate any graphic component library with it.

Is it possible? Does anyone have an example?

1

There are 1 best solutions below

6
On

Confirmation - it is possible to use Material UI components in a Deno Fresh app.

Took the past several hours to get working but ultimately it comes down setting up your deno.json imports correctly (I have it aliased as $mui in my deno.json file pasted below).

The tricky parts were

  1. Figuring out how to properly structure the import url for Mui
  2. Making sure to use islands for certain Mui components that require front-end reactivity

Let's start by breaking down the import needed that I've defined in my deno.json imports array.

"$mui": "https://esm.sh/@mui/[email protected]?alias=react:preact/compat,react/jsx-runtime:preact/compat/jsx-runtime&[email protected]?dts",    

The import URL provided for my $mui alias is a way to use the Material-UI (MUI) library with Preact in a project, specifically when importing from the esm.sh CDN.

Let's break down the import to understand what each part does:

  1. Base URL and Package Name:

    • https://esm.sh/@mui/[email protected]
    • This is the base URL for the esm.sh CDN, followed by the package name (@mui/material) and its version (5.8.7). It specifies that you're importing the Material-UI library, version 5.8.7.
  2. Aliases (alias Query Parameter):

    • ?alias=react:preact/compat,react/jsx-runtime:preact/compat/jsx-runtime
    • This part of the URL tells the CDN to replace (alias) any imports of react and react/jsx-runtime within the MUI package with preact/compat and preact/compat/jsx-runtime, respectively.
    • react:preact/compat means "wherever MUI is importing React, use Preact Compat instead."
    • react/jsx-runtime:preact/compat/jsx-runtime means "wherever MUI is importing React's JSX runtime, use Preact's Compat JSX runtime instead."
  3. Dependencies (deps Query Parameter):

    • &[email protected]?dts
    • This specifies that the version of Preact to be used as a dependency is 10.8.1.
    • The ?dts at the end indicates that TypeScript definitions are also requested.

Putting it all together, this URL is used to import the Material-UI library from the esm.sh CDN, while ensuring that it works with Preact instead of React. It does this by aliasing React to Preact Compat, making it possible to use MUI (which is originally built for React) in a Preact project. This is particularly useful in environments like Deno, where you might be working with Preact and want to leverage the Material-UI component library.

Here's my entire deno.json file

// deno.json
{
  "lock": false,
  "tasks": {
    "db:dump": "deno run --allow-read --allow-env --unstable tasks/db-dump.ts",
    "db:reset": "deno run --allow-read --allow-env --unstable tasks/db-reset.ts",
    "db:restore": "deno run --allow-read --allow-env --unstable tasks/db-restore.ts",
    "check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
    "start:no-watch": "deno run --unstable -A --env dev.ts",
    "start": "deno run --unstable -A --watch=static/,routes/ --env dev.ts",
    "build": "deno run -A --unstable dev.ts build",
    "preview": "deno run -A --unstable main.ts",
    "update": "deno run -A -r https://fresh.deno.dev/update ."
  },
  "lint": {
    "rules": {
      "tags": [
        "fresh",
        "recommended"
      ]
    }
  },
  "exclude": ["cov/", "_fresh/", "**/_fresh/*"],
  "imports": {
    "react": "https://cdn.skypack.dev/preact@latest?dts",
    "react-dom": "https://cdn.skypack.dev/preact@latest?dts",
    "$mui": "https://esm.sh/@mui/[email protected]?alias=react:preact/compat,react/jsx-runtime:preact/compat/jsx-runtime&[email protected]?dts",    
    "@emotion/css/": "https://esm.sh/@emotion/css@11/",
    "@emotion/react/": "https://esm.sh/@emotion/react@11/",
    "@emotion/styled/": "https://esm.sh/@emotion/styled@11/",
    "googleapis": "npm:[email protected]",
    "lunchbox": "https://deno.land/x/[email protected]/mod.ts",
    "$fresh/": "https://deno.land/x/[email protected]/",
    "preact": "https://esm.sh/[email protected]",
    "preact/": "https://esm.sh/[email protected]/",
    "@preact/signals": "https://esm.sh/*@preact/[email protected]",
    "@preact/signals-core": "https://esm.sh/*@preact/[email protected]",
    "tailwindcss": "npm:[email protected]",
    "tailwindcss/": "npm:/[email protected]/",
    "tailwindcss/plugin": "npm:/[email protected]/plugin.js",
    "$std/": "https://deno.land/[email protected]/",
    "@/": "./",
    "$gfm": "https://deno.land/x/[email protected]/mod.ts",
    "preact-render-to-string": "https://esm.sh/*[email protected]",
    "twind-preset-tailwind/": "https://esm.sh/@twind/[email protected]/",
    "twind-preset-ext": "https://esm.sh/@twind/[email protected]/",
    "std/": "https://deno.land/[email protected]/",
    "stripe": "npm:/[email protected]",
    "feed": "npm:/[email protected]",
    "kv_oauth/": "https://deno.land/x/[email protected]/",
    "tabler_icons_tsx/": "https://deno.land/x/[email protected]/tsx/",
    "@twind/core": "https://esm.sh/@twind/[email protected]",
    "fresh_charts/": "https://deno.land/x/[email protected]/"
  },
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  },
  "nodeModulesDir": true
}

Here's my routes/index.tsx file

import { Container } from '$mui';
import AppBar from "@/islands/AppBar.tsx"
import { PageProps } from "$fresh/server.ts";

export default function Home(props: PageProps) {
  console.log(props)
  return (
    <div>
      <AppBar />

      <Container maxWidth="md">
        <h1>
          Testing
        </h1>
      </Container>
    </div>
  )
}

Here's my routes/_app.tsx file

import type { AppProps } from "$fresh/server.ts";

export default function App({ Component }: AppProps) {
  return (
    <html>
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Site Name</title>
        <link rel="stylesheet" href="/styles.css" />
      </head>
      <body>
        <Component />
      </body>
    </html>
  );
}

And lastly, the second gotcha with integrating Material UI components in a deno fresh app - being aware that some Mui UI Components only work within Deno Fresh islands (components defined in the islands folder of your deno fresh project).

The Mui UI components that need to be created within islands are the components that require front-end reactivity to work properly.

As an example, I had to move the Mui UI AppBar into an island for it to work properly. Then you can import this island component. Once it's in an island, you can import this AppBar island into any other deno fresh page/component for use and it works as expected - it just requires some front-end reactivity so must be defined in an island in accordance to deno fresh's island architecture.

// islands/AppBar.tsx

import { Typography, AppBar as MuiAppBar, Toolbar, Button } from '$mui';
import { config } from "../utils/constants.ts";
export default function AppBar() {
    return (
        <MuiAppBar position="static">
            <Toolbar
                sx={{
                    backgroundColor: '#333',
                    color: '#fff'
                }}
            >
                <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                    {config("app.name")}
                </Typography>
                <Button color="inherit">Sign In</Button>
            </Toolbar>
        </MuiAppBar>
    )
}

And last but not least, as proof of it properly working, here is an image of the deno fresh app with the AppBar Mui ui component being used. And lala, both Stylistically and functionally Mui UI Components work properly in a deno fresh project.

enter image description here

Hope that helps!