Typescript: Apoyar proyectos de "tamaño mediano"

Creado en 10 jun. 2015  ·  147Comentarios  ·  Fuente: microsoft/TypeScript

Escuchar a @nycdotnet me ha

La propuesta aquí se inició por primera vez en tiempos prehistóricos (incluso antes del n. ° 11), cuando los dinosaurios caminaban por la tierra quemada. Si bien nada en esta propuesta es novedoso, en sí mismo, creo que es hora de que abordemos el tema. La propia propuesta de Steve es la # 3394.

El problema

Actualmente, en TypeScript, es bastante fácil comenzar y ponerse en marcha, y lo hacemos más fácil cada día (con la ayuda de cosas como # 2338 y el trabajo en System.js). Esto es maravilloso. Pero hay un pequeño obstáculo a medida que crece el tamaño del proyecto. Actualmente tenemos un modelo mental que es algo como esto:

  • Proyectos de pequeño tamaño: use tsconfig.json, mantenga la mayor parte de su fuente en el directorio actual
  • Proyectos de gran tamaño: use compilaciones personalizadas, coloque el código fuente donde lo necesite

Para proyectos de pequeño tamaño, tsconfig.json le brinda una forma fácil de configurar para comenzar con cualquiera de los editores de una manera multiplataforma. Para proyectos a gran escala, es probable que termine cambiando a sistemas de compilación debido a los variados requisitos de los proyectos a gran escala, y el resultado final será algo que funcione para sus escenarios, pero es difícil de configurar porque es demasiado difícil de configurar. la variedad de sistemas y opciones de construcción.

Steve, en su entrevista, señala que este no es el modelo correcto del mundo y yo tiendo a estar de acuerdo con él. En cambio, hay tres tamaños de proyecto:

  • Proyectos de pequeño tamaño: use tsconfig.json, mantenga la mayor parte de su fuente en el directorio actual
  • Proyectos de tamaño mediano: aquellos con compilaciones estándar y componentes compartidos
  • Proyectos de gran tamaño: use compilaciones personalizadas, coloque el código fuente donde lo necesite

A medida que escala el tamaño del proyecto, argumenta Steve, debe poder escalar a través del paso medio, o el soporte de la herramienta se cae demasiado rápido.

Propuesta

Para solucionar esto, propongo que apoyemos proyectos "medianos". Estos proyectos tienen pasos de compilación estándar que podrían describirse en tsconfig.json hoy, con la excepción de que el proyecto se compila a partir de varios componentes. La hipótesis aquí es que hay bastantes proyectos a este nivel que podrían beneficiarse de este apoyo.

Metas

Proporcione una experiencia fácil de usar para los desarrolladores que crean proyectos de "tamaño mediano" tanto para la compilación de la línea de comandos como cuando trabajan con estos proyectos en un IDE.

No goles

Esta propuesta _no_ incluye la compilación opcional, ni ningún paso fuera de lo que el compilador maneja hoy. Esta propuesta tampoco cubre el empaquetado o empaquetado, que se manejarán en una propuesta separada. En resumen, como se menciona en el nombre, esta propuesta cubre solo los proyectos de 'tamaño medio' y no las necesidades de aquellos a gran escala.

Diseño

Para admitir proyectos de tamaño medio, nos centramos en el caso de uso de un tsconfig.json que hace referencia a otro.

Ejemplo tsconfig.json de hoy:

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    },
    "files": [
        "core.ts",
        "sys.ts"
    ]
}

Sección tsconfig.json 'dependencias' propuesta:

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    },
    "dependencies": [
        "../common", 
        "../util"
    ],
    "files": [
        "core.ts",
        "sys.ts"
    ]
}

Las dependencias apuntan a:

  • Un directorio, donde se puede encontrar un tsconfig.json
  • Un tsconfig.json directamente

Las dependencias son jerárquicas. Para editar el proyecto completo, debe abrir el directorio correcto que contiene la raíz tsconfig.json. Esto implica que las dependencias no pueden ser cíclicas. Si bien puede ser posible manejar dependencias cíclicas en algunos casos, en otros casos, es decir, aquellos con tipos que tienen dependencias circulares, puede que no sea posible hacer una resolución completa.

Cómo funciona

Las dependencias se crean primero, en el orden en que se enumeran en la sección 'dependencias'. Si una dependencia no se genera, el compilador se cerrará con un error y no continuará con la compilación del resto del proyecto.

A medida que se completa cada dependencia, se pone a disposición de la compilación actual un archivo '.d.ts' que representa las salidas. Una vez que se completan todas las dependencias, se crea el proyecto actual.

Si el usuario especifica un subdirectorio como una dependencia, y también implica su compilación al no proporcionar una sección de 'archivos', la dependencia se compila durante la compilación de la dependencia y también se elimina de la compilación del proyecto actual.

El servicio de idiomas puede ver cada dependencia. Debido a que cada dependencia se eliminará de su propio tsconfig.json, esto puede significar que se necesitarían crear varias instancias de servicios de idiomas. El resultado final sería un servicio de lenguaje coordinado que fuera capaz de refactorizar, navegar por el código, encontrar todas las referencias, etc. a través de dependencias.

Limitaciones

Agregar un directorio como dependencia que no tiene tsconfig.json se considera un error.

Se supone que los resultados de las dependencias son independientes y están separados del proyecto actual. Esto implica que no puede concatenar el .js de salida de una dependencia con el proyecto actual a través de tsconfig.json. Las herramientas externas, por supuesto, pueden proporcionar esta funcionalidad.

Como se mencionó anteriormente, las dependencias circulares se consideran un error. En el caso simple:

A - B
\ C

A es el 'proyecto actual' y depende de dos dependencias: B y C. Si B y C no tienen dependencias, este caso es trivial. Si C depende de B, B se pone a disposición de C. Esto no se considera circular. Sin embargo, si B depende de A, esto se considera circular y sería un error.

Si, en el ejemplo, B depende de C y C es autónomo, esto no se consideraría un ciclo. En este caso, el orden de compilación sería C, B, A, que sigue la lógica que tenemos para /// ref.

Optimizaciones / mejoras opcionales

Si una dependencia no se va a reconstruir, se omite su paso de compilación y se reutiliza la representación '.d.ts' de la compilación anterior. Esto podría extenderse para manejar si la compilación de dependencias ha construido dependencias que aparecerán más adelante en la lista de 'dependencias' del proyecto actual (como sucedió en el ejemplo dado en la sección Limitaciones).

En lugar de tratar los directorios pasados ​​como dependencias que no tienen un tsconfig.json como casos de error, podríamos aplicar opcionalmente 'archivos' predeterminados y la configuración del proyecto actual a esa dependencia.

Committed Monorepos & Cross-Project References Suggestion

Comentario más útil

Documentos / publicación de blog en progreso a continuación (se editará en función de los comentarios)

Animaría a cualquiera que siga este hilo a que lo pruebe. Estoy trabajando en el escenario de monorepo ahora para solucionar los últimos errores / características allí y debería tener alguna orientación al respecto pronto


Referencias de proyectos

Las referencias de proyectos son una nueva característica de TypeScript 3.0 que le permite estructurar sus programas de TypeScript en partes más pequeñas.

Al hacer esto, puede mejorar en gran medida los tiempos de compilación, hacer cumplir la separación lógica entre componentes y organizar su código de nuevas y mejores formas.

También estamos introduciendo un nuevo modo para tsc , el indicador --build , que funciona de la mano con las referencias del proyecto para permitir compilaciones de TypeScript más rápidas.

Un proyecto de ejemplo

Veamos un programa bastante normal y veamos cómo las referencias de proyectos pueden ayudarnos a organizarlo mejor.
Imagina que tienes un proyecto con dos módulos, converter y units , y un archivo de prueba correspondiente para cada uno:

/src/converter.ts
/src/units.ts
/test/converter-tests.ts
/test/units-tests.ts
/tsconfig.json

Los archivos de prueba importan los archivos de implementación y realizan algunas pruebas:

// converter-tests.ts
import * as converter from "../converter";

assert.areEqual(converter.celsiusToFahrenheit(0), 32);

Anteriormente, era bastante incómodo trabajar con esta estructura si usaba un solo archivo tsconfig:

  • Fue posible que los archivos de implementación importen los archivos de prueba.
  • No fue posible construir test y src al mismo tiempo sin que apareciera src en el nombre de la carpeta de salida, lo que probablemente no desee
  • Cambiar solo las partes internas en los archivos de implementación requería volver a revisar las pruebas, aunque esto nunca causaría nuevos errores
  • Cambiar solo las pruebas requirió verificar la implementación nuevamente, incluso si nada cambió

Puede usar varios archivos tsconfig para resolver algunos de esos problemas, pero aparecerían otros nuevos:

  • No hay una verificación incorporada y actualizada, por lo que siempre termina ejecutando tsc dos veces
  • Invocar tsc dos veces incurre en una mayor sobrecarga de tiempo de inicio
  • tsc -w no se puede ejecutar en varios archivos de configuración a la vez

Las referencias de proyectos pueden resolver todos estos problemas y más.

¿Qué es una referencia de proyecto?

tsconfig.json archivos references . Es una matriz de objetos que especifica proyectos para hacer referencia:

{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../src" }
    ]
}

La propiedad path de cada referencia puede apuntar a un directorio que contiene un archivo tsconfig.json , o al archivo de configuración en sí (que puede tener cualquier nombre).

Cuando haces referencia a un proyecto, suceden cosas nuevas:

  • La importación de módulos de un proyecto referenciado cargará su archivo de declaración de salida ( .d.ts )
  • Si el proyecto al que se hace referencia produce un outFile , las declaraciones del archivo de salida .d.ts serán visibles en este proyecto
  • El modo de construcción (ver más abajo) construirá automáticamente el proyecto referenciado si es necesario

Al dividirse en varios proyectos, puede mejorar en gran medida la velocidad de la verificación de tipos y la compilación, reducir el uso de memoria cuando se utiliza un editor y mejorar la aplicación de las agrupaciones lógicas de su programa.

composite

Los proyectos referenciados deben tener habilitada la nueva configuración composite .
Esta configuración es necesaria para garantizar que TypeScript pueda determinar rápidamente dónde encontrar las salidas del proyecto al que se hace referencia.
Habilitar la bandera composite cambia algunas cosas:

  • La configuración rootDir , si no se establece explícitamente, tiene como valor predeterminado el directorio que contiene el archivo tsconfig
  • Todos los archivos de implementación deben coincidir con un patrón include o aparecer en la matriz files . Si se viola esta restricción, tsc le informará qué archivos no se especificaron
  • declaration debe estar encendido

declarationMaps

También hemos agregado soporte para mapas de fuentes de declaración .
Si habilita --declarationMap , podrá utilizar funciones del editor como "Ir a la definición" y Cambiar el nombre para navegar y editar el código de forma transparente a través de los límites del proyecto en los editores compatibles.

prepend con outFile

También puede habilitar anteponer la salida de una dependencia usando la opción prepend en una referencia:

   "references": [
       { "path": "../utils", "prepend": true }
   ]

Anteponer un proyecto incluirá el resultado del proyecto por encima del resultado del proyecto actual.
Esto funciona tanto para archivos .js archivos .d.ts , y los archivos de mapas de origen también se emitirán correctamente.

tsc solo usará archivos existentes en el disco para realizar este proceso, por lo que es posible crear un proyecto donde no se puede generar un archivo de salida correcto porque la salida de algún proyecto estaría presente más de una vez en el archivo resultante .
Por ejemplo:

  ^ ^ 
 /   \
B     C
 ^   ^
  \ /
   D

En esta situación, es importante no anteponer cada referencia, porque terminará con dos copias de A en la salida de D ; esto puede llevar a resultados inesperados.

Advertencias para las referencias de proyectos

Las referencias de proyectos tienen algunas ventajas y desventajas que debe conocer.

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

Además, para preservar la compatibilidad con los flujos de trabajo de compilación existentes, tsc no creará dependencias automáticamente a menos que se invoque con el conmutador --build .
Aprendamos más sobre --build .

Modo de compilación para TypeScript

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

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

  • Encuentra todos los proyectos referenciados
  • Detecta si están actualizados
  • Cree proyectos obsoletos en el orden correcto

Puede proporcionar tsc -b con múltiples rutas de archivo de configuración (por ejemplo, tsc -b src test ).
Al igual que tsc -p , especificar el nombre del archivo de configuración en sí es innecesario si se llama tsconfig.json .

tsc -b Línea

Puede especificar cualquier número de archivos de configuración:

 > tsc -b                                # Build the tsconfig.json in the current directory
 > tsc -b src                            # Build src/tsconfig.json
 > tsc -b foo/release.tsconfig.json bar  # Build foo/release.tsconfig.json and bar/tsconfig.json

No se preocupe por ordenar los archivos que pasa en la línea de comandos: tsc los reordenará si es necesario para que las dependencias siempre se construyan primero.

También hay algunas banderas específicas para tsc -b :

  • --verbose : Imprime un registro detallado para explicar lo que está sucediendo (se puede combinar con cualquier otra bandera)
  • --dry : muestra lo que se haría pero en realidad no crea nada
  • --clean : Elimina las salidas de los proyectos especificados (se puede combinar con --dry )
  • --force : Actúe como si todos los proyectos estuvieran desactualizados
  • --watch : Modo reloj (no se puede combinar con ninguna bandera excepto --verbose )

Advertencias

Normalmente, tsc producirá salidas ( .js y .d.ts ) en presencia de errores de sintaxis o tipo, a menos que noEmitOnError esté activado.
Hacer esto en un sistema de compilación incremental sería muy malo: si una de sus dependencias desactualizadas tuviera un nuevo error, solo lo vería una vez porque una compilación posterior omitiría la compilación del proyecto ahora actualizado.
Por esta razón, tsc -b actúa efectivamente como si noEmitOnError estuviera habilitado para todos los proyectos.

Si verifica cualquier salida de compilación ( .js , .d.ts , .d.ts.map , etc.), es posible que deba ejecutar una compilación --force después de cierto control de fuente operaciones dependiendo de si su herramienta de control de código fuente conserva mapas de tiempo entre la copia local y la copia remota.

msbuild

Si tiene un proyecto de msbuild, puede activar el modo de compilación agregando

    <TypeScriptBuildMode>true</TypeScriptBuildMode>

a su archivo de proyecto. Esto permitirá la construcción incremental automática así como la limpieza.

Tenga en cuenta que, al igual que con tsconfig.json / -p , las propiedades del proyecto TypeScript existente no se respetarán; todas las configuraciones deben administrarse utilizando su archivo tsconfig.

Algunos equipos han configurado flujos de trabajo basados ​​en msbuild en los que los archivos tsconfig tienen el mismo orden de gráficos implícito que los proyectos administrados con los que están emparejados.
Si su solución es así, puede continuar usando msbuild con tsc -p junto con las referencias del proyecto; estos son completamente interoperables.

Guia

Estructura general

Con más archivos tsconfig.json , generalmente querrá usar la herencia de archivos de configuración para centralizar las opciones comunes del compilador.
De esta manera, puede cambiar una configuración en un archivo en lugar de tener que editar varios archivos.

Otra buena práctica es tener una "solución" tsconfig.json archivo que simplemente tiene references para todos sus proyectos de nodo hoja.
Esto presenta un punto de entrada simple; por ejemplo, en el repositorio de TypeScript simplemente ejecutamos tsc -b src para construir todos los puntos finales porque enumeramos todos los subproyectos en src/tsconfig.json
Tenga en cuenta que a partir de 3.0, ya no es un error tener una matriz files vacía si tiene al menos un reference en un archivo tsconfig.json .

Puede ver estos patrones en el repositorio de TypeScript; consulte src/tsconfig_base.json , src/tsconfig.json y src/tsc/tsconfig.json como ejemplos clave.

Estructuración de módulos relativos

En general, no se necesita mucho para realizar la transición de un repositorio utilizando módulos relativos.
Simplemente coloque un archivo tsconfig.json en cada subdirectorio de una carpeta principal determinada y agregue reference s a estos archivos de configuración para que coincidan con las capas previstas del programa.
Deberá establecer outDir en una subcarpeta explícita de la carpeta de salida, o establecer rootDir en la raíz común de todas las carpetas del proyecto.

Estructuración para outFiles

El diseño de las compilaciones que usan outFile es más flexible porque las rutas relativas no importan tanto.
Una cosa a tener en cuenta es que generalmente no querrá usar prepend hasta el "último" proyecto; esto mejorará los tiempos de compilación y reducirá la cantidad de E / S necesaria en una compilación determinada.
El repositorio de TypeScript en sí mismo es una buena referencia aquí; tenemos algunos proyectos de "biblioteca" y algunos proyectos de "punto final"; Los proyectos de "endpoint" se mantienen lo más pequeños posible y solo incorporan las bibliotecas que necesitan.

Estructuración para monorepos

TODO: Experimente más y descubra esto. Rush y Lerna parecen tener diferentes modelos que implican diferentes cosas de nuestra parte.

Todos 147 comentarios

¡Oh Dios mío!

: +1:

¡Sí! Esto tiene mucho sentido para los casos de uso que ha proporcionado. Proporcionar a las herramientas que usamos una mejor visión de cómo se pretende consumir nuestro código es definitivamente lo correcto. ¡Cada vez que F12 en un archivo .d.ts por error siento ganas de estrangular a un gatito!

Jonathan,

