Skip to content

Instantly share code, notes, and snippets.

@HansKre
Last active March 24, 2024 10:51
Show Gist options
  • Save HansKre/0dc7419dd6be7a00ef8f2826ee3c550d to your computer and use it in GitHub Desktop.
Save HansKre/0dc7419dd6be7a00ef8f2826ee3c550d to your computer and use it in GitHub Desktop.
Project Setup

Setup ESLint, Prettier and Husky

  1. Execute following commands

    npm install --save-dev eslint-config-prettier eslint-plugin-prettier eslint-plugin-md prettier
  2. Add following scripts to your package.json

     "lint:js": "eslint src/**/*.js",
     "lint": "eslint . --cache --max-warnings=0",
     "lint:fix": "eslint --fix .",
  3. Add .eslintrc

    {
      "env": {
        "node": true,
        "es6": true
      },
      "parserOptions": {
        "ecmaVersion": 2018
      },
      "extends": [
        "eslint:recommended",
        "plugin:prettier/recommended",
        "plugin:md/prettier"
      ],
      "plugins": ["prettier"],
      "rules": {
        "prettier/prettier": "error"
      }
    }
  4. Add .eslintignore

    dist/
    node_modules/
  5. Add .prettierrc

    {
      "printWidth": 80,
      "useTabs": false,
      "tabWidth": 2,
      "singleQuote": true,
      "trailingComma": "es5",
      "semi": true,
      "bracketSpacing": true,
      "bracketSameLine": false,
      "arrowParens": "always",
      "proseWrap": "preserve",
      "endOfLine": "auto"
    }
  6. Validate that Linting is working: npm run lint

  7. Install husky: npx husky -init && npm install

  8. Change .husky/pre-commit to:

    #!/bin/sh
    . "$(dirname "$0")/_/husky.sh"
    npm run lint:fix
  9. rm .husky/_/.gitignore

  10. To omit devDependencies from being installed use: npm install --production

  11. How to update dependencies

    npm install
    npm update
    # remove the packages specified as devDependencies from the `node_modules` folder
    npm prune --production
    # commit the update
    git add . && git commit -m "Update dependencies"
    # add devDependencies back again
    npm install
    git status
    # if new node_modules/foo folders show up, add them to .gitignore file

TS Project Config

tsconfig

Type Checking

https://www.typescriptlang.org/tsconfig#Type_Checking_6248

The strict flag enables a wide range of type checking behavior that results in stronger guarantees of program correctness. Turning this on is equivalent to enabling all of the strict mode family options, which are outlined below. You can then turn off individual strict mode family checks as needed.

// tsconfig.compilerOptions
"noUnusedParameters": true,
"noUnusedLocals": true,
"allowUnreachableCode": false,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"strict": true,

eslint

strict TS rules

StackOverflow

To enable TypeScript-rules, typed-linting must be enabled in parserOptions first.

See this for the strict rules.

If your project enables typed linting, we suggest enabling the recommended-type-checkedÏ and stylistic-type-checked configurations to start. If a majority of developers working on your project are comfortable with TypeScript and typescript-eslint, consider replacing recommended-type-checked with strict-type-checked.

rules

  • strict-type-checked Contains all of recommended, recommended-type-checked, and strict, along with additional strict rules that require type information. Rules newly added in this configuration are similarly useful (and opinionated) to those in strict.
//.eslintrc.cjs
/* eslint-env node */
module.exports = {
  extends: [
    'eslint:recommended',
    // 'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-type-checked',
    // 'plugin:@typescript-eslint/strict-type-checked',
    // 'plugin:@typescript-eslint/stylistic',
    'plugin:@typescript-eslint/stylistic-type-checked',
    // further rules ...
    'plugin:react/recommended',
    'plugin:prettier/recommended',
    'plugin:storybook/recommended',
  ],
  plugins: ['@typescript-eslint'],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    // https://stackoverflow.com/questions/58510287/parseroptions-project-has-been-set-for-typescript-eslint-parser
    // to always use tsconfig.jsons nearest to source files
    project: true,
    tsconfigRootDir: __dirname, // "."
  },
  root: true,
};

lint-staged

npm i -D lint-staged

Config

Simple configuration in package.json

"lint-staged": {
    "*.{scss,html,json,md}": "prettier --write  --config ./.prettierrc",
    "*.scss": "stylelint --fix",
    "*.{js,tsx,ts}": "eslint --fix --no-ignore --max-warnings=0",
    // do not use the below!! because it checks the staged
    // files, but not whether changed file breaks another file
    "**/*.ts": "tsc-files --noEmit"
  },

Advanced config with own lint-staged.config.js

// lint-staged.config.js
const { ESLint } = require('eslint');

const removeIgnoredFiles = async (files) => {
  const eslint = new ESLint();
  // ignore files which are in .eslintignore
  const ignoredFiles = await Promise.all(
    files.map((file) => eslint.isPathIgnored(file))
  );
  const filteredFiles = files.filter((_, i) => !ignoredFiles[i]);
  return filteredFiles.join(' ');
};

