210509 JavaScript engine과 Web API 그리고 Callback queue


자바스크립트의 비동기 코드가 runtime에 의존적이라고 하는 이유에 대한 의문을 시작으로 이번 포스팅을 시작하려고 한다. 이전에 비동기 함수를 테스팅하는 과정에서 에러가 발생을 했었는데 이때 비동기 코드가 runtime에 의존적인 결과코드이기 때문에 별도의 설정을 해줘야 했고, 이 에러를 겪고 해결해나가기 전에는 그 이유에 대해서 잘 몰랐었기 때문에 혹시 과거의 나와 같이 의문을 갖는 사람들을 위해 포스팅을 해보려고 한다.

이 이유에 대해서 이해하려면 자바스크립트 코드의 실행과정에 대해서 우선적으로 이해를 해야한다.

  • 자바스크립트 코드의 실행과정

자바스크립트에서는 작성된 코드들은 call stack에 쌓인 후에 실행이 된다. 하지만 JS엔진에서는 비동기 작업을 지원하지 않기 때문에 코드들 중에서 비동기 작업을 Web API(browser에 의해 제공)에 위임을 한다.

  • JS runtime engine의 역할

    JS runtime engine은 standard JS 코드를 실행하는 역할을 하는 JS engine(V8 for chrome)로 아래와 같은 프로그래밍 언어로써 독립적으로 요구되는 특징들을 지원한다.

    • variable, functions,
    • scoping, scope chaining, execution context, execution scope
  • Web API의 역할

    • Web API는 Browser로부터 제공되는 wrapper로, 표준 JS 프로그래밍 언어의 일부가 아니며, 아래의 기능들을 지원한다.

    • ajax, events(onkeyPress, onBlur와 같은 이벤트들)

    • console.log, window object, DOM을 읽고 쓰기와 같은 기능

    • 비동기 작업(Asynchronous task)지원

    • 표준 JS 언어는 위와같은 기능들을 지원하지 않기 때문에 브라우저는 JS 엔진을 브라우저의 custom wrapper들로 감싸서 웹에서의 여러 조작들이 지원 가능하도록 한다.
      (위와 같은 이유로 browser가 JS의 runtime으로 불린다)

      cf) Node는 JS에게 위와 같은 기능들을 제공하는 server side에서 동작하는 runtime이다.

    Read More

210509 ReferenceError regeneratorRuntime is not defined


NodeJS 프로젝트를 진행하면서 비동기로 작성한 함수를 위한 테스트 코드를 작성하고 실행을 했는데 ReferenceError: regeneratorRuntime is not defined 에러가 발생했다.
이전에 ReactJS 프로젝트를 진행하면서 테스트 코드를 작성했을때도 위와 똑같은 에러가 발생했었는데 이러한 경우에는 아래의 두 방법으로 해결을 할 수 있다.

Solution 1) regenerator-runtime를 설치해서 비동기 함수를 작성한 파일의 최상단에 import 'regenerator-runtime/runtime';를 선언해주면 간단하게 해결할 수 있다.

Solution 2) .babelrc 의 설정을 아래와 같이 업데이트한다.

Using @babel/preset-env without that target set will result in the tests using async failing with ReferenceError: regeneratorRuntime is not defined.

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}

이 regenerator-runtime이 무엇인가 한 번 구글링을 해보니,
regenerator-runtime은 compiled/transpiled 비동기 함수들을 위한 runtime을 지원해주는 녀석이라고 한다.
Babel과 같은 컴파일러는 자바스크립트 syntax에 대해서 최신 문법을 이전 문법으로 변환을 해주지만, runtime에 의존적인 결과 코드(비동기 코드)들은 regenerator-runtime으로부터 지원을 받는다고 한다.

210505 screen.getBy*와 getBy의 차이, findBy*/getBy*/queryBy* method의 차이


테스트 코드를 작성하던 도중에 헷갈렸던 개념들에 대해서 반복학습을 위해 포스팅을 해두려고 한다.

screen.getBy vs getBy

screen을 사용하는 이점은 필요한 query를 추가할 때 더 이상 render 함수로 컴포넌트를 렌더링해서 최신 상태를 유지 하지 않아도 된다는 점이다.
beforeEach()함수 내에서 공통적으로 컴포넌트를 렌더링했다면, 이하의 test 블럭에서는 screen을 통해 렌더링된 컴포넌트를 통해 테스트 코드를 작성할 수 있다.

