패키지 만들기

패키지 만들기 (Creating packages)

Dart 생태계는 라이브러리나 도구 같은 소프트웨어를 공유하기 위해 패키지를 사용해요. 이 페이지에서는 표준 공유 패키지(Package — Dart 라이브러리와 리소스, 그리고 그것들을 설명하는 pubspec.yaml 파일을 담은 디렉터리)를 만드는 방법을 알려드릴게요.

출처: Creating packages

본문

새 패키지 만들기

패키지의 초기 디렉터리와 구조를 만들려면 dart create 명령어와 package 템플릿을 사용해요.

$ dart create -t package <PACKAGE_NAME>

사용 가능한 템플릿과 -t 플래그 사용법을 더 알아보려면 dart create 문서를 참고하세요.

패키지를 구성하는 것

다음 다이어그램은 패키지의 가장 단순한 배치를 보여줘요.

라이브러리의 최소 요구 사항은 두 가지예요.

  • pubspec 파일: 라이브러리의 pubspec.yaml 파일은 애플리케이션 패키지(Application package — 실행 가능한 애플리케이션을 담은 Dart 패키지)와 같아요. 패키지가 라이브러리임을 나타내는 특별한 지정은 없어요.
  • lib 디렉터리: 예상할 수 있듯이 라이브러리 코드는 lib 디렉터리 아래에 있고, 다른 패키지에 공개되어요. 필요에 따라 lib 아래에 어떤 계층 구조든 만들 수 있어요. 관례상 구현 코드는 lib/src 아래에 둬요. lib/src 아래의 코드는 비공개로 간주되며, 다른 패키지가 src/...를 import할 필요가 없어야 해요. lib/src 아래의 API를 공개하려면 lib 바로 아래의 파일에서 lib/src 파일을 export할 수 있어요.

패키지 구성

작고 개별적인 라이브러리(미니 라이브러리라고 해요)를 만들면 패키지를 유지보수하고 확장하고 테스트하기가 가장 쉬워요. 대부분의 경우 두 클래스가 긴밀하게 결합된 상황이 아니라면 각 클래스는 자체 미니 라이브러리에 두는 게 좋아요.

lib 바로 아래에 lib/.dart라는 "메인" 라이브러리 파일을 만들고 모든 공개 API를 export해요. 그러면 사용자가 한 파일만 import해서 라이브러리 전체 기능을 얻을 수 있게 돼요.

lib 디렉터리에는 import 가능한 다른 비-src 라이브러리도 포함될 수 있어요. 예를 들어 메인 라이브러리가 여러 플랫폼에서 동작하지만, dart:iodart:js_interop에 의존하는 별도의 라이브러리를 만들 수도 있죠. 어떤 패키지는 메인 라이브러리와 달리 접두사(prefix)를 붙여 import하도록 만들어진 별도 라이브러리를 갖기도 해요.

실제 패키지인 shelf의 구성을 살펴볼게요. shelf 패키지는 Dart로 웹 서버를 쉽게 만들 수 있는 방법을 제공하며, Dart 패키지에서 흔히 쓰이는 구조로 배치되어 있어요.

lib 바로 아래의 메인 라이브러리 파일 shelf.dartlib/src의 여러 파일에서 API를 export해요. 의도한 것보다 많은 API를 노출하지 않도록 — 그리고 개발자에게 패키지 전체 공개 API의 개요를 주기 위해 — shelf.dartshow를 사용해 정확히 어떤 심볼을 export할지 지정해요.

// lib/shelf.dart
export 'src/cascade.dart' show Cascade;
export 'src/handler.dart' show Handler;
export 'src/hijack_exception.dart' show HijackException;
export 'src/middleware.dart' show Middleware, createMiddleware;
export 'src/middleware/add_chunked_encoding.dart' show addChunkedEncoding;
export 'src/middleware/logger.dart' show logRequests;
export 'src/middleware_extensions.dart' show MiddlewareExtensions;
export 'src/pipeline.dart' show Pipeline;
export 'src/request.dart' show Request;
export 'src/response.dart' show Response;
export 'src/server.dart' show Server;
export 'src/server_handler.dart' show ServerHandler;

shelf 패키지에는 미니 라이브러리인 shelf_io도 포함되어 있어요. 이 어댑터는 dart:ioHttpRequest 객체를 처리해요.

라이브러리 파일 import하기

다른 패키지의 라이브러리 파일을 import할 때는 package: 지시자를 사용해 그 파일의 URI를 지정해요.

import 'package:utilities/utilities.dart';

내 패키지의 라이브러리 파일을 import할 때는 두 파일 모두 lib 안에 있거나 둘 다 lib 밖에 있으면 상대 경로를 사용해요. import하는 파일이 lib 안에 있고 import하는 쪽이 lib 밖에 있다면 package:를 사용해요.

다음 다이어그램은 lib과 web 양쪽에서 lib/foo/a.dart를 import하는 방법을 보여줘요.

라이브러리 파일 조건부 import · export

라이브러리가 여러 플랫폼을 지원한다면 라이브러리 파일을 조건부로 import하거나 export해야 할 수도 있어요. 일반적인 사례는 웹과 네이티브 플랫폼을 모두 지원하는 라이브러리예요.

