코딩 플레이그라운드 만들며 맛보는 요즘 FE 개발 환경 Part 2

Posted on 22 February, 2021

파트 1에서는 기본 자바스크립트 개발 환경을 러나와 함께 구축해봤어요. 기본 환경만으로 충분하신 분들도 계시겠지만 보통은 그렇지 않을 겁니다. 이제 두 가지 개발 환경을 추가할 건데요. 타입 스크립트와 리액트 개발 환경이에요.

우선 타입스크립트 환경을부터 추가할 거예요. 그전에 러나로 해줘야 할 작업이 있습니다. 타입스크립트 환경도 결국 지금까지 저희가 구축한 자바스크립트 환경 위에 만들어질 것이기 때문에 디펜던시 모듈을 공유할 수 있도록 만들고 환경 설정 파일들도 기본 자바스크립트의 환경설정 파일들을 확장해서 사용할 거예요. 그리고 그 타입스크립트 환경 위에 리액트 개발 환경을 구축할 겁니다. 혹시라도 타입 스크립트 없이 기본 자바스크립트 환경위에 리액트만 추가하시고 싶으시다면 타입스크립트 관련 작업들은 생략하시면 됩니다.

pgts 패키지 추가하기

자 이제 파트 1에서 했던것과 동일하게 러나의 create 커맨드를 이용해 타입스크립트 환경인 pgts 패키지를 추가합니다.

 npx lerna create pgts

패키지 설정을 완료하면 packages/pgts 디렉터리가 생겼을 겁니다. 그리고 분명 러나가 만들어 놓은 디렉터리들이 있겠죠 1부 초반에 했던 것처럼 __test__ 디렉터리를 지우고 lib 디렉터리를 src로 바꿉니다. src/pgts.js 파일도 src/index.ts 파일로 바꿉니다. pgjs와 동일한 환경에서 시작합니다. packages/pgjs/packages.json의 내용을 카피해서 pgtspacakges.json 덮어써 주세요. 그리고 namepgts로 바꿔줍니다. main 속성이나 directories도 수정해 주시면 좋지만 우리 플레이그라운드에는 별로 중요한 속성은 아니니까요. 신경 쓰이시는 분들은 나머지 속성들도 바꿔주세요. 사실 packages/pgjs의 내용을 그대로 packages/pgts 을 복붙하고 packages.json만 수정해도 됩니다. 러나의 create 을 쓸 필요가 없죠. 다음에는 그렇게 할 겁니다.

devDependencies 모듈들 루트로 끌어당기기

자 이제 pgts 패키지는 추가했지만 아직 packages.json 외에는 아무것도 없는 상태에요. 초반에 이야기했듯 pgjs의 디펜던시 모듈은 공통으로 사용될 모듈이기 때문에 pgts 와 공유해서 사용할 것입니다. 이것을 가능하게 해주는 것이 바로 러나지요. 러나는 다수의 프로젝트를 모노리포로 관리하기 위한 여러 가지 기능을 제공합니다. 지금 우리가 할 작업은 디펜던시 모듈을 루트에서 꺼내 쓰도록 이동시킬 건데요. 이 작업은 러나의 link 커맨드로 쉽게 처리할 수 있습니다.

npx lerna link convert

link 명령이 실행되면 기존에 node_modules에 설치되어 있던 devDependencies들이 루트로 이동하고 각 패키지와 루트의 package.json의 내용도 수정됩니다. 확인해보세요. 이제 pgjspgtsdevDependencies 모듈들이 프로젝트 루트를 통해 공유하게 됩니다.

반대로 devDependencies지만 공통으로 사용되지 않는 것은 dependencies로 설치해서 옮겨지지 않게 할 수 있어요. 우린 플레이그라운드를 만드는 것이니 상관없죠. 배포할 생각이 없으니까요. 그냥 몽땅 다 devDependencies로 설치해서 모든 디펜던시가 공유되도록 할 겁니다.

이 작업으로 모든 디펜던시가 루트로 옮겨진 패키지들은 packages-lock.json 도 필요 없으니 지워주세요.

설정파일 중복 줄이기

공통 모듈은 루트에서 공유해서 쓸 수 있지만 아직 pgts 환경에는 설정 파일이 하나도 없기 때문에 아무것도 할 수 없어요. 이제 설정 파일을 만들어 줄 건데요. pgjspgts는 동일한 환경을 비슷하게 사용하다 보니 결국 설정 파일도 중복되는 내용이 많을 겁니다. 가능한 범위에서 중복을 줄여가며 설정을 해볼게요.

ESLint 설정 부터 시작하죠

.eslint.js에 정의된 ESLlint의 설정들은 extends 옵션으로 다른 파일에서 끌어와서 사용할 수 있어요. 파트 1에서 우리는 packages/pgjs/.eslintrc.js에 ESLint 설정을 했었는데 이제 이 파일을 packages 디렉터리로 옮기고 모든 pgjspgts가 사용하도록 만들 거예요. 일단 파일을 옮겨주세요. 그리고 packages/pgjspackages/pgts.eslintrc.js 파일을 각각 새로 만들고 아래와 같이 입력합니다.

module.exports = {
  extends: '../.eslintrc',
};

사실 pgjs 패키지는 packages 디렉터리로 옮긴 공용 .eslintrc.js 파일을 그대로 쓸 것이기 때문에 packages/pgjs/.eslintrc.js를 따로 만들 필요가 없어요. ESLint는 설정 파일이 없으면 부모 디렉터리로 타고 올라가면서 찾기 때문에 굳이 만들지 않아도 되는 것이죠. 하지만 특정 룰을 별도로 지정해야 할 수도 있기 때문에 나중을 위해에 만들었습니다. 자 이제 기본 설정을 토대로 pgtspgts만의 그리고 pgjspgjs만의 ESLint 설정을 추가할 수 있게 되었어요.

다음은 바벨 설정입니다

바벨 설정 파일도 비슷합니다. ESLint와 동일하게 extends라는 옵션으로 설정 파일을 확장할 수 있어요. 사용법 역시 동일합니다. 눈보다 손이 빠르신 분들은 벌써 하시고 계시겠죠? packages/pgjs/.babelrc 파일을 packages 디렉터리로 복사합니다. 그리고 packages/pgjspackages/pgts.babelrc 파일을 각각 새로 만들고 아래와 같이 입력합니다.

{
  "extends": "../.babelrc"
}

아직은 pgtspgjs나 동일한 환경이기 때문에 바벨 설정은 그대로 사용해요. 나중에 pgts 바벨 설정에는 타입 스크립트 설정이 추가될 거예요.

그리고 마지막으로 웹팩입니다

웹 팩은 extends 같은 확장 옵션을 제공하진 않아요. 그런데 어차피 webpack.config.js 파일이 자바스크립트 파일이고 설정 객체만 리턴해주면 되기 때문에 확장하기 쉽습니다. 저는 packages/webpack.config.base.js라는 파일을 만들고 그 안에 기본 설정을 리턴해주는 함수를 정의해 각 패키지가 이 함수를 임포트 해서 쓰도록 했습니다. 자 일단 해볼까요?

우선 packages/webpack.config.base.js 파일을 만든다음 pacakges/pgjswebpack.config.js의 내용을 복붙합니다. 결국 이 내용이 기본 웩팩 환경이 되겠네요. 다만 외부에서 끌어 쓸때 경로 __dirname은 서로 다를테니 경로를 받는 매개변수를 추가하고 사용하겠습니다.

