Node: No resuelva los enlaces simbólicos cuando requiera

Creado en 16 oct. 2015  ·  204Comentarios  ·  Fuente: nodejs/node

Actualmente, Node.js resuelve los enlaces simbólicos cuando los requiere y luego usa la ubicación real del paquete/archivo como su __filename y __dirname en lugar del enlace simbólico.

Esto es un problema porque los módulos enlazados no actúan igual que los módulos copiados localmente.

Por ejemplo:

    index.js //require("dep1")
    node_modules
        dep1
            index.js //require("dep2")
        dep2
            index.js //console.log('fun!'):

funciona, pero

    index.js //require("dep1")
    node_modules
        dep1 -> ../../dep1
        dep2
            index.js
dep1
    index.js //require("dep2")

no lo hace, porque dep1 no actúa como si estuviera ubicado en el directorio node_modules del módulo y, por lo tanto, no puede encontrar dep2.

Esto es especialmente un problema cuando uno quiere vincular módulos que tienen dependencias entre pares.

Por lo tanto, sugiero cambiar el comportamiento para que ya no resuelva los enlaces simbólicos.
¿Qué piensas?

discuss module

Comentario más útil

Supongamos que tenemos la siguiente estructura de directorios:

.
├── index.js
└── node_modules
    ├── dep1
    │   ├── index.js
    │   └── node_modules
    │       └── dep2 -> ../../dep2
    └── dep2
        ├── index.js
        └── node_modules
            └── dep1 -> ../../dep1

7 directories, 3 files

Donde dep1 -> dep2, dep2 -> 1 - una dependencia circular muy simple (index.js -> dep1).

sh-3.2$ cat index.js
require('dep1')

sh-3.2$ cat node_modules/dep
dep1/ dep2/

sh-3.2$ cat node_modules/dep1/index.js
require('dep2')
console.log('dep1')

sh-3.2$ cat node_modules/dep2/index.js
require('dep1')
console.log('dep2')

Si ejecuto node index.js , obtengo el siguiente resultado:

sh-3.2$ node index.js
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1

Lo cual definitivamente no se espera.

require.cache :

[ '/Users/alex/repos/ied/sandbox/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js' ]

Entonces, incluso bajo la suposición de que el _algoritmo_ actual es correcto, la implementación real es actualmente incorrecta. Este PR rompió dependencias circulares.

Por lo tanto, creo que tendría absolutamente sentido revertir este PR y pensar en una mejor solución.


Comparado con 5.0.0

dep1
[ '/Users/alex/repos/ied/sandbox/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep2/index.js' ]

Todos 204 comentarios

Esto rompería npm link , ¿no?

si necesita dep1, y es un enlace simbólico, y no tiene dep2 en su camino, entonces algo está mal, ¿no es así? ¿El módulo no debería incluir su propia carpeta node_module?

Dicho esto, las dependencias planas en npm @ 3 ofrecen un caso extremo extraño para esto. Pero vale la pena mencionar que estoy bastante seguro de que están desaprobando las dependencias entre pares.

@Trott ¿por qué lo haría? En teoría, todo lo que funciona con la ruta real también debería funcionar bien con la ruta virtual. El único problema real que veo es que cuando se requiere un módulo varias veces a través de diferentes enlaces simbólicos, npm no lo almacena en caché y se carga varias veces. Pero si eso realmente sucede, creo que algo está mal diseñado.

@ElAlfaNerd
No, porque es una dependencia entre iguales.
No creo que NPM esté depreciando las dependencias de pares en general, pero está cambiando su comportamiento para advertir cuando una dependencia de pares no se cumple. Tal vez incluso eliminen la propiedad peerDependencies, pero eso no evita que las personas dependan de las cosas. Los desarrolladores solo tendrán que administrarlos por su cuenta.

No creo que la necesidad de dependencias entre pares desaparezca en el futuro.

También pedí un nuevo comando npm que solo funcionaría si arreglamos el comportamiento de require como sugerí aquí: https://github.com/npm/npm/issues/10000#issuecomment -148737934

@VanCoding Estaba pensando en ciertos casos extremos, pero no estaba pensando mucho en ello, así que sí, ese comentario en particular puede no ser un problema.

@othiym23 (énfasis agregado):

Creo que tiene razón en que el comportamiento de require() no es útil en este caso, pero también creo que dada la importancia de tener ese comportamiento bloqueado para la estabilidad de todo el ecosistema de Node, va a ser muy difícil Cambia ahora.

Cualquier cambio importante en require() podría resultar en todo tipo de ruptura del ecosistema (potencialmente difícil de predecir). Así que el listón está muy, muy alto.

Lo sé, pero creo que el comportamiento actual tal vez incluso cuente como un error porque
parece que no hay razón para comportarse así..
Am 17.10.2015 7:06 am. schrieb "Rich Trott" [email protected] :

Además, la API de módulos (de la que forma parte require) está bloqueada, lo que
medio:

Solo se aceptarán correcciones relacionadas con la seguridad, el rendimiento o correcciones de errores.
No sugiera cambios de API en esta área; serán rechazados.


Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/nodejs/node/issues/3402#issuecomment-148934364 .

La prueba de fuego es esta: ¿existe una probabilidad distinta de cero de que el cambio propuesto rompa la aplicación existente de alguien? Si la respuesta es 'sí' (y creo que lo es), entonces debe rechazarse.

Bueno, ¿cómo vamos a calcular esa probabilidad? ¿Y qué significa un cero?
significa casualidad? Cero como que ni siquiera el módulo se va a romper o no 1% o más
de los paquetes? Si es esto último, entonces realmente dudo que el 1% de los
los módulos se verían afectados por ese cambio. Si realmente hay módulos que
romper con este cambio, entonces están haciendo uso de indocumentados (en mi
opinión buggy) comportamiento y por lo tanto está bien cuando se rompe.

Mirémoslo así: uno siempre puede obtener el camino real desde el
camino virtual, pero si uno obtiene el camino real, el camino virtual se pierde.

Entonces, incluso si rompemos algunos módulos, ¿no es la decisión correcta arreglar esto? I
realmente creo que también ayudaría a solucionar problemas para npm.

Pero por supuesto es tu decisión.
Am 17.10.2015 7:34 am. schrieb "Ben Noordhuis" < [email protected]

:

La prueba de fuego es esta: ¿existe una probabilidad distinta de cero de que el cambio propuesto sea
va a romper la aplicación existente de alguien? Si la respuesta es 'sí' (y
Creo que lo es), entonces debería ser rechazado.


Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/nodejs/node/issues/3402#issuecomment-148936831 .

@VanCoding , tal vez los enlaces duros ayuden. no lo he probado

@dlongley ¿Creo que no puedes vincular un directorio?

Tengo exactamente el mismo problema que @VanCoding y también estoy un poco confundido por el comportamiento actual...

Esperaría que un paquete vinculado resuelva las dependencias según la ubicación a la que estaba vinculado. Por supuesto, primero debe verificar sus propios node_modules, pero cuando vuelva a "subir en el árbol", esperaría que verifique la estructura de carpetas a la que estaba vinculado, no desde dónde estaba vinculado.

@asbjornenge ,

¿No puedes vincular un directorio, creo?

No, no puede, pero tal vez podría escribir un script para crear un directorio simulado y vincular cualquier archivo js . Creo que eso sería todo lo que se necesitaría para módulos simples. No es una solución perfecta, pero tal vez ayudaría en algunos casos. Claramente, debe haber una mejor manera de vincular las dependencias entre pares durante el desarrollo.

Esperaría que un paquete vinculado resuelva las dependencias según la ubicación a la que estaba vinculado.

Yo también, pero creo que esto se ha discutido antes, y puede haber proyectos que dependan del comportamiento actual. Solo necesitaremos encontrar una forma de especificar que se desea el otro comportamiento.

y puede haber proyectos por ahí que dependen del comportamiento actual

Como dije antes:

  • aunque lo más probable es que existan tales módulos, solo hay unos pocos (estimado muy por debajo del 1% de los módulos)
  • serán fáciles de arreglar, mientras que es imposible evitar el comportamiento actual
  • están haciendo uso de un comportamiento no documentado

Y como parece...

  • los desarrolladores ya esperan que se comporte como sugerí, pero se sorprenderían cuando descubran cómo se comporta realmente

Solo necesitaremos encontrar una forma de especificar que se desea el otro comportamiento.

@dlongley , no me gusta. Prefiero 'mantenerlo simple...' a largo plazo.

@VanCoding presenta un argumento convincente.

Tal vez deberíamos hacer este 'cambio importante' menor y simplemente agregar una capacidad de exclusión voluntaria. La exclusión voluntaria debe eliminarse en una versión futura.

Estoy de acuerdo con el argumento de @VanCoding .

Tal vez deberíamos hacer este 'cambio importante' menor y simplemente agregar una capacidad de exclusión voluntaria. La exclusión voluntaria debe eliminarse en una versión futura.

:+1: Estoy a favor de esto si puede obtener consenso.

Tal vez deberíamos hacer este 'cambio importante' menor y simplemente agregar una capacidad de exclusión voluntaria. La exclusión voluntaria debe eliminarse en una versión futura.

Eso es una adición, entonces semver-minor . Le advierto que somos muy conservadores a la hora de hacer cualquier cosa con el sistema de módulos.

Si alguien quiere armar un PR para esto (entendiendo que, nuevamente, el listón para esto es muy, muy alto y, por lo tanto, incluso si es una gran idea y una excelente implementación, es muy probable que esto no suceda ) , podría ser interesante usar https://github.com/nodejs/citgm (si esa herramienta está lista para uso general?) para ver si algo demasiado grande para fallar en el ecosistema ahoga el cambio.

(Para evitar lo obvio y explicar el comentario en negrita anterior: Sí, es fácil arreglar cualquier módulo que se rompa. Sin embargo, eso no es lo suficientemente bueno. Este es el trato: digamos que esto rompe el módulo asíncrono. Arreglarlo en una nueva versión de asíncrono es teóricamente no hay problema. Pero luego obtener los miles y miles de módulos que dependen de async para actualizar a la versión fija es un gran problema. Y hacer que todos usen esos miles y miles de módulos para actualizar _esos_ módulos a versiones fijas ... Esto es por eso que una API de módulo estable e imperfecta es mucho más preferible que una API de módulo dinámica).

Por supuesto, si hubiera una manera de confirmar que ningún módulo en npm depende del comportamiento actual, eso contribuiría en gran medida (en mi opinión, de todos modos) a hacer aceptable el cambio. Sin embargo, no tengo idea de cómo se haría para demostrar eso. Pero puede usar citgm para al menos confirmar que no romperá ninguno de los pocos más importantes.

@ Fishrock123 , tiendo a pensar que mi sugerencia debería esperar hasta el próximo semver-major ya que sugerí un cambio importante con 'opt-out'.

@Trott , estos son buenos puntos. Lo último que quieren los administradores de sistemas son problemas al actualizar un centro de datos a una nueva versión de Node.js.

También debemos considerar cualquier argumento para el comportamiento actual además de la compatibilidad. ¿Hay casos en los que el comportamiento actual es más intuitivo que el cambio propuesto? Si no es así, creo que el cambio propuesto debería implementarse tarde o temprano.

Sé que los Módulos están en Estabilidad 3-Bloqueados .

Solo se aceptarán correcciones relacionadas con la seguridad, el rendimiento o correcciones de errores.
No sugiera cambios de API en esta área; serán rechazados.

Hay buenas maneras de introducir un cambio radical durante un período de tiempo. Las estadísticas indicarían el mejor curso de acción, incluso si eso significa un trabajo adicional en la parte delantera antes de que se pueda implementar este cambio.

Esto me está causando dolor en este momento. Estoy trabajando en una mejora de una biblioteca que automáticamente carga complementos haciendo require(pluginName) . Pero como he usado npm link para instalar la biblioteca, require falla porque no se puede encontrar pluginName en el árbol.

Creo que este comportamiento es inesperado, especialmente dada la interacción con npm link . Creo que un paquete vinculado debería comportarse de manera idéntica a un paquete instalado en la misma ubicación.

De los documentos de los módulos:

Si no se encuentra allí, se mueve al directorio principal, y así sucesivamente, hasta llegar a la raíz del sistema de archivos.

Supongo que esto es ambiguo, pero diría que el "directorio principal" debería ser relativo a la ruta utilizada para cargar el módulo. Si hago require('/home/me/project/node_modules/foo/index.js') e index.js hace require('something-else') , creo que es incorrecto buscar something-else en relación con cualquier cosa que no sea la ruta completa en el primer requerimiento.

@Trott , podría valer la pena echar un vistazo a https://github.com/brson/taskcluster-crater para ver cómo prueban el ecosistema de óxido contra cambios potencialmente peligrosos.

@fenduru :+1:

Mirando los ejemplos de la documentación, es, en mi opinión, un comportamiento inesperado para un archivo que resulta ser un enlace simbólico para comportarse de manera diferente. Espero que se comporte igual que cualquier otro archivo, que es como se describe en @fenduru .

Además, la documentación da una buena razón para la forma en que se realiza la búsqueda de módulos:

Esto permite que los programas localicen sus dependencias, para que no entren en conflicto.

El hecho de que la búsqueda no funcione de esta manera con módulos con enlaces simbólicos hace que sea muy difícil hacer uso de dependencias localizadas (esta es la queja principal). Como se señaló anteriormente, también provoca un comportamiento diferente (e inesperado) con npm link . Quizás este problema se clasifique mejor como un error de "comportamiento inesperado" en lugar de una solicitud de cambio.

Solo una nota aquí de que una solución al comportamiento inesperado no debería cambiar el comportamiento existente para los identificadores de módulos que son nativos o que comienzan con '/', '../' o './'. En esos últimos tres casos tiene sentido usar realpath ; cambiar eso sería incorrecto y me imagino que causaría estragos.

También encontré que este comportamiento require/symlink es inesperado e indeseable. Va en contra de la práctica común de UNIX.

Por curiosidad, ¿el nodo en Windows también tiene este comportamiento requerido/enlace simbólico? (En un momento, Windows no admitía enlaces simbólicos). Si el comportamiento del nodo en UNIX y Windows difiere en este sentido, se fortalecería el caso contra la resolución de enlaces simbólicos en require() en UNIX para ser consistente.

Como mínimo, se podría introducir un indicador de línea de comando de nodo opcional para deshabilitar la resolución de enlaces simbólicos cuando se requiere().

@kzc ,

Por curiosidad, ¿el nodo en Windows también tiene este comportamiento requerido/enlace simbólico?

Suponiendo que fs.realPathSync elimina las referencias a los enlaces simbólicos en Windows, entonces parece que sí (pero solo lo comprobé rápidamente): https://github.com/nodejs/node/blob/master/lib/module.js#L150

Parece que el problema es que no se hace distinción entre cargar un módulo con un identificador que comienza con '/', '../' o './' y uno que no lo hace; todos reciben el realpath Tratamiento realpath solo se usara en los casos anteriores, entonces los enlaces simbólicos se tratarían como otros archivos (creo) y obtendría el comportamiento esperado y la capacidad de localizar dependencias para módulos con enlaces simbólicos.

Independientemente de si los módulos requeridos tienen rutas relativas o absolutas, aún se pueden convertir en rutas absolutas canonicalizadas _sin_ resolver los enlaces simbólicos. Veo la necesidad de que el caché del módulo use rutas absolutas para que sepa cuándo se trata del mismo módulo al que se hace referencia desde varias rutas relativas diferentes. Pero si alguien introduce explícitamente un enlace simbólico en la ruta de un módulo, entonces diría que la mayoría esperaría que se tratara de manera diferente a la versión resuelta del enlace simbólico de acuerdo con su posición de directorio no resuelta.

@kzc ,

Independientemente de si los módulos requeridos tienen rutas relativas o absolutas, aún se pueden convertir en rutas absolutas canonicalizadas sin resolver los enlaces simbólicos.

Esto es problemático para el caso que se muestra aquí: http://grokbase.com/t/gg/nodejs/12cbaygh7a/require-resolving-symlink

This is required because of the way that executables are typically
linked. On Unix, you have something like:

/usr/local/node_modules/foo/bin/foo.js

which does `require("../lib/foo-internals.js")`.

That script is linked to

/usr/local/bin/foo

If we didn't realpath, then it'd be looking for
/usr/local/lib/foo-internals.js instead of
/usr/local/node_modules/foo/lib/foo-internals.js.

La secuencia de comandos de inicio de nodo es un caso especial que podría resolverse con un enlace simbólico si el archivo tiene un sufijo shebang o no .js.

Tengo el mismo problema que @fenduru.

  1. Lib instala complementos a través npm install , terminan en el directorio correcto (directorio de proyecto de usuario).
  2. También verifiqué require.main.paths[0] , es 100% correcto y contiene el directorio del proyecto del usuario.
  3. No obstante : require y require.resolve ambos tiran.

Esto es inesperado.

Solo sucede cuando se enlaza con un enlace simbólico/npm. En una instalación normal todo funciona como se esperaba.
No quiero npm publish cada cambio. ¿Cómo voy a seguir desarrollando la librería?

Dado que el directorio está realmente incluido en require.main.paths este problema es sin duda un error si me preguntas. Por lo menos vale la pena arreglarlo.

:+1:

