Skip to content

Instantly share code, notes, and snippets.

@ChangJoo-Park

ChangJoo-Park/blog.md Secret

Created May 13, 2020
Embed
What would you like to do?

Vue.js 개발자를 위한 Ember.js Octane 에디션

이 글은 Ember.js를 홍보하는 글이 아닙니다.

특정 라이브러리, 프레임워크를 옹호하지 않습니다.

Ember.js에서 Vue.js로 넘어가다

2015년 정도에 Ruby on Rails를 한창 하던 시기에 Ember.js를 함께 사용하였습니다. Ember.js가 Ruby on Rails 를 사용하는 개발자들에 의해 만들어져서 꽤 통합이 괜찮았고, 별 다른 설정 없이 json-api를 지원하는 것도 큰 장점이었습니다.

프론트엔드의 웹 컴포넌트와 구글의 Polymer의 출현으로 컴포넌트 개발 방식에 관심을 가지기 시작했고 Router와 Controller를 기준으로 많은 작업이 이루어졌던 Ember.js도 컴포넌트 방식으로 점차 변화되고 있었습니다.

2016년 경 Vue.js를 알게 되었고, 모든 것들을 컴포넌트를 기반으로 만들 수 있다는 컨셉에 수긍해서 조금씩 Vue.js를 사용하게 되었고 꽤 만족감이 높았습니다.

어쩌다보니 Vue.js가 주로 사용하는 라이브러리가 되었고, 공식문서 번역 및 커뮤니티 참여 등을 하면서 점점 더 Ember.js를 사용할 일이 없었습니다. 솔직하게 말하면 굳이 러닝커브가 가파른 프레임워크를 다른 사람들에게 알려줄 실력이 부족했던 것 같습니다.

다시 Ember.js 를 사용한다면?

🔔 실제로 회사에서 사용할 마음은 없습니다. (아직)

🔔 https://metapost.dev 를 Ember Octane으로 바꿀 생각은 있습니다.

그럼에도 불구하고 Ember.js에는 꾸준히 관심을 가졌는데 2019년에 와서야 최근의 라이브러리에 가까워졌습니다. 오히려 장황한 내장 메소드들을 제거하고 네이티브 클래스를 적극 활용하는 방식으로 바뀌어 Plain JavaScript에 가까워지고 있습니다.

이전에는 데이터 하나를 변경하는 경우에도 this.set('counter', 1) 같이 사용하고, 가져올때는 this.get('counter') 를 이용하곤 했습니다. 최신 자바스크립트 라이브러리에서는 당연히 this.counter = 1 같이 사용하고 있습니다.

최근 Ember.js 는 Ember.js Octane Edition(엠버 옥테인 으로 발음)이라는 새로운 릴리즈를 하였는데, 그동안의 컴포넌트 기반 라이브러리에 많은 영향을 받은 것으로 보입니다. 정확하게 말하자면 Glimmer.js를 Ember.js가 정식으로 품게 되면서 Ember Octane가 되었습니다.

물론 회사에서는 Vue.js를 사용하고 있고, 요즘 프론트엔드 라이브러리의 선택지는 React.js, Vue.js 가 주도하고 있고 조금 더 과감하면 Svelte나 Lit를 사용하지 않을까 싶습니다.

React.js는 이미 Hook 기반으로 변화하고 있고, Vue.js도 3버전에 오면서 Hook 기반 개발 방식으로 변화하고 있습니다. 이러한 흐름이 있지만 꼭 그래야할 필요는 없다고 생각해서 다시 Ember.js 를 돌아보려고 합니다.

알아볼 것들

Ember.js 의 큰 변화를 만들어낸 Glimmer.js에서 제공하는 Glimmer Component와 Vue.js를 비교하고 Vue.js를 쓰는 사용자가 Ember.js를 사용한다면 어떻게 해야하는지 정리해 보겠습니다. Ember.js는 프레임워크이고 Vue.js는 라이브러리이기 때문에 Vue.js의 컴포넌트를 기준으로 Ember.js를 어떻게 바라봐야하는지 살펴봅니다.

Glimmer.js는 Ember.js를 만든 Tom Dale이 만든 초경량화 컴포넌트 라이브러리입니다. Glimmer.js만 단독으로 사용할 수 있고 Glimmer.js로 만든 컴포넌트를 웹 컴포넌트로 빌드해서 사용할 수 도 있습니다.

