Saltar al contenido principal

Organiza el código Dart con paquetes y librerías

Aprende cómo organizar tu código Dart en librerías y paquetes reutilizables.

Ver en YouTube en una nueva pestaña: "The Dart platform"

En este capítulo, pasarás de la sintaxis básica de Dart a construir aplicaciones de línea de comandos "al estilo Dart", adoptando las buenas prácticas. Aprenderás a refactorizar tu código en componentes reutilizables creando un paquete dedicado para manejar los argumentos de línea de comandos.

Este paso te prepara para construir una aplicación de línea de comandos más avanzada en los próximos capítulos, que integrará paquetes especializados para la lógica de Wikipedia y un framework de command_runner robusto. Este capítulo te ayuda a entender las librerías de Dart, las sentencias de exportación y cómo estructurar tu proyecto para una mejor organización y mantenibilidad.

Qué lograrás

Crear un paquete Dart reutilizable
Usar exportaciones para definir una API pública
Agregar dependencias de ruta local (path dependencies)

Prerrequisitos

#
  • Haber completado el Capítulo 3, que cubrió programación asíncrona y solicitudes HTTP.

Tareas

#

En este capítulo, refactorizarás la aplicación de CLI dartpedia existente extrayendo la lógica de parseo de argumentos de línea de comandos en un paquete separado llamado command_runner. Esto mejorará la estructura de tu proyecto, haciéndolo más modular y mantenible.

Tarea 1: Crear el paquete command_runner

#

Primero, crea un nuevo paquete Dart para albergar la lógica de parseo de argumentos de línea de comandos.

  1. Navega al directorio raíz de tu proyecto (/dartpedia).

  2. Ejecuta el siguiente comando en tu terminal:

    bash
    dart create -t package command_runner
    

    Este comando crea un nuevo directorio llamado command_runner con la estructura básica de un paquete Dart. Ahora deberías ver una nueva carpeta command_runner en la raíz de tu proyecto, junto con tu paquete cli.

Tarea 2: Implementar la clase CommandRunner

#

Ahora que has creado el paquete command_runner, añade una clase de marcador de posición (placeholder) que eventualmente manejará la lógica de parseo de argumentos de línea de comandos.

  1. Abre el archivo command_runner/lib/command_runner.dart. Elimina cualquier código de marcador de posición existente y agrega lo siguiente:

    dart
    /// A simple command runner to handle command-line arguments.
    ///
    /// More extensive documentation for this library goes here.
    library;
    
    export 'src/command_runner_base.dart';
    // TODO: Export any other libraries intended for clients of this package.
    

    Aspectos destacados del código anterior:

    • library; declara este archivo como una librería, lo que define los límites y la interfaz pública de una unidad reutilizable de código Dart.
    • export 'src/command_runner_base.dart'; es una línea crucial que hace que las declaraciones de command_runner_base.dart estén disponibles para otros paquetes que importen el paquete command_runner. Sin esta sentencia export, las clases y funciones dentro de command_runner_base.dart serían privadas para el paquete command_runner, y no podrías usarlas en tu aplicación dartpedia.
  2. Abre el archivo command_runner/lib/src/command_runner_base.dart.

  3. Elimina cualquier código de marcador de posición existente y añade la siguiente clase CommandRunner a command_runner/lib/src/command_runner_base.dart:

    dart
    class CommandRunner {
      /// Runs the command-line application logic with the given arguments.
      Future<void> run(List<String> input) async {
        print('CommandRunner received arguments: $input');
      }
    }
    

    Aspectos destacados del código anterior:

    • CommandRunner es una clase que sirve como un sustituto simplificado por ahora. Su método run actualmente solo imprime los argumentos que recibe. En capítulos posteriores, expandirás esta clase para manejar un parseo de comandos complejo y configurable.
    • El tipo de retorno Future<void> indica que este método podría realizar operaciones asíncronas, pero no devuelve un valor.

Tarea 3: Agregar command_runner como una dependencia

#

Ahora que has creado el paquete command_runner y añadido una clase CommandRunner de marcador de posición, necesitas decirle a tu aplicación cli que depende de command_runner. Debido a que el paquete command_runner está ubicado localmente dentro de tu proyecto, puedes usar una dependencia de ruta (path dependency).

  1. Abre el archivo cli/pubspec.yaml.

  2. Debido a que el paquete cli es un paquete de aplicación y no está destinado a ser publicado en pub.dev, añade publish_to: none después del campo name. Esto ayuda a evitar la publicación accidental y previene advertencias del analyzer al añadir dependencias de ruta.

    yaml
    name: cli
    publish_to: none
    # ...
    
  3. Localiza la sección dependencies. Agrega las siguientes líneas:

    yaml
    dependencies:
      http: ^1.3.0 # Keep your existing http dependency
      command_runner:
        path: ../command_runner # Points to your local command_runner package
    

    Esta sección le indica a la aplicación cli que depende del paquete command_runner, y especifica que el paquete está ubicado en el directorio ../command_runner (relativo al directorio cli).

  4. Ejecuta dart pub get en el directorio /dartpedia/cli de tu terminal para obtener la nueva dependencia.

