[![](https://img.shields.io/badge/feature/slices-1.0-blue)](https://featureslices.dev/v1.0)
In that examples TypeScript used as common language, but you free to choose.
- Authmenow
Work in progress
FeatureSlices define your frontend application structure as easy and convenient.
- All files and directories has name in param-case. Because it lowers cognitive load to select case for concrete file. One case for all files. File structure should define what type file have. Example:
component-name.tsx
Source code of the application contained in src
:
api
features
lib
pages
ui
This is reusable functions or classes to make requests to the server. It is can be REST-like, GraphQL, WebSocket, etc.
Code in the api
can be partially generated (swagger or OpenAPI).
What is API:
src/
api/
api-name.ts
request.ts
For example api-name
is a group of requests, having a common purpose. More that one namespace can be created.
Not recommended to add src/api/index.ts
file, because different api should be separated one from one. If you import api/user
, you see just requests related to user. But if you import api
, you can be drowned in tons of requests for application.
Example:
src/
api/
post.ts
feed.ts
session.ts
request.ts
In this example: post
, feed
, session
is a namespaced requests, but request
is a base function, to make requests.
Example post.ts
:
import { request } from './request'
interface PostNew {
title: string;
content: string;
}
export const postsList = () => request({ method: "GET", path: "/posts" })
export const postCreate = (post: PostNew) => request({ method: "POST", path: "/posts", body: post })
export const postGet = (postId: string) => request({ method: "GET", path: `/posts/${postId}` })
Structure and shape of each method on your own. But it should be consistent with all other requests.
Recommended to setup imports in your project, to allow import from root (
src/
) or use some aliases.
From api
source code you can import only src/lib
and package dependencies, nothing more.
Good:
import { postsList } from 'api/post'
// or
import * as posts from '@api/post'
Bad:
import { posts } from '../../../api'
It is list of your reusable code and shared state, groupped by one idea or entity.
Structure:
src/
features/
feature-name/
components/
models/
templates/
__tests__/
index.ts
readme.md
stories.tsx
- One feature can depends on another feature. But not recommended to do it too often.
- Do not make cyclic dependencies, when feature A depends on feature B, feature B on C, and C depends on A. Prefer tree over graph.
- Export all entities from your feature through
index.ts
. It is helps to support your feature easier, because through that public interface, you can known what is used, and what can be refactored without stress. - Write tests and documentation for feature, to ease understanding how to use it.
- You can have as many features as you need, but no more.
Feature structure:
components/
here should be atoms, molecules and organisms of your features. All should be related to single idea or entity.models/
here place business-logic, that can be reused (instantiated) or shared between other features and pages. Any shared state also here.templates/
it is a place for atomic design templates for pages. Templates also should be relate to one idea or entity.__tests__/
ortests/
— any name of the directory to write integration or smoke tests.index.ts
— public interface of your feature, all entities and units should be imported through this file. Import from inside is restricted.- Bad:
import {Some} from 'features/example/components/some
- Good:
import {Some} from 'features/example'
- Bad:
readme.md
— documentation of your featurestories.tsx
— if you use storybook
Common utils
or helper
code should be groupped by meaning and placed at its own library in this directory.
Example:
You have two functions: parse a string to a username, and parse an array of strings to list of usernames. Instead of placing a first function to a
lib/string
, and a second to alib/array
, group it by meaning: username. The best place:lib/username
Structure
src/
lib/
lib-name/
index.ts
readme.md
tests.ts
- Spend some time to think about name for library and internal structure.
- Write documentation and test for your library.
- Treat to library as it is writen to publish to npm. It should have good coverage, easy to understand documentation.
What is page? Page is a component rendered on specific url. Or another router's unit, that separates one component of another.
Structure:
src/
pages/
page-name/
index.tsx
model.ts
another/
nested/
index.tsx
model.ts
index.tsx
model.ts
paths.ts
routes.ts
index.ts
- Each page is a pair: component and model files.
- In component file you should place page component, also you can place here another unique components that makes page rich.
- Page component's name should be built from page's route with suffix
Page
. Example:/register/confirm
—RegisterConfirmPage
- In model file you should place logic for that page.
- Extra files:
paths.ts
- contains map from route name to route pathroutes.ts
- contains common router configurationindex.ts
- contains render for routes configuration
- You can merge
paths
,routes
andindex
in a single file, if you don't need it separated.
Example component file:
// src/pages/register/confirm/index.ts
import * as React from 'react'
import { Just } from '@app/ui'
import * as model from './model'
export const RegisterConfirmPage: React.FC = () => {
model.useLogic();
return (
<RegisterConfirmContent>
Hello from registration confirmation page
</RegisterConfirmContent>
)
}
const RegisterConfirmContent: React.FC = ({ children }) => (
<Just example>
{children}
</Just>
)
Example model file:
// src/pages/register/confirm/model.ts
import * as React from 'react'
export const useLogic = () => {
React.useEffect(() => {
console.log("Handle on mount here")
}, [])
}
Example paths file:
- Functions is used here, to consistent create path with parameters and without.
- You should use this functions in all application to redirect or create links. It is helps when you want to change path.
// src/pages/paths.ts
export const paths = {
home: () => `/`,
login: () => `/login`,
register: () => `/register`,
registerConfig: (code: string) => `/register/confirm/${code}`,
}
Example routes file:
// src/pages/routes.ts
import { paths } from './paths'
import { HomePage } from './home'
import { LoginPage } from './login'
import { RegisterPage } from './register'
import { RegisterConfirmPage } from './register/confirm'
import { Error404Page } from './error404'
// react-router-config example
export const routes = [
{
path: paths.home(),
exact: true,
component: HomePage,
},
{
path: paths.login(),
exact: true,
component: LoginPage,
},
{
path: paths.register(),
exact: true,
component: RegisterPage,
},
{
path: paths.registerConfig(':code'), // pass path parameter
exact: true,
component: RegisterConfirmPage,
},
{
path: '*',
component: Error404Page,
},
]
Example pages index file:
// src/pages/index.ts
import { renderRoutes } from 'react-router-config';
import { routes } from './routes'
export const Pages = () => renderRoutes(routes)
Now Pages
component can be easily used in Application
to bootstrap routing.
Example application file:
// src/application.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { GlobalTemplate } from '@app/ui'
import { Pages } from './pages'
const Application = () => (
<GlobalTemplate>
<Pages />
</GlobalTemplate>
)
ReactDOM.render(
<BrowserRouter>
<Application />
</BrowserRouter>,
document.querySelector("#root"),
)
TO DO