조건부로 import/export하려면 dart:* 라이브러리의 존재 여부를 확인해야 해요. dart:iodart:js_interop의 존재를 확인하는 조건부 export 코드 예시는 이렇게 생겼어요.

// lib/hw_mp.dart
export 'src/hw_none.dart' // Stub implementation
    if (dart.library.io) 'src/hw_io.dart' // dart:io implementation
    if (dart.library.js_interop) 'src/hw_web.dart'; // package:web implementation

이 코드가 하는 일을 정리하면 이래요.

  • dart:io를 사용할 수 있는 앱(예: 커맨드라인 앱)에서는 src/hw_io.dart를 export해요.
  • dart:js_interop을 사용할 수 있는 앱(웹 앱)에서는 src/hw_web.dart를 export해요.
  • 그 외에는 src/hw_none.dart를 export해요.

파일을 조건부로 import하려면 위 코드와 같되 exportimport로 바꾸면 돼요.

조건부로 export되는 모든 라이브러리는 같은 API를 구현해야 해요. 예를 들어 dart:io 구현은 이렇게 생겼어요.

// lib/src/hw_io.dart
import 'dart:io';

void alarm([String? text]) {
  stderr.writeln(text ?? message);
}

String get message => 'Hello World from the VM!';

그리고 UnsupportedError를 던지는 스텁(stub)을 사용하는 기본 구현은 이렇게 생겼어요.

// lib/src/hw_none.dart
void alarm([String? text]) => throw UnsupportedError('hw_none alarm');

String get message => throw UnsupportedError('hw_none message');

어떤 플랫폼에서든 조건부 export 코드가 있는 라이브러리를 import할 수 있어요.

import 'package:hw_mp/hw_mp.dart';

void main() {
  print(message);
}

추가 파일 제공하기

잘 설계된 패키지는 테스트하기 쉬워요. test 패키지를 사용해 테스트를 작성하고, 테스트 코드는 패키지 최상위의 test 디렉터리에 두는 걸 권장해요.

공개용으로 만든 커맨드라인 도구가 있다면 그것들을 공개되는 bin 디렉터리에 두세요. dart pub global activate를 사용해 커맨드라인에서 도구를 실행할 수 있게 해요. pubspec의 executables 섹션에 도구를 나열하면 사용자가 dart pub global run을 호출하지 않고도 직접 실행할 수 있어요.

라이브러리를 사용하는 예시를 포함하는 것도 도움이 돼요. 이것은 패키지 최상위의 example 디렉터리에 넣어요.

개발 중에 만들었지만 공개용이 아닌 도구나 실행 파일은 tool 디렉터리에 넣어요.

README.mdCHANGELOG.md처럼 라이브러리를 pub.dev 사이트에 게시할 때 필요한 다른 파일들은 Publishing a package에서 설명해요. 패키지 디렉터리를 구성하는 방법에 대한 더 자세한 내용은 pub package layout conventions을 참고하세요.

라이브러리 문서화

dart doc 도구를 사용해 라이브러리의 API 문서를 생성할 수 있어요. dart doc/// 문법을 사용하는 문서 주석(documentation comment)을 소스에서 찾아 파싱해요.

/// The event handler responsible for updating the badge in the UI.
void updateBadge() {
  ...
}

생성된 문서의 예시는 shelf 문서를 참고하세요.

생성된 문서에 라이브러리 수준의 문서를 포함하려면 library 지시자를 추가하고 그 바로 위에 주석을 붙여요. 라이브러리를 문서화하는 방법과 이유에 대해서는 Effective Dart: Documentation을 참고하세요.

오픈소스 라이브러리 배포

라이브러리가 오픈소스라면 pub.dev 사이트에서 공유하는 것을 권장해요. 라이브러리를 게시하거나 업데이트하려면 pub publish를 사용해요. 이 명령어는 패키지를 업로드하고 그 페이지를 만들거나 업데이트해요. 예시는 shelf 패키지 페이지를 참고하세요.

게시를 위해 패키지를 준비하는 방법에 대한 자세한 내용은 Publishing a package을 참고하세요.

pub.dev 사이트는 패키지를 호스팅할 뿐 아니라 패키지의 API 참조 문서를 생성하고 호스팅해요. 최신 생성 문서에 대한 링크는 패키지의 About 상자에 있어요. 예를 들어 shelf 패키지의 API 문서를 참고하세요. 이전 버전 문서에 대한 링크는 패키지 페이지의 Versions 탭에 있어요.

패키지의 API 문서가 pub.dev 사이트에서 잘 보이도록 하려면 다음 단계를 따라보세요.

  • 패키지를 게시하기 전에 dart doc 도구를 실행해서 문서가 문제없이 생성되고 예상대로 보이는지 확인해요.
  • 패키지를 게시한 후 Versions 탭을 확인해서 문서가 성공적으로 생성되었는지 확인해요.
  • 문서가 전혀 생성되지 않았다면 Versions 탭에서 failed를 클릭해 dart doc 출력을 확인해요.

리소스

패키지에 대해 더 알아보는 데 쓰는 리소스들이에요.

더 알아보기