Preact project setup with formatting and linting

This article describes the basic project setup of the Gachou frontend in more detail.

This article describes the basic project setup of the Gachou frontend in more detail. Specifically, I will write about:

  • Preact and TypeScript
  • eslint and prettier
  • lint-staged and husky
  • GitLab CI/CD pipelines

Why TypeScript?

In the past, I have done frontend projects in JavaScript and in TypeScript, and I don’t want to miss the advantages of TypeScript anymore. It gives you a lot of IDE code-completion, verified documentation and static checks that save you a lot of time. In my view, this outweighs the disadvantage of having to write (sometimes complicated) types.

Why Preact?

I have chosen Pre act for a couple of reasons:

  • I wanted to use something familiar, and I have used React and Vue.js a lot in the past years.
  • In my experience, React works better with TypeScript than Vue.js, mostly because tsx is supported by TypeScript out of the box,
  • I wanted to try something new, so I chose “Preact” instead of React. Preact aims to be a “Fast 3kB alternative to React”.
  • It is not completely compatible, and the community is not as big as the React community, but my impression is that it has a solid set of tools, like router, pre-rendering, etc.

The first step to creating the gachou-web-ui project was running preact-cli

npx preact-cli create typescript gachou-web-ui

Configuring eslint and Prettier

If you are developing serious code you should definitely use eslint. It helps you identify bugs, enforce best-practices, and format your code. If you haven’t heard of eslint before, watch Feross Aboukhadijeh’s talk about this topic or have a look at https://eslint.org.

You should at least configure the recommended set of rules, but I also like to add other rules like eqeqeq to prevent harmful use of == for comparing values. Also, you do not have to specify all rules from the beginning of the project. You can add new rules to the configuration when the need arises.

Even though eslint can also format your code, I think Prettier handles this purpose better for the following reasons:

  • I want bugs highlighted in red in my IDE, but formatting should just happen without me noticing. That’s why I don’t want code-formatting rules in eslint.
  • Prettier formats much more rigorously than eslint. You can add spaces and new-lines all over a file. When you run Prettier, you can be (pretty) sure that everything is back as it was before. This comes very handy, if you want to keep you git-history free of code-format changes.

If you want to use both tools, you have to disable eslint-rules for layout and formatting, because they might format the code differently than Prettier does. Luckily, there is eslint-config-prettier which disables those rules.

There is also an eslint-plugin-prettier which is used by default, in some framework, but I do not like to use.

It runs Prettier as an eslint-rule. With that plugin, my IDE highlights wrongly formatted code, which is very distracting.

.eslintrc

Preact-CLI adds eslint by default, but only with a very basic configuration in package.json. This is the configuration that I use, not in package.json, but in a dedicated config-file .eslintrc:

const baseConfig = {
  extends: ["prettier", "eslint:recommended"],
  ignorePatterns: ["build/"],
  parserOptions: {
    sourceType: "module",
    ecmaVersion: 2021,
  },
  rules: {
    eqeqeq: ["error", "always", { null: "ignore" }],
  },
};

const typescriptFiles = {
  files: "**/*.+(ts|tsx)",
  parser: "@typescript-eslint/parser",
  parserOptions: {
    project: "./tsconfig.json",
  },
  plugins: ["@typescript-eslint/eslint-plugin"],
  extends: [
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
  ],
};

const configFilesBasedOnNodeJs = {
  files: [
    "./*.config.js",
    "./.eslintrc.js",
    "./.eslintrc.js",
    "./.storybook/main.js",
  ],
  parserOptions: {
    sourceType: "script",
    ecmaVersion: 2021,
  },
  env: {
    node: true,
  },
};

module.exports = {
  ...baseConfig,
  overrides: [typescriptFiles, configFilesBasedOnNodeJs],
};

.prettierignore + .eslintignore

In gachou-web-ui I just ignore the same files to Prettier and eslint,even though there are some lines that are not required for eslint (like log-files). It is easier to maintain two files with exactly the same contents.

node_modules
/build
/*.log
/.idea
/.yarn
/junit.xml
/coverage/
yarn.lock
yarn.error.log
!/.storybook

When to run eslint and prettier?

Firstly, use an IDE integration to use Prettier as formatter and eslint for inspections. There are plugins for most IDEs.

To make it easier for developers, you should add scripts to the package.json

{
  "scripts": {
    "lint": "eslint --cache '**/*.{js,jsx,ts,tsx}'",
    "format": "prettier -w .",
    "test:format": "prettier --check ."
  }
}

To make sure that the main-branch of your repo only contains correct and well-formatted code, you should run the test:format-script and lint-script in your CI-pipeline before merging. For gachou-web-ui I use GitLab CI/CD and Yarn 3 with the following .gitlab-ci.yml:

test:static:
  stage: test
  image: node:16
  cache:
    paths:
      - .yarn/cache
      - .eslintcache
  script:
    - "corepack enable"
    - "yarn config set enableGlobalCache false"
    - "yarn"
    - "yarn lint"
    - "yarn test:format"

With this setup, you will probably see the CI-pipeline fail very often, because you forgot to run Prettier or eslint before pushing. To avoid this, you should also use lint-staged and husky to run both them before each commit, on the staged files. That way, you don’t have to think about formatting anymore. It will just happen. And if eslint finds any issues in your changed files, the commit will fail.

In the gachou-web-ui I installed lint-staged like described in its README-file.

npx mrm@2 lint-staged

The configuration for lint-staged is written to package.json. I had to add some more file-types to make sure all relevant files are checked and formatted.

{
  "lint-staged": {
    "*.{js,ts,jsx,tsx}": "eslint --cache --fix",
    "*.{js,jsx,ts,tsx,css,md}": "prettier --write"
  }
}

Conclusion

I hope this post has shown you the benefits of using eslint and Prettier along with CI-pipelines and pre-commit hooks. I also hope that it helps you set up your next web-frontend project. gachou-web-ui will evolve further, but this setup lays the groundwork for future changes.

There are still more aspects I would like to write about, and more posts may come. If you have any feedback, you can contact my via the public e-mail address in my GitHub Profile.