Cozy-guidelines

Cozy Code Guidelines

Download as .zip Download as .tar.gz View on GitHub

Cozy Code Guidelines

Naming of Functions

To name functions, you can use some keywords that everyone will understand, like those ones :

fetchSomething : should access data by making a server side call

getSomething : should access data without making a server side call

findSomething : like the get, but with arguments

normalizeSomething : should transform data to conform to a given schema or to make sure that is coherent with other documents

saveSomething : creates or updates a document

hasSomething or isSomething : should describe a boolean variable

computeSomething : create something from other elements

makeSomething : create something from scratch

doSomethingAndForget : to convert an async func doSomething to a sync one doSomethingAndForget

ensureSomething : make sure something {is/has been} done

Naming of Queries

By “naming queries” we mean defining the as attribute of the queries options usable in cozy-client:

const queryResult = useQuery(Q(DOCTYPE), {
    as: ...,
    fetchPolicy: ...
  })

By default as must be defined according to the doctype used: as: DOCTYPE

If the query can be built with parameters (in this case id, account and date), these parameters must be included after id by preceding them with a /[PARAM_NAME]/ (note that this is unnecessary for id):

as: `${DOCTYPE}/${id}/account/${account}/date/${date}`

Import order

import libs from externals // (ex: lodash)

import libs from internal // (ex: cozy-client)

import libs from local // (absolute or relative path, depending on the project)
import styles from local // (absolute or relative path, depending on the project)

JavaScript

Return value

Avoid using undefined and prefer returning null.

Why ? undefined is used for variable that have not been assigned yet. null is for a variable with no value. See http://www.ecma-international.org/ecma-262/6.0/#sec-undefined-value, http://www.ecma-international.org/ecma-262/6.0/#sec-null-value

Promises vs async/await

Always use async / await when applicable, instead of Promises and then() chains.

Why ? Cleaner, Clearer, more readable, easier for handling errors See https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9

Comments

Only comments things that have business logic complexity.

Why ? Comments are an apology, not a requirement. Good code mostly documents itself. See https://github.com/ryanmcdermott/clean-code-javascript#comments

❌ Bad :

// Initiate API UserData object, for checkFormLogin token
const req1 = await request({
  url: `https://www.deezer.com/ajax/gw-light.php?method=deezer.getUserData&input=3&api_version=1.0&api_token=&cid=`
})

✅ Good

function initiateAPIForToken () {
  return request({
    url: `https://www.deezer.com/ajax/gw-light.php?method=deezer.getUserData&input=3&api_version=1.0&api_token=&cid=`
  })
}

Date

Date management dependencies may increase app bundle and are not always useful.

In order to uniform our usage of date management package, we discourage any use of moment The main reason is that project was built for the previous era of the JavaScript ecosystem and since September 2020, for more details

Looking for alternatives, we use in that order:

JavaScript Internationalization API

Because nothing beats using native features:

Especially

Date-fns

Date-fns can be used directly inside our app.

const { f } = useI18n()

The whole configuration can be found inside Cozy-UI/I18n folder

Redirections

If you need to make a link between cozy’s apps, you should always use AppLinker from Cozy-UI. It handles a lot of use cases (flagship app, mobile app…). Never rely on window.location in the cozy-apps’ codebase. We do a few things that can have repercussion on this value. Always use the generateWebLink method. To use it, you’ll have to get the subdomainType. And to get the subdomainType, you’ll need to read it from the client: const { subdomain: subDomainType } = client.getInstanceOptions()

React

Generic code style

"eslintConfig": {
    "extends": ["eslint-config-cozy-app"]
  }

Let the formatters do their jobs.

Prefer Functional Components

For any new component in our codebase, we prefer using functional component

React Memo

Usage of React.memo / useMemo / useCallback is quite tricky. We recommend to use React.memo wisely. We use React.memo only for medium/big component that renders often with the same props.

img

Don’t use memoization if you can’t quantify the performance gains.

Strictly, React uses memoization as a performance hint. While in most situations React avoids rendering a memoized component, you shouldn’t count on that to prevent rendering.

Could I use Memoization?

Use the React profiler or a profiling extension to measure the benefits of applying React.memo().

Examples when to use memoization

Live example

In this example, we have an example of (not so big) component rendering a lot (because of realtime values provided by usePokemon hook).

