feat(docs): add codebase best practices (#42591)

* feat(docs): add codebase best practices

* add anchor to linter setup

* add typescript highlighting, and basic idea

* apply silly standards

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* add stateful class component example

Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>

* encourage use of functional components

* split filename extension

Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>

* add comments for future me

* add redux structure

* add tsx to prism imports

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>
This commit is contained in:
Shaun Hamilton
2021-07-06 15:30:09 +01:00
committed by GitHub
parent ceb0b0f403
commit 2f729976fc
4 changed files with 313 additions and 150 deletions

View File

@ -3,6 +3,7 @@
- [Frequently Asked Questions](FAQ.md)
- **Code Contribution**
- [Set up freeCodeCamp locally](how-to-setup-freecodecamp-locally.md)
- [Codebase best practices](codebase-best-practices.md)
- [Open a pull request](how-to-open-a-pull-request.md)
- [Work on coding challenges](how-to-work-on-coding-challenges.md)
- [Work on video challenges](how-to-help-with-video-challenges.md)

View File

@ -0,0 +1,135 @@
# Codebase Best Practices
## General JavaScript
In most cases, our [linter](how-to-setup-freecodecamp-locally?id=follow-these-steps-to-get-your-development-environment-ready) will warn of any formatting which goes against this codebase's preferred practice.
It is encouraged to use functional components over class-based components.
## Specific TypeScript
### Migrating a JavaScript File to TypeScript
#### Retaining Git File History
Sometimes changing the file from `<filename>.js` to `<filename>.ts` (or `.tsx`) causes the original file to be deleted, and a new one created, and other times the filename just changes - in terms of Git. Ideally, we want the file history to be preserved.
The best bet at achieving this is to:
1. Rename the file
2. Commit with the flag `--no-verify` to prevent Husky from complaining about the lint errors
3. Refactor to TypeScript for migration, in a separate commit
> [!NOTE]
> Editors like VSCode are still likely to show you the file has been deleted and a new one created. If you use the CLI to `git add .`, then VSCode will show the file as renamed in stage
### Naming Conventions
#### Interfaces and Types
For the most part, it is encouraged to use interface declarations over type declarations.
React Component Props - suffix with `Props`
```typescript
interface MyComponentProps {}
// type MyComponentProps = {};
const MyComponent = (props: MyComponentProps) => {};
```
React Stateful Components - suffix with `State`
```typescript
interface MyComponentState {}
// type MyComponentState = {};
class MyComponent extends Component<MyComponentProps, MyComponentState> {}
```
Default - object name in PascalCase
```typescript
interface MyObject {}
// type MyObject = {};
const myObject: MyObject = {};
```
<!-- #### Redux Actions -->
<!-- TODO: Once refactored to TS, showcase naming convention for Reducers/Actions and how to type dispatch funcs -->
## Redux
### Action Definitions
```typescript
enum AppActionTypes = {
actionFunction = 'actionFunction'
}
export const actionFunction = (
arg: Arg
): ReducerPayload<AppActionTypes.actionFunction> => ({
type: AppActionTypes.actionFunction,
payload: arg
});
```
### How to Reduce
```typescript
// Base reducer action without payload
type ReducerBase<T> = { type: T };
// Logic for handling optional payloads
type ReducerPayload<T extends AppActionTypes> =
T extends AppActionTypes.actionFunction
? ReducerBase<T> & {
payload: AppState['property'];
}
: ReducerBase<T>;
// Switch reducer exported to Redux combineReducers
export const reducer = (
state: AppState = initialState,
action: ReducerPayload<AppActionTypes>
): AppState => {
switch (action.type) {
case AppActionTypes.actionFunction:
return { ...state, property: action.payload };
default:
return state;
}
};
```
### How to Dispatch
Within a component, import the actions and selectors needed.
```tsx
// Add type definition
interface MyComponentProps {
actionFunction: typeof actionFunction;
}
// Connect to Redux store
const mapDispatchToProps = {
actionFunction
};
// Example React Component connected to store
const MyComponent = ({ actionFunction }: MyComponentProps): JSX.Element => {
const handleClick = () => {
// Dispatch function
actionFunction();
};
return <button onClick={handleClick}>freeCodeCamp is awesome!</button>;
};
export default connect(null, mapDispatchToProps)(MyComponent);
```
<!-- ### Redux Types File -->
<!-- The types associated with the Redux store state are located in `client/src/redux/types.ts`... -->
## Further Literature
- [TypeScript Docs](https://www.typescriptlang.org/docs/)
- [TypeScript with React CheatSheet](https://github.com/typescript-cheatsheets/react#readme)

View File

@ -19,7 +19,7 @@ We also support Windows 10 via WSL2, which you can prepare by [reading this guid
Some community members also develop on Windows 10 natively with Git for Windows (Git Bash), and other tools installed on Windows. We do not have official support for such a setup at this time, we recommend using WSL2 instead.
**Prerequisites:**
#### Prerequisites:
| Prerequisite | Version | Notes |
| --------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------- |
@ -42,7 +42,7 @@ npm -v
Once you have the prerequisites installed, you need to prepare your development environment. This is common for many development workflows, and you will only need to do this once.
**Follow these steps to get your development environment ready:**
##### Follow these steps to get your development environment ready:
1. Install [Git](https://git-scm.com/) or your favorite Git client, if you haven't already. Update to the latest version; the version that came bundled with your OS may be outdated.

View File

@ -1,42 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>Contribution Guidelines | freeCodeCamp.org</title>
<link rel="icon" href="images/branding/favicon.ico">
<link rel="icon" href="images/branding/favicon.ico" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="description" content="Description" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<!--social-->
<meta content='freeCodeCamp.org' name='og:title' />
<meta content='Learn to code — for free.' name='og:description' />
<meta content='https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920x1080-indigo.png' property='og:image' />
<meta content="freeCodeCamp.org" name="og:title" />
<meta content="Learn to code — for free." name="og:description" />
<meta
content="https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920x1080-indigo.png"
property="og:image"
/>
<meta content='summary_large_image' key='twitter:card' name='twitter:card' />
<meta content='https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920x1080-indigo.png'
name='twitter:image:src' />
<meta content='freeCodeCamp.org' name='twitter:title' />
<meta content='Learn to code — for free.' name='twitter:description' />
<meta
content="summary_large_image"
key="twitter:card"
name="twitter:card"
/>
<meta
content="https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920x1080-indigo.png"
name="twitter:image:src"
/>
<meta content="freeCodeCamp.org" name="twitter:title" />
<meta content="Learn to code — for free." name="twitter:description" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w=="
crossorigin="anonymous" />
crossorigin="anonymous"
/>
<!-- Theme -->
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css"> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify/themes/vue.css">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsify/themes/vue.css"
/>
<!-- Custom theme stylesheet -->
<link rel="stylesheet" href="_theme.css">
<link rel="stylesheet" href="_theme.css" />
</head>
</head>
<body class="close">
<body class="close">
<!-- Navigation (we are using a div, instead of nav to avoid conflict with docsify's nav) -->
<div class='universal-nav'>
<div class="universal-nav">
<a class="app-name-link" data-nosearch="" href="/">
<img alt="freeCodeCamp.org" src="images/branding/primary_logo.svg">
<img alt="freeCodeCamp.org" src="images/branding/primary_logo.svg" />
</a>
<a class="translations-link" data-nosearch="" href="/#/i18n">
Translations
@ -47,7 +62,6 @@
<div id="app"></div>
<script>
window.$docsify = {
homepage: 'index',
relativePath: true,
@ -57,7 +71,6 @@
'/i18n/?': '/_translations.md'
},
// break the caching
requestHeaders: {
'cache-control': 'no-cache'
@ -67,7 +80,6 @@
coverpage: true,
onlyCover: true,
// Navigation
autoHeader: true,
auto2top: true,
@ -90,7 +102,7 @@
},
// Add languages here for message box translations
'flexibleAlerts': {
flexibleAlerts: {
note: {
label: {
'/i18n/chinese/': '注意',
@ -130,14 +142,12 @@
},
remoteMarkdown: {
tag: 'remote-markdown-url',
tag: 'remote-markdown-url'
},
plugins: [
function (hook, vm) {
hook.beforeEach(function (markdown) {
// -- ignore the translations list page
if (vm.route.file === '_translations.md') return markdown;
@ -147,7 +157,8 @@
'[<i class="far fa-edit"></i> Update the translation](' +
'https://translate.freecodecamp.org/contributing-docs' +
') or [visit the English version](' +
'/' + vm.route.path.split('/').pop() +
'/' +
vm.route.path.split('/').pop() +
') of this guide to update instructions.';
}
@ -162,32 +173,44 @@
// Used from https://github.com/ckoliber/docsify-rtl/blob/master/build/docsify-rtl.js,
// Currently this is a hack because can't use the plugin as is.
if (vm.route.path.search('Arabic') !== -1 || vm.route.path.search('Hebrew') !== -1) {
for (var counter = 0, elements = document.getElementsByClassName("markdown-section"); counter < elements.length; counter++) {
if (
vm.route.path.search('Arabic') !== -1 ||
vm.route.path.search('Hebrew') !== -1
) {
for (
var counter = 0,
elements =
document.getElementsByClassName('markdown-section');
counter < elements.length;
counter++
) {
var item = elements[counter];
item.dir = "rtl";
item.dir = 'rtl';
}
}
};
if (vm.route.path.search('Arabic') === -1 && vm.route.path.search('Hebrew') === -1) {
for (var counter = 0, elements = document.getElementsByClassName("markdown-section"); counter < elements.length; counter++) {
if (
vm.route.path.search('Arabic') === -1 &&
vm.route.path.search('Hebrew') === -1
) {
for (
var counter = 0,
elements =
document.getElementsByClassName('markdown-section');
counter < elements.length;
counter++
) {
var item = elements[counter];
item.dir = "ltr";
item.dir = 'ltr';
}
}
};
return (
markdown +
'\n----\n' +
dynamicText
);
return markdown + '\n----\n' + dynamicText;
// -- do not add logic below this line --
});
}
]
}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
@ -201,13 +224,17 @@
<script src="https://cdn.jsdelivr.net/npm/docsify-copy-code@2"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-plugin-flexible-alerts@1"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-jsx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-typescript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-tsx.min.js"></script>
<script src="https://unpkg.com/docsify-pagination/dist/docsify-pagination.min.js"></script>
<script src="https://unpkg.com/docsify-remote-markdown/dist/docsify-remote-markdown.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js"
<script
src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js"
integrity="sha512-RXf+QSDCUQs5uwRKaDoXt55jygZZm2V++WUZduaU/Ui/9EGp3f/2KZVahFZBKGH0s774sd3HmrhUy+SgOFQLVQ=="
crossorigin="anonymous"></script>
</body>
crossorigin="anonymous"
></script>
</body>
</html>