module.exports = {
  '*.{js,ts, tsx}': async (files) => {
    const filesToLint = await removeIgnoredFiles(files);
    return [`eslint --max-warnings=0 ${filesToLint}`];
  },
  '*.{css,html,json,md}': 'prettier --write --config ./.prettierrc.js',
};
// .eslintignore
node_modules
**/*.js
generated

pre-commit

npx --no-install lint-staged

@trivago/prettier-plugin-sort-imports

.prettierrc.js

module.exports = {
  semi: true,
  tabWidth: 2,
  printWidth: 100,
  singleQuote: true,
  trailingComma: 'es5',
  bracketSameLine: false,
  arrowParens: 'always',
  importOrderSeparation: true,
  plugins: [require.resolve('@trivago/prettier-plugin-sort-imports')],
  importOrder: [
    '^@dlb-library/(.*)$',
    '^src/app/apollo/(.*)$',
    '^src/app/components/(.*)$',
    '^src/app/constants/(.*)$',
    '^src/app/generated/(.*)$',
    '^src/app/pages/(.*)$',
    '^src/app/types',
    '^src/app/utils',
    '^[./]',
  ],
};

.eslintrs.json

"rules": {
    "react/react-in-jsx-scope": "off",
    "comma-dangle": "off",
    "@typescript-eslint/comma-dangle": "off"
  },
import alias from '@rollup/plugin-alias';
import url from '@rollup/plugin-url';
import { exec } from 'child_process';
import { glob } from 'glob';
import { fileURLToPath } from 'node:url';
import { extname, relative, resolve } from 'path';
import { defineConfig, normalizePath } from 'vite';
import dts from 'vite-plugin-dts';
import { libInjectCss } from 'vite-plugin-lib-inject-css';
import { viteStaticCopy } from 'vite-plugin-static-copy';
/**
* The @rollup/plugin-alias plugin is used to resolve the aliases in js-files only. To achieve the same for .d.ts-files, we need to run the tsc-alias command after the build is done:
* https://github.com/ezolenko/rollup-plugin-typescript2/issues/201
*/
const tscAlias = () => {
return {
name: 'tsAlias',
writeBundle: () => {
void new Promise((resolve, reject) => {
exec('tsc-alias', function callback(error, stdout, stderr) {
if (stderr || error) {
reject(stderr || error);
} else {
resolve(stdout);
}
});
});
},
};
};
const projectRootDir = resolve(__dirname);
export default defineConfig({
plugins: [
url(),
dts({
include: ['src'],
}),
libInjectCss(),
alias({
entries: [
{
find: 'assets',
replacement: resolve(projectRootDir, 'src/assets'),
},
{
find: 'components',
replacement: resolve(projectRootDir, 'src/components'),
},
{
find: 'containers',
replacement: resolve(projectRootDir, 'src/containers'),
},
{
find: 'store',
replacement: resolve(projectRootDir, 'src/store'),
},
{
find: 'scss',
replacement: resolve(projectRootDir, 'src/scss'),
},
{
find: 'typings',
replacement: resolve(projectRootDir, 'src/typings'),
},
{
find: 'utils',
replacement: resolve(projectRootDir, 'src/utils'),
},
],
}),
viteStaticCopy({
targets: [
{
src: normalizePath(resolve(projectRootDir, 'src/scss', '_vars.scss')),
dest: './scss',
},
{
src: normalizePath(
resolve(projectRootDir, 'src/config', 'devProxy.cjs')
),
dest: './config',
},
{
src: normalizePath(
resolve(projectRootDir, 'src/config', 'webpack.config.cjs')
),
dest: './config',
},
],
}),
tscAlias(),
],
build: {
lib: {
entry: resolve(__dirname, 'index.ts'),
name: '@foo/shared-frontend',
formats: ['es'],
},
rollupOptions: {
external: [
'react/jsx-runtime',
'lodash',
'moment',
'react',
'react-dom',
'react-final-form',
'storeon',
'storeon/react',
],
input: Object.fromEntries(
glob.sync('src/**/*.{ts,tsx}').map((file) => [
// The name of the entry point
// src/nested/foo.ts becomes nested/foo
relative('src', file.slice(0, file.length - extname(file).length)),
// The absolute path to the entry file
// src/nested/foo.ts becomes /project/src/nested/foo.ts
fileURLToPath(new URL(file, import.meta.url)),
])
),
output: {
assetFileNames: 'styles/[name][extname]',
entryFileNames: '[name].js',
},
},
},
});

Webpack configs

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
          exclude: /node_modules/,
          loader: "ts-loader",
          options: {
            // Ignore TypeScript errors when compiling
            // this helps start a project even with errors
            transpileOnly: true,
          },
      },
      // let's webpack ignore certrain file-patterns
      {
        test: /\.map$/,
        loader: "ignore-loader",
      },
    ]
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment