Cómo agregar TypeScript a un proyecto de JavaScript

Me encanta escribir código. Y quiero ser realmente bueno en eso. Pero de alguna manera, escribir JavaScript nunca ha sido mi fuerte.

No importa cuánto practique, los mismos errores seguían apareciendo en producción: cannot read property of undefinedexcepciones, la famosa [Object object]cadena e incluso llamadas a funciones con un número inválido de parámetros.

Además, la mayoría de las bases de código en las que estaba trabajando eran JavaScript realmente grandes. Así que aquí hay un bonito diagrama de cómo me sentí siendo yo:

En esta publicación, evitaré explicar por qué TypeScript es increíble (y lo es), y me centraré en las tareas que debe completar si desea migrar su proyecto de JavaScript vanilla a un proyecto de TypeScript mixto.

Al final de la publicación, serás una persona más feliz y podrás responder las siguientes preguntas:

  • ¿Cómo puedo agregar tipos a mi proyecto de JavaScript?
  • ¿Qué es TypeScript?
  • ¿Cómo puedo usar TypeScript en un proyecto de JavaScript?
  • ¿Cuáles son los pasos para convertir una aplicación JavaScript para que admita TypeScript?
  • ¿Cómo puedo encargarme de la construcción y el embalaje?
  • ¿Cómo puedo cuidar de las pelusas?
  • ¿Cómo puedo "vender" TypeScript a mi organización y desarrolladores?

¿Cómo puedo agregar tipos a mi proyecto de JavaScript?

Vanilla JavaScript no admite tipos en este momento, por lo que necesitamos algún tipo de abstracción además de JavaScript para hacerlo.

Algunas abstracciones comunes están utilizando estática de tipo ortográfico de Facebook llamado flowy el lenguaje de Microsoft llama: typescript.

Esta publicación de blog examinará el uso y la adición de TypeScript a su proyecto de JavaScript.

¿Qué es Typecript?

TypeScript es un superconjunto escrito de JavaScript que se compila en JavaScript simple.

TypeScript consta de algunas partes. El primero es el lenguaje TypeScript: este es un nuevo lenguaje que contiene todas las funciones de JavaScript. Consulte las especificaciones para obtener más información.

El segundo es el compilador TypeScript tsc(el motor del sistema de tipos) que es un motor de compilación que crea archivos ts y genera archivos js.

Hola mundo en TypeScript

Como ejemplo, estos son los pasos que debe seguir para escribir su primera aplicación TypeScript:

  1. instalar TypeScript con npm i typescript
  2. crea una carpeta llamada exampley cd en ella (en tu terminal)
  3. crea un archivo llamado hello.world.ts
  4. escribe el siguiente código en él:
const firstWords:string = "hello world" console.info(firstWords); 

y luego guárdelo.

5. ejecute el tsccomando para ejecutar el compilador de TypeScript en la carpeta actual

6. observe que tiene un hello.jsarchivo que ahora puede ejecutar :)

7. correr node ./hello.js

¿Cómo puedo usar TypeScript en un proyecto de JavaScript?

Hay un par de estrategias para realizar esta "migración" (tanto de empresa como de código). Los he enumerado a continuación por su "costo" y por el valor que brindan.

Sugeriría comenzar con el "soporte de aplicaciones TS" y seguir adelante después de haber demostrado el valor para su equipo de desarrollo.

El enfoque de "un pequeño paso para el hombre": adición de compatibilidad con TS para aplicaciones existentes

Mi primera sugerencia es crear una mezcla de los dos lenguajes en un solo proyecto y luego escribir todo el código "futuro" en TypeScript.

La combinación de dos idiomas en un solo proyecto suena bastante mal al principio, pero funciona bastante bien ya que TS fue creado para un uso gradual. Al principio, se puede usar como JS con archivos .ts y líneas de importación extrañas.

En esta estrategia, compilaremos los archivos TypeScript migrados y simplemente copiaremos los archivos JavaScript en una carpeta de salida.

El gran beneficio de este enfoque es que permite una curva de aprendizaje gradual para el equipo de desarrollo (y para usted) con el lenguaje y sus características. También le brinda experiencia práctica y conocimiento de sus pros y contras.

Recomiendo encarecidamente comenzar desde este paso y luego repetirlo con su equipo antes de seguir adelante. Para un rápido "cómo hacer esto", desplácese hacia abajo a la The steps to convert a javascript application to support typescriptparte.

El enfoque abierto para los negocios: agregando soporte de TS para bibliotecas existentes.

Una vez que tenga algo de experiencia práctica con TS y su equipo de desarrollo esté de acuerdo en que vale la pena seguir adelante, le sugiero que convierta sus bibliotecas y módulos internos para admitir TS.

Esto se puede hacer de dos maneras:

La primera forma implica el uso de archivos de declaración. Una simple adición de d.tsarchivos ayuda al compilador de TS a verificar el tipo de código JavaScript existente y le brinda soporte de autocompletado en su IDE.

Esta es la opción "más barata", ya que no requiere ningún cambio de código en la biblioteca. También le brinda la máxima potencia y compatibilidad con tipos en su código futuro.

La segunda forma es realizar una reescritura completa de TypeScript, que puede llevar mucho tiempo y ser propensa a errores. No lo desaconsejaría, a menos que demuestre que su equipo valora el ROI.

El esqueleto: un paso hacia el futuro

Supongo que la mayoría de los desarrolladores son "vagos" y normalmente inician su aplicación copiando desde un esqueleto (que normalmente contiene registros, métricas, configuración, etc.)

This step helps you navigate your way into a bright future, by creating an "official" skeleton for your company. It will be 100% TS, and deprecates the old JS skeleton if one exists.

This typescript-node-starter is a really good first project to start with.

The all in approach - Converting a full codebase from JS into TS

This option requires a total rewrite from JavaScript code to TypeScript.

I would recommend doing this as a final step in the TS migration process since it requires a total application re-write and deep knowledge of TypeScript and it's features.

You can do such a rewrite (it's a long process) in the following manner:

  1. Define clear types for your application business logic, API, & HTTP's
  2. Use @types packages for all the libraries in your package.json. Most of the libraries out there support TS, and in this process I suggest migrating them one by one (by just adding @types/ in your package.json file).
  3. Convert your application logical components in order of their importance. The more unique the business logic, the better.
  4. Convert the IO parts of your application, database layers, queues and so on.
  5. Convert your tests.

Keep in mind that there are automated tools designed to ease this process, for example ts-migrate from the Airbnb team.

It tackles this problem from a different perspective, and converts all files to TypeScript. It also allows gradual improvements (like mentioned in the steps above) while the entire codebase is TypeScript from day one.

How to convert a JavaScript application to support TypeScript.

Install typescript

by running : npm install typescript.

Typescript config file

Add a typescript config file, which can be created using the tsc --init command in you CLI.

Here is an example of how our initial config looked:

{ "compilerOptions": { "target": "esnext", "module": "commonjs", "allowJs": true, "checkJs": false, "outDir": "dist", "rootDir": ".", "strict": false, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ "strictNullChecks": true, "resolveJsonModule": true, "sourceMap": true, "baseUrl": ".", "paths": { "*": [ "*", "src/*", "src/setup/*", "src/logic/*", "src/models/*", "config/*" ] }, }, "exclude": ["node_modules", "dist"], "include": [ "./src", "./test", "./*", "./config" ] }

A few things to notice above:

  • We read all the files in the src or test or config directory (using the include flag).
  • We accept JavaScript files as inputs (using the allowJs flag).
  • We emit all of the output files in build (using the outDirflag).

Create your first .TS file in your project

I recommend starting by adding a simple TypeScript file (or changing a really simple JS file to a TS one) and deploying. Take this migration one step at a time.

Take care of your package.json file

Here is how our package.json looks before and after:

{ "scripts": { "start": "node ./application.js", "mocha": "mocha --recursive --reporter spec -r test/bootstrap.js", "test": "npm run mocha -- test/ -r test/integration/bootstrap.js", } }
{ "scripts": { "start": "node ./dist/application.js", "build-dist": "./node_modules/typescript/bin/tsc", "mocha": "mocha --recursive --reporter spec -r ./dist/test/bootstrap.js", "test": "npm run mocha -- ./dist/test/ -r ./dist/test/integration/bootstrap.js" } }

As you can see, most of the changes were about adding the prefix dist to most of our build commands. We also added a build-dist script that compiles our codebase and moves all files to a dedicated folder called dist.

Add source-map-support

One of the big issues when adding TypeScript to your project is that you are adding a layer of indirection between the code you write and the code that actually runs in production (since .ts is transpiled  to .js  in run time).

For example, imagine the following TypeScript program:

const errorMessage: string = "this is bad" throw new Error(a)

When we run it, it will throw the following stack-trace:

Error: this is bad at Object. (/Users/dorsev/work/git/example/hello.js:3:7)

This is problematic since our code-base contains only .ts files. And since most production code contains hundreds of lines, it will be really time-consuming translating these numbers and files properly.

Luckily for us, there is a solution for this called source-map-support!

This allows us to ensure that stack-traces will have proper .ts file names and line numbers like we are used to :)

This can be done by running npm install source-map-support and then adding the following line in the first lines of your application:

require('source-map-support').install();

The code now looks like this:

require('source-map-support').install(); const a:string = "this is bad" throw new Error(a)

And when we compile it we run tsc --sourcemap hello.ts. Now we get the following stack-trace which is awesome :)

Error: this is bad at Object. (/Users/dorsev/work/git/example/hello.ts:3:7)

