과거의 JS interop

과거의 JS interop

Dart의 이전 JS interop 지원에 대한 아카이브예요.

출처: Past JS interop

본문

JavaScript interop의 진화

Dart 3.3은 JS interop의 새로운 세대를 도입해서, Dart 코드 안에서 JavaScript와 브라우저 기능에 접근하는 통합된 기능·API 세트를 제공해요. 이 현대적인 접근 방식은 개발자 경험을 향상시키고 WebAssembly(Wasm) 지원을 가능하게 하여 Dart를 웹의 미래에 맞춰 줘요.

다음 표는 Dart의 새 JS 및 웹 interop 솔루션을 이전 대응물에 매핑해 줘요:

새 interop 라이브러리 레거시 라이브러리
package:web dart:html, dart:indexed_db, dart:svg, dart:web_audio, dart:web_gl
dart:js_interop, dart:js_interop_unsafe package:js, dart:js, dart:js_util

Dart용 JS interop의 이전 버전들은 레거시로 간주되며 Dart 3.7(2025년 2월)부터 deprecated예요. 앞으로는 dart:js_interop을 사용하는 걸 선호하고, 가능하면 이전 interop 라이브러리의 사용처를 마이그레이션해요. dart:html 같은 브라우저 API 지원은 이제 package:web가 담당해요.

dart:js

dart:js는 JS 객체와 상호 운용하기 위한 구체적인 object wrapper를 노출했어요. 이 wrapper는 감싸진 JS 객체의 프로퍼티를 동적으로 가져오고, 설정하고, 호출하는 문자열 기반 메서드를 포함했어요. 감싸는 비용 때문에 성능이 덜 좋았고 사용하기에도 더 어려웠어요. 예를 들어 interop 멤버를 선언할 수 없고 문자열에 의존했기 때문에 코드 완성(code-completion)이 되지 않았죠. allowInterop 같은 dart:js에서 노출된 많은 기능은 나중에 다른 interop 라이브러리를 통해 다시 노출됐어요.

이 라이브러리는 package:jsdart:js_util이 출시된 이후로 레거시 상태예요.

package:js

package:js는 interop 타입과 멤버를 선언하는 기능을 도입했어요. 사용자들이 interop extension type 대신 interop 클래스를 작성할 수 있게 해 줬죠. 런타임에 이 클래스들은 dart:js_interopJSObject와 유사한 타입으로 소거됐어요.

@JS()
class JSType {}

package:js 사용자라면 dart:js_interop의 문법과 의미가 익숙할 거예요. 클래스 정의를 extension type으로 바꾸고 많은 경우 그대로 동작하게 함으로써 dart:js_interop으로 마이그레이션할 수 있을지도 몰라요.

다만 중대한 차이점들이 있어요:

  • package:js 타입은 브라우저 API와 interop하는 데 사용할 수 없었어요. dart:js_interop 타입은 사용할 수 있어요.
  • package:js는 dynamic dispatch를 허용했어요. 즉 package:js 타입을 dynamic으로 캐스트하고 그 위에서 interop 멤버를 호출하면 올바른 멤버로 전달됐어요. 이제 dart:js_interop에서는 더 이상 불가능해요.
  • package:js@JSexternal 멤버의 반환 타입이 검사되지 않았기 때문에 건전성(soundness) 보장이 없었어요. dart:js_interop은 건전해요.
  • package:js 타입은 인스턴스 멤버의 이름을 바꾸거나 external이 아닌 멤버를 가질 수 없었어요.
  • package:js 타입은 non-interop 클래스의 하위 타입이 될 수도 있고 상위 타입이 될 수도 있었어요. 이것은 자주 모의(mock)에 사용됐어요. dart:js_interop에서는 JS 객체를 대신 바꿔치기해서 모의를 해요. mocking 튜토리얼을 참고해요.
  • @anonymous 타입은 객체 리터럴 생성자를 가진 interop 타입을 선언하는 방법이었어요. dart:js_interop은 타입을 그렇게 구분하지 않으며, 어떤 external named-argument 생성자든 객체 리터럴 생성자예요.

@staticInterop

package:js@JS@anonymous와 함께 나중에 @staticInterop을 노출했는데, 이는 interop extension type의 프로토타입이었어요. 이는 dart:js_interop만큼 표현력이 있고 제한적이며, extension type이 나올 때까지의 과도기 문법으로 의도됐어요.

@staticInterop 타입은 암시적으로 JSObject로 소거됐어요. 모든 인스턴스 멤버를 extension에서 선언해서 정적 의미론만 사용할 수 있게 요구했고, 더 강한 건전성 보장을 가졌어요. 사용자가 브라우저 API와 상호 작용하는 데 사용할 수 있었고, 이름 변경과 external이 아닌 멤버 같은 것도 허용했어요. interop extension type처럼 dynamic dispatch는 지원하지 않았어요.

@staticInterop 클래스는 거의 항상 클래스를 extension type으로 바꾸고 어노테이션을 제거하는 것만으로 interop extension type으로 마이그레이션할 수 있어요.

dart:js_interop은 extension type이 언어에 추가될 때까지 정적 interop 의미론을 지원하기 위해 @staticInterop(그리고 @anonymous@staticInterop도 함께 사용할 때만)을 노출했어요. 이제 그런 모든 타입은 extension type으로 마이그레이션되어야 해요.

dart:js_util

dart:js_utilpackage:js 타입에서 선언할 수 없거나 값을 주고받는 데 필요한 많은 유틸리티 함수를 제공했어요. 여기에는 다음과 같은 멤버가 포함됐어요:

  • allowInterop (지금은 Function.toJS예요)
  • getProperty/setProperty/callMethod/callConstructor (지금은 dart:js_interop_unsafe에 있어요)
  • 다양한 JS 연산자
  • 타입 검사 헬퍼
  • 모의 지원
  • 그 외 더 많은 것들

dart:js_interopdart:js_interop_unsafe에는 이제 이 헬퍼들이 (어쩌면 다른 문법으로) 들어 있어요.

더 알아보기