Theoretical examples

In Cozy application, we may consider using memoization inside:

Styling / theming

See also cozy-ui guidelines on component development.

Test libraries and tools

Tests

Unit test files

Unit test files should be located next to their source file, rather then in a subfolder (for example __tests__). This keeps them closer to their source file and encourages their maintenance.

:+1: Cool

src
├── greetings
│   └── components
│        └── Greeting.jsx
│        └── Greeting.spec.js

❌ Bad

src
├── greetings
│   └── components
│        └── Greeting.jsx
test
├── greetings
│   └── components
│        └── Greeting.spec.js

Data Test Id

In order to uniform data-testid, we decided to use only data-testid. It helps coherency when testing with Testing Library

:+1: Cool

<div
  data-testid="id-of-div-to-test"
/>

❌ Bad :


<div
  data-test-id="incorrect-way-to-use-data-testid"
/>

queryBy ⚡️ toBeDefined

queryBy* methods return an array of Element(s) or null and .toBeDefined() fail only if value is undefined

:+1: Cool

expect(queryByTestId('foo')).toBeInDocument()
expect(queryByTestId('foo')).toBe(null)

❌ Bad :

expect(queryByTestId('foo')).toBeDefined()

See: https://testing-library.com/docs/queries/about/#types-of-queries

Dependencies

👉 Avoid directly calling Material-UI in Cozy Library and Cozy Application

Apart from Cozy-UI, repository should prevent from requiring material-ui as dependency.

👉 Inside Cozy library, any package except cozy-* or react or react-dom can be a dependency

It’s complicated to be sure that the application calling a cozy-library has any other dependency. That’s why it’s recommended to use dependency (when called in the production code)

Example:

See more about those rules