findBy* / getBy* / queryBy* method의 차이

  • getBy*

    조건에 맞는 요소를 찾았을때 query에 일치하는 노드를 반환
    조건에 맞는 요소를 못 찾았을때 에러를 반환
    → 존재하지 않는 DOM element를 assert할때에는 try-catch문으로 발생한 에러를 별도의 함수에 담아 tobedefined() matcher로 확인을 해야 한다. 이 경우에는 queryBy*를 사용해서 assert하는 것이 좋다.

  • queryBy*

    조건에 맞는 요소를 찾았을때 query에 일치하는 노드를 반환
    조건에 맞는 요소를 못 찾았을때 null을 반환
    → 존재하지 않는 DOM element를 assert할때 유용하다.

  • findBy*

    조건에 맞는 요소를 찾았을때 resolved Promise 반환
    조건에 맞는 요소를 못 찾았을때 rejected Promise 반환

    기존의 waitForElement()함수는 이제 deprecated되었기 때문에 findBy* 함수를 사용해야 한다.

    1
    2
    3
    4
    5
    6
    test('nowPlaying API method 테스트 - 데이터가 문제없이 화면에 로드되는지 확인', async () => {
    const { findByTestId } = render(<Movie />);

    await findByTestId('loading-text');
    await findByTestId('contents-list');
    });

210504 Error) You should not use Link outside a <Router>


<StaticRouter>와 <MemoryRouter>를 사용

이번에 컴포넌트 단위로 단위테스트를 하던 중에 You should not use XXX and <withRouter> outside a <Router>에러를 접했다.
Router의 내부에서만 사용할 수 있는 <withRouter>와 <Link>를 사용한 컴포넌트를 테스트를 할때에는 개별 컴포넌트만 호출해서 렌더링한 후에 테스트를 진행하기 때문에 실제로는 Router 내에 구현이 되어있다고 하더라도 위와같은 에러가 발생하게 되는 것이다.

위의 에러에 대한 해결책은 테스트 코드에서 <MemoryRouter> 또는 <StaticRouter>를 사용해서 테스트하려는 컴포넌트를 감싸서 렌더링해주면 된다.
Header.test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import Header from './Header';
import { MemoryRouter } from 'react-router';



