Búsqueda de sitios web

gestión de dependencias - Prevención de importaciones inapropiadas y aplicación de la jerarquía del proyecto en Typescript


En un proyecto de TS me gustaría que lo siguiente estuviera bloqueado:

  • Un archivo de la carpeta common importando desde la carpeta projectA
  • Un archivo de la carpeta projectB importando desde la carpeta projectA

Me gustaría que se permitiera lo siguiente:

  • Un archivo de la carpeta projectA importando desde la carpeta common.

Estoy al tanto de las Referencias. Sin embargo, según entiendo, requieren una compilación para la verificación de tipos (si se hace tal separación, primero se debe compilar para crear los archivos d.ts), lo cual preferiría evitar.

¿Qué opciones tengo? ¿Es posible lograrlo simplemente mediante archivos tsconfig separados para cada uno de esos proyectos/carpetas?

Respuesta #1

TLDR; Realmente deberías usar Referencias. Es exactamente para lo que sirven.

Pero primero abordemos algunos de tus pensamientos específicos:

  1. ¿Es posible lograrlo simplemente mediante archivos tsconfig separados para cada uno de esos proyectos/carpetas?

    Sí, pero no es muy flexible. Podrías aislar common configurando su rootDir a .. Entonces obtendrías un error '/path/to/projectA' no está bajo 'rootDir' si intentaras importar projectA en common. Pero para poder importar common en projectA, su rootDir tendría que ser más global, pero entonces eso permitiría que importaras projectB.

    No solo eso, según la documentación de Referencias del Proyecto:

    Anteriormente, esta estructura era bastante incómoda de trabajar si usabas un solo archivo tsconfig:

    • Era posible que los archivos de implementación importaran los archivos de prueba
    • No era posible construir test y src al mismo tiempo sin que src apareciera en el nombre de la carpeta de salida, lo que probablemente no quieras
    • Cambiar solo los internals en los archivos de implementación requería typechecking de las pruebas nuevamente, aunque esto nunca causaría nuevos errores
    • Cambiar solo las pruebas requería verificar el tipo de la implementación nuevamente, incluso si nada había cambiado

    Podrías usar múltiples archivos tsconfig para resolver algunos de esos problemas, pero aparecerían nuevos:

    • No hay comprobación actualizada incorporada, así que terminas ejecutando tsc dos veces
    • Invocar tsc dos veces incurre en más sobrecarga de tiempo de inicio
    • tsc -w no puede ejecutarse en múltiples archivos de configuración a la vez
  2. Estoy al tanto de las Referencias. Sin embargo, según entiendo, requieren una compilación para la verificación de tipos (Si se hace tal separación, primero se debe compilar para crear archivos d.ts) lo cual preferiría evitar.

    ¿Cuál es la razón de esta aversión?

    • Si se trata del costo inicial de construir un clon de un proyecto nuevo, eso se compensará ampliamente con los tiempos de compilación mejorados (ver argumentos a favor a continuación). Los beneficios de esto último para la productividad del desarrollador superarán con creces los costos de lo primero.

      ¡Irónicamente, cuanto más te preocupes por el costo inicial, mayor será el beneficio de los tiempos de construcción mejorados!

    • Si es que quieres poder navegar un clon reciente en un editor consciente de tipos y enlaces como VS Code o WebStorm sin tener que compilar, puedes lograr esto incluyendo los archivos .d.ts en el control de versiones.


Esto es lo que los documentos dicen específicamente:

Debido a que los proyectos dependientes hacen uso de archivos .d.ts que se generan a partir de sus dependencias, tendrás que registrar ciertos resultados de la compilación o compilar un proyecto después de clonarlo antes de poder navegar por el proyecto en un editor sin ver errores espurios. Estamos trabajando en un proceso de generación de .d.ts detrás de escena que debería ser capaz de mitigar esto, pero por ahora recomendamos informar a los desarrolladores que deben compilar después de clonar.

El argumento a favor de las referencias de proyecto

De los documentos:

  • puedes mejorar mucho los tiempos de construcción

    Una característica muy esperada son las compilaciones incrementales inteligentes para proyectos de TypeScript. En la versión 3.0 puedes usar la bandera --build con tsc. Esto es efectivamente un nuevo punto de entrada para tsc que se comporta más como un orquestador de compilaciones que como un simple compilador.

    Ejecutar tsc --build (tsc -b para abreviar) hará lo siguiente:

    • Encontrar todos los proyectos referenciados
    • Detectar si están actualizados
    • Construir proyectos desactualizados en el orden correcto

    No te preocupes por el orden de los archivos que pases en la línea de comandos: tsc los reorganizará si es necesario para que las dependencias siempre se construyan primero.

  • hacer cumplir la separación lógica entre componentes

  • organiza tu código de maneras nuevas y mejores.

Hay algunos beneficios/características más útiles en el documento de Referencias del Proyecto.