Tarea 4: Importar y usar el paquete command_runner

#

Ahora que has agregado command_runner como una dependencia, puedes importarlo en tu aplicación cli y reemplazar tu lógica de manejo de argumentos existente con la nueva clase CommandRunner. Este paso también corrige el comportamiento de salida del programa discutido al final del Capítulo 3.

  1. Abre el archivo cli/bin/cli.dart.

  2. Agrega la siguiente sentencia de importación en la parte superior del archivo, junto con tus otras importaciones:

    dart
    import 'package:command_runner/command_runner.dart';
    

    Esta sentencia importa el paquete command_runner, haciendo que la clase CommandRunner esté disponible para su uso.

  3. Refactoriza la función main y elimina la lógica antigua: Actualmente, tu función main del Capítulo 3 maneja directamente comandos como version, help y wikipedia, y luego llama a searchWikipedia. Ahora reemplazarás toda esta lógica personalizada de manejo de comandos con una sola llamada a la nueva clase CommandRunner.

    Tu archivo cli/bin/cli.dart (del Capítulo 3) debería verse así actualmente:

    dart
    import 'dart:io';
    import 'package:http/http.dart' as http;
    import 'package:command_runner/command_runner.dart';
    
    const version = '0.0.1';
    
    void main(List<String> arguments) {
      if (arguments.isEmpty || arguments.first == 'help') {
        printUsage();
      } else if (arguments.first == 'version') {
        print('Dartpedia CLI version $version');
      } else if (arguments.first == 'wikipedia') {
        final inputArgs = arguments.length > 1 ? arguments.sublist(1) : null;
        searchWikipedia(inputArgs);
      } else {
        printUsage();
      }
    }
    
    void searchWikipedia(List<String>? arguments) async { /* ... existing logic ... */ }
    void printUsage() { /* ... existing logic ... */ }
    Future<String> getWikipediaArticle(String articleTitle) async { /* ... existing logic ... */ }
    

    **Ahora, reemplaza todo el contenido de cli/bin/cli.dart (excepto la importación de http) con la siguiente versión actualizada:

    dart
    import 'dart:io';
    import 'package:http/http.dart' as http;
    import 'package:command_runner/command_runner.dart';
    
    void main(List<String> arguments) async { // main is now async and awaits the runner
      var runner = CommandRunner(); // Create an instance of your new CommandRunner
      await runner.run(arguments); // Call its run method, awaiting its Future<void>
    }
    

    Aspectos destacados del código anterior:

    • void main(List<String> arguments) async aborda directamente el problema de que el programa no finalizaba limpiamente del Capítulo 3. Nota que main ahora está declarada como async. This es esencial porque runner.run() devuelve un Future, y main debe hacer await de su completación para asegurar que el programa espere a que todas las tareas asíncronas terminen antes de salir.
    • var runner = CommandRunner(); crea una instancia de la clase CommandRunner de tu nuevo paquete command_runner.
    • await runner.run(arguments); llama al método run en la instancia de CommandRunner, pasando los argumentos de la línea de comandos.

    Funciones eliminadas:

    • Las funciones printUsage, searchWikipedia y getWikipediaArticle ahora están completamente eliminadas de cli/bin/cli.dart. Su lógica se rediseñará y se moverá al paquete command_runner en futuros capítulos, como parte de la construcción del framework completo de línea de comandos.

Tarea 5: Ejecutar la aplicación

#

Ahora que has refactorizado el código y actualizado la aplicación cli para usar el paquete command_runner, ejecuta la aplicación para verificar que todo esté funcionando correctamente en esta etapa.

  1. Abre tu terminal y navega al directorio cli.

  2. Ejecuta el comando wikipedia:

    bash
    dart run bin/cli.dart wikipedia Computer_programming
    
  3. Asegúrate de que la aplicación ahora se ejecute sin errores e imprima los argumentos en la consola, demostrando que el control se ha transferido con éxito a tu nuevo paquete command_runner.

    bash
    CommandRunner received arguments: [wikipedia, Computer_programming]
    