> 👉 No dependencies between Cozy Library A Cozy library should not add any other Cozy library. Those Cozy libraries should only be devDependencies + peerDependencies. Why? - in order to reduce library size - in order to avoid dependencies cycle > 👉 No react or react-dom as dependencies in Cozy Library A Cozy library should not add React or React-DOM. Those packages should only be devDependencies + peerDependencies. Why? - in order to have different versions of React and compatibility problem with Hooks We follow the practise of [Material-UI](https://github.com/mui/material-ui/blob/master/package.json) or [React-Query](https://github.com/tannerlinsley/react-query/blob/master/package.json)

Unit Commit

Each dependency upgrade should be inside its own commit, with the possible changes required by the upgrade.

This helps understanding which changes belong to dependency upgrade, and what belongs to the feature of the Pull Request. It is very useful when reverting commits.

Commit messages

A git repository lives with an history that let developers or automatic procedure to find useful information.

They have to look like that:

type(optional scope): Subject

optional body

footer with references to issue tracker IDS

Type

One of:

Scope (optional)

The scope should reflect the part of the codebase, or a specific Component, that is updated by the commit. It should be very concise (one or two words).

Example: feat(Chart): Redraw on data update

Here, the commit is updating the Chart component of the application. We know it directly from the commit message.

Subject

Subjects should:

📌 A note about emojis in commit message

You can use emojis in your commit subject but, if so, you should add it after the type. You can also use emojis in body message anyway you want. Suggested Emoji/task relations

❌  Bad
🚑 fix: when a list contains more than 50 items, the scroll is broken

✅  Good
fix: A too long list broke the scrolling 🚑

Pay attention to commit descriptions. It is important that they describe the why of a solution and not only the what and especially the how. This makes it easier to understand architectural choices and decisions that seem clear at the time of implementation but will be much less clear to someone else in 6 months or 1 year.

When writing a body, the blank line between the title and the body is required and you should limit the length of each line to no more than 72 characters.

To summarize:

For more details, see https://dhwthompson.com/2019/my-favourite-git-commit

The footer is optional and is used to reference issue tracker IDs.

Breaking change

Following the conventional commits, each commit introducing a breaking change must have a BREAKING CHANGE: description. The description should contain a migration path, i.e. a way to overcome the change for the apps using the impacted code.

See commit example here

##### Example ```git feat: Summarize changes in around 50 characters or less More detailed explanatory text, if necessary. Wrap it to about 72 characters or so. In some contexts, the first line is treated as the subject of the commit and the rest of the text as the body. The blank line separating the summary from the body is critical (unless you omit the body entirely); various tools like `log`, `shortlog` and `rebase` can get confused if you run the two together. Explain the problem that this commit is solving. Focus on why you are making this change as opposed to how (the code explains that). Are there side effects or other unintuitive consequenses of this change? Here's the place to explain them. Further paragraphs come after blank lines. - Bullet points are okay, too - Typically a hyphen or asterisk is used for the bullet, preceded by a single space, with blank lines in between, but conventions vary here If you use an issue tracker, put references to them at the bottom, like this: Resolves: #123 See also: #456, #789 ```

Pull requests

Before merging a PR, the following things must have been done:

Travis

Deploy

Use the deploy section of travis.yml instead of after_success along with checks on environment variables.

Travis has lots of documentation to easily deploy on npm, github pages, … you can find documentation here.

If you want use a specific command use script, but avoid environement variable checks in after_success.

❌ Bad :

after_success:
- test $TRAVIS_BRANCH = "master" && test $TRAVIS_REPO_SLUG = "cozy/cozy-realtime" && test $TRAVIS_PULL_REQUEST = "false" && yarn travis-deploy-once "yarn semantic-release"

✅ Good

deploy:
- provider: script
  skip_cleanup: true
  script: yarn travis-deploy-once "yarn semantic-release"
  on:
    branch: master
    repo: cozy/cozy-realtime

Secrets

Encrypted variables

Travis

All encrypted variables must be set in .travis.yml not in the Travis web interface. They should be preceded by the command to regenerate them. This makes regenerating the variables easy and all the build information is versionned and peer-reviewed.

When using travis encrypt with the --add flag, travis-cli will reformat the entire .travis.yml file and remove all comments. We suggest not using this flag at all.

Example :

# NPM_TOKEN
# To generate a new key: travis encrypt NPM_TOKEN=<token> -r cozy/cozy-realtime
- secure: WDFj9IULpiNSR6h/i8dtmbm+h4hMAUk8EA8wve9sPrJV1GL5qsMgreMYV7uMx7S93K7h1EoILzS1877tLWJJdQ7f7UgakOUVXb41s0GOfQRznDYivqllYE+X9eUkh8gOBjjCF8G3dW4+w4bbY2X97ZC5hhxwQb3DgKWNdOuGLZXZRVmVNLR0XcEkR8p1CKJe4p/iNwianj2L9Q3wk1QvrBP74lwIJIY0i692fW9SKya/BTWGV+9mgGnR8TkAZUViZT2NygNpYxF4NDcXm1Kv2Y47e9Nr9ekGHuzTcCvT/K3hlpxzjo9VgY4lFvjr5izJ/vTScfB0JuHUs3SQFtrz9yI5DBx4OuUm7iJre2dRfUflJhO4KiCtmbZMh7CnBiMSTWFxPHxiD9kZaDU5EunfCRkWcdeSQTwo5bvscHzha7QNUsdzp/xMvOyhqvmoxXapzxymRzRaYntnvkVCZSJIGzHcc9FhsPRd2AQGyk5uffK4lAOVQ+D+d0WCh+5NagEQSPJ6rymsraJpdvR7OBMXVVAmJs76MnNWCQ3DPozIDkNxTxiWWXC02FZBeKrdnVoSLNUCj4jvdLwi4FmQbi2JNMk5zdOojqtt66LiZ8LtjnHzUXZ2dhfRL0URQm97UVagVmWNkte/6PaS/UeHCr193cwthbSFnanjHDclP0eBjvE=

Secrets File

By default secrets file should not been versionned. But if the file will be available on the client side of your application, then we consider it public as said by firebase here (https://firebase.google.com/docs/projects/learn-more#config-files-objects). For instance, file as google-services.json can be versionned in our github repository. If you add this kind of file, please share the word on our security channel.

Feature flags

Master must always be in a state ready to be deployed in production. That is why we use feature flags on each new routes or for each new features not ready to go to production.

To understand better how feature flags works, have a look to our internal wiki.

Release process

Release Schema

You can find more information about the recommended workflow here

Cozy Logo

Cozy Logo

What is Cozy?

Cozy Logo

Cozy is a platform that brings all your web services in the same private space. With it, your web apps and your devices can share data easily, providing you with a new experience. You can install Cozy on your own hardware where no one profiles you.

Community

You can reach the Cozy Community by: