Skip to content

Commit

Permalink
enforce unidirectional codebase
Browse files Browse the repository at this point in the history
  • Loading branch information
alan2207 committed May 18, 2024
1 parent f705e48 commit 03e1c29
Show file tree
Hide file tree
Showing 29 changed files with 78 additions and 18 deletions.
22 changes: 21 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ module.exports = {
'plugin:vitest/legacy-recommended',
],
rules: {
// disables cross-feature imports:
'import/no-restricted-paths': [
'error',
{
zones: [
// disables cross-feature imports:
// eg. src/features/auth should not import from src/features/comments, etc.
{
target: './src/features/auth',
from: './src/features',
Expand All @@ -72,6 +73,25 @@ module.exports = {
from: './src/features',
except: ['./users'],
},
// enforce unidirectional codebase:

// e.g. src/app can import from src/features but not the other way around
{
target: './src/features',
from: './src/app',
},

// e.g src/features and src/app can import from these shared modules but not the other way around
{
target: [
'./src/components',
'./src/hooks',
'./src/lib',
'./src/types',
'./src/utils',
],
from: ['./src/features', './src/app'],
},
],
},
],
Expand Down
42 changes: 41 additions & 1 deletion docs/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ src/features/awesome-feature
+-- utils # utility functions for a specific feature
```

It might be a bad idea to import across the features. Instead, compose different features at the application level in the routes/pages. This way, you can ensure that each feature is independent and can be easily moved or removed without affecting other parts of the application and also makes the codebase less convoluted.
NOTE: You don't need all of these folders for every feature. Only include the ones that are necessary for the feature.

Previously, it was recommended to use barrel files to export all the files from a feature. However, it can cause issues for Vite to do tree shaking and can lead to performance issues. Therefore, it is recommended to import the files directly.

It might be a bad idea to import across the features. Instead, compose different features at the application level. This way, you can ensure that each feature is independent and can be easily moved or removed without affecting other parts of the application and also makes the codebase less convoluted.

To forbid cross-feature imports, you can use ESLint:

Expand All @@ -61,6 +65,8 @@ To forbid cross-feature imports, you can use ESLint:
'error',
{
zones: [
// disables cross-feature imports:
// eg. src/features/auth should not import from src/features/comments, etc.
{
target: './src/features/auth',
from: './src/features',
Expand All @@ -86,7 +92,41 @@ To forbid cross-feature imports, you can use ESLint:
from: './src/features',
except: ['./users'],
},

// More restrictions...
],
},
],
```

You might also want to enforce unidirectional codebase architecture. This means that the code should flow in one direction, from the top to the bottom of the application. This is a good practice to follow as it makes the codebase more predictable and easier to understand. To enforce this, you can use ESLint:

```js
'import/no-restricted-paths': [
'error',
{
zones: [
// Previous restrictions...

// enforce unidirectional codebase:
// e.g. src/app can import from src/features but not the other way around
{
target: './src/features',
from: './src/app',
},

// e.g src/features and src/app can import from these shared modules but not the other way around
{
target: [
'./src/components',
'./src/hooks',
'./src/lib',
'./src/types',
'./src/utils',
],
from: ['./src/features', './src/app'],
},
],
},
],
```
4 changes: 2 additions & 2 deletions src/app.tsx → src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RouterProvider } from 'react-router-dom';

import { AppProvider } from '@/providers/app';
import { router } from '@/routes';
import { AppProvider } from './main-provider';
import { router } from './routes';

function App() {
return (
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions src/routes/index.tsx → src/app/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const router = createBrowserRouter([
path: 'discussions',
lazy: async () => {
const { DiscussionsRoute } = await import(
'@/routes/app/discussions/discussions'
'./app/discussions/discussions'
);
return { Component: DiscussionsRoute };
},
Expand All @@ -71,7 +71,7 @@ export const router = createBrowserRouter([
path: 'discussions/:discussionId',
lazy: async () => {
const { DiscussionRoute } = await import(
'@/routes/app/discussions/discussion'
'./app/discussions/discussion'
);
return { Component: DiscussionRoute };
},
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook, act } from '@testing-library/react';

import { useNotifications, Notification } from '../notifications';
import { useNotifications, Notification } from '../notifications-store';

test('should add and remove notifications', () => {
const { result } = renderHook(() => useNotifications());
Expand Down
1 change: 1 addition & 0 deletions src/components/ui/notifications/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './notifications';
export * from './notifications-store';
File renamed without changes.
3 changes: 1 addition & 2 deletions src/components/ui/notifications/notifications.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useNotifications } from '@/stores/notifications';

import { Notification } from './notification';
import { useNotifications } from './notifications-store';

export const Notifications = () => {
const { notifications, dismissNotification } = useNotifications();
Expand Down
2 changes: 1 addition & 1 deletion src/features/comments/components/create-comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Plus } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { Form, FormDrawer, Textarea } from '@/components/ui/form';
import { useNotifications } from '@/stores/notifications';
import { useNotifications } from '@/components/ui/notifications';

import {
useCreateComment,
Expand Down
2 changes: 1 addition & 1 deletion src/features/comments/components/delete-comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Trash } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { ConfirmationDialog } from '@/components/ui/dialog';
import { useNotifications } from '@/stores/notifications';
import { useNotifications } from '@/components/ui/notifications';

import { useDeleteComment } from '../api/delete-comment';

Expand Down
2 changes: 1 addition & 1 deletion src/features/discussions/components/create-discussion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Plus } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { Form, FormDrawer, Input, Textarea } from '@/components/ui/form';
import { useNotifications } from '@/components/ui/notifications';
import { Authorization, ROLES } from '@/lib/authorization';
import { useNotifications } from '@/stores/notifications';

import {
createDiscussionInputSchema,
Expand Down
2 changes: 1 addition & 1 deletion src/features/discussions/components/delete-discussion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Trash } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { ConfirmationDialog } from '@/components/ui/dialog';
import { useNotifications } from '@/components/ui/notifications';
import { Authorization, ROLES } from '@/lib/authorization';
import { useNotifications } from '@/stores/notifications';

import { useDeleteDiscussion } from '../api/delete-discussion';

Expand Down
2 changes: 1 addition & 1 deletion src/features/discussions/components/update-discussion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Pen } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { Form, FormDrawer, Input, Textarea } from '@/components/ui/form';
import { useNotifications } from '@/components/ui/notifications';
import { Authorization, ROLES } from '@/lib/authorization';
import { useNotifications } from '@/stores/notifications';

import { useDiscussion } from '../api/get-discussion';
import {
Expand Down
2 changes: 1 addition & 1 deletion src/features/users/components/delete-user.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from '@/components/ui/button';
import { ConfirmationDialog } from '@/components/ui/dialog';
import { useNotifications } from '@/components/ui/notifications';
import { useUser } from '@/lib/auth';
import { useNotifications } from '@/stores/notifications';

import { useDeleteUser } from '../api/delete-user';

Expand Down
2 changes: 1 addition & 1 deletion src/features/users/components/update-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Pen } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { Form, FormDrawer, Input, Textarea } from '@/components/ui/form';
import { useNotifications } from '@/components/ui/notifications';
import { useUser } from '@/lib/auth';
import { useNotifications } from '@/stores/notifications';

import {
updateProfileInputSchema,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/api-client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Axios, { InternalAxiosRequestConfig } from 'axios';

import { useNotifications } from '@/components/ui/notifications';
import { env } from '@/config/env';
import { useNotifications } from '@/stores/notifications';

function authRequestInterceptor(config: InternalAxiosRequestConfig) {
if (config.headers) {
Expand Down
2 changes: 1 addition & 1 deletion src/testing/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import userEvent from '@testing-library/user-event';
import Cookies from 'js-cookie';
import { RouterProvider, createMemoryRouter } from 'react-router-dom';

import { AppProvider } from '@/providers/app';
import { AppProvider } from '@/app/main-provider';

import {
createDiscussion as generateDiscussion,
Expand Down

0 comments on commit 03e1c29

Please sign in to comment.