Ember Octane에서는 기존의 컴포넌트(클래식 컴포넌트)와 함께 Glimmer.js의 컴포넌트를 함께 지원합니다. 최신 버전 ember-cli(vue cli와 유사)를 사용한다면 컴포넌트 생성 터미널 명령어를 이용하여 Glimmer Component를 만들 수 있습니다.

이 글은 Vue.js를 이미 사용하고 있는 개발자를 대상으로 Ember Octane을 소개하는 것이기 때문에 어느정도 Vue.js에 대한 이해가 있어야 합니다. Vue.js에 생소한 경우에는 공식문서 등을 보고 오시면 좋습니다.

Vue 3가 베타 단계이고 앞으로는 Vue 3를 사용하겠지만 기본적으로 Vue.js 2 를 기준으로 다룹니다. 그리고 실제로 Vue.js를 이용해 개발하면서 사용하는 여러가지 함수들 중 자주 사용하는 내용만 다룹니다.

Nuxt는 Ember.js의 fastboot 패키지와 1:1로 대응합니다. 이 글을 읽고 Ember.js에 관심이 있으신 분은 fastboot 도 알아보시면 좋습니다.

라이프사이클 훅

Vue.js는 거의 대부분의 상태 사이사이마다 라이프사이클 훅을 제공합니다. 그러나 기본적으로 Ember Octane은 컴포넌트 클래스의 constructor 밖에 제공하지 않는다. Vue.js의 beforeCreate 시점에 호출됩니다.

Vue.js에서 제공하는 엘리먼트와 관련된 상태의 훅을 사용하려면 https://github.com/emberjs/ember-render-modifiers 를 사용해야하는데 이 라이브러리를 사용하면 mounted(did-render) 훅과 updated(did-update) beforeDestroy(willDestroy)훅을 사용할 수 있습니다.

Ember Octane은 네이티브 클래스의 라이프사이클 훅인 constructor 만 컴포넌트 클래스코드 안에서 정의하고, ember-render-modifiers에서 제공하는 did-render, did-update, will-destroy는 컴포넌트의 DOM 엘리먼트에서 호출해야합니다.

DOM의 변경사항으로 발생하는 라이프사이클이므로 적절한 위치에서 호출 되는 것으로 생각됩니다.

https://gist.github.com/a40e43cd3e8ff02c0aafbcfb11751857

다양한 훅을 제공하지 않지만 자주 사용하는 최소한의 컴포넌트 라이프사이클 훅을 가지고 있고 추가로 modifier를 이용해 확장하는 것을 권장gkqslek. Ember Octane 이전에는 Vue.js 에서 제공하는 대부분의 라이프사이클 훅을 가지고 있었으나 Glimmer.js의 컴포넌트를 사용하는 시점에서 모두 제거되고 constructor만 남게 되었다.

Ember Octane에 Glimmer.js를 통합하면서 발생하는 차이점은 Glimmer.js는 didInsertElement 훅을 제공하지만 Ember Octane에서 이 훅을 선언하면 Ember.js 애플리케이션에서는 didInsertElement 훅을 지원하지 않으니 @ember/render-modifiers 를 사용하라는 에러 메시지를 보여줍니다.

Vue.js 3 부터는 라이프사이클 훅의 이름에 onCreated 처럼 on 을 붙여서 사용합니다.

데이터

Ember Octane은 네이티브 클래스를 활용하는 것을 큰 특징으로 내세우고 있어 Vue.js 처럼 명시적으로 기능에 따라 코드 블럭을 나누지 않습니다. 다만 데코레이터를 적극 활용하여 어떤 프로퍼티가 옵저버(Ember Octane은 tracked라고 함)를 가지는지 알 수 있습니다.

구분된 코드 블럭은 가독성에 큰 장점이 있으나 네이티브 클래스를 이용하는 경우 클래스 변수의 경우는 변수처럼, 메소드의 경우는 메소드처럼 보이기 때문에 특별히 data, computed, methods 등의 구분을 할 필요가 없습니다.

Vue 3 도 Ember Octane 과 마찬가지의 흐름을 보이는데 setup 메소드 안에서 메소드나 데이터, computed properties를 정의하고 메소드 마지막에 리턴하는 방법을 사용합니다.

  1. data Vue.js 의 data는 옵저버를 가진 프로퍼티를 만드는 역할을 하는데, Ember Octane에서 @tracked 데코레이터를 가진 클래스 변수와 동일한 역할을 합니다. 값이 변경되면 template에 변경사항을 자동으로 반영합니다.

https://gist.github.com/7b21ab580662560d29895a5b614261cb

https://gist.github.com/ffd25f6a2508bbe11c35939a0595bcb5

