Skip to content

Commit

Permalink
INFO (serif) : Shared Data Layer added. INFO (serif) : Redux and RTKQ…
Browse files Browse the repository at this point in the history
…uery integration must be added on next article.
  • Loading branch information
serifcolakel committed Apr 25, 2024
1 parent 5ac9ee6 commit 83898b1
Show file tree
Hide file tree
Showing 84 changed files with 4,030 additions and 104 deletions.
3 changes: 3 additions & 0 deletions .env.custom
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NX_BASE_PLATZI_STORE_SERVICE_URL=https://api.escuelajs.co/api/v1
NX_ACCESS_TOKEN_KEY=accessToken
NX_REFRESH_TOKEN_KEY=refreshToken
3 changes: 3 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NX_BASE_PLATZI_STORE_SERVICE_URL=https://api.escuelajs.co/api/v1
NX_ACCESS_TOKEN_KEY=accessToken
NX_REFRESH_TOKEN_KEY=refreshToken
3 changes: 3 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NX_BASE_PLATZI_STORE_SERVICE_URL=https://api.escuelajs.co/api/v1
NX_ACCESS_TOKEN_KEY=accessToken
NX_REFRESH_TOKEN_KEY=refreshToken
36 changes: 19 additions & 17 deletions SHADCN_UI_SETUP.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# Shared UI Setup For Micro Frontend Application (Module Federation with React) with Nx Workspace
# Shared Data-Layer Setup For Micro Frontend Application with Nx Workspace

This tutorial will guide you through setting up a shared `UI library` for a `Micro Frontend Application` using Nx Workspace, React, and Tailwind CSS. We will use `Shadcn UI` for the UI components.
This tutorial will guide

## Link for Final Implementation

The final implementation of the tutorial can be found in the following repository commits:

