HTML5로 서비스 만들기

펄핏 Perfitt·2022년 3월 2일
4
post-thumbnail

안녕하세요, 저는 펄핏에서 솔루션 개발 리드를 맡고 있는 마니입니다.
제가 펄핏에서 HTML5 표준을 바탕으로 개발한 발 측정 모듈을 간단하게 소개해드리며 웹 개발의 트렌드나 변화에 대한 소소한 제 생각들을 공유하고자 합니다.

시작하기에 앞서, 여러분은 Web FrontEnd의 기본 소양이 무엇이라고 생각하시나요?

누군가는 React, Vue, Angular 등의 프레임워크를 사용할 줄 알아야 한다, 혹은 JQuery를 이용하여 사용자가 볼 수 있는 View만 구현할 수 있으면 된다 등 널리 쓰이는 라이브러리나 프레임워크에 치중되어 있는 말을 많이 하는 것 같습니다.

그렇다면, 2022년 웹 기반 서비스가 응용 개발의 70% 이상을 차지하고 있는 시대에 살고 있는 웹 개발자의 한명으로서, 추상화가 많이 이루어진 라이브러리, 프레임워크에서 벗어나 HTML5 표준을 이용하여 개발했던 경험은 어땠을까요? 제가 한번 이야기 해보겠습니다.

1. HTML5

아시다시피 HTML5은 WHATWG와 W3C가 발표한 가장 최신 버전의 HTML이고, 2014년 10월 28일부터 완전히 표준이 되어버린 마크업 언어입니다. (프로그래밍 언어가 아니에요)

HTML5는 HTML의 div, p, a 등과 같은 태그에 대한 정의만 포함된 개념이 아닌 DOM에서 사용되는 API 스펙까지 정의 해놓은 정의서입니다. HTML5는 정의서 이므로 Chrome, Webkit, Mozilla 등 Browser Engine이 지원해주는 것 까지만 이용할 수 있고 정의서에 있다고 모든 기능을 사용할 수는 없습니다. 이런 기능적인 제한을 웹브라우저 수용도라 하는데 2021/04/20 기준 웹브라우저 수용도는 555점을 만점 기준으로 아래표와 같습니다.

위의 표와 같이 각 브라우저별 다른 수용도를 가지고 있으므로 CSS를 작업할 때마다 브라우저에 따라 다른 형태, 다른 꼴을 보게 되는 것입니다.


2. Javascript (ECMAScript)

웹 개발자라면 HTML5 이야기를 하는데 단짝 친구 Javascript를 얘기하지 않으면 너무 슬플 것 같아 Javascript에 대한 이야기도 잠깐 해보고자 합니다.

한 번쯤은 ES6, ES7 등 ECMA script 등에 대해서 들어보셨나요? HTML은 그저 마크업 언어이기 때문에 문서의 구조, 데이터 외에는 표현을 하기 힘듭니다. 해당 마크업 구조가 사용자의 인터렉션에 의해 변경된다거나 이미 그려진 DOM에 실시간으로 데이터를 추가하는 과정은 Javascript의 도움 없이 힘든 것이 현실입니다.

document.querySelector("p").innerHTML = "사랑해요 펄핏";

저 역시 칙칙한 화면을 조금 더 액티브하게 만들어 보고 싶어 위와 같이 Javascript를 이용해 본 적이 있습니다. 최근에는 Javascript 관련 기술이 많다보니 별도의 라이브러리나 프레임워크가 포함되지 않은 순수 Javascript를 Vanillar JS라고 부르기도 합니다만, 좀 더 본격적인 Javascript 얘기는 전문 서적의 영역으로 남겨두고 저는 펄핏에서 가장 많이 사용되는 React에 관련된 이야기를 해보고자 합니다.

2-1. React Framework? Library?

React는 라이브러리입니다. 라이브러리와 프레임워크 둘 다 비슷하면서 다른 부분들이 많은데 라이브러리는 프레임워크의 일부니 비슷한 부분이 많을 수 밖에 없다고 생각합니다. 제가 생각하는 프레임워크와 라이브러리의 가장 큰 차이는 할리우드 원칙이 잘 설명하고 있는 것 같습니다.

할리우드 원칙을 간단하게 말씀드리면 [라이브러리는 개발자가 이미 정의된 함수를 호출하는 것] 이고, [프레임워크는 Life-Cycle이 개발자가 정의한 함수를 호출하는 것] 입니다.