// ..
module.exports = (env, argv, dirname = __dirname) => {
 // ..
 entry: [], // 7번 라인 빈 배열로 수정
 // .. 
 path: path.resolve(dirname, 'dist'),   // 10번 라인 수정
 // .. 
     '@src': path.resolve(dirname, 'src/'),  // 26번 라인 수정

엔트리 포인트는 패키지마다 다를테니 기본 설정에서는 빈 배열로 만듭니다. 그리고 이것을 확장하는 쪽에서 추가합니다.

자 여기까지 했으면 이제 pacakges/pgjspacakges/pgts에서 기본 설정 확장하는 웹팩 설정을 만들어 볼게요 pgjswebpack.config.js 파일을 수정하면 될 것이고 pgts에는 webpack.config.js 파일을 추가해야 합니다.

const baseConfig = require('../webpack.config.base');

module.exports = (env, argv) => {
  let config = baseConfig(env, argv, __dirname);

  config.entry.push('./src/index.js'); // `packages/pgts` 에서는 `index.ts`

  return config;
};

돌다 거의 동일하지만 pgts에서는 엔트리 포인트 파일의 확장자를 타입 스크립트의 확장자인 ts를 써야 합니다. 주석을 참조해서 바꿔주세요.

서버를 띄워 테스트해볼까요? 테스트하기 전에 npm script로 서버를 띄우는 스크립트를 추가할 거에요. 러나로 디펜던시가 link 되었기 때문에 npx로는 웹팩을 실행할 수 없거든요. packages/pgtspackages/pgjspackages.json에 스크립트를 추가합니다.

"scripts": {
   "serve": "webpack serve --mode development",
   "test": "jest"
 },

중복을 줄이는 설정 파일은 여기까지 할 거에요. 사실 제스트 하나 남았는데 제스트도 웹팩처럼 옵션으로 설정 확장을 제공하진 않지만 웹팩과 동일하게 설정 파일이 어차피 자바스크립트 일반 객체기 때문에 스프레드를 이용해서 쉽게 확장할 수 있어요. 원하시는 분들은 따로 작업하시면 될 것 같습니다. 저는 패스.

타입스크립트(typescript) 개발 환경 만들기

지금까진 pgts 패키지와 pgjs는 차이가 없어요. 완전히 동일합니다. 이제 pgts 패키지가 타입스크립트를 사용할 수 있게 해볼게요. 총 세가지 작업을 할거에요

  1. 바벨이 타입스크립트를 알아먹게 설정
  2. tsconfig.json 설정
  3. ESLint가 타입스크립트를 알아먹게 설정
  4. 제스트에서 타입스크립트를 알아먹게 설정

바벨 for 타입스크립트

자 이제 첫 번째 작업으로 바벨이 타입스크립트를 알아먹도록 해볼게요.

바벨 설정도 너무 쉬워요. packages/pgts 디렉터리로 이동해서 바벨 타입 스크립트 프리셋 설치합니다. 바벨로 타입 스크립트를 사용할 때 필요한 플러그인들을 한 번에 설치할 수 있어요. lerna link convert 이후에는 패키지의 로컬 디펜던시는 add 말고 npm을 사용해서 설치해야 정상 설치되는 모듈이 있습니다. 이유는 명확하지 않지만 node_modules 가 공유되면서 생기는 문제 같습니다. 이 부분은 시간 나면 확인해볼게요.(아시는 분은 연락 좀....) 어차피 모든 로컬 디펜던시들은 link를 사용해 루트로 보내버릴 것이기 때문에 일시적인 문제입니다.

npm i -D @babel/preset-typescript

그리고 바벨 설정에도 추가해야겠죠? packages/pgts/.babelrc 파일에 프리셋을 추가합니다.

{
  "extends": "../.babelrc",
  "presets": [
    "@babel/preset-typescript"
  ]
}

이제 바벨이 타입스크립트를 트랜스파일을 할 수 있습니다. 한번 확인해볼까요? packages/pgts/index.ts를 아래와 같이 수정합니다.

export function tsTest(a: number, b: number) {
  return a + b;
}

console.log(tsTest(1, 2));

그리고 npm run serve로 서버를 띄웁니다. 콘솔 창에 3이란 숫자가 출력되면 성공이지만 애초에 바벨 설정이 잘못되었다면 서버를 띄울 때 에러가 나서 바로 알 수 있습니다. export는 나중에 제스트로 테스트해보기 위해 미리 해둡니다.

tsconfig.json

바벨과 함게 타입스크립트를 사용할 수 있게 되었지만 아직 부족해요. 타입스크립트의 기능들을 온전하게 사용하려면 tsconfig.json 파일을 만들어 타입스크립트 설정을 해줘야 합니다. 현재는 packages/pgts/tsconfig.json 을 하나만 만들어 줘도 되는 상황이지만 나중에 리액트도 타입스크립트를 같이 쓸 예정이기 때문에 tsconfig.json도 디폴트 설정을 만들고 확장하는 구조로 만들어 두겠습니다.

packages/pgtsconfig.json 을 파일을 추가합니다.

{
  "compilerOptions": {
    "noEmit": true,
    "target": "ESNext",
    "module": "commonJS",
    "strict": true,
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": false,
    "sourceMap": true,
    "lib": ["esnext", "dom", "dom.iterable"],
    "types": ["node", "jest"],
    "downlevelIteration": true
  },
  "exclude": ["node_modules", "**/*.spec.ts"]
}

아마 특별한 수정 없이 그대로 써도 되는 설정일 겁니다. 사용하시다가 바꿔야 할 상황이 생기면 그때마다 조금씩 상황에 맞게 수정하시면 됩니다.

그리고 packages/pgtsconfig.json을 확장하는 설정 파일 packages/pgts/tsconfig.json을 만듭니다.

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@src/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "src/**/*.js",
    "src/**/*.ts",
    "src/**/*.tsx"
  ]
}

