Soporte para Package Exports en React Native
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Con el lanzamiento de React Native 0.72, Metro —nuestra herramienta de construcción JavaScript— ahora incluye soporte beta para el campo "exports" en package.json. Cuando se habilita, añade las siguientes funcionalidades:
-
Los proyectos de React Native funcionarán con más paquetes npm sin configuración adicional
-
Nuevas capacidades para que los paquetes definan su API y apunten a React Native
-
Algunos cambios que rompen compatibilidad en la resolución de paquetes (en casos extremos)
En esta publicación cubriremos cómo funciona Package Exports y qué significan estos cambios para ti como desarrollador de aplicaciones React Native o mantenedor de paquetes.
¿Qué es Package Exports?
Introducido en Node.js 12.7.0, Package Exports es el enfoque moderno para que los paquetes npm especifiquen puntos de entrada —el mapeo de subrutas del paquete que pueden importarse externamente y a qué archivo(s) deberían resolverse.
El soporte para "exports" mejora la integración de los proyectos React Native con el ecosistema JavaScript más amplio (usado en ~16.6k paquetes actualmente), y brinda a los autores de paquetes un conjunto de funciones estandarizadas para que los paquetes multiplataforma puedan apuntar a React Native.
"exports" puede usarse junto con, o en lugar de, "main" en un archivo package.json.
{
"name": "@storybook/addon-actions",
"main": "./dist/index.js",
...
"exports": {
".": {
"node": "./dist/index.js",
"import": "./dist/index.mjs",
"default": "./dist/index.js"
},
"./preview": {
"import": "./dist/preview.mjs",
"default": "./dist/preview.js"
},
...
"./package.json": "./package.json"
}
}
Aquí hay un ejemplo de código de aplicación que consume el paquete anterior importando diferentes subrutas de @storybook/addon-actions.
import {action} from '@storybook/addon-actions';
// -> '@storybook/addon-actions/dist/index.js'
import {action} from '@storybook/addon-actions/preview';
// -> '@storybook/addon-actions/dist/preview.js'
import helpers from '@storybook/addon-actions/src/preset/addArgsHelpers';
// Inaccessible - not listed in "exports"!
Las características principales de Package Exports son:
-
Encapsulación de paquetes: Solo las subrutas definidas en
"exports"pueden importarse desde fuera del paquete —dando a los paquetes control sobre su API pública. -
Alias de subrutas: Los paquetes pueden definir subrutas personalizadas que mapean a ubicaciones de archivo diferentes (incluyendo mediante patrones de subrutas) —permitiendo reubicar archivos mientras se mantiene la API pública.
-
Exportaciones condicionales: Una subruta puede resolverse a un archivo diferente según el entorno. Por ejemplo, para apuntar a entornos
"node","browser"o"react-native"—reemplazando la especificación del campo"browser".
Las capacidades completas de "exports" se detallan en la especificación de Puntos de Entrada de Paquetes de Node.js.
Dado que estas características se solapan con conceptos existentes en React Native (como las extensiones específicas de plataforma), y dado que "exports" ha estado activo en el ecosistema npm durante algún tiempo, contactamos a la comunidad de React Native para asegurarnos de que nuestra implementación satisfaría las necesidades de los desarrolladores (PR, RFC final).
Para desarrolladores de aplicaciones
Package Exports puede activarse hoy mismo, en versión beta.
-
Las importaciones de paquetes que dependen de características de Package Exports (como Firebase y Storybook) ahora deberían funcionar según lo diseñado.
-
Los proyectos de React Native for Web que usan Metro ahora podrán utilizar la exportación condicional
"browser", eliminando la necesidad de soluciones alternativas.
Habilitar Package Exports conlleva algunos cambios que rompen compatibilidad en casos límite que podrían afectar proyectos específicos, y que puedes probar hoy mismo.
En una futura versión de React Native, Package Exports estará habilitado por defecto. En una situación de huevo o gallina, las aplicaciones de React Native eran anteriormente un obstáculo para que algunos paquetes migraran a "exports" — o usaban nuestro campo raíz "react-native" como solución alternativa. El soporte de estas características en Metro permitirá que el ecosistema avance.
Habilitar Package Exports (beta)
Package Exports puede habilitarse en el archivo metro.config.js de tu aplicación mediante la opción resolver.unstable_enablePackageExports.
const config = {
// ...
resolver: {
unstable_enablePackageExports: true,
},
};
Metro expone dos opciones adicionales del resolver que configuran el comportamiento de las exportaciones condicionales:
-
unstable_conditionNames— El conjunto de nombres de condición a validar al resolver exportaciones condicionales. Por defecto, coincide con['require', 'import', 'react-native']. -
unstable_conditionsByPlatform— Nombres de condición adicionales a validar al resolver para una plataforma objetivo. Por defecto, coincide con'browser'cuando la plataforma es'web'.
¡Recuerda usar el preset de Jest para React Native! Jest incluye soporte para Package Exports por defecto. En las pruebas, puedes anular qué customExportConditions se resuelven usando la opción testEnvironmentOptions.
Si estás usando TypeScript, el comportamiento de resolución puede coincidirse configurando moduleResolution: 'bundler' y resolvePackageJsonImports: false dentro del tsconfig.json de tu proyecto.
Validar cambios en tu proyecto
Para proyectos existentes, recomendamos que los primeros adoptantes sigan estos pasos para ver si ocurren cambios de resolución después de habilitar unstable_enablePackageExports. Este es un proceso único. Es probable que no haya cambios en absoluto, pero queremos que los desarrolladores opten por activarlo con certeza.
💡 Validating changes in your project
If you are not using Yarn, substitute yarn for npx (or the relevant tool used in your project).
-
Get all resolved dependencies (before changes):
# Replace index.js with your entry file if needed, such as App.js
yarn metro get-dependencies index.js --platform android --output before.txt- Expo CLI: Run
npx expo customize metro.config.jsif your project doesn't have ametro.config.jsfile yet. - For full coverage, substitute
--platform androidfor the other platforms in use by your app (e.g.ios,web).
- Expo CLI: Run
-
Enable
resolver.unstable_enablePackageExportsinmetro.config.js. -
Get all resolved dependencies (after changes):
yarn metro get-dependencies index.js --platform android --output after.txt -
Compare!
diff before.txt after.txt
Cambios importantes
Decidimos una implementación de Package Exports en Metro que cumple con la especificación (lo que requiere algunos cambios que rompen compatibilidad), pero que por lo demás es retrocompatible (ayudando a las aplicaciones con importaciones existentes a migrar gradualmente).
El cambio clave que rompe compatibilidad es que cuando un paquete proporciona "exports", este se consultará primero (antes que otros campos de package.json) —y se usará directamente el objetivo de subruta coincidente.
-
Metro no expandirá
sourceExtscontra el especificador de importación. -
Metro no resolverá las extensiones específicas de plataforma contra el archivo de destino.
Para más detalles, consulta todos los cambios que rompen compatibilidad en la documentación de Metro.
La encapsulación de paquetes es permisiva
Cuando Metro encuentra una subruta no listada en "exports", recurrirá a la resolución heredada. Esta es una función de compatibilidad para reducir la fricción en importaciones permitidas anteriormente en proyectos existentes de React Native.
En lugar de lanzar un error, Metro registrará una advertencia.
warn: You have imported the module "foo/private/fn.js" which is not listed in
the "exports" of "foo". Consider updating your call site or asking the package
maintainer(s) to expose this API.
Planeamos implementar un modo estricto para la encapsulación de paquetes en el futuro, alineándonos con el comportamiento predeterminado de Node. Por lo tanto, recomendamos que todos los desarrolladores aborden estas advertencias si los usuarios las reportan.
Para mantenedores de paquetes (vista previa)
Según nuestro plan de implementación, Package Exports estará habilitado para la mayoría de proyectos en la próxima versión de React Native (0.73) este año.
No tenemos planes de eliminar la compatibilidad con el campo "main" ni otras funciones actuales de resolución de paquetes a corto plazo.
Package Exports permite restringir el acceso a los internos de tu paquete y ofrece capacidades más predecibles para que las bibliotecas apunten a React Native y React Native for Web.
Si ya usas "exports" actualmente
Si tu paquete usa "exports" junto con el campo raíz "react-native", considera los cambios que rompen compatibilidad mencionados. Para usuarios que habiliten esta función en Metro, "exports" tendrá prioridad durante la resolución de módulos.
En la práctica, anticipamos que el cambio principal será la aplicación (vía advertencias) de subrutas inaccesibles en sus apps, al respetar la encapsulación de "exports".
Migración a "exports"
Agregar un campo "exports" a tu paquete es completamente opcional. Las funciones existentes de resolución de paquetes se comportarán idénticamente para paquetes que no usen "exports" —y no tenemos planes de eliminar este comportamiento.
Creemos que las nuevas funciones de "exports" ofrecen un conjunto de capacidades convincentes para mantenedores de paquetes de React Native.
-
Refuerza tu API de paquete: Es buen momento para revisar la API de módulos de tu paquete, que ahora puede definirse formalmente mediante alias de subrutas exportados. Esto evita que usuarios accedan a APIs internas, reduciendo la superficie para errores.
-
Exportaciones condicionales: Si tu paquete apunta a React Native for Web (ej.
"react-native"y"browser"), ahora puedes controlar el orden de resolución de estas condiciones (ver siguiente encabezado).
Si decides implementar "exports", recomendamos tratarlo como un cambio que rompe compatibilidad. Preparamos una guía de migración en la documentación de Metro que incluye cómo reemplazar funciones como extensiones específicas de plataforma.
No dependas de los comportamientos permisivos de la implementación de Metro. Aunque Metro es retrocompatible, los paquetes deben seguir cómo "exports" está documentado en la especificación e implementado estrictamente por otras herramientas.
La nueva condición "react-native"
Introdujimos "react-native" como condición comunitaria (para usar con exportaciones condicionales). Representa React Native como framework, junto a otros entornos reconocidos como "node" y "deno" (RFC).
Definiciones de Condiciones Comunitarias — "react-native"
Será coincidido por el framework React Native (todas las plataformas). Para apuntar a React Native for Web, debe especificarse "browser" antes de esta condición.
Esto reemplaza el campo raíz "react-native" anterior. El orden de prioridad para su resolución previa lo determinaban los proyectos, lo que creaba ambigüedad al usar React Native for Web. Bajo "exports", los paquetes definen concretamente el orden de resolución para puntos de entrada condicionales —eliminando esta ambigüedad.
"exports": {
"browser": "./dist/index-browser.js",
"react-native": "./dist/index-react-native.js",
"default": "./dist/index.js"
}
Optamos por no introducir condiciones "android" e "ios" debido a la prevalencia de otros métodos de selección de plataforma existentes y la complejidad de cómo podría funcionar este comportamiento entre frameworks. Utilice en su lugar la API Platform.select().
El futuro: "exports" estable, habilitado por defecto
En la próxima versión de React Native, nuestro objetivo es eliminar el prefijo unstable_ para esta función (tras abordar el trabajo de rendimiento planificado y cualquier error) y habilitaremos la resolución de Exportaciones de Paquetes por defecto.
Con "exports" habilitado para todos, podemos avanzar con la comunidad de React Native; por ejemplo, los paquetes principales de React Native podrían actualizarse para separar mejor los módulos públicos e internos.

Agradecimientos
Agradecemos a los miembros de la comunidad de React Native que brindaron comentarios sobre el RFC: @SimenB, @tido64, @byCedric, @thymikee.
Un enorme agradecimiento a @motiz88 y @robhogan en Meta por apoyar el desarrollo de esta función.