In recent versions of nodejs, this is supported natively by using the --enable-source-maps flag.

How to take care of your build (Travis) & packaging

Let's just examine the before and after changes on our build configuration file.

This is how our .travis file looked before (simplified edition):

jobs: include: - &build-and-publish before_script: - npm install --no-optional --production - npm prune --production before_deploy: - XZ_OPT=-0 tar --exclude=.git --exclude=reports.xml --exclude=${ARTIFACTS_MAIN_DIR} --exclude=.travis.yml --exclude=test -cJf "${ARTIFACTS_PATH}/${REPO_NAME}".tar.xz * .??* - &test before_script: - npm install --no-optional script: - echo "Running tests" - npm run lint && npm test

And this is how it looked after:

jobs: include: - &build-and-publish before_script: - npm install --no-optional --production - npm run build-dist # Build dist folder - npm prune --production before_deploy: - cp -rf config/env-templates ./dist/config/ - cp -rf node_modules ./dist/ - cd dist - XZ_OPT=-0 tar --exclude=.git --exclude=reports.xml --exclude=${ARTIFACTS_MAIN_DIR} --exclude=.travis.yml --exclude=test -cJf "${REPO_NAME}.tar.xz" * - mv ${REPO_NAME}.tar.xz "../${ARTIFACTS_PATH}" - cd .. - &test before_script: - npm install --no-optional - npm run build-dist script: - echo "Running tests" - npm run lint && npm test

Notice most changes concern "packaging" to the tar.xz file and running the build-dist command before accessing the dist folder.

How can I take care of linting?

There are a couple of linting solutions available.

The first solution we used was tsfmt  –  but then we decided against it later on because it requires you to maintain two separate configurations for your project (one for TypeScript using tsfmt and a separate one for JavaScript using eslint). The project also looks deprecated.

We then found TSLint  which redirected us to the eslint plugin for TypeScript. We then configured it as follows:

This was our eslintrc.js:

module.exports = { rules: { indent: [2, 2, { SwitchCase: 1 }], 'no-multi-spaces': 2, 'no-trailing-spaces': 2, 'space-before-blocks': 2, }, overrides: [{ files: ['**/*.ts'], parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], extends: ['plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended'] }] }

Which we configured to run using a lint-fix command in our package.json which looks as follows:

{ "scripts": { "lint-fix": "node_modules/.bin/eslint . --fix" }, "pre-commit": ["lint-fix"] }

How to "sell" typescript to your development team

I believe one of the most critical aspects of introducing TypeScript to your organization is the "pitch" and how you present it to your development team.

Here is the presentation we presented internally which revolved around the following topics:

  1. Explain why we think TypeScript is awesome
  2. What is TypeScript
  3. Some basic code examples. The main point in this part is not to "teach" 100% TypeScript, since people will do that on their own. Instead, give people the feeling  that they can read and write TypeScript, and that the learning curve is not so hard.
  4. Advanced code examples, like Union types and Algebraic data-types which provide enormous values to a JS developer. This are a real treats, on top of typed-language and the compiler that will attract your developers to it.
  5. How to start using it. Encourage people to download the vs-code IDE and to add an annotation (//@ts-check) so they can start seeing the magic!  In our company, we prepared in advances some really cool mistakes that ts-check catches, and we did a live demo (2-3 minutes) to show how fast the TypeScript compiler can help them  using JS docs with type annotations or ts-check).
  6. Deep dive into some features. Explain ts.d files and @types packages which are some of the things you will encounter really early in your TypeScript codebases.
  7. Live PR's from your work. We showed the PR we created early on, and encouraged people to review it and try it out for themselves.
  8. Share some cool resources. There is a lot of content online, and it's hard to figure out good from bad. Do your teammates a solid and dig deeper and try to find quality content about the tools you use and need. Scroll down to the conclusion for my resources.
  9. Create a public pull request .  I recommend trying to get as much support as possible for its approval.

10.  Create a positive buzz in your organization about the change!

I highly recommend tweaking this list according to your team, standards, and time-constraints.

Conclusion

Typescript is super awesome! If you are writing production grade software and the business requirements and availability are high, I strongly encourage you to give typescript a try.

Just remember to take it one step at a time. New languages and frameworks are hard, so take the time to learn and to educate yourself and your team before pushing this process forward.

Create a short feedback loop and value proposition. It's hard to "sell" a new language to your team and management as it takes time and resources.

So design your migration process with short feedback loops, and try to define clear KPI's (fewer bugs in production, easier refactoring times, and so on) and make sure the value proposition for your use-case is constantly justified until it becomes the de-facto standard.  

Make learning resources readily available. I really enjoyed this talk about TypeScript first steps and this blog post about incremental migration to TypeScript.

Also, don't miss out on the deno project and the ts-node project. I'm super excited and looking forward to using them soon.