+1 esto es un dolor y hace que el enlace npm sea casi inútil para mí (ver https://github.com/npm/npm/issues/5080).

Resolver enlaces simbólicos de esta manera es simplemente incorrecto y el nodo no debería hacerlo.

Para solucionar este problema, parece que el nodo debería poder cargar varias instancias del mismo archivo de módulo (misma ubicación de ruta real).

Esto significa almacenar archivos de módulos con enlaces simbólicos en require.cache usando sus nombres de enlace simbólico en lugar de sus nombres de ruta real. Debería haber una nueva instancia en el caché para cada módulo que se cargue a través de una ruta diferente (enlace simbólico diferente) para garantizar que se utilicen las rutas de búsqueda adecuadas para cualquier llamada require() realizada desde dentro de ese módulo . Una vez hecho esto, la ruta de búsqueda para encontrar otro módulo podría primero recorrer la ruta del enlace simbólico y luego, si no se encuentra ninguna coincidencia, podría recorrer la ruta real. Esto podría ayudar con la compatibilidad con versiones anteriores.

Sin embargo, esto parece ser una desviación significativa de cómo funciona actualmente el caché requerido. No está claro cómo los proyectos existentes se verían afectados por esto. Sin embargo, en realidad solo puede afectar a los proyectos que usan dependencias entre pares, que son los proyectos que necesitan esta solución. ¿Es aceptable un cambio para corregir este comportamiento inesperado? ¿Qué opinas, @jasnell?

¿Hay alguien interesado en este tema que se sienta lo suficientemente cómodo para crear un PR para tratar de resolverlo de una manera sensata?

Para resumir el problema central aquí:

Si desea trabajar en una dependencia en el contexto de un paquete dependiente, agregue un subdirectorio node_modules al paquete dependiente y enlace a su dependencia allí. Cualquier cambio que realice en la dependencia se reflejará en el paquete dependiente.

Si desea trabajar en una de las N dependencias del mismo nivel en el contexto de un paquete dependiente, no tiene ese remedio. No existe un mecanismo simple por el cual vincular a una sola dependencia del mismo nivel en el contexto del paquete dependiente. Esto hace que el desarrollo de sistemas que utilicen dependencias entre pares sea innecesariamente difícil.

Si cambiamos el comportamiento de la ruta de búsqueda del módulo para subir buscando <parent>/node_modules usando primero la ruta del enlace simbólico y luego la ruta real, este problema se eliminaría. Los desarrolladores de proyectos que no usan dependencias de pares pero quieren asegurarse de que se carguen las dependencias adecuadas, pueden continuar usando el comportamiento actual de agregar un subdirectorio node_modules . Así ya se resolvieron los conflictos de dependencia en proyectos existentes.

El caso de uso de dependencia no par que cambiaría sería el siguiente:

Un paquete dependiente requería una dependencia que no fuera del mismo nivel que fuera un enlace simbólico. Esa dependencia no tenía todas sus subdependencias en un subdirectorio local node_modules . Por lo tanto, se recorrieron sus directorios principales realpath buscando la subdependencia en <parent>/node_modules hasta que se encontró. Con este nuevo comportamiento, sus directorios principales de ruta de enlace simbólico se recorrerían primero, antes de recorrer sus directorios principales realpath . Esto podría potencialmente encontrar una subdependencia diferente. El comportamiento original se puede restaurar fácilmente agregando un subdirectorio local node_modules .

La única pregunta es, ¿los paquetes dependían anteriormente de este comportamiento?

Este comportamiento no debería afectar a los paquetes instalados de npm a menos que tengan scripts de instalación personalizados que creen enlaces simbólicos de esta manera (y parece cuestionable por qué se haría esto). Existe la posibilidad de que esto afecte los paquetes instalados globalmente y npm link , pero solo si npm install -g no garantiza que todas las dependencias secundarias no pares requeridas estén instaladas en un node_modules subdirectorio. Si no tiene esta garantía, podría agregarse en futuras versiones para garantizar un comportamiento correcto con las nuevas versiones de node.

Aquí hay una propuesta alternativa para resolver este problema que no debería afectar a ningún paquete a menos que ya estén usando dependencias del mismo nivel y, por lo tanto, es posible que no deba considerarse un cambio potencialmente importante.

El nodo podría ser consciente de las "Dependencias de pares" al verificar este valor en package.json :

Supongamos que tenemos un paquete top que depende de dos paquetes framework y plugin , donde plugin está vinculado a otro directorio que contiene una versión de desarrollo de plugin :

|--top
   |--node_modules
     |--framework
     |--plugin (symlink to /dev/plugin)
|--dev
   |--plugin
      |--package.json ({"peerDependencies": {"framework": "^1.0.0"}})
   |--framework

En este escenario, queremos que top cargue plugin desde el directorio dev y queremos que plugin cargue top/framework no dev/framework .

El paquete plugin incluye una entrada peerDependencies en su package.json que tiene framework como clave. Cuando el script top llama a require('plugin') , el nodo encuentra plugin a través de su mecanismo normal. Sin embargo, al cargar plugin , el nodo ve su archivo package.json y que tiene una entrada peerDependencies . Esto hace que plugin se identifique a través de su ruta absoluta (que puede ser un enlace simbólico) en lugar de su realpath . Desde una perspectiva de implementación, una entrada en require.cache usa la ruta absoluta como clave y una nueva instancia de módulo como valor (a menos que se haya almacenado previamente en caché desde la ruta absoluta).

Ahora, cuando el script plugin llame a require('framework') , dado que framework está en la entrada peerDependencies en package.json , se buscará caminando primero plugin 's module.parent (que será el camino top ) antes de caminar su realpath . Cualquier otro objetivo requerido (que no esté presente en peerDependencies ) se buscará usando el realpath del módulo como de costumbre.

Creo que este enfoque manejaría el caso de uso en cuestión (desarrollar paquetes que usan dependencias de pares y usar npm link con dependencias de pares) sin afectar a ningún otro paquete.

¿Alguien aquí tiene comentarios sobre esta propuesta? ¿La gente preferiría este sobre el anterior, o ninguno? ¿Pensamientos generales?

Realmente no creo que el nodo deba buscar en package.json y hacer cosas especiales con peerDependencies. Debería funcionar como se esperaba para cualquier paquete con enlace simbólico. No hay necesidad de un caso especial peerDependencies si el nodo deja de perder el tiempo con la resolución de enlaces simbólicos.

No veo la necesidad de recorrer el camino real en absoluto y sugeriría que (a excepción de cualquier caso de compatibilidad con versiones anteriores, posiblemente temporalmente) esto debería evitarse si es posible.

Considerar:

A/node_modules/B (symlink to B)
A/node_modules/C
B
  • require('C') de A debe usar A/node_modules/C
  • require('C') de B debe usar B/node_modules/C si está disponible, y volver a A/node_modules/C (esto no sucede en este momento)

¿Por qué querría usted _alguna vez_ comenzar a buscar en el padre de A/B?

Incluso si piensa en vincular subdependencias:

A/node_modules/B/node_modules/C (symlink to C)
C

Buscar en el camino real _todavía_ no tiene ningún sentido; eso significaría buscar en el padre de A/C.

El único escenario posible en el que puedo pensar en el que sería necesario buscar el camino real sería vincular a una dependencia que está a la mitad del árbol de otra persona :

A/node_modules/D (symlink to B/node_modules/D???)
B/node_modules/C
B/node_modules/D

...que no tiene ningún sentido en absoluto. ¿¡¿¡Por qué harías esto!?!?

Por lo tanto, nunca resuelva los enlaces simbólicos, use siempre la ruta virtual y no haga nada especial.

Me interesaría si alguien pudiera hacer un parche para esto (aunque sea imperfecto) para que podamos probarlo. También creo que podríamos conseguir a alguien que ayude a quien esté interesado, si es necesario. :)

Asunto relacionado:

Módulo [rfc]: cambiar orden de carga #4595

Estoy 100% de acuerdo con @SystemParadox , no veo ninguna razón para resolver el enlace simbólico.
Se supone que los enlaces ayudan al desarrollo al simular un entorno de nodo classic y aún así poder realizar cambios fácilmente.
Si cambiar esto afecta a alguien, lo más probable es que sea solo en un entorno local usando trucos locos debido a este comportamiento.
No puede escribir un módulo asumiendo que su padre o dependencias están en una ubicación "real" diferente, ya que no tiene ningún control sobre la estructura del archivo cuando su módulo se instala en otra máquina. Además, lo más probable es que no pueda escribir ningún código que cree enlaces simbólicos, ya que no puede asumir que tiene permiso para hacerlo (la creación de enlaces simbólicos está deshabilitada para los usuarios de Windows de forma predeterminada).
Además, todavía no veo un caso de uso en el que el comportamiento actual sea deseable o incluso se use en cualquier lugar en este momento. Lo único que veo es que las personas se esfuerzan mucho por eludir este mal comportamiento en el nodo para poder trabajar con el enlace simbólico. A veces, tenía que resolver copiar mi módulo al destino final y desarrollarlo directamente allí para poder probarlo.

Creé un problema hace mucho tiempo en nodejs/node-v0.x-archive/issues/18203 y creo que volveré a publicar el problema aquí ya que aquí es donde está la discusión.

He estado trabajando durante un tiempo con link para desarrollar varios módulos o simplemente reutilizar mi versión local.
Sin embargo, cada vez que vinculo un módulo usando peerDependencies, el enlace se vuelve muy doloroso.

Por ejemplo, si tengo una estructura como

[email protected] D:\temp\pkg1
├── [email protected]
└── [email protected] -> D:\temp\pkg2

Y por alguna razón pkg2 intenta requerir lodash y luego aparece el error Error: Cannot find module 'lodash' .
Sin embargo, sin el enlace, funciona perfectamente.
Un problema aún mayor es que si tengo lodash instalado globalmente, funciona con el enlace, pero no usa la versión prevista porque podría tener la versión 2.8.0 instalada globalmente.
Esto es lo peor, porque no obtiene un error, todo se ejecuta y en un momento puede fallar y tendrá que buscar en todas partes para finalmente darse cuenta de que no estaba usando la versión correcta debido a su enlace.

Ahora quería intentar encontrar el origen del problema y tal vez solucionarlo.
Me di cuenta de que cuando requiere pkg2 en mi ejemplo, su ruta será D:\temp\pkg2\index.js en lugar de D:\temp\pkg1\node_modules\pkg2\index.js , lo que dará como resultado que las rutas de resolución sean

paths: Array[3]
0: "D:\temp\pkg2\node_modules"
1: "D:\temp\node_modules"
2: "D:\node_modules"

En vez de

paths: Array[4]
0: "D:\temp\pkg1\node_modules\pkg2\node_modules"
1: "D:\temp\pkg1\node_modules"
2: "D:\temp\node_modules"
3: "D:\node_modules"

Traté de cambiar la función tryFile en module.js de la siguiente manera

// check if the file exists and is not a directory
function tryFile(requestPath) {
  var fs = NativeModule.require('fs');
  var stats = statPath(requestPath);
  if (stats && !stats.isDirectory()) {
    return requestPath; // <= Fix: use the symlink directly
    //return fs.realpathSync(requestPath, Module._realpathCache);
  }
  return false;
}

Y esta fue la extensión del cambio requerido para solucionar el problema.

En lugar de usar fs.realpathSync() para la resolución requerida del módulo, debe normalizarse con path.resolve() que devuelve una ruta canónica absoluta sin resolver los enlaces simbólicos.

Conceptualmente, sería mejor tener algo como este parche contra node-v5.5.0:

--- lib/module.js.orig  2016-01-24 12:25:44.000000000 -0500
+++ lib/module.js   2016-01-24 12:41:44.000000000 -0500
@@ -123,7 +123,7 @@
 }

 function toRealPath(requestPath) {
-  return fs.realpathSync(requestPath, Module._realpathCache);
+  return path.resolve(requestPath);
 }

 // given a path check a the file exists with any of the set extensions
@@ -287,6 +287,7 @@
     debug('Module._load REQUEST %s parent: %s', request, parent.id);
   }

+  if (isMain) request = fs.realpathSync(request);
   var filename = Module._resolveFilename(request, parent);

   var cachedModule = Module._cache[filename];

El único lugar donde es necesario resolver los enlaces simbólicos es si el script de inicio del nodo resulta ser un enlace simbólico; consulte isMain arriba.

Por supuesto, los datos de lstat tendrían que almacenarse en caché a través de algún otro medio, ya que Module._realpathCache ya no se usaría. El parche anterior no abordó ese detalle de implementación.

@VanCoding

El único problema real que veo es que cuando se requiere un módulo varias veces a través de diferentes enlaces simbólicos, npm no lo almacena en caché y se carga varias veces.

Sí, el almacenamiento en caché es un problema real. Actualmente, podría vincular con símbolos todas las dependencias a una tienda global porque comparten la misma memoria caché. Aunque npm no hace eso, pero teóricamente alguien podría crear un administrador de paquetes usando ese método. Consulte: https://nodejs.org/dist/latest-v5.x/docs/api/modules.html#modules_addenda_package_manager_tips

Creé un PR para este problema aquí: https://github.com/nodejs/node/pull/5950

Pasa todo Canary in the Gold Mine en Linux y Windows, las cosas se ven bastante bien en lo que respecta a la estabilidad. Almacenamiento en caché W/R/T, los módulos aún se almacenan en caché, solo de acuerdo con su ruta simbólica canónica. sistema de archivos nos da.

Creo que este problema se puede cerrar ahora.

¡Hecho!

Desafortunadamente, al igual que mi comentario anterior mencionado, puede romper el comportamiento de caché de los administradores de paquetes alternativos. Y probé ied , pnpm , npminstall en Node V6 hoy. ¡Todos esos instaladores de paquetes alternativos se basan en el comportamiento del enlace de símbolo original y ahora todos pierden caché en Node V6! Eso significa que si instala babel6 con estas herramientas, sufrirá el mismo problema de rendimiento que babel6 en npm2. 😭

@jasnell , sugiero revertir este PR antes de encontrar la solución.

cc los autores de ied @alexanderGugel , pnpm @rstacruz , npminstall @fengmk2 y @dead-horse

https://github.com/nodejs/node/pull/5950/files#diff -d1234a869b3d648ebfcdce5a76747d71R119 Está roto todos los instaladores de paquetes de nodos más rápidos. Creo que el módulo api está bloqueado hace mucho tiempo, ¿por qué necesitamos cambiar este comportamiento?

@hax @fengmk2 Bueno, es una nueva versión principal, por lo que está permitido (y se espera) que introduzca cambios importantes. Si confía en el comportamiento anterior, todavía existen las versiones anteriores, incluido LTS.

Además, no olvide que el comportamiento anterior no se documentó de ninguna manera, por lo que los paquetes que ahora están rotos hicieron uso de un comportamiento no documentado, que siempre debe esperar romper.

Creo que otros y yo dejamos muy claro por qué era necesario este cambio.

Lo bueno es que debería haber formas de reparar los paquetes ahora rotos para que funcionen con Node 6.x nuevamente, ya que siempre puede obtener la ruta real de la ruta virtual. No era posible al revés.

Necesita cambiarse porque resolver enlaces simbólicos como este es incorrecto y está roto.

¿Puede alguien explicarme por qué los administradores de paquetes tienen problemas con esto? Simplemente me parece que están usando el mismo comportamiento frágil de resolver enlaces simbólicos cuando no deberían. Si ya lo estuvieran haciendo bien, no creo que tuvieran ningún problema ... independientemente de la forma en que el nodo lo esté haciendo.

Además, no olvide que el comportamiento anterior no se documentó de ninguna manera, por lo que los paquetes que ahora están rotos hicieron uso de un comportamiento no documentado, que siempre debe esperar romper.

De hecho, está documentado.

Dado que Node.js busca la ruta real de los módulos que carga (es decir, resuelve los enlaces simbólicos) y luego busca sus dependencias en las carpetas node_modules como se describe aquí, esta situación es muy sencilla de resolver con la siguiente arquitectura:

Fuente : Anexo: Consejos para el administrador de paquetes

Dependemos en gran medida de los enlaces simbólicos en ied. Así que haz muchos otros proyectos. Este cambio dará como resultado errores difíciles de depurar y, en mi opinión, el beneficio es muy limitado. El comportamiento anterior todavía está bien documentado e incluso se describe en Addenda: Package Manager Tips . Si no es en eso, ¿en qué podemos confiar cuando escribimos un administrador de paquetes?

cc @TheAlphaNerd

Y module API es Stability: 3 - Locked https://nodejs.org/api/modules.html#modules_modules , debe mantenerse muy estable.

image

Si perdimos el caché del módulo, no hay forma de hacer que npm install sea ​​más rápido y eficiente.

@alexanderGugel guau, tienes razón. No sabía que esto existe.

Pero aún así, no está documentado claramente. En realidad, solo indica que resuelve los enlaces simbólicos cuando busca un módulo. Pero no indica que __dirname y __filename en el módulo cargado también tendrán la ruta resuelta. Tampoco establece que los requisitos de dichos módulos se originarán en la ruta resuelta.

Entonces, básicamente, podríamos mantener el comportamiento documentado y aun así descifrar su código;)

@ fengmk2 solo el comportamiento documentado está bloqueado, creo. Todavía se permiten cambios, siempre que sigan las especificaciones.

Pero aún así, no está documentado claramente.

Dado que Node.js busca la ruta real de los módulos que carga (es decir, resuelve los enlaces simbólicos) y luego busca sus dependencias en las carpetas node_modules como se describe aquí , esta situación es muy sencilla de resolver con la siguiente arquitectura:

Yo diría que es extremadamente claro y rompe una API bloqueada.

Este cambio dará como resultado errores difíciles de depurar y, en mi opinión, el beneficio es muy limitado.

Además de estar mal documentado, el comportamiento anterior de los módulos con enlaces simbólicos era contrario a la intuición, iba en contra de la práctica común de los enlaces simbólicos de UNIX e impedía que los desarrolladores probaran fácilmente sus módulos sin copiarlos ni instalarlos.

@kzc Esa es una pregunta diferente y el razonamiento original detrás del cambio. Sin embargo, no justifica romper una API bloqueada que se mencionó en una agenda para administradores de paquetes.

@alexanderGugel Este nuevo comportamiento es consistente con mi interpretación de la especificación del módulo documentado.

¿Qué falla exactamente en estos otros administradores de paquetes? Tal vez podamos ayudar a encontrar una solución.

@VanCoding @kzc

Según tengo entendido, se permiten cambios pero no de esta manera.

Este cambio es demasiado apresurado (solo 2 días antes del lanzamiento de 6.0) y carece de una reflexión y pruebas cuidadosas. Ya señalé que el comportamiento actual no es indocumentado y doy el enlace del documento, y advertí las posibles rupturas en el comentario 29 días antes .

Ahora rompe todos los instaladores de paquetes alternativos. :ira1:

@hax ,

Ahora rompe todos los instaladores de paquetes alternativos.

Cuando dices que los "rompe", ¿quieres decir que no funcionan correctamente o que ahora son menos eficientes?

Como dije aquí:

https://github.com/nodejs/node/pull/5950#issuecomment-215101726

Este fue en realidad un comportamiento _roto_ para otro caso de uso, sin recurso, antes de corregir este error.

Gracias por el aviso sobre esto. De hecho, esto romperá la estructura de pnpm,
¡gorrón!
El 27 de abril de 2016 a las 9:48 p. m., "HE Shi-Jun" [email protected] escribió:

@VanCoding https://github.com/VanCoding @kzc https://github.com/kzc

Según tengo entendido, se permiten cambios pero no de esta manera.

Este cambio es demasiado apresurado (solo 2 días antes del lanzamiento de 6.0) y carece de la
cuidadoso pensamiento y prueba. Ya señalé que el comportamiento actual no es
un indocumentado y dar el enlace del documento, y advertido de la posible
roturas en el comentario 29 dias
https://github.com/nodejs/node/issues/3402#issuecomment-202921652 .

Ahora rompe todos los instaladores de paquetes alternativos [imagen: :rage1:]


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/nodejs/node/issues/3402#issuecomment-215088696

Dado que Node.js busca la ruta real de los módulos que carga (es decir, resuelve los enlaces simbólicos)

El documento del módulo https://nodejs.org/download/release/v6.0.0/docs/api/modules.html#modules_addenda_package_manager_tips v6.0.0 todavía nos dice que resolves symlinks .

CC @nodejs/ctc

@fengmk2 ,

El documento del módulo v6.0.0 todavía nos dice que resolverá los enlaces simbólicos.

Para el script principal, eliminará la referencia, de lo contrario no lo hará.

@dlongley any modules it loads significa cada módulo, no solo el script principal.

y luego busca sus dependencias en las carpetas node_modules...

Todavía busca dependencias de la misma manera. Si crea un enlace simbólico a un módulo que tiene un node_modules en su ruta real, buscará allí las dependencias. Este comportamiento no ha cambiado. Lo que no hará es cambiar el identificador del módulo a la ruta real, rompiendo así el comportamiento de resolución del módulo al moverse _hacia arriba_ en la ruta. Esa documentación no dice qué ruta se usará para identificar el módulo que cargó, solo dice cómo encontrará las dependencias.

Al buscar dependencias, el comportamiento esperado más sensato es buscar en los directorios node_modules locales y luego avanzar por la ruta, desde donde se requiere el módulo, no desde su ubicación sin referencia en el disco. Si usa la ruta sin referencia, no extraerá los módulos que se especifican en su proyecto, extraerá las cosas de otro lugar, lo cual es inesperado _y_ no puede hacer nada para solucionarlo. Si especifica un marco en su proyecto, ese es el que debe cargar su complemento, no algún otro marco en otro lugar del disco.

He estado pensando en esto más en el contexto del almacenamiento en caché de los administradores de paquetes y los consejos del administrador de paquetes. Tal vez me estoy perdiendo algo, pero ¿no debería funcionar como se describe incluso con este cambio?

¿Quizás alguien con problemas con el administrador de paquetes podría explicar con más detalle qué es lo que está mal? @rstacruz , ¿cómo rompe esto pnpm? Revisé su diseño .store y no puedo ver por qué tendría problemas con esta solución (en una nota al margen, ¡pnpm se ve increíble! Definitivamente voy a intentarlo).

Demos un paso atrás un poco. Antes de considerar revertir algo o debatir si esto rompe o no una API bloqueada, debemos trabajar para encontrar una solución que aborde _ambos_ problemas. Tal como lo entiendo hasta ahora (corríjame si me equivoco), el nuevo comportamiento no _detiene_ el funcionamiento de los administradores de paquetes alternativos, ¿solo hace que su funcionamiento sea menos eficiente? ¿Es eso correcto?

Si estoy leyendo la conversación anterior correctamente, este cambio hace que la optimización del rendimiento utilizada por algunos de los administradores de paquetes alternativos sea ineficaz porque hace que el módulo se almacene en caché de acuerdo con el enlace simbólico en lugar de la ruta real, lo que lleva a una situación en la que el ¿Se puede cargar el mismo código de módulo varias veces si los enlaces simbólicos son diferentes? ¿Eso también es correcto?

Suponiendo que esa sea la situación, me parece que el verdadero problema es que estamos combinando de manera inapropiada la identidad del módulo con las rutas de búsqueda. Una solución adecuada debería tratar de desacoplar la ruta de búsqueda de la clave de caché.

@dlongley ... ¿puedo pedirle a usted (y a cualquier otra persona con la que haya estado trabajando en esto) que busque una solución al problema de identidad que también conserve la solución al problema que este problema originalmente buscaba resolver?

Aquí hay otro problema: parecería que no teníamos ninguna prueba que verificara el comportamiento anterior y ninguna cobertura de estrategias alternativas de gestión de paquetes en nuestra prueba de humo del ecosistema. @TheAlphaNerd , ¿puede ver cómo agregar algunos de estos administradores de paquetes alternativos a las pruebas de citgm?

@jasnell ,

Si estoy leyendo la conversación anterior correctamente, este cambio hace que la optimización del rendimiento utilizada por algunos de los administradores de paquetes alternativos sea ineficaz porque hace que el módulo se almacene en caché de acuerdo con el enlace simbólico en lugar de la ruta real, lo que lleva a una situación en la que el ¿Se puede cargar el mismo código de módulo varias veces si los enlaces simbólicos son diferentes? ¿Eso también es correcto?

Si un enlace simbólico es diferente, se asume una instancia de módulo diferente, sí.

Suponiendo que esa sea la situación, me parece que el verdadero problema es que estamos combinando de manera inapropiada la identidad del módulo con las rutas de búsqueda. Una solución adecuada debería tratar de desacoplar la ruta de búsqueda de la clave de caché.

Bueno, para las dependencias de pares, _quieres_ una instancia en memoria diferente de un módulo si la ruta del enlace simbólico es diferente. Si tiene la misma instancia en la memoria, podría comunicarse erróneamente con dos marcos diferentes (con el mismo módulo de memoria) si su aplicación carga dos versiones diferentes del mismo marco. Creo que este es quizás un caso de uso poco probable, pero sería un comportamiento inesperado e irreparable si lo encontrara. Especificar una ruta diferente a un módulo es una forma de establecer un límite claro. Tal vez todavía se podría usar un caché, pero evite instanciar el código.

En cualquier caso, soy +1 por encontrar una solución para ambos problemas.

Echo un vistazo a lib/module.js#L388 , tal vez podamos cambiar la clave Module._cache a realpath y resolver el problema lose cache .

  var filename = Module._resolveFilename(request, parent, isMain);
  var cacheKey = fs.realpathSync(filename);

  var cachedModule = Module._cache[cacheKey];
  if (cachedModule) {
    return cachedModule.exports;
  }

El comportamiento del nodo 6.0.0 es completamente consistente con el comportamiento documentado de require:

https://nodejs.org/api/modules.html#modules_module_caching_caveats

Advertencias de almacenamiento en caché del módulo#

Los módulos se almacenan en caché en función de su nombre de archivo resuelto. Dado que los módulos pueden resolverse en un nombre de archivo diferente según la ubicación del módulo que llama (cargando desde las carpetas node_modules), no es una garantía de que require('foo') siempre devolverá exactamente el mismo objeto, si se resuelve en archivos diferentes .

Además, en sistemas de archivos o sistemas operativos que no distinguen entre mayúsculas y minúsculas, diferentes nombres de archivos resueltos pueden apuntar al mismo archivo, pero la memoria caché aún los tratará como módulos diferentes y volverá a cargar el archivo varias veces. Por ejemplo, require('./foo') y require('./FOO') devuelven dos objetos diferentes, independientemente de si ./foo y ./FOO son o no el mismo archivo.

@kzc ... en este punto, lo que probablemente sería más productivo sería decir que existe cierta ambigüedad en los documentos sobre cuál es o no el comportamiento esperado, y el sistema de módulos actualmente es un poco inconsistente consigo mismo en varios saludos. En lugar de detenernos en eso, tenemos un problema específico que abordar: ¿cómo podemos _preservar_ el comportamiento corregido que implementa este cambio mientras _también_ evitamos que los administradores de paquetes alternativos tengan que sufrir un impacto en el rendimiento? Si es posible, centremos la conversación en eso sin detenernos más en lo que dicen (o no dicen) los documentos.

Creo que si no está utilizando dependencias de pares (o es decir, módulos identificados por una ruta ubicada _up_ ruta en su padre), no debe asumir que obtendrá la misma copia de un módulo en particular en la memoria. La buena noticia es que si desea que los módulos compartan la misma instancia de un módulo en la memoria, puede estar seguro de que lo hará si coloca ese módulo compartido (o un enlace simbólico a él) en la ruta node_modules de sus padres. . No estoy familiarizado con los instaladores de módulos de nodos alternativos. ¿Se pueden modificar para aprovechar esta garantía?

Estoy muy interesado en resolver el problema del caché y puedo dedicar algo de tiempo a encontrar una solución.

@fengmk2 desafortunadamente, el almacenamiento en caché en la ruta real solo presentará más problemas aquí; digamos que desea tener dos versiones diferentes de un marco en su aplicación (que aún comparten el mismo nombre). En v6, la vinculación simbólica a cada directorio de marco logra eso y cargará cada uno por separado, pero si almacenamos en caché en ruta real, entonces uno de los proyectos se cargará y reemplazará al otro de una manera no determinista.

@jasnell
En teoría, hay formas en que los administradores de paquetes podrían solucionar el problema:

En la instalación, modifique la propiedad "principal" de package.json para que apunte a un archivo recién creado que se encuentra en el mismo directorio que package.json y contiene:

module.exports = require("/absolute/path/to/module");

No es bonito, pero creo que podría funcionar.

pero si almacenamos en caché en realpath, entonces uno de los proyectos se cargará en un objeto ya almacenado en caché e inicializado.

@lamara Creo que el mismo camino real debería requerirse solo una vez.

Aquí hay un intento de precisar la semántica que subyace al problema aquí:

Los módulos deben identificarse de forma única.

Los administradores de paquetes alternativos esencialmente indican que la misma versión package.json de un módulo debería indicar la singularidad de un módulo. Solo estoy suponiendo, pero esperaría que estén escritos con esta garantía pero que la implementen explotando el antiguo comportamiento realpath . Supongo que colocan un módulo en el disco en alguna parte, y para esa versión particular del módulo, siempre vinculan a esa instancia en el disco.

Ahora, ese tipo de singularidad no es explícitamente cierto en el nodo; nunca lo ha sido. La razón es que el nodo en realidad no conoce el campo version en package.json . En el nodo, los módulos están _en realidad_ identificados de forma única por ruta. Esta también es una forma sencilla de razonar sobre los módulos y cómo se cargarán: los identificadores más simples con los que tenemos que trabajar son los nombres de archivo. La documentación también indica que cargar un módulo usando una ruta diferente debería darte un módulo diferente.

En cualquier caso, para implementar el tipo de unicidad que deseaban los administradores de paquetes alternativos, se explotó un detalle de implementación no documentado (e inesperado), a saber, el uso de realpath para establecer identificadores de módulos. Para que quede claro, las rutas secundarias buscadas al cargar módulos se documentan con un ejemplo, pero este comportamiento no depende realmente del uso de realpath que es engañoso en la documentación. Este mismo comportamiento de carga persiste cuando no se usa realpath . Para reiterar el punto de @jasnell , claramente la documentación es inconsistente y necesita trabajo.

Ahora, los enlaces simbólicos son archivos por derecho propio. Son una abstracción del sistema de archivos que le permiten crear nuevos identificadores pero, cuando se leen, en realidad extraen datos de otro lugar. De acuerdo con el mecanismo utilizado para identificar módulos, un enlace simbólico identifica un nuevo módulo, incluso si el contenido de ese módulo en el disco es el mismo. Esto asegura que obtenga una copia nueva de ese módulo si aún no se ha cargado. Además, al conservarlos como identificadores, la ruta de búsqueda de módulos que se encuentran en la _ruta principal_ ahora funciona de la manera esperada. Anteriormente, los módulos que había indicado específicamente en su proyecto no se cargaban, sino que se cargaban módulos no deseados de cualquier otro lugar del disco.

¿Es posible que los administradores de paquetes alternativos exploten de manera similar este comportamiento esperado ahora consistente para lograr el tipo de singularidad que desean? ¿Puede encontrar formas de colocar módulos en rutas principales de manera adecuada? npm v3 adopta un enfoque similar para garantizar que las versiones del mismo módulo solo se instalan una vez y se instancian una vez.

También debo mencionar que la forma de unicidad presentada por los administradores de paquetes alternativos (si estoy en lo correcto), en realidad no funcionará en algunos casos para los usuarios de dependencias de pares, si están empleando más de una versión de un marco en su aplicación. Entonces, si se va a admitir ese caso de uso, se necesitaría un cambio.

_Autor de ied aquí (de donde se derivó pnpm AFAIK)_

Los administradores de paquetes alternativos esencialmente indican que la misma versión de package.json de un módulo debe indicar la singularidad de un módulo.

No, el shasum del paquete se utiliza para identificarlo de forma única. Dicho esto, la forma en que el administrador de paquetes identifica esos módulos es irrelevante. Lo que es relevante es el hecho de que se están utilizando para construir los nombres de ruta desde los cuales se vinculan los reales (como en "sin enlaces simbólicos").

Solo estoy suponiendo, pero esperaría que estén escritos con esta garantía pero que la implementen explotando el antiguo comportamiento de la ruta real.

Correcto. Se aprovechó el hecho de que Node sigue el camino real. Yo diría que esa es la forma en que se está documentando:

Dado que Node.js busca la ruta real de los módulos que carga (es decir, resuelve los enlaces simbólicos) y luego busca sus dependencias en las carpetas node_modules como se describe aquí [...]

Supongo que colocan un módulo en el disco en alguna parte, y para esa versión particular del módulo, siempre vinculan a esa instancia en el disco.

Correcto también. Si a depende de b, se creará un enlace simbólico en a/node_modules/b -> /some/where/b .

Ahora, ese tipo de singularidad no es explícitamente cierto en el nodo; nunca lo ha sido.

Yo diría que sí, ya que

  1. la API está bloqueada
  2. el caché del módulo siempre se ha implementado de esa manera
  3. es bastante común requerir singletons suponiendo que se devolverá el mismo paquete

La razón es que el nodo en realidad no conoce el campo de versión en package.json. En el nodo, los módulos en realidad se identifican de forma única por ruta.

Lo cual está perfectamente bien. Node no se preocupa (ni debería) por una versión específica del paquete. En su lugar, debería usar la ruta real (como lo hizo antes). De esa forma, se puede garantizar la exclusividad sin sacrificar el rendimiento debido a los módulos redundantes.

En cualquier caso, para implementar el tipo de unicidad que deseaban los administradores de paquetes alternativos, se explotó un detalle de implementación no documentado (e inesperado), a saber, el uso de realpath para establecer identificadores de módulos.

Para ser claros, las rutas secundarias buscadas al cargar módulos se documentan con un ejemplo, pero este comportamiento en realidad no depende del uso de la ruta real, que es engañosa en la documentación. Este mismo comportamiento de carga persiste cuando no se usa realpath. Para reiterar el punto de @jasnell , claramente la documentación es inconsistente y necesita trabajo.

Acordado. Nuevamente, incluso si no se documentó, la API está bloqueada.

¿Es posible que los administradores de paquetes alternativos exploten de manera similar este comportamiento esperado ahora consistente para lograr el tipo de singularidad que desean? ¿Puede encontrar formas de colocar módulos en rutas principales de manera adecuada? npm v3 adopta un enfoque similar para garantizar que las versiones del mismo módulo solo se instalen una vez y se instancian una vez.

Lamentablemente no. Necesitamos poder hacer referencia explícita a los paquetes y recuperar exactamente la misma instancia del módulo. De lo contrario, la única forma de implementar dependencias circulares es aplanar árboles y básicamente hacer lo que hace npm3.

Hagamos esto un poco más concreto. Supongamos que tenemos lo siguiente...

     /index.js
     /node_modules
                  /foo --> ../../modules/foo
                  /bar (@version=1)
                  /baz
                      /node_modules
                                   /foo --> ../../../../modules/foo
                                   /bar (@version=2)
modules
       /foo
          require('bar')
       /bar (@version=3)

Supongamos que el módulo foo exporta lo siguiente, asumiendo que bar es una dependencia de pares:

const bar = require('bar');
module.exports = bar;

Ahora suponga que nuestras tres versiones diferentes de bar cada una lo hace (en orden de versión)

myApp/node_modules/bar/index.js

module.exports = 1;

myApp/node_modules/baz/node_modules/bar/index.js

module.exports = 2;

módulos/bar/index.js

module.exports = 3;

Y baz hace lo siguiente:

module.exports = require('foo');

¿Cuál es el resultado esperado cuando se ejecuta node myApp/index.js ?

const foo = require('foo');
const baz = require('baz');
console.log(foo);
console.log(baz);

Bajo v5.11.0, el comportamiento es que se arroje un error porque bar no se puede resolver en relación con la ruta real de foo :

bash-3.2$ node -v
v5.11.0
bash-3.2$ node index
module.js:341
    throw err;
    ^

Error: Cannot find module 'bar'
    at Function.Module._resolveFilename (module.js:339:15)
    at Function.Module._load (module.js:290:25)
    at Module.require (module.js:367:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/james/tmp/symlink/modules/foo/index.js:3:13)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Module.require (module.js:367:17)
bash-3.2$ 

Tenga en cuenta que v5.11.0 lanza _even_ con /modules/bar existente en el mismo nivel que /modules/foo .

El comportamiento en master (y v6) con este cambio aplicado es para que foo encuentre la versión de bar que sea adecuada para él:

bash-3.2$ ~/node/main/node/node -v
v7.0.0-pre
bash-3.2$ ~/node/main/node/node index
1
2
bash-3.2$ 

