TypeScript. Advanced Project Setup
Tags: typescript, javascript, programming, beginners
Published: 2022-01-18 08:12:41

> In the previous article, we talked a little about the reasons for the appearance of TypeScript, and also examined its use in comparison with JavaScript. In this article, we will prepare our working environment for more efficient and high-quality work.

## Setting up EditorConfig

In order for the formatting of all the files we create to be correct, let's set up **EditorConfig**. [EditorConfig](https://editorconfig.org) is a tool that regulates some basic settings of files created in the editor: encoding, line break character and tab parameters. This is an extremely convenient way to configure the above parameters, which guarantees uniformity when working in different code editors and even different operating systems.


The tool is supported by a huge number of development environments. In some of them by default, in some through the installation of the plugin. Information about support and links to plugins for all editors can be found on the [official page of the utility](https://editorconfig.org).

Now the actual setup. In the project's root, create a file **.editorconfig** and put the following content there:

```conf
root=true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true

[*.{js,ts}]
indent_style = space
indent_size = 2

[{package.json,tsconfig*.json}]
indent_style = space
indent_size = 2
```

This configuration sets UTF-8 encoding for all files in the project, as well as line wrapping `if` and adding an empty line to the end of the file when saving it, as is customary in Unix systems.

There is a generally accepted code standard in the JavaScript ecosystem, which we will get to know better later. It regulates two spaces as a tab character. Therefore, we set these settings for the files **package.json**, **tsconfig.json** and all **\*.js**, **\*.ts** files.


To make sure that everything works, you can open the file **index.ts** and using the tab character. Also, when saving, one empty line should be added at the end of the file. Make sure that the file is formatted correctly, if not, make the necessary changes.

If everything works and is formatted properly, we can move on.

## Basic configuration of **tsconfig.json**

Up to this moment, when calling the build, we passed the parameters directly to the command line command. In practice, this is rare, since usually a project contains a fairly large number of options, so it can be inconvenient to pass them as arguments. For these purposes, there is a configuration file **tsconfig.json**. When the **tsc** command is called, this file is searched for and parameters are read from it.


In this article, we will get acquainted with the “essentials” options. And in the future we will consider more advanced settings. In the root of the project, create a file **tsconfig.json** and add the following content to it:

```json
{
  "compilerOptions": {
    "outDir": "public/scripts",
    "target": "es2015",
    "module": "es2015",
    "moduleResolution": "node"
  },
  "files": [
    "src/index.ts"
  ]
}
```

For greater clarity, the project will be developed to work in the browser, so the `OutDir` parameter has been changed to `public/scripts`. The name "public" is a common name on the web for a folder that stores "assets", such as scripts, style sheets, images and html pages.


The `target` option has already been installed by us earlier. The parameter determines which version of the ECMAScript standard the source TypeScript code will be compiled into. It is absolutely normal practice in web development to write code using the latest language specifications, which may not yet be supported by all browsers, and in the process of building the project to transform the code into older versions of the language. The es2015 (ES6) specification has fairly broad support in modern browsers. So first let's leave target with the value es2015.


The `module` option allows you to define the modular system that will be used for connecting files. The ES6 specification has its own import and export syntax for these purposes. Also in the JavaScript ecosystem there are other, older modular systems such as **CommonJS** and **AMD**. We can safely use the ES6 standard code, but at the same time redefine the modular system to another one. A little later we'll look at why this might be necessary, but for now let's set module to es2015 as well.


The `moduleResolution` option is quite specific and practically unnecessary in everyday life. However, some options depend on each other and one option may implicitly change the value of another if its value has not been explicitly set. This behavior can lead to compilation errors. So 'moduleResolution' when installing `module` in some values changes automatically to the outdated value `classic`, which was used in TypeScript. To cancel such unforeseen cases, let's explicitly set `moduleResolution` to the current value of `node`.


The file to compile from the **tsc** command argument has moved to the `files` option. As you can see, the option accepts an array of files, so if you need to transpile several unrelated files, for example, for different pages of the application, then they can be placed here.


It should be noted that if the file **index.ts** contains the connection code (`import`) of other **\*.ts** files, then they will be automatically included in the build and there is no need to specify them in `files`.


Now let's edit the package.json:

```json
{
  "name": "dev-to-project",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "start": ""
  },
  "devDependencies": {
    "typescript": "^4.2.4"
  }
}
```

We cleaned up a little extra for convenience. We removed the empty `description`, the now unnecessary `main` and the redundant `author` and `license`. We also removed the `test` script. The `start` script has been left empty for now and the `build` command has simply been changed to `tsc`.


Let's run the build and make sure that the file is being created **public/scripts/index.js**. We will delete the **dist** folder from the last build.

## Setting up the launch in the browser

To begin with, we will bring the code to a more "natural" form. In real projects, they don't write everything in one file. Select the entity `Book` in a separate file **book.ts**, and the set of books in **book-collection.ts**.


```ts
//book.ts
export class Book {
  name: string
  genre: string
  pageAmount: number

  constructor (name: string, genre: string, pageAmount: number) {
    this.name = name
    this.genre = genre
    this.pageAmount = pageAmount
  }
}
```

```ts
// book-collection.ts
import { Book } from './book.js'

export const books = [
  new Book('Harry Potter', 'fantasy', 980),
  new Book('The Fellowship of the Ring', 'fantasy', 1001),
  new Book('How to be productive', 'lifestyle', 500),
  new Book('A Song of Ice and Fire', 'fantasy', 999) 
]
```

```ts
// index.ts
import { Book } from './book.js'
import { books } from './book-collection.js'

function findSuitableBook (
  genre: string,
  pagesLimit: number,
  multipleRecommendations = true
): Book | Book[] {
  const findAlgorithm = (book: Book) => {
    return book.genre === genre && book.pageAmount <= pagesLimit
  }

  if (multipleRecommendations) {
    return books.filter(findAlgorithm)
  } else {
    return books.find(findAlgorithm)
  }
}

const recommendedBook = findSuitableBook('fantasy', 1000)

if (recommendedBook instanceof Book) {
  console.log(recommendedBook.name)
} else {
  console.log(recommendedBook[0].name)
}
```

It is fair to note that the **js** extension is used in the import, not **ts**, despite the fact that the attached files actually have the **ts** extension in the source code base. You cannot use the **ts** extension in the `import` construction.

Let's build a project - `npm run build`


It's time to run our code in the browser. To do this, create a file ** in the **public** folderindex.html ** with the following contents:

```html
<!--index.html-->
<!DOCTYPE html>
<head>
    <script type="module" src="/scripts/index.js"></script>
</head>
```

> **Important**! Due to the fact that we use ES6 imports, when connecting the `script` tag, it is necessary to set the `type` attribute equal to `module`. Without this, an error will occur when executing the script.

Since the initial slash in the script address is **/scripts/index.js** means that the script needs to be searched on the same server. That is, open **index.html** just as a file is not enough. We need to start the server. To do this, install the following package:

```bash
npm install --save-dev http-server
```

Also, let's add the server startup command to the **package.json**:

```json
{
  "name": "dev-to-project",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "start": "http-server -p 3000"
  },
  "devDependencies": {
    "http-server": "^0.12.3",
    "typescript": "^4.2.4"
  }
}
```

Let's start the server by executing 'npm start'. The **http-server** utility will host the contents of the **public** folder on the server at **http://localhost:3000**. Let's open this address in the browser, open the console and make sure that we see the message "Harry Potter”.

## Setting up **tsconfig.json** for different environments

We made sure that everything is configured correctly and the build is running. Since we have to edit a lot of code, it would be great to rebuild the project automatically when files are changed, rather than starting the build manually. This is extremely convenient for development, but it is absolutely not necessary to create a production assembly. Therefore, we need to create two build commands: `build` - for the production environment and `build:dev` - for development.

Each of these commands will use its own **tsconfig.json** with slightly different parameters and delete the result of the previous build before starting a new process.

First, let's make changes to the **package.json**:

```json
{
  "scripts": {
    "build": "rm -rf public/scripts && tsc",
    "build:dev": "rm -rf public/scripts && tsc -p tsconfig-dev.json",
    "start": "http-server -p 3000"
  },
  "devDependencies": {
    "http-server": "^0.12.3",
    "typescript": "^4.2.4"
  }
}
```

> **Important**! If you are running on Windows, the `build` and `build:dev` commands must be specified as follows: `rd -r public\\scripts 2>null & tsc` and `rd -r public\\scripts 2>null & tsc -p tsconfig-dev.json`.

The `rm -rf public/scripts` command will delete the folder with the previously prepared code and immediately after that start the build process. The `build` command as before, it uses the **tsconfig.json** file, and `build:dev` uses the `-p` argument to specify the path to another configuration file - **tsconfig-dev.json**. Let's create it:

```json
{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "watch": true,
        "inlineSourceMap": true,
        "inlineSources": true
    }
}
```

Here we use the `extends` option to inherit the settings from the original configuration file. This avoids duplication and the support of two configs. We only redefine the options that interest us. In this case, we specify the `watch` option with the value `true` to tell the compiler that we want to monitor changes to the source files and run the build every time they change.


But the differences between **production** and **development** environments do not end there. We have added two more flags. In order to understand why they are needed, we need to open the page of our application and pay attention to the line "Harry Potter” in the console. There you can notice that the message was created by line number 19 of the file **index.js**.
In other words, the browser shows the origin of the message in the already compiled code. This will not be convenient in the future when searching and correcting errors. For these purposes, we added the `inlineSourceMap` and `inlineSources` flags. They include its source code in the compiled code, which allows the browser to refer to the original code during debugging.


Let's see what it looks like and check how it works in practice. Run the command `npm run build:dev`. The first thing we can notice is that the console has not returned control to us, instead we see a message like:

```log
[6:23:22 PM] Starting compilation in watch mode...

[6:23:23 PM] Found 0 errors. Watching for file changes.
```

The compiler keeps track of the source files. Let's open the file **book-collection.ts**, add “and the Philosopher\'s Stone" to the title of the book “Harry Potter" and save the file. Now open the compiled **book-collection file.js** and check its contents.

It can be seen that the title of the book has changed and we did not have to run the build manually for this. You can also notice a large line starting with the characters "//# sourceMappingURL" - this is the source code map. In order to test it in action, let's refresh the application page and look at the message "Harry Potter and the Philosopher's Stone". Now we see that the message refers to the source file **index.ts** line 21.


So, we have completed the configuration of our project at this stage. At the end of the series of articles, we will return to this issue to learn more about all kinds of TypeScript options.

## Setting up ESLint

Before taking the next steps in the direction of TypeScript, we will prepare a working environment according to all industry standards. We have already set up EditorConfig so that all the files we create comply with certain rules. [ESLint](https://eslint.org) is a code quality control tool. It allows you to describe a list of rules for formatting code, language constructs used, and so on. This allows you to write code in a single style. If these rules are not followed, the code will be underlined in the editor. You can also run the check from the console.


It is considered good practice to run checks on the `git` hook `pre-commit` and before building the project during deployment. Since this does not relate to the topic of this article, we will not dwell on it here. Just configure the checks in the IDE and the commands to run through the console.


The Webstorm IDE has ESLint support by default, and a [plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) must be installed for Visual Studio Code. The plugin exists for other code editors as well.

There are a large number of ready-made configurations. Several of them are the main accepted standards in the industry, for example, [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) or [JavaScript Standard Style](https://standardjs.com). But they will be redundant for our training project. Included with ESLint standard config with minimal settings is which we will use.


In addition to ESLint itself, we will need additional packages to support TypeScript syntax. We will install all the necessary dependencies:

```bash
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
```

In order for the IDE to start validating the code, you need to create a configuration file **.eslintrc** in the root of the project:

```json
{
    "root": true,
    "parser": "@typescript-eslint/parser",
    "plugins": [
      "@typescript-eslint"
    ],
    "extends": [
      "eslint:recommended",
      "plugin:@typescript-eslint/eslint-recommended",
      "plugin:@typescript-eslint/recommended"
    ]
}
```


This configuration file will "teach" ESLint to understand TypeScript syntax and apply the most standard rules recommended by the ESLint team.
It is normal practice to modify the config to suit your needs if necessary. Since the standard config has a minimum of settings, we will add some options there:

```json
{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "@typescript-eslint"
  ],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "quotes": ["error", "single"],
    "indent": ["error", 2]
  }
}
```

In the `rules` section, we added a couple of mandatory conditions - single quotes and two space characters for indentation must be used. The full list of rules can be found on the official ESLint website and in the repository of the TypeScript ESLint plugin.


The IDE should now respond to the use of double quotes or tab characters. Try using four spaces instead of two or double quotes instead of single ones. If the code is underlined in these places, it means that everything is configured correctly. Do not rush to fix these errors, we will do it using ESLint. First, let's add the linting scripts for the command line. To do this, add to the `scripts` section of **project.json** file two more commands:

```json
"scripts": {
  "build": "rm -rf public/scripts && tsc",
  "build:dev": "rm -rf public/scripts && tsc -p tsconfig-dev.json",
  "start": "http-server -p 3000",
  "lint": "eslint src --ext .js --ext .ts",
  "lint-fix": "eslint src --ext .js --ext .ts --fix"
}
```

By running `npm run lint` we should see all the errors in the console. And by running `npm run lint-fix`, automatic error correction will be performed. Those errors that are not corrected automatically must be corrected manually.


And so, we got a little acquainted with TypeScript and prepared an effective environment with a lot of important tools that we will use regularly. In the next articles in the series, we will get to know TypeScript and, in particular, data types more closely.