Vue.js 3 부터는 Ref, Reactive 두가지 메소드를 이용해 옵저버를 가진 데이터 속성을 정의 할 수 있다. 자세한 내용은 Vue Composition API 를 확인하세요

https://gist.github.com/51d5bf783a4d87bd1037cd9a872700d1

  1. props

Vue.js의 props는 외부로부터 전달받는 속성을 의미하는데 Ember Octane은 this.args를 사용합니다. 기본값이나 타입체크, 검증 등의 기능은 제공하지 않습니다. 만약 데이터의 검증이 필요하면 직접 구현하거나 Ember Data를 사용합니다. Vue.js와 Ember Octane 모두 외부에서 전달 받은 값의 경우에는 불변(immutable) 속성입니다.

Vue.js는 props를 정의하여야 사용할 수 있으나 Ember Octane은 추가적인 정의 없이 템플릿의 컴포넌트 정의에서 지정된 내용을 this.args 로 접근합니다.

https://gist.github.com/efe5dbf0010d6ce4867000427adbaa7a

컴포넌트 클래스 메소드에서 this.args.title 와 같이 사용한다. 모든 원시 속성들과 메소드까지 전달할 수 있습니다.

Vue.js 3 에서는 setup 메소드의 파라미터로 props를 사용합니다 setup(props) 와 같이 선언하면 됩니다.

  1. watch

Vue.js에는 watch 를 제공하여 데이터의 변화를 추적할 수 있는데, Ember Octane은 네이티브 클래스를 활용하기 떄문에 명시적인 watch 기능을 제공하지 않습니다. 대신 setter를 선언하여 값이 변화하는 시점에 원하는 작업을 할 수 있습니다.

  1. computed properties

Ember Octane은 computed properties를 네이티브 클래스의 getter, setter를 이용해서 해결하는데, firstName, lastName을 연결해서 보여주는 fullName 이라는 속성이 있을 때 아래와 같이 표현한다.

https://gist.github.com/fedd2810b100f7e7892799830956b526

Vue.js에서 제공하는 캐시는 제공하지 않는다.

Vue.js 3는 const myComputed = computed(() => 1) 과 같이 computed 메소드를 이용하여 선언합니다.

  1. methods

Vue.js는 methods를 이용하는데 여기서 선언한 함수는 템플릿에서 함수 이름으로 호출할 수 있습니다. Ember Octane에서는 네이티브 클래스의 함수 선언을 하고 템플릿에서 호출하는 경우 @action 데코레이터를 추가하면 됩니다. @action 데코레이터를 가지지 않은 함수는 자동으로 컴포넌트 클래스 안에서만 호출하는 함수로 간주됩니다.

https://gist.github.com/993d5a6880a3e61c32aa1eacc5bfa5cf

https://gist.github.com/83f826d33515dd79e3c0dfcd32f06916

위 예제에서 this.increment 함수의 기본 파라미터는 event 이고, increment 메소드에 amount 라는 증가량을 전달하고 싶으면 템플릿과 메소드를 다음과 같이 변경해야 합니다.

https://gist.github.com/9cb67d55abc18c2894b4fa2b62f775fc

https://gist.github.com/77867ca56d284f98bfd1ddf7e53aeb3c

파라미터를 추가하려면 fn modifier를 이용한다. fn modifier에 이어 사용할 함수와 전달할 값을 지정하면 됩니다.

템플릿

Vue.js는 Single File Component를 큰 장점으로 가지는 라이브러리입니다. 이 때문에 한 컴포넌트 안에서 일어나는 일들을 한 파일만 열어봐도 알 수 있는 장점이 있습니다.

template에 선언된 템플릿에는 script 영역에 있는 data, computed properties, methods를 사용할 수 있습니다. Ember Octane도 같습니다. 데이터 섹션에서 살펴본 것과 같이, firstName, lastName과 fullName 을 템플릿에 mustache 를 이용해 표현할 수 있다.

https://gist.github.com/65a18515e5143227935a021aff144a69

Vue.js 2,3 그리고 Ember Octane 모두 같습니다.

template 구성에 큰 차이가 있다면 Ember Octane은 Handlebars 문법을 적극적으로 활용한다. 정확히는 Handlebars 문법을 처리하는 glimmer-vm을 이용합니다.. Handlebars 문법을 차용하므로 v-bind라거나 v-on같은 키워드가 없습니다. DOM 엘리먼트 텍스트에 적는 것 처럼 mustache를 바로 사용합니다.

