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) - [Frequently Asked Questions](FAQ.md)
- **Code Contribution** - **Code Contribution**
- [Set up freeCodeCamp locally](how-to-setup-freecodecamp-locally.md) - [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) - [Open a pull request](how-to-open-a-pull-request.md)
- [Work on coding challenges](how-to-work-on-coding-challenges.md) - [Work on coding challenges](how-to-work-on-coding-challenges.md)
- [Work on video challenges](how-to-help-with-video-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. 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 | | 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. 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. 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,96 +1,108 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<title>Contribution Guidelines | freeCodeCamp.org</title>
<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"
/>
<!--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"
/>
<head> <meta
<meta charset="UTF-8"> content="summary_large_image"
<title>Contribution Guidelines | freeCodeCamp.org</title> key="twitter:card"
<link rel="icon" href="images/branding/favicon.ico"> name="twitter:card"
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> />
<meta name="description" content="Description"> <meta
<meta name="viewport" content="https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920x1080-indigo.png"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> name="twitter:image:src"
<!--social--> />
<meta content='freeCodeCamp.org' name='og:title' /> <meta content="freeCodeCamp.org" name="twitter:title" />
<meta content='Learn to code — for free.' name='og:description' /> <meta content="Learn to code — for free." name="twitter: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' /> <link
<meta content='https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920x1080-indigo.png' rel="stylesheet"
name='twitter:image:src' /> href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
<meta content='freeCodeCamp.org' name='twitter:title' /> integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w=="
<meta content='Learn to code — for free.' name='twitter:description' /> crossorigin="anonymous"
/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" <!-- Theme -->
integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w==" <!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css"> -->
crossorigin="anonymous" /> <link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsify/themes/vue.css"
/>
<!-- Custom theme stylesheet -->
<link rel="stylesheet" href="_theme.css" />
</head>
<!-- Theme --> <body class="close">
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css"> --> <!-- Navigation (we are using a div, instead of nav to avoid conflict with docsify's nav) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify/themes/vue.css"> <div class="universal-nav">
<!-- Custom theme stylesheet --> <a class="app-name-link" data-nosearch="" href="/">
<link rel="stylesheet" href="_theme.css"> <img alt="freeCodeCamp.org" src="images/branding/primary_logo.svg" />
</a>
<a class="translations-link" data-nosearch="" href="/#/i18n">
Translations
</a>
</div>
</head> <!-- App with its own nav, search and sidebar -->
<div id="app"></div>
<script>
window.$docsify = {
homepage: 'index',
<body class="close"> relativePath: true,
<!-- Navigation (we are using a div, instead of nav to avoid conflict with docsify's nav) --> // common aliases
<div class='universal-nav'> alias: {
<a class="app-name-link" data-nosearch="" href="/"> '.*/_navbar.md': '/_navbar.md',
<img alt="freeCodeCamp.org" src="images/branding/primary_logo.svg"> '/i18n/?': '/_translations.md'
</a> },
<a class="translations-link" data-nosearch="" href="/#/i18n">
Translations
</a>
</div>
<!-- App with its own nav, search and sidebar --> // break the caching
<div id="app"></div> requestHeaders: {
<script> 'cache-control': 'no-cache'
window.$docsify = { },
homepage: 'index', // Cover Page
coverpage: true,
onlyCover: true,
relativePath: true, // Navigation
// common aliases autoHeader: true,
alias: { auto2top: true,
'.*/_navbar.md': '/_navbar.md',
'/i18n/?': '/_translations.md'
},
loadSidebar: true,
maxLevel: 2,
subMaxLevel: 2,
// break the caching topMargin: 90,
requestHeaders: {
'cache-control': 'no-cache'
},
// Cover Page // we do not use the built in navbar other then in mobile view
coverpage: true, loadNavbar: true,
onlyCover: true, mergeNavbar: true,
// Plugins
search: {
depth: 3,
noData: 'No results!',
placeholder: 'Search...'
},
// Navigation // Add languages here for message box translations
autoHeader: true, flexibleAlerts: {
auto2top: true,
loadSidebar: true,
maxLevel: 2,
subMaxLevel: 2,
topMargin: 90,
// we do not use the built in navbar other then in mobile view
loadNavbar: true,
mergeNavbar: true,
// Plugins
search: {
depth: 3,
noData: 'No results!',
placeholder: 'Search...'
},
// Add languages here for message box translations
'flexibleAlerts': {
note: { note: {
label: { label: {
'/i18n/chinese/': '注意', '/i18n/chinese/': '注意',
@ -125,89 +137,104 @@
} }
}, },
pagination: { pagination: {
crossChapter: true crossChapter: true
}, },
remoteMarkdown: { remoteMarkdown: {
tag: 'remote-markdown-url', tag: 'remote-markdown-url'
}, },
plugins: [ plugins: [
function (hook, vm) { function (hook, vm) {
hook.beforeEach(function (markdown) {
// -- ignore the translations list page
if (vm.route.file === '_translations.md') return markdown;
hook.beforeEach(function (markdown) { // -- add "Update this translation" link for all i18n language pages
if (vm.route.path.search('i18n') !== -1) {
// -- ignore the translations list page var dynamicText =
if (vm.route.file === '_translations.md') return markdown; '[<i class="far fa-edit"></i> Update the translation](' +
'https://translate.freecodecamp.org/contributing-docs' +
// -- add "Update this translation" link for all i18n language pages ') or [visit the English version](' +
if (vm.route.path.search('i18n') !== -1) { '/' +
var dynamicText = vm.route.path.split('/').pop() +
'[<i class="far fa-edit"></i> Update the translation](' + ') of this guide to update instructions.';
'https://translate.freecodecamp.org/contributing-docs' +
') or [visit the English version](' +
'/' + vm.route.path.split('/').pop() +
') of this guide to update instructions.';
}
// -- add "Edit this guide on GitHub" link for all English language pages
if (vm.route.path.search('i18n') === -1) {
var dynamicText =
'[<i class="far fa-edit"></i> Edit this guide on GitHub](' +
'https://github.com/freeCodeCamp/freeCodeCamp/blob/main/docs/' +
vm.route.file +
')';
}
// 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++) {
var item = elements[counter];
item.dir = "rtl";
} }
};
if (vm.route.path.search('Arabic') === -1 && vm.route.path.search('Hebrew') === -1) { // -- add "Edit this guide on GitHub" link for all English language pages
for (var counter = 0, elements = document.getElementsByClassName("markdown-section"); counter < elements.length; counter++) { if (vm.route.path.search('i18n') === -1) {
var item = elements[counter]; var dynamicText =
item.dir = "ltr"; '[<i class="far fa-edit"></i> Edit this guide on GitHub](' +
'https://github.com/freeCodeCamp/freeCodeCamp/blob/main/docs/' +
vm.route.file +
')';
} }
};
return ( // Used from https://github.com/ckoliber/docsify-rtl/blob/master/build/docsify-rtl.js,
markdown + // Currently this is a hack because can't use the plugin as is.
'\n----\n' + if (
dynamicText vm.route.path.search('Arabic') !== -1 ||
); vm.route.path.search('Hebrew') !== -1
// -- do not add logic below this line -- ) {
for (
var counter = 0,
elements =
document.getElementsByClassName('markdown-section');
counter < elements.length;
counter++
) {
var item = elements[counter];
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++
) {
var item = elements[counter];
item.dir = 'ltr';
}
}
} return markdown + '\n----\n' + dynamicText;
</script> // -- do not add logic below this line --
});
}
]
};
</script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<!-- Theme --> <!-- Theme -->
<!-- <script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0"></script> --> <!-- <script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0"></script> -->
<!-- Plugins --> <!-- Plugins -->
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-copy-code@2"></script> <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@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/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-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://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"
integrity="sha512-RXf+QSDCUQs5uwRKaDoXt55jygZZm2V++WUZduaU/Ui/9EGp3f/2KZVahFZBKGH0s774sd3HmrhUy+SgOFQLVQ=="
crossorigin="anonymous"></script>
</body>
<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>
</html> </html>