여기서도 extends 옵션을 사용해 설정을 확장했습니다.

ESLint for 타입스크립트

ESLint도 타입스크립트에 맞게 설정을 해야 정상적으로 정적 분석을 할 수 있습니다. 그래야 이 친구가 잔소리를 할 수 있죠. 설정은 간단해요. 우선 필요한 디펜던시를 설치합니다.

npm i -D @typescript-eslint/parser @typescript-eslint/eslint-plugin

타입스크립트를 위한 파서와 플러그인을 설치하고 바로 packages/pgts/.eslintrc.js 파일을 수정합니다.

module.exports = {
  parser: '@typescript-eslint/parser',
  extends: ['../.eslintrc', 'plugin:@typescript-eslint/recommended'],
  plugins: ['@typescript-eslint'],
   parserOptions: {
    ecmaFeatures: {
      impliedStrict: true,
    },
    project: './tsconfig.json',
    tsconfigRootDir: __dirname,
  },
};

아마 큰 문제 없이 동작할 겁니다. 확인하고 싶으신 분은 index.ts 파일에 약간의 장난질(?)을 한 다음 ESLint가 타입스크립트 경고를 주는지 확인해 보시면 될 것 같습니다.

제스트 for 타입스크립트

이번엔 제스트가 타입스크립트 코드도 테스트할 수 있도록 설정해볼게요. 이번에도 동일한 패턴입니다. 필요 디펜던시 설치! 그리고 설정!

설치! packages/pgts

npm i -D ts-jest

설정! packages/pgts/jest.config.js

module.exports = {
  preset: 'ts-jest/presets/js-with-babel',
  moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node', 'd.ts'],
  moduleNameMapper: {
    '^@src/(.*)$': '<rootDir>/src/$1',
  },
  globals: {
    'ts-jest': {
      babelConfig: true,
    },
  },
  watchPathIgnorePatterns: ['/node_modules/'],
};

제스트용 타입스크립트 프리셋을 사용하는 설정이고요. 프리셋중에서도 바벨을 사용하는 프리셋으로 설정했습니다.

테스트 코드! packages/pgts/src/index.test.ts

import { tsTest } from './index';

describe('tsTest', () => {
  it('needs tests', () => {
    expect(tsTest(1, 2)).toEqual(3);
  });
});

확인!

> npm test
 PASS  src/index.test.ts
  tsTest
    ✓ needs tests (2 ms)

아마 특별한 문제 없이 정상적으로 테스트까지 완료하셨을 거에요. 하지만 index.test.ts 파일을 열어보시면 아마 describeit 함수 위치에서 타입스크립트 경고가 뜰 겁니다. 제스트 API를 타입스크립트가 몰라서 생기는 문제에요. 제스트 타입 파일도 설치해 주면 해결됩니다.

npm i -D @types/jest

이제 타입스크립트 환경의 설정은 모두 끝났습니다.

리액트(React) 개발 환경 만들기