Configuración de ejemplo

  • src/tsconfig.json

    Incluso si no tienes código en la raíz, este tsconfig puede ser donde vayan todas las configuraciones comunes (los demás heredarán de él), y permitirá un simple tsc --build src para construir todo el proyecto (y con --force para construirlo desde cero).

    {
      "compilerOptions": {
        "outDir": ".", // prevents this tsconfig from compiling any files
    
        // we want subprojects to inherit these options:
        "target": "ES2019", 
        "module": "es2020", 
        "strict": true,
        ...
      },
    
      // building this project will build all of the following:
      "references": [  
        { "path": "./common" }
        { "path": "./projectA" }
        { "path": "./projectB" }
      ]
    }
    
    • src/common/tsconfig.json

      Debido a que common no tiene referencias, las importaciones se limitan a objetivos dentro de su directorio y npm_modules. Incluso podrías restringir este último, creo, dándole su propio package.json.

      {
        "extends": "../tsconfig.json", //inherit from root tsconfig
      
        "compilerOptions": {
        "rootDir": ".",
        "outDir": "../../build/common",
        "composite": true
        }
      }
      
    • src/projectA/tsconfig.json

      projectA puede importar common debido a la referencia declarada.

      {
        "extends": "../tsconfig.json", //inherit from root tsconfig
      
        "compilerOptions": {
        "rootDir": ".",
        "outDir": "../../build/projectA",
        "composite": true
        },
        "references": [
          { "path": "../common" }
        ]
      }
      
    • src/projectB/tsconfig.json

      projectB puede importar common Y projectA debido a las referencias declaradas.

      {
        "extends": "../tsconfig.json", //inherit from root tsconfig
      
        "compilerOptions": {
        "rootDir": ".",
        "outDir": "../../build/projectB",
        "composite": true
        },
        "references": [
          { "path": "../common" }
          { "path": "../projectA" }
        ]
      }
      

Construye

Estos son solo algunos ejemplos. Uso las formas abreviadas de los interruptores de tsc a continuación, por ejemplo, -b en lugar de --build. Todos los comandos se ejecutan desde la raíz del repositorio.

tsc -b src - construye todo el árbol.

tsc -p src/projectA/ compila solo projectA.

tsc -b src/projectA/ compila projectA y cualquier dependencia que esté desactualizada.

tsc -b -w src - compilar y observar todo el árbol.

tsc -b --clean src - elimina la salida de todo el árbol.

tsc -b -f src - forzar una reconstrucción de todo el árbol.

Usa el conmutador -d o -dry para obtener una vista previa de lo que hará tsc -b.

Respuesta #2

Sugiero usar un linter para ese trabajo, no es necesario ajustar el paso de compilación o usar Referencias de Proyecto.

eslint-plugin-import es un plugin de ESLint bastante popular, compatible con TS y puede hacer lo que quieras. Después de haber configurado typescript-eslint (si aún no lo has hecho), puedes experimentar con estas reglas:

  • import/no-restricted-paths - Restringir qué archivos se pueden importar en una carpeta determinada
  • import/no-relative-parent-imports - Evitar importaciones a carpetas en rutas relativas de padres
  • import/no-internal-modules - Evitar importar los submódulos de otros módulos

Vamos a intentar con la siguiente estructura de proyecto:

|   .eslintrc.js
|   package.json
|   tsconfig.json
\---src
    +---common
    |       common.ts
    |       
    +---projectA
    |       a.ts
    |       
    \---projectB
            b.ts

.eslintrc.js:

module.exports = {
  extends: ["plugin:import/typescript"],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    sourceType: "module",
    project: "./tsconfig.json",
  },
  plugins: ["@typescript-eslint", "import"],
  rules: {
    "import/no-restricted-paths": [
      "error",
      {
        basePath: "./src",
        zones: [
          // disallow import from projectB in common
          { target: "./common", from: "./projectB" }, 
          // disallow import from projectB in projectA
          { target: "./projectA", from: "./projectB" },     
        ],
      },
    ],
    "import/no-relative-parent-imports": "error",
  },
};

Cada zona consiste en la ruta de destino y una ruta origen. La ruta de destino es la ruta donde se deben aplicar las importaciones restringidas. La ruta origen define la carpeta que no se permite usar en una importación.

Examinando el archivo ./src/common/common.ts:

import { a } from "../projectA/a"; // works 
// Error: Unexpected path "../projectB/b" imported in restricted zone.
import { b } from "../projectB/b";

La regla import/no-relative-parent-imports también se queja por ambas importaciones, como para a.ts:

No se permiten importaciones relativas desde directorios superiores. Por favor, pase lo que está importando en tiempo de ejecución (inyección de dependencias), mueva common.ts al mismo directorio que ../projectA/a o considere convertir ../projectA/a en un paquete.

La tercera regla import/no-internal-modules no se utilizó, pero también la menciono aquí, ya que puede ser muy útil para restringir el acceso a carpetas/módulos hijos y emular (al menos) algún tipo de modificador interno de paquete en TS.

Artículos relacionados