Esto tiene el efecto secundario de que, a pesar de que foo se extrae de la misma ubicación en el disco, mantiene un estado interno diferente (una instancia usa la versión 1 bar , la otra usa la versión bar 2, que, según la forma en que se configuran los enlaces simbólicos, es _exactamente_ lo que uno esperaría que hiciera. Las dos instancias con enlace simbólico de foo se tratan como si no fueran enlaces simbólicos en absoluto y las dependencias se resuelven en relación con su ubicación en las rutas node_module relevantes.

@jasnell ,

Esto tiene el efecto secundario de que, a pesar de que foo se extrae de la misma ubicación en el disco, mantiene un estado interno diferente (una instancia usa la versión 1 de la barra, la otra usa la versión 2 de la barra, que, según la forma en que se configuran los enlaces simbólicos, es exactamente lo que uno esperaría que hiciera.Las dos instancias de foo con enlaces simbólicos se tratan como si no fueran enlaces simbólicos en absoluto y las dependencias de pares se resuelven en relación con su ubicación en las rutas de node_module relevantes.

+1, sí, este es exactamente el comportamiento esperado y lo que hace el nodo 6.x. Pensando desde la perspectiva de la persona que configuró myApp , ese es solo el resultado que me gustaría ver.

@VanCoding

module.exports = require("/absolute/path/to/module");
No es bonito, pero creo que podría funcionar.

Sí, esto podría funcionar y lerna usar este método. Tal vez los administradores de paquetes alternativos también podrían usar este método, no estoy seguro...

Antes de recurrir a trucos "no bonitos" ;-) ... veamos si hay una mejor solución para encontrar.

@alexanderGugel ... Observo que diste mi comentario https://github.com/nodejs/node/issues/3402#issuecomment -215146463 con el pulgar hacia abajo, ¿quieres explicar más tus pensamientos?

@jasnell No estoy de acuerdo con la premisa de que ese es el comportamiento esperado. Actualmente estoy armando un ejemplo también.

Hago un pr https://github.com/nodejs/node/pull/6425 para recuperar require cache .

@jasnell ,

No he pensado mucho en esto, así que solo lanzo la idea...

Una forma potencial de avanzar es cambiar el comportamiento del almacenamiento en caché en función de si un módulo se carga desde un directorio secundario node_modules o desde algún lugar de la ruta principal. Habría que pensar en las repercusiones de esto. Sin embargo, no soy fanático, en general, de nada más complejo que identificar módulos de forma única por ruta, porque se vuelve difícil razonar.

@fengmk2 No creo que su representante comercial pueda resolver el problema por completo... porque con el cambio de comportamiento actual (tratar el enlace del símbolo igual que la copia), significa que es posible obtener una ruta de búsqueda diferente con un módulo diferente, consulte el mismo módulo mediante enlaces de símbolos . Creo que primero tenemos que resolver el problema semántico.

Supongamos que tenemos la siguiente estructura de directorios:

.
├── index.js
└── node_modules
    ├── dep1
    │   ├── index.js
    │   └── node_modules
    │       └── dep2 -> ../../dep2
    └── dep2
        ├── index.js
        └── node_modules
            └── dep1 -> ../../dep1

7 directories, 3 files

Donde dep1 -> dep2, dep2 -> 1 - una dependencia circular muy simple (index.js -> dep1).

sh-3.2$ cat index.js
require('dep1')

sh-3.2$ cat node_modules/dep
dep1/ dep2/

sh-3.2$ cat node_modules/dep1/index.js
require('dep2')
console.log('dep1')

sh-3.2$ cat node_modules/dep2/index.js
require('dep1')
console.log('dep2')

Si ejecuto node index.js , obtengo el siguiente resultado:

sh-3.2$ node index.js
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1
dep2
dep1

Lo cual definitivamente no se espera.

require.cache :

[ '/Users/alex/repos/ied/sandbox/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/node_modules/dep2/node_modules/dep1/index.js' ]

Entonces, incluso bajo la suposición de que el _algoritmo_ actual es correcto, la implementación real es actualmente incorrecta. Este PR rompió dependencias circulares.

Por lo tanto, creo que tendría absolutamente sentido revertir este PR y pensar en una mejor solución.


Comparado con 5.0.0

dep1
[ '/Users/alex/repos/ied/sandbox/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep1/index.js',
  '/Users/alex/repos/ied/sandbox/node_modules/dep2/index.js' ]

Todavía no estamos en la etapa de "hey, revirtamos esto". Sigamos trabajando en esto para ver si podemos resolver _ambos_ problemas. Gran ejemplo aunque @alexanderGugel ... Estoy trabajando en ello ahora para ver si puedo encontrar un camino a seguir.

:+1: Para revertir. La implementación actual es incorrecta y el comportamiento inesperado.

Espero que cualquier decisión de resolución de módulo no se base en un ejemplo de bucle de dependencia circular artificial. El anterior mecanismo de resolución de enlace simbólico no documentado tenía muchos problemas propios. Analicemos ejemplos de la vida real.

@mcollina ¿Le importa dar más detalles sobre el voto negativo? ¿No podrías reproducir el error?

@kzc Las dependencias circulares son muy reales. Y el anterior es literalmente solo el más simple. No creo que sea un placer depurar esto en un directorio node_modules anidado de 5 niveles de profundidad.

El ejemplo documentado para ciclos de módulos funciona como se esperaba:

https://nodejs.org/api/modules.html#modules_cycles

@alexanderGugel ,

No creo que sea un placer depurar esto en un directorio node_modules anidado de 5 niveles de profundidad.

¿Bajo qué circunstancias surgiría tal escenario, aparte de con un administrador de paquetes alternativo que realiza la instalación de esta manera?

@dlongley Al enlazar dependencias (privadas) para el desarrollo local.

Por ejemplo, estoy trabajando en una aplicación que depende de 3 paquetes privados, llamémoslos priv-a , priv-b , priv-c .

Decido mover algunas funciones de priv-a a priv-b y priv-c , pero quiero probar la aplicación final antes de fusionar algo.

Así que podría hacer lo siguiente:

  1. Clonar priv-a , priv-b , priv-c
  2. Clonar la aplicación
  3. Eliminar priv-a/my-company/priv-b , priv-b/my-company/priv-c etc.
  4. Enlace simbólico a las dependencias privadas individuales para que la última versión local se pueda usar dentro de ellas.
  5. Iniciar la aplicación
  6. Me pregunto por qué require.cache es enorme.

Punto tomado, no tiene 5 niveles de profundidad. Lo que intento decir es que esto se vuelve menos trivial cuando se trabaja localmente con paquetes privados.

@alexanderGugel ,

Si esos módulos no están diseñados para usarse ya en un patrón cíclico (por ejemplo, un requisito retrasado), ¿no cambiará su comportamiento al hacer referencia a ellos de forma cíclica (ver: Ciclos de módulos ) si son la misma instancia?

@dlongley No, suponiendo que no anule module.export (pero use export en su lugar), todo funcionaría bien. Dicho esto, esto estaba destinado a proporcionar un "tipo de" ejemplo del mundo real.

@alexanderGugel Estoy de acuerdo con lo que jasnell ha dicho repetidamente, esta discusión no debería ser sobre revertir el parche, sino sobre encontrar la mejor manera de avanzar. Creo que este parche corrige un defecto en el sistema del módulo de nodos. Los administradores de paquetes, los módulos y las estrategias de administración de dependencias del desarrollador que se basaron en el comportamiento defectuoso deberán revisarse. Con suerte, podemos encontrar una manera de hacer que ese proceso sea lo menos doloroso posible. Además, creo que el lanzamiento de una versión es el momento ideal para hacer un cambio de esta magnitud.

@alexanderGugel ... solo quería dibujar su escenario un poco más ... Modifiquémoslo un poco para que una de las dependencias circulares tenga una dependencia de pares adicional. En el siguiente ejemplo, foo y bar son dependencias circulares exactamente como en su ejemplo. Sin embargo, foo tiene una dependencia de pares en baz . bar depende de foo pero también depende de la versión 2 de baz . myApp3 usa foo pero usa baz versión 1.

      /index.js
      /node_modules
                   /baz (<strong i="16">@version</strong> = 1)
                   /foo
                       /node_modules
                                    /bar --> ../../bar
                   /bar
                       /node_modules
                                    /baz (<strong i="17">@version</strong> = 2)
                                    /foo --> ../../foo

myApp3/index.js es:

console.log(require('foo'));

myApp3/node_modules/baz/index.js es

module.exports = 1;

`myApp3/node_modules/foo/index.js' es

module.exports.baz = require('baz');
module.exports.bar = require('bar');

`myApp3/node_modules/bar/index.js' es

module.exports = require('foo');

`myApp3/node_modules/bar/node_modules/baz/index.js' es

module.exports = 2;

Ejecutar myApp3/index.js usando v5.11.0 produce:

bash-3.2$ node -v
v5.11.0
bash-3.2$ node index.js
{ baz: 1, bar: [Circular] }
bash-3.2$

Ejecutando myApp3/index.js usando rendimientos maestros:

bash-3.2$ ~/node/main/node/node -v
v7.0.0-pre
bash-3.2$ ~/node/main/node/node index.js
{ baz: 1, bar: { baz: 2, bar: { baz: 2, bar: [Object] } } }
bash-3.2$ 

Nuevamente, este parecería ser el comportamiento esperado en lo que respecta a la _dependencia entre pares_.

Tiene toda la razón en que la dependencia cíclica es un problema aquí, pero el problema de la dependencia entre pares es igualmente legítimo. Ciertamente no es un problema trivial.

@alexanderGugel ,

  1. Me pregunto por qué require.cache es enorme.

¿Realmente te preguntarías eso, o simplemente no te darías cuenta (o no te importaría) en ese escenario? Si su módulo en realidad no solo está exponiendo una API, esperaría que necesite planificar dependencias circulares de todos modos.

@mattcollier

Los administradores de paquetes, los módulos y las estrategias de administración de dependencias del desarrollador que se basaron en el comportamiento defectuoso deberán revisarse.

La razón por la que creo que esta no es una solicitud razonable es que brinda muy pocos beneficios para el 90 % de los casos de uso, mientras que fundamentalmente "rompe" (en el sentido de que es incompatible con versiones anteriores) una API bloqueada. Lo cual no es el fin del mundo, pero lo que me preocupa es el hecho de que esto se vuelve extremadamente difícil de depurar para el usuario promedio.

Creo que es seguro asumir que, de hecho, hay muchos módulos que dependen del comportamiento actual. Por ejemplo, algunas personas enlazan archivos por una u otra razón, no necesariamente paquetes individuales, sino incluso archivos que tienen algún tipo de estado o que tienen efectos secundarios al ser require d. Por ejemplo, podrían establecer una conexión de base de datos.

Pero siento que en realidad hay tres discusiones diferentes aquí:

  1. Un error actual en module.js que resuelve incorrectamente las dependencias circulares.
  2. Una discusión sobre el comportamiento correcto cuando se requieren paquetes con enlaces simbólicos.
  3. Una solicitud para revertir el comportamiento modificado.
  4. Varias relaciones públicas sobre revertir el compromiso o resolver el problema de alguna otra manera

Creo que probablemente deberíamos concentrarnos en 2.

@alexanderGugel ,

Me pregunto por qué require.cache es enorme.

¿Realmente te preguntarías eso, o simplemente no te darías cuenta (o no te importaría) en ese escenario?

Aparte del aumento marginal del uso de la memoria, supongo que probablemente no lo notarías en absoluto. Lo que me preocupa es el hecho de que de repente tienes tres "versiones" diferentes (como en instancias de módulos) de la misma dependencia, lo que hace que sea significativamente más difícil de depurar. Especialmente cuando hay un estado que se almacena de una forma u otra en ellos.

Por ejemplo, el escenario típico podría ser un archivo (estoy hablando específicamente de archivos aquí, no de dependencias (externas)) que establece una conexión con la base de datos cuando se requiere.

Por ejemplo, algo como esto:

const redis = require('node-redis')
const client = redis.createClient()
module.exports = client

@alexanderGugel ,

Por ejemplo, el escenario típico podría ser un archivo (estoy hablando específicamente de archivos aquí, no de dependencias (externas)) que establece una conexión con la base de datos cuando se requiere.

Si está compartiendo de esa manera, entonces creo que debería usar una dependencia de pares.

Tampoco puede garantizar que algún otro módulo que cargue y que no controle no ingrese y cargue esa misma dependencia y haga algo para cambiar su configuración. Si desea conectarse a alguna infraestructura/marco compartido, debe usar dependencias de pares, para eso están.

@dlongley ¿Qué pasa entonces con los paquetes que tienen efectos secundarios?

Por ejemplo, paquetes que decoran String.prototype para admitir colores, o anulan console.log ; no digo que sea una gran idea, pero ese tipo de casos son problemáticos aquí.

@alexanderGugel ,

¿Está diciendo que son problemáticos porque pueden repetir su código más de una vez, si la aplicación que requiere tiene algunos enlaces simbólicos configurados de alguna manera no óptima? ¿Cuáles son los problemas específicos?

@alexanderGugel ,

Tenga en cuenta que un paquete que tiene efectos secundarios ciertamente debe verificar si esos efectos secundarios ya se han aplicado. Similar a un polirelleno. ¿Qué sucede si ya se cargó una versión diferente del paquete (en el disco)? Surgiría el mismo problema.

@alexanderGugel ... Definitivamente estoy de acuerdo en que la dependencia circular es problemática aquí. Simplemente trabajando en varios escenarios para sacar esto más adelante. Tomemos otra versión modificada del caso de dependencia entre pares que describo en mi último comentario (https://github.com/nodejs/node/issues/3402#issuecomment-215177747)

Hagamos baz y _opcional_ dependencia de pares de foo . bar depende de foo y _requiere_ baz . myApp4 , depende de foo pero no requiere baz . Tenemos una estructura como:

      /index.js
      /node_modules
                   /foo
                       /node_modules
                                    /bar --> ../../bar
                   /bar
                       /node_modules
                                    /baz
                                    /foo --> ../../foo    

myApp4/node_modules/bar/index.js es (para simular el requisito de que baz esté presente)

module.exports = require('foo');
require('assert')(module.exports.baz);

myApp4/node_modules/foo/index.js es (para simular que baz es opcional para foo)

try {
  module.exports.baz = require('baz');
} catch (err) {

}

module.exports.bar = require('bar');

Teniendo en cuenta que bar se escribió con la suposición específica de que baz estaría presente, al ejecutar este ejemplo en v5.11.0 se obtiene:

bash-3.2$ node -v
v5.11.0
bash-3.2$ node index

assert.js:90
  throw new assert.AssertionError({
  ^
AssertionError: undefined == true
    at Object.<anonymous> (/Users/james/tmp/symlink/myApp4/node_modules/bar/index.js:3:18)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Module.require (module.js:367:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/james/tmp/symlink/myApp4/node_modules/foo/index.js:8:22)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
bash-3.2$ 

Ejecutar este ejemplo en rendimientos maestros:

bash-3.2$ ~/node/main/node/node -v
v7.0.0-pre
bash-3.2$ ~/node/main/node/node index
{ bar: { baz: 2, bar: { baz: 2, bar: [Object] } } }
bash-3.2$ 

Que, de nuevo, es lo que (creo) uno esperaría.

Lo siento, @jasnell , solo necesito pedir que se revierta este PR lo antes posible.

¡Porque acabo de descubrir que no solo afecta el rendimiento debido al caché, sino que también ROMPE totalmente los tres administradores de paquetes alternativos!

Simplemente intente instalar babel-cli en ied, pnpm, npminstall y ejecute babel-node xxx.js

Resultado:

pnpm y npminstall simplemente se bloquean hasta el límite de memoria de 1,4 GB. (Igual que el caso https://github.com/nodejs/node/issues/3402#issuecomment-215166795)

error de informe ied:

> @ test /Users/hax/repo/contributing/test-module-cache/test/node6-ied
> babel-node .

module.js:440
    throw err;
    ^

Error: Cannot find module 'core-js/library/fn/get-iterator'
    at Function.Module._resolveFilename (module.js:438:15)
    at Function.Module._load (module.js:386:25)
    at Module.require (module.js:466:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/hax/repo/contributing/test-module-cache/test/node6-ied/node_modules/5f08788603a474f19afc575a8394071afd5fbe0c/node_modules/babel-core/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-types/node_modules/babel-traverse/node_modules/babel-typenpm ERR! Test failed.  See above for more details.

(Observe la última línea larga larga de la información de error...)

@alexanderGugel ¿de qué comentario estás hablando?

@mcollina .. Yo _creo_ que fue un error de edición... Creo que el comentario estaba destinado a @mattcollier

@hax Node 6.x aún está actualizado, ya no hay nadie que dependa de esa versión específica. Así que tenemos tiempo para hablar sobre esto y los administradores de paquetes tienen tiempo para encontrar formas de respaldarlo.

También es importante tener en cuenta que revertir el compromiso requiere un PR que requeriría un tiempo de revisión antes de que pudiera aterrizar, que luego permanecería un rato mientras se preparaba una nueva versión. Es un proceso de al menos unos días y lo más pronto que publicaremos una nueva versión es probablemente el próximo lunes o martes. Sí, sabemos que a algunas personas les gustaría revertir la confirmación, pero comprendan que simplemente revertir la confirmación hoy no resuelve los problemas. Podemos darnos el lujo de tomarnos un poco de tiempo para revisar el problema más a fondo y ver si hay un mejor camino a seguir. Revertir es siempre un curso posible _si_ no podemos encontrar una solución razonable a _ambos_ problemas.

@mcollina
@jasnell Sí, lo siento.

La resolución del módulo Node 6.0.0 resuelve una serie de problemas históricos y hace que la resolución del módulo sea mucho más lógica y predecible. No asuma que el nodo es necesariamente la base del código que debe modificarse debido a la dependencia del código de usuario en un comportamiento mal especificado. Tome el caso de truncamiento de salida process.exit / stdout - evidentemente se decidió que los módulos de usuario deben cambiar.

Chicos... ¿no podemos estar de acuerdo en que el nuevo comportamiento es más natural y esperado por la mayoría de los desarrolladores en este momento? Lo siento, @alexanderGugel , sé que no está de acuerdo aquí, pero tal vez esté siendo deshonesto consigo mismo porque está cegado por su deseo de volver al comportamiento anterior. No es ofensivo, realmente respeto tu opinión, pero veo la posibilidad de que sea así.

Ha mencionado muchos casos de uso que no son realmente un problema con el nuevo comportamiento, como ya pudo señalar @jasnell . También creo que su caso con el círculo de dependencia no es un problema. No hay forma de que alguna vez encuentre tal problema usando un administrador de paquetes que funcione correctamente. Y si lo hace a mano, no lo hará para tantos paquetes, que será difícil de depurar.

Quiero decir, si discutimos así, también podríamos afirmar que deberíamos prohibir los bucles y las funciones recursivas de JavaScript porque permite bucles sin fin. Pero si lo haces bien, esto no sucederá.

¿Cómo tratan otros idiomas los enlaces simbólicos?
Pensé que podría ser interesante ver cómo otros idiomas tratan los enlaces simbólicos y, por lo tanto, investigué un poco:

C/C++: pragma una vez carga el archivo varias veces cuando se incluye a través de diferentes enlaces simbólicos
Python: la importación carga un módulo varias veces cuando se importa a través de diferentes enlaces simbólicos
PHP: include carga un archivo varias veces cuando se incluye a través de diferentes enlaces simbólicos

Para mí, esto deja aún más claro que los desarrolladores esperan y quieren esto de esa manera. Lo sé, ya lo he dicho varias veces, pero como es importante, lo digo de nuevo: siempre puedes obtener el camino real del camino virtual.

Además, he aprendido en programación que siempre es mejor diseñar el código para admitir tantos casos de uso como sea posible, de modo que se puedan construir cosas más específicas sobre él. Creo que este es el caso del nuevo comportamiento.

Dicho esto, recomiendo encarecidamente centrarse en buscar formas de arreglar los otros administradores de paquetes ahora. Ya he mostrado una posible forma de hacerlo. Tal vez hay mejores maneras. ¡Vamos a discutir!

Si bien el esfuerzo que se dedicó a esta discusión es excelente, usamos mucho los enlaces simbólicos para todo tipo de cosas. Intenté ejecutar nuestra aplicación con Node 6.0.0, pero falló por los motivos mencionados anteriormente (paquetes privados, etc.). Estoy muy contento de haber encontrado este problema, porque ese tipo de cambios son realmente difíciles de rastrear.

Si bien ciertamente es más puro, el comportamiento actual está documentado y lo hemos desarrollado esperando que esté allí. ¿Hay alguna posibilidad de que se pueda conservar?

Quiero señalar el siguiente comentario que hizo @bnoordhuis al comienzo de esta conversación:

Quiero decir, si discutimos así, también podríamos afirmar que deberíamos prohibir los bucles y las funciones recursivas de JavaScript porque permite bucles sin fin. Pero si lo haces bien, esto no sucederá.

Reducción al absurdo. Dicho esto, parece que esto de hecho introduce al menos un error en el mecanismo de solicitud actual.

Lo siento, @alexanderGugel , sé que no está de acuerdo aquí, pero tal vez esté siendo deshonesto consigo mismo porque está cegado por su deseo de volver al comportamiento anterior. No es ofensivo, realmente respeto tu opinión, pero veo la posibilidad de que sea así.

Estoy bastante seguro de que no lo soy. Resolver las rutas de destino "reales" es la única forma en que puede garantizar la unicidad de los paquetes requeridos sin hacer nada de la magia del "árbol aplanado" de npm3 (ver pnpm ). Dicho esto, estoy más que feliz de estar convencido de lo contrario.

La prueba de fuego es esta: ¿existe una probabilidad distinta de cero de que el cambio propuesto rompa la aplicación existente de alguien? Si la respuesta es 'sí' (y creo que lo es), entonces debe rechazarse.

Creo que después de esta discusión es seguro asumir que esto _de hecho_ rompe el código de la aplicación.

Dicho esto, recomiendo encarecidamente centrarse en buscar formas de arreglar los otros administradores de paquetes ahora. Ya he mostrado una posible forma de hacerlo. Tal vez hay mejores maneras. ¡Vamos a discutir!

Me disculpo si me perdí eso, pero ¿qué camino propusieron para esos administradores de paquetes?

@alexanderGugel No hay problema. Eso sucede rápido en una gran discusión como esta. Aquí está el comentario

Resolver las rutas de destino "reales" es la única forma en que puede garantizar la unicidad de los paquetes requeridos sin hacer nada de la magia del "árbol aplanado" de npm3 (ver pnpm).

No necesitamos identificar archivos de forma única por su ruta absoluta. Ya tenemos un sistema para identificarlos de manera única: el nombre del paquete + la ruta relativa del archivo en ese paquete. Eso es todo lo que se necesita. Ni siquiera tienes que usar la magia del árbol aplanado, podrías hacerlo de la manera que propuse. Y hay potencialmente otras formas.

Reducción al absurdo. Dicho esto, parece que esto de hecho introduce al menos un error en el mecanismo de solicitud actual.

Eso no es un error, es solo la forma en que lo has programado. Es lo mismo que llamar a while(true) console.log('hello world') un error. Por cierto, también podrías hacer lo mismo con PHP. ¿Es un error? No lo creo.

La prueba de fuego es esta: ¿existe una probabilidad distinta de cero de que el cambio propuesto rompa la aplicación existente de alguien? Si la respuesta es 'sí' (y creo que lo es), entonces debe rechazarse.

Creo que después de esta discusión, es seguro asumir que esto de hecho rompe el código de la aplicación.

Buen punto. ¿ @bnoordhuis aprobó fusionar este cambio?

@alexanderGugel No hay problema. Eso sucede rápido en una gran discusión como esta. Aquí está el comentario

Esto no funciona, ya que el directorio en el que se descargó el paquete debe ser compartido por varias dependencias.

Esto no funciona, ya que el directorio en el que se descargó el paquete debe ser compartido por varias dependencias.

¿Puedes quizás explicar más esto?

Entonces, por ejemplo, ya no puede requerir archivos específicos de un paquete, por ejemplo, require('my-package/some-file.js') no funcionará. El ejemplo más popular podría ser rxjs .

@alexanderGugel ,

La prueba de fuego es esta: ¿existe una probabilidad distinta de cero de que el cambio propuesto rompa la aplicación existente de alguien? Si la respuesta es 'sí' (y creo que lo es), entonces debe rechazarse.

Creo que después de esta discusión, es seguro asumir que esto de hecho rompe el código de la aplicación.

Esa es la prueba de fuego para determinar si el cambio debe ser parte de un parche de semver como corrección de errores. En cambio, el cambio se convirtió en un bache importante, lo cual es apropiado.

@alexanderGugel : ¿puede pensar en una forma de modificar administradores de paquetes alternativos para que funcionen con el nodo 6.x?

Todavía no estoy listo para llamar a ninguno de los lados de esta discusión, ya que creo firmemente que hay un camino a seguir que aborda ambas preocupaciones. Como dije, podemos permitirnos un poco de tiempo para explorar más el tema.

Alargando aún más el ejercicio de pensamiento (tengan paciencia conmigo...). Supongamos que en lugar de usar _ya sea_ la ruta real _o_ el enlace simbólico como clave de caché, asumimos alguna función de una tupla de ambos... llamémosla f(symlink,realpath) . Recorrámoslo.

Suponiendo la siguiente estructura utilizando un camino circular:

      /index.js
      /node_modules
                   /baz (@version=1)
                   /foo
                       /node_modules
                                    /bar --> ../../bar
                   /bar
                       /node_modules
                                    /baz (@version=2)
                                    /foo --> ../../foo

(La tupla es f(symlink , realpath) )

Instancia 1 de foo =

f(myApp4/node_modules/foo, myApp4/node_modules/foo)

En este caso, la Instancia 1 de foo obtendrá baz (@version=1)

Específicamente, al resolver foo en relación con myApp , se determina que actualmente no hay ninguna instancia de foo en relación con myApp , por lo que una instancia de foo Se crea

Instancia 1 de bar =

(myApp4/node_modules/bar, myApp4/node_modules/bar)

Específicamente, al resolver bar en relación con myApp , se determina que actualmente no hay ninguna instancia bar en relación con myApp , por lo que una instancia de bar Se crea

Instancia 1 de bar , Instancia 2 de foo =

(myApp4/node_modules/bar/node_modules/foo, myApp4/node_modules/foo)

La instancia 2 de foo vería baz (@version=2)

Específicamente, al resolver foo relativo a bar relativo a myApp , se determina que existe una instancia existente de bar relativa a myApp por lo que se utiliza, sin embargo, no existe una instancia de foo relativa a bar relativa a myApp , por lo que se crea una instancia de foo .

Instancia 1 de foo , Instancia 2 de bar =

(myApp4/node_modules/foo/node_modules/bar, myApp4/node_modules/bar)

Específicamente, al resolver bar en relación con foo en relación con myApp , se determina que existe una instancia existente de foo en relación con myApp por lo que se utiliza, sin embargo, no existe una instancia de bar relativa a foo relativa a myApp , por lo que se crea una instancia de bar .

Instancia 1 de foo , Instancia 2 de bar , Instancia 1 de foo =

(myApp4/node_modules/foo/node_modules/bar/node_modules/foo, myApp4/node_modules/foo)

Aquí, al resolver foo relativo a bar relativo a foo relativo a myApp , se determina que existe una instancia existente de foo relativo a myApp por lo que se utiliza. Se determina que existe una instancia existente de bar relativa a foo relativa a myApp , por lo que también se utiliza. Luego se determina que una instancia existente de foo en relación con bar en relación con foo existe como antepasado de bar , por lo que se utiliza. No se ha creado una nueva instancia de foo .

Instancia 1 de bar , Instancia 2 de foo , Instancia 1 de bar =

(myApp4/node_modules/bar/node_modules/foo/node_modules/bar, myApp4/node_modules/bar)

Aquí, al resolver bar relativo a foo relativo a bar relativo a myApp , se determina que existe una instancia existente de bar relativo a myApp por lo que se utiliza. Se determina que existe una instancia existente de foo relativa a bar relativa a myApp , por lo que también se utiliza. Luego se determina que una instancia existente de bar en relación con foo en relación con bar existe como antepasado de foo , por lo que se utiliza. _No_ se crea una nueva instancia de bar .

Esto puede continuar... tenga en cuenta que al verificar el antepasado, podemos determinar si se requiere una nueva instancia o no.

Instancia 1 de bar , Instancia 2 de foo , Instancia 1 de bar , Instancia 2 de foo

(myApp4/node_modules/bar/node_modules/foo/node_modules/bar/node_modules/foo, myApp4/node_modules/foo)

Instancia 1 de foo , Instancia 2 de bar , Instancia 1 de foo , Instancia 2 de bar

(myApp4/node_modules/foo/node_modules/bar/node_modules/foo/node_modules/bar, myApp4/node_modules/bar)

Al pasar por esto, me parece obvio que existe una oportunidad obvia de cambiar la forma en que se calcula la clave de caché para garantizar que se trate la ruta circular y que las dependencias entre pares se manejen de manera adecuada.

Hagámoslo un poco más extremo... y agreguemos un módulo zzz en algún lugar de la mezcla:

      /index.js
      /node_modules
                   /baz (@version=1)
                   /foo
                       /node_modules
                                    /bar --> ../../bar
                   /bar
                       /node_modules
                                    /baz (@version=2)
                                    /foo --> ../../foo
                                    /zzz
                                        /node_modules
                                                     /baz (@version=3)
                                                     /foo --> ../../../../foo
(myApp/node_modules/bar/node_modules/zzz/node_modules/foo, myApp/node_modules/foo)

Al resolver foo relativo a zzz relativo a bar relativo a myApp , no hay ninguna instancia de foo en la ascendencia directa. Hay una instancia de foo relativa a bar relativa a myApp , pero no hay ninguna instancia de foo relativa a zzz relativa a bar relativo a myApp , por lo que se crea una nueva instancia de foo .

Siguiendo este enfoque, necesitaríamos dividir el caché en dos. Una memoria caché que almacena el código real extraído de la ubicación de la ruta real y una memoria caché secundaria que contiene las instancias reales de los objetos generados por ese código. Esto es para que no tengamos que buscar ese código nuevamente, ya tenemos una copia en la memoria que ejecutamos nuevamente cuando se requiere una nueva instancia del módulo.

La versión tl/dr es: para cada módulo, lo ubicamos en el disco y almacenamos en caché el código real, luego determinamos si se requiere o no una nueva instancia de ese módulo en función de si existe o no una instancia existente en el requerimiento directo. ascendencia.

Ahora, _obviamente_ este tipo de enfoque tendría un impacto en el rendimiento, por lo que tendríamos que probarlo para verificar que no solo cumpliera con las expectativas, sino que realmente funcionara lo suficientemente decente como para usarlo... y estoy bastante seguro de que Me he perdido algunos detalles matizados aquí en alguna parte.

(Realmente espero que al menos algo de eso tenga sentido ;-)...)

(escrito antes de @jasnell )

No, creo que es imposible apoyarlo completamente. No estoy demasiado preocupado por las estrategias específicas de pnpm, ied o npm install, pero lo que me asusta es que esto básicamente significa que no hay otra forma que no sea nombres de ruta súper largos que se rompen en Windows o un gráfico de dependencia plano que tarda una eternidad en calcularse para admitir un mecanismo de instalación sensato que sirve versiones de paquetes idénticas a diferentes dependencias.

El antiguo comportamiento abrió la puerta a alternativas que se basaban en enlaces simbólicos. El nuevo algoritmo es limitante en ese sentido. Dicho esto, entiendo completamente y aprecio el razonamiento detrás de este cambio, pero estoy decepcionado de que se haya tomado la decisión de romper los paquetes existentes en lugar de encontrar una solución que agregue la funcionalidad deseada de manera compatible con versiones anteriores.

(¡¡GUAU! ¡Recibí un pulgar hacia arriba de @alexanderGugel ! ;-) ¡Quizás estas reacciones de github no sean tan malas después de todo!)

No estoy de acuerdo "el nuevo comportamiento es más natural y esperado por la mayoría de los desarrolladores".

Haré todo lo posible para explicar mi opinión. (por favor, perdonen mi pobre inglés)

Los enlaces de símbolos NO son iguales a copiar.

Desde el principio, los enlaces de símbolos naturalmente tienen dos aspectos diferentes, siga el enlace O no siga el enlace. Por ejemplo, ¿cuál es el directorio principal de un enlace de símbolo? Depende de si sigues o no sigues. Si no sigue, simplemente trátelo como una copia. Puedes pensar que es más natural. Pero hay algunos casos IMPORTANTES que debes seguir. P.ej. ejecutando el guión. Es por eso que hay un caso especial incluso en este nuevo comportamiento: script principal.

Podría argumentar que la mayoría de los desarrolladores están familiarizados con los enlaces de símbolos del script principal, entonces, ¿por qué deberían cambiar de opinión en otro lugar? Tan inconsistente es doloroso.

Pero me gustaría aclarar que nunca estuve en contra de este problema, y ​​también me encontré con el enlace npm y el problema de peerDeps (es por eso que veo este problema).

El problema aquí es que no debemos enviar dichos cambios sin pensarlo y probarlo cuidadosamente. En mi memoria, el núcleo del nodo es conservador. Por ejemplo, costó varios años discutir si agregar una API basada en promesas, aunque agregar una API no romperá ningún código existente. Por otro lado, este caso tiene demasiada prisa y rompe todos los administradores de paquetes alternativos, lo cual es una parte valiosa del ecosistema de Node. No puedo entender que esas acciones se basen en reglas coherentes.

Es dañino e injusto bloquear a los usuarios de administradores de paquetes alternativos para actualizar a 6.0. La mayoría de los usuarios del paquete alt son usuarios empresariales y lo usan en CI env para mejorar el rendimiento. Dan muchos comentarios útiles. Por ejemplo, según mi conocimiento, Alibaba, que es uno de los jugadores pesados ​​​​de nodos más grandes, usa tnpm que se basa en npminstall. Entonces, la situación actual hace que los desarrolladores de nodos de Alibaba no puedan probar 6.0 y dar sus comentarios.

Es por eso que sugiero que este problema se resuelva lo antes posible.

Por supuesto, espero que podamos encontrar una solución que satisfaga ambos requisitos. Pero según mi experiencia en resoluciones de módulos, sospecho que podemos lograr este objetivo muy rápidamente. Odio revertir también, pero a veces es la mejor opción.

Gracias por escuchar mi voz.

@hax ... definitivamente estamos trabajando para resolver el problema lo más rápido posible, pero no es algo que pueda solucionarse _hoy_ ya que probablemente tomará varios días (hasta una semana) antes de que se pueda realizar una nueva versión. En este momento estamos discutiendo opciones para corregir y revertir el cambio más reciente es _definitivamente_ una de las opciones. Lo que estoy tratando de hacer antes de que decidamos es tomarnos un poco de tiempo para averiguar cuáles son las opciones adicionales.

Ya es hora de que lleve a los niños a la escuela ahora, así que voy a dejarlos un rato. Volveremos más tarde para continuar la discusión/exploración de esto.

@jasnell El problema con su enfoque es que aún obtiene diferentes instancias del módulo, cuando usa diferentes enlaces simbólicos, ¿verdad? Si entiendo correctamente el problema de otros administradores de paquetes, quieren almacenar todos los paquetes en una sola carpeta y vincularlos, de modo que la jerarquía sea completamente plana.

@alexanderGugel Gracias, no pensé en eso. Pero todavía creo que hay formas de hacerlo, pero pueden ser más complejas. Por ejemplo, los administradores de paquetes podrían agregar lo siguiente al comienzo de cada archivo .js:
var __filenameReal = require("fs").realpathSync(__filename); if(__filenameReal != __filename) return require(__filenameReal);

Pero eso también sería solo resolver la punta del iceberg. Hay muchos casos extremos, como archivos con shebangs, archivos con otras terminaciones como coffescript o archivos que nunca se requieren. El administrador de paquetes tendría que saber qué archivos se requieren directamente de otros paquetes, lo que necesitaría un análisis estático, como lo hace, por ejemplo, browserify, lo que podría ser incluso peor en cuanto a rendimiento.

Pero, como dije en la segunda parte de este comentario, también podríamos concluir que nuestros problemas con las dependencias entre pares se originan en NPM y no en require y que tal vez NPM deba arreglar esto, para que podamos volver al comportamiento anterior. .

Pero, de nuevo, se siente mal usar la ruta absoluta para identificar un módulo de manera única. Tal vez, también podríamos hacerlo configurable a través de una opción de línea de comandos. Esa podría no ser la peor de todas las opciones que tenemos.

Estoy usando pnpm y :heart: tanto como la siguiente persona.

Sigo pensando que este nuevo enfoque de resolución de dependencias basado en la ruta del enlace simbólico es el comportamiento preferido. Cualquier otra cosa es súper confusa (me costó mucho depurar esto la primera vez que lo encontré).

También creo que requerir de dos rutas diferentes -> dos instancias es el comportamiento preferido. Hacer que el comportamiento cambie dependiendo de si es un archivo con enlace simbólico o no es mucho más confuso.

Ambos comportamientos antiguos requieren un conocimiento implícito del funcionamiento interno. El nuevo enfoque proporciona una mejor abstracción, menos conocimiento implícito y menos sobrecarga cognitiva.

Quédatelo :+1:

No exactamente. Con el enfoque sugiero que hay dos cachés. Uno que
almacena en caché el código basado en ruta real, y un segundo que almacena en caché instancias
relativo al contexto ancestral. Un módulo que requiera 'foo' obtendría un
instancia existente o nueva de foo en función de si una instancia ya
existe en su ascendencia. Si los enlaces simbólicos apuntan a la misma ubicación, el
el código vendría de esa misma ubicación.
El 27 de abril de 2016 a las 15:03, "Patrik Stutz" [email protected] escribió:

@jasnell https://github.com/jasnell El problema con su enfoque es
que todavía obtiene diferentes instancias del módulo, cuando usa diferentes
enlaces simbólicos, ¿verdad? Si entiendo el problema de otros gestores de paquetes.
correctamente, quieren almacenar todos los paquetes en una sola carpeta, y
enlace simbólico, de modo que la jerarquía sea completamente plana.

@alexanderGugel https://github.com/alexanderGugel Gracias, no lo hice
Piénsalo. Pero todavía creo que hay formas de hacerlo, pero pueden
ser más complejo. Por ejemplo, los administradores de paquetes podrían agregar lo siguiente a
el comienzo de cada archivo .js:
var __filenameReal = require("fs").realpathSync(__filename);
if(__filenameReal != __filename) return require(__filenameReal);

Pero eso también sería solo resolver la punta del iceberg. Hay un
muchos casos extremos, como archivos con shebangs, archivos con otras terminaciones como
coffescript, o archivos que nunca se requieren en absoluto. El administrador de paquetes
tendría que saber qué archivos se requieren directamente de otros paquetes,
que necesitaría un análisis estático, como por ejemplo lo hace browserify, que
tal vez sería incluso peor en cuanto a rendimiento.

Pero, como dije en la 2da parte de este
https://github.com/nodejs/node/pull/5950#issuecomment-215109810
comentario, también podríamos simplemente concluir que nuestros problemas con los compañeros
las dependencias se originan en NPM y no en require y que tal vez NPM
necesita arreglar esto en su lugar, para que podamos volver al comportamiento anterior.

Pero, de nuevo, se siente mal usar la ruta absoluta para
identificar un módulo. Tal vez, también podríamos hacerlo configurable a través de un
opción de línea de comandos. Esa podría no ser la peor de todas las opciones que tenemos.


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/nodejs/node/issues/3402#issuecomment-215244154

@jasnell si esto es así o tal vez con múltiples cachés y un enlace simbólico. muy claro, fácil debugz :scream:

Bueno, podría tener otra opción:

¿Qué pasa si hacemos que el comportamiento de require sea configurable a través de un archivo adicional, por ejemplo, ".require_realpath".

Ejemplo:
Hacemos require('foo/lib/bar.js');

Ahora, cuando require encuentra el archivo y está a punto de cargarlo, comprueba si existe 'bar/.require_realpath'. Si no es así, el archivo se requiere de la nueva manera. Si existe, se carga de la forma antigua.

Esto sería muy fácil de implementar para los administradores de paquetes y necesitaría muy pocos cambios para node.js.

Pero esto suena demasiado bueno para ser verdad, ¿cuáles son los inconvenientes de esto?

FWIW, después de haber hojeado este hilo, resolver el uso de enlaces simbólicos será importante al usar módulos es6. Dado que la ruta no se puede resolver en tiempo de ejecución, y cuando desee elegir entre una compilación de depuración o versión para un módulo nativo. Eso está muy por delante, pero simplemente tirándolo por ahí.

@trevnorris ¿Puede dar más detalles sobre eso? ¿O puedes dar un ejemplo? No estoy seguro de entender cómo los módulos ES6 afectarían esto.

@VanCoding Estaba a punto de sugerir algo similar a su propuesta con archivos de puntos, excepto en la granularidad del directorio. Corrígeme si es la misma propuesta que la tuya. Si existe un archivo de punto .node_legacy en un directorio, se seguirá cualquier archivo con enlace simbólico en ese directorio como lo hizo el nodo 5.x y versiones anteriores. Si no está allí, se usaría la nueva heurística de resolución del módulo que no sigue el enlace simbólico. El script principal aún seguiría las reglas de resolución de enlaces simbólicos heredados independientemente.

También pediría un nuevo indicador de línea de comando de nodo o una variable de entorno para anular el comportamiento de resolución del módulo para que sea todo heredado o completamente nuevo y los archivos de puntos se ignorarían por completo.

@kzc sí, es más o menos lo mismo que mi propuesta. Con la única excepción de que require solo busca el archivo '.node_legacy' cuando la ruta comienza con un nombre de paquete. Las rutas absolutas y relativas siempre se requieren de la nueva manera.

Pero no estoy seguro de qué sería mejor. Tendríamos que elaborar los casos de uso...

Además, soy +1 para el indicador de línea de comando adicional.

Además, soy +1 para el indicador de línea de comando adicional.

Una configuración de variable de entorno de algún tipo tiene la ventaja de permitirle ejecutar scripts shebang JS ejecutables utilizando el binario de nodo que ya está en su ruta. Pero en este punto, aceptaría cualquier forma o ambas.

Creo que una variable de entorno tendría más sentido, pero también podría confundir aún más a la gente...

Por qué creo que una variable de entorno tiene más sentido: el caso de uso principal para la nueva estrategia require es cuando vincula paquetes para el desarrollo local. Así que me imagino que eso es algo que solo quieres hacer temporalmente... luego deberías recurrir a lo que realmente está instalado...

@alexanderGugel También se podría argumentar que el caso de uso principal para el comportamiento anterior es permitir que los administradores de paquetes realicen la optimización de caché, y para todo lo demás, se puede usar el nuevo sistema,

Entonces, el beneficio de tener dotfiles sería que no tiene que meterse con las variables de entorno o los argumentos de la línea de comandos porque el administrador de paquetes lo hace por usted.

Pero, sinceramente, me gusta la dirección en la que va esto. Hágalo configurable y todos estarán contentos de cualquier manera.

@trevnorris ... es importante señalar que el nuevo comportamiento _todavía permite_ resolver a través de enlaces simbólicos. Sin embargo, la diferencia es que las instancias del módulo se almacenan en caché según la ruta del enlace simbólico en lugar de la ruta real, lo que significa que se cargarán varias instancias del mismo código según el enlace simbólico.

@alexanderGugel y @VanCoding ... repasemos un poco el enfoque del archivo de puntos y veamos dónde aterrizan las cosas (utilicemos .module_legacy para este archivo hipotético)

Escenario 1: Sin .module_legacy

     /index.js
     /node_modules
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /bar --> ../../../myApp/node_modules/bar

En este caso, no hay archivos .module_legacy en ninguna parte. Se aplica la nueva semántica. Cada foo con enlace simbólico es su propia instancia y terminamos con el gran problema de caché de dependencia circular. Sin embargo, la dependencia del par baz se resuelve correctamente con cada instancia separada de enlace simbólico de foo .

Escenario 2: myApp/.module_legacy

     /.module_legacy
     /index.js
     /node_modules
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /bar --> ../../../myApp/node_modules/bar

En este caso, el archivo .module_legacy existe en el nivel superior de myApp . En este caso, las reglas antiguas se aplicarían en todos los casos. Tendría una sola instancia de foo cargada evitando el problema de dependencia circular, pero Node no podría ubicar la dependencia de pares baz porque no está en una ruta relativa a la ruta real de foo.

Escenario 3: modules/foo/.module_legacy

     /index.js
     /node_modules
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /.module_legacy
          /node_modules
                       /bar --> ../../../myApp/node_modules/bar

En este caso, el archivo .module_legacy aparece solo dentro foo . Lo que terminaríamos aquí es una mezcla de comportamientos nuevos y viejos. El módulo myApp/node_modules/bar se cargaría con las nuevas reglas, mientras que el module/foo se cargaría con las antiguas. Es un poco más difícil razonar sobre lo que sucede aquí debido a la dependencia circular. Por ejemplo, si hago require('bar') desde dentro de myApp/index.js , obtendría la misma instancia de bar que obtendría si hago require('bar') desde dentro de module/foo/index.js ?

Además, qué versión de baz será foo cuando lo haga require('baz') .

Escenario 4: myApp/node_modules/bar/.module_legacy

     /index.js
     /node_modules
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /.module_legacy
                      /node_modules
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /bar --> ../../../myApp/node_modules/bar

Esto es similar al escenario 3, excepto que el archivo .module_legacy se mueve a bar . Los mismos problemas se aplican aquí como Escenario 3.

Escenario 5: myApp/node_modules/bar/.module_legacy y `modules/foo/.module_legacy

     /index.js
     /node_modules
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /.module_legacy
                      /node_modules
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /.module_legacy
          /node_modules
                       /bar --> ../../../myApp/node_modules/bar

En este escenario, tanto foo como bar tienen sus propios archivos .module_legacy . La propia aplicación utiliza las nuevas reglas. Supongamos que mi myApp/index.js hace lo siguiente:

const bar = require('bar');
const foo = require('foo');

Se debe devolver una sola instancia de foo según las reglas anteriores, pero no podrá encontrar la dependencia del par baz . Esto termina sufriendo el mismo problema que el Escenario 2.

Mirando esto, permitir .module_legacy en niveles arbitrarios se complica rápidamente y hace bastante difícil razonar sobre lo que está sucediendo. Tener el archivo .module_legacy en el nivel superior (o no tenerlo en absoluto) hace que sea mucho más fácil: estamos usando el nuevo comportamiento o no. Un indicador de línea de comando o una variable de entorno harían lo mismo, pero cada uno tiene su propio costo.

Dado esto, un posible enfoque que podemos tomar a corto plazo sería agregar un indicador de línea de comando que, cuando se establece, revierte la carga del módulo al comportamiento anterior de modo que, si un desarrollador encuentra problemas con dependencias circulares en su gráfico de dependencia, podrían encender la bandera para revertir. Personalmente, creo que este es un enfoque mucho mejor que usar los archivos de puntos.

@jasnell

Tampoco soy un gran admirador del enfoque de variable de entorno/archivo heredado.

Parece que actualmente estamos hablando de dos objetivos aquí:

  1. no romper el código de la aplicación
  2. Permitir que los administradores de paquetes y los usuarios que dependen de la semántica anterior requieran la ruta resuelta de una forma u otra.

Suponiendo que estamos de acuerdo con descifrar el código de la aplicación (parece que estamos bastante de acuerdo con eso), quiero proponer un enfoque un poco más radical:

En lugar de tener un solo directorio node_modules , tenga un directorio shared_node_modules adicional desde el cual los enlaces simbólicos se resolverán de acuerdo con el algoritmo anterior o indique de alguna manera que los enlaces simbólicos deben resolverse de acuerdo con el algoritmo anterior usando su nombre.

Enfoque 1: shared_node_modules

- project
  - node_modules
    - a -> ../some/where/else (might not even be a symlink)
  - shared_node_modules
    - a -> ../some/where

Dado que a está en shared_node_modules , será necesario usar el algoritmo anterior .
Si node_modules y shared_node_modules están presentes, shared_node_modules tiene prioridad.

Enfoque 2: Prefijo

- project
  - node_modules
    - a -> ../some/_where

_where tiene el prefijo _ , lo que indica que _where es _interno_, lo que significa que debe resolverse de acuerdo con el comportamiento anterior.

Enfoque 3: Prefijo en linkname (probablemente más bonito)

- project
  - node_modules
    - _a -> ../some/where

_a tiene el prefijo _ , lo que indica que _a es _interno_, lo que significa que debe resolverse de acuerdo con el comportamiento anterior.

Tenga en cuenta que los usuarios seguirán necesitando _a usando require('a') .


Lo contrario también funcionaría: Podríamos...

  1. volver al antiguo comportamiento
  2. resuelve los enlaces simbólicos en extra_node_modules de acuerdo con el nuevo algoritmo.

De esa forma sería un cambio retrocompatible, ambos casos serían compatibles y sería más fácil diferenciar entre las dos estrategias. También es bastante fácil de cambiar.

¿Pensamientos?


Pensando más en esto, realmente creo que el enfoque 3 sería un buen compromiso. De esa manera, podríamos admitir ambos casos, al mismo tiempo que permitimos que los administradores de paquetes (de cualquier tipo en realidad) y los usuarios que requieren un flujo de trabajo más avanzado que involucre muchos enlaces simbólicos (por ejemplo, si trabaja con muchos paquetes privados al mismo tiempo) para utilice el mecanismo de solicitud original.

Estaría más que feliz de hacer este cambio si todos están de acuerdo con él.


No es que esto tampoco entre en conflicto con los nombres de los paquetes npm: https://github.com/npm/validate-npm-package-name#naming -rules (prefijo _ ).

cc @rstacruz

@jasnell En el esquema de archivos de puntos, imaginé que el archivo de puntos estaría literalmente en el mismo directorio donde vive el enlace simbólico. Si está presente, se sigue el enlace simbólico como en el algoritmo de resolución del módulo anterior; de lo contrario, se aplicaría el nuevo esquema de resolución. Un archivo de puntos en un directorio principal no se aplicaría a sus directorios secundarios. Cada directorio se evaluaría de forma independiente en cuanto al esquema de resolución utilizado.

Enfoque 3: Prefijo en linkname (probablemente más bonito)

@alexanderGugel , este enfoque se ve genial y es fácil de seguir con la ruta real del enlace simbólico o no.

@kzc ... gracias por la aclaración :-) ... repasemos eso un poco. Espero haber entendido bien tu intención...