https://gist.github.com/4f3d228f86c6da15fb70ec38e788c32d

https://gist.github.com/7a64faa53e062a6d7c995c750beb7cc5

Vue의 경우에는 v-bind을 이용해 보간을 하지만 Ember Octane은 Handlebars 템플릿을 처리할때 DOM 엘리먼트 텍스트와 동일하게 mustache가 있으면 해당 데이터를 이용하여 렌더링합니다.

메소드의 경우에 v-on 혹은 @ 를 사용하는 Vue가 훨씬 간단하게 표현하는 것으로 보입니다. Ember Octane의 경우에는 Handlebars 문법의 한계인지 조금은 장황합니다.

https://gist.github.com/dcfccf658a19e3de4cf9971845959b0b

https://gist.github.com/5f8bf84ebb18938acdeb485e298dfb7b

Ember Octane의 메소드 호출은 on, "이벤트" 함수 순으로 호출합니다. Ember Octane의 경우가 이렇고 Glimmer.js는 아래와 같이 작성해야한다.

https://gist.github.com/67f9226df93c76b541f7310ca951c446

아직 통합이 완전하지는 않아 서로 다른 문법을 가지고 있으므로 주의해야합니다.

Vue.js 에서 가능한 v-on에서 연산을 하는 코드는 Ember Octane에서는 불가능합니다.

https://gist.github.com/b454d8f619303899519cb4d12dbe466d

이런 코드는 Ember Octane에서 할 수 없으니 반드시 increment 와 같은 함수를 선언하고 호출해야합니다.

조건부 렌더링

Vue.js와 Ember Octane 모두 if를 이용한 조건부 렌더링을 할 수 있으나 문법이 약간 다릅니다. v-if를 쓰거나, {{ if }} 를 쓰는 차이 정도이다. Ember Octane은 v-show같은 역할을 하는 기능은 제공하지 않습니다.

https://gist.github.com/d5b56726bb7101e99dec7e9ad2dba018

https://gist.github.com/362e244aff06f97ad55a621c39668fa9

https://gist.github.com/19051409a49c08599c774a951961887e

Ember Octane은 if를 이용해 Vue.js에서 class를 바인딩 하는 것 처럼 만들 수 있습니다.

https://gist.github.com/ad99355ff3c9fbb34e825859b68429ce

https://gist.github.com/9984d9f856bb341e8402810d3292b422

Ember Octane은 if를 이용한 삼항연산을 하는 방식으로 class에 바로 mustache문법을 사용할 수 있습니다

루프

