라이너 익스텐션 개발기 in 라이너콘

안녕하세요? 라이너 프론트엔드 개발자 마크입니다.


지난 5월 24일, 라이너는 ‘도약’이라는 주제로 자체 기술 세미나 ‘라이너콘’을 개최했는데요. 감사하게도 많은 분들이 관심 가지고 참여해주신 덕분에 성공적으로 마칠 수 있었습니다.


총 3개의 기술 세션과 3개의 라이트닝 토크 중, 저는 ‘하이라이팅 유틸리티, AI 프로덕트가 되다’라는 주제로 세미나의 첫 세션을 맡게 되었습니다. 현장에 참여하시지 못한 분, 혹은 라이너가 익스텐션을 어떻게 개발했는지에 대해 관심이 있으신 분들을 위해 발표 내용을 공유드리고자 합니다 😃

라이너콘!




제3의 플랫폼, 익스텐션


익스텐션은 웹, 앱과 다르게 유저가 브라우징하는 모든 페이지에서 컨텐츠를 제공할 수 있습니다. 최근 ChatGPT의 등장과 함께 많은 주목을 받고 있으며 제3의 플랫폼으로 빠르게 도약하고 있습니다.


라이너도 익스텐션으로 유저들이 빠르게 똑똑해질 수 있도록 도와줍니다. 정보 수집을 도와주는 하이라이팅 기능으로 시작해서, 현재는 읽고 있는 페이지에 대한 정보 탐색을 도와주는 AI 기능까지 제공하고 있습니다. 익스텐션으로 유저가 수집한 문서는 라이너의 추천 풀에 들어가게 되고, 수집한 행동 로그를 이용해 유저들에게 개인화 추천을 제공합니다.

정보 탐색을 도와주는 Copilot 기능



익스텐션의 동작 원리


그렇다면 익스텐션은 어떤 방식으로 동작할까요?

보통의 웹 페이지는 그림과 같은 구조를 가집니다. 브라우저를 사용하는 유저는 원하는 콘텐츠를 웹 서버에 요청하고, 웹 서버는 응답으로 해당 콘텐츠의 HTML, CSS, Javascript를 내려주죠.


일반적인 웹의 구조


해당 페이지가 로딩되면서 익스텐션도 자신의 정적 파일들을 현재 웹 페이지에 로드시킵니다. 그 파일들을 Content Script라고 하는데요. Content Script를 이용해 기존 웹 페이지의 UI를 바꾼다거나 새로운 UI를 추가하며 유저에게 여러 콘텐츠를 제공할 수 있습니다.



만약 원하는 정보가 익스텐션 외부에 위치해 있다면 어떻게 할까요? 동일 도메인이 아니기 때문에 Content Script에서 Server로 직접 요청을 보내면 우리가 잘 아는 CORS 에러가 발생합니다. 따라서 Content Script는 익스텐션에서 일종의 프록시 서버와 같은 역할을 하는 Service Worker로 대신 요청을 보냅니다. 그러면 Service Worker가 서버로 요청을 보내고, 응답을 받아 Content Script에 전달합니다.


즉, 익스텐션은 Content Script와 Service Worker 사이의 통신에 의해 작동합니다.

Content Script와 Service Worker의 통신 구조



라이너 익스텐션의 진화


혹시 포켓몬스터 다들 보셨나요?


요즘 팀에서 라이너 프로덕트를 포켓몬에 빗대어 많이 이야기하고는 하는데요. 캐터피가 진화해서 단데기가 되고, 단데기가 진화해서 버터플이 되는 것처럼 라이너 익스텐션도 하이라이팅 유틸리티로부터 시작되어 AI 프로덕트로 진화했습니다. 라이너라는 제품이 캐터피에서 버터풀이 되기 위해서 가장 중요한 것은 개발 속도였고, 이에 맞춰 코드 베이스도 진화해야만 했습니다.



기존 코드 베이스에는 빠른 개발을 방해하는 페인 포인트가 세 가지 있었습니다.

  1. 8천 줄 이상의 긴 코드
  2. 모듈화 부재
  3. 통일되지 않은 인터페이스

위 문제들을 해결하지 않고서는 라이너는 빠르게 나아갈 수 없었습니다. 라이너 프론트엔드 개발자들이 어떻게 이 문제를 정의하고 해결했는지, 그리고 그 과정에서 어떻게 성장했는지 이야기를 나눠보고자 합니다.



페인 포인트 1 – 8천 줄 이상의 긴 코드