Escenario 1:

     /index.js
     /node_modules
                  /.module_legacy
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /bar --> ../../../myApp/node_modules/bar

Esto tendría como resultado la creación de múltiples instancias de foo . El primero (en el enlace simbólico myApp/node_modules/foo ) no podría encontrar su dependencia de pares baz (@version=1) , mientras que el segundo encontraría su dependencia de pares baz (@version=2) . Se crearían varias instancias de bar ,

Nuestro caché se vería algo así como...

{
  "modules/foo": { },
  "modules/foo/node_modules/bar": { },
  "modules/foo/node_modules/bar/node_modules/foo": { },
  "modules/foo/node_modules/bar/node_modules/baz": { },
  "modules/foo/node_modules/bar/node_modules/foo/node_modules/bar": { },
  ...
  "myApp/node_modules/bar": { },
  "myApp/node_modules/bar/node_modules/foo": { },
  "myApp/node_modules/bar/node_modules/baz": { },
  "myApp/node_modules/bar/node_modules/foo/node_modules/bar": { },
  "myApp/node_modules/bar/node_modules/foo/node_modules/bar/node_modules/foo": { },
  "myApp/node_modules/bar/node_modules/foo/node_modules/bar/node_modules/baz": { },
  ...
}

En otras palabras, todo lo que hemos hecho aquí es _combinar_ ambos problemas que estamos tratando de resolver :-)