하지만 현재 개발자들이 React를 어떻게 사용하는지 보면 Create React App를 통해 React Boilerplate를 만들고 Redux를 통해 상태를 관리하며 React-Router-Dom를 이용하여 History를 조작 하는 것을 많이 하는 듯 합니다. 더 이상 View만 집중하고 있는 React 만을 이용하여 개발하기 힘든 생태계를 갖고 있다고 볼 수 있습니다.

그래서 저는 React가 이제 프레임워크에 가깝다고 생각합니다. React 자체로 라이브러리 언어도 개발하는 환경이 이제 그 레벨을 넘어선 게 아닌가 합니다.

2-2. React Standalone

많은 분들이 CRA를 이용하여 React의 Boilerplate를 만들어 사용하다보니 React는 Standalone으로 사용할 수 있다는 사실을 잊고 있지는 않을까요?

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
      <App />
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

위 코드는 CRA를 이용하여 Project를 만들면 생성되는 src/index.js 파일 입니다. ReactDOM.render 부분을 자세히 보면 document.getElementById#root를 가져와 React를 렌더링하겠다 라는 소스 코드로 보입니다.

그럼 위 코드의 #root는 어디에 있을까요? 그건 바로 public/index.html 안에 있습니다.

public/index.html

<!DOCTYPE html>
<html>
  <head>
		<!-- 중략 --!>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

이처럼 id가 "root" 인 div만 있으면 어느 환경에서도 만들 수 있습니다. 예컨데 JSP, PHP 등의 다른 SSR 형태로 개발된 동적 / 정적 HTML이여도 Webpack으로 Compile된 JS 파일만 있으면 사용할 수 있다는 뜻이며, 이 부분은 Angular JS가 엄청난 위용을 갖고 있을 때 React의 초기 세일즈 포인트였습니다. 하지만 지금은 딱히 신경을 안 쓰는 부분인 것 같습니다.

2-3. JSX

과거에는 JQuery를 이용한 직접적인 DOM 제어로 html 태그에 대한 구조적 이해가 어느정도 바탕이 될 수 있었지만, 최근에는 React로 웹 프론트 개발을 시작하시는 분들이 많아지면서 간혹 DOM 구조에 대한 이해도가 부족한 경우를 종종 보게 됩니다. 그래서인지 JSX를 처음부터 사용하다 보면 다음과 같은 코드가 html DOM 구조를 직접적으로 변경한다고 착각할 수 있습니다.

다음은 간단하게 p를 클릭하면 해당 텍스트가 사랑해요 펄핏으로 변경되는 기능을 React와 Vanilla JS로 구현해본 코드 입니다.

React JSX

function Perfitt() {
	const [ text, setText ] = React.useState("");
	/* ... 중략 ... */
	return (
		<p onClick={() => setText("사랑해요 펄핏")}>{text}</p>
	)
}

Vanilla JS

<html>
	<!-- 중략 --!>
	<p onClick="changeText()"></p>
	<!-- 후략 --!>
	<script>
		function changeText() {
			document.querySelector("p").innerHTML = "사랑해요 펄핏";
		}
	</script>
</html>

이 간단한 코드들의 동작은 똑같을까요?

정답은 처음은 같아도 두번째 부터는 다르게 동작한다 입니다. 기본적으로 Vanilla JS로 작성된 부분은 textNode를 생성하여 기존에 있었던 사랑해요 펄핏을 지우고 다시 생성하는 방식입니다. 하지만 JSX는 가상의 DOM을 이용해서 더블 버퍼링하는 개념에 가까워 첫 번째 클릭 시에는 똑같이 동작 하더라도 두 번째 클릭 시에는 React의 JSX는 기존의 DOM과 비교해서 바뀐 부분만 변경하기 때문에 위는 textNode를 더 이상 생성하지 않고 비교만 하고 끝날 것입니다.

같은 DOM을 제어하는 기능이지만 그 동작 구조는 사뭇 다르게 되어있는 것이 흥미로운 부분이지요.


3. HTML5로 서비스 개발하기

