Disagree with something you saw in the project and want to understand why it's implemented this way? Just want to read up on my thoughts on JS/TS/PWA development? Either way, you're in the right place.
When or if you consider forking/cloning the project and use it as a base for any project, you can (obviously) disregard/alter any of the standards written here. Just be sure to keep this file up-to-date with your own coding standards. It's easy to lose your way after a while (or in a team).
All configuration files live outside of any folders, in the root of the project to avoid pathing conflicts of any kind.
Any configuration that is environment- or IDE-specific is not to be included in the repository as it
is the responsibility/choice of each developer. This includes the .editorconfig
file as well - if
linting tools are provided, then "any" editor can be configured to use them - no need to duplicate
code/rules or restrain the developers. Any files that can be generated by the application are to be
kept out of the repository as well.
All generated folders live in the root of the project for easy accessibility by internal or external
tools. Examples of these types of folders are: coverage
(for code coverage collection),
node_modules
(obviously) and public
(the generated assets folder).
The config
folder contains any and all files pertaining to building and configuring the
environment for the application. This includes the build configuration for webpack (and its
environments) and any environment-specific global runtime variables.
The src
folder contains only (1) actual application code, (2) assets to be copied or used during
the build process and (3) testing functions (helpers) to be used when testing the application.
Inside the src
folder, you'll find 3 folders that contain these types of code/information
respectively. They are (1) app
, (2) static
and (3) testing
.
All project dependencies are to be written as ~MAJOR.MINOR
. Patch versioning is only acceptable
where an incompatibility or breaking change exists between two patch versions.
Project needs webpack
, which is in version 3.9.1
.
In the package.json
file the dependency must be written as "webpack": "~3.9"
so that any and all
patches/bugfixes to 3.9
are applied to the project (3.9.1
, 3.9.2
, ...) but no (possible)
breaking features (3.10.0
) are added automatically.
Project needs at least webpack
3.9.1
due to an impactful bugfix on that specific patch version.
In the package.json
file the dependency must be written as "webpack": "~3.9 >=3.9.1"
for the
same reasons described above.
In typical node applications, production packages are packages that are needed during runtime. All
other dependencies are devDependencies
. In applications that are completely pre-built and need no
production packages (like in this repo), production packages are the packages needed to build the
production version of an application. This makes things easier on automators like jenkins that build
our production build but have no need to install, for example, linters. Additionally, this
separation also makes a linter's job easier for detecting dev dependencies being bundled with the
final production code.
Any module or submodule is contained inside its own folder. No other information, asset, module or component is to be included in that folder.
Any component or module that is a direct child of the app
folder (i.e. files inside the app
folder) is a starting point for the application. In a PWA, there is only one.
Any component and module should have (when applicable): the module code (.js
or .ts
), the style
(.css
or .scss
), the unit test (.spec.js
or .spec.ts
) and the export file (index.js
or
index.ts
).
This is intended for several reasons:
- a component/module should be used as a blackbox
- non-tested components/modules are easily spotted
- adding/deleting a component/module is a clean and self-contained action
File name
s, and type
s should be done in kebab-case. File naming rule is the following:
[name](.type?)(.spec?).[ext]
. Examples:
example.component.ts
- component namedExampleComponent
example.style.scss
- style to be imported byExampleComponent
example.component.spec.js
- unit tests forExampleComponent
my-module.js
- service/module namedMyModule
my-module.spec.ts
- unit tests forMyModule
Note that index.js
(or index.ts
) is exempt from these rules as it is the default name and
extension used on node for "importing a folder". This file should only import and export the
component or module it pertains to.
Any cluster of similar modules should be grouped by folder inside app
. Typical examples are:
containers
- components that hold state information and control its flowcomponents
- dumb components that receive information onlymodels
- models/transformers/factories for objectsservices
- modules for calculations, API requests, ...state
orstore
- modules that contain the application's state
Note that these are only examples and that inside these folders, similar modules can and should also be grouped according to their relativeness or usefulness.
All imports follow these rules:
- Order is: packages first, project modules next, and local modules last. There is always a blank line separating these 3 types of imports
- Deconstructed import objects are written alphabetically. Types or interfaces (example: in typescript or when using JS PropTypes) are imported last when deconstructing.
No module or component is imported or exported through the default
mechanism. Airbnb coding
standards are intended for typical JS/Node packages but they do not take into account the volatility
of code, features, requirements and dependencies that a frontend project has to deal with. For more
information on the subject, see this issue and PR. If there
needs to be a default import (for example due to a restriction of an external library), make sure
there is also a matching named import in order to keep code consistency.
All other linting rules from airbnb-base
must be followed as it is the chosen standard for this
project.
Disabling rules on eslint
or stylelint
should only be done in cases where there is no other
feasible option. When disabling a rule, disable it only for the next line of code with
eslint-disable-next-line
for 3 main reasons:
- it is a failsafe to not have a rule disabled on the entire project due to forgetfulness or bad code reviews
- never adds to the line char count
- adding/removing them results in simpler diffs
Note that if 3 or more lines in a row require the disabling of a rule, use eslint-disable
and
eslint-enable
.
Naming convention is as follows: class names, interfaces and typing in general are to be written in PascalCase, objects and singletons in camelCase and global variables in UPPER_SNAKE_CASE.
Unit tests can generally be divided in 2 categories:
- whitebox - for testing, for example, a service to fetch data from an API
- blackbox - for testing, for example, a visual component of a web page
Regardless of what type of test you are performing, any and all external dependencies (parents and
children) should be mocked - auto-mocked by jest
if possible.
Testing a service or module is done in a whitebox style, where every public function is tested independently and where external dependencies are mocked (sibling function mocking should be done on an as needed basis). This is done in order to verify that every function of the service/module behaves according to its specifications.
The first describe
on a service test should be written as the name of the exported service;
second-layer describe
s should represent function names and should be prefaced by a #
; following
describe
s should be written as if prefaced by the word when
. When writing it
s, they should
start with a present tense verb and should be read as if prefaced by the word it
. (See
src/app/services/helper/index.spec.js
for an example)
Testing a visual component is done is a blackbox style, where the only thing that matters is the result on the page (i.e. what is seen by the user). That said, the tests focus only on testing the output on the page. This is done because changing the library/framework, a dependency, parent/child component cannot majorly impact the unit tests.
The first describe
on a component test should be written as the name of the exported component;
following describe
s should be written as if prefaced by the word when
. When writing it
s, they
should start with a present tense verb and should be read as if prefaced by the word it
. (See
src/app/components/home/home.component.spec.ts
for an example)
Anything lower than 100% test coverage is not acceptable. If a component or module cannot be fully
tested, then it should be refactored - testability is a measurement of code maintainability. Only in
extreme cases should you use something like /* istanbul ignore next */
.
Versioning should be done in the semver way described here.
When version is below v1
, minor versions become breaking changes or new features, while patch
versions become minor features or bugfixes.