Escenario 2:

     /index.js
     /node_modules
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /.module_legacy
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /bar --> ../../../myApp/node_modules/bar

Nuevamente, se crearían dos instancias de foo . Uno encontraría su dependencia de los compañeros, el otro no.
Se cargarían dos instancias de bar .

Nuestro caché se vería algo así como:

{
  "myApp/node_modules/baz": { },
  "myApp/node_modules/foo": { },
  "myApp/node_modules/foo/node_modules/bar": { },
  "modules/foo": { },
  "modules/foo/node_modules/bar": { },
}

Escenario 3:

     /index.js
     /node_modules
                  /.module_legacy
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /.module_legacy
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /bar --> ../../../myApp/node_modules/bar

Solo se cargaría una única versión de foo , que no encontraría su dependencia de pares. Se cargarían dos instancias de bar .

Nuestro caché se vería algo así como:

{
  "modules/foo": { },
  "modules/foo/node_modules/bar": { },
  "myApp/node_modules/bar": { }
}

Escenario 4:

     /index.js
     /node_modules
                  /.module_legacy
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /.module_legacy
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /.module_legacy
                       /bar --> ../../../myApp/node_modules/bar

Una vez que se crea la instancia de foo , no se puede encontrar su dependencia de pares. Se crearía una instancia de bar .

Nuestro caché se vería algo así como:

{
  "modules/foo": { },
  "myApp/node_modules/bar": { }
}

Escenario 5:

     /index.js
     /node_modules
                  /.module_legacy
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /.module_legacy
                       /bar --> ../../../myApp/node_modules/bar

Este es interesante... se crearía una instancia de bar . Creo que se crearían dos instancias de foo , cada una de las cuales apuntaría al bar único, que apunta a solo una de las instancias de foo .

Nuestro caché se vería algo así como:

{
  "modules/foo": { },
  "myApp/node_modules/bar": { },
  "myApp/node_modules/bar/node_modules/baz": { },
  "myApp/node_modules/bar/node_modules/foo": { }
}

Escenario 6:

     /index.js
     /node_modules
                  /baz (@version=1)
                  /foo --> ../../foo
                  /bar
                      /node_modules
                                   /baz (@version=2)
                                   /foo --> ../../../../foo
modules
      /foo
          /node_modules
                       /.module_legacy
                       /bar --> ../../../myApp/node_modules/bar

Se crearía una instancia de bar . Dos instancias foo que apuntan a bar . El bar apuntaría a una de las instancias de foo .

Nuestro caché se vería algo así como:

{
   "myApp/node_modules/baz": { },
   "myApp/node_modules/foo": { },
   "myApp/node_modules/bar": { },
   "myApp/node_modules/bar/node_modules/baz": { },
   "myApp/node_modules/bar/node_modules/foo": { },
}

@alexanderGugel ... en este punto, recorriendo los diversos ejercicios sobre esto, parece que cualquier escenario en el que tengamos _tanto_ el enfoque antiguo como el nuevo que se utilizan dentro de un solo proceso solo empeora el problema porque terminamos con las desventajas de ambos enfoques más de lo que realmente resuelve nada. Necesitamos un solo comportamiento, no múltiples. Tiene que ser (a) Volver al comportamiento anterior, (b) Seguir con el nuevo comportamiento o (c) Encontrar un comportamiento que funcione para ambos.

@jasnell Creo que el Approach 3 @alexanderGugel puede solucionar ambos problemas. No es necesario revertir y mantener la ruta del enlace simbólico tiene una forma de usar la ruta real para requerir.

En otras palabras, todo lo que hemos hecho aquí es combinar ambos problemas que estamos tratando de resolver :-)

@jasnell El antiguo esquema de enlace simbólico nunca encontró las dependencias de pares correctas. De modo que se preservaría ese "comportamiento" heredado. No estoy sugiriendo que sea una mejora. De ahí el impulso del nuevo algoritmo de resolución. Según mi experiencia, la única forma en que funcionó el comportamiento del enlace simbólico heredado fue en un escenario de node_modules plano donde cada módulo estaba en el mismo nivel entre sí.

Necesitamos un solo comportamiento, no múltiples. Tiene que ser (a) Volver al comportamiento anterior, (b) Seguir con el nuevo comportamiento o (c) Encontrar un comportamiento que funcione para ambos.

  • @jasnell

Ya tenemos ambos escenarios allí, lo cual está perfectamente bien. En isMain , resolvemos la ruta real, de lo contrario usamos el nombre del enlace. Esos son exactamente los dos casos que necesitamos cubrir.

@jasnell Creo que el Enfoque 3 de @alexanderGugel puede solucionar ambos problemas. No es necesario revertir y mantener la ruta del enlace simbólico tiene una forma de usar la ruta real para requerir.

  • @fengmk2

¡El cambio sugerido es bastante trivial! En lugar de usar la ruta real cuando isMain , la usamos si el nombre del archivo comienza con _ (asumiendo que queremos mantener las cosas compatibles con versiones anteriores, de lo contrario sería exactamente al revés: Use la ruta real solo si el nombre comienza con _ ).

@jasnell El antiguo esquema de enlace simbólico nunca encontró las dependencias de pares correctas. De modo que se preservaría ese "comportamiento" heredado. No estoy sugiriendo que sea una mejora. De ahí el impulso del nuevo algoritmo de resolución. Según mi experiencia, la única forma en que funcionó el comportamiento del enlace simbólico heredado fue en un escenario de node_modules plano donde cada módulo estaba en el mismo nivel entre sí.

Es por eso que estoy 👎 usando el nombre de archivo + la ruta real como clave para el caché del módulo. En realidad, combina ambos problemas y esa es la solución que introduce una estrategia adicional (archivo legacy ).

Hmm... Me mantendré escéptico hasta que vea un ejemplo de trabajo ;-) Al analizar todos los escenarios, no veo un camino consistente hacia adelante con una implementación que use ambas semánticas. ¡Estoy feliz de que se demuestre que estoy equivocado! :-)

@jasnell PR está en camino :)

@alexandergugel
¿Quiere decir que la detección _ se invierte para el script principal? parece raro...
Tal vez podríamos hacerlo consistente usando solo el nuevo comportamiento si comienza con _ y cambia el comportamiento de npm link para que siempre tenga el prefijo _ .

@hax No, no hay detección de prefijos allí (al menos actualmente). La "segunda" estrategia que mencioné se refería al hecho de que require actualmente ya está usando la ruta real resuelta para los primeros (=principales) módulos, que se indica a través isMain en module.js .

En realidad voy a tener que ir a la cama ahora. Todavía no conseguí que funcionara por completo, pero debería ser bastante sencillo (algo así ).

+1 para continuar explorando la opción de prefijo en la esencia de @alexanderGugel .

El punto común de los esquemas de archivos de prefijos y puntos es agregar un bit adicional de información a un enlace simbólico para saber si se debe realizar una ruta real o no. Sería bueno mantener el nombre del enlace simbólico y codificar ese bit de otra manera. En Mac, es posible cambiar los bits de permiso en el enlace simbólico que, de lo contrario, no se usan, pero no creo que sea así en otros sistemas operativos. ¿Hay otros bits no utilizados relacionados con enlaces simbólicos a nuestra disposición que funcionen en todas las plataformas? Solo lanzando la idea por ahí.

Veo más de un par de archivos JS con prefijos _ en node_modules para mis proyectos. lodash es uno. Y una instancia de un directorio que comienza con _ - node_modules/fbjs/lib/__mocks__

Usar el @ menos usado puede ser una mejor alternativa para un prefijo.

No veo agregar un prefijo como una opción. La razón de esto es que muchos módulos asumen que pueden acceder a sus carpetas de dependencias directas en ./node_modules/x

Entonces, si alguien tiene una aplicación express como esta:

app.use(express.static("/public/bootstrap","./node_modules/bootstrap/dist/"))

Esto ya no funcionaría. Necesitaría require.resolve la ubicación del módulo primero y luego path.resolve relativo desde allí. Supongo que eso afectaría a muchos módulos.

Básicamente, estamos hablando de 2 enfoques diferentes aquí:

A) agregar la información al enlace simbólico
B) agregar la información al módulo

Lo que @kzc y @alexanderGugel están proponiendo cae bajo el enfoque A. El problema con A es que es realmente complicado y difícil de razonar.