긴 코드에서 발생하는 문제점은 크게 두 가지가 있었는데요. 하나는 단일 파일로 큰 기능을 동시에 각각 개발할 때 발생하는 코드 컨플릭트였고, 다른 하나는 Lint와 Prettier가 돌지 않을 정도로 코드가 길어 퀄리티가 잘 관리되지 못했다는 점입니다. 이는 결국 속도 저하로 이어졌습니다.


가장 단순하고도 효과적인 해결책은 코드를 나누는 것이었습니다. 단일 코드 파일을 순서대로 분리하였습니다. 개발 환경에서는 나누어져 있는 게 편하지만, 프로덕션 환경에서는 성능을 위해 Shell Script로 코드를 통합하도록 했습니다. 덕분에 생산성과 성능, 두 마리 토끼를 모두 잡을 수 있었습니다.



결국 평균 8,000줄의 긴 코드에서 평균 400줄 이하의 작은 코드로 나눌 수 있었고, 이는 기능 개발에 큰 도움이 되었습니다.


라이너 익스텐션 코드 베이스는 이제 알에서 깨어나 캐터피가 되었습니다.



페인 포인트 2 – 모듈화 부재



모듈화가 되어있지 않아서 코드의 재사용이 어려웠고, 이는 생산성 저하로 이어졌습니다. 또한 모듈화가 되어있지 않아 전역 변수로 상태를 관리하다보니 중복된 식별자 문제가 발생했고, 순서대로 코드를 실행시키다보니 순서에 의존적인 구조가 되었고, 결국 잦은 버그가 발생했습니다. 개발 속도 뿐만 아니라 기존 제품을 유지 보수하는 비용도 중요했기에 모듈화는 중요한 사안이었습니다.

라이너 프론트엔드 플래닛은 모듈화 문제를 해결하기 위해 가장 대표적인 번들러인 Webpack이라는 도구를 사용했습니다.

모듈화가 되면서 기존의 어려움을 해결할 수 있었는데요. 기존에는 코드의 재사용이 어려웠지만, 모듈화가 되면서 컴포넌트나 함수를 재사용 할 수 있게 되었습니다. 중복된 식별자 문제도 더 이상 전역 변수가 아니라 모듈, 클로저를 사용함으로써 해결할 수 있었습니다. 순서에 의존적인 구조도 의존성 관계가 정립되면서 자연스럽게 해결되었습니다.

물론 번들러를 도입하고나서 발생했던 Trade-Off도 존재했습니다. 모든 코드를 옮기지는 못해 기존 Context가 남아 있었고, 번들러를 통해 만든 영역과 분리되면서 단절되었고, 개발과 빌드 과정 중간에 블랙 박스가 들어가다보니 개발, 빌드 과정이 추상화 되어 러닝 커브가 생겼습니다.

모듈화의 장점이 너무 컸기에, 생길 수 있는 문제점에 대해 너무 깊게 고민하기보다는 빠르게 도입해보고 조금씩 이터레이션 하자는 결론을 내렸습니다. 결국 라이너 익스텐션 코드 베이스는 기능별로 모듈화가 되었습니다.

그리고 결론은 성공이었습니다. 개발 생산성이 더욱 향상되었고, 이제 라이너 익스텐션 코드 베이스는 한 단계 더 진화해서 단데기가 되었습니다.


페인 포인트 3 – 통일되지 않은 인터페이스



기능의 수가 많아지면서, 각각의 기능에 대한 의존성도 점점 커졌고 그때부터 고민이 시작되었는데요. 문제는 각각의 컴포넌트가 독립적으로 개발되다보니, 서로 사용하는 인터페이스가 달랐다는 점입니다. 각각 컴포넌트를 그리고 지우는 함수를 만들 때, 어떤 컴포넌트는 render와 hide, 어떤 컴포넌트는 draw와 erase 등 서로 구현과 인터페이스가 모두 제각기었습니다.

제각기였던 컴포넌트 인터페이스



어떤 차를 보아도 핸들, 악셀, 브레이크와 같은 공통의 인터페이스를 가집니다. 또한 공통의 인터페이스는 모두 겉으로 보았을 때 같은 역할을 수행하고, 그 원리, 즉 구현은 철저히 숨겨져 있습니다. 그래서 사람들은 인터페이스만 익힌다면 어떤 차를 타더라도 구현에 관계 없이 운전할 수 있습니다. 이것이 공통의 인터페이스와 캡슐화가 가지는 장점이라고 볼 수 있습니다.

자동차의 구현과 인터페이스



구현과 실제로 동작 방식도 다르다보니, 개발자는 기존 컴포넌트를 건드릴 때 인터페이스 뿐만 아니라 구현까지 모두 알아야 했습니다. 조금은 엄격하고 통일된 인터페이스가 필요했습니다.