describe('header 컴포넌트 내부의 메인 메뉴관련 테스트 코드', () => {
let header;
beforeEach(() => {
header = render(<MemoryRouter><Header/></MemoryRouter>)
});

describe('메뉴 클릭시 제대로 연결된 페이지가 렌더링되는지 확인하는 테스트 코드', () => {
test('Movie 메뉴 클릭시 Movie page component가 렌더링된다.', () => {
const { getByTestId } = header;
expect(getByTestId('movie-menu')).toBeTruthy();
fireEvent.click(getByTestId('movie-menu'));
expect(screen.findByTestId('movie-page-container')).toBeInTheDocument;
});
......

210504 React TDD Practice with RTL

RTL(React Testing Library)

RTL(React-Testing-Library)는 모든 테스트를 DOM 위주로 한다. 별도로 props나 state를 조회하는 테스트는 하지 않는다. 그 이유는 컴포넌트를 리팩토릴할때 내부 구조와 네이밍은 많이 바뀔 수 있어도 실제 동작방식은 크게 바뀌지 않기 때문이다.
따라서 RTL에서는 컴포넌트의 기능이 똑같이 동작한다면, 컴포넌트의 내부 구현 방식이 바뀌어도 실패하지 않도록 테스트를 지원한다.

  • DOM 시뮬레이션은 JSDOM이라는 도구를 사용하여 document.body에 React 컴포넌트를 렌더링한다.

  • @testing-library/jest-dom 은 DOM관련 matcher를 사용할 수 있게 지원해주는 라이브러리이다.

snapshot 테스트

1
2
3
4
5
6
7
8
9
10
import { render } from '@testing-library/react';
import Profile from './Profile';

describe('<Profile /> 컴포넌트 테스트', () => {
test('snapshot 테스트',() => {
const snapshot = render(<Profile surname="lee", givenname="hyungi" />);
// container는 검사하는 컴포넌트의 최상위 DOM을 가르킨다.
expect(snapshot.container).toMatchSnapshot();
});
});

Read More

210502 React with TypeScript TIL - keyof와 typeof의 혼합 사용, JavaScript의 Curring 방식의 함수 작성

React with TypeScript

keyof와 typeof의 혼합 사용

특정 변수의 타입을 정의할때 만약에 해당 타입이 다른 타입과 종속적인 관계에 있다면, 종속관계에 있는 해당 타입의 정의를 참조해서 타입을 정의해야 한다.

아래는 스프라이트 기법으로 이미지를 잘라내서 사용하기 위한 이미지 좌표의 타입 정의이다.

1
2
3
4
5
6
7
8
const coords = {
img1: '0',
img2: '-142px',
img3: '-284px'
} as const;

type a = keyof typeof coords; // coords 타입(typeof)의 key 값(img1 | img2 | img3)
type imgCoords = typeof coords[keyof typeof coords] // '0' | '-142px' | '-284px'

Read More

210502 Front-end Side Project Report 1/2일차

나의 사이트 프로젝트 기록

Front-end Side Project Report 1/2일차

오늘은 본격적으로 사이드 프로젝트를 시작한지 2일차가 되는 날이다.
이전에도 간단하게 사이드 프로젝틑를 진행했지만, 매번 프로젝트를 할때마다 뭔가 애매하게 알고 있는 개념들이 많았다. 그래서 우선적으로 내가 확실하게 알고 있는 것과 모르고 있는 것을 구분하는 것을 시작으로 부족했던 부분을 추가적으로 학습하였다.

아직 완전히 알고 있다고 할 수는 없지만, 이전과 비교했을때 애매하다고 생각되는 개념들이 어느정도 개념이 잡혀서 이제는 공부했던 개념들을 하나씩 적용시켜가면서 프로젝트를 하나씩 완성시켜 나가려고 한다.

이번 사이드 프로젝트는 ReactJS를 활용한 영화 컨텐츠 검색 어플리케이션 개발이다. 이번 사이드 프로젝트는 테스트 주도 개발(TDD)방식으로 진행을 하고, 어제 저녁부터 오늘까지 header와 page 컴포넌트의 틀을 만들었다.
이번 어플리케이션이 비교적 간단한 어플리케이션이기 때문에 TDD방식으로 실패하는 테스트 코드의 작성부터 테스트 코드를 통과하기 위한 코드 작성, 코드 refactoring을 적용하는데 좋은 연습이 될 것 같다는 생각이 든다.

테스트 코드를 작성하기 전에 간단하게 시각적 요소에 대한 테스트외부 입력에 따른 상태변경에 대한 테스트로 나눠서 시나리오를 구성해보았다. 이 부분에 대해서는 이전에 네이버의 프론트엔드 개발자 분이 올려주신 프론트엔드 개발에 있어서의 테스트 코드 작성이라는 주제의 블로그 포스팅이 많은 도움이 되었다.

Read More

210501 Setting up my own webpack with react and babel

REACT, WEBPACK, BABEL

CRA을 사용하지 말고, 나만의 React 프로젝트를 위한 웹팩을 만들자

React프로젝트를 만들때 CRA를 사용해서 손쉽게 React 프로젝트를 생성 할 수 있다. 하지만 이렇게 프로젝트를 생성하다보면, 막상 Webpack을 직접 커스텀해서 해야할때 마냥 어렵게만 느껴지고, 프로젝트에 불필요한 요소들이 자동으로 생성되기 때문에 좋지 않다.

기본적으로 설치해야 될 패키지들은 아래를 참고하자.
package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"dependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1",
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"@babel/preset-react": "^7.10.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"babel-loader": "^8.1.0",
"react-refresh": "^0.9.0",
"webpack": "^5.3.2",
"webpack-cli": "^4.1.0",
"webpack-dev-server": "^3.11.0"
}

Read More

210501 babel / webpack / react-scripts

 React/Babel/Webpack

본 포스팅 내용은 과거에 개인적으로 공부할때 정리했던 ReactJS의 내용을 복습의 목적으로 다시 정리하는 포스팅입니다.

react-scripts

react-based project를 사용할때 직접 빌드 환경을 셋팅하는 것은 많은 시간이 소요된다. 그래서 React 개발팀이 react-scripts라는 npm 패키지를 만들었는데, 이 패키지에는 평균적인 React app에서 많은 사람들이 필요로하는 것들에 대한 기본적인 setup을 포함하고 있다.
위에서 설명한 Babel과 Webpack 또한 react-scripts의 dependency로써 포함되어있다.

babel

babel은 높은 버전의 ECMAScript(unsupported or cutting-edge)를 ES5로 변환해주는 역할을 하는 transpiler이다. (ES5는 범용적인 브라우저에서 지원을 하기 때문)

webpack

webpack은 dependency를 분석기이자 module bundler이다. 예를들어 module A가 B를 dependency로 요청을하고, module B가 C를 dependency로 요청을 한다면 webpack은 C-B-A 와 같이 dependency map을 생성한다. 실제로는 매우 복잡하지만, 기본적인 컨셉은 webpack이 모듈들을 복잡한 dependency 관계들과 함께 번들들로 통합한다.

webpack과 babel의 관계: 웹팩이 종속성을 처리할 때, 웹팩은 자바스크립트 위에서 작동하기 때문에 모든 것을 자바스크립트로 변환해야 한다. 그 결과, 다른 로더를 사용하여 다른 유형의 리소스/코드를 javascript로 변환한다. ES6 또는 ES7에 대한 변환이 필요할 때에는 babel-loader를 사용해서 webpack과 babel을 연결시켜 사용한다.