NPM 및 Browserify와 함께 Scala.js 사용

게시 됨: 2022-03-11

Scala 언어를 JavaScript로 컴파일러인 Scala.js를 사용하면 현대 JavaScript 세계에서 Scala.js의 표준 종속성 관리가 너무 제한적이라는 것을 알 수 있습니다. Scala.js는 WebJar를 사용하여 종속성을 관리하는 반면 JavaScript 개발자는 NPM을 사용하여 종속성을 관리합니다. NPM에 의해 생성된 종속성은 서버 측이므로 일반적으로 Browserify 또는 Webpack을 사용하여 브라우저 코드를 생성하는 추가 단계가 필요합니다.

이 게시물에서는 Scala.js를 NPM에서 사용할 수 있는 과다한 JavaScript 모듈과 통합하는 방법을 설명합니다. 여기에 설명된 기술의 실제 예제는 이 GitHub 리포지토리를 확인할 수 있습니다. 이 예제를 사용하고 이 게시물을 읽으면 NPM을 사용하여 JavaScript 라이브러리를 수집하고 Browserify를 사용하여 번들을 만들고 그 결과를 자신의 Scala.js 프로젝트에서 사용할 수 있습니다. 모든 것이 SBT에서 관리되므로 Node.js를 설치하지 않고도 이 모든 것이 가능합니다.

Browserify 및 Scala.js

Browserify의 마법은 Java 세계에서도 훌륭하게 작동할 수 있습니다!
트위터

Scala.js에 대한 종속성 관리

오늘날 JavaScript로 컴파일되는 언어로 애플리케이션을 작성하는 것은 매우 일반적인 관행이 되었습니다. 점점 더 많은 사람들이 CoffeeScript 또는 TypeScript와 같은 확장 JavaScript 언어 또는 현재 ES6을 사용할 수 있게 해주는 Babel과 같은 변환기로 이동하고 있습니다. 동시에 기업용 애플리케이션에는 Google Web Toolkit(Java에서 JavaScript로의 컴파일러)이 주로 사용됩니다. 이러한 이유로 스칼라 개발자로서 나는 더 이상 Scala.js를 이상한 선택으로 생각하지 않습니다. 컴파일러는 빠르고 생성된 코드는 효율적이며 전반적으로 프론트엔드와 백엔드 모두에서 동일한 언어를 사용하는 방법일 뿐입니다.

그렇긴 하지만 JavaScript 세계에서 Scala 도구를 사용하는 것은 아직 100% 자연스럽지 않습니다. JavaScript 생태계에서 Scala 생태계로의 간극을 메워야 하는 경우가 있습니다. 언어로서의 Scala.js는 JavaScript와 뛰어난 상호 운용성을 가지고 있습니다. Scala.js는 Scala 언어를 JavaScript로 변환하는 컴파일러이기 때문에 Scala 코드를 기존 JavaScript 코드에 인터페이스하기가 매우 쉽습니다. 가장 중요한 것은 Scala.js가 유형이 지정된 인터페이스(또는 외관)를 생성하여 유형이 지정되지 않은 JavaScript 라이브러리에 액세스할 수 있는 기능을 제공한다는 것입니다(TypeScript로 수행하는 작업과 유사). Java, Scala 또는 Haskell과 같은 강력한 형식의 언어에 익숙한 개발자의 경우 JavaScript는 너무 느슨한 형식입니다. 당신이 그러한 개발자라면 아마도 주된 이유는 당신이 Scala.js를 사용하고 싶어할 수 있기 때문일 것입니다.

SBT를 기반으로 하고 여전히 다소 열려 있는 표준 Scala.js 도구 체인의 문제는 다음과 같습니다. 추가 JavaScript 라이브러리와 같은 종속성을 프로젝트에 포함하는 방법은 무엇입니까? SBT는 WebJar를 표준화하므로 WebJar를 사용하여 종속성을 관리해야 합니다. 불행히도, 제 경험상 그것은 불충분한 것으로 판명되었습니다.

WebJar의 문제

언급했듯이 JavaScript 종속성을 검색하는 표준 Scala.js 방법은 WebJars를 기반으로 합니다. 스칼라는 결국 JVM 언어입니다. Scala.js는 주로 프로그램 빌드에 SBT를 사용하고 마지막으로 SBT는 JAR 종속성을 관리하는 데 탁월합니다.

이러한 이유로 WebJar 형식은 JVM 세계에서 JavaScript 종속성을 가져오기 위해 정확히 정의되었습니다. WebJar는 웹 자산을 포함하는 JAR 파일로, 단순 JAR 파일에는 컴파일된 Java 클래스만 포함됩니다. 따라서 Scala.js의 아이디어는 Scala가 JAR 종속성을 추가하는 것과 유사한 방식으로 WebJar 종속성을 추가하여 JavaScript 종속성을 가져와야 한다는 것입니다.

작동하지 않는 것을 제외하고는 좋은 아이디어입니다.

Webjars의 가장 큰 문제는 임의의 JavaScript 라이브러리의 임의 버전이 WebJar로 거의 사용할 수 없다는 것입니다. 동시에 대다수의 JavaScript 라이브러리를 NPM 모듈로 사용할 수 있습니다. 그러나 자동화된 npm-to-webjar 패키저를 사용하여 NPM과 WebJar 사이에 브리지가 있다고 가정합니다. 그래서 NPM 모듈로 사용할 수 있는 라이브러리를 가져오려고 했습니다. 제 경우에는 웹 페이지에서 Minecraft와 같은 세계를 구축하기 위한 라이브러리인 VoxelJS였습니다. 라이브러리를 WebJar로 요청하려고 했지만 디스크립터에 라이선스 필드가 없기 때문에 브리지가 실패했습니다.

WebJar 라이브러리가 작동하지 않습니다

다른 라이브러리에서 다른 이유로 이 답답한 경험에 직면할 수도 있습니다. 간단히 말해서 WebJar로 야생의 각 라이브러리에 액세스할 수 없는 것처럼 보입니다. JavaScript 라이브러리에 액세스하기 위해 WebJar를 사용해야 하는 요구 사항은 너무 제한적인 것 같습니다.

NPM 및 Browserify 입력

이미 지적했듯이 대부분의 JavaScript 라이브러리에 대한 표준 패키징 형식은 모든 버전의 Node.js에 포함된 NPM(Node Package Manager)입니다. NPM을 사용하면 사용 가능한 거의 모든 JavaScript 라이브러리에 쉽게 액세스할 수 있습니다.

NPM은 노드 패키지 관리자입니다. Node는 V8 JavaScript 엔진의 서버 측 구현이며 Node.js에서 서버 측에서 사용할 패키지를 설치합니다. 그대로 NPM은 브라우저에 쓸모가 없습니다. 그러나 NPM 패키지로도 배포되는 유비쿼터스 Browserify 도구 덕분에 NPM의 유용성이 브라우저 애플리케이션과 함께 작동하도록 잠시 확장되었습니다.

Browserify는 간단히 말해서 브라우저용 패키지 도구입니다. 브라우저 애플리케이션에서 사용할 수 있는 "번들"을 생성하는 NPM 모듈을 수집합니다. 많은 JavaScript 개발자는 이러한 방식으로 작업합니다. NPM으로 패키지를 관리하고 나중에 웹 애플리케이션에서 사용하기 위해 브라우저화합니다. Webpack과 같이 동일한 방식으로 작동하는 다른 도구가 있음을 염두에 두십시오.

SBT에서 NPM으로의 격차 채우기

방금 설명한 이유 때문에 내가 원했던 것은 NPM을 사용하여 웹에서 종속성을 설치하고 Browserify를 호출하여 브라우저에 대한 종속성을 수집한 다음 Scala.js와 함께 사용하는 방법이었습니다. 작업은 예상보다 조금 더 복잡했지만 여전히 가능합니다. 실제로, 나는 그 일을했고 여기에 설명합니다.

SBT 내에서 실행할 수 있다는 것을 알았기 때문에 간단하게 Browserify도 선택했습니다. 나는 그것이 가능하다고 생각하지만 Webpack으로 시도하지 않았습니다. 운 좋게도 나는 진공 상태에서 시작할 필요가 없었습니다. 이미 여러 조각이 있습니다.

  • SBT는 이미 NPM을 지원합니다. Play Framework용으로 개발된 sbt-web 플러그인은 NPM 종속성을 설치할 수 있습니다.
  • SBT는 JavaScript 실행을 지원합니다. sbt-jsengine 플러그인 덕분에 Node 자체를 설치하지 않고도 Node 도구를 실행할 수 있습니다.
  • Scala.js는 생성된 번들을 사용할 수 있습니다. Scala.js에는 애플리케이션에 임의의 JavaScript 라이브러리를 포함할 연결 기능이 있습니다.

이러한 기능을 사용하여 NPM 종속성을 다운로드한 다음 Browserify를 호출하여 bundle.js 파일을 생성할 수 있는 SBT 작업을 만들었습니다. 절차를 컴파일 체인에 통합하려고 했고 모든 것을 자동으로 실행할 수 있지만 각 컴파일에서 번들링을 처리해야 하는 것은 너무 느립니다. 또한 항상 종속성을 변경하지 않습니다. 따라서 종속성을 변경할 때 가끔 번들을 수동으로 생성해야 하는 것이 합리적입니다.

그래서 내 솔루션은 하위 프로젝트를 구축하는 것이 었습니다. 이 하위 프로젝트는 NPM 및 Browserify를 사용하여 JavaScript 라이브러리를 다운로드하고 패키징합니다. 그런 다음 종속성 수집을 수행하는 bundle 명령을 추가했습니다. 결과 번들은 Scala.js 애플리케이션에서 사용할 리소스에 추가됩니다.

JavaScript 종속성을 변경할 때마다 이 "번들"을 수동으로 실행해야 합니다. 언급했듯이 컴파일 체인에서 자동화되지 않습니다.

번들러 사용 방법

내 예를 사용하려면 다음을 수행하십시오. 먼저 일반적인 Git 명령으로 저장소를 체크아웃하십시오.

 git clone https://github.com/sciabarra/scalajs-browserify/

그런 다음 Scala.js 프로젝트의 bundle 폴더를 복사합니다. 번들링을 위한 하위 프로젝트입니다. 기본 프로젝트에 연결하려면 build.sbt 파일에 다음 줄을 추가해야 합니다.

 val bundle = project.in(file("bundle")) jsDependencies += ProvidedJS / "bundle.js" addCommandAlias("bundle", "bundle/bundle")

또한 project/plugins.sbt 파일에 다음 행을 추가해야 합니다.

 addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1") addSbtPlugin("com.typesafe.sbt" % "sbt-js-engine" % "1.1.3")

완료되면 종속성을 수집하는 데 사용할 수 있는 새 명령 bundle 이 있습니다. src/main/resources 폴더 아래에 bundle.js 파일을 생성합니다.

번들은 Scala.js 애플리케이션에 어떻게 포함되어 있습니까?

방금 설명한 bundle 명령은 NPM과의 종속성을 수집한 다음 bundle.js 를 생성합니다. fastOptJS 또는 fullOptJS 명령을 실행하면 ScalaJS는 JavaScript 종속성으로 지정한 모든 리소스를 포함하여 myproject-jsdeps.js 를 생성하므로 bundle.js 도 생성됩니다. 애플리케이션에 번들 종속성을 포함하려면 다음 포함을 사용해야 합니다.

 <script src="target/scala-2.11/myproject-jsdeps.js"></script> <script src="target/scala-2.11/myproject-fastopt.js"></script> <script src="target/scala-2.11/myproject-launcher.js"></script>

이제 번들을 myproject-jsdeps.js 의 일부로 사용할 수 있습니다. 번들이 준비되었고 작업이 어느 정도 완료되었습니다(종속성 가져오기 및 브라우저로 내보내기). 다음 단계는 다른 문제인 Scala.js 코딩 문제인 JavaScript 라이브러리를 사용하는 것입니다. 완전성을 위해 이제 Scala.js에서 번들을 사용하고 가져온 라이브러리를 사용하기 위해 파사드를 만드는 방법을 논의합니다.

Scala.js를 사용하면 서버와 클라이언트 간에 코드를 공유할 수 있습니다.

Scala.js를 사용하면 서버와 클라이언트 간에 코드를 쉽게 공유할 수 있습니다.
트위터

Scala.js 애플리케이션에서 일반 JavaScript 라이브러리 사용

요약하자면 NPM과 Browserify를 사용하여 번들을 만들고 해당 번들을 Scala.js에 포함하는 방법을 살펴보았습니다. 그러나 일반 JavaScript 라이브러리를 어떻게 사용할 수 있습니까?

나머지 게시물에서 자세히 설명할 전체 프로세스는 다음과 같습니다.

  • NPM에서 라이브러리를 선택하고 bundle/package.json 에 포함합니다.
  • bundle/lib.js 의 라이브러리 모듈 파일에서 require 와 함께 로드하세요.
  • Scala.js에서 Bundle 객체를 해석하기 위해 Scala.js 파사드를 작성하십시오.
  • 마지막으로 새로 입력된 라이브러리를 사용하여 애플리케이션을 코딩합니다.

종속성 추가

NPM을 사용하면 표준인 package.json 파일에 종속성을 포함해야 합니다.

따라서 jQuery 및 Loadash와 같은 두 개의 유명한 라이브러리를 사용한다고 가정해 보겠습니다. 이 예제는 적절한 모듈이 있는 Scala.js에 대한 종속성으로 사용할 수 있는 jQuery용 훌륭한 래퍼가 이미 있고 Lodash는 Scala 세계에서 쓸모가 없기 때문에 이 예제는 단지 데모용입니다. 그럼에도 불구하고 좋은 예라고 생각하지만 단지 예를 들어보십시오.

따라서 npmjs.com 웹 사이트로 이동하여 사용하려는 라이브러리를 찾고 버전도 선택하십시오. jquery-browserify browserify 버전 13.0.0과 lodash 버전 4.3.0을 선택했다고 가정해 보겠습니다. 그런 다음 다음과 같이 packages.jsondependencies 블록을 업데이트합니다.

 "dependencies": { "browserify": "13.0.0", "jquery-browserify": "1.8.1", "lodash": "4.3.0" }

번들을 생성하는 데 필요하므로 항상 browserify 를 유지하십시오. 하지만 번들에 포함할 필요는 없습니다.

NPM이 설치된 경우 bundle 디렉토리에서 다음을 입력하면 됩니다.

 npm install --save jquery-browserify lodash

또한 package.json 을 업데이트합니다. NPM이 설치되어 있지 않아도 걱정하지 마십시오. SBT는 Node.js 및 NPM의 Java 버전을 설치하고 필요한 JAR을 다운로드하여 실행합니다. 이 모든 것은 SBT에서 bundle 명령을 실행할 때 관리됩니다.

라이브러리 내보내기

이제 패키지를 다운로드하는 방법을 알았습니다. 다음 단계는 Browserify가 번들로 수집하고 나머지 애플리케이션에서 사용할 수 있도록 지시하는 것입니다.

Browserify require 브라우저의 Node.js 동작을 에뮬레이트하는 require 의 수집기입니다. 이러한 라이브러리를 Scala.js로 내보내야 하기 때문에 번들은 Bundle 이라는 최상위 JavaScript 객체도 생성합니다. 따라서 JavaScript 개체를 내보내고 모든 라이브러리를 이 개체의 필드로 요구하는 lib.js 를 편집해야 합니다.

Scala.js jQuery 및 Lodash 라이브러리로 내보내려면 코드에서 다음을 의미합니다.

 module.exports = { "jquery": require("jquery-browserify"), "lodash": require("lodash") }

이제 명령 bundle 을 실행하기만 하면 라이브러리가 다운로드되고 수집되어 번들에 배치되어 Scala.js 애플리케이션에서 사용할 준비가 됩니다.

번들 액세스

지금까지:

  • Scala.js 프로젝트에 번들 하위 프로젝트를 설치하고 올바르게 구성했습니다.
  • 원하는 라이브러리에 대해 package.json 에 추가했습니다.
  • lib.js 에서 필요했습니다.
  • bundle 명령을 실행했습니다.

결과적으로 이제 Bundle 최상위 JavaScript 개체가 있으며 이 개체의 필드로 사용할 수 있는 라이브러리에 대한 모든 진입점을 제공합니다.

이제 Scala.js와 함께 사용할 준비가 되었습니다. 가장 간단한 경우에 다음과 같이 라이브러리에 액세스할 수 있습니다.

 @js.native object Bundle extends js.Object { def jquery : js.Any = js.native def lodash: js.Any = js.native }

이 코드를 사용하면 Scala.js에서 라이브러리에 액세스할 수 있습니다. 그러나 라이브러리가 여전히 형식화되지 않았기 때문에 Scala.js로 수행해야 하는 방식은 아닙니다. 대신에 유형이 지정된 "파사드" 또는 래퍼를 작성해야 원래 유형이 지정되지 않은 JavaScript 라이브러리를 유형이 지정된 확장 방식으로 사용할 수 있습니다.

