Javascript
| Field | Value |
|---|---|
| Author | Malakhov Dmitry (Dmitry Malakhov) |
| Audience | TS / JS Developers & Engineers |
| Revision | 0.0.1 |
| Revision Date | 12.02.2026 |
| Description | Initial version |
Preface
The purpose of this document is to reach the same general code writing and code organization requirement across all of our JS and TS repositories. This is mostly not about the attention of manual spelling checks - being the developers, we should reach the full automatization where it’s possible. Still there are some topics that are not very obvious for being changed automatically - most of them are covered in this doc.
The industry standard principles are considered as core and default requirements for any developer, such things as SOLID require a separate book; DRY and others are too obvious to mention.
All of us are using different development environments and IDEs, and this option should never be limited - we may use VSCode, Cursor, IntelliJIDEA, vim, and event notepad or nano for some cases. I’m going to provide a setup, suitable for VSCode/Cursor, that will enrich the experience of working with this software in a separate section of this document.
The document is covering the different environments: general, backend and browser specific cases.
First of all, it is important to note that any contemporary JS/TS developer is considered as NodeJS first. We use the code environment to write readable code to build it into the plain JS, that we can call a machine-ready - it will contain the polyfills, it can be minified and even obfuscated. On the build step, NodeJS engine is always involved, and this requires the knowledge of this platform for the developer. So, let’s fix this fact - most of the requirements there are related to the source code we write - not the build target.
There are rule level labels defined in the document, helping to define the strictness of a section or a rule.
Modules system
Must The ESM modules should be in use in the source code only, instead of the cases when we write plain JS files for immediate evaluation with NodeJS engine for some cases.
Should Try to follow the condition of partial imports and exports where you don’t need the entire module:
- Instead of
- import crypto from “node:crypto”
- Use
- import { publicEncrypt, randomBytes } from “node:crypto”
Organize your own code exports to provide it in the same way.
Packages management
We’re not limited by using different packaging engines, such as npm, yarn, pnpm etc. It should be defined on the repository level by the repo owner. But, once it’s defined, no other package manager should be used in this repo - it shouldn’t contain yarn.lock and package-lock.json files simultaneously, for example.
Must Lock files should be always included into the git index.
Should Once some external package is upgraded even into a minor version, the developer should pass the automated tests suite locally to ensure that nothing is impacted with this update.
Recommended Any project or repo should have a policy for the scheduled or event-based upgrade of the external libraries due to the safety purposes.
Must The “engines” section of package.json files should contain the most of software versions, at least, node and package manager versions.
Must The development dependencies should never come to the target build. For this, ensure that it’s contained in the “devDependencies” section of the package.json:
- linters/prettier
- testing frameworks
- builders and bundlers
- types
- …
Recommended It is also appreciated if the version of the module is defined in the package.json file.
Node modules
Recommended Some package managers may provide their own way of keeping the installed dependencies. If you are not aware of this feature, it’s highly likely you don’t need it. It may lead to unexpected behaviour of some features like type resolution in TS monorepo workspaces.
Solution for modern yarn versions: place “.yarnrc.yml” file at the root of the repo. It should contain the line:
nodeLinker: node-modules
Repository secrets handling
Must It is obvious that we should never commit the secrets to avoid exposing it. Always add .env files to gitinore for the backend projects.
In the case of frontend application, there’s no sense in it - all code is public, so you may commit the environment files safely.
Compilers
Recommended It is very welcomed and appreciated to use the Typescript in all of the new projects. It provides type safety, readability and more other useful features of working with the source code.
In case it's a legacy backend JS repo, use babel to provide all of the modern JS features for the source code.
.babelrc config file: { "presets": [ [ "env", { "targets": { "node": "current" } } ] ], "sourceMaps": "inline", "retainLines": true, "plugins": ["transform-optional-chaining"] }
Recommended If it’s a frontend repo, try to pick a modern bundler, such as Vite and Typescript as the compiler.
Avoid using Webpack bundler where possible, once you compare the build time with Vite, you will never wish to switch back.
Monorepository recipes
Optional The modern package managers provide awesome experience and features of working with monorepos. I’ll try to cover the yarn case, anyone who is familiar with pnpm is welcomed to extend this section.
Ensure you’re working with the latest yarn version installed globally.
In package.json, add the root section “workspaces”. Reference for example:
"workspaces": [ "src/*", "packages/*", "bots/*", "swagger", "e2e-tests" ]
Optional There are even more development tools allowing to speed up the development process of working with the workspaces. It provides tasks caching, watch modes and lot more:
- Turborepo
- Nix
Linting and formatting
This section is dedicated to the things that may alleviate most of the boring standards, such as indentation, quotes and semi signs in our code.
There are 2 kinds of linting and formatting approaches, and mostly we use the both:
- IDE-level: resolves it automatically on the fly while typing, pasting the code or saving the file. Mark the linting errors in the editor window.
- CLI-level: tools for check, format the file or the entire repository and show the report with the console commands. Useful for the CI jobswe may receive a non-zero exit code if some developer has ignored the rules described there.
Recommended For the FE repositories, most of the described below are probably not actual, as most of the modern FE frameworks provide their own set of formatting on repo initialization. If not, please use it.
.editorconfig file
Must The purpose of this file is to align a few possible requirements amongst the different IDEs. You may consider it as a small contract between the different code environments helping you to reach the same formatting while you are writing.
Minimum and safe default case:
.editorconfig root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 2
Prettier (general)
Must Both prettier and eslint packages are mandatory for the projects, and it works simultaneously.
Install npm package: “prettier”
.prettierrc.mjs export const prettierConfig = { arrowParens: "avoid", printWidth: 80, semi: true, singleQuote: false, tabWidth: 2, trailingComma: "all", };
ESLint common configs
Must
common.config.mjs import importPlugin from "eslint-plugin-import"; import prettier from "eslint-plugin-prettier"; export const commonConfigs = [ importPlugin.flatConfigs.recommended, importPlugin.configs.typescript, { ignores: ["dist"], }, { plugins: { prettier, }, rules: { "prettier/prettier": [ "warn", { endOfLine: "auto", }, ], }, }, { files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { ecmaVersion: "latest", sourceType: "module", }, rules: { "import/exports-last": ["error"], "import/first": ["error"], "import/named": ["error"], "import/newline-after-import": ["warn", { considerComments: true }], "import/no-absolute-path": ["error"], "import/no-empty-named-blocks": ["error"], "import/no-extraneous-dependencies": ["error"], "import/no-relative-packages": "error", "import/no-self-import": "error", "import/order": [ "warn", { alphabetize: { caseInsensitive: false, order: "asc", orderImportKind: "asc", }, groups: [ "builtin", "external", "internal", "parent", "sibling", "index", ], named: { enabled: true, export: true, import: true, require: true, types: "types-last", }, "newlines-between": "always", }, ], }, }, { settings: { "import/internal-regex": "^@lib/", "import/resolver": { node: true, typescript: true, }, }, }, ];
ESLint config for Javascript
Optional
Install packages:
eslint eslint-plugin-import eslint-plugin-prettier eslint-plugin-jest eslint-plugin-mocha @eslint/js globals
eslint.config.mjs import pluginJs from "@eslint/js"; import pluginChaiFriendly from "eslint-plugin-chai-friendly"; import mochaPlugin from "eslint-plugin-mocha"; import globals from "globals"; import { commonConfigs } from "./common.config.mjs"; /** @type {import('eslint').Linter.Config[]} */ export const jsConfig = [ { languageOptions: { globals: globals.node } }, pluginJs.configs.recommended, mochaPlugin.configs.flat.recommended, ...commonConfigs, { ignores: ["*.ts"], }, { files: ["test/**/*.js"], languageOptions: { globals: globals.mocha }, plugins: { "chai-friendly": pluginChaiFriendly, mocha: mochaPlugin }, rules: { "mocha/no-async-describe": "off", "mocha/no-mocha-arrows": "off", }, }, ];
ESLint config for Typescript
Optional
Add packages:
eslint eslint-plugin-import eslint-plugin-prettier eslint-plugin-jest eslint-plugin-mocha globals @types/eslint__js @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript typescript-eslint
eslint.config.mjs import eslint from "@eslint/js"; import pluginJest from "eslint-plugin-jest"; import { config as tsEslintConfig, configs as tsEslintConfigs, } from "typescript-eslint"; import { commonConfigs } from "./common.config.mjs"; export default tsEslintConfig( eslint.configs.recommended, ...tsEslintConfigs.strictTypeChecked, ...tsEslintConfigs.stylisticTypeChecked, ...commonConfigs, { languageOptions: { parserOptions: { project: ["./tsconfig.eslint.json"], tsconfigRootDir: process.cwd(), }, }, }, // Jest test files { files: ["**/*.spec.ts", "**/*.test.ts"], languageOptions: { globals: pluginJest.environments.globals.globals, }, plugins: { jest: pluginJest }, rules: { "jest/no-disabled-tests": "warn", "jest/no-focused-tests": "warn", "jest/no-identical-title": "error", "jest/prefer-to-have-length": "warn", "jest/valid-expect": "error", }, }, { files: ["**/*.js", "**/*.mjs"], ...tsEslintConfigs.disableTypeChecked, }, );
tsconfig.eslint.json { "extends": "./tsconfig.json", "compilerOptions": { "noEmit": true, "types": ["jest", "node"] }, "include": ["src", "jest.config.ts", "jest.setup.ts", "**/*.spec.ts"], "exclude": ["dist"] }
For monorepo, avoid copy-pasting these config files across all workspaces - remember DRY principle. Create a new workspace package and export it from there.
Add commands in your package.json file:
"lint:fix": "eslint --fix", "lint": "eslint",
VSCode setup
Recommended
Install the extensions:
dbaeumer.vscode-eslint esbenp.prettier-vscode editorconfig.editorconfig
Create .vscode folder at the root of your repo, add the file “settings.json”:
settings.json { "cSpell.enabled": false, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnPaste": true, "editor.formatOnSave": true, "eslint.useESLintClass": true, "eslint.enable": true, "eslint.validate": ["javascript", "typescript"], "eslint.format.enable": true, "eslint.workingDirectories": [ { "mode": "auto" } ], "search.exclude": { "**/.pnp.*": true, "**/.yarn": true }, "[json]": { "editor.codeActionsOnSave": { "source.fixAll": "never" } }, "typescript.preferences.importModuleSpecifier": "relative" }
Testing framework selection
Recommended
In most of our legacy backend repositories, the “mocha” is in use, but I advise migration to “jest”. Why is it better:
- Extended possibilities of mocking the modules, functions, properties. You may be sure you’re testing only your internal code with no side impact.
- Rich variety of the reporters. You may report into a file or a command line the details and output
- Watch mode with filters by file or testcase name
- Report on code coverage with the tests.