Portfolio
Typescript
React

Actualizando Portafolio [Parte 5]

En el episodio de hoy, hacemos la migración a Typescript del proyecto de portafolio el cual usa React.

Ivan Robles

5 min read
Actualizando Portafolio [Parte 5]

Esta es una serie de posts, describiendo los procesos que llevé acabo para la creación de mi portafolio, el cual se encuentra en: https://sharmaz.github.io/me/

[Parte 1, El diseño]
[Parte 2, El setup]
[Parte 3, El desarrollo]
[Parte 4, Los workflows]
[Parte 5, La migración a typescript] 👈️ Aquí estamos

En el episodio de hoy, hacemos la migración a Typescript del proyecto de portafolio el cual corre React con archivos .jsx, donde gracias a Github Actions y sus Workflows, corremos pruebas, linter y deployamos.

Al hacer esta migración me encontré con una serie de errores, voy a mencionar lo que tuve que hacer para resolverlos.

Pero primero hay que instalar las dependencias necesarias para usar typescript con react:

npm install --save-dev typescript ts-loader @typescript-eslint/eslint-plugin

Después de esto creamos el archivo de configuración de Typescript tsconfig.json donde le vamos definir lo siguiente:

  • allowJS, Vamos a permitir Javascript normal, esto es porque vamos a comenzar a migrar por partes y no queremos que se rompa la aplicación.
  • target, vamos a transpilar a ES5 para que nuestra app sea compatible con navegadores viejos.
  • module, el tipo de módulos que vamos a utilizar, algunas dependencias utilizan commonjs, aunque nosotros vamos a usar ES modules, le aclararemos eso mas adelante.
  • jsx, usamos react-jsx porque React.
  • outDir, Es el directorio de salida a donde se va a transpilar nuestra app (para distribución).
  • strict, Si queremos modo estricto.
  • esModuleInterop, Vamos a usar ES modules entonces activamos esta opción.
  • Include, vamos a incluir los archivos o rutas donde vamos a usar TS.
  • Exclude, vamos a excluir las dependencias en node modules.
{
  "compilerOptions": {
    "allowJs": true,
    "target": "es5",
    "module": "commonjs",
    "jsx": "react-jsx",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["./src/**/*",],
  "exclude": ["./node_modules"],
}

Esta es la configuración inicial de Typescript, mas adelante vamos a ir agregando más opciones.

Ahora nos vamos al archivo de configuración de Webpack, como estamos creando una migración paso a paso, vamos a agregar a nuestra lista de resolve las extensiones de .ts y .tsx:

resolve: {
  extensions: ['.js', '.jsx', '.json', '.tsx', '.ts'],
},

Luego agregamos la regla para usar estas extensiones usando ts-loader:

{
  test: /\\.(ts|tsx)$/,
  exclude: /node_modules/,
  use: 'ts-loader',
},

Ahora nos pasamos al package.json para agregar el script de Typescript para verificar que todo este correcto:

"scripts": {
  ...
  "typecheck": "tsc"
}

Si ejecutamos npm run typecheck, nos va a saltar un error: No inputs were found in config file in TypeScript debido que no tenemos todavía archivos .ts o .tsx, en nuestro ./src podemos agregar un archivo ./src/placeholder.ts con el siguiente contenido:

export {};

Sirve por el momento ya que solo estamos haciendo la configuración, aunque igual podemos pasar a la acción modificando alguno de nuestros componentes actuales.

Ahora cuando comencemos a cambiar de .jsx a .tsx ESlint nos va a regañar con el mensaje:  Disallow file extensions that may contain JSX, hay que agregar la extensión de .tsx en las reglas de react/jsx en nuestro archivo .eslintrc.js:

rules: {
	...
	'react/jsx-filename-extension': [1, { extensions: ['.js', 'ts', '.jsx', '.tsx'] }],
  ...
},

Y de una vez en ese mismo archivo le agregamos el plugin de typescript-eslint en extends y en plugins, ademas yo utilizo el estilo de código de airbnb, hay que instalar su versión en Typescript y agregarla a los extends:

npm install -D eslint-config-airbnb-typescript

Y luego:

"extends": [
  ...
  "plugin:@typescript-eslint/recommended",
	"airbnb-typescript",
],

"plugins": [
  ...
  "@typescript-eslint"
],

En la mera migración de los archivos jsx a tsx nos vamos a ir pa’ atras, al usar imágenes vamos a tener problemas, en mi caso yo estoy usando formatos svg y webp, Typescript nos va a decir que cannot find module 'loader.svg' or its corresponding type declarations.

Yo estoy usando un loader para svgs que se llama SVGR, hay que agregar opciones para especificarle que se van a usar extensiones tsx:

{
  test: /\\.svg$/i,
  issuer: /\\.[jt]sx?$/,
  resourceQuery: { not: [/url/] },
  use: [
    {
      loader: '@svgr/webpack',
      options: {
        typescript: true,
        ext: 'tsx',
      },
    },
  ],
},

Sin embargo esto no soluciona el problema, debido a que yo estoy utilizando una imagen loader.svg como componente:

import LoaderSvg from '../assets/images/loader.svg';

<LoaderSvg className="animate-spin-slow" />

Hay que tipar las imágenes, hay que crear un archivo src/types/svg.d.ts:

declare module '*.svg' {
  import React from 'react';

  const SVG: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
  export default SVG;
}

Y ahora podemos crear otro archivo similar para imágenes, yo utilizo solo svgs y webp pero funciona también para los demás tipos de imágenes, este archivo puede ser src/types/images.d.ts:

declare module '*.webp';
declare module '*.png';
declare module '*.jpg';

Estos archivos hay que incluirlos en nuestra configuración de Typescript tsconfig.json :

"include": [
    "./src/**/*",
    "./src/types"
  ],

Con esto ya estaría resuelto el problema de cargar imágenes en archivos .tsx.

Pero me fui pa’tras otra vez al darme cuenta de que algunas cosas de TailwindCSS no funcionan como las animaciones, hay que hacer un cambio en el tailwind.config.cjs y es especificar que vamos a usar Tailwind en archivos .tsx:

content: ['./src/**/*.{js,jsx,tsx}', './dist/index.html'],

De esta manera nos aseguramos que nuestro proyecto fluya de manera correcta al hacer la transición a Typescript.

…O no, te puede suceder como a mi que tuve una serie de errores al incluir Jest y algunas cosas chocaban con ESlint lo siguiente en este post es parecido a un espagueti de errores y como atacarlos.

Por  tercera vez me fui pa’tras porque truenan las pruebas en Jest quien se queja con este error Jest encountered an unexpected token hay que instalar y configurar:

npm install --save-dev @types/jest ts-jest @babel/preset-typescript

En .babelrc le agregamos el preset de Typescript:

"presets": [
      "@babel/preset-env",
      "@babel/preset-typescript",
      ["@babel/preset-react",
      { "runtime": "automatic" }]
  ],

Bien, ahora hay que agregar un transform en jest.config.js:

/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  transform: {
    '^.+\\\\.[tj]sx?$': ['ts-jest'],
  },
	...
};

Siguiendo con la migración, note que comencé a tener algunos problemas con las propTypes: **Type 'Requireable<(InferProps<{ id: Requireable<string>; }> | null | undefined)[]>' is not assignable to type 'Validator<Jobs>'**, no tiene sentido tenerlas cuando estas haciendo Typescript ya que estamos haciendo validación por types. La solución fácil para esos errores fue deshacerme de propTypes.

…Adiós vaquero 😟

Cuando pensaba que todo iba bien, me salió el siguiente error al correr el linter Error while loading rule '@typescript-eslint/dot-notation': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.

Agregamos en parserOptions el path o ruta de nuestro archivo de configuración de Typescript:

"parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module",
    "project": ["./tsconfig.json"]
  },

También tuve un problema donde el componente  <App /> era confundido por un elemento type que no existía, las solución era que la extensión del test debía ser .tsx y yo la tenía como .ts .

El siguiente error me ocurrió primero con ESlint, luego con Jest, Webpack , Tailwind y Babel: Parsing error: ESLint was configured to run on '<tsconfigRootDir>/jest.config.js' using 'parserOptions.project': <tsconfigRootDir>/tsconfig.json  y la solución es la misma, solo hay que incluir los demás archivos de configuración en .tsconfig.json de la siguiente manera:

"include": [
    ".eslintrc.js",
    ".babelrc",
    "jest.config",
    "jest.setup.js",
    "postcss.config.cjs",
    "tailwind.config.cjs",
    "webpack.config.js",
    "./src/**/*",
    "./src/types"
  ],

Con esto ya deberíamos poder seguir con la migración de JSX a TSX.

Conclusión

Esta es la ultima parte de esta serie de posts.

Probablemente esta configuración vaya a cambiar con el tiempo y así lo aseguro ya que esta pensado en que sea mi Sandbox para probar cosas nuevas y experimentar.

Y pensándolo bien creo que de aquí en adelante si hago cambios voy a hacer fork de este repositorio (el cual probablemente ya cambió).

Repo: https://github.com/Sharmaz/sharmaz-me-dev

Nos vemos en el próximo post!! See ya in Space!! Cowboys 🤠