Revisión

#

Qué lograste

Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.
Creaste un paquete Dart reutilizable

Usaste dart create -t package command_runner para estructurar un nuevo paquete de librería destinado a ser utilizado en otros paquetes. Los paquetes de librería son la forma estándar de organizar y compartir librerías de Dart reutilizables, ya sea dentro de un workspace de proyecto o publicados en pub.dev.

Usaste exportaciones para definir una API pública

Añadiste sentencias export en tu archivo de librería para exponer librerías específicas y sus definiciones a los consumidores. Si no se exporta desde archivos en el directorio raíz lib, el código en lib/src/ permanece privado para el paquete. Esto te permite diseñar cuidadosamente una API pública para otros, mientras mantienes un control total sobre los detalles de implementación.

Agregaste dependencias de ruta local

Configuraste pubspec.yaml para depender de un paquete local usando una dependencia de ruta. Esto permite proyectos multipaquete donde los paquetes pueden evolucionar juntos antes de ser publicados por separado.

Cuestionario

#

Pon a prueba tu comprensión

1 / 3
What is the purpose of the export statement in a Dart library?
  1. Para hacer que las declaraciones de otros archivos estén disponibles a través de la API pública de tu librería.

    ¡Correcto!

    Exportar te permite volver a exponer declaraciones. Por ejemplo, export 'src/command.dart'; hace que Command sea accesible para cualquiera que importe tu paquete.

  2. Para importar múltiples librerías con una sola sentencia.

    No exactamente.

    import e export sirven para propósitos diferentes. import hace que el código esté disponible para tu librería, mientras que export hace algo para los consumidores de tu librería.

  3. Para ocultar detalles de implementación de otras librerías.

    No exactamente.

    La sentencia export hace lo contrario a ocultar. Para ocultar miembros que son solo de implementación, no los incluyas en ninguna sentencia export.

  4. Para especificar qué versiones del SDK de Dart soporta tu paquete.

    No exactamente.

    Las restricciones de SDK van en pubspec.yaml, no en el código Dart. export es una característica del lenguaje Dart, no una opción de configuración.

How do you add a local package (one on your filesystem) as a dependency?
  1. Usar una restricción de versión como command_runner: ^1.0.0.

    No exactamente.

    Las restricciones de versión son para paquetes publicados en pub.dev. Los paquetes locales requieren un tipo diferente de especificación de dependencia.

  2. Usar una dependencia de ruta (path dependency) como command_runner: {path: ../command_runner}.

    ¡Correcto!

    Las dependencias de ruta (path dependencies) apuntan a un directorio local. Esto es perfecto para proyectos multipaquete o para desarrollar paquetes antes de publicarlos.

  3. Copiar la carpeta del paquete en el directorio lib/ de tu proyecto.

    No exactamente.

    Copiar crea duplicación y problemas de versiones. Dart proporciona una forma de referenciar paquetes por su ubicación sin copiarlos.

  4. Ejecutar dart pub add command_runner --local

    No exactamente.

    No hay una bandera --local para dart pub add. Debes especificar la ubicación de manera diferente en pubspec.yaml o pasando un descriptor de paquete a dart pub add.

A package has code in lib/src/parser.dart. Why might you create lib/parser.dart that just contains export 'src/parser.dart';?
  1. Para permitir a los usuarios importar desde una ruta limpia mientras se mantienen los archivos de implementación organizados en src/.

    ¡Correcto!

    Los usuarios pueden escribir import 'package:mylib/parser.dart'; en lugar de buscar dentro de src/. Esto separa tu API pública de la organización interna.

  2. Para hacer que el código se ejecute más rápido al reducir la profundidad de la importación.

    No exactamente.

    Exportar no afecta al rendimiento en tiempo de ejecución. El número de directorios en una ruta de importación no tiene impacto en la velocidad del código.

  3. Porque Dart requiere que todos los archivos públicos estén en el directorio raíz lib/.

    No exactamente.

    Dart no requiere esta estructura. Es una convención, no un requisito del lenguaje.

  4. Para evitar que otros paquetes importen los archivos de src/ directamente.

    No exactamente.

    Técnicamente, los archivos en src/ aún se pueden importar directamente. Las buenas prácticas de Dart lo desaconsejan, pero no lo impiden.

Siguiente lección

#

En el próximo capítulo, te sumergirás en los conceptos de la programación orientada a objetos (OOP) en Dart. Aprenderás cómo crear clases, definir relaciones de herencia y construir un framework de parseo de argumentos de línea de comandos más robusto utilizando principios de OOP dentro de tu nuevo paquete command_runner.