DEV Community

Cover image for TypeScript. Advanced Project Setup
Elijah Zobenko
Elijah Zobenko

Posted on

TypeScript. Advanced Project Setup

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 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.

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

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
Enter fullscreen mode Exit fullscreen mode

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:

{
  "compilerOptions": {
    "outDir": "public/scripts",
    "target": "es2015",
    "module": "es2015",
    "moduleResolution": "node"
  },
  "files": [
    "src/index.ts"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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:

{
  "name": "dev-to-project",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "start": ""
  },
  "devDependencies": {
    "typescript": "^4.2.4"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

//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
  }
}
Enter fullscreen mode Exit fullscreen mode
// 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) 
]
Enter fullscreen mode Exit fullscreen mode
// 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)
}
Enter fullscreen mode Exit fullscreen mode

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:

<!--index.html-->
<!DOCTYPE html>
<head>
    <script type="module" src="/scripts/index.js"></script>
</head>
Enter fullscreen mode Exit fullscreen mode

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:

npm install --save-dev http-server
Enter fullscreen mode Exit fullscreen mode

Also, let's add the server startup command to the package.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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

{
  "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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "watch": true,
        "inlineSourceMap": true,
        "inlineSources": true
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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

[6:23:23 PM] Found 0 errors. Watching for file changes.
Enter fullscreen mode Exit fullscreen mode

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 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 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 or JavaScript Standard Style. 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:

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
Enter fullscreen mode Exit fullscreen mode

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

{
    "root": true,
    "parser": "@typescript-eslint/parser",
    "plugins": [
      "@typescript-eslint"
    ],
    "extends": [
      "eslint:recommended",
      "plugin:@typescript-eslint/eslint-recommended",
      "plugin:@typescript-eslint/recommended"
    ]
}
Enter fullscreen mode Exit fullscreen mode

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:

{
  "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]
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

"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"
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)