Supongamos el siguiente escenario:

/
    a/ -> symlink to ./b, configured to use real path
    b/
        index.js -> symlink to ../b.js, configured to use virtual path
    b.js -> console.log(__filename)
    index.js -> require("./a/index.js");

pregunta: ¿cuál sería la salida?
/a/index.js?
/b/index.js?
/b.js?

El problema es que puede haber múltiples enlaces simbólicos en una ruta, y tendríamos que recorrer toda la ruta y verificar si es un enlace simbólico y cómo está configurado y decidir qué configuración tiene prioridad sobre la otra. No es una tarea trivial.

Por eso propuse hacerlo con el enfoque B y configurarlo donde apunta la ruta: en el módulo. Eso significa que, si requerimos un módulo a través de diferentes enlaces simbólicos, siempre obtenemos la misma configuración. Pero también significa que no podemos configurarlo de manera diferente para cada enlace simbólico. La pregunta es: ¿importa? Tal vez no.

Veamos los 3 escenarios del mundo real:

MNP

app/
    node_modules/
        a/
            index.js -> require("b");
            node_modules/
                b/
                     index.js -> require("c"); //peer dependency
        c/
            index.js
    index.js -> require("a"); require("c");

En este escenario, todo funciona bien tanto en la forma nueva como en la antigua, ya que no se usaron enlaces simbólicos.

MNP desarrollando un

app/
    node_modules/
        a/ -> symlink to ../../a_dev
        c/
            index.js
    index.js -> require("a"); require("c");
a_dev/
    index.js -> require("b");
    node_modules/
        b/
            index.js -> require("c"); //peer dependency

En este escenario, todo funciona bien en la forma nueva, pero no funcionó en la forma anterior, porque b/index.js no pudo encontrar 'c'.

otro administrador de paquetes

app/
    node_modules/
        a/ -> symlink to ../../a
        c/ -> symlink to ../../c
     index.js -> require("a"); require("c");
a/
    node_modules/
        b/ -> symlink to ../../b
     index.js -> require("b");
     .modules_legacy
b/
    node_modules
        c/ -> symlink to ../..c
    index.js -> require("c"); //peer dependency
    .modules_legacy
c/
    index.js
    .modules_legacy

Esto había funcionado de la forma antigua, pero actualmente no funciona de la forma nueva. Pero usando archivos .modules_legacy, podríamos arreglarlo.

Es importante tener en cuenta que el archivo .modules_legacy no es nada que un desarrollador agregaría manualmente a su módulo. De hecho, un desarrollador ni siquiera debería saber que existe. La función propuesta sería básicamente para uso exclusivo del administrador de paquetes. Entonces, los administradores de paquetes deciden agregar el archivo o no según la jerarquía del módulo que construyen. Deberíamos evitar que los desarrolladores agreguen un archivo '.modules_legacy' a sus módulos de forma manual, porque si lo hacen, ya no funcionarán con NPM.

Sí, también podríamos optar por la opción var de línea de comandos/entorno, pero entonces los usuarios de NPM tendrían que proporcionarla cuando estén desarrollando la aplicación y un módulo simultáneamente, o los usuarios de otros administradores de paquetes tendrían que proporcionarla cada vez. ejecutan su aplicación.

No tengo :cow: con ninguno de los enfoques emergentes (_symlink, .modules_legacy o variable env), soy un :+1: para cualquiera de los dos.

Lo importante es que la estrategia de resolución estándar sea ​​clara y sencilla.

Agregar un gancho alternativo en cualquiera de esas formas es un caso de esquina aceptable que proporciona grandes beneficios (sí, pnpm :heart:) :rocket: Recordemos documentarlo bien :stuck_out_tongue_winking_eye:

@alexanderGugel Código de ejemplo:

import p from `mod/${process.config.target_defaults.default_configuration}/p`;

En este caso, intento cargar un recurso nativo (a saber p.node ) y determinar si debe cargar la compilación Release o Debug según el tipo de compilación del nodo que estoy ejecutando. Desafortunadamente, los módulos es6 no permiten la resolución de rutas en tiempo de ejecución. Así que tendría que vincular p.node antes de ejecutar mi secuencia de comandos, si no quisiera cambiar mi secuencia de comandos cada vez que quisiera cambiar las configuraciones.

Esto podría automatizarse con bastante facilidad durante la construcción, con un pequeño parche. por ejemplo, usando un comando como ./node_g $(which node-gyp) podría detectar automáticamente con qué configuración de nodo está construyendo y reescribir automáticamente el enlace simbólico.

@VanCoding Sí, en el escenario de "otro administrador de paquetes" con una estructura plana node_modules , el enfoque del archivo de puntos en el nivel del módulo funcionaría tan bien como lo hizo la resolución heredada. No estamos tratando de hacer que el mecanismo de resolución heredado sea mejor de lo que era, solo para preservar su comportamiento.

En su propuesta, ¿el uso de .modules_legacy solo se aplicaría a los enlaces simbólicos de archivos javascript individuales en el mismo directorio que el archivo de puntos y cualquier enlace simbólico dentro del directorio secundario inmediato node_modules ?

Además, en tu ejemplo:

c/
    index.js
    .modules_legacy

¿Estaba .modules_legacy aquí superfluo? Todavía funcionaría igual si se eliminara porque no hay enlaces simbólicos presentes en el directorio inmediato ni c/ tiene un directorio secundario node_modules que contiene enlaces simbólicos, ¿correcto?

Lo que @kzc y @alexanderGugel están proponiendo cae bajo el enfoque A.

Solo estaba señalando que _ como prefijo no sería una buena opción debido a su uso generalizado. Prefiero un enfoque de archivo de puntos o codificar la información dentro del enlace simbólico real de alguna manera en lugar de su nombre.

Editar: codificar el bit de comportamiento heredado de la ruta real dentro del enlace simbólico del sistema operativo en sí mismo no sobreviviría al tar y al destar, por lo que retiro esta idea como inviable.

👍 por usar _ como prefijo para enlaces simbólicos que deben resolverse de acuerdo con el algoritmo anterior.

Solo estaba señalando que _ como prefijo no sería una buena opción debido a su uso generalizado.

Como @alexanderGugel señaló anteriormente, los nombres de paquetes que comienzan con _ no son nombres de paquetes válidos de npm. La vinculación de paquetes es, con mucho, el caso de uso más común para usar el mecanismo anterior.

@kzc ¿Puede señalar un caso de uso específico en el que un enlace simbólico con prefijo _ se usa para compartir un directorio node_modules ubicado en la ruta real? Creo que esto debería ser extremadamente poco común.

Además, no llamaría a esto "comportamiento heredado". Creo que la discusión anterior es prueba suficiente de que existen casos de uso legítimos para ambos enfoques.

Dicho esto, el prefijo usando . también tiene sentido. . está oculto, por lo que implica que se comporta de manera diferente.

👎 por usar un archivo separado. Es menos flexible aplicar este comportamiento de forma "real" módulo por módulo, en lugar de por enlace.

Esto también afecta el código completamente fuera de node_modules. Y rompe una de las reglas del sistema del módulo de nodo antiguas y confiables (según tengo entendido): cada archivo se ejecuta exactamente una vez. Especialmente dado el estado del sistema de módulos como "bloqueado", eso no debería esperarse (creo).

Este archivo por ejemplo:

const bar = require('./bar'); // bar contains an index.js
const foo = require('./foo'); // foo is a symlink to bar
console.log('foo === bar', foo === bar); // expected to be `true`

En el nodo 4, esto ejecuta el código de nivel superior en bar/index.js una vez y en el nodo 6 lo ejecutará varias veces, incluidos los efectos secundarios que pueda tener (dejando de lado las preocupaciones estilísticas sobre los efectos secundarios de nivel superior). Hacer un cambio de este tipo en una parte bloqueada de la API sin ningún período de desaprobación / suscripción parece extraño.

Como @alexanderGugel señaló anteriormente, los nombres de paquetes que comienzan con _ no son nombres de paquetes válidos de npm. La vinculación de paquetes es, con mucho, el caso de uso más común para usar el mecanismo anterior.

@nowtvicebergteam Eso está bien para los nombres de los paquetes, pero como @jkrems señala, ¿qué pasa con los archivos JS con enlaces simbólicos individuales? Potencialmente pueden tener prefijos _ .

El enfoque de archivos de puntos maneja tanto archivos con enlaces simbólicos como directorios con enlaces simbólicos.

@jkrems ,

Cada archivo se ejecuta exactamente una vez.

Un enlace simbólico es un archivo. Con el nuevo comportamiento, esta afirmación ahora es realmente cierta.

@jkrems que depende de su definición de archivo. Dicho simplemente: un enlace simbólico _es_ un archivo que apunta a otro archivo. La pregunta ahora es: ¿debería resolverse el enlace simbólico y debería usarse la ruta resuelta para el almacenamiento en caché, la búsqueda de módulos, etc. o debería usarse en su lugar el enlace simbólico no resuelto? La mitad de las publicaciones en este número tratan exactamente sobre esa pregunta.

Sobre tu problema específico: No creo que esto afecte al ecosistema, sino solo a las instalaciones locales. Razón simple: muchos módulos están bajo el control de versión de git y git no permite enlaces simbólicos. Pero esa es solo mi opinión personal. Podría estar equivocado.

Editar: lo que dijo @dlongley 😄

@benurb git admite enlaces simbólicos (al menos cuando solo te importa *nix, no estás seguro de la compatibilidad con Windows). Y conozco bastantes proyectos que verifican los enlaces simbólicos en git. Y eso depende del hecho de que la semántica requerida es "mismo archivo, como en usted-editar-uno-y-ambos-cambiar". Era parte del contrato por todo este tiempo, después de todo.

Para ser claros: no estoy diciendo "nunca hagas este cambio". Estoy diciendo: hacer un cambio en una parte bloqueada del contrato API del nodo _sin desaprobación y/o transición de suscripción_ no se ve bien.

@jkrems Windows no los admite afaik. Pero ese es otro tema. Lo siento por mencionar esto. Volviendo al tema principal: "mismo archivo, como en usted-editar-uno-y-ambos-cambiar". Como @dlongley y yo señalamos, este no es el caso. Si edita el contenido del destino del enlace simbólico, solo cambia su contenido. Cambiar el contenido de un enlace simbólico significa cambiar la ruta a la que apunta el enlace. El enlace y el destino son técnicamente dos archivos. Puede ver eso, por ejemplo, por el hecho de cómo se comporta rm. No necesita llamar a rm -r incluso si un enlace simbólico apunta a un directorio. Simplemente porque es un archivo.

En *nix, un archivo consta de un nombre y un inodo (su contenido). Como ambos, el enlace simbólico y su objetivo, tienen un nombre y un contenido, son de hecho dos archivos diferentes.

@benurb No creo que un argumento filosófico sobre "cuál es el contenido de un enlace simbólico / qué hay en un nombre" esté fomentando la discusión. Todo lo que dije fue: hay aplicaciones que dependen del comportamiento actual y era bien sabido que formaban parte de un nodo de API bloqueado proporcionado. Y, de nuevo, no digo que sea mejor o peor que el nuevo comportamiento. Estoy diciendo que cambiarlo sin ningún proceso en torno a la implementación no está en línea con la forma en que se describe "bloqueado" en los documentos.

Sí, perdón por la larga explicación. Estamos en la misma página aquí, ya que estoy de acuerdo en que el cambio no sucedió tan bien como debería. El caso es que este cambio soluciona un problema que experimenté en muchos proyectos desde que comencé a usar peer deps regularmente. Me gusta el nuevo comportamiento y no creo que deba revertirse. Por otro lado, veo que esto causa problemas con los módulos vinculados, pero probablemente se puedan solucionar con las soluciones proporcionadas anteriormente ( _ dirname, etc.). Pero vincular archivos en lugar de módulos/directorios es algo completamente diferente y en mi humilde opinión se comporta correctamente de la forma en que lo observó.

@jkrems , como se explicó anteriormente en este hilo, la documentación es inconsistente en este tema. Esto llevó a que las personas tuvieran expectativas y sorpresas mixtas, y algunos casos simplemente no funcionaron en absoluto. Debido a que el comportamiento anterior podría considerarse un error, una propuesta original fue realizar este cambio sin cambiar el semestre principal, pero se rechazó porque se determinó que era posible que algunas personas confiaran explícitamente en el comportamiento anterior. Esto también fue antes de que saliera a la luz que los administradores de paquetes alternativos lo usaron para lograr una mayor eficiencia. Entonces, el cambio se hizo como un cambio importante de Semver.

Estoy de acuerdo en que, si es posible, deberíamos tratar de proporcionar un medio por el cual las personas puedan usar el antiguo comportamiento según sea necesario, y creo que la mayoría de las personas en este hilo apoyan esa idea y han propuesto formas de lograrlo.

@jasnell El problema con el uso de la versión de prefijo _ es que esencialmente necesitamos stat dos veces la cantidad de archivos, lo que podría resultar en regresiones de rendimiento significativas para require() .

Por lo tanto, solo como otra idea, ¿qué sucede si elegimos el mecanismo apropiado en función del destino del enlace, en lugar del nombre del enlace?

Si el enlace simbólico apunta a una ruta absoluta, use el mecanismo anterior; de lo contrario, use el nuevo.

Si el enlace simbólico apunta a una ruta absoluta, use el mecanismo anterior; de lo contrario, use el nuevo.

El uso de symilnks que apuntan a rutas absolutas es legítimo en el nuevo esquema de resolución. Así que preferiría que encontráramos otro mecanismo explícito.

Aquí hay otra propuesta de resolución de módulo unificador que es una variación de la idea del prefijo _ :

Si un archivo o directorio con enlace simbólico tiene un archivo con el mismo nombre en el mismo directorio con un sufijo @ entonces se usa el antiguo esquema de resolución de ruta real en ese enlace; de ​​lo contrario, se usa el nuevo esquema de resolución.

Nuevo esquema de resolución:

foo1.js --> ../../foo1.js

bar1 --> /usr/local/whatever1/

Antiguo esquema de resolución (realpath):

foo2.js --> ../../foo.js
foo2.js@

bar2 --> /usr/local/whatever2/
bar2@

Tiene la ventaja de mantener intacto el nombre del enlace simbólico, además de ser fácil de entender. Los enlaces simbólicos dentro del mismo directorio pueden usar el esquema de resolución de módulo nuevo o antiguo según el caso.

@kzc También me gusta esta versión. Sería incluso más simple de implementar en realidad (se podría hacer con stat adicionales en readFilePath ). ¿Estás trabajando en un PR para esto? O puedo recoger eso?

Acabo de enviar un PR para la solución _ , pero sería trivial agregar @ como sufijo. Creo que ambas soluciones estarían bien.

@alexanderGugel vi su PR que ya hace la mayor parte del trabajo. Sería feliz si lo recogiera. ¡Gracias!

Los archivos adicionales con el sufijo @ junto a los enlaces simbólicos que indican el comportamiento antiguo de la ruta real tienen la ventaja de que funcionarían con instalaciones de enlaces simbólicos de estilo antiguo utilizando las versiones de nodo 5.x y anteriores.

Si bien creo que la solución con los archivos @ es mucho mejor que prefijar los enlaces simbólicos con "_", sigo creyendo que ahora estamos en el camino equivocado.

Nuevamente quiero señalar el siguiente escenario:

/a/b/c/d/e -> /e
/e/f/g/h/j.js

ahora hagamos require('/a/b/c/d/e/f/g/h/j.js');

Actualmente, eso no es absolutamente ningún problema, solo lea el archivo y eso es todo.
Con su propuesta, primero tendríamos que hacer las siguientes comprobaciones:

isSymlink('/a')
isSymlink('/a/b')
isSymlink('/a/b/c')
isSymlink('/a/b/c/d')
isSymlink('/a/b/c/d/e')
isSymlink('/a/b/c/d/e/f')
isSymlink('/a/b/c/d/e/f/g')
isSymlink('/a/b/c/d/e/f/g/h')
isSymlink('/a/b/c/d/e/f/g/h/j.js')

Y luego, cuando hayamos descubierto que /a/b/c/d/e es un enlace simbólico, también tendríamos que comprobar si existe /a/b/c/d/e@ .

Muchas comprobaciones para cargar un solo archivo, ¿no crees?

Pero se complica aún más cuando una ruta consta de 2 enlaces simbólicos:

/a/b/c -> /c
/c/d/e -> /e
/e/f/g.js

En este caso, tendríamos que comprobar la existencia de /a/b/c@ y /c/d/e@ y decidir cuál tiene prioridad sobre el otro. Aún mejor, tal vez tendríamos que resolver algún camino semirreal. Si, por ejemplo /a/b/c@ existe, pero /c/d/e@ no, la ruta semi-real podría ser /c/d/e/f/g.js .

Un caso más:

/a/b/c -> /c
/c -> /d
/d/e/f.js

Esta es una situación peligrosa, porque /c@ no sería visible a través de la ruta virtual. Entonces tendríamos que realpath('/a/b/c') primero para luego verificar la existencia de /c@ , porque verificar la existencia de /a/b/c@ es algo diferente.

Creo que este enfoque es demasiado complejo y potencialmente significa una gran pérdida de rendimiento.

Sé que este enfoque nos daría la máxima flexibilidad. Pero, ¿realmente lo necesitamos? ¿No es nuestro objetivo aquí mantener el nuevo comportamiento y encontrar la solución más ligera y elegante para seguir siendo compatible con los otros administradores de paquetes?

Lo que he propuesto anteriormente haría exactamente eso. Con el menor trabajo extra posible. Lo único que sería aún más ligero sería el entorno y las opciones de línea de comandos. Tal vez también debería hacer una PR para que veas lo liviano que es.

@VanCoding está hablando de su propuesta de archivo .module_legacy , ¿verdad? Tal vez también sería una opción aplicar una propiedad como "_loadingBehavior": "realpath" al paquete.json.

@benurb exactamente! También sería posible ponerlo en el archivo package.json, ya que siempre habría un archivo package.json donde colocaríamos un archivo ".module_legacy".

Sin embargo, sería más rápido simplemente buscar la existencia de un archivo que analizar un archivo json y verificar si existe una clave. Pero definitivamente entendiste el concepto :)

@benurb 👎 Al hacer esto módulo por módulo en lugar de enlace simbólico por enlace simbólico. Es menos flexible y las relaciones públicas de @alexanderGugel muestran que el impacto en el rendimiento es muy limitado: https://github.com/nodejs/node/pull/6460#issuecomment -215617902

La solución _ no requiere una verificación adicional si existe un archivo, solo si el archivo (o enlace simbólico) no se pudo encontrar en primer lugar.

En una nota al margen, no lo llamaría module_legacy , ya que requerir a través de la ruta real de un paquete no debería quedar obsoleto debido a la flexibilidad que proporciona.

La comparación con PHP anterior no es realmente un argumento válido en mi opinión, ya que el require de Node es significativamente diferente en primer lugar. También se podría argumentar que recurrir al directorio node_modules del padre es "incorrecto" desde la perspectiva del usuario, lo que definitivamente no es el caso y permite, por ejemplo, dependencias circulares en primer lugar.