Muchas gracias por los amables comentarios y por decidir aceptar esto. TypeScript es una herramienta tan excelente y esta funcionalidad ayudará a muchas personas que desean componenteizar sus bases de código de tamaño mediano que no pueden justificar la ineficiencia exigida por proyectos enormes que se basan en una división estricta de preocupaciones (por ejemplo, los portales de Azure o el proyecto Monacos de el mundo con> 100kloc y muchos equipos independientes). En otras palabras, esto realmente ayudará a las "personas normales". Además, otros ya han propuesto cosas para esto, por ejemplo @NoelAbrahams (# 2180), y otros, así que no puedo reclamar originalidad aquí. Esto es solo algo que he estado necesitando por un tiempo.

Creo que tu propuesta es excelente. El único defecto que veo frente a mi propuesta (# 3394), que ahora he cerrado, es la falta de un mecanismo de respaldo para las referencias.

Considere el siguiente escenario del mundo real que detallé aquí: https://github.com/Microsoft/TypeScript/issues/3394#issuecomment -109359701

Tengo un proyecto de TypeScript grunt-ts que depende de un proyecto diferente csproj2ts. Casi nadie que trabaje en grunt-ts también querrá trabajar en csproj2ts, ya que tiene un alcance de funcionalidad muy limitado. Sin embargo, para alguien como yo, sería genial poder trabajar en ambos proyectos simultáneamente y refactorizar / ir a la definición / encontrar todas las referencias en ellos.

Cuando hice mi propuesta, sugerí que el objeto de dependencias fuera un objeto literal con fallbacks con nombre. Una versión más acorde con tu propuesta sería:

"dependencies": {
   "csproj2ts": ["../csproj2ts","node_modules/csproj2ts/csproj2ts.d.ts"],
   "SomeRequiredLibrary": "../SomeRequiredLibraryWithNoFallback"
}

Para simplificarlo para que siga siendo una matriz, sugiero la siguiente implementación alternativa de una sección hipotética futura dependencies del archivo grunt-ts tsconfig.json :

"dependencies": [
   ["../csproj2ts","node_modules/csproj2ts/csproj2ts.d.ts"],
   "../SomeRequiredLibraryWithNoFallback"
]

La regla de resolución para cada elemento de tipo arreglo en dependencies sería: el _primer_ elemento que se encuentra en cada uno es el que se usa, y el resto se ignora. Los elementos de tipo cadena se manejan tal y como establece la propuesta de Jonathan.

Esta es una solución un poco más complicada de implementar, sin embargo, le da al desarrollador (y a los autores de la biblioteca) una flexibilidad mucho mayor. Para los desarrolladores que no necesitan desarrollar en csproj2ts (y por lo tanto no tienen un archivo ../csproj2ts/tsconfig.json ), la dependencia será solo un archivo de definición que se agrega al contexto de compilación. Para los desarrolladores que _tienen_ un archivo ../csproj2ts/tsconfig.json , la propuesta funcionará exactamente como lo describió anteriormente.

En el ejemplo anterior, se requeriría que "../SomeRequiredLibraryWithNoFallback" esté allí como en su propuesta existente, y su ausencia sería un error del compilador.

Muchas gracias por considerar esto.

Hay dos problemas aquí que tenemos que romper, hay construcción y hay soporte de servicio de idiomas.

Para Build, no creo que tsconfig sea el lugar adecuado para esto. esto es claramente un problema del sistema de construcción. Con esta propuesta, el compilador de mecanografiado debe estar en los negocios de:

  • averiguar la dependencia
  • cheques actualizados para afirma
  • gestión de la configuración (lanzamiento frente a depuración)

Todos estos son claramente responsabilidad de los sistemas de construcción; estos son problemas difíciles y ya existen herramientas que lo hacen, por ejemplo, MSBuild, grunt, gulp, etc.
Una vez que tsconfig y tsc se conviertan en el controlador de compilación, querrá que use todas las CPU para construir subárboles no relacionados, o tener comandos de compilación previa y posterior para cada proyecto, y posiblemente también compile otros proyectos. de nuevo, creo que existen herramientas de construcción que son buenas en lo que hacen, y no es necesario que las recreemos.

Para el servicio de idiomas,
Creo que está bien que las herramientas conozcan varios archivos tsconfig y puedan inferir de eso y ayudarlo más, pero eso no debería afectar su compilación. consideraría algo como:

"files" : [
    "file1.ts",
    {
        "path": "../projectB/out/projectB.d.ts",
         "sourceProject": "../projectB/"
     }
]

Donde tsc solo buscará en "ruta", pero las herramientas pueden buscar otra información y tratar de ser lo más útiles posible.

Reconozco la existencia del problema, pero no creo que agrupar la construcción y las herramientas sea la solución correcta. tsconfig.json debe seguir siendo una bolsa de configuración (es decir, una alternativa json a los archivos de respuesta) y no convertirse en un sistema de compilación. un tsconfig.json representa una única invocación de tsc. tsc debe permanecer como un compilador de proyecto único.

Los proyectos de MSBuild en VS son un ejemplo del uso de un sistema de compilación para crear características IDE y ahora las personas no están contentas con él porque es demasiado grande.

Gracias por tu respuesta, Mohamed. Déjame reafirmar para ver si entiendo:

  • Cree que la tarea de coordinar las compilaciones de varios proyectos debe seguir siendo el dominio de las herramientas de compilación dedicadas.
  • Cree que puede haber algo en esta sugerencia para el servicio de lenguaje TypeScript.
  • Crees que ejecutar tsc --project en este tsconfig.json sería lo mismo que ejecutar tsc file1.ts ../project/out/project.d.ts . Sin embargo, abrir un proyecto de este tipo en VS u otro editor con el servicio de lenguaje TypeScript permitiría "ir a la definición" para llevar al desarrollador al _archivo actual de TypeScript_ donde se definió la función (en lugar de la definición en projectB.d.ts )

¿Tengo ese derecho?

Si es así, creo que esto es muy justo. En mi propuesta original (https://github.com/Microsoft/TypeScript/issues/3394), dije que mi idea estaba incompleta porque no incluía el paso de copiar los resultados emitidos desde donde saldrían en la biblioteca referenciada a donde la biblioteca de referencia los esperaría en tiempo de ejecución. Creo que estás diciendo "por qué ir a la mitad del camino de la construcción cuando lo que realmente se necesita es el soporte del servicio de idiomas".

Para cambiar un poco los datos en su ejemplo, ¿estaría dispuesto a apoyar algo como esto?

"files" : [
    "file1.ts",
    {
        "path": "externalLibraries/projectB.d.ts",
         "sourceProject": "../projectB/"
     }
]

El supuesto es que el proyecto actual se enviaría con una definición para el proyectoB que se usaría de forma predeterminada, pero si la fuente real para el proyectoB está disponible, se usaría la fuente real en su lugar.

@nycdotnet lo

¡Suena genial!

Estoy de acuerdo con @mhegazy y, de hecho, creo que es importante que TypeScript deje de pensar en sí mismo como un 'compilador' y comience a pensar en sí mismo como un 'verificador de tipo' y un 'transpilador'. Ahora hay soporte de transpilación de un solo archivo. No veo ninguna razón para que se creen archivos JavaScript compilados, excepto en el tiempo de ejecución / agrupación. Tampoco veo por qué es necesario generar las definiciones de referencia externas durante la verificación de tipos cuando la fuente de escritura mecanografiada real está disponible.

La resolución de archivos y paquetes es responsabilidad del sistema de compilación que está utilizando (browserify, systemjs, webpack, etc.), por lo que para que las herramientas funcionen, el servicio de idiomas debe poder resolver archivos de la misma manera que cualquier sistema / plataforma de compilación que usted utilice. están usando. Esto significa implementar un LanguageServicesHost personalizado para cada sistema de compilación o proporcionar una herramienta para cada uno que genere las entradas de mapeo correctas en tsconfig.json. Cualquiera de estos es aceptable.

@nycdotnet Creo que su caso de uso para múltiples rutas de respaldo se manejaría mejor usando npm link ../csproj2ts ?

Estoy de acuerdo con @mhegazy en que debemos mantener la compilación separada del compilador / transpilador. Me gusta incluir la dependencia tsconfig -> tsconfig en la sección de archivos, asumiendo que si la sección no enumera ningún archivo * .ts, todavía los busca. P.ej

"archivos": [
{
"ruta": "bibliotecas externas / proyectoB.d.ts",
"sourceProject": "../projectB/"
}
]

Aún incluirá todos los archivos ts en el directorio y subdirectorio que contiene el archivo tsconfig.json.

@dbaeumer , entonces quieres tenerlo en una propiedad diferente, ¿correcto? actualmente, si los archivos están definidos, siempre se usa e ignoramos la parte include * .ts.

@mhegazy no es necesariamente una sección diferente aunque

Creo que esto es muy necesario. Sin embargo, no creo que podamos evitar la cuestión de la construcción. Eso no significa que debamos implementar un sistema de compilación junto con esta propuesta, pero deberíamos haber pensado en alguna buena guía que funcione en una configuración como la propuesta. No será trivial hacerlo bien (y necesitamos resolverlo para Visual Studio).

En la propuesta anterior (donde se hace referencia tanto a .d.ts como a la fuente), ¿detecta si el .d.ts está desactualizado (es decir, necesita ser reconstruido)? ¿Las operaciones como Refactorizar / Renombrar funcionan en todos los proyectos (es decir, actualiza el nombre en la fuente del proyecto al que se hace referencia, no solo su archivo .d.ts que se sobrescribirá en la próxima compilación)? ¿GoToDef me lleva al código original en el proyecto al que se hace referencia (no al medio de un archivo .d.ts gigante para todo el proyecto)? Estos parecerían implicar la necesidad de resolver, y en algunos casos analizar, la fuente de los proyectos referenciados, ¿en qué caso es tan útil el .d.ts?

La solución general, que tenemos hoy, tiene un .d.ts como resultado de la compilación de un proyecto y luego se hace referencia como entrada en el otro proyecto. Esto funciona bien para la construcción, por lo que no es necesario cambiarlo.

El problema es el escenario de edición. no desea revisar un archivo generado durante la edición. Mi solución propuesta es proporcionar una "pista" de dónde provienen los .d.ts generados. El servicio de idiomas no cargará los .d.ts y, en su lugar, cargará un "proyecto" desde la ruta de la pista. de esta manera, goto def lo llevará al archivo de implementación en lugar del .d.ts y, de manera similar, los errores funcionarían sin la necesidad de una compilación.

operaciones como renombrar, se "propagarán" de un proyecto a otro, de manera similar, encontrar referencias, servirá.

Hoy depende completamente del host (el IDE, lo que sea) encontrar el archivo tsconfig.json, aunque TS luego proporciona API para leerlo y analizarlo. ¿Cómo imagina que esto funcione si hubiera varios tsconfig.json ubicados de manera jerárquica? ¿El anfitrión seguirá siendo responsable de resolver el archivo inicial pero no los demás o será el anfitrión responsable de resolver todos los tsconfigs?

Parece que hay un compromiso entre conveniencia / convenciones y flexibilidad.

¿No comenzaría esto con la capacidad de construir archivos d.ts como se describe en # 2568 (o al menos tener importaciones relativas)?

@spion, no estoy seguro de ver la dependencia aquí. puede tener múltiples salidas de un proyecto, no tiene que ser un solo archivo de delectación. el sistema de construcción debe poder saber eso y conectarlos como entradas a proyectos dependientes.

@mhegazy Vaya, lo siento. Mirando el tema nuevamente, parece que está más relacionado con el servicio de idiomas. Leí lo siguiente

  • Proyectos de tamaño mediano: aquellos con compilaciones estándar y componentes compartidos

y asumió automáticamente que está relacionado con un mejor soporte para npm / browserify (o webpack) build worfklows donde partes del proyecto son módulos externos.

AFAIK, ¿todavía no hay forma de generar archivos .d.ts para módulos externos? Si es así, la única forma en que un servicio de idiomas podría vincular proyectos que importan módulos externos sería tener algo como esto en tsconfig.json:

{ 
  "provides": "external-module-name"
}

que informaría al LS cuando se haga referencia a los proyectos en otro tsconfig.json

AFAIK, ¿todavía no hay forma de generar archivos .d.ts para módulos externos?

No creo que esto sea cierto. llamar a tsc --m --d generará un archivo de declaración que es un módulo externo en sí mismo. La lógica de resolución intentará encontrar un .ts y, si no, un .d.ts con el mismo nombre,

@spion TypeScript puede generar archivos d.ts para módulos externos como dijo @mhegazy , pero esto da como resultado una proporción 1: 1 de definiciones a archivos fuente que es diferente de cómo se consumen típicamente las definiciones de TypeScript de una biblioteca. Una forma de evitar esto es esta biblioteca TypeStrong: https://github.com/TypeStrong/dts-bundle

@mhegazy lo siento, quise decir "módulos externos ambientales", es decir, si escribo external-module-name en TypeScript e importo una de sus clases desde otro módulo:

import {MyClass} from 'external-module-name'

no hay forma de hacer que tsc genere el archivo .d.ts apropiado que declare 'external-module-name'

@nycdotnet Soy consciente de dts-bundle y dts-generator, pero aún así, si el servicio de idiomas debe conocer la fuente de mi otro proyecto, también debería saber qué nombre de módulo proporciona para poder rastrear las importaciones correctamente

¿Cuál es el estado de esta función? parece que esta es una opción importante para proyectos de tamaño medio. ¿Cómo se configura un proyecto que tiene código fuente en diferentes carpetas con una configuración específica "requirejs"?

@llgcode, por favor, eche un vistazo a https://github.com/Microsoft/TypeScript/issues/5039 , esto debería estar disponible en typescript@next .

Realmente no entiendo qué es tan difícil de escribir un gulpfile con tareas que compilen los subproyectos tal como lo necesita para proyectos de tamaño mediano. Incluso hago esto en proyectos de pequeño tamaño. La única razón por la que uso tsconfig.json es para VS Code

Primero no uso gulp. En segundo lugar, este puede ser un gran proyecto en el que no desea volver a compilar todo el tiempo. Pero si tiene una buena solución con gulp, déjeme saber cómo hacerlo.

@llgcode Bueno, gulp es un corredor de tareas. Escribe un archivo gulpfile.js donde define tantas tareas como desee con gulp.task() . Dentro de su tarea, puede tomar un flujo de archivos de entrada con gulp.src() y luego .pipe() a través de una canalización de transformaciones, como compilación, concatenación, minificación, mapas de origen, copia de activos ... haga todo lo que sea posible con los módulos Node y NPM.
Si debe compilar varios proyectos, simplemente defina una tarea que haga esto. Si desea utilizar varios tsconfig.json, gulp-typescript tiene soporte para eso, o simplemente puede leer los archivos json. También son posibles las construcciones incrementales. No sé cómo está estructurado su proyecto, si los tiene en diferentes repositorios y usa submódulos, o lo que sea. Pero gulp es 100% flexible.

Ok, gracias parece ser una gran herramienta. si tengo require con mapeo como require ("mylibs / lib") y mis archivos están, por ejemplo, en una carpeta project / src / lib.js, entonces la finalización no funcionará en atom y no sé cómo se resolverá mecanografiado o gulp el mapeo / configuración hecho con "mylibs" y una ruta local. así que creo que esta nueva opción de rutas en # 5039 es una buena solución para este problema.

@llgcode Bueno, con gulp puede obtener todos los archivos (incluidos los archivos .d.ts) con globs, consulte https://www.npmjs.com/package/gulp-typescript#resolving -files.

Creo que TypeScript está tratando de hacer mucho aquí, es un transpilador y no una herramienta de compilación. La gestión de "dependencias" como se menciona en la propuesta es realmente la tarea de los administradores de paquetes o los sistemas de control de versiones y conectarlos juntos es la tarea de las herramientas de construcción.

@felixfbecker No estoy de acuerdo. Cada compilador (con verificación de tipos) que conozco tiene una opción como esta. por ejemplo:
gcc -> incluir archivos
java -> classpath y sourcepath
ir -> GOPATH
python -> PYTHONPATH
el compilador / transpilador necesita saber qué archivos de origen deben transpilarse, qué archivos de origen son solo archivos include / lib.
Se necesita una herramienta de compilación como gulp para saber qué hacer cuando cambia un archivo.

De acuerdo con @llgcode . Además, además de exponerse como compilador, TypeScript también se expone como un servicio de lenguaje, que proporciona resaltado de sintaxis (detección en realidad) y funcionalidad de finalización al IDE. Y ESO también necesita caminar por el árbol de dependencia.

@llgcode @unional Puntos válidos. Una cosa que también puede ayudar es hacer que la propiedad files en tsconfig.json acepte globs para que pueda definir todos los archivos en todas las carpetas que desea incluir. Pero veo de dónde vienes y por qué uno puede querer múltiples tsconfig.json para proyectos más grandes.

AFAIK para CommonJS, esto ya es compatible a través de node_modules y npm link ../path/to/other-project

El enlace npm no funciona tan pronto como comienzas a reutilizar las bibliotecas en los proyectos. Si usa una biblioteca común entre dos proyectos propios separados, (tomando rxjs como ejemplo) el mecanografiado le dirá que 'Observable no se puede asignar a Observable'. Esto se debe a que las rutas de inclusión siguen carpetas de enlaces simbólicos a dos carpetas node_modules diferentes y, a pesar de ser la misma biblioteca. Las soluciones alternativas dan como resultado la creación de tareas gulp o repositorios npm locales / privados, básicamente de vuelta a la opción de proyecto grande.

@EricABC probablemente se deba a que están usando declaraciones de módulos externos ambientales, en cuyo caso también deberían incluir definiciones para los archivos .d.ts basados ​​en node_modules recién admitidos. Aparte de eso, no debería haber ningún problema, ya que los tipos de TS solo se verifican estructuralmente, por lo que no importa si provienen de módulos diferentes o tienen nombres diferentes siempre que las estructuras coincidan.

Gracias @spion , asumí que estaba basado en archivos, parece que me vas a salvar de un dolor que me aflige a mí mismo.

Una cosa que también puede ayudar es hacer que la propiedad files en tsconfig.json acepte globs ...

Hay una propiedad include en discusión

Preguntas y comentarios:

  • dependencies debería permitir la ruta completa tsconfig.json porque tsc lo permite
  • ¿Por qué introducir una nueva palabra clave ( dependencies ) cuando files ya existe y está bien?
    Ejemplo:
{
    "compilerOptions": {
        // ...
    },
    "files": [
        "../common/tsconfig.json", // <== takes the `files` part of the tsconfig.json
        "../common/tsconfig.util.json", // <==
        "core.ts",
        "sys.ts"
    ]
}
  • ¿Qué sucede si la dependencia tsconfig.json también especifica compilerOptions ?


Vayamos más lejos / wild :-) y posiblemente permitamos (en el futuro) compilerOptions , exclude ... para hacer referencia a otro tsconfig.json:

// File app/tsconfig.json
{
    "compilerOptions": "../common/tsconfig.compilerOptions.json",
    "files": [
        "../common/tsconfig.json",
        "../common/tsconfig.util.json",
        "core.ts",
        "sys.ts"
    ],
    "exclude": "../common/exclude.json"
}

// File ../common/tsconfig.compilerOptions.json
{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true
    }
}

// File ../common/exclude.json
{
    "exclude": [
        "node_modules",
        "wwwroot"
    ]
}

// File ../common/tsconfig.util.json
{
    "files": [
        "foo.ts",
        "bar.ts"
    ]
}

Tienes la lógica: files , compilerOptions , exclude ... puede hacer referencia a otros archivos tsconfig.json y solo "tomará" la parte de palabra clave coincidente del otro tsconfig .json file => fácil y escalable. Por lo tanto, puede dividir un tsconfig.json en varios archivos si lo desea y reutilizarlos.

Al leer parte de su discusión, parece más relevante hacer bien este "servicio de lenguaje" / goto definición. Los depuradores de JavaScript utilizan sourceMaps. Ahora, si tsc generó datos de sourceMap no solo en .js sino también en archivos .d.ts ...

Más allá de eso, realmente no veo mucho beneficio en activar compilaciones de proyectos secundarios desde el archivo tsconfig.json. Si necesita este tipo básico de dependencias de tiempo de compilación, un simple script de shell haría el trabajo. Si necesita un edificio incremental inteligente, por otro lado, el enfoque propuesto parece demasiado simple. En muchos escenarios, tsc termina siendo solo un paso de construcción entre otros. ¿Qué tan extraño escribir dependencias para tsc en tsconfig.json pero algún otro archivo para el resto? Nuevamente, para cosas simples con tsc como el único paso de compilación que haría un script de shell.

De todos modos, ¿qué tal generar mapeo de origen en archivos .d.ts como en archivos .js?

simplemente usamos módulos de nodo + enlace npm, lo único que no funciona es que moduleResolution: node no es compatible con los módulos ES6, que permiten optimizaciones de alineación / agitación de árboles (ver también # 11103)

No quiero salirse del tema, pero de alguna manera, esto parece paralelo a los desafíos de trabajar con múltiples proyectos de paquetes de Node a nivel local. No creo que sea tan simple como usar "enlace npm". Es posible que tengan diferentes scripts de compilación, necesitaría ejecutarlos todos en el orden correcto, es más difícil hacerlo de forma incremental, es más difícil usar el modo de observación, es más difícil depurar cosas y es más difícil resolver mapas de origen. Dependiendo de su editor de elección, esto podría ser aún más desafiante.

En general, me rindo, lo pongo todo en un proyecto gigante y luego lo divido en paquetes separados una vez que se han estabilizado, pero eso solo ayuda porque el desafío se enfrenta con menos frecuencia. Encuentro toda la experiencia realmente, realmente molesta. ¿Me estoy perdiendo de algo?

Entonces, sea lo que sea que esto termine siendo, solo espero poder finalmente tener una solución elegante para toda esta experiencia de desarrollo.

¡Solo estoy diciendo que esto sería genial para nuestro trabajo diario!

Debido a la naturaleza del desarrollo web, tenemos varios proyectos ts y cada proyecto contiene varios archivos ts compilados en un solo archivo js ( --outFile ). Estos son proyectos similares a aplicaciones (hacen algo específico o agregan una función específica) o proyectos similares a lib (código reutilizable para las aplicaciones). A menudo trabajamos en varios de estos proyectos ts al mismo tiempo, mejorando las bibliotecas para facilitar el desarrollo de las aplicaciones. Pero tampoco creo que ninguno de mis compañeros desarrolladores tenga todos nuestros proyectos ts en sus entornos locales en ningún momento.

Para mejorar nuestro flujo de trabajo, nuestras opciones actuales son

  • Tira todo en 1 proyecto ts

    • Podemos hacer referencia a los archivos .ts directamente, lo cual es mucho mejor que usar los archivos d.ts

    • Pero necesitamos una herramienta externa para mapear y concatenar conjuntos de archivos, ya que necesitamos limitar los archivos js solicitados a través de Internet mientras mantenemos la modularidad de las aplicaciones (que son específicas de la página o función).

    • Como se mencionó, no todos estos proyectos ts son necesarios en todo momento para todos, por lo que esto agregaría mucha hinchazón al sistema de archivos. Alguien que trabaja en el proyecto X puede necesitar los proyectos A, B y D, pero alguien que trabaja en Y puede necesitar A y C.

  • Mantener los proyectos separados (nuestra situación actual)

    • Necesitamos hacer referencia a los archivos d.ts compilados de los otros proyectos ts, ya que incluir un archivo .ts directamente lo agregaría a la salida. Sería mucho más rápido si pudiéramos f12 directamente en nuestro propio código fuente.

    • Y necesitamos agregar herramientas / scripts para compilar varios de estos proyectos al mismo tiempo. Actualmente, iniciamos comandos tsc -d -w desde múltiples terminales, o iniciamos un script que lo hace para todos los subdirectorios donde se encuentra un tsconfig.

    • Entiendo que otras herramientas pueden ayudar con esto (por ejemplo, gulp) o tal vez podamos resolver algo con archivos globs en tsconfigs, sin embargo, eso no ayudaría con el primer problema de los archivos d.ts.

Nuestro proyecto (s) simplemente no es lo suficientemente grande como para mantener el desarrollo de las bibliotecas estrictamente separado de las aplicaciones, pero no lo suficientemente pequeño como para que podamos unir todo. Nos encontramos sin opciones elegantes con mecanografiado.

Si dependencies puede ser la mejor opción de ambos mundos, sería asombroso; la funcionalidad deetiquetas a todos los archivos ts de una dependencia, pero compile la salida de acuerdo con el propio tsconfig de esa dependencia.

¿Hay alguna actualización sobre este tema? ¿Cómo podemos tener varios proyectos mecanografiados que se compilan de forma independiente entre sí? ¿Cómo podemos describir tal dependencia en tsconfig.json?

Esto ahora se incluye en la hoja de ruta en la sección futura con el título "Soporte para referencias de proyectos". Entonces, supongo que un archivo .tsconfig podrá vincular otro archivo .tsconfig como dependencia.

¿Hay alguna actualización sobre este tema? ¿Cómo podemos tener varios proyectos mecanografiados que se compilan de forma independiente entre sí? ¿Cómo podemos describir tal dependencia en tsconfig.json?

La dependencia de compilación debe estar codificada en su sistema de compilación. Los sistemas de compilación como gulp, gruñido, brócoli, msbuild, basal, etc. están diseñados para manejar estos casos.
Para la información de tipo, la salida de un proyecto debe incluir un .d.ts y eso debe pasarse como entrada al otro.

@mhegazy Nuestro proyecto funciona así. Tenemos varios paquetes en un monorepo de lerna, cada paquete tiene sus propias dependencias en package.json, y esas mismas dependencias en la propiedad "types" en su tsconfig.json . Cada proyecto se compila con --outFile (es un proyecto más antiguo que aún no se ha movido a los módulos ES), y los puntos clave "typings" package.json del paquete .d.ts archivo.

Usamos gulp para agrupar / mirar.

Funciona en su mayor parte, pero hay algunos problemas:

  • Debido a problemas como # 15488 y # 15487, necesitamos tener un enlace explícito para que las referencias funcionen correctamente.
  • Ir a la definición lo llevará a un archivo .d.ts incluido. Idealmente, esto lo llevaría a la fuente en otro proyecto.
  • La forma más rápida de hacer una compilación completa es lerna run build --sort (efectivamente tsc en cada directorio), que tiene una sobrecarga adicional porque generará un proceso de compilación de TypeScript para cada paquete, realizando una gran cantidad de trabajo repetido .

Estoy siguiendo de cerca este tema ya que también estamos en la misma situación que otros describieron.
Múltiples "proyectos", cada uno con su archivo tsconfig.json.

Tenemos el proceso de compilación funcionando como lo señaló .d.ts y que se usa como entrada para los proyectos dependientes.

El problema real es la compatibilidad con IDE: cuando se buscan referencias, solo se encuentran dentro del alcance de un solo tsconfig.json . Peor aún, los efectos en cascada de un archivo modificado no se propagan entre proyectos porque los archivos dependientes fuera del alcance tsconfig.json no se vuelven a compilar. Esto es muy malo para el mantenimiento de nuestros proyectos y, a veces, provoca errores de compilación que podrían haberse detectado en el IDE.

It's happening

OH DIOS MÍO

Un escenario actualizado en el que me encantaría tener esto involucra componentes de React. Tenemos un repositorio de componentes que contiene módulos JSX (átomos, moléculas y organismos) que hacen que los componentes de la interfaz de usuario sean apropiados para todas las aplicaciones de nuestra empresa. Todos los desarrolladores front-end utilizan este repositorio de componentes mientras trabajan en sus aplicaciones individuales. Sería MUY AGRADABLE si pudiera tener una experiencia de servicio de lenguaje TypeScript que me permitiera editar la interfaz de usuario de mi aplicación específica y "Ir a la definición" en el repositorio común de componentes de la interfaz de usuario. Hoy tenemos que agrupar estos componentes individualmente y copiarlos. Este es el problema de "hacer su propio proyecto de plomería" que me encantaría ver solucionado (para el cual hay una historia muy bonita en el mundo .NET con proyectos bajo solución).

Referencias del proyecto: escalabilidad incorporada para TypeScript

Introducción

TypeScript se ha escalado a proyectos de cientos de miles de líneas, pero no admite de forma nativa este tipo de escala como comportamiento integrado. Los equipos han desarrollado soluciones alternativas de diversa eficacia y no existe una forma estandarizada de representar grandes proyectos. Si bien hemos mejorado enormemente el rendimiento del comprobador de tipos a lo largo del tiempo, todavía existen límites estrictos en términos de qué tan rápido puede llegar TS de manera razonable,
y restricciones como el espacio de direcciones de 32 bits que impiden que el servicio de idiomas se amplíe "infinitamente" en escenarios interactivos.

De manera intuitiva, cambiar una línea de JSX en un componente de front-end no debería requerir volver a escribir la verificación de todo el componente de lógica empresarial central de un proyecto de 500.000 LOC. El objetivo de las referencias de dividir su código en bloques más pequeños. Al permitir que las herramientas operen en partes más pequeñas de trabajo a la vez, podemos mejorar la capacidad de respuesta y ajustar el ciclo de desarrollo central.

Creemos que a nuestra arquitectura actual le quedan muy pocos "almuerzos gratis" en términos de drásticas mejoras en el rendimiento o el consumo de memoria. En cambio, esta partición es una compensación explícita que aumenta la velocidad a expensas de un trabajo inicial. Los desarrolladores tendrán que dedicar algún tiempo a razonar sobre el gráfico de dependencia de su sistema, y ​​es posible que ciertas características interactivas (por ejemplo, cambios de nombre entre proyectos) no estén disponibles hasta que mejoremos las herramientas.

Identificaremos las restricciones clave impuestas por este sistema y estableceremos pautas para el tamaño del proyecto, la estructura del directorio y los patrones de construcción.

Escenarios

Hay tres escenarios principales a considerar.

Módulos relativos

Algunos proyectos utilizan ampliamente importaciones relativas . Estas importaciones se resuelven sin ambigüedades en otro archivo en el disco. Las rutas como ../../core/utils/otherMod serían comunes de encontrar, aunque generalmente se prefieren estructuras de directorio más planas en estos repositorios.

Ejemplo

Aquí hay un ejemplo del proyecto perseus de Khan Academy:

Adaptado de https://github.com/Khan/perseus/blob/master/src/components/graph.jsx

const Util = require("../util.js");
const GraphUtils = require("../util/graph-utils.js");
const {interactiveSizes} = require("../styles/constants.js");
const SvgImage = require("../components/svg-image.jsx");

Observaciones

Si bien la estructura del directorio implica una estructura de proyecto, no es necesariamente definitiva. En la muestra de Khan Academy anterior, se puede inferir que util , styles y components probablemente serían su propio proyecto. Pero también es posible que estos directorios sean bastante pequeños y en realidad se agrupen en una unidad de compilación.

Mono-repositorio

Un repositorio único consta de varios módulos que se importan a través de rutas import * as C from 'core/thing ) pueden ser comunes. Por lo general, pero no siempre, cada módulo raíz se publica en NPM.

Ejemplo

Adaptado de https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts

import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core';
import {forkJoin} from 'rxjs/observable/forkJoin';
import {map} from 'rxjs/operator/map';
import {AbstractControl, FormControl} from './model';

Observaciones

La unidad de división no es necesariamente la parte inicial del nombre del módulo. rxjs , por ejemplo, en realidad compila sus subpartes ( observable , operator ) por separado, al igual que cualquier paquete con alcance (por ejemplo, @angular/core ).

Outfile

TypeScript puede concatenar sus archivos de entrada en un solo archivo JavaScript de salida. Las directivas de referencia, o el orden de archivos en tsconfig.json, crean un orden de salida determinista para el archivo resultante. Esto rara vez se usa para nuevos proyectos, pero aún es frecuente entre las bases de código más antiguas (incluido el propio TypeScript).

Ejemplo

https://github.com/Microsoft/TypeScript/blob/master/src/compiler/tsc.ts

/// <reference path="program.ts"/>
/// <reference path="watch.ts"/>
/// <reference path="commandLineParser.ts"/>

https://github.com/Microsoft/TypeScript/blob/master/src/harness/unittests/customTransforms.ts

/// <reference path="..\..\compiler\emitter.ts" />
/// <reference path="..\harness.ts" />

Observaciones

Algunas soluciones que usan esta configuración cargarán cada outFile través de una etiqueta script separada (o equivalente), pero otras (por ejemplo, el propio TypeScript) requieren la concatenación de los archivos anteriores porque están creando salidas monolíticas .

Referencias del proyecto: una nueva unidad de aislamiento

Algunas observaciones críticas de la interacción con proyectos reales:

  • TypeScript suele ser "rápido" (<5-10 s) cuando se verifican proyectos por debajo de 50.000 LOC de código de implementación (no .d.ts)
  • Los archivos .d.ts, especialmente por debajo de skipLibCheck , son casi "gratuitos" en términos de verificación de tipo y costo de memoria
  • Casi todo el software se puede subdividir en componentes de menos de 50.000 LOC
  • Casi todos los proyectos grandes ya imponen alguna estructuración de sus archivos por directorio de una manera que produce subcomponentes de tamaño moderado
  • La mayoría de las ediciones ocurren en componentes de nodo de hoja o de nodo de hoja cercano que no requieren volver a verificar o reemitir toda la solución.

Poniendo todo esto en conjunto, si fuera posible solo verificar un fragmento de 50.000 LOC de código de implementación a la vez, casi no habría interacciones "lentas" en un escenario interactivo, y casi nunca nos quedaríamos sin memoria.

Introducimos un nuevo concepto, una referencia de proyecto , que declara un nuevo tipo de dependencia entre dos unidades de compilación de TypeScript donde no se comprueba el código de implementación de la unidad dependiente; en su lugar, simplemente cargamos su salida .d.ts desde una ubicación determinista.

Sintaxis

Se agrega una nueva opción references (TODO: Bikeshed!) A tsconfig.json :

{
  "extends": "../tsproject.json",
  "compilerOptions": {
    "outDir": "../bin",
    "references": [
      { "path": "../otherProject" }
    ]
  }
}

La matriz references especifica un conjunto de otros proyectos para hacer referencia a este proyecto.
Cada references objeto path apunta a un archivo tsconfig.json o una carpeta que contiene un archivo tsconfig.json .
Se pueden agregar otras opciones a este objeto a medida que descubramos sus necesidades.

Semántica

Las referencias de proyectos cambian el siguiente comportamiento:

  • Cuando la resolución del módulo se resuelve en un archivo .ts en un subdirectorio del rootDir de un proyecto, en su lugar se resuelve en un archivo .d.ts en el outDir ese proyecto

    • Si esta resolución falla, probablemente podamos detectarla y emitir un error más inteligente, por ejemplo, Referenced project "../otherProject" is not built lugar de un simple "archivo no encontrado".

  • Nada más (TODO: hasta ahora?)

Restricciones para la ejecución de proyectos referenciados

Para mejorar significativamente el rendimiento de la compilación, debemos asegurarnos de restringir el comportamiento de TypeScript cuando ve una referencia de proyecto.

Específicamente, las siguientes cosas deberían ser ciertas:

  • Nunca lea ni analice los archivos .ts de entrada de un proyecto referenciado
  • Solo el tsconfig.json de un proyecto referenciado debe leerse del disco
  • La verificación actualizada no debería requerir violar las restricciones anteriores

Para mantener estas promesas, debemos imponer algunas restricciones a los proyectos a los que hace referencia.

  • declaration se establece automáticamente en true . Es un error intentar anular esta configuración.
  • rootDir defecto es "." (el directorio que contiene el archivo tsconfig.json ), en lugar de inferirse del conjunto de archivos de entrada
  • Si se proporciona una matriz files , debe proporcionar los nombres de todos los archivos de entrada

    • Excepción: los archivos incluidos como parte de las referencias de tipo (por ejemplo, los de node_modules/@types ) no necesitan especificarse

  • Cualquier proyecto al que se haga referencia debe tener una matriz references (que puede estar vacía).

¿Por qué "declaration": true ?

Las referencias de proyectos mejoran la velocidad de compilación mediante el uso de archivos de declaración (.d.ts) en lugar de sus archivos de implementación (.ts).
Entonces, naturalmente, cualquier proyecto referenciado debe tener declaration configuración
Esto está implícito en "project": true

¿Por qué alterar rootDir ?

rootDir controla cómo se asignan los archivos de entrada a los nombres de los archivos de salida. El comportamiento predeterminado de TypeScript es calcular el directorio de ["src/a.ts", "src/b.ts"] producirá los archivos de salida ["a.js", "b.js"] ,
pero el conjunto de archivos de entrada ["src/a.ts", "b.ts"] producirá los archivos de salida ["src/a.js", "b.js"] .

Calcular el conjunto de archivos de entrada requiere analizar cada archivo raíz y todas sus referencias de forma recursiva,
que es caro en un proyecto grande. Pero no podemos cambiar este comportamiento hoy sin romper los proyectos existentes de una mala manera, por lo que este cambio solo ocurre cuando se proporciona la matriz references .

Sin circularidad

Naturalmente, los proyectos no pueden formar un gráfico con circularidad. (TODO: ¿Qué problemas realmente causa esto, además de crear pesadillas de orquestación?) Si esto ocurre, verá un mensaje de error que indica la ruta circular que se formó:

TS6187: Project references may not form a circular graph. Cycle detected:
    C:/github/project-references-demo/core/tsconfig.json ->
    C:/github/project-references-demo/zoo/tsconfig.json ->
    C:/github/project-references-demo/animals/tsconfig.json ->
    C:/github/project-references-demo/core/tsconfig.json

tsbuild

Esta propuesta es intencionalmente vaga sobre cómo se usaría en un sistema de construcción "real". Muy pocos proyectos escalan más allá del límite "rápido" de 50.000 LOC sin introducir algo más que tsc para compilar el código .ts.

El escenario del usuario de "No puedes construir foo porque bar aún no está construido" es un tipo de tarea obvia de "Ve a buscar eso" de la que una computadora debería encargarse, en lugar de que una carga mental para los desarrolladores.

Esperamos que herramientas como gulp , webpack , etc. (o sus respectivos complementos de TS) comprendan las referencias del proyecto y manejen correctamente estas dependencias de compilación, incluida la verificación actualizada.

Para garantizar que esto sea posible , proporcionaremos una implementación de referencia para una herramienta de orquestación de compilación de TypeScript que demuestra los siguientes comportamientos:

  • Comprobación rápida y actualizada
  • Orden del gráfico del proyecto
  • Construir paralelización
  • (TODO: ¿otros?)

Esta herramienta debe usar solo API públicas y estar bien documentada para ayudar a los autores de herramientas a comprender la forma correcta de implementar referencias de proyectos.

HACER

Secciones a completar para completar completamente esta propuesta

  • Cómo harías la transición de un proyecto existente

    • Básicamente, coloque los archivos tsconfig.json y luego agregue las referencias necesarias para corregir los errores de compilación

  • Impacto en baseUrl

    • Hace que la implementación sea difícil pero efectivamente no afecta al usuario final

  • Breve discusión de los proyectos de anidamiento (TL; DR debe estar permitido)
  • Esquema del escenario de subdividir un proyecto que ha crecido "demasiado grande" en proyectos más pequeños sin afectar a los consumidores
  • Descubre el escenario de Lerna

    • El punto de datos disponible (N = 1) dice que no lo necesitarían porque su compilación ya está estructurada de manera efectiva de esta manera

    • Encuentre más ejemplos o contraejemplos para comprender mejor cómo las personas hacen esto

  • ¿Necesitamos una configuración dtsEmitOnly para las personas que están canalizando su JS a través de, por ejemplo, webpack / babel / rollup?

    • Tal vez references + noEmit implica esto

¡Fantástico!

Descubre el escenario de Lerna

  • El punto de datos disponible (N = 1) dice que no lo necesitarían porque su compilación ya está estructurada de manera efectiva de esta manera

¿"Esto" se refiere a la propuesta o la implementación de construcción de referencia? Si bien podría usar lerna para hacer la compilación (mi equipo lo hace), es complicado y sería mucho más eficiente si TS (o una herramienta creada a partir de esta propuesta) se encarga de sí misma.

La sección TODO es TODO para toda la propuesta.

¡Bonito!

Cualquier proyecto al que se haga referencia debe tener una matriz de referencias (que puede estar vacía).

¿Es esto realmente necesario? ¿No sería suficiente si dicho paquete tuviera archivos .d.ts ?
(En ese caso, ¿puede que ni siquiera sea necesario que haya un tsconfig.json también?)

Mi caso de uso: considere un proyecto (por ejemplo, de terceros) que no usa outDir , por lo que .ts , .js y .d.ts estarán al lado de entre sí, y TS intentará compilar el .ts lugar de usar el .d.ts .

La razón para no usar outDir para mí es permitir más fácilmente las importaciones de estilo import "package/subthing" , que de lo contrario tendrían que ser, por ejemplo, import "package/dist/subthing" con outDir: "dist" .
Y poder usar el paquete NPM o su repositorio fuente directamente (por ejemplo, con npm link ).

(Sería útil si package.json permitiera especificar un directorio en main , pero lamentablemente ...)

¿Necesitamos una configuración dtsEmitOnly para las personas que están canalizando su JS a través de, por ejemplo, webpack / babel / rollup?

¡Absolutamente! Esta es una gran pieza que falta en este momento. Actualmente, puede obtener un solo archivo d.ts cuando usa outFile , pero cuando cambia a módulos y usa un paquete, lo pierde. Ser capaz de emitir un solo archivo d.ts para el punto de entrada de un módulo (con export as namespace MyLib ) sería increíble. Sé que las herramientas externas pueden hacer esto, pero realmente sería genial si se integrara en los servicios de lenguaje y emisor.

¿Es esto realmente necesario? ¿No sería suficiente si dicho paquete tuviera archivos .d.ts?

Necesitamos algo en el tsconfig de destino que nos diga dónde esperar que estén los archivos de salida. Una versión anterior de esta propuesta tenía "Debe especificar un rootDir explícito" que era bastante engorroso (tenía que escribir "rootDir": "." en cada tsconfig). Dado que queremos cambiar una variedad de comportamientos en este mundo, tenía más sentido decir que obtienes un comportamiento de "proyecto" si tienes una matriz de referencias y eso es lo que está codificado, en lugar de especificar un montón de banderas que tendrías que indicar explícitamente.

Esta propuesta se alinearía estrechamente con la forma en que ya hemos estructurado nuestros proyectos de TypeScript. Nos hemos subdividido en unidades más pequeñas, cada una de las cuales tiene un tsconfig.json y se construye de forma independiente a través de gulp. Los proyectos se referencian entre sí haciendo referencia a los archivos d.ts.

En un mundo ideal, el proyecto al que se hace referencia no necesitaría ser preconstruido. es decir. TypeScript realiza una "compilación" del proyecto al que se hace referencia y mantiene el equivalente "d.ts" en la memoria del servicio de lenguaje. esto permitiría que los cambios realizados en el proyecto "fuente" aparezcan en el proyecto "dependiente" sin necesidad de una reconstrucción.

Necesitamos algo en el tsconfig de destino que nos diga dónde esperar que estén los archivos de salida.

Eso solo es cierto cuando se usa outDir , ¿no es así?

Como en: si tengo un tsconfig que:

  • NO usa outDir (pero tiene declaration: true , por supuesto), entonces no necesitamos rootDir , ni references
  • tiene outDir , entonces necesitaría references y / o rootDir (y declaration: true ) para ser configurado

La razón para preguntar es que podría habilitar el 'modo de proyecto' para cualquier paquete TS con solo hacer referencia a él, es decir, está bajo mi control.

En ese caso, también sería bueno si también funciona tan pronto como encuentre el archivo .d.ts que está buscando (es decir, no se quejará si no hay archivos .ts o archivos tsconfig). Porque eso permitirá otro caso de 'reemplazar' una versión de NPM (que solo puede tener archivos .d.ts) por su versión de origen cuando sea necesario.

Por ejemplo, considere los paquetes de NPM MyApp y SomeLib.
SomeLib podría tener tsconfig: declaration: true .

Repositorio como:

package.json
tsconfig.json
index.ts
sub.ts

Compilado, esto se convierte en:

package.json
tsconfig.json
index.ts
index.d.ts
index.js
sub.ts
sub.d.ts
sub.js

Esta estructura permite, por ejemplo,

// somewhere in MyApp
import something from "SomeLib/sub";

En el paquete NPM publicado, actualmente siempre tengo que eliminar los archivos .ts; de lo contrario, TS volverá a compilar todas las fuentes si MyApp usa SomeLib:

Entonces, en NPM, esto se convierte en:

package.json
index.d.ts
index.js
sub.d.ts
sub.js

Ahora, si pongo references: ["SomeLib"] en el tsconfig de MyApp, sería bueno si funciona 'tal cual' tanto para la versión NPM como para la versión fuente de SomeLib , es decir, que no se quejará, por ejemplo, falta tsconfig, siempre que encuentre sub.d.ts en el lugar correcto.


Pregunta relacionada, pero diferente:

Ahora me doy cuenta de que SI el autor de SomeLib pone references en su tsconfig, esto permitiría publicar paquetes NPM CON los archivos .ts, en el futuro. Pero entonces, supongo que TS siempre los recompilaría cuando cualquier paquete dependiente no ponga explícitamente references: ["SomeLib"] en su tsconfig.

¿O la intención también es que references en MyLib automáticamente también introduzca un 'límite de proyecto' cuando solo import esté (es decir, no references ')?

IIRC, una de las ideas iniciales era que si un módulo estaba, por ejemplo, ubicado a través de node_modules , entonces se preferirían los archivos .d.ts archivos .ts , pero esto se volvió a cambiar más tarde , porque la heurística ("hasta node_modules ") era demasiado problemática en general. ¿Podría ser que tener un 'límite de proyecto' explícito resolvería esto (por ejemplo, un projectRoot: true , en lugar de o además de tener references )?

Para el caso de lerna, esperaba una solución más sencilla.

  1. En los directorios de paquetes individuales, los paquetes no deberían saber nada sobre la estructura de monorepo. Los archivos tsconfig json individuales no deben contener ninguna referencia.

    • esto le permite dividir paquetes individuales en repositorios separados y simplemente hacer que las herramientas de repositorio principal los clonen, por ejemplo, ProseMirror: https://github.com/ProseMirror/prosemirror

  2. En el repositorio raíz del "espacio de trabajo" (que puede contener todo el código, pero también podría simplemente clonar otros repositorios), tenga las referencias en su tsconfig.json

    • esto se hace solo para que las herramientas puedan reconocer y seguir las referencias hasta la fuente en lugar de entrar en archivos .d.ts.

Toda mi preocupación es que una vez que agrega una referencia usando una ruta "../xx" relativamente descendente a los archivos de configuración del proyecto individual, ya no se pueden usar como módulos independientes; tienen que estar en una estructura de espacio de trabajo específica.

Agregar el nuevo concepto de un "espacio de trabajo" tsconfig.json resuelve este problema. De esa manera, si, por ejemplo, "clona git" el paquete individual, instalar sus dependencias de la manera normal (por ejemplo, usando npm o yarn) debería permitirle trabajar en él por separado, ya que las dependencias compiladas traerían sus archivos de definición. Si clona todo el espacio de trabajo y ejecuta el comando para traer todos los paquetes, la configuración del espacio de trabajo le permitirá navegar a través de todas las fuentes.

Tenga en cuenta que un espacio tsconfig.json trabajo package.json https://yarnpkg.com/lang/en/docs/workspaces/

Hice una pequeña prueba de concepto aquí.

https://github.com/spion/typescript-workspace-plugin

Simplemente agregue el complemento a todos sus archivos tsconfig.json de los repositorios individuales

{
  "plugins": [{"name": "typescript-workspace-plugin"}]
}

Luego, en el nivel superior package.json junto con la entrada "espacios de trabajo" de yarn, agregue una entrada "fuentes del espacio de trabajo":

{
  "workspaces": ["packages/*"],
  "workspace-sources": {
    "*": ["packages/*/src"]
  }
}

El campo funciona exáctamente como el campo "rutas" en tsconfig.json pero solo afecta el servicio de lenguaje de los proyectos individuales, apuntándolos a las fuentes del paquete. Restaura la funcionalidad adecuada de "ir a la definición / tipo" y similares.

Eso solo es cierto cuando se usa outDir, ¿no es así?

Correcto. Teníamos la hipótesis de que casi todos los que tienen un proyecto grande están usando outDir . Me interesaría escuchar sobre proyectos que no

Ahora, si pongo referencias: ["SomeLib"] en el tsconfig de MyApp, sería bueno si funciona 'tal cual' tanto para la versión NPM como para la versión fuente de SomeLib

Gran fan, me gusta mucho esta idea. Necesito pensar si es realmente necesario o no.

Una advertencia aquí es que creo que los autores de paquetes deben a) publicar los archivos .ts y tsconfig juntos en un lugar donde TS los encuentre, ob) no publicar

¿O es la intención también que las referencias en MyLib también introduzcan automáticamente un 'límite de proyecto' cuando se lo importe (es decir, no se haga referencia a él)?

Hablamos con references nunca "ve" un archivo .ts fuera de la carpeta del proyecto, esto incluye archivos debajo de los directorios exclude d. Este cambio solo hace que el escenario de lerna funcione ("trabajo" significa que "las referencias de módulo siempre se resuelven en .d.ts") fuera de la caja, así como otros.

Necesito mirar más el modelo de "espacio de trabajo".

Eso solo es cierto cuando se usa outDir, ¿no es así?

Correcto. Teníamos la hipótesis de que casi todos los que tienen un gran proyecto utilizan outDir. Me interesaría escuchar sobre proyectos que no

Tenemos 67 proyectos TS en la solución de Visual Studio que se compilan sin outdir y grunttasks postbuild para crear la estructura del directorio de salida (y uglify y otros postprocesos).

La mayoría de los proyectos tienen un tsconfig.json.

 "include": [
    "../baseProj/Lib/jquery.d.ts",
    "../baseProj/baseProj.d.ts"
  ]

Me tomé un tiempo para leer la propuesta de referencias y corregir: los usuarios del espacio de trabajo de AFAICT lerna y yarn no necesitan ninguna de las funciones del espacio de trabajo propuestas aquí:

  1. Ya tenemos un gráfico de dependencia basado en package.json, por lo que sabemos el orden en el que ejecutar la compilación. De hecho, lerna tiene un comando de ejecución genérico que puede ejecutar esto en orden, y no es difícil escribir una herramienta que también agregue paralelismo cuando corresponda. skipLibCheck debería hacer que el impacto en el rendimiento sea insignificante, pero no lo he comprobado.
  2. Lerna y Yarn ya crean enlaces simbólicos a los otros módulos en la ubicación correspondiente de node_modules. Como resultado, todos los dependientes pueden seguir al package.json del otro módulo, leer el campo de tipos / tipificaciones y encontrar el archivo de definición de tipo module.d.ts al que se hace referencia.

Lo que no tenemos, y lo que proporciona el complemento que escribí, es una forma de cargar todas las fuentes al mismo tiempo. Cuando necesito hacer cambios en dos o más módulos al mismo tiempo, no quiero que "ir a la definición" y "ir a la definición del tipo" me envíen al archivo .d.ts. Quiero que me envíe a la ubicación del código fuente original, para que quizás pueda editarlo. De lo contrario, simplemente cargaría el directorio del proyecto individual y los enlaces simbólicos node_modules creados por lerna / yarn simplemente funcionarían.

Lo mismo para nosotros. En lugar de Lerna, usamos Rush para calcular nuestro gráfico de dependencia, pero el efecto es el mismo. Cuando construimos proyectos, tsc es solo una de las muchas tareas que deben ejecutarse. Nuestras opciones del compilador se calculan mediante un sistema de compilación más grande, y nos estamos moviendo a un modelo donde tsconfig.json no es un archivo de entrada, sino una salida generada (principalmente para el beneficio de VS Code).

Lo que no tenemos, y lo que proporciona el complemento que escribí, es una forma de cargar todas las fuentes al mismo tiempo. Cuando necesito hacer cambios en dos o más módulos al mismo tiempo, no quiero que "ir a la definición" y "ir a la definición del tipo" me envíen al archivo .d.ts. Quiero que me envíe a la ubicación del código fuente original, para que quizás pueda editarlo.

+1 esto sería genial.

Si estamos soñando con un mejor soporte para múltiples proyectos, mi primera solicitud sería un servicio de compilación, algo así como cómo funciona VS Code IntelliSense. Nuestras compilaciones serían significativamente más rápidas si Rush pudiera invocar tsc 100 veces sin tener que hacer girar el motor del compilador 100 veces. El compilador es uno de nuestros pasos de compilación más costosos. En un monorepo, los tiempos de construcción son realmente importantes.

@iclanton @ nickpape-msft @ qz2017

¡Sí, por favor!

Creo que uno de los resultados más útiles del sistema de proyectos sería si
'ir a la definición' fue al archivo fuente en lugar del archivo d.ts y
"Buscar todas las referencias" buscado en el árbol de referencia del proyecto.

Es de suponer que esto también desbloquearía las refactorizaciones de tipo de 'cambio de nombre global'.

El jueves 9 de noviembre de 2017 a las 9:30 p.m. Salvatore Previti [email protected]
escribió:

¡Sí, por favor!

-
Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/3469#issuecomment-343356868 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANX6d19Zz7TCd_GsP7Kzb-9XJAisG6Hks5s07VXgaJpZM4E-oPT
.

Hablamos con

Bien, y en ese caso, ¿por qué habría que especificar referencias específicas?
Parece que sería suficiente tener una bandera (como projectRoot: true ).
Por ejemplo, ¿cuál sería la diferencia entre references: ["foo"] y solo references: [] ?
Porque si import "foo" o import "bar" , ambos ignorarán los archivos .ts .

Entonces, en ese caso, la propuesta se convierte en:

Dado este tsconfig.json (TODO bikeshed en projectRoot ):

{
  "extends": "../tsproject.json",
  "compilerOptions": {
    "projectRoot": true
   }
}

Cuando tsc necesita resolver algo fuera de la carpeta del proyecto (incluidos los archivos de los directorios excluidos), solo verá los archivos .d.ts (alternativamente, puede que simplemente _preferir_ .d.ts archivos, y vuelva a tsconfig y / o .ts si solo ve eso).

Esto hace que la resolución durante la compilación sea rápida y sencilla.
Funciona para referencias de monorepo (es decir, import "../foo" ) y referencias basadas en paquetes (es decir, import "foo" ).
Funciona para paquetes NPM y su representación de código fuente.
Y elimina la necesidad de resolver la maquinaria tsconfig.json durante la compilación, aunque el mensaje de error si no puede encontrar el .d.ts será menos útil.

Suena demasiado bueno para ser verdad, si es que es así de simple, así que probablemente estoy pasando por alto algo importante :)