동일한 제목을 한 10년 전 쯤 HTML4, XHTML에서 HTML5로 넘어가는 시기 즈음 나온 컬럼에서 본 것 같네요. 하지만 제가 직접 이런 제목으로 포스팅 하는 것은 실제 업무에서 React, Vue, jQuery 등 이미 너무 무거워진 라이브러리나 프레임워크를 버리고 HTML5 API의 발전을 경험한 것이 큰 도움이 되었기 때문입니다.

이번에 펄핏에서 개발하고자 했던 것은

  1. 웹 환경에서
  2. 카메라를 사용하여 발을 촬영하고
  3. TensorFlow.js를 이용하여 간단한 이미지 검사를 하는 것이었습니다.

여기에 사용된 카메라 모듈은 사내 여러 기능에서 재사용될 것으로 예상되어 npm 혹은 js 파일로 제공하는 것을 목표로 했습니다.

3-1. DOM 조작

HTML5을 활용하지만 결국 웹 환경의 서비스를 개발하다 보면 DOM 조작 할 일이 생깁니다. 이번 경우에는 발 사이즈를 서버에서 받아와 프론트에서 보여주는 작업에서 필요했기에 어떤 Javascript 기술을 사용하는 것이 좋을지 고민이었습니다.

우선 React, jQuery, Vanilla JS 세 가지 케이스를 보면서 DOM 조작을 어떻게 하는지 살펴 보겠습니다.

React

function ChangeText() {
	const[ foot, setFoot ] = React.useState(null);
	
	React.useEffect(()=>{
		Axios(/* 중략 */).then(res => {
			setFoot(res.data.foot);
		}).catch(console.warn);
	}, [ setFoot ]);	

	return (
		{ foot && <p>{foot.width} / {foot.length}</p> }
	);
};

jQuery

$(document).ready(()=>{
	$.ajax({
		/* 중략 */
		complete: (data) => {
			$(document).append($("<p/>", { // p tag 생성
				text: `${data.foot.width} / ${data.foot.length}`
			}));
		}
	});
});

Vanilla JS

document.addEventListner("load", () => {
	fetch(/* 중략 */)
  .then((response) => response.json())
  .then(function(data) {
		const $p = document.createElement("p");
		$p.innerText = `${data.foot.width} / ${data.foot.length}`
		document.append($p);
  });
});

세 코드를 봤을 때 여러분 마음에는 무엇이 가장 마음에 드시나요?

세 가지 모두 특징이 있습니다. 우선 DOM 조작 관점에서 봤을 때 저는 jQuery가 제일 좋아 보입니다. 위에서 설명했듯 React는 직접적인 DOM 조작이 아닌 Virtual Dom을 이용하여 DOM을 변경하므로 그보다 직접적이고 간결해 보이지요.

Component로 만들 수 있다라는 관점에서는 React가 좋은 선택으로 보입니다. Component가 된다는 점은 추상화를 하기 쉽다는 이야기고 추상화를 하면 구현해야하는 Layer를 구분 할 수 있기에 개발자간의 협업도 더 편할 수 있습니다.

그렇다면 Vanilla JS는 어떤 관점에서 좋을까요? 정답은 jQuery와 React의 초기 실행 속도 및 낭비되는 코드에 비해 속도 측면에서 좋습니다. 우리가 하고자 하는 일은 데이터를 서버에서 받아와서 p 태그의 데이터를 변경해주는 일 뿐인데, React, Query의 Initialize 작업은 많은 리소스를 소모합니다.

저는 사진 촬영 기능과 TensorFlow.js 때문에 서버 작업이 많이 사용되고 있는 상황이라, 더 느려질 수 있는 가능성을 최대한 방지하기 위해서 Vanilla JS를 선택했습니다.

3-2. Web Camera

여러분은 과거 웹 환경에서 웹캠 혹은 컴퓨터의 기능을 이용하기 위해서 ActiveX를 설치했던 기억이 있으신가요? 저는 ActiveX로 개발해본 경험은 없지만 ActiveX 사용을 위해 필요한 것들을 간략하게 생각해보자면 MFC를 이용하여 Windows 기반의 ActiveX 컨트롤을 만들고 html안에 Object 태그로 만들어진 ActiveX를 삽입한 후 인터페이스를 정의해 컨트롤과 통신하는 작업을 해야 합니다.