Scala.js에서 래핑하려는 특정 JavaScript 라이브러리에 의존하기 때문에 어떻게 파사드를 작성하는지 여기에서 말할 수 없습니다. 토론을 완료하기 위해 예만 보여 드리겠습니다. 자세한 내용은 공식 Scala.js 문서를 확인하세요. 또한 사용 가능한 파사드 목록을 참조하고 영감을 얻기 위해 소스 코드를 읽을 수 있습니다.

지금까지 우리는 여전히 매핑되지 않은 임의의 라이브러리에 대한 프로세스를 다루었습니다. 기사의 나머지 부분에서 이미 사용 가능한 파사드가 있는 라이브러리를 언급하고 있으므로 논의는 단지 예일 뿐입니다.

Scala.js에서 JavaScript API 래핑

JavaScript에서 require 를 사용하면 여러 가지가 될 수 있는 객체를 얻습니다. 함수일 수도 있고 객체일 수도 있습니다. 단지 문자열이나 부울일 수도 있습니다. 이 경우 require 는 부작용에 대해서만 호출됩니다.

내가하고있는 예제에서 jquery 는 추가 메소드를 제공하는 객체를 반환하는 함수입니다. 일반적인 사용법은 jquery(<selector>).<method> 입니다. jquery$.<method> 사용을 허용하기 때문에 이것은 단순화입니다. 그러나 이 단순화된 예제에서는 이러한 모든 경우를 다루지는 않을 것입니다. 일반적으로 복잡한 JavaScript 라이브러리의 경우 모든 API를 정적 Scala 유형에 쉽게 매핑할 수 있는 것은 아닙니다. JavaScript 객체에 대한 동적(유형이 지정되지 않은) 인터페이스를 제공하는 js.Dynamic 에 의존해야 할 수도 있습니다.

따라서 보다 일반적인 사용 사례를 캡처하기 위해 Bundle 객체 jquery 에서 정의했습니다.

 def jquery : js.Function1[js.Any, Jquery] = js.native

이 함수는 jQuery 객체를 반환합니다. 특성의 인스턴스는 제 경우에 단일 메소드로 정의됩니다(단순화, 사용자 고유의 것을 추가할 수 있음):

 @js.native trait Jquery extends js.Object { def text(arg: js.Any): Jquery = js.native }

Lodash 라이브러리의 경우 직접 호출할 수 있는 함수 모음이므로 전체 라이브러리를 JavaScript 객체로 모델링합니다.

 def lodash: Lodash = js.native

lodash 특성은 다음과 같습니다(단순화, 여기에 메서드를 추가할 수 있음).

 @js.native trait Lodash extends js.Object { def camelCase(arg: js.Any): String = js.native }

이러한 정의를 사용하여 이제 NPM에서 로드된 다음 브라우저에서 기본 jQuery 및 Lodash 라이브러리를 사용하여 Scala 코드를 작성할 수 있습니다.

 object Main extends JSApp { def main(): Unit = { import Bundle._ jquery("#title").text(lodash.camelCase("This is a test")) } }

여기에서 전체 예제를 확인할 수 있습니다.

결론

저는 Scala 개발자이고, 서버와 클라이언트 모두에 동일한 언어를 사용할 수 있기 때문에 Scala.js를 발견했을 때 매우 기뻤습니다. Scala는 Java보다 JavaScript와 더 유사하기 때문에 Scala.js는 브라우저에서 매우 쉽고 자연스럽습니다. 또한 Scala의 강력한 기능을 풍부한 라이브러리, 매크로, 강력한 IDE 및 빌드 도구 모음으로 사용할 수도 있습니다. 다른 주요 이점은 서버와 클라이언트 간에 코드를 공유할 수 있다는 것입니다. 이 기능이 유용한 경우가 많이 있습니다. Coffeescript, Babel 또는 Typescript와 같은 Javascript용 트랜스파일러를 사용하는 경우 Scala.js를 사용할 때 많은 차이점을 느끼지 못하지만 여전히 많은 이점이 있습니다. 비밀은 각 세계의 장점을 최대한 활용하고 서로 잘 작동하도록 하는 것입니다.

관련: 스칼라를 배워야 하는 이유