Como otros también señalan, sigue siendo muy importante que 'IntelliSense' siga funcionando con los archivos .ts.

Por lo tanto, si se hace una solicitud para 'ir a la definición', 'buscar referencias', etc., debe usar un mapeo inverso para ubicar el archivo .ts archivo .d.ts que usó entonces lejos.

Este mapeo se puede hacer usando, por ejemplo:

  • usando una cadena de comentario incrustada como //# sourceURL = ../src/foo.ts en .d.ts

    • se podría usar un mapa de origen más elaborado para mapear desde un .d.ts 'enrollado' hasta el .ts

  • resolviendo el archivo .js , y usando su mapa fuente para ubicar el archivo `.ts

Esto introduce el tema de la reconstrucción de .d.ts cuando se cambia ese .ts , pero no estoy seguro de que deba resolverse con esta propuesta. Por ejemplo, hoy en día ya es el caso de que debe haber algún proceso para reconstruir los archivos .js , incluso desde el proyecto raíz mismo. Así que supongo que sería seguro asumir que si eso está presente, también habrá algo para reconstruir las dependencias. Podría ser un montón de tsc paralelos para cada paquete, podría ser tsbuild , podría ser un IDE inteligente con compileOnSave comportamiento similar a

@pgonzal @michaelaird https://github.com/spion/typescript-workspace-plugin solo hace eso: restaura ir a la definición y encontrar todas las referencias para un proyecto de espacio de trabajo multi-tsconfig yarn / lerna / etc.

Interesante ... Me pregunto si podríamos hacer que esto funcione con Rush. Echaremos un vistazo.

Para su información, otra herramienta simple que construimos como parte de este esfuerzo fue wsrun

Similar a lerna run , ejecuta un comando para todos los paquetes en el espacio de trabajo.

Dado que las dependencias de mecanografiado deben compilarse en orden, wsrun es capaz de ejecutar un comando de paquete en un orden topológico basado en sus dependencias package.json . Es compatible con el paralelismo durante la construcción. No es un paralelo óptimo, pero eso se puede mejorar más adelante.

Solo una nota, oao es otra herramienta monorepo para hilo. Recientemente también agregó soporte para el ordenamiento 'topológico' de comandos.

Solo me gustaría publicar la palabra "refactorización" aquí, ya que es un objetivo importante para nosotros, aunque probablemente tangencial a la propuesta actual (https://github.com/Microsoft/TypeScript/issues/3469#issuecomment-341317069) y no se menciona en este número muy a menudo.

Nuestro caso de uso es un monorepo con varios proyectos de TS, básicamente se puede reducir a common-library más un par de aplicaciones: app1 y app2 . Cuando se trabaja en el IDE, estos deben tratarse como un solo proyecto, por ejemplo, la refactorización de cambio de nombre debería funcionar en los tres módulos, pero app1 y app2 también son dos objetivos de compilación separados. Si bien estoy de acuerdo en que la compilación es generalmente una preocupación separada, la verdad es que nuestras aplicaciones son bastante pequeñas y hacer algo como cd app1 && tsc estaría perfectamente bien para nosotros.

Si TypeScript encuentra una buena manera de respaldar esto, sería increíble.

Para la refactorización / referencias de proyectos cruzados de monorepo, encontré que esta configuración me funciona si está trabajando en vscode:

tsconfig raíz:

"compilerOptions": {
   "baseUrl": ".",
   // global types are different per project
   "types": [],
   "paths": {
      "lib": ["packages/lib/src"],
      "xyz1": ["packages/xyz1/src"],
      "xyz2": ["packages/xyz2/src"],
   }
},
"include": ["./stub.ts"], // empty file with export {} to stop vscode complaining about no input files
"exclude": ["node_modules"]

paquetes / xyz1 / tsconfig.json

{
  "extends": "../../tsconfig",
   "compilerOptions": {
      "types": ["node"],
   },
   "include": ["src/**/*"]
}

paquetes / xyz2 / tsconfig.json

{
  "extends": "../../tsconfig",
   "compilerOptions": {
      "types": ["webpack-env"]
   },
  "include": ["src/**/*"]
} 

paquetes / lib / tsconfig.json

{
   "extends": "../../tsconfig",
    "compilerOptions": { ... },
    "include": ["src/**/*"],
    // special file to load referenced projects when inside in lib package, without it they won't be 
    // visible until you open some file in these projects 
    "files": ["./references.ts"],
}

paquetes / lib / tsconfig-build.json

{
   "extends": "./tsconfig",
    // exclude referenced projects when building
   "files": []
}

paquetes / lib / referencias.ts

import "xyz1";
import "xyz2";

export {};

Necesita la propiedad main correcta en el paquete package.json y puede ser types incluso para paquetes sin lib, por ejemplo:

  "main": "src/main.tsx",
  "types": "src/main.tsx",

De esta manera, refactorizar / renombrar algo en lib también refactorizará las referencias en xyz1 y xyz2 . También los proyectos podrían tener diferentes bibliotecas globales / de destino de esta manera.

En Gradle, simplemente lo llaman: construcción compuesta .

Por cierto, ¿me puede indicar si debo comenzar si quiero contribuir al compilador de TypeScript? Cloné el repositorio y no es poca cosa (uso para leer la fuente: angular, iónica, expresa cuando no puedo obtenerla de los documentos o estoy lejos de Internet ...) Realmente necesito ayuda de uno para indicarme el camino a seguir, por favor.

¡Gracias!
Futuro brillante para TypeScript.

Tengo un prototipo que me gustaría que la gente probara si están usando rutas de módulo relativas. Hay un repositorio de muestra en https://github.com/RyanCavanaugh/project-references-demo que describe el escenario básico y muestra cómo funciona.

Para probar localmente:

git clone https://github.com/RyanCavanaugh/TypeScript
git checkout pr-lkg
npm install
npm run build
npm link

Luego, en su otra carpeta:

npm link typescript

Mantendré la etiqueta pr-lkg apuntando a la última confirmación de trabajo a medida que cambien las cosas. A continuación en mis todos:

  • [x] Agrega mejores mensajes de error cuando no se crean proyectos dependientes
  • [] Aplicar el comportamiento de redireccionamiento a rutas de módulo no relativas
  • [] Exponer una API para que las herramientas de compilación la consuman

@RyanCavanaugh Realmente no puedo construirlo debido a errores como faltar el módulo del o Error: Cannot find module 'C:\github\TypeScript\built\local\tsc.js' (¿tal vez su ruta local?) Pero en general, la estructura se ve muy bien.

¿Es esto tsc -only o también tendrá en cuenta las refactorizaciones de todo el proyecto en VSCode con el servidor de idiomas?

... también falta la etiqueta pr-lkg .

La etiqueta está ahí (no es una rama). Ver https://github.com/RyanCavanaugh/TypeScript/tree/pr-lkg

El references en tsconfig.json es opt-in por dependencia, ¿no tendría más sentido que se aplicara a todo lo que se resuelve fuera de rootDir ?

Me estoy imaginando algo así como una propiedad sandbox que podría verse así:

# tsconfig.json
{
  "compilerOptions": {
    "outDir": "lib",
    "sandbox": "."
  },
  "include": ["src/index.ts"]
}

Sandbox también establecería rootDir en el mismo valor. En lugar de proporcionar explícitamente rutas a directorios que contienen tsconfig.json , se aplicaría la resolución normal del módulo, y podría buscar en el árbol FS para encontrar tsconfig.json automáticamente.

# package.json
{
  "name": "animals",
  "module": "src",
  "typings": "lib",
  "dependencies": {
    "core": "*"
  }
}

De esta manera:

  • No tiene que mantener dos listas sincronizadas ( references y dependencies ).
  • El paquete no tiene conocimiento del sistema de archivos fuera del suyo.
  • Utilice una estrategia de resolución de módulo de nodo en lugar de rutas personalizadas.

@RyanCavanaugh , hasta donde tengo entendido, solo puedo trabajar localmente con estos cambios y no podré enviar tales proyectos para pruebas, por ejemplo, en travis-ci.org. ¿Derecha?

Notas de una reunión de hoy con @billti / @mhegazy


  • Find References / Rename tiene que funcionar, al menos para escenarios donde el contexto de compilación necesario para determinar el cierre de proyectos cabe en la memoria
  • El cambio de nombre de toda la solución caminará para encontrar un archivo de "solución", trabajará hacia atrás para encontrar proyectos de referencia y luego cargará la
  • tsbuild necesita manejar un modo -w
  • Es posible que las soluciones no tengan subproyectos que se originen fuera de la carpeta de la solución
  • Necesita documentación clara para, por ejemplo, gulp, webpack

Barra lateral: este cambio de nombre no funciona hoy

function f() {
  if (Math.random() > 0.5) {
    return { foo: 10 };
  } else {
    return { foo: 20 };
}
// rename foo here doesn't rename *both* instances in the function body
f().foo;

Gracias Ryan, Mohamed y Bill. Hacer que el escenario Buscar referencias / Renombrar funcione en todos los proyectos fue uno de los casos de uso principales que tenía en mente cuando presenté la queja original acerca de que TypeScript no admite proyectos de tamaño mediano. Los proyectos de tamaño mediano son modulares pero no grandes. La propuesta y el trabajo que he visto aquí hasta ahora se sienten más como un juego de escalabilidad. Esto es muy importante para la salud a largo plazo de TypeScript, pero es más beneficioso para proyectos grandes, no medianos. Las cosas que escucho en este comentario de Ryan suenan más en la línea de lo que se necesita para mejorar la ergonomía del desarrollo de proyectos de tamaño mediano en TypeScript.

Como siempre, ¡muchas gracias a todo el equipo de TypeScript por sus esfuerzos! Estás haciendo un trabajo increíble.

Hay un truco con el espacio de trabajo de lerna / yarn que te hará la vida mucho más fácil.
Apunta las entradas main y types en tu package.json de los subproyectos a tu src / index. ts , y el escenario Buscar referencias / Cambiar nombre simplemente funcionará.
Y podrá compilar todo su proyecto con un solo tsc en ejecución.
Puede hacer eso para algunos de sus paquetes, puede hacerlo para todos. tu llamada.

Hay algunos inconvenientes y trampas (si tiene aumento en un paquete o la importación de cualquier símbolo global, contaminará todo su programa), pero en general funciona muy bien.
Cuando desee publicar en NPM, simplemente configure los tipos principales y los valores apropiados (como parte de su compilación, más o menos)

Con la configuración anterior, obtuve más o menos todas las características esperadas

aquí hay un truco con el espacio de trabajo de lerna / yarn que te hará la vida mucho más fácil.
Apunte las entradas principales y de tipos en su package.json de los subproyectos a su archivo src / index.ts, y el escenario Buscar referencias / Renombrar simplemente funcionará.

En mi experiencia, el problema con esa configuración es que TypeScript comenzará a tratar los archivos ts de los paquetes _external_ como si fueran _sources_ del paquete que los requiere, no como bibliotecas externas. Esto provoca una serie de problemas.

  • Los paquetes externos se compilan varias veces, cada vez utilizando el tsconfig del paquete _requiring_. Si los paquetes requeridos tienen tsconfigs diferentes (por ejemplo, diferentes bibliotecas), esto puede causar que aparezcan errores de compilación falsos en el paquete requerido hasta que se compile de nuevo.

  • Los paquetes que requieren también se compilan más lentamente porque incluyen más archivos de los necesarios.

  • El rootDir de todos los paquetes se convierte en el directorio de nivel superior, lo que potencialmente permite la inclusión directa de cualquier archivo TS de cualquier paquete, en lugar de solo incluirlo desde index . Si los desarrolladores no tienen cuidado, pueden omitir la API del paquete requerido. Además, si el proceso de compilación no es sólido, los paquetes requeridos pueden terminar conteniendo código duplicado del paquete requerido que estaba destinado a ser externo.

En nuestros proyectos hemos descartado la dependencia de archivos TS por los inconvenientes. Todas las dependencias entre paquetes están en los archivos index.d.ts , por lo que el compilador las trata como externas y todo está bien.

Por supuesto, dependiendo de .d.ts tiene el problema de requerir una compilación de varios pasos (no es posible de inmediato con herramientas como el paquete web) y el problema de una mala experiencia con IDE (cambios de nombre, referencias que no cruzan los límites del paquete ).

Estoy de acuerdo con algunos de los otros sentimientos: el mecanografiado es un compilador, no un sistema de compilación. Necesitamos nuestras herramientas para respaldar mejores compilaciones de proyectos múltiples. Sé que hay algunas opciones en la comunidad que comienzan a hacer esto. Como ejemplo, C # tiene un compilador llamado Roslyn y una herramienta de compilación llamada MSBuild.

Debate hoy con @mhegazy sobre cómo hacer que el cambio de nombre funcione con la menor molestia posible.

El peor caso no degenerado para el cambio de nombre se ve así:

// alpha.ts
const v = { a: 1 };
export function f() { return v; }
export function g() { return v; }

// alpha.d.ts (generated)
export function f(): { a: number };
export function g(): { a: number };

// beta.ts (in another project)
import { f } from '../etc/alpha';
f().a;

// gamma.ts (in yet another project)
import { g } from '../etc/alpha';
g().a;

La observación clave es que es imposible saber que cambiar el nombre de f().a debería cambiar el nombre de g().a menos que pueda ver alpha.ts para correlacionar los dos.

Un bosquejo del plan de implementación:

  • Tener representaciones de SourceFile en memoria "ricas" y "esbeltas" de archivos .d.ts. Los archivos "lean" se leen desde el disco; los "ricos" se producen como resultado de la generación de .d.ts en memoria
  • Los archivos fuente "ricos" .d.ts tienen referencias de sus nodos identificadores a los nombres de origen en el archivo fuente original
  • Durante el cambio de nombre, primero vamos a definir el símbolo en cuestión, como de costumbre. Si esto se origina en un .d.ts con referencia a un proyecto, lo cargamos de implementación y creamos su archivo .d.ts "enriquecido".

    • Nota: este proceso es iterativo, es decir, puede requerir rastrear varios niveles hasta que aterrice en un archivo de implementación real (uno que no sea un redireccionamiento .d.ts debido a una referencia de proyecto)

  • Ahora use los punteros "enriquecidos" para averiguar qué otros identificadores en .d.ts del proyecto se originan en el mismo identificador de archivo fuente.
  • En cada proyecto posterior, escanee el texto en busca del identificador renombrado y vea si su resultado "ir a la definición" es alguna de las ubicaciones "iniciadas desde el mismo símbolo" en el .d.ts
  • Realice el cambio de nombre en el archivo de implementación

@RyanCavanaugh irá a la definición / ¿buscará que todas las referencias funcionen con este modelo?

Notas de una discusión anterior con Anders y Mohamed sobre una gran cantidad de preguntas abiertas

  • ¿ prepend también se aplica a .d.ts ? sí
  • ¿Qué hacemos con @internal en la rama de prueba interna? Necesitamos mantener las declaraciones internas en los archivos .d.ts locales, pero no queremos que aparezcan en las versiones de salida.

    • Quitar --stripInternal

    • Pero no lo desprecies (¿todavía ...?)

    • Ryan para escribir la herramienta remove-internal (doneish)

    • built -> El proceso LKG elimina las declaraciones @internal

  • ¿Qué sucede si cambia un archivo .d.ts de, por ejemplo, @types ?

    • Necesita forzar manualmente una reconstrucción si desea ver posibles nuevos errores ☹️

    • Eventualmente podría escribir un archivo "archivos que miré.txt" en la carpeta de salida si esto se vuelve realmente problemático

  • ¿Es noEmitOnError obligatorio? Si.

    • De lo contrario, las reconstrucciones ocultarían errores.

  • referenceTarget -> composable ✨ 🚲 🏡 ✨
  • ¿Qué pasa con los proyectos de nodo hoja que no quieren que se emitan declaraciones, pero sí quieren una reconstrucción rápida?

    • tsbuild o su equivalente puede verificar su cumplimiento con los requisitos no relevantes de upstream de composable

  • Referencias circulares, (¿cómo) funcionan?

    • Por defecto, no

    • Puede especificar, por ejemplo, { path: "../blah", circular: true } si desea hacer esto

    • Si lo hace, depende de usted asegurarse de que su compilación sea determinista y siempre alcance un punto fijo (¡¿posiblemente no sea así?!)

    • La reasignación de una circular: la importación opcional (pero priorizada)

Miscelánea

  • @weswigham tiene una idea alternativa para el cambio de nombre que debemos discutir con @mhegazy

Ya he perdido. En su mayoría, solo quería mantener la interpretación de los mapas de origen fuera del compilador (responsabilidades separadas para herramientas separadas) pero mientras no estaba, trabajé en agregarlo de todos modos (porque aparentemente es deseable una definición perfecta).

@RyanCavanaugh
¿Debería renombrar / encontrar todas las referencias que funcionen en los proyectos referenciados después de fusionar # 23944? ¿También deberíamos usar composite: true y projectReferences: [] en caso de que solo se necesiten servicios de idiomas (pero no tsbuild)?

¿Debería renombrar / encontrar todas las referencias que funcionen en los proyectos referenciados después de fusionar # 23944?

todavía no. pero estamos trabajando en esto a continuación.

También deberíamos usar composite: true y projectReferences: [] en caso de que solo se necesiten servicios de lenguaje (pero no tsbuild)?

No estoy seguro de entender la pregunta. ¿Qué quiere decir "servicio de idiomas" y no "construir"?

No estoy seguro de entender la pregunta. ¿Qué quiere decir "servicio de idiomas" y no "construir"?

Solo me interesa la compatibilidad con el editor (cambiar el nombre / buscar todas las referencias / etc ...) en múltiples proyectos en monorepo, no en la nueva herramienta de compilación (también conocida como build mode ) (# 22997) ya que estoy usando babel para mi compilación.

Eso debería funcionar. build es una función opcional, no es necesario que la use si no desea ... similar a cómo tsc no es necesario para su experiencia de servicio de idiomas en VSCode, por ejemplo.

Sin embargo, es probable que deba compilar con declaraciones y mapas de declaraciones para producir los metadatos necesarios para que funcionen las referencias de proyectos cruzados.

No estoy seguro de haber entendido correctamente todos los aspectos de la propuesta, pero ¿sería posible que los proyectos individuales no hicieran referencia a otros por ruta, sino por su nombre? El proyecto del espacio de trabajo debe tener una forma de especificar cada ruta de proyecto, similar a los espacios de trabajo de hilo a través de globs, o tal vez enumerando cada nombre de proyecto individual:

Básicamente, en lugar de:

"dependencies": [
    "../common", 
    "../util"
],

¿Podemos por favor tener

"dependencies": [
    "common", 
    "util"
],

y tener un espacio de trabajo tsconfig.json

"workspace": {
  "common": "packages/common",
  "util": "packages/util"
}

O mejor aún, sintaxis de rutas:

"workspace": {
  "*":"packages/*"
}

Beneficios:

  • capacidad para especificar diferentes reglas de búsqueda según el sistema del módulo
  • capacidad de, por ejemplo, retroceder a node_modules cuando el paquete se descarga fuera del espacio de trabajo
  • capacidad de agregar libremente múltiples espacios de trabajo para trabajar clonando múltiples repositorios y teniendo una configuración diferente para las rutas, de modo que pueda trabajar en más paquetes en paralelo.

O al menos, ¿podemos tener los nombres que no son de ruta (aquellos que no comienzan con './' o '../') reservados para uso futuro ...

No estoy seguro de cuánto está relacionado esto, pero Yarn 1.7 introdujo recientemente un concepto de "espacios de trabajo enfocados", vea esta publicación de blog .

¿Alguien aquí está lo suficientemente familiarizado con los espacios de trabajo y el trabajo que @RyanCavanaugh está haciendo en torno a las referencias del proyecto / modo de compilación de TypeScript para tal vez dejar un comentario explicando si se relacionan en absoluto? Mi intuición es que _en algún lugar_ entre los espacios de trabajo de Yarn (npm también los obtendrá este año) y las futuras versiones de TypeScript se encuentra un buen soporte para monorepos con múltiples proyectos y bibliotecas compartidas. (Sentimos el dolor, actualmente).

Realmente me encantaría recibir una actualización sobre el progreso de esta función. Estamos planeando trasladar Aurelia vNext a un monorepo en el próximo mes más o menos. Nuestra nueva versión es 100% TypeScript y nos encantaría usar un sistema de proyecto TS oficial en lugar de Lerna, si podemos. También estamos felices de ser los primeros en adoptar / probar las nuevas funciones :)

El soporte básico y la definición de goto usando el soporte del tsc --b para soporte de compilación ya está disponible y está destinado a TS 3.0. La base del código mecanografiado se ha movido para usarlo. Actualmente estamos probando este soporte usando el repositorio de mecanografiado.

Lo que aún se necesita en este punto por hacer: 1. obtener buscar todas las referencias / cambiar el nombre para trabajar en escenarios de múltiples proyectos. 2. abordar la actualización de archivos .d.ts en segundo plano en el editor, y 3. --watch soporte para escenarios de múltiples proyectos. también muchas, muchas pruebas.

Este boleto ha estado en los libros durante 3 años. ¿Aún quedan 3/6 artículos pendientes?

@claudeduguay Este es un cambio fundamental en los proyectos que admite TypeScript, es hora de celebrar, ¿no crees? ¡Estoy muy feliz por esto!

@mhegazy Esta es una gran noticia. Estoy muy feliz de saber que el equipo de TS también lo está probando en su propio proyecto. Esperando que se terminen las últimas cosas y tener esto como una opción para Aurelia :) Tan pronto como haya algo de documentación sobre cómo configurarlo, trasladaremos nuestro vNext al nuevo sistema de proyectos. ¡No puedo esperar!

@mhegazy ¿Puede arrojar algo de luz sobre cómo funcionaría todo esto con los archivos y proyectos package.json basados ​​en módulos ES2015? Por ejemplo, en Aurelia vNext, tenemos paquetes como @aurelia/kernel , @aurelia/runtime , @aurelia/jit , etc. Esos son los nombres de los módulos que se usarán en import declaraciones a lo largo de los distintos proyectos. ¿Cómo entenderá el compilador de TS que los nombres de estos módulos se asignan a las diversas carpetas a las que se hace referencia? ¿Recogerá los archivos package.json ubicados en cada carpeta a la que se hace referencia? ¿En qué se diferenciará de Lerna o Yarn Workspaces? Mi mirada inicial a los enlaces anteriores me hace pensar que necesitaría usar proyectos de TS (compilación) en combinación con Lerna (enlace de depuración y publicación) para obtener una solución que funcione, pero no veo cómo TS se va a construir correctamente. si no se puede integrar con package.json y node_modules. La fuente del repositorio de TS es bastante diferente a su proyecto Lerna promedio (nada parecido en realidad), así que estoy empezando a preguntarme si esto podrá satisfacer nuestras necesidades. Cualquier otra información que pueda proporcionar, y esp. una configuración de solución de demostración funcional similar a la que he descrito aquí, sería muy útil. ¡Gracias!

Comparto las mismas preguntas que @EisenbergEffect. En particular, también espero que esto funcione bien con un monorepo administrado por lerna .

Dos escenarios de monorepo a considerar

Comencemos con una configuración en la que haya enlazado todo con lerna:

/packages
  /a
    /node_modules
      /b -> symlink to b with package.json "types" pointing to dist/index.d.ts
  /b
    /dist
      /index.d.ts -> built output of entry point declaration file

Lo clave que queremos que suceda aquí es reconstruir b que construimos a iff a está desactualizado. Así que agregaríamos "references": [ { "path": "../b" } ] a a 's tsconfig.json y ejecutaríamos tsc --build en a para obtener compilaciones ascendentes correctas de b . En este mundo, las referencias de proyectos simplemente sirven como una representación del gráfico de construcción y permiten reconstrucciones de incrementos más inteligentes. Idealmente, lerna y TS podrían cooperar aquí y reflejar las dependencias package.json en tsconfig.json cuando corresponda.

Otro escenario (probablemente menos común) sería si no estuvieras enlazando simbólicamente pero aun así quisieras "actuar como si" estuvieras trabajando en un conjunto de paquetes en vivo. Puede hacer esto hoy con un mapeo de rutas bastante tedioso, y algunas personas lo hacen. Las referencias de proyectos aquí ayudarían de manera similar con el orden de compilación, pero sería muy deseable tener soporte para una propiedad en el archivo tsconfig de referencia para crear automáticamente un mapeo de ruta siempre que se haga referencia a él; por ejemplo, si tuvieras

{
  "compilerOptions": { "outDir": "bin" },
  "package": "@RyanCavanaugh/coolstuff"
}

luego agregar "references": [{ "path": "../cool" }] automáticamente agregaría un mapeo de ruta de @RyanCavanaugh/coolstuff -> ../cool/bin/ . No hemos agregado esto todavía, pero podemos analizarlo si resulta ser un escenario más común.

Idealmente, lerna y TS podrían cooperar aquí y reflejar las dependencias package.json en tsconfig.json donde sea apropiado

En lugar de depender de herramientas externas, podríamos optar por leer su package.json (siempre que esté junto con su tsconfig) como referencias potenciales si composite: true está configurado (verifique si cada paquete resuelto tiene un tsconfig.json , si tiene uno, considérelo un nodo de proyecto construible y repita). Dado que todo está enlazado simbólicamente en su lugar, ni siquiera deberíamos necesitar alterar la resolución (¿mucho? ¿Alguna?) Para manejar el espacio de trabajo. Lerna en realidad no configura ningún elemento específico de ts (o específico de compilación) como es afaik, simplemente enlaza todo en su lugar y administra el control de versiones. Esto sería una optimización de lo que hacemos hoy, que es cargar los archivos ts (ya que los preferimos a las declaraciones) y volver a compilar todo sin importar si están desactualizados.

@RyanCavanaugh Esto suena muy emocionante. ¿Alguna idea de si funcionará con la estrategia de enlace simbólico common / temp / package.json que contiene el superconjunto de todas las dependencias para todos los paquetes en el repositorio. Luego usamos pnpm para realizar una sola operación de instalación para este paquete sintético. (PNPM utiliza enlaces simbólicos para crear un gráfico acíclico dirigido en lugar de la estructura de árbol de NPM, lo que elimina las instancias de biblioteca duplicadas). Luego, Rush crea una carpeta node_modules para cada proyecto en el repositorio, hecha de enlaces simbólicos que apuntan a las carpetas apropiadas en common / temp . El resultado es totalmente compatible con TypeScript y el algoritmo de resolución estándar de NodeJS. Es muy rápido porque hay un archivo shrinkwrap y una ecuación de control de versiones para todo el repositorio, al tiempo que permite que cada paquete especifique sus propias dependencias.

No ponemos nada especial en tsconfig.json para este modelo. Si se requiere alguna configuración especial de TypeScript para la función de ir a definición, lo ideal sería que la autogenerara durante la instalación en lugar de tenerla almacenada en Git.

@pgonzal ¡ Gracias por el enlace a Rush! No lo había visto todavía. Lo comprobaré esta noche.

@RyanCavanaugh Gracias por la explicación. Tu primer escenario con lerna es el más cercano a lo que tendríamos. Aquí está nuestro repositorio de UX con TS y lerna como ejemplo de algo que nos gustaría usar en el soporte del nuevo proyecto en https://github.com/aurelia/ux

@weswigham Parece que lo que está describiendo también encajaría en nuestro escenario. Repositorio de ejemplo anterior.

Solo tenga en cuenta que en el caso de los espacios de trabajo de hilo, los módulos no están vinculados simbólicamente en el directorio de cada paquete individual, sino que están vinculados simbólicamente en el espacio node_modules trabajo de nivel superior

Por lo que, dicho sea de paso, creo que las referencias que no comienzan con un punto ('./' o '../') deberían reservarse para el futuro. Con suerte, esos terminarán siendo "referencias con nombre", manejadas a través de la estrategia de resolución del módulo activo en lugar de tratarse como rutas relativas.

@spion solo usaremos un nombre de propiedad que no sea path para eso si es necesario (por ejemplo, "references": [ { "module": "@foo/baz" } ] ); No quiero causar confusión en la que "bar" y "./bar" significan lo mismo en files pero algo diferente en references

Documentos / publicación de blog en progreso a continuación (se editará en función de los comentarios)

Animaría a cualquiera que siga este hilo a que lo pruebe. Estoy trabajando en el escenario de monorepo ahora para solucionar los últimos errores / características allí y debería tener alguna orientación al respecto pronto


Referencias de proyectos

Las referencias de proyectos son una nueva característica de TypeScript 3.0 que le permite estructurar sus programas de TypeScript en partes más pequeñas.

Al hacer esto, puede mejorar en gran medida los tiempos de compilación, hacer cumplir la separación lógica entre componentes y organizar su código de nuevas y mejores formas.

También estamos introduciendo un nuevo modo para tsc , el indicador --build , que funciona de la mano con las referencias del proyecto para permitir compilaciones de TypeScript más rápidas.

Un proyecto de ejemplo

Veamos un programa bastante normal y veamos cómo las referencias de proyectos pueden ayudarnos a organizarlo mejor.
Imagina que tienes un proyecto con dos módulos, converter y units , y un archivo de prueba correspondiente para cada uno:

/src/converter.ts
/src/units.ts
/test/converter-tests.ts
/test/units-tests.ts
/tsconfig.json

Los archivos de prueba importan los archivos de implementación y realizan algunas pruebas:

// converter-tests.ts
import * as converter from "../converter";

assert.areEqual(converter.celsiusToFahrenheit(0), 32);

Anteriormente, era bastante incómodo trabajar con esta estructura si usaba un solo archivo tsconfig:

  • Fue posible que los archivos de implementación importen los archivos de prueba.
  • No fue posible construir test y src al mismo tiempo sin que apareciera src en el nombre de la carpeta de salida, lo que probablemente no desee
  • Cambiar solo las partes internas en los archivos de implementación requería volver a revisar las pruebas, aunque esto nunca causaría nuevos errores
  • Cambiar solo las pruebas requirió verificar la implementación nuevamente, incluso si nada cambió

Puede usar varios archivos tsconfig para resolver algunos de esos problemas, pero aparecerían otros nuevos:

  • No hay una verificación incorporada y actualizada, por lo que siempre termina ejecutando tsc dos veces
  • Invocar tsc dos veces incurre en una mayor sobrecarga de tiempo de inicio
  • tsc -w no se puede ejecutar en varios archivos de configuración a la vez

Las referencias de proyectos pueden resolver todos estos problemas y más.

¿Qué es una referencia de proyecto?

tsconfig.json archivos references . Es una matriz de objetos que especifica proyectos para hacer referencia:

{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../src" }
    ]
}

La propiedad path de cada referencia puede apuntar a un directorio que contiene un archivo tsconfig.json , o al archivo de configuración en sí (que puede tener cualquier nombre).

Cuando haces referencia a un proyecto, suceden cosas nuevas:

  • La importación de módulos de un proyecto referenciado cargará su archivo de declaración de salida ( .d.ts )
  • Si el proyecto al que se hace referencia produce un outFile , las declaraciones del archivo de salida .d.ts serán visibles en este proyecto
  • El modo de construcción (ver más abajo) construirá automáticamente el proyecto referenciado si es necesario

Al dividirse en varios proyectos, puede mejorar en gran medida la velocidad de la verificación de tipos y la compilación, reducir el uso de memoria cuando se utiliza un editor y mejorar la aplicación de las agrupaciones lógicas de su programa.

composite

Los proyectos referenciados deben tener habilitada la nueva configuración composite .
Esta configuración es necesaria para garantizar que TypeScript pueda determinar rápidamente dónde encontrar las salidas del proyecto al que se hace referencia.
Habilitar la bandera composite cambia algunas cosas:

  • La configuración rootDir , si no se establece explícitamente, tiene como valor predeterminado el directorio que contiene el archivo tsconfig
  • Todos los archivos de implementación deben coincidir con un patrón include o aparecer en la matriz files . Si se viola esta restricción, tsc le informará qué archivos no se especificaron
  • declaration debe estar encendido

declarationMaps

También hemos agregado soporte para mapas de fuentes de declaración .
Si habilita --declarationMap , podrá utilizar funciones del editor como "Ir a la definición" y Cambiar el nombre para navegar y editar el código de forma transparente a través de los límites del proyecto en los editores compatibles.

prepend con outFile

También puede habilitar anteponer la salida de una dependencia usando la opción prepend en una referencia:

   "references": [
       { "path": "../utils", "prepend": true }
   ]

Anteponer un proyecto incluirá el resultado del proyecto por encima del resultado del proyecto actual.
Esto funciona tanto para archivos .js archivos .d.ts , y los archivos de mapas de origen también se emitirán correctamente.

tsc solo usará archivos existentes en el disco para realizar este proceso, por lo que es posible crear un proyecto donde no se puede generar un archivo de salida correcto porque la salida de algún proyecto estaría presente más de una vez en el archivo resultante .
Por ejemplo:

  ^ ^ 
 /   \
B     C
 ^   ^
  \ /
   D

En esta situación, es importante no anteponer cada referencia, porque terminará con dos copias de A en la salida de D ; esto puede llevar a resultados inesperados.

Advertencias para las referencias de proyectos

Las referencias de proyectos tienen algunas ventajas y desventajas que debe conocer.

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

Además, para preservar la compatibilidad con los flujos de trabajo de compilación existentes, tsc no creará dependencias automáticamente a menos que se invoque con el conmutador --build .
Aprendamos más sobre --build .

Modo de compilación para TypeScript

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

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

  • Encuentra todos los proyectos referenciados
  • Detecta si están actualizados
  • Cree proyectos obsoletos en el orden correcto

Puede proporcionar tsc -b con múltiples rutas de archivo de configuración (por ejemplo, tsc -b src test ).
Al igual que tsc -p , especificar el nombre del archivo de configuración en sí es innecesario si se llama tsconfig.json .

tsc -b Línea

Puede especificar cualquier número de archivos de configuración:

 > tsc -b                                # Build the tsconfig.json in the current directory
 > tsc -b src                            # Build src/tsconfig.json
 > tsc -b foo/release.tsconfig.json bar  # Build foo/release.tsconfig.json and bar/tsconfig.json

No se preocupe por ordenar los archivos que pasa en la línea de comandos: tsc los reordenará si es necesario para que las dependencias siempre se construyan primero.

También hay algunas banderas específicas para tsc -b :

  • --verbose : Imprime un registro detallado para explicar lo que está sucediendo (se puede combinar con cualquier otra bandera)
  • --dry : muestra lo que se haría pero en realidad no crea nada
  • --clean : Elimina las salidas de los proyectos especificados (se puede combinar con --dry )
  • --force : Actúe como si todos los proyectos estuvieran desactualizados
  • --watch : Modo reloj (no se puede combinar con ninguna bandera excepto --verbose )

Advertencias

Normalmente, tsc producirá salidas ( .js y .d.ts ) en presencia de errores de sintaxis o tipo, a menos que noEmitOnError esté activado.
Hacer esto en un sistema de compilación incremental sería muy malo: si una de sus dependencias desactualizadas tuviera un nuevo error, solo lo vería una vez porque una compilación posterior omitiría la compilación del proyecto ahora actualizado.
Por esta razón, tsc -b actúa efectivamente como si noEmitOnError estuviera habilitado para todos los proyectos.

Si verifica cualquier salida de compilación ( .js , .d.ts , .d.ts.map , etc.), es posible que deba ejecutar una compilación --force después de cierto control de fuente operaciones dependiendo de si su herramienta de control de código fuente conserva mapas de tiempo entre la copia local y la copia remota.

msbuild

Si tiene un proyecto de msbuild, puede activar el modo de compilación agregando

    <TypeScriptBuildMode>true</TypeScriptBuildMode>

a su archivo de proyecto. Esto permitirá la construcción incremental automática así como la limpieza.

Tenga en cuenta que, al igual que con tsconfig.json / -p , las propiedades del proyecto TypeScript existente no se respetarán; todas las configuraciones deben administrarse utilizando su archivo tsconfig.

Algunos equipos han configurado flujos de trabajo basados ​​en msbuild en los que los archivos tsconfig tienen el mismo orden de gráficos implícito que los proyectos administrados con los que están emparejados.
Si su solución es así, puede continuar usando msbuild con tsc -p junto con las referencias del proyecto; estos son completamente interoperables.

Guia

Estructura general

Con más archivos tsconfig.json , generalmente querrá usar la herencia de archivos de configuración para centralizar las opciones comunes del compilador.
De esta manera, puede cambiar una configuración en un archivo en lugar de tener que editar varios archivos.

Otra buena práctica es tener una "solución" tsconfig.json archivo que simplemente tiene references para todos sus proyectos de nodo hoja.
Esto presenta un punto de entrada simple; por ejemplo, en el repositorio de TypeScript simplemente ejecutamos tsc -b src para construir todos los puntos finales porque enumeramos todos los subproyectos en src/tsconfig.json
Tenga en cuenta que a partir de 3.0, ya no es un error tener una matriz files vacía si tiene al menos un reference en un archivo tsconfig.json .

Puede ver estos patrones en el repositorio de TypeScript; consulte src/tsconfig_base.json , src/tsconfig.json y src/tsc/tsconfig.json como ejemplos clave.

Estructuración de módulos relativos

En general, no se necesita mucho para realizar la transición de un repositorio utilizando módulos relativos.
Simplemente coloque un archivo tsconfig.json en cada subdirectorio de una carpeta principal determinada y agregue reference s a estos archivos de configuración para que coincidan con las capas previstas del programa.
Deberá establecer outDir en una subcarpeta explícita de la carpeta de salida, o establecer rootDir en la raíz común de todas las carpetas del proyecto.

Estructuración para outFiles

El diseño de las compilaciones que usan outFile es más flexible porque las rutas relativas no importan tanto.
Una cosa a tener en cuenta es que generalmente no querrá usar prepend hasta el "último" proyecto; esto mejorará los tiempos de compilación y reducirá la cantidad de E / S necesaria en una compilación determinada.
El repositorio de TypeScript en sí mismo es una buena referencia aquí; tenemos algunos proyectos de "biblioteca" y algunos proyectos de "punto final"; Los proyectos de "endpoint" se mantienen lo más pequeños posible y solo incorporan las bibliotecas que necesitan.

Estructuración para monorepos

TODO: Experimente más y descubra esto. Rush y Lerna parecen tener diferentes modelos que implican diferentes cosas de nuestra parte.

También busco comentarios sobre # 25164

@RyanCavanaugh Muy buen artículo, y la excelente característica, sería muy bueno probarlo, especialmente. después de pasar días organizando nuestro gran proyecto en subproyectos con referencias de archivos de configuración.

Tengo un par de notas:

  1. ¿Qué es un monorepo? Sería bueno describir un poco más este caso de uso.
  2. En la mayoría de los casos (especialmente en el caso de grandes proyectos), hay muchos artefactos adicionales generados durante la compilación. En nuestro caso, estos son CSS, HTML, archivos de imagen, etc., a través de gulp. Me pregunto cómo se adaptaría el uso de tales herramientas de construcción a esta nueva forma de hacer las cosas. Digamos, me gustaría ejecutar watch no solo en archivos * .ts, sino en todos nuestros otros archivos (estilos, marcado, etc.). ¿Como hacer eso? ¿Necesita ejecutar, digamos gulp watch y tsc -b -w en paralelo?

@vvs a monorepo es una colección de paquetes de NPM generalmente administrados por una herramienta como Rush o Lerna

Si está usando gulp, querría usar un cargador que entendiera las referencias del proyecto de forma nativa para obtener la mejor experiencia. @rbuckton ha hecho algo de trabajo aquí, ya que tenemos algunos desarrolladores que utilizan un archivo gulp internamente; tal vez pueda opinar sobre cómo se ven los buenos patrones allí

@RyanCavanaugh Esto se ve bien. Estoy muy interesado en la guía de Lerna :)

@RyanCavanaugh esto se ve muy bien, actualmente estoy trabajando para probarlo con nuestra lerna monorepo.

Lo único que no me quedó claro en su escrito fue la opción prepend . No entendí muy bien qué problema está abordando, en qué situación querría usarlo y qué sucede si no lo usa.

¡Esto es asombroso! Trabajo en ts-loader y proyectos relacionados. ¿Es probable que sean necesarios cambios para respaldar esto en proyectos que usan LanguageServiceHost / WatchHost TypeScript?

(Consulte https://github.com/TypeStrong/ts-loader/blob/master/src/servicesHost.ts para ver un ejemplo de lo que quiero decir).

Si es así, ¡todas las orientaciones / relaciones públicas se reciben con gratitud! De hecho, si desea que esto se pruebe en el mundo de los paquetes web, me complacerá ayudarlo a lanzar una versión de ts-loader que admita esto.

Por supuesto, si "simplemente funciona", es aún mejor: sonríe:

¡Buen trabajo!

@yortus @EisenbergEffect He configurado un repositorio lerna de muestra en https://github.com/RyanCavanaugh/learn-a con un README que describe los pasos que tomé para que funcione.

Si lo entiendo correctamente, tsc -b X no hará nada si todo (X y todas sus dependencias y dependencias transitivas) está actualizado. ¿Se pregunta si eso es algo que podríamos obtener incluso sin la -b para proyectos individuales sin referencias? (menos dependencias en ese caso, por supuesto)

Esto está muy bien. Tiendo a usar un Lerna con una configuración como esta (para separar el repositorio mono por función). Supongo que funcionaría igual de bien.

{
"lerna": "2.11.0",
"paquetes": [
"paquetes / componentes / ","paquetes / bibliotecas / ",
"paquetes / frameworks / ","paquetes / aplicaciones / ",
"paquetes / herramientas / *"
],
"versión": "0.0.0"
}

¿Entonces esto está disponible en typescript@next ?

Lo probaré con nuestro repositorio de espacio de trabajo de hilo. Tenemos que usar nohoist para algunos módulos que aún no admiten espacios de trabajo, por lo que será bueno ver cómo lo maneja.

@RyanCavanaugh Tomé el repositorio para una prueba esta noche. Abrí un problema en el repositorio para informar algunos problemas que tenía. Gracias de nuevo por armar esto. Espero usarlo pronto.

¡Muy interesante! Actualmente, en mi empresa, utilizamos mi propia herramienta llamada mtsc para admitir el modo de visualización de varios proyectos al mismo tiempo. Tenemos alrededor de 5 proyectos que deben compilarse y observarse en el mismo repositorio.

Los proyectos tienen diferentes configuraciones como; Orientación ECMA (es5, es6), tipos (nodo, broma, DOM, etc.), emitir (algunos usan webpack y otros compilan en js). Todos comparten una cosa, y ese es el complemento tslint , el resto puede ser diferente. Mi herramienta también ejecuta tslint después de la compilación de un proyecto (por proyecto y se detiene si un proyecto se vuelve a compilar antes de que tslint finalice).

Mi principal preocupación con la propuesta actual es que no se puede decir qué proyectos comparten qué recursos. Tenemos un proyecto de servidor y cliente, que ambos usan una carpeta de utilidad especial, pero que no queremos ver los errores de compilación de dos veces. Pero eso se puede arreglar con un filtro, así que no es gran cosa :)

Probé el nuevo modo --build con nuestro lerna monorepo, que actualmente consta de 17 paquetes interdependientes. Tomó un tiempo hacer que todo funcionara, pero ahora todo funciona, y poder construir de forma incremental es una gran mejora para nosotros.

Encontré algunos problemas que describo a continuación. Espero que esta sea una retroalimentación útil para el equipo de TS y pueda ayudar a otros a conseguir que el modo --build funcione para sus proyectos.

Comentarios para el modo tsc --build

1. Espúreo "\está desactualizado porque el archivo de salida '2.map' no existe "

Recibí este mensaje para cada paquete en cada compilación, por lo que cada compilación se convirtió en una reconstrucción completa incluso cuando no se cambió nada. Me di cuenta de que @RyanCavanaugh ya ha solucionado esto en # 25281, por lo que ya no es un problema si actualiza a 20180628 o más tarde todas las noches. Los siguientes problemas asumen que ha actualizado al menos a 20180628 noche.

2. "\está actualizado con los archivos .d.ts de sus dependencias "cuando no

EDITAR: informado en # 25337.

Para reproducir este problema, configure @RyanCavanaugh 's learn-a cesión temporal de la muestra según sus instrucciones . Ejecute tsc -b packages --verbose para ver que todo se construye la primera vez. Ahora cambie la línea 1 en pkg1/src/index.ts a import {} from "./foo"; y guárdelo. Ejecute tsc -b packages --verbose nuevamente. La compilación para pkg2 se omite, aunque pkg1 se modificó de una manera que rompe el código fuente de pkg2 . Ahora puede ver un garabato rojo en pkg2/src/index.ts . Vuelva a compilar con tsc -b packages --force y se muestra el error de compilación. Los siguientes problemas asumen la construcción con --force para solucionar este problema.

3. Se generaron archivos .d.ts que causaron errores de compilación de "identificador duplicado" en los paquetes posteriores.

EDITAR: informado en # 25338.

Para reproducir este problema, configure @RyanCavanaugh 's learn-a cesión temporal de la muestra según sus instrucciones . Ahora ejecute lerna add @types/node para agregar tipificaciones de Node.js a los tres paquetes. Ejecute tsc -b packages --force para confirmar que aún se compila bien. Ahora agregue el siguiente código a pkg1/src/index.ts :

// CASE1 - no build errors in pkg1, but 'duplicate identifier' build errors in pkg2
// import {parse} from 'url';
// export const bar = () => parse('bar');

// CASE2 - no build errors in pkg1 or in downstream packages
// import {parse, UrlWithStringQuery} from 'url';
// export const bar = (): UrlWithStringQuery => parse('bar');

// CASE3 - no build errors in pkg1 or in downstream packages
// export declare const bar: () => import("url").UrlWithStringQuery;

// CASE4 - no build errors in pkg1, but 'duplicate identifier' build errors in pkg2
// import {parse} from 'url';
// type UrlWithStringQuery = import("url").UrlWithStringQuery;
// export const bar = (): UrlWithStringQuery => parse('bar');

Descomente un caso a la vez y ejecute tsc -b packages --force . Los casos 1 y 4 provocan una avalancha de errores de compilación en pkg2 . Con los casos 2 y 3, no hay errores de compilación. La diferencia importante con los casos 1 y 4 parece ser la primera línea en el pkg1/lib/index.d.ts generado:

/// <reference path="../node_modules/@types/node/index.d.ts" />

Los casos 2 y 3 no generan esta línea. Cuando se construye pkg2 en los casos 1 y 4, incluye dos copias idénticas de declaraciones @types/node en diferentes rutas, y eso provoca los errores de 'identificador duplicado'.

Quizás esto sea por diseño, ya que los casos 2 y 3 funcionan. Sin embargo, parece bastante confuso. No hay errores de compilación ni advertencias en pkg1 para ninguno de estos 4 casos, pero el comportamiento de compilación posterior es muy sensible al estilo exacto de las declaraciones exportadas. Creo que (a) pkg1 debería generar un error para los casos 1 y 4, o (b) los cuatro casos deberían tener el mismo comportamiento de compilación posterior, o (c) debería haber una guía clara del equipo de TS sobre cómo escribir declaraciones para evitar este problema.

4. Problemas con import tipos en generadas .d.ts archivos cuando se utilizan los espacios de trabajo de hilo

Al tratar de obtener trabajo modo de construcción con nuestro paquete de 17 monorepo, trabajé a través de una serie de errores de generación causados por las rutas relativas en import tipos en generadas .d.ts archivos. Finalmente resolví que el problema estaba relacionado con la elevación del módulo. Es decir, cuando se utilizan espacios de trabajo de hilo, todos los módulos instalados se elevan al directorio monorepo-root node_modules , incluidos los enlaces simbólicos para todos los paquetes del monorepo. Cambié el monorepo para usar la propiedad packages en lerna.json , lo que hace que lerna use su propio algoritmo de arranque sin elevación, y eso resolvió el problema. De todos modos, es un enfoque mejor / más seguro, aunque más lento.

No estoy seguro de si TS tiene la intención de admitir configuraciones de módulos, por lo que no he desarrollado una reproducción para los problemas que encontré, pero podría intentar hacer una si hay interés. Creo que el problema puede ser que algunas compilaciones obtienen el mismo tipo a través del directorio de nivel superior packages (según la configuración de tsconfig) y el directorio de nivel superior node_modules (según import tipos en archivos .d.ts generados). Esto a veces funciona debido a la escritura estructural, pero falla para cosas como símbolos únicos exportados.

5. Configuración repetitiva

Configurar un monorepo para usar lerna básicamente solo requiere poner algo como "packages": ["packages/*"] en lerna.json . Lerna calcula la lista exacta de paquetes expandiendo globstars y luego calcula el gráfico de dependencia exacto observando las dependencias declaradas en el package.json cada paquete. Puede agregar y eliminar paquetes y dependencias a voluntad, y lerna mantiene al día sin necesidad de tocar su configuración.

El modo TypeScript --build implica un poco más de ceremonia. Los patrones globales no se reconocen, por lo que todos los paquetes deben enumerarse y mantenerse explícitamente (por ejemplo, en packages/tsconfig.json ) en el repositorio de learn-a @RyanCavanaugh. El modo de compilación no tiene en cuenta las dependencias package.json , por lo que cada paquete debe mantener la lista de otros paquetes de los que depende tanto en su archivo package.json (bajo "dependencies" ) como es tsconfig.json archivo (menos de "references" ).

Este es un inconveniente menor, pero lo incluyo aquí ya que encontré el galimatías notable en comparación con el enfoque lerna's .

6. Fallo de tsc con aumentos de módulo globales

EDITAR: informado en # 25339.

Para reproducir este problema, configure @RyanCavanaugh 's learn-a repo muestra de acuerdo con sus instrucciones . Ahora ejecute lerna add @types/multer para agregar multer mecanografía a los tres paquetes. Ejecute tsc -b packages --force para confirmar que aún se compila bien. Ahora agregue la siguiente línea a pkg1/src/index.ts :

export {Options} from 'multer';

Ejecute tsc -b packages --force nuevamente. El compilador falla debido a una aserción violada. Miré brevemente el seguimiento y la aserción de la pila, y parece que tiene algo que ver con el aumento global del espacio Express nombres

gracias @yortus por los comentarios. confiar en apreciarlo. para 3, creo que es https://github.com/Microsoft/TypeScript/issues/25278.

Para 4, no estoy familiarizado con la elevación de módulos como concepto. ¿Puedes elaborar y / o compartir una reproducción?

@mhegazy muchos de los que usan lerna y yarn usan espacios de trabajo (incluyéndome a mí). Más información aquí: https://yarnpkg.com/lang/en/docs/workspaces/

Actualmente estoy usando espacios de trabajo de hilo, lerna, tsconfigs extendidos donde el tsconfig base declara paths compartido para todos los paquetes con el módulo elevado que se encuentra en root/node_modules . Cuando escucho yarn y monorepo , pienso workspaces porque esa es la intención de la función: facilitar el uso y reducir la duplicación. Esperaba que este cambio simplemente eliminara mi largo / doloroso paths declarado en mi tsconfig base.

Aquí hay una muestra de nuestro tsconfig raíz monorepo (si es de alguna ayuda):

{
  "extends": "./packages/build/tsconfig.base.json",
  "compilerOptions": {
    "baseUrl": "./packages",
    "paths": {
      "@alienfast/build/*": ["./build/src/*"],
      "@alienfast/common-node/*": ["./common-node/src/*"],
      "@alienfast/common/*": ["./common/src/*"],
      "@alienfast/concepts/*": ["./concepts/src/*"],
      "@alienfast/faas/*": ["./faas/src/*"],
      "@alienfast/math/*": ["./math/src/*"],
      "@alienfast/notifications/*": ["./notifications/src/*"],
      "@alienfast/ui/*": ["./ui/src/*"],
      "@alienfast/build": ["./build/src"],
      "@alienfast/common-node": ["./common-node/src"],
      "@alienfast/common": ["./common/src"],
      "@alienfast/concepts": ["./concepts/src"],
      "@alienfast/faas": ["./faas/src"],
      "@alienfast/math": ["./math/src"],
      "@alienfast/notifications": ["./notifications/src"],
      "@alienfast/ui": ["./ui/src"],
    }
  },
  "include": ["./typings/**/*", "./packages/*/src/**/*"],
  "exclude": ["node_modules", "./packages/*/node_modules"]
}

Intentaré bifurcar una muestra:
https://github.com/RyanCavanaugh/learn-a

Aquí hay un PR no para fusionar al repositorio de
https://github.com/RyanCavanaugh/learn-a/pull/3/files

También utilizamos izado de módulos en Jupyterlab , con lerna e hilo. Nos permite compartir básicamente nuestras dependencias instaladas entre todos nuestros paquetes, por lo que solo existen una vez en el sistema de archivos, en el proyecto raíz.

Entiendo que los espacios de trabajo son un poco más limpios que tener que usar el comando link entre todos los paquetes para que puedan acceder entre sí (o al menos acceder a sus dependencias).

Como se indicó anteriormente, la elevación de módulos mueve todas las dependencias a un directorio raíz node_modules . Esto aprovecha el hecho de que la resolución del módulo de nodo siempre recorrerá el árbol de directorios y buscará en todos los directorios node_modules hasta que encuentre el módulo requerido. Los módulos individuales en su monorepo se enlazan simbólicamente en esta raíz node_modules y todo simplemente funciona. La publicación del blog de hilo probablemente lo explica mejor que yo.

No se garantiza que ocurra el izado. Si tiene versiones no coincidentes del mismo paquete, no se levantarán. Además, muchas herramientas existentes no admiten la elevación porque hacen suposiciones sobre dónde estará node_modules o no siguen correctamente la resolución del módulo de nodo. Debido a esto, hay una configuración nohoist que puede deshabilitar la elevación para módulos o dependencias específicas.

Agregué un sexto elemento a mis comentarios anteriores . tsc bloquea en el escenario que se describe allí.

@mhegazy No estoy seguro de que el elemento 3 esté relacionado con # 25278. # 25278 describe la emisión de declaración no válida. Mis archivos de declaración generados eran sintácticamente y semánticamente válidos, pero causaron que los proyectos posteriores se construyeran con dos copias de mecanografía de nodos, lo que resultó en errores de 'identificador duplicado'.

Como se indicó anteriormente, la elevación de módulos mueve todas las dependencias a un directorio raíz node_modules. Esto aprovecha el hecho de que la resolución del módulo de nodo siempre recorrerá el árbol de directorios y buscará en todos los directorios de node_modules hasta que encuentre el módulo requerido.

Por cierto, hay una desventaja de este modelo, que conduce a "dependencias fantasma" donde un proyecto puede importar una dependencia que no fue declarada explícitamente en su archivo package.json. Cuando publica su biblioteca, esto puede causar problemas, como (1) que se instale una versión diferente de la dependencia que la que se probó / esperada, o (2) la dependencia falta por completo porque se obtuvo de un proyecto no relacionado que no está instalado en este contexto. PNPM y Rush tienen opciones arquitectónicas destinadas a proteger contra dependencias fantasma.

Tengo una pregunta general sobre tsc --build : ¿El compilador de TypeScript busca asumir el papel de orquestar la compilación para proyectos en un monorepo? Normalmente, la cadena de herramientas tendrá toda una serie de tareas, cosas como:

  • preprocesamiento
  • generando cadenas localizadas
  • convertir activos a JavaScript (css, imágenes, etc.)
  • compilación (comprobación de tipos / transpilación)
  • enrollando los archivos .js (por ejemplo, paquete web)
  • enrollando los archivos .d.ts (por ejemplo, API Extractor)
  • Posprocesamiento, incluidas pruebas unitarias y generación de documentación.

Normalmente, un sistema como Gulp o Webpack gestiona esta canalización, y el compilador es solo un paso en el medio de la cadena. A veces, una herramienta secundaria también ejecuta la compilación de otra manera, por ejemplo, Jest + ts-jest por jest --watch .

¿ tsc tiene como objetivo gestionar estas cosas por sí mismo? Y si no es así, ¿hay alguna manera de que un orquestador de compilación convencional resuelva el gráfico de dependencia en sí mismo y, por ejemplo, invoque tsc repetidamente en cada carpeta de proyecto en el orden correcto (después de que se haya actualizado el preprocesamiento)?

O, si el diseño es procesar un monorepo completo en una sola pasada (mientras que hoy construimos cada proyecto en un proceso de NodeJs separado), también tengo curiosidad por saber cómo participarán las otras tareas de compilación: por ejemplo, ¿ejecutaremos webpack en todos los proyectos a la vez? (En el pasado, eso provocó problemas de falta de memoria). ¿Perderemos la capacidad de aprovechar la concurrencia de múltiples procesos?

Estas no son críticas por cierto. Solo estoy tratando de comprender el panorama general y el uso previsto.

@pgonzal correcto, hay muchas partes que no son de tsc para construir un monorepo del mundo real. Para nuestra lerna monorepo, tomé el siguiente enfoque:

  • cada paquete en el monorepo define opcionalmente un script prebuild y / o un script postbuild en su package.json . Estos contienen los aspectos de la compilación que no son tsc.
  • en el package.json del monorepo, hay estos scripts:
    "prebuild": "lerna run prebuild", "build": "tsc --build monorepo.tsconfig.json --verbose", "postbuild": "lerna run postbuild",
  • Eso es todo. Ejecutar yarn build en el nivel de monorepo ejecuta los scripts prebuild para cada paquete que los define, luego ejecuta el paso tsc --build , luego ejecuta todos los postbuild guiones. (Por convención tanto en npm como en yarn, ejecutar npm run foo es aproximadamente lo mismo que npm run prefoo && npm run foo && npm run postfoo .)

¿Cómo maneja jest --watch o webpack-dev-server ? Por ejemplo, cuando se modifica un archivo de origen, ¿se vuelven a ejecutar los pasos previos y posteriores a la compilación?

¿Tiene esto alguna implicación en ts-node y los flujos de trabajo relacionados? Algunas de nuestras aplicaciones de ayuda se ejecutan "directamente" desde TypeScript, como "start": "ts-node ./src/app.ts" o "start:debug": "node -r ts-node/register --inspect-brk ./src/app.ts" .

Se informó otro problema con el modo de compilación en # 25355.

Gracias por todos los excelentes comentarios e investigaciones hasta ahora. Realmente aprecio a todos los que se tomaron el tiempo para probar cosas y patear los neumáticos.

@yortus re https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -400439520

Excelente reseña, gracias de nuevo por proporcionarnos esto. Tus problemas en orden -

  1. Reparado
  2. PR arriba en # 25370
  3. Discutiendo sobre el problema registrado: no es evidente de inmediato cuál es la solución correcta, pero haremos algo
  4. Investigando (abajo)
  5. Registrado # 25376
  6. Técnicamente no relacionado con --build AFAICT. Esta es una nueva afirmación que agregamos recientemente; Nathan está investigando

@rosskevin 🎉 para el PR en el repositorio learn-a ! Voy a fusionar eso en una rama para que podamos comparar y contrastar mejor.

@pgonzal re https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -401577442

Tengo una pregunta general sobre tsc --build: ¿el compilador de TypeScript busca asumir el rol de orquestar la compilación para proyectos en un monorepo?

Gran pregunta; Quiero responder a esta muy claramente: definitivamente no .

Si está contento hoy usando tsc para construir su proyecto, queremos que sea feliz mañana usando tsc -b para construir su proyecto de varias partes. Si está contento hoy usando gulp para construir su proyecto, queremos que sea feliz mañana usando gulp para construir su proyecto de varias partes. Tenemos control sobre el primer escenario, pero necesitamos autores de herramientas y complementos que nos ayuden con el segundo, por lo que incluso tsc -b es solo una envoltura delgada sobre las API expuestas que los autores de herramientas pueden usar para ayudar a que las referencias de proyectos funcionen bien. en sus modelos de construcción.

El contexto más amplio es que hubo un debate interno bastante sólido sobre si tsc -b debería existir, o en su lugar ser una herramienta / punto de entrada separado: construir un orquestador de compilación de propósito general es una tarea enorme y no una de las que estamos registrarse para. Para nuestro propio repositorio usamos tsc con un marco de ejecución de tareas liviano y ahora usamos tsc -b con el mismo ejecutor de tareas, y esperaría que cualquier otra persona que migre también mantenga su cadena de compilación existente en su lugar con solo pequeños retoques.

@borekb re https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -401593804

¿Tiene esto alguna implicación en ts-node y los flujos de trabajo relacionados? Algunas de nuestras aplicaciones de ayuda se ejecutan "directamente" desde TypeScript

Para los scripts de un solo archivo, que implícitamente no pueden tener referencias de proyecto, no hay impacto.

@EisenbergEffect tuvo algunas preguntas en el repositorio learn-a sobre el cambio de nombre entre proyectos y otras características del servicio de idiomas. La gran pregunta abierta aquí es si podremos obtener esta función en un estado utilizable para 3.0 o no. Si es así, entonces el cambio de nombre entre proyectos "simplemente funcionará", sujeto a la advertencia de que obviamente es imposible para nosotros encontrar de manera concluyente todos los proyectos posteriores y actualizarlos; este será un "mejor esfuerzo" basado en algunas heurísticas para buscar otros proyectos.

Si no creemos que el cambio de nombre entre proyectos sea aceptablemente estable + completo para 3.0, es probable que bloqueemos las operaciones de cambio de nombre solo cuando el símbolo renombrado esté en el archivo de salida .d.ts de otro proyecto; permitirle hacer esto sería muy confuso porque el archivo .d.ts se actualizaría en una compilación posterior del proyecto ascendente después de que se modificó el proyecto ascendente, lo que significa que podrían pasar días entre el momento en que realiza un cambio de nombre local y el momento en que se da cuenta de que el código de declaración en realidad no se había actualizado.

Para características como Ir a la definición, estas están funcionando hoy en VS Code y funcionarán listas para usar en futuras versiones de Visual Studio. Todas estas funciones requieren que los archivos .d.ts.map estén habilitados (active declarationMap ). Hay un trabajo por función para iluminar esto, por lo que si ve que algo no funciona como se esperaba, registre un error, ya que es posible que nos hayamos perdido algunos casos.

Problemas abiertos que estoy rastreando en este punto:

  • Cambio de nombre entre proyectos: @ andy-ms está implementando
  • Necesito analizar las configuraciones de los módulos elevados y comprender sus implicaciones, en mí
  • Debe haber una versión del repositorio de muestra learn-a que use yarn , y otra que use pnpm , y otra que use uno de esos en modo elevado

Preguntas abiertas

  • ¿Deberíamos implementar la lógica para leer package.json s para inferir las referencias del proyecto? Registrado # 25376
  • ¿Debería la emisión .d.ts.map estar implícitamente activada para proyectos composite ?

@RyanCavanaugh para agregar a

Problemas abiertos que estoy rastreando en este momento

También mencionamos tener una caché de salida incremental, separada de la ubicación de salida real del proyecto, para manejar cosas como actualizar las declaraciones en segundo plano en el LS (hoy, los cambios no se propagan a través de los límites del proyecto en el editor hasta que se crea), stripInternal , y procesos de compilación mutantes (donde nuestras salidas de compilación están mutadas en su lugar y, por lo tanto, no son adecuadas para las operaciones de LS).

perdón por una pregunta tonta, ya que está marcada en la hoja de ruta, ¿cómo habilito esta función?

@ aleksey-bykov puede usarlo en mecanografiado @ next.

Probé esto en nuestro monorepo impulsado por el espacio de trabajo de hilo y funciona bien.

Una cosa que noté fue que tsc --build --watch reporta errores pero luego no muestra nada para decir que la compilación ahora está arreglada. El modo de vigilancia estándar tsc en 2.9 ha comenzado a dar un recuento de errores y es bueno ver un cero allí para que sepa que la construcción se ha completado.

tengo una carpeta llena de * .d.ts y nada más, ¿qué se supone que debo hacer al respecto?

  • convertirlo en un proyecto y hacer referencia a él (probado, no funcionó)
  • use "incluir" para ello

@timfish que los comentarios coinciden con otros que he escuchado; registrado # 25562

@ aleksey-bykov https://github.com/Microsoft/TypeScript/issues/3469#issuecomment -400439520 debería ayudar a explicar algunos conceptos

@RyanCavanaugh parece que la referencia del proyecto solo funciona para commonjs y la resolución del módulo de nodo, ¿no es así?

en tu ejemplo:

import * as p1 from "@ryancavanaugh/pkg1";
import * as p2 from "@ryancavanaugh/pkg2";

p1.fn();
p2.fn4();
  1. ¿Qué es el módulo @ryancavanaugh ¿Tiene algo que ver con la forma en que TS resuelve los módulos?
  2. ¿Se supone que este ejemplo funciona con AMD (resolución de módulo clásica)?
  3. ¿Se requiere outFile para encontrar las definiciones?
  4. dónde se supone que deben estar los archivos d.ts para que el proyecto de referencia los encuentre (¿puedo seguir usando outDir? ¿TS los encontrará allí?)

tengo 2 proyectos simples essentials y common y las cosas en común no pueden resolver las cosas compiladas en Essentials:

image

@ aleksey-bykov

  1. Es solo un nombre de módulo con ámbito, resuelto bajo el algoritmo de resolución de módulo de nodo habitual
  2. Puede usar referencias de proyecto con cualquier sistema de módulo, incluido el clásico, pero los nombres de ejemplo (módulos con ámbito) no son muy fáciles de usar fuera del nodo
  3. No
  4. TypeScript busca que los archivos .d.ts estén en el lugar donde el proyecto al que se hace referencia los construye

Si tiene un repositorio de muestra o algo, puedo diagnosticar por qué está recibiendo ese error

@RyanCavanaugh por favor hazlo
ejemplo.zip

@RyanCavanaugh , también parece que tsc --build --watch inicialmente no genera ningún archivo hasta que ve una modificación de un archivo fuente.

Hilo demasiado largo (tanto en el tiempo como en el espacio); retomemos la discusión en el número de la suerte 100 * 2 ^ 8: # 25600

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Antony-Jones picture Antony-Jones  ·  3Comentarios

DanielRosenwasser picture DanielRosenwasser  ·  3Comentarios

siddjain picture siddjain  ·  3Comentarios

blendsdk picture blendsdk  ·  3Comentarios

seanzer picture seanzer  ·  3Comentarios