- [Add UI package with Shadcn components and use them on apps](https://github.com/serifcolakel/mfe-tutorial/commit/5704168095b2c83b8b51823ed585a1cdf3210dbc)
- [Add UI package with Button component and update dependencies](https://github.com/serifcolakel/mfe-tutorial/commit/cafa9a12f9c95a9a1536ff4e11c2a2008a3d89a5)
-

> Live Demo: [Micro Frontend Application with Nx Workspace](https://relaxed-mochi-7581fa.netlify.app/)
Expand All @@ -29,34 +28,37 @@ Before we begin, make sure you have the following things set up:

## Table of Contents

- [Create UI Library](#create-ui-library)
- [Add Tailwind CSS Setup](#add-tailwind-css-setup)
- [Shadcn UI Setup](#shadcn-ui-setup)
- [Add Button Component](#add-button-component)
- [Add Shadcn UI Hover Card](#add-shadcn-ui-hover-card)
- [Add Shadcn UI Badge](#add-shadcn-ui-badge)
- [Conclusion](#conclusion)
- [Create React Library](#create-react-library)

## Create UI Library
## Create React Library

First, we need to create a UI library using the Nx Workspace. We will use the `@nx/react:library` generator to create the UI library.
First, we need to create a React library using the Nx Workspace. We will use the `@nx/react:library` generator to create the React library.

> With Script
```bash
pnpm exec nx generate @nx/react:library --name=ui --bundler=vite --directory=packages/ui --projectNameAndRootFormat=as-provided --no-interactive
pnpm exec nx generate @nx/react:library --name=data --bundler=vite --directory=apps/data --projectNameAndRootFormat=as-provided --no-interactive --dry-run
```

The Scripts are explained below:

- **--name** : The name of the library. In this case, we are naming it `data`.
- **--bundler** : The bundler to use for the library. In this case, we are using `vite`.
- **--directory** : The directory where the library will be created. In this case, we are creating it in the `apps/data` directory.
- **--projectNameAndRootFormat** : The format to use for the project name and root. In this case, we are using `as-provided`.
- **--no-interactive** : Disable interactive prompts.
- **--dry-run** : Show what will be generated without actually generating it.

> With Nx Console
![Nx Console](https://i.hizliresim.com/przb27y.png)

## Add Tailwind CSS Setup
## Add Services For Data Layer

Next, we need to add the Tailwind CSS setup to the UI library. We will use the `@nx/react:setup-tailwind` generator to add the Tailwind CSS setup.
Next, we need to add the services for the data layer in the `data` library. We will create a `data` service that fetches data from an API.

```bash
pnpm exec nx generate @nx/react:setup-tailwind --project=ui --no-interactive
pnpm add axios
```

- **Configure Tailwind Config** : Update the `packages/ui/tailwind.config.js` file with the following content:
Expand Down
84 changes: 84 additions & 0 deletions apps/container/module-federation.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,90 @@ import { ModuleFederationConfig } from '@nx/webpack';
const config: ModuleFederationConfig = {
name: 'container',
remotes: ['info'],
shared: (name, defaultConfig) => {
// "react-hook-form": "^7.51.3"
if (name.includes('react-hook-form')) {
return {
singleton: true,
eager: true,
requiredVersion: '^7.51.3',
};
}

// "@hookform/resolvers": "^3.3.4"
if (name.includes('@hookform/resolvers')) {
return {
...defaultConfig,
strictVersion: false,
requiredVersion: '^3.3.4',
};
}

// "zod": "^3.22.5"
if (name.includes('zod')) {
return {
singleton: true,
eager: true,
requiredVersion: '^3.22.5',
};
}

// react 18.2.0
if (name.includes('react')) {
return {
singleton: true,
eager: true,
requiredVersion: '^18.2.0',
};
}

// react-dom 18.2.0
if (name.includes('react-dom')) {
return {
singleton: true,
eager: true,
requiredVersion: '^18.2.0',
};
}

// "react-redux": "^9.1.1",
if (name.includes('react-redux')) {
return {
singleton: true,
eager: true,
requiredVersion: '^9.1.1',
};
}

// "@reduxjs/toolkit": "^2.2.3",
if (name.includes('@reduxjs/toolkit')) {
return {
singleton: true,
eager: true,
requiredVersion: '^2.2.3',
};
}

// @radix-ui/react-toast (required ^1.1.5)
if (name.includes('@radix-ui/react-toast')) {
return {
singleton: true,
eager: true,
requiredVersion: '^1.1.5',
};
}

// @radix-ui/react-slot (required ^1.0.2)
if (name.includes('@radix-ui/react-slot')) {
return {
singleton: true,
eager: true,
requiredVersion: '^1.0.2',
};
}

return false;
},
};

export default config;
16 changes: 16 additions & 0 deletions apps/container/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@
"extractLicenses": true,
"vendorChunk": false,
"webpackConfig": "apps/container/webpack.config.prod.ts"
},
"custom": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"webpackConfig": "apps/container/webpack.config.custom.ts"
}
}
},
Expand All @@ -64,6 +73,10 @@
"production": {
"buildTarget": "container:build:production",
"hmr": false
},
"custom": {
"buildTarget": "container:build:custom",
"hmr": false
}
}
},
Expand All @@ -88,6 +101,9 @@
},
"production": {
"buildTarget": "container:build:production"
},
"custom": {
"buildTarget": "container:build:custom"
}
}
},
Expand Down
55 changes: 7 additions & 48 deletions apps/container/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,14 @@
import { Button } from '@mfe-tutorial/ui';
import * as React from 'react';
import { NavLink, Route, Routes } from 'react-router-dom';
import { Loader } from 'lucide-react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const HomePage = React.lazy(() => import('../pages/home'));
const Info = React.lazy(() => import('info/InfoContainer'));
import { routes } from '../routes';

export function App() {
return (
<React.Suspense fallback={null}>
<nav
style={{
display: 'flex',
justifyContent: 'center',
flexDirection: 'row',
gap: '2rem',
margin: '1rem',
}}
>
<NavLink
style={({ isActive }) => ({
backgroundColor: isActive ? 'lightblue' : 'blue',
padding: '0.5rem',
borderRadius: '0.5rem',
textDecoration: 'none',
color: 'white',
})}
to="/"
>
Home
</NavLink>
<NavLink
style={({ isActive }) => ({
backgroundColor: isActive ? 'lightblue' : 'blue',
padding: '0.5rem',
borderRadius: '0.5rem',
textDecoration: 'none',
color: 'white',
})}
to="/info"
>
Info
</NavLink>
<Button>Click Me</Button>
<Button variant="destructive">Click Me</Button>
<Button variant="secondary">Click Me</Button>
</nav>
<Routes>
<Route element={<HomePage />} path="/" />
<Route element={<Info />} path="/info" />
</Routes>
</React.Suspense>
<RouterProvider
fallbackElement={<Loader className="animate-spin" size="4rem" />}
router={createBrowserRouter(routes)}
/>
);
}

Expand Down
8 changes: 5 additions & 3 deletions apps/container/src/bootstrap.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DataLayerProviders } from '@mfe-tutorial/data';
import { Toaster } from '@mfe-tutorial/ui';
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';

import App from './app/app';

Expand All @@ -14,8 +15,9 @@ const root = ReactDOM.createRoot(element);

root.render(
<StrictMode>
<BrowserRouter>
<DataLayerProviders>
<Toaster />
<App />
</BrowserRouter>
</DataLayerProviders>
</StrictMode>
);
6 changes: 6 additions & 0 deletions apps/container/src/pages/home/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { paths } from '@mfe-tutorial/data';
import { NavLink } from 'react-router-dom';

import { HoverCardDemo } from '../../components/hover-card';
import SocialLinks from '../../components/social-links';

export default function HomePage() {
return (
<div className="h-[90vh] flex flex-col justify-center items-center bg-gray-100 gap-y-4 w-full">
<p className="text-[200px] animate-wiggle">🌍</p>
<NavLink className="text-lg text-blue-500" to={paths.login}>
Go to the Login App
</NavLink>
<h1 className="text-4xl font-bold text-primary">
Welcome to the Container!
</h1>
Expand Down
49 changes: 49 additions & 0 deletions apps/container/src/pages/login/hooks/use-login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { zodResolver } from '@hookform/resolvers/zod';
import {
LoginRequest,
loginRequestSchema,
paths,
usePlatziStoreAuth,
} from '@mfe-tutorial/data';
import { useToast } from '@mfe-tutorial/ui';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';

export default function useLogin() {
const navigate = useNavigate();
const { toast } = useToast();
const { error, handleLogin, loading, onResetError } = usePlatziStoreAuth();

const loginForm = useForm<LoginRequest>({
defaultValues: {
email: 'john@mail.com',
password: 'changeme',
},
resolver: zodResolver(loginRequestSchema),
});

async function onSubmit(data: LoginRequest) {
const result = await handleLogin(data);

toast({
title: result.title,
description: result.message,
variant: result.success ? 'default' : 'destructive',
});

if (result.success) {
navigate(paths.info);
}
}

return {
loginForm,
loading:
loading ||
loginForm.formState.isLoading ||
loginForm.formState.isSubmitting,
error,
onSubmit,
onResetError,
};
}
Loading

0 comments on commit 83898b1

Please sign in to comment.