@VanCoding

Con su propuesta, primero tendríamos que hacer las siguientes comprobaciones:

isSymlink('/a')
isSymlink('/a/b')
isSymlink('/a/b/c')
isSymlink('/a/b/c/d')
isSymlink('/a/b/c/d/e')
isSymlink('/a/b/c/d/e/f')
isSymlink('/a/b/c/d/e/f/g')
isSymlink('/a/b/c/d/e/f/g/h')
isSymlink('/a/b/c/d/e/f/g/h/j.js')

Sugiero que no se sigan los enlaces simbólicos del directorio principal para mantener el esquema más simple si la gente del administrador de paquetes alternativo no tiene problemas con él. Solo se sigue el directorio del módulo final o el enlace del archivo JS en presencia de un archivo con el sufijo @ del mismo nombre en el mismo directorio.

Editar: anteriormente cité la sección incorrecta. Reparado.

Y luego, cuando hayamos descubierto que /a/b/c/d/e es un enlace simbólico, también tendríamos que verificar si /a/b/c/d/e@ existe.

Eso es correcto. Esperemos el @ -suffix symlink peer file PR de @alexanderGugel antes de emitir juicios sobre el desempeño.

Otra variación más de las propuestas de archivos de prefijo _ y '@' ...

Enumere los nombres de los enlaces simbólicos en un archivo (digamos .module_realpath ) en cada directorio en el que desee utilizar el antiguo comportamiento de resolución del módulo de enlaces simbólicos realpath. Esto daría como resultado menos llamadas de stat() que la propuesta " @ file ".

Todavía otra variación...

El nodo solo leería en un solo archivo .module_realpath al inicio ubicado en el mismo directorio donde vive el script principal de la ruta real y solo aquellos enlaces simbólicos con nombre tendrán la ruta real. La ubicación de este archivo .module_realpath podría anularse opcionalmente con un indicador de línea de comando --realpath-symlink-file para apuntar a un archivo diferente. De esa manera, todas las decisiones sobre la ruta real o no se pueden hacer en la memoria y ser completamente explícitas. (Editar: --module-realpath-file para el nombre de la bandera puede ser mejor).

Después de pensar un poco más sobre esto, realmente creo que el enfoque de prefijo _ es el correcto. También jugué con la idea del archivo @ . Creo que también podría haber cierta confusión sobre cómo funciona el mecanismo de resolución actualmente propuesto .

@VanCoding @kzc

While I think the solution with the @-files is way better that prefixing symlinks with "_", I still believe we're on the wrong road now.

I again want to point out the following scenario:

/a/b/c/d/e -> /e
/e/f/g/h/j.js
now let's do require('/a/b/c/d/e/f/g/h/j.js');

Currently, that's absolutely no problem, just read the file and that's it.
With your proposal, we'd have to do the following checks first:

isSymlink('/a')
isSymlink('/a/b')
isSymlink('/a/b/c')
isSymlink('/a/b/c/d')
isSymlink('/a/b/c/d/e')
isSymlink('/a/b/c/d/e/f')
isSymlink('/a/b/c/d/e/f/g')
isSymlink('/a/b/c/d/e/f/g/h')
isSymlink('/a/b/c/d/e/f/g/h/j.js')
And then when we've discovered that /a/b/c/d/e is a symlink, we'd also have to check if /a/b/c/d/e@ exists.

Este es un escenario interesante. Echemos un vistazo a lo que realmente sucede.

La forma más fácil de averiguarlo es agregando un par de debug a 6460:

diff --git a/lib/module.js b/lib/module.js
index 249b010..67b2aeb 100644
--- a/lib/module.js
+++ b/lib/module.js
@@ -113,6 +113,7 @@ function readPackagePath(filename, exts, isMain) {
 }

 function readFilePath(requestPath, isMain) {
+  debug(`readFilePath ${requestPath}`);
   if (isMain) {
     return fs.realpathSync(requestPath);
   }
@@ -123,6 +124,7 @@ function readFilePath(requestPath, isMain) {
 // resolve to the absolute realpath if running main module,
 // otherwise resolve to absolute while keeping symlinks intact.
 function tryFile(requestPath, isMain) {
+  debug(`tryFile ${requestPath}`);
   const rc = stat(requestPath);
   if (rc === 0) {
     return readFilePath(requestPath, isMain);

Porque esa es la parte "costosa" en la que en realidad estamos resolviendo el nombre de archivo / stat ing.

Si ahora creamos la estructura de directorios que propuso en su ejemplo original, terminaremos con un árbol que se parece a esto:

/Users/alex/a
└── b
    └── c
        └── d
            └── e -> /Users/alex/e

4 directories, 0 files
/Users/alex/e
└── f
    └── g
        └── h
            └── j.js

3 directories, 1 file

Ahora, veamos qué sucede _realmente_ cuando requerimos un archivo "debajo" del enlace simbólico:

NODE_DEBUG=module ./out/Release/node -e "require('/Users/alex/a/b/c/d/e/f/g/h/j.js')"

Producción:

MODULE 93110: Module._load REQUEST vm parent: [eval]
MODULE 93110: load native module vm
MODULE 93110: Module._load REQUEST /Users/alex/a/b/c/d/e/f/g/h/j.js parent: [eval]
MODULE 93110: looking for "/Users/alex/a/b/c/d/e/f/g/h/j.js" in ["/Users/alex/repos/node/node_modules","/Users/alex/repos/node_modules","/Users/alex/node_modules","/Users/node_modules","/Users/alex/.node_modules","/Users/alex/.node_libraries","/Users/alex/repos/node/out/lib/node"]
MODULE 93110: readFilePath /Users/alex/a/b/c/d/e/f/g/h/j.js
MODULE 93110: load "/Users/alex/a/b/c/d/e/f/g/h/j.js" for module "/Users/alex/a/b/c/d/e/f/g/h/j.js"
required j.js

De hecho, la solución _ es rápida, por el hecho de que no necesitamos comprobar si existe o no un archivo @ .

TL; DR En resumen, esa no es la forma en que funciona la resolución del módulo. Tenga en cuenta que nunca verificamos si un archivo es un enlace simbólico, sino que resolvemos su ruta real .

@alexanderGugel Estoy de acuerdo con el esquema de enlace simbólico con prefijo _ . Es simple y fácil razonar sobre qué comportamiento está obteniendo. Solo pensé que la propuesta de archivo con sufijo @ o las otras variaciones harían que el árbol de instalación fuera compatible con versiones anteriores del nodo, ya que no altera el nombre del enlace simbólico.

Con respecto a https://github.com/nodejs/node/issues/3402#issuecomment -215597796

Si el enlace simbólico apunta a una ruta absoluta, use el mecanismo anterior; de lo contrario, use el nuevo.

¿Podría aclarar el uso de "ruta absoluta" en esa declaración?

Bajo la propuesta del prefijo '_', el siguiente enlace simbólico usaría el nuevo esquema de resolución o el antiguo realpath :

foo ---> /usr/local/foo

Aquí hay otra idea (conceptualmente similar a la propuesta de module_realpath de @kzc )....

En lugar de confiar en el comportamiento dinámico de require, podríamos proporcionar algunos
manera de "arrancar" el caché requerido, esencialmente anulando el valor predeterminado de Node
conducta.

Dado un archivo index.js que se parece a esto:

require('a')
require('b')

Y un archivo node_modules.json en el mismo directorio:

{
  "a": "/some/completely/different/path"
}

Ahora, al ejecutar node index.js , Node leería en node_modules.json
archivo y arranque Module._cache con él.

Esto proporcionaría una ruta de migración fácil para los casos de uso que se basan en el antiguo
mecanismo (el archivo node_modules.json podría generarse fácilmente), mientras que
permitiendo posibles mejoras de rendimiento a través de alguna forma de estática
declaración de rutas requeridas resueltas (como en "no node_modules path fallback
o resolución de ruta real de enlace simbólico").

Este tipo de información también podría codificarse en package.json .
Pero la estrategia requerida estaría definida en el paquete que requiere algunos
dependencia, en lugar de en el archivo package.json de la dependencia misma (el
paquete requerido ).

... solo una idea que podría valer la pena investigar (potencialmente independiente de
esta discusión).

@kzc

La propuesta de prefijo _ y la idea de ruta absoluta están separadas.

Bajo la propuesta del prefijo '_', el siguiente enlace simbólico usaría el nuevo esquema de resolución o el antiguo realpath:

foo ---> /usr/local/foo

Al usar el enfoque _ , esta ruta se resolvería de acuerdo con el mecanismo v6.0.0, ya que el nombre del vínculo ( foo ) no comienza con un guión bajo.

Si el enlace se llamara _foo , se resolvería de acuerdo con el mecanismo de ruta real original.

Al usar la solución de ruta absoluta, este vínculo se resolvería de acuerdo con el mecanismo anterior a la versión 6.0.0 (ruta real).


Si el enlace simbólico apunta a una ruta absoluta, use el mecanismo anterior; de lo contrario, use el nuevo.
¿Podría aclarar el uso de "ruta absoluta" en esa declaración?

Absoluto como en path.absolute (comenzando con / relativo a la raíz del sistema de archivos).

Absoluto: /hello/world
No absoluto: hello/world

foo ---> /usr/local/foo

Al usar el enfoque _, esta ruta se resolvería de acuerdo con el mecanismo v6.0.0

Gracias por la aclaración. Solo quería verificar que el prefijo de subrayado es el único "decisivo" del comportamiento de resolución del módulo realpath o v6.0.0 en su propuesta de prefijo _ .

@alexanderGugel Creo que la propuesta de arranque node_modules.json es buena por varias razones:

  1. Es explícito y claro.
  2. No altera los nombres de los enlaces simbólicos.
  3. Tendrá el mismo rendimiento que la propuesta de prefijo _ .

Simplemente elimine las dependencias circulares. Todos tus problemas se han ido. (excepto de los quejosos abusadores de esta característica).

@domenkozar

Simplemente elimine las dependencias circulares. Todos tus problemas se han ido. (excepto de los quejosos abusadores de esta característica).

Las dependencias circulares no tienen nada que ver con la capacidad de vincular un paquete.

Además de eso, la mayoría de los paquetes altamente modularizados (por ejemplo, browserify) tienen dependencias circulares de una forma u otra. Y eso es totalmente genial. No hay ninguna razón por la que este caso de uso no deba admitirse.

(en general, no creo que sea constructivo llamar a la gente "quejumbrosa", solo mi opinión)

@kzc

@alexanderGugel Creo que la propuesta de arranque de node_modules.json es buena por varias razones:

  1. Es explícito y claro.

Yo diría que el prefijo de enlaces simbólicos con _ también es un buen indicador de que son de alguna manera "diferentes". Tenga en cuenta que la mayoría de los usuarios no crearían esos enlaces manualmente, sino que utilizarían herramientas como npm link , pnpm , npminstall o ied que crearlos

  1. No altera los nombres de los enlaces simbólicos.

Ese es un buen punto. Sin embargo, no estoy seguro de que sea una gran desventaja, ya que aún mantendríamos las cosas compatibles con versiones anteriores a v6.0.0.

  1. Tendrá el mismo rendimiento que la propuesta de prefijo _.

Tendrá un rendimiento un poco menor, porque ahora necesita leer y analizar el archivo node_modules.json para cada directorio en la ruta que podría contener enlaces simbólicos.

Las dependencias circulares no tienen nada que ver con la capacidad de vincular un paquete.

Todo este anidamiento es el resultado de dependencias circulares. De lo contrario, podría calcular un gráfico DAG de dependencias y listo. Tal como lo hacemos en Nix.

@domenkozar

Todo este anidamiento es el resultado de dependencias circulares. De lo contrario, podría calcular un gráfico DAG de dependencias y listo. Tal como lo hacemos en Nix.

Si bien me gusta Nix, no creo que este sea el lugar adecuado para despotricar sobre la forma en que se resuelven los módulos en general . Sugeriría presentar un nuevo problema que describa el comportamiento propuesto.

Aparentemente, el nodo usa la resolución de enlace simbólico (https://github.com/nodejs/node/issues/6500#issuecomment-216378313) para protegerse de cargar un módulo binario más de una vez.

Hasta que se encuentre otra solución para afirmar la identidad de un módulo en particular de alguna otra manera portátil (¡no es fácil!), esto no se puede resolver en este momento. (Habría habido dos rutas de código require diferentes para módulos binarios y de JavaScript). https://github.com/nodejs/node/issues/6500 es un buen ejemplo de cómo la implementación https://github.com/nodejs/node/pull/5950 falla en ese sentido.

Disculpas por llegar tarde aquí, pero ¿alguien puede decirnos lo que se discutió? Es un hilo largo, y no estoy seguro de cuál es la resolución en este momento.

Tl; Dr es esto: tuvimos un error en el cargador de módulos que impidió el enlace simbólico
dependencias entre pares de encontrarse unos a otros. Lo arreglamos pero la solución se rompió
otras cosas. Por lo tanto, actualmente estamos buscando revertir esa corrección y analizar
resolverlo de otra manera.
El 3 de mayo de 2016 a las 5:21 a. m., "Rico Sta. Cruz" [email protected] escribió:

Disculpas por llegar tarde aquí, pero ¿alguien puede decirnos lo que se discutió?
Es un hilo largo, y no estoy seguro de cuál es la resolución en este momento.


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/nodejs/node/issues/3402#issuecomment-216511065

¡Gracias!
El 3 de mayo de 2016 a las 9:04 p. m., "James M Snell" [email protected] escribió:

Tl; Dr es esto: tuvimos un error en el cargador de módulos que impidió el enlace simbólico
dependencias entre pares de encontrarse unos a otros. Lo arreglamos pero la solución se rompió
otras cosas. Por lo tanto, actualmente estamos buscando revertir esa corrección y analizar
resolverlo de otra manera.
El 3 de mayo de 2016 a las 5:21 a. m., "Rico Sta. Cruz" [email protected] escribió:

Disculpas por llegar tarde aquí, pero ¿alguien puede decirnos lo que se discutió?
Es un hilo largo, y no estoy seguro de cuál es la resolución en el
momento.


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/nodejs/node/issues/3402#issuecomment-216511065


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/nodejs/node/issues/3402#issuecomment-216520014

Las dependencias de @jasnell Peer eran solo una ventaja. El comportamiento de resolución del módulo 6.0.0 también facilita la prueba de módulos con enlaces simbólicos sin necesidad de copiar archivos o instalaciones. También permite el diseño de instalaciones personalizadas hechas a mano para ahorrar espacio en disco en dispositivos limitados. Ofrecía mucha flexibilidad de la que carece el antiguo esquema.

Sería útil si el comportamiento de resolución del módulo 6.0.0 pudiera existir detrás de un indicador de línea de comando predeterminado en falso en lugar de eliminarlo por completo.

Eso muy bien podría ser una opción, pero primero tenemos que desarmar las cosas, y
luego avanzar desde allí.

Definitivamente estoy considerando el enfoque de bandera para habilitar el nuevo comportamiento.
Ya comencé a explorar eso, de hecho, y no debería ser demasiado
difícil de hacer en absoluto. También me gustaría seguir investigando si
podemos resolverlo sin una bandera aunque.

@jasnell Gracias. Siempre es preferible una solución sin bandera. Pero incluso con la bandera sería muy útil.

Tl;Dr es esto: tuvimos un error en el cargador de módulos que impedía que las dependencias de pares enlazadas simbólicamente se encontraran entre sí. Lo arreglamos pero la solución rompió otras cosas. Por lo tanto, actualmente estamos buscando revertir esa solución y buscar resolverla de una manera diferente.

No estoy del todo de acuerdo en que esto sea un error.

Sin embargo, si vamos a decir que la ruta de búsqueda require() debe incluir las carpetas node_modules según la ubicación del enlace simbólico de un módulo, entonces no es aceptable _eliminar_ los node_modules carpetas basadas en la ubicación de ruta real de un módulo.

Aquí hay un repositorio de git con 2 ejemplos claros de lo que cambió, y por qué esto es sutil e ineficiente, o completamente dañino y sorprendente, según cómo se vincularon las dependencias del módulo: https://github.com/isaacs/node6-module -cambio de sistema

(Nota: he escrito programas que require() un módulo desde una ubicación con enlace simbólico y espero que aún pueda cargar sus dependencias, por lo que, si bien el ejemplo es artificial en su minimalidad, no es artificial en principio y refleja algún uso del mundo real.)

Una solución ideal, si el objetivo es que los módulos enlazados se encuentren entre sí si no dependen unos de otros (lo cual, de nuevo, soy muy escéptico acerca de que sea una buena idea) tendría que priorizar el realpath anterior a 6.0 -comportamiento de búsqueda basado en la ubicación como la ruta de búsqueda de primera prioridad, y _luego_ agregue las ubicaciones de búsqueda node_modules como un conjunto de rutas de prioridad más baja.

La entrada de caché aún debe basarse en la ruta real por motivos de eficiencia y minimizar el cambio semántico en lo que es un singleton.

Si se ve afectado por esto hoy, puede solucionar fácilmente el error configurando la variable de entorno NODE_PATH node_modules en la carpeta node_modules donde está pegando cosas.

Creo que sería una idea interesante agregar las rutas de búsqueda del módulo principal a las llamadas require() realizadas por otros módulos, pero incluso eso debería enviarse al frente y al centro como un cambio significativo y potencialmente peligroso.

Se actualizó el repositorio de git con algunas propuestas específicas.

@isaacs

si es un objetivo que los módulos enlazados se encuentren entre sí si no dependen unos de otros (lo cual, de nuevo, soy muy escéptico acerca de que sea una buena idea)

Bueno, con la creciente tendencia del enfoque monorepo y un impulso significativo a la velocidad de desarrollo, ese pensamiento merece el beneficio de la duda.

La vinculación simbólica se puede arreglar y funciona muy bien :)

Dado que este problema tiene la etiqueta _discutir_, pensé que podría preguntar aquí...

La segunda mejor cosa para poder hacer const WebSocket = require('../vendors/node/node_modules/ws'); (que no se puede hacer) es tener un directorio node_modules enlazado como se muestra a continuación.

¿Qué versión del comportamiento permitiría lo siguiente?

/var/www/project/
├── app
│   └── ws
│       ├── node_modules -> /var/www/project/vendors/node/node_modules
│       └── wsserver.js
└── vendors
    └── node
        ├── node_modules
        └── package.json

@ majid4466 enlace simbólico que el directorio node_modules debería funcionar con el nodo actual.

En cuanto a la pregunta de apertura original: dep1 probablemente no debería requerir dep2, sino proporcionar una fábrica que pueda producir cualquier cosa basada en dep2. La fábrica podría tener una propiedad de metadatos para declarar qué otros módulos debe proporcionar la aplicación (o un administrador de complementos). Es un tipo de inyección de dependencia .

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

Temas relacionados

Brekmister picture Brekmister  ·  3Comentarios

srl295 picture srl295  ·  3Comentarios

Icemic picture Icemic  ·  3Comentarios

vsemozhetbyt picture vsemozhetbyt  ·  3Comentarios

seishun picture seishun  ·  3Comentarios