Based on Airbnb JavaScript Style Guide
- Enforcing these Standards
- Naming
- Coding
- Formatting
- Comments
- TypeScript
- CSS
- GIT
- Node.js
- Recommended Frameworks
- Recommended Libraries
- Definition of Done Checklist
- Development environment
In order to enforce these standards in your code, it is required to use ESLint and Prettier, with our accompanying @mediamonks/eslint-config and @mediamonks/prettier-config configurations, created around these standards.
Choosing good names is critical to creating code that is easy to use and easy to understand. You should always take the time to think about whether you have chosen the right name for something, especially if it is part of the public API.
All code, names, comments, etc. must be in English.
Tip: install a spell checker in your IDE to avoid typos. VSCode or WebStorm
Do not reuse names. Do not use names that can mean multiple things. Always use the same name for the same thing. So use the same name within the JavaScript, CSS and HTML for the same thing. Align naming with the whole team: Backend, Frontend, UX, Design, PM, client, etc.
For big projects with their own jargon it could help to create a dictionary.
Names must be descriptive for the working or usage of the class, method or variable.
For example: a button must always end with Button
.
MenuButton
a button used in the menuButtonMenu
a menu with buttonsMenuButtonIcon
an icon in the button of the menu
There is no limit for the length of a name, so prefer a long name which is clear and descriptive than a short name which is not clear.
A name should make sense within its context and should not have unnecessary information for that
context. For example a variable that holds the name of a user can be named name
within a User
context. However if you need to hold the name of a user in another place, userName
might be a
better name. Adding user
within a User
context (user.userName
) is redundant and should be
avoided.
Avoid them as a general rule. For example, calculateOptimalValue()
is a better method name than
calcOptVal()
. Being clear is more important than minimizing keystrokes. If you don't abbreviate,
developers won't have to remember whether you shortened a word like qualified to qual
or qlfd
.
Abbreviations that are part of the HTML, CSS and/or JavaScript API are allowed, like:
url
for Uniform Resource Locatoruri
for Uniform Resource Identifiersrc
for sourcedom
for Document Object Modelimg
for image
You should only use these abbreviations within the same context. So only use img
if you refer to
an HTML Image Tag. (<img>
)
We have standardized on a few abbreviations that are allowed to use:
app
for applicationargs
for argumentsauto
for automatic, as inautoLayout
bin
for binarycta
for "Call to action". It's the part of the application that the user needs to click in order to take the action you want them to take.fps
for frames per secondid
for identifier. Please note that 'd' should be written in lowercase when used in combination with another word, likeuserId
.info
for information, as inGridRowInfo
init
for initializelib
for librarymax
for maximum, as inmaxHeight
min
for minimum, as inminWidth
param
orparams
for parameter or parameters respectivelyprop
orprops
for property or properties respectivelyref
for referencetemp
for temporaryui
for user interfaceutil
orutils
for utility or utilities, as inStringUtils
// bad
prevButton.addEventListener('click', onPrevClick);
// good
previousButton.addEventListener('click', onPreviousClick);
// bad
buttons.forEach((elm) => {
//...
});
// better
buttons.forEach((element) => {
// prettier should add brackets by default
//...
});
// best
buttons.forEach((button) => {
//...
});
Should always have a singular name, unless the object is only used to hold other values and
these other value are more important then the object itself, like Props
, Settings
or Options
.
For example: MyComponentProps
, ProductionSettings
or CalendarOptions
.
Or other kind of lists should have a plural name or end with List
or Collection
, like
userList
.
If a folder holds multiple files, but all related to one main type, it should have a singular name. If it holds multiple main files of a type, it should have a plural name.
For example, the folder page
contains a single page, with maybe some helper files. The folder
pages
contains multiple pages.
Prefer using a verb as a name to indicate it will do something. Like render
, open
or getData
.
All non-functions should have a noun as a name, not a verb.
Although getters and setters are technically functions, they are used as if they are properties. Therefore, their name should be a noun.
Some frameworks support
computed
properties. They work like getters, so their name should be a noun as well.
Should start with is
, has
, will
or should
. Like isValid
or hasValues
.
To indicate that a function or property is used as a callback or handler you can start the name with
on
, like: onClick
, onLoadComplete
, onResize
. Do not postfix the name with handler
since
this is redundant when there is already an on
.
Also note that a name of only on
+ event name
might not be descriptive enough, depending on the
context. Using a more descriptive name is recommended.
// bad
function complete() {
//...
}
// bad
function handleComplete() {
//...
}
// bad
function completeHander() {
//...
}
// bad
function onCompleteHander() {
//...
}
// good
function onComplete() {
//...
}
// better
function onImageLoadComplete() {
//...
}
Avoid negations. “Don’t ever not avoid negative logic”. Prefer isShown
over isHidden
or
isEnabled
over isDisabled
. Do not use names like notEditable
.
We are not using prefixes for any name. Interfaces should follow the exact naming rules as classes,
and should not use the I
or any other pre- or postfix.
If the usages of the generic is obvious, then naming that generic T
is sufficient. As long as the
usage is clear you can use U
, V
etc. for any following generic.
If the usage is not obvious, you should use a more descriptive name. The same naming rules as for classes will apply then.
// bad, prefix generics with T
class Response<TResponseData> {
public data: TResponseData;
}
// good, use common abbreviations like T(ype), K(ey), V(alue), P(roperty) etc.
type Partial<T> = { [P in keyof T]?: T[P] };
// better, add more semantic context by extending the type
type ResponseData = Record<string, unknown>;
class Response<T extends ResponseData> {
public data: T;
}
Depending on the use case there is a choice to implement an interface or type alias, but be aware using types impacts compilation performance.
// bad, you should not prefix interfaces with I
interface IResponse {
//...
}
// bad, prefix types with T
type TResponse = {
//...
};
// good, no prefix
interface Response {
//...
}
// good, no prefix
type Response = {
//...
};
PascalCase Every individual word start with an upper case character, no underscores, no dashes.
camelCase Starts with a lower case character, every following individual word start with an upper case character, no underscores, no dashes.
When a private property has a public getter and/or setter, it's recommended to prefix the private
property with an underscore (_
) to prevent naming conflicts.
Note that prefixing a property name with an underscore is not allowed by the ESLint configuration. So in order to do this you need to disable this linting rule for this line.
// eslint-disable-next-line @typescript-eslint/naming-convention
private _isActive: boolean = false;
public get isActive(): boolean {
return this._isActive;
}
SNAKE_UPPER_CASE Only use upper case characters, individual words must be separated with an underscore.
Abbreviations and acronyms should be treated as words, which means only the first character will be capitalized for camelCase and PascalCase.
const jsonApiSdkUrl = new JsonApiSdkUrl();
If a file contains only one class, type or object, or when there is one main class, type or object with some helper classes, types or objects, the file should have the same name, in the same casing, as that (main) class, type or object. If a file contains a class, but only an instance of that class is exported, the file should have the same name as the class, but written in camelCase.
If a file holds multiple classes, types and/or objects, and they are all more or less equal in importance, the file should have a name that describes all the classes, types and/or objects, written in camelCase.
JavaScript only
TypeScript only
JavaScript with JSX syntax. If a file has the .jsx
extension, it must contain JSX code.
TypeScript with JSX syntax. If a file has the .tsx
extension, it must contain JSX code.
Every function or class should do one thing (and do it good). If it needs to do more than one thing, split it up. Keep your files, classes and functions small. It’s okay to have a file with just a single line.
Prefer writing pure functions, which means they do not manipulate the input arguments or reference/manipulate global state. This makes your code better scalable and testable.
Prefer to use arrow functions when this
should be bound to the outside context, and not to the
function itself. Arrow functions do not have their own context, so it will lexically go up a scope,
and use the value of this
in the scope in which it was defined.
const human = {
message: 'Hello, World!',
say() {
setTimeout(() => {
console.log(this.message);
}, 1000);
}
};
Also arrow functions are good in case of inline callbacks, which are most often found in map
,
filter
, reduce
methods in order to improve code readability.
[1, 2, 3]
.map((x) => x * 5)
.filter((x) => x < 10)
Prefer to use keyword function
to create functions in cases:
- Function is at top level
- Function contains complex logic
- If there are no advantages to using the arrow function
Benefits of using the keyword function
instead of arrow function:
- Function is not anonymous and has a name, so you get a better stack trace in case of an error
- Hoisting allows a function to be used before it is declared, so the order is not important
Example of creating a function using the function
keyword:
function secondsToDurationFormat(value: number): string {
const days = Math.floor((value / 86400) % 365);
const hours = Math.floor((value / 3600) % 24);
const minutes = Math.floor((value / 60) % 60);
const seconds = Math.floor(value % 60);
return `P${days}DT${hours}H${minutes}M${seconds}S`;
};
Write code that is reusable, scalable and testable.
- Do not copy code to another place.
- Avoid using the same string twice in a project.
- Move shared logic to a shared place.
- Make sure you do not have to adapt changes in multiple places.
See https://en.wikipedia.org/wiki/Magic_number_(programming)
Every switch
must have a default
. If there is no need to handle the default
, either throw an
Error
or add a comment that the default is explicitly ignored.
switch (state) {
case 1: {
// ....
break;
}
case 2: {
// ....
break;
}
default: {
throw new Error(`Unhandled value for state '${state}'`);
}
}
throw an error for things that should not occur
switch (state) {
case 1: {
// ....
break;
}
case 2: {
// ....
break;
}
default: {
// do nothing
break;
}
}
add a comment that the default is explicit ignored
Adding the comment makes it clear the developer did not forget to implement the default.
All code within a project should have the same formatting. To enforce that we use Prettier.
Reconsider if your code really needs comments. Code should be self explaining. Don't add comments that already tell what code does (by just repeating) or that are needed in order for the code to make any sense. Code that needs such comments is probably bad. Instead, it is recommended to write the intention of the code.
If you're new to a project or piece of code, when going over it, and trying to understand it, add explanatory comments on things that took you some time to figure out.
If someone else asks during a code review why something is done a certain way, see if you can answer it with a code comment instead of a reply in the review tool (when applicable).
Since regular expressions can be hard to read, they should have a comment that indicate what they do. Especially when they are complex.
Don't leave commented out code into project. You can always find it back in the version control system. If for some reason you want to keep commented out code in the project, add a comment explaining why it is commented out.
If something needs to be changed or refactored later, add a // TODO
comment to indicate what the
issue is.
If you refactor code that has comments, please check afterwards if the comments still make sense or need to be updated.
Use TypeScript in strict mode and do not allow native JavaScript. Therefore everything must be typed. It’s not needed to type something when TypeScript can resolve the type.
Keep your code as strict as possible, so keep all functions and properties private
unless they
have to be protected
or public
.
Explicitly define whether your constructor
functions are for internal or external interfaces.
// bad
constructor() {
//...
}
// good
public constructor() {
//...
}
In order to be as strict as possible, every property should be set to readonly unless it should be writable.
Always prefer ReadonlyArray
over a regular Array
unless it must be possible to modify the Array.
They provide an implicit definition of children
, which can be misleading to components which do
not accept children
. It is encouraged to instead either define children?: ReactNode
on your prop
type or wrap your prop type in the PropsWithChildren
type util.
type MonkComponentProps = {
// other properties
children?: ReactNode;
};
function MonkComponent({ children }: MonkComponentProps): ReactElement {
//...
}
// with `PropsWithChildren`
type MonkComponentProps = {
// other properties
};
function MonkComponent({ children }: PropsWithChildren<MonkComponentProps>): ReactElement {
//...
}
As they are built-ins, they do not allow for custom generics and thus do not support Generic Components.
In addition to its own components, React allows returning primitive values from components. While this is nice, it sometimes might be a mistake to return a primitive, instead of a valid component; this can most commonly happen when your component renders itself conditionally (e.g. using logical operators).
TypeScript return type inference would inherently "hide" this potential bug from us, because of this we strongly encourage to type the return types of components:
function MonkComponent(): ReactElement {
//...
}
We recommend checking out https://react-typescript-cheatsheet.netlify.app for more in-depth and advanced usages of TypeScript within React
Arrays should be typed as Array<T>
rather than T[]
for consistency.
Although return types are optional for TypeScript (TypeScript is very good at figuring out what the return type of a function is) it is absolutely recommended to explicitly add a return type for public (API) functions.
Adding a return type improves readability and can also help to prevent bugs. Accidentally returning the wrong type would not cause an error in the function declaration if there is no explicit return type set.
This document is written to capture the conventions of writing styles for our projects. Many of the rules will be or are part of our linter and prettier settings. Due to variety of our projects and methods there might be a need for a more detailed approach per situation. This might also impact how code is being organized, structured on an architectonic level. Therefore, this part of the conventions will focus on the rules that are applicable to the majority of our implementation methods.
It's important to note that client and project requirements always undo choices made in these guidelines
Names should be meaningful and not presentational:
/* Bad */
/* presentational with color and position reference */
.button-orange {
}
.left-block {
}
/* meaningless */
.yee-u88 {
}
Some good examples would be:
/* Good */
.video {
}
.gallery {
}
Selectors are placed on separate lines:
/* Good */
h1,
h2,
h3 {
text-transform: uppercase;
}
/* Bad */
h1,
h2,
h3 {
text-transform: uppercase;
}
Don't use ids as a selector (unless there is no other option)
Don't combine element types with classes and ids. Preferably don't style on elements at all. Reasonable exceptions are in a base reset or when styling elements that can not be targeted in another way (e.g. elements that are generated from a wysiwyg editor or coming from external sources).
/* Recommended */
.overview {
}
/* Encouraged exceptions */
.wysiwyg {
h1,
h2,
h3 {
text-transform: uppercase;
}
}
/* Bad */
ul.overview {
}
.overview li {
}
Rulesets are separated by empty lines
/* Good */
.foo {
color: #f00;
}
.bar {
color: #0f0;
}
.baz {
color: #00f;
}
/* Bad */
.foo {
color: #f00;
}
.bar {
color: #0f0;
}
.baz {
color: #00f;
}
Properties have their own unique line
/* Good */
.gallery {
background: #000;
color: #fff;
}
/* Bad */
.gallery {
background: #000;
color: #fff;
}
It is ok to use multiple lines for styles if it increases legibility. This is often the case when using complex background styles or named grid-areas.
.gallery {
background-color: white;
background-image: radial-gradient(midnightblue 9px, transparent 10px), repeating-radial-gradient(midnightblue
0, midnightblue 4px, transparent 5px, transparent 20px, midnightblue 21px, midnightblue 25px, transparent
26px, transparent 50px);
background-size: 30px 30px, 90px 90px;
background-position: 0 0;
}
The use of z-index can cause unwanted side effects that can be tricky to debug and manage. To avoid
relying on z-index: 99999;
we use an
scss function in
most of our frameworks, which is part of seng-scss. The indices of the list items will update when
new items are added. This will help increase the maintainability.
// Maintained in variables.scss
$zLayout: default header overlay;
.overlay {
inset: 0;
position: fixed;
z-index: zindex($zLayout, overlay);
}
It's recommended to use this $zLayout
in situations where its z-index needs to be compared to
other components. For z-axis hierarchies within components (with a new stacking context) a numerical
approach is allowed (and perhaps recommended) or create a local variable:
.overlay {
$overlayZLayout: default top;
.visual {
position: relative;
z-index: zindex($overlayZLayout, top);
}
}
For environments that use a css-in-js solution it's recommended to use a similar approach. In the following example a numeric enum is used:
// Using a numeric enum to create a z-index map
enum ZIndex {
Navigation = 1,
// Value for 'Dialog' will automatically be set to 2 by the Typescript compiler
Dialog,
}
export const Navigation = styled.nav`
...
z-index: ${ZIndex.Navigation}
`;
export const Dialog = styled.dialog`
...
z-index: ${ZIndex.Dialog}
`;
We use GitFlow for our branching strategy.
Branch names should adhere to the following structure:
bugfix
orfeature
+/
+{TICKET_KEY}-{TICKET_TITLE}
e.g.bugfix/AB-1234-accessibility-homepage-contrast
Some projects will automatically deploy to an environment when pushing commits into a specific
branch. Which branch is connected to which environment should be written in the README.md
of the
project.
Please read: https://chris.beams.io/posts/git-commit/
- If possible, add the key of the corresponding ticket in the commit message.
- Make sure it is always clear why a change was made.
- Only commit one feature at the time.
- Always check your commit in details to avoid committing wrong code.
Always let someone else review your code in the Pull/Merge Request. Make sure all code review comments are resolved, before you merge it! Please read our Pull Request Guidelines.
When setting up Node.js on a new machine, it is strongly recommended to use a versioning tool such as nvm. There are often times when we must switch between versions for testing or for certain features. Tools such as nvm make this easy and simple.
You must always use the Active LTS (Long-term support) version of Node.js as it is considered stable and will ensure that you don't encounter any unexpected issues. Furthermore, when creating a new project or tool, it must always target the Active LTS version, unless there is a good reason not to e.g. an experimental tool or long-term project. To find out the current LTS version, you can use a tool such as nvm or simply check the Node.js website.
The Node.js ecosystem provides many choices for package managers, all with their own benefits and trade-offs. At Media.Monks, we decided to stick with what is included in Node.js - npm. For most cases its feature-set is sufficient and its performance has been gradually improving. Being shipped together with Node.js also means that developers have to worry less about their package manager, as long as their Node.js version is up to date with our recommendations.
We recommend using React for large Single Page Applications (SPA's). React is suited for long term projects that need stable and maintainable code. React works great together with TypeScript.
Start a new React project with one of our custom templates.
We recommend using Vue for 'campaign like' Single Page Applications (SPA's). Vue is suited for projects that needs complex interaction, flexibility and fast development.
Start a new Vue project with Vue Skeleton.
We recommend using Muban for Server Rendered Websites. Muban makes integration into CMS systems, like AEM, Drupal, Liferay and Umbraco easier.
- classnames - A simple JavaScript utility for conditionally joining classNames together.
- Draggable - Green Sock library for dragging elements
- grid-overlay - provides developers a customizable, responsive grid-overlay to compare with the layout provided by design to compare layouts
- GSAP - Green Sock Animation Platform -Animation library
- Litepicker - Date range picker - lightweight, no dependencies
- Lottie - parses Adobe After Effects animations exported as json with Bodymovin and renders them natively on mobile and on the web
- scroll-tracker-component-manager - The ScrollTrackerComponentManager is a Class that tracks whether a component is within your viewport based on your scroll position.
- seng-scss - An SCSS library of mixins and functions
- seng-device-state-tracker - DeviceStateTracker is a utility class that tracks which media query is currently active using the matchMedia API.
- storybook - UI component dev & test
- axios - Promise based HTTP client
- bowser - A small, fast and rich-API browser/platform/engine detector
- date-fns - provides the most comprehensive, yet simple and consistent toolset for manipulating JavaScript dates
- i18next - Internationalization library
- lodash - A modern JavaScript utility library delivering modularity, performance & extras.
- seng-disposable-event-listener - event listening management
- seng-disposable-manager - event management
- seng-event - Provides Classes and utilities for dispatching and listening to events.
- Yup - Form validation
- isntnt - Composable TypeScript predicate
- ts-essentials - TypeScript utilities
- Formik - Forms
- MobX - Simple, scalable state management
- React Router - Router library for React
- react-i18next - Internationalization library for React
- react-intl - Internationalization library for React
- router-path - Dynamic route path creator
- Redux - A Predictable State Container for JS Apps
- redux-actions - Flux Standard Action utilities for Redux.
- redux-thunk - Thunk middleware for Redux.
- reselect - Selector library for Redux
- typesafe-actions - Typesafe utilities designed to reduce types verbosity and complexity in Redux Architecture.
- vue-transition-component - Provides GreenSock transition functionality to vue.js components.
- vuex-persistedstate - Persist Vuex state with localStorage.
- vuex - State management pattern + library for Vue.js applications
- controller-controller - Manage different controllers based on viewport.
- patch-package - Lets app authors instantly make and keep fixes to npm dependencies.
- connect-api-mocker - Middleware that fakes REST API server with filesystem.
Start a new Muban project with the Quick Start guide.
- Read the ticket. If no ticket is present, create one yourself or ask the Project Manager to create one.
- Make sure the ticket is clear and actionable. If not, reassign the ticket to the person responsible for the creation of the tickets (the project manager or project lead) until the ticket is 100% clear.
- Create a feature branch (
feature/ticket-number-feature-name
or for Jira use the default branch name when creating a branch from a ticket).
- Double check if feature is properly working on all browsers specified in the browser matrix.
- Double check if feature is properly working on all resolutions.
- Review all commits and check if there is room for improvement.
- Could any of the functions you wrote be reused in other components/features? If so, rewrite it and restart the checklist process.
- Ask yourself in which scenarios could this fail?
- Make sure to check that you are handling possible error cases.
- Merge latest develop into branch and see if there are no conflicts. If there are conflicts please ask for help if you don't know which part of the code should stay.
- Remove unnecessary comments.
- Check the name and semantics of all functions, properties, variables etc. Do they still make sense? Could someone that doesn't know the code understand what it is doing?
- Read your code again. Do you think it can be done better or optimized? Do it. Start process again.
- Read the description of the ticket / email again. Did you really do what is asked for? Does your change solve the issue?
- Run build tasks and see if they work.
- Does your project have code that isn't used anymore? Throw it away!
- Make sure all linting is passing.
- Check that
HTML5 Semantic Elements
are used appropriately (
header
,section
,footer
,main
...). - All images have an
alt
property - All
<a>
havetitle
property - Check if all images are optimized (Saved for web and compressed, resized accordingly)
- Headings (
h1
,h2
,h3
...) are used in the correct order. - All
input
's have alabel
- Check if you can navigate the website with keyboard (using tabs)
- Check if all videos are compressed correctly
- Add
aria
labels. - Check if the fonts are being loaded properly.
- Check if images are being lazy loaded and for proper usage of picture with responsive images
- Check the
hover
,disabled
,invalid
anderror
states on buttons, links, form elements, etc.
- Check for proper html
doctype
. - Check for proper
viewport
tags. - Check the site has a
title
tag. - Check for Social sharing
meta
tags. - Check the site
description
. - Check the site again while throttling the internet connection. Does it still work? Do you have loaders in place?
- What happens if you press the
back
button in the browser? And then theforward
button? - What happens if you refresh the page halfway the flow?
- What happens if you directly enter a page at the end of the flow?
- What happens if you navigate away during an async process?
- What happens if you manually change a value in the URL?
- Is there a
404
? - Check for a favicon.
- Check for JavaScript errors in the console.
- Remove all console.logs.
- QA/Staging/UAT/Dev deployments before every Production release are a must. No matter if it's a hotfix or if the PM is pushing. Unless everything is broken, please follow the rules.
- No Friday deploys. Inform your project manager not to rely on Friday deployments 😀
- Run the website through page insights / Google Lighthouse. (Run audits in chrome)
Free code editor made by Microsoft. https://code.visualstudio.com/
Code linting / formatting:
Styling Framework:
Collaborating:
JS/TS Framework:
Miscellaneous:
User settings.json:
{
"editor.formatOnSave": true,
"editor.renderWhitespace": "all",
"editor.rulers": [100],
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"html.format.indentHandlebars": true,
"html.format.wrapAttributes": "force-expand-multiline",
"javascript.preferences.importModuleSpecifier": "relative",
"typescript.preferences.importModuleSpecifier": "relative"
}
Integrated development environment focussed on web development made by JetBrains. https://www.jetbrains.com/webstorm/