Wednesday, September 6, 2023
Enforcing coding style with @vercel/style-guide Coding Style
ESLint
Prettier
TypeScript
@vercel/style-guide
offers predefined configs of the following tools in a single package.
If you are not sure which ESLint rules or plugins to use (there are A LOT!!!) or wish to enforce code quality but not sure where to start, @vercel/style-guide
is a decent drop-in solution.
@vercel/style-guide
is strict, VERY strict. If you are getting used to the default config provisioned by a project CLI, e.g., Next.js and Vite , it might take you some time to adapt to it.
Also, I found some configs aren't making too much sense in the way they are configured. I found myself fighting against those configs instead of enhancing my code quality. Thus, I have overridden some of the configs to how I feel comfortable. The good news to you is, I'm glad to share them with you.
FYI, the following are the config files I'm running in my Next.js project. Feel free to copy and/or customize them according to your needs.
.eslintrc.js const { resolve } = require ( 'node:path' );
const { JAVASCRIPT_FILES } = require ( '@vercel/style-guide/eslint/constants' );
const project = resolve (__dirname, 'tsconfig.json' );
/** @type {import('eslint').Linter.Config} */
module . exports = {
root: true ,
extends: [
require. resolve ( '@vercel/style-guide/eslint/browser' ),
require. resolve ( '@vercel/style-guide/eslint/node' ),
require. resolve ( '@vercel/style-guide/eslint/react' ),
require. resolve ( '@vercel/style-guide/eslint/next' ),
require. resolve ( '@vercel/style-guide/eslint/typescript' ),
],
parserOptions: { project },
settings: {
'import/resolver' : { typescript: { project } },
/**
* enable MUI Joy components to be checked
* @see { @link https://github.com/jsx-eslint/eslint-plugin-jsx-a11y?tab=readme-ov-file#configurations }
*/
'jsx-a11y' : {
polymorphicPropName: 'component' ,
components: {
Button: 'button' ,
Icon: 'svg' ,
IconButton: 'button' ,
Image: 'img' ,
Input: 'input' ,
Link: 'a' ,
List: 'ul' ,
ListDivider: 'li' ,
ListItem: 'li' ,
ListItemButton: 'button' ,
NextImage: 'img' ,
NextLink: 'a' ,
Textarea: 'textarea' ,
},
},
},
rules: {
'@typescript-eslint/explicit-function-return-type' : 'off' ,
'@typescript-eslint/no-confusing-void-expression' : [
'error' ,
{ ignoreArrowShorthand: true },
],
'@typescript-eslint/no-shadow' : 'off' ,
'@typescript-eslint/no-misused-promises' : [
'error' ,
{ checksVoidReturn: { attributes: false } },
],
'@typescript-eslint/restrict-template-expressions' : [
'error' ,
{
allowAny: false ,
allowBoolean: false ,
allowNullish: false ,
allowRegExp: false ,
allowNever: false ,
},
],
'react/function-component-definition' : [
'warn' ,
{
namedComponents: 'arrow-function' ,
unnamedComponents: 'arrow-function' ,
},
],
'react/jsx-sort-props' : [
'warn' ,
{
callbacksLast: true ,
shorthandFirst: true ,
multiline: 'last' ,
reservedFirst: true ,
},
],
// sort import statements
'import/order' : [
'warn' ,
{
groups: [
'builtin' ,
'external' ,
'internal' ,
'parent' ,
'sibling' ,
'index' ,
],
'newlines-between' : 'always' ,
alphabetize: { order: 'asc' },
},
],
// sort named imports within an import statement
'sort-imports' : [ 'warn' , { ignoreDeclarationSort: true }],
},
overrides: [
/**
* JS files are using @babel/eslint-parser, so typed linting doesn't work there.
* @see { @link https://github.com/vercel/style-guide/blob/canary/eslint/_base.js }
* @see { @link https://typescript-eslint.io/linting/typed-linting#how-can-i-disable-type-aware-linting-for-a-subset-of-files }
*/
{
files: JAVASCRIPT_FILES ,
extends: [ 'plugin:@typescript-eslint/disable-type-checked' ],
},
// Varies file convention from libraries, e.g. Next.js App Router and Prettier
// Must use default export
{
files: [
'*.config.{mjs,ts}' ,
'src/app/**/{page,layout,not-found,*error,opengraph-image,apple-icon}.tsx' ,
'src/app/**/{sitemap,robots}.ts' ,
'src/components/emails/*.tsx' ,
],
rules: {
'import/no-default-export' : 'off' ,
'import/prefer-default-export' : [ 'error' , { target: 'any' }],
},
},
// module declarations
{
files: [ '**/*.d.ts' ],
rules: { 'import/no-default-export' : 'off' },
},
],
};
tsconfig.json {
"extends" : "@vercel/style-guide/typescript/node20" , // depending on which Node.js version is used
"compilerOptions" : {
"lib" : [ "dom" , "dom.iterable" , "esnext" ],
"allowJs" : true ,
"noEmit" : true ,
"module" : "esnext" ,
"moduleResolution" : "bundler" ,
"resolveJsonModule" : true ,
"isolatedModules" : true ,
"jsx" : "preserve" ,
"incremental" : true ,
"plugins" : [{ "name" : "next" }],
"paths" : { "@/*" : [ "./src/*" ] }
},
"include" : [
"svgr.d.ts" ,
"next-env.d.ts" ,
"**/*.ts" ,
"**/*.tsx" ,
".next/types/**/*.ts"
],
"exclude" : [ "node_modules" ]
}
For Prettier, I'm not overriding the options in @vercel/style-guide/prettier
, but I've extended it to support Prisma .
prettier.config.mjs import vercelPrettierOptions from '@vercel/style-guide/prettier' ;
/** @type {import('prettier').Config} */
const config = {
... vercelPrettierOptions,
plugins: [ ... vercelPrettierOptions.plugins, 'prettier-plugin-prisma' ],
};
export default config;