리액트 환경은 타입스크립트 환경 위에 만들 겁니다. 정확히는 자바스크립트 환경 위에 타입스크립트 환경 위에 리액트 개발 환경이네요. 한 단계 한 단계 쌓아가고 있습니다. 리액트 환경도 pgjs에서 pgts를 만들 때와 비슷해요. 우리가 사용하고 있는 환경들을 리액트에 맞게 조금씩 수정할 겁니다. 우선 패키지부터 추가하죠.

react 패키지 추가하기

편의를 위해 이번에는 lerna create 를 사용하지 않고 패키지를 만들거에요. 그냥 복사하는 거죠. pacakges/pgreact 디렉도리를 만들고 packages/pgtsnode_modules 을 제외한 모든 내용을 그대로 복사해서 옮겨 올게요. .babelrc 같은 히든 파일들도 잊지말고 복사합니다. 복사한 후에는 딱히 문제는 없지만 packages.jsonnamepgreact로 바꿔줍니다. 그리고 npm i로 디펜던시를 설치하고 서버도 띄워 보고 테스트도 돌려봅니다. 아마 문제 없이 잘 돌아갈거에요. 아직은 그냥 pgts와 동일하니까요.

리액트 설치 및 바벨 설정

리액트 개발 환경을 만들고 있으니 당연히 리액트를 설치해야 합니다. 그리고 jsx를 사용해야 하니 바벨도 리액트를 알 수 있도록 설정할거에요. 우선 필요한 디펜던시들을 한꺼번에 설치하겠습니다.

pacakges/pgreact 에서 실행합니다.

npm i -D react react-dom core-js @babel/preset-react

리액트는 설치만 하면 바로 사용할 수 있지만 jsx를 사용하려면 추가 작업을 해야 합니다. 브라우저는 jsx를 모르니까요. 바벨을 이용해 jsx를 올바른 자바스크립트 코드로 트랜스파일 할 수 있게 설정해볼게요. 디펜던시는 이미 설치했으니 설정만 하면 됩니다.

.babelrcpresets 옵션 배열에 @babel/preset-react을 추가합니다. 리액트에 필요한 바벨 플러그인 들을 모아 놓은 프리셋을 사용하겠다는 뜻이에요.

{
  "extends": "../.babelrc",
  "presets": [
    "@babel/preset-typescript",
    "@babel/preset-react" // 추가
  ]
}

자 이제 리액트를 써볼까요? pacakges/src/index.ts 파일의 확장자를 tsx로 바꾸고 이 파일에서 간단한 리액트 컴포넌트를 만들어볼게요.

import React from 'react';
import ReactDOM from 'react-dom';

const Hello: React.FC = () => {
  return <h1>Hello World</h1>;
};

ReactDOM.render(<Hello />, document.body);

엔트리 포인트 파일의 확장자를 바꿨으니 pacakges/react/webpack.config.js 설정에서도 바꿔줍니다.