고민 끝에 완성도가 높으면서도, 팀원들이 모두 운전할 수 있는 인터페이스를 가진 리액트라는 자동차를 도입하였습니다. 리액트를 도입함으로써 많은 장점을 누릴 수 있게 되었습니다. 모든 사람이 같은 인터페이스를 사용하면서 개발 생산성이 향상되었고 React Query와 같은 편리한 리액트 생태계 라이브러리를 사용할 수 있게 되었습니다. 자연스럽게 생산성도 높아졌습니다.

인터페이스까지 통일되면서 라이너 익스텐션 코드 베이스는 버터풀로 진화하기 위한 준비를 마쳤습니다.

이외에도 많은 변곡점들이 있었지만, 크게는 파일 분리, 모듈화, 그리고 리액트를 도입함으로써 라이너 익스텐션 코드 베이스는 진화할 수 있었습니다. 하지만 무엇보다 중요한 건 복잡도가 높은 기능들도 익스텐션에서 안정적으로 개발할 수 있게 되었다는 점입니다. 덕분에 라이너는 하이라이팅 유틸리티에서 AI 프로덕트로 무사히 진화할 수 있었습니다.


What We Learned?



여태까지 프로덕트와 코드 베이스의 진화에 대해서만 이야기했는데요. 이제 그 과정 속에서 팀의 성장에 대해 이야기 해 보겠습니다. 약 2년 간 익스텐션 개발은 소중한 경험이었고, 많은 것들을 배울 수 있는 기회였습니다. 그 중 가장 큰 배움 3가지에 대해 이야기 해 보겠습니다.

보통 프론트엔드 개발을 하면 모든 게 갖춰진 프레임워크 속에서 개발하는 일이 많습니다. 그렇게 되면 이 기술이 왜 필요한지, 어떤 문제를 해결해주는지에 대해 깊게 생각하지 못하는 경우가 많습니다. 저도 마찬가지였고요. 바닐라 자바스크립트 기반 프로젝트에서 Webpack과 Babel, 그리고 리액트까지 도입하다보니 기존 기술들에 대한 이해도가 높아졌고, 새로운 기술 스택을 도입하는데에 대한 자신감이 생겼습니다.

비교적 최근까지 리액트 기반 구조가 아니었기에 바닐라 자바스크립트 개발을 많이 경험할 수 있었습니다. 다른 사이트의 DOM을 많이 조작해야 하기에 DOM 기반 라이브러리도 많이 공부할 수 있었고, 정보를 저장하는 store를 만들기 위해 클로저를 사용하다가 클래스 기반 문법으로도 개발할 수 있는 기회도 있었습니다. 컬렉션이나 객체를 관리하기 위해 웹 개발보다는 조금 더 다양한 기법들을 사용하다보니 자연스레 이해도도 많이 높아졌습니다.

마지막으로 리액트 기반의 사고가 아닌, 조금 더 다양한 영역의 사고를 할 수 있었습니다. 컴포넌트를 어떻게 역할에 맞게 만들 수 있을 지 고민하는 것뿐만 아니라 어떻게 하면 컴포넌트 자체를 만들 수 있을 지, 비동기 데이터를 어떻게 하면 조금 더 잘 관리할 수 있을 지 고민을 할 수 있었고 이는 라이너 프론트엔드만의 특별한 무기가 되었습니다.


마치며



마지막으로 라이너 프론트엔드 플래닛이 해결하고자 하는 문제 3가지에 대해 소개하고 마치려고 합니다.

  1. 리액트 도입 이전에 작성되었던 코드 중, 임팩트 순으로 리액트로 포팅을 진행하고자 합니다.
  2. 클라이언트 캐싱 로직을 고도화해 하이라이트 정보를 빠르게 띄워 유저 경험을 개선하고자 합니다.
  3. 번들 파일을 기능에 따라 분리하여 Content Script 용량으로 인한 브라우저 경험 저해를 최소화하고자 합니다.


라이너에서는 기술 세미나 말고도 매주 전사 미팅 이후의 패스터 세미나, 플래닛 위클리 미팅에서의 지식 공유 등 주기적인 공유 문화를 이어가고 있습니다. 라이너가 공유 문화를 지향하는 이유는 나의 배움과 정보가 다른 사람들에게 결정적인 도움이 될 수 있고, 만약 내가 틀렸다면 이를 빠르게 알고 배울 수 있기 때문입니다. 라이너 팀원들은 개인의 성장 뿐만 아니라 팀의 성장을 위해 오늘도 배움을 공유하고 있습니다.

빠르게 성장하면서도 배움을 나누고 싶으신 분이 있으시다면 언제든지 연락주시면 감사하겠습니다 😉



라이너 채용 페이지 바로가기