작업 대부분은 webRTC에 정의된 기능으로 가능합니다. webRTC에 정의된 기능을 간단히 소개하면,

  • 스트리밍 오디오, 비디오 또는 데이터의 획득
  • 네트워크 정보의 획득 및 다른 WebRTC 클라이언트들과 정보 교환
  • 에러 보고, 세션 초기화 / 종료를 위한 Signal 통신 관리
  • 미디어와 클라이언트의 지원 기능(해상도와 코덱 등) 에 대한 정보 교환
  • 스트리밍 오디오, 비디오, 데이터의 송수신

과 같습니다. 상당히 훌륭한 기능들이 많지만 펄핏에서 필요한 것은 카메라 기능이므로 스트리밍 비디오 기능을 통해 구현 하였습니다. 그 과정을 간단히 표현하면 다음과 같습니다.

  1. Video Stream을 userMediaStream으로 받아온다.
  2. 받아온 Video Stream을 Frame 단위로 캡쳐하여 canvas 위에 그린다.
  3. canvas 위에 필요한 가이드 라인을 그린다. 사용된 HTML5 API의 Code Snipets을 보여 드리면,
// 카메라 stream 가져오기
const videoConstants = { 
    video: { 
        facingMode: { exact: "environment" } 
    }, 
    audio: false 
};

async function getUserMediaStream() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia(videoConstants);
        return stream;
    } catch(e) {
			/* 중략 */
    }
};
...
...
// canvas 및 software zoom 기능
function getCaptureImage(video, zoom) {
    const _canvas = document.createElement("canvas");
    const width = video.videoWidth;
    const height = video.videoHeight;

    const targetRatio = _canvas.height / video.videoHeight;
    let x = (_canvas.width - width * targetRatio * zoom) / 2;
    let y = (_canvas.height - height * targetRatio * zoom) / 2;
    _canvas.getContext('2d').clearRect(0, 0, _canvas.width, _canvas.height);
    _canvas.getContext('2d').drawImage( video, 
        0, 0, width, height,
        x, y, width * targetRatio * zoom, height * targetRatio * zoom);
    return _canvas;
};

위와 같이 구성됩니다.

이처럼, webRTC와 Canvas를 이용해 Javascript 만으로 손쉽게 아래와 같은 카메라 기능을 만들었습니다.

3-3. TensorFlow.js

카메라에서 촬영된 사진을 검증하기 위한 TensorFlow 모델을 구동해야 했습니다. TensorFlow 모델은 서버에서도 구동이 가능하지만 속도 측면에서 이점을 얻고자 프론트 환경에서 구동하면 어떨까라는 생각을 하였고, 구글에서 만들어주신 TensorFlow.js 를 이용하여 프론트에서 구현해 보았습니다.

let model;
async function init() {
    model = await automl.loadObjectDetection('model.json');
};

async function detectKitObjects(video) {
    if(!model) return
    try {
        const predictions = await model.detect(video, { });
				/* 중략 */
    } catch(e) {
        console.warn(e);
    }
};

export { init, detectKitObjects };

위처럼 몇 안되는 코드로 사진에서 간단한 Object Detection 모델을 이용할 수 있었습니다.

TensorFlow.js에서 백엔드로 사용할 수 있는 기술이 여러가지 있는데 그 중 WebGL 혹은 WebAssembly 등은 HTML5의 API 이므로 큰 관점에서 TensorFlow.js도 HTML5 API의 연장선이 아닐까 합니다.

TensorFlow.js 부분은 그 자체만으로도 많은 내용이 있으므로 꼭 다음에 한번 더 포스팅 하고 싶네요.


4. 마치며

서비스를 개발할 때 React, Vue와 같은 프레임워크를 사용하면 확실히 협업과 개발 속도에서 상당한 이 점이 있는 것이 맞습니다. 하지만 여러분이 프론트엔드 개발자를 지향하고 있으면 Javascript에 대한 이해도를 높이기 위해 한 번쯤은 Vanilla JS와 HTML5로 프로젝트를 진행해 보셨으면 하는 것이 저의 추천 사항입니다.

이번 글에서 소개하지는 못했지만, 개발하면서 경험한 하위 버전의 브라우저를 지원하기 위해 만든 Webpack, Babel(혹은 Parcel) 등 Precomplier에 대해 설정하는 것도 재밌있고 유익한 경험이었습니다.

좋은 프로덕트 개발을 위해 고민하고 계신 분들에게 작은 도움이 되길 바라며 첫번째 글을 마치겠습니다.

profile
Beyond The Size Limit

0개의 댓글