// …
config.entry.push('./src/index.tsx’); // ts를 tsx로 변경
// …

자 이제 서버를 띄우면 될 것 같지만 아직 안돼요. 왜냐면 우린 웹팩을 통해 바벨을 연동하고 있는데 웹팩 바벨로더에 아직 tsx파일을 태우지 않았어요. 그 부분을 수정합니다. 이 내용은 pacakges/webpack.config.base.js에 있어요

//…
module: {
      rules: [
        {
          test: /\.(ts|tsx|js|jsx)$/, // 여기를 수정합니다.
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
//…

그리고 한 가지 더 packages/pgreact/tsconfig.jsonjsx를 사용하겠다고 선언합니다.

// …
"compilerOptions": {
    "baseUrl": "./",
    "paths": {},
    "jsx": "react" // 추가
  }
//…

이거 안 해줬다고 큰 문제가 생기는 건 아니지만 타입스크립트가 자꾸 경고해서 귀찮아요.

자 이제 서버를 띄워 확인합니다. 헬로 월드가 나오죠? 안된다면 위쪽을 다시 한번 째려보세요.

ESLint for 리액트

기본적인 환경은 만들었으니 이제 ESLint 에게 우리의 리액트 코드들도 감시해달라고 할게요. 코드에서 발생할 수 있는 실수들을 대신 찾아주고 이런저런 잔소리를 해주기 때문에 리액트 개발할 때도 ESLint는 필수입니다. 우선 ESLint 플러그인들을 설치할게요.

npm i -D eslint-plugin-react eslint-plugin-react-hooks @types/react @types/react-dom

리액트 타입 선언과 리액트 훅 플러그인도 같이 설치했습니다. 여러분 이러니저러니 해도 훅을 사용하는 게 더 좋습니다. 익숙함의 차이라고 생각합니다. 플러그인 설치가 끝나면 설정에 플러그인을 추가합니다.

pacakges/react/.eslintrc.js

{
  extends: [
   //…,
   'plugin:react/recommended', // 추가
     'plugin:react-hooks/recommended', // 추가
  ],
  plugins: [ 
   // …,
   'react',  // 추가
   'react-hooks'  // 추가
  ],
  settings: {   // settings 추가
    react: {
     version: 'detect',
  },
  // … 
},
}

ESLint 설정은 이렇게 끝입니다. 정상적으로 설정이 되었는지 확인해볼까요? index.tsxHello 컴포넌트에 훅을 사용하는 코드를 추가해볼게요. 아래와 같이 코드를 변경했을때 react-hooks/exhaustive-deps 경고가 뜬다면 정상 설정된 것입니다.

import React, { useState, useEffect } from 'react';

const Hello: React.FC = () => {
  const [a] = useState(0);

  useEffect(() => {
    console.log(a);
  }, []); // 여기서 경고가 납니다.
// ...

확인만 하고 다시 원복 해주세요.

제스트 for 리액트

제스트를 사용해서 E2E테스트도 할 수 있지만 여기서는 유닛테스트만 고려합니다. 이미 제스트는 설치했으니 바로 테스트 코드를 작성할 수 있습니다. 다만 테스트 편의를 위해 유틸성 도구를 추가하겠습니다. 이름 하야 테스팅 라이브러리(testing-library)입니다. 프론트 엔드의 테스트 코드를 작성할 때 사용할 수 있는 유용한 기능들을 갖고 있어 쉽고 빠르게 테스트 코드를 작성할 수 있게 해줍니다. 리액트뿐만 아니라 뷰, 앵귤러등의 다양한 프레임웍 그리고 DOM까지 지원합니다. 쓰지말아야 할 특별한 이유가 없다면 사용할 것을 권장합니다.

npm i @testing-library/react

이미 제스트의 설정에 리액트까지 고려가 되어 있어서 따로 설정을 건드릴 필요가 없습니다.

이제 Hello 컴포넌트를 위한 간단한 테스트 코드를 작성해볼게요. 현재는 Hello 컴포넌트가 엔트리 포인트 파일에 정의되어 있기 때문에 별도의 파일로 컴포넌트로 분리하는 작업부터 하겠습니다.

packages/pgreact/src/hello.tsx

import React from 'react';

const Hello: React.FC = () => {
  return <h1>Hello World</h1>;
};

export default Hello;

index.tsx 파일은 이제 분리된 Hello 컴포넌트를 사용합니다.

import React from 'react';
import ReactDOM from 'react-dom';
import Hello from './hello';

ReactDOM.render(<Hello />, document.body);

packages/pgreact/src/index.test.ts 파일의 확장자를 tsx로 바꿔주세요. 그래야 jsx를 써도 타입스크립트가 경고하지 않아요. 그리고 간단하게 Hello 컴포넌트의 테스트 코드를 작성합니다.

import React from 'react';
import { render } from '@testing-library/react';

import Hello from './hello';

describe('Component test', () => {
  it('needs tests', () => {
    const { getByText } = render(<Hello />);

    expect(getByText('Hello World').nodeName).toEqual('H1');
  });
});

테스팅 라이브러리의 render 함수를 이용해 Hello 컴포넌트를 가상의 DOM으로 렌더링하고 getByText 함수로 현재 렌더링된 DOM 노드 중에 Hello World라는 텍스트를 갖고 있는 노드를 찾습니다. 그리고 찾은 노드가 H1이 맞는지를 검증합니다.

Hello 컴포넌트가 워낙 간단하고 로직이랄 게 없어서 이런 테스트 코드를 작성했지만 엘리먼트를 변경하는 로직이 없는 상황에서 노드의 이름을 확인하는 테스트 코드는 의미 없는 테스트를 하는 겁니다. 이런 단순 렌더링은 애초에 테스트가 필요도 없고 굳이 하겠다면 스냅샷을 사용해야 합니다. 그리고 엘리먼트의 이름을 확인하는 것은 큰 의미가 없는 테스트 코드일 경우가 많습니다. 엘리먼트 구조는 바뀔 수도 있고 정말 노드 구조가 중요한지블라블라블라… 또 기승전테스트라 불필요한 사족은 줄이겠습니다.

스타일드 컴포넌트(styled-component)

CSS를 자바스크립트 코드나 프레임웍 컴포넌트에 녹일려는 시도들은 정말 많습니다. 저도 몇 가지를 검토해봤었고 현재는 스타일드 컴포넌트에 정착했습니다. 이 정도 도구는 사실 언제든지 다른 것으로 교체할 수 있겠죠. 프로젝트마다 다른 시도를 해보는 것도 재미있을 것 같습니다. 아무튼 저는 최근까진 스타일드 컴포넌트를 사용해왔으니 리액트 환경의 마지막으로 스타일드 컴포넌트까지 설치하고 마무리하겠습니다.

npm i styled-components

스타일드 컴포넌트는 자바스크립트 문법하에 돌아가기 때문에 설치하면 바로 사용할 수 있어요. index.tsx를 수정해볼까요?

import React from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';

export const Hello: React.FC = () => {
  return <Title>Hello World</Title>;
};

const Title = styled.h1`
  color: blue;
`;

ReactDOM.render(<Hello />, document.body);

Title 이라는 스타일드 컴포넌트를 추가해서 css로 색을 입혀봤습니다. 처음에 스타일드 컴포넌트를 적용하게 되면 기존 css와는 다른 작업 방식 때문에 당황하게 되는데요. 포괄적이지만 간단한 팁을 드리자면 스타일드 컴포넌트 하나하나를 css 클래스라고 생각하세요. 그렇게 생각하면 구조를 잡을 때 조금은 도움이 되실 겁니다.

lerna convert link 한번더

tsreact에도 중복된 디펜던시가 있으므로 이것들 역시 루트로 옮기고 루트의 node_modules에서 모두가 공유하도록 해야 합니다.

초반에 했었죠? 커맨드 한 번이면 됩니다.

npx lerna convert link

package.josn을 확인해보시면 리액트 환경에만 필요한 것만 남고 모두 루트로 이동한 것을 확인할 수 있습니다. 이제 프로토타입이나 실험적인 프로젝트를 몇 십 개씩 만들면서도 하드용량을 아낄 수 있습니다 :)

플레이그라운드의 활용

자 일단은 제가 필요한 환경 구성은 끝났습니다. 예전에는 개발 관련 유튜브 동영상을 보다가 거기에 나온 예제 코드를 실험해볼까? 하는 마음에 에디터를 열었다가도 필요한 환경을 구성하는 게 귀찮아서 에이 그냥 눈으로만 보자 이랬었는데 이제는 필요한 환경을 그대로 복사해서 새로운 이름만 주고 곧바로 편안한 에디터 환경에서 실험해볼 수 있게 되었어요. 예를 들어 필요한 환경이 리액트 환경이라면 packages/mytest 라는 디렉터리를 만들고 packages/pgreact 의 내용을 고대로 복사한 뒤 바로 실험할 코드들을 입력할 수 있게 되었죠.

작업 중에 독립된 환경에서 실험해보고 싶은 내용이나 프로토타입등도 동일한 방식으로 하고 있고 간단한 테스트 뿐 아니라 조금 진지한 프로젝트도 프로토타이핑은 여기서 시작합니다. 저는 프로젝트별로 디렉터리를 만들고 환경을 복사한 다음 깃 브랜치를 별도로 만들어서 관리합니다. 이러면 main 브랜치는 깔끔하게 환경 패키지들만 유지할 수 있습니다.

또 하나 해볼 만한 작업은 lerna.jsonpackages 옵션은 배열로 여러 개를 지정할 수 있는데요. 여기에 예를 들어 apps/* 같은 경로를 추가해서 환경 설정만 해둔 패키지들과 그 환경을 이용해 실험하는 프로젝트의 위치를 분리하면 관리하기 더 수월할 것 같아요. 기본 환경 설정들의 경로 수정으로 내용이 쓸데없이 길어질 것 같아서 이 글에서는 다루지 않지만 공유하는 리포에는 조만간 적용해 두겠습니다.

한 가지 아쉬운 건 Export 같이 프로젝트 하나를 독립시킬 수 있는 기능이 필요한데 그걸 구현하려니 해야 할 작업이 참 많네요. 그런 상황에서는 아직 한 땀 한 땀 옮겨야해요.

또 다른 활용

오픈소스나 라이브러리를 개발하는 개발자라면 작업 내용을 배포전에 다양한 지원 환경에서 적어도 한 번 이상 테스트하게 됩니다. 오픈소스 라이브러리 같은 경우 바닐라 자바스크립트는 당연한 것이고 ESM 그리고 타입스크립트에서도 명확한 타입과 함께 잘 돌아가야 하죠. 거기에 리액트, 뷰(Vue), 앵귤러(Angular)와 같은 프레임웍 컴포넌트까지 지원된다면 테스트해야 할 사용 환경이 힘겨울 정도로 많아집니다. 이럴 때 각 환경을 플레이그라운드에서 미리 구성해 놓고 그때그때마다 바로 꺼내서 테스트한다면 각 환경들을 관리하기도 수월하고 테스트 자체도 훨씬 편해지겠죠? 제가 이렇게도 사용하고 있습니다. 심지어 아직 NPM에 배포되지 않은 모듈도 러나의 bootstrap 으로 로컬 모듈을 마치 배포한 모듈인 양 디펜던시로 사용하며 테스트 해볼 수 있죠. 이게 러나의 강점이기도 하구요.

마무리 그리고 빠진 한가지

자 여기까지 해서 언제든지 쉽게 간이 프로젝트를 추가할 수 있는 플레이그라운드를 만들었어요.

사실 애초의 계획과는 다르게 한 가지 스택이 빠졌어요. 바로 스토리북이죠. 환경별로 스토리북으로 프로젝트를 관리한다면 필요할 때마다 스토리를 추가해 독립적인 실험을 해볼 수 있겠죠? 겔러리 형태의 UI도 제공되기 때문에 관리도 쉽고요. 스토리북이 있다면 필요한 환경이 있을 때 디렉터리를 복사할 필요가 없어요. 디렉터리를 복사하는 방법은 스토리북이 빠진 것을 메꾸는 어쩔 수 없는 임시방편이에요. 처음에 구상했던 플레이그라운드는 스토리북에서 완성이 되는 모습이었습니다. 스토리북이 빠진 이유는 스토리북이 아직 웹팩5를 지원하지 않기 때문이에요. 제외할 수밖에 없었습니다. 웹팩5는 현재 서드파티의 마이그레이션 문제로 많은 어려움을 겪고 있습니다. 언젠간 스토리북이 웹팩5를 지원하게 되면 그때 파트 3를 작성해 마무리를 짓겠습니다. 당분간은 디렉터리 복사에 의존해야네요.

아무튼 플레이그라운드를 빌미로 요즘 FE 개발 환경 중 필수적인 것들은 하나씩 건드려 봤어요. 개발하는 프로젝트에 따라 추가해야 할 도구들은 많을 테지만 그래도 대부분 이 환경 위에 하나씩 올라갈 겁니다. 든든한 버팀목 같은 도구들이죠.

내년엔 바뀌거나 추가될 가능성도 있겠네요. 현재 몇 가지 도구들이 물망에 오르고 있습니다. 내년은 또 내년에 기회 되면 소개하도록 하겠습니다.

완성된 해처리의 리포는 여기입니다.

Buy Me A Coffee

크리에이티브 커먼즈 라이선스이 저작물은 크리에이티브 커먼즈 저작자표시-비영리-변경금지 4.0 국제 라이선스에 따라 이용할 수 있습니다.

© Sungho Kim2021