조건부 렌더링과 같이 Ember Octane도 v-for에 해당하는 each 키워드를 제공하는데 이는 역시 Handlebars의 문법을 따릅니다. {{#each items as |item| }} {{/each}} 와 같이 사용한다.

https://gist.github.com/3d2fb823fb8f18b14d54b57f25e8f29b

지시어

DOM 엘리먼트를 확장하는 directives는 Ember Octane에도 있습니다. ember-modifier 를 이용합니다.

https://gist.github.com/804807d3fc9ff18f2b76765861057a0d

https://gist.github.com/ca7ec7851b22cf8bf0de978210ec28b3

https://gist.github.com/064c4c2a66a268f99c037fc6ef89e7ae

조금 장황하지만 modifier 함수를 선언하고 element와 eventName 등을 넘겨 받아 처리할 수 있습니다. 위에서 말한 ember-render-modifier도 ember-modifier를 기반으로 만들어진 라이브러리이다.

컴포넌트 조합

Ember Octane은 모든 컴포넌트가 글로벌 컴포넌트입니다. 따로 스크립트 코드로 컴포넌트를 등록하고, import를 이용해 가져올 필요가 없습니다. 컴포넌트를 만들고 필요한 곳에서 사용하면됩니다.

컴포넌트를 import 후 등록하여 사용하는 것이 트리-쉐이킹에 큰 장점을 가지지만 Ember Octane의 빌드 과정을 자세히 알지 못하는 상황이라 비교는 하지 못합니다.

Vue.js는 Webpack을 사용하고, Ember Octane은 Brocolli를 사용하여 두 빌드 도구의 차이를 살펴보면 알 수 있을 것으로 생각합니다.

Ember Octane도 Vue.js와 같이 "Data Down, Action Up" 이라는 기본 룰을 따릅니다. 부모 컴포넌트는 자식 컴포넌트에 데이터를 넘기고 자식 컴포넌트는 변경된 결과를 부모에게 알려주는 방식을 말합니다.

https://gist.github.com/3125858f3bcabe9470ec00581174b93d

https://gist.github.com/f54d39259971f431ad99ecc6a6c557e8

https://gist.github.com/12dcc6b642723854e915ab7710e85ed8

이렇게 Parent 컴포넌트 안에 Child 컴포넌트를 정의할 수 있습니다. 이 예제는 yield (Vue.js라면 slot)를 이용한 것이므로 Child 컴포넌트로 action을 주입하려면 Parent와 Child 컴포넌트를 모두 가지고 있는 컨트롤러에 action 메소드를 정의해야 합니다. 이 글의 범위인 컴포넌트 범위 비교에서 벗어나므로 Parent 컴포넌트가 Child 컴포넌트를 바로 가지도록 바꿉니다.

https://gist.github.com/c4d6948769da6e79c0e35635ed23ecd3

https://gist.github.com/559e9c999043e37a7d92c93b1850bd2e

https://gist.github.com/d0fe7b4ebc32b00281f4574926100d4b

https://gist.github.com/05347ca705dc0c7e71419da5e45c95be

Parent 컴포넌트에 callParent 메소드를 선언하고, Child 컴포넌트에 전달하면, Child 컴포넌트에서 사용할 수 있습니다.

당연하게도 if를 이용한 조건부 렌더링, each를 이용한 반복문도 가능하다.

기타

Vue.js는 컴패니언 라이브러리로 라우팅을 위한 vue-router와 Vuex를 함께 제공합니다. 선택적으로 사용하겠지만 프로젝트 규모가 조금만 커져도 사용을 피할 수 없습니다. 저는 개발하는 거의 모든 프로젝트에서 사용하고 있습니다. Vue.js가 View 레이어를 담당한다고 하지만 실제로 추가로 설치하는 의존성들이 많습니다.

Ember.js는 프레임워크이기 때문에 컴포넌트, 라우터, 서비스(글로벌 상태관리로 사용)를 제공하고 있고, 추가적으로 데이터 모델 유틸리티와 API 호출을 담당하는 Ember Data라는 도구도 포함되어있습니다.

이전 Ember.js에서 Ember Octane으로 업데이트 되면서 API가 많이 간소화 되었습니다.

Ember.js Octane vs Classic Cheat Sheet에서 클래식 컴포넌트와 Ember Octane 버전의 차이를 보실 수 있습니다.

Glimmer.js API는 더욱 놀라운데 외부에 노출된 기능은 두가지 밖에 없습니다. Ember Octane으로 넘어오면서 컴포넌트 기반 개발에 필요한 것만 남기고 모두 Plain JavaScript를 사용하자고 작정이라도 한 느낌이 듭니다.

  • @glimmer/component
  • @glimmer/tracking

그러면 Ember Octane을 써야만 하는가?

Ember.js를 써야하는가? 라고 질문을 하는 분이이 있다면 조금 회의적입니다. 오히려 이렇게 질문 하고 싶습니다.

  • 최근 프론트엔드 뉴스 혹은 커뮤니티에서 Ember.js, Ember Octane, Glimmer.js를 들어본 적 있나요?
  • Ember.js 개발자를 찾는다는 이야기를 들어본적 있나요?

다만 추천한다면 주변인들 그리고 커뮤니티의 의견에 별로 영향을 받지 않고 자신에게 맞는 프론트엔드 프레임워크를 찾는 경험을 하고 싶다면 추천하고 싶습니다. 다른 진영의 개발 방식은 원래 사용하는 개발 방식을 변화하는 아이디어를 주곤 합니다. Ember.js를 쓰다가 Vue.js를 몇년동안 쓰고 있는 것 처럼 큰 변화를 줄 수도 있습니다. 그리고 Ember.js의 기본인 Convention over Configuration 에 공감하는 개발자라면 더할나위없이 좋은 도구라고 생각한다.

생태계는 Vue.js에 비해서 말도 못하게 열악한데, Ember Observer에 올라온 Ember.js 관련 라이브러리만 보아도 알 수 있습니다. UI 라이브러리만 해도 몇 페이지가 나오는 Vue.js에 비하면 거의 지원이 없다고 생각이 들 정도입니다. 그나마 위안이 되는 것은 최근에는 tailwind.css가 있어서 외부 UI 라이브러리를 쓰는 일이 적다는 점입니다.

말려도 Ember.js를 해보고 싶다면

Ember.js를 배우고 싶은 분께 추천하는 웹사이트 목록입니다.

구글에 검색할 때 Ember Octane 이라고 검색해야 최근의 정보를 얻으 실 수 있습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment