zero to without CRA

건둔덕 ·2023년 8월 11일
1

zero to without CRA

목록 보기
1/1
post-thumbnail

며칠전 회사에서 디자인 시스템을 구축하라는 명을 받았다.

이때다 싶어 평소에 사용해보고 싶었던 ViteStorybook을 사용해서 디자인 시스템을 구축하고 더불어 npm에 배포 후 원하는 프로젝트에서 편리하게 사용해보기로 했다.
(시니어 프론트엔드 개발자가 없어서 나름대로 사전 조사 후 하기로 결정함. 막 한거 아님.)

하쥐만 문득 vite를 지금부터 게속 사용하게되면 CRA 없이 react 설정과 webpack 설정 등을 직접 전부 해보려고 했던 내가.. 시간이 흐를수록 절대 하지 않을 것 같다..

그래서 CRA 없이 처음부터 세팅을 해보기로 맘 먹었다.

세팅하기로 이왕 맘 먹은김에 완전 제대로 하기위해 세팅에 필요한 각각의 요소들의 역할이나 자세한 정보들까지 조사하고 공부하기로 했다.


React

npm init -y
npm i react react-dom react-router-dom

우선 npm init을 해주고 React를 사용하는 프로젝트에서 거~의 필수적으로 사용되는 패키지들을 설치 해주겠다.

  • react: React를 사용하기 위해 설치 해야하며 component, state, props 등의 react에서 주요 기능들을 포함하고 있다.
  • react-dom: React의 컴포넌트들을 DOM에 렌더링을 해주기 위한 라이브러리이다.
  • react-router-dom: SPA 중 React의 라우팅 처리를 도와준다.

Babel

BabelJavascript의 컴파일러로서 주로 ECMAScript 2015(ES6) 이상 버전의 코드들을 그 이전 버전으로 트랜스파일링 해주며, 특정 기능이 브라우저에서 기본적으로 지원 되지 않는 경우 폴리필을 제공해준다.

또, 프론트엔드 프레임워크 및 라이브러리들(React, Vue, Angular)의 특정 문법들을 vanila javascript로 변환해준다!

정리하자면, Babel은 최신 Javascript를 안전하게 사용하게 해주고, 브라우저 간의 호환성 문제도 해결해주며, React를 사용할 때 JSX 문법으로 작성된 코드들을 Javascript로 변환 시켜줄 수 있다. (플러그인이나 프리셋을 통해 더 확장해서 변환 처리도 가능)

npm i -D @babel/preset-env @babel/preset-react @babel/preset-typescript core-js

babel에서 필수로 사용해야하는 패키지들을 설치해준다.

  • @babel/preset-env: 구문 변환 및 폴리필 같은 다양한 처리를 해준다.
  • @babel/preset-react: JSX 문법을 JS로 바꿔주는 등의 처리를 해준다.
  • @babel/preset-typescript: typescript를 javascript로 바꿔주는 등의 처리를 해준다.
  • core-js: pollyfill을 위해서 설치해주는 패키지

babel 패키지를 설치한 후 bable.config.js파일을 루트 디렉토리에 생성하고 설정을 해준다.

const presets = [
  "@babel/preset-react", // jsx 구문들을 javascript로 변환하는데 사용.
  [
    "@babel/preset-env", // 브라우저의 버전과 node.js의 버전 등에 대해 호환성을 생각하지 않고 최신 javascript의 기능들을 사용할 수 있음.
    {
      modules: false, // 컴파일할 모듈 시스템을 결정. => false로 설정하면 모듈이 변환되지 않아서 ES6 모듈을 사용함.
      useBuiltIns: "usage", // 코드에서 실제로 사용하고 있는 pollyfill만 포함하도록 처리(babel 자동감지 처리).
      corejs: 3, // 사용중인 core-js의 버전을 지정. => 위의 useBuiltIns를 제대로 작동하게 하기 위해서 사용.
    },
  ],
  "@babel/preset-typescript", // tsc를 사용하여 ts를 js로 변환하지 않아도 ts를 작성하고 js를 출력할 수 있도록  사용. => 트랜스파일 처리는 되지만 타입 검사는 안해줌.
];

const plugins = [];

module.exports = { presets, plugins };

위의 설정값들을 옆에 주석값들을 자세히 달아 두었으니 참고해서 사용하면 된다!

한 가지 정확하게 짚어줄 부분은 modules: false 부분이다.
@babel/preset-env은 babel을 사용하는 이유 중 가장 큰 이유이다(그렇다고 다른 기능이 필요 없다는게 절대 아님.)

하지만 @babel/preset-env 설정이 되어 있으면 CommonJS 모듈 방식으로 자동 변환이 이뤄진다. CommonJS 모듈 방식은 Tree Shaking을 해줄수가 없기 때문에 modules: false 로 설정하면 모듈 방식을 CommonJS 로 변환하지 않는다는 뜻이므로 webpackTree Shaking을 해줄 수 있다.


Webpack

webpack이란 javascript를 중심으로 한 모듈 번들러 이다. webpack 자체적으로는 javascriptjson만 번들링이 가능한데, loader가 있으면 다른 유형의 파일들도 웹팩이 처리할 수 있는 모듈로 변환이 가능하다.

webpack이 나오기 한참 전으로 거슬러 가본다면 javascript는 모듈(module)도 없었다. 그리고 시간이 지남에 따라 CJS, ESM 방식의 모듈이 만들어졌다.

이제는 모듈은 사용할 수 있는데, js 파일을 동시에 여러 개 호출하게되면 속도 문제가 발생하게 되었다. 또, 특정 js 파일의 로딩이 지연되면 전체가 늦어져서 문제가 되었다.

결국 여러 문제를 해결하기 위해서 나온 것이 번들러이고, 현재 가장 유명한 번들러로는 Webpack이라고 할 수 있다. 하지만 빌드가 오래걸리는 것이 문제점이라고 볼 수 있다.

Vite는 Esbuild 기반으로 만들어진 Snowpack의 컨셉을 모두 흡수하면서도 좀 더 간결하고 사용성이 좋게 만들어진 번들러이다.

// 웹 팩에 필요한 패키지들
npm i -D webpack webpack-cli webpack-dev-server webpack-merge

// JS가 아닌 다른 유형의 파일들을 번들링 하기위한 패키지들
npm i -D css-loader style-loader babel-loader

// Plugin
npm i -D html-webpack-plugin css-minimizer-webpack-plugin webpack-bundle-analyzer react-refresh @pmmmwh/react-refresh-webpack-plugin 

webpack, loader, plugin 패키지들을 설치해준다.

  • webpack: webpack을 사용하기 위해 설치해야한다.

  • webpack-cli: CLI에서 webpack 명령어를 사용하기 위해 사용한다.

  • webpack-dev-server: 개발 서버로 실행하기 위해서 사용한다.

  • webpack-merge: 모드에 따라 다른 설정을 적용한 파일을 번들링 하는데 사용한다.

  • css-loader: css 파일을 읽기 위해 사용한다.

  • style-loader: css를 inline으로 <style></style> 안에 넣어주기 위해 사용한다.

  • babel-loader: webpack이 번들링 처리를 할 때 babel을 사용하게 해주기 위해 사용한다.

  • html-webpack-plugin: 번들링 결과를 포함한 HTML을 제공하기 위해 사용한다.

  • css-minimizer-webpack-plugin: css를 압축하기 위해서 사용한다.

  • webpack-bundle-analyzer: 번들 사이즈를 UI로 보여주기 위해 사용

  • @pmmmwh/react-refresh-webpack-plugin: React에서 HMR(Hot Module Replacement)을 적용하기 위해서 사용 (이게 없으면 코드 변경 시 새로고침됨)


webpack의 패키지를 설치한 후 webpackconfig 설정을 해주면된다.
webpackconfig 설정은 3가지로 나눠서 처리했다.
webpack의 설정 파일들에 대한 내용은 주석으로 아주 자세하게 달아 두었으니 참고하실 분은 참고 바람!


  • webpack.common.ts - 공통으로 사용할 webpack 설정
// webpack.common.ts
import path from "path";
import webpack from "webpack";

import HtmlWebpackPlugin from "html-webpack-plugin";

const configuration: webpack.Configuration = {
  resolve: {
    extensions: [".ts", ".tsx", ".js", "jsx"], // import 할 때 확장자를 설정해주지 않아도 됌. => ex) import './index.tsx' -> import './index'
    alias: {
      "@src": path.resolve(__dirname, "../src/"), // 상대 경로들에 대해서 절대 경로를 지정해 줄 수 있음. => ex) ../../../src/ -> @src/
    },
  },
  entry: "./src/index", // webpack이 종속성 그래프를 만들 때의 진입점 파일을 설정.
  module: {
    rules: [
      {
        test: /\.(ts|tsx|js|jsx)$/, // 규칙을 적용시킬 확장자들을 선언. => ts,tsx,js,jsx 지정
        use: ["babel-loader"], // 사용할 트랜스파일러를 지정. => 위의 test에서 지정된 확장자들을 babel을 사용해서 트랜스파일 처리.
        exclude: /node_modules/, // 제외할 디렉토리 지정. => babel이 node_modules를 트랜스파일 처리 하지 않게 제외 시킴.
      },
    ],
  },
  plugins: [
    // webpack 번들에 대한 스크립트가 포함된 index.html을 기본적으로 생성하고, 추가 설정 시 생성되는 html파일 내의 설정을 조정할 수 있다. => public폴더 안의 index.html을 기본 템플릿으로 설정
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "..", "public", "index.html"),
    }),
    new webpack.ProvidePlugin({ React: "react" }), // import 하지 않아도 자동으로 로드되는 모듈을 지정 => React를 직접 import하지 않아도 webpack에 의해 번들로 제공되는 파일에서 참조될 땜마다 React를 자동으로 import함.
  ],
};

export default configuration;

  • webpack.dev.ts - 개발 모드에서 사용할 webpack 설정
// webpack.dev.ts

import path from "path";
import webpack from "webpack";
import "webpack-dev-server";
import { merge } from "webpack-merge";
import common from "./webpack.common";

import ReactRefreshPlugin from "@pmmmwh/react-refresh-webpack-plugin";

const configuration: webpack.Configuration = {
  mode: "development", // 개발 모드를 활성화하여 디버깅 및 개발에 최적화된 번들링과 HMR이 가능해짐.
  devtool: "inline-source-map", // 소스맵 생성 방식 정의 - 디버깅을 수월하게 하기위해 소스 맵 파일을 번들리에 포함 시킴. => 번들링된 파일에서 에러가 났을 때 소스 맵 파일로 인해 React에서 에러가 난 부분의 정확한 위치를 찾을 수 있음.
  output: {
    path: path.resolve(__dirname, "../dist"), // 번들 파일들을 출력할 디렉토리를 지정.
    filename: "[name].bundle.js", // 출력된 번들 파일의 이름 지정.
  },
  module: {
    rules: [
      {
        test: /\.css$/, // .css 확장자를 타겟팅
        use: ["style-loader", "css-loader"], // test에 선언된 확장자 파일들을 'style-loader', 'css-loader'를 사용해 파일을 변환함.
        exclude: /node_modules/, // node_modules는 제외하고 적용
      },
    ],
  },
  plugins: [new ReactRefreshPlugin()], // webpack dev 서버에 react의 변경 사항을 실시간으로 반영 => react용 HMR
  devServer: {
    static: path.join(__dirname, "public"), // 정적 파일을 제공할 디렉토리 설정
    port: 3000, // 개발 서버의 포트 번호 설정 => auto로 설정 가능
    open: true, // 서버가 시작할 때 자동으로 브라우저를 여는 설정
    compress: true, // 모든 자원들을 gzip 압축하여 제공
    historyApiFallback: true, // 404 에러 대신 index.html 페이지로 리디렉션 시킴 => SPA를 위한 설정
    hot: true, // HMR을 활성화 시켜 코드 변경 시 전체 페이지 새로고침 없이 해당 모듈만 반영 시킴. => RefreshWebpackPlugin이 없으면 react에서 state나 props를 일반 HMR로는 빠른 대응이 불가능 해서 RefreshWebpackPlugin를 플러그인에 포함시켜 react에 특화된 HMR을 제공
  },
  watchOptions: {
    ignored: /node_modules/, // 변경된 부분을 감지하는 파일 목록 중 node_moduels의 변경은 감지 옵션에서 제외 시킴.
  },
};

export default merge(common, configuration);

  • webpack.prod.ts - 프로덕션용 webpack설정
// webpack.prod.ts
import path from "path";
import webpack from "webpack";
import { merge } from "webpack-merge";
import common from "./webpack.common";

import MiniCssExtractPlugin from "mini-css-extract-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";

const configuration: webpack.Configuration = {
  mode: "production", // 최적화와 압축을 위한 여러 플러그인들이 기본적으로 활성화 됌.
  devtool: "cheap-module-source-map", // 소스 맵 생성방식 정의 - 각 열에 대한 정보 제공하지 않고 행에 대한 매핑 정보만 제공하고 프로젝트 원본 소스 코드의 매핑 뿐만 아니라 loader에 의해 변환된 모든 모듈들에 대한 소스 맵도 생성
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "[name].[contenthash].js", // 출력 파일의 이름을 결정. => [contenthash] 사용 시 파일 이름이 같을 때 캐시에 저장하는 브라우저의 특성을 활용해 파일 내용이 변경 될 때만 파일 이름을 바꿔줌.
    clean: true, // 빌드 전 'output.paht' 경로에 있는 디렉토리 내용을 자동으로 삭제.
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"], // MiniCssExtractPlugin.loader와 css-loader을 같이 사용하게 되면, css를 별도의 파일로 추출해 줄 수 있어 js와 css를 병렬 처리가 가능하게함.
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin()], // 플러그인에 포함시켜 MiniCssExtractPlugin를 사용하게함. => css를 별도의 파일로 추출하여 캐싱 및 병렬 처리를 통해 성능을 최적화 함.
  optimization: {
    usedExports: true, // tree shaking을 활성화. => 사용하지 않는 코드를 제거 (import하고 사용하지 않는 코드는 번들링 후 제거)
    minimize: true, // 번들을 최소화 시킴.
    minimizer: [new CssMinimizerPlugin()], // css의 공백, 주석등을 제거해서 파일 크기를 줄여줌.
  },
};

export default merge(common, configuration);

webpack 설정을 마무리한 후 package.jsonscripts를 아래와 같이 해주면 된다!

"scripts": {
  "dev": "npx webpack serve --config config/webpack.dev.ts",
  "build:dev": "npx webpack --config config/webpack.dev.ts",
  "build:prod": "npx webpack --config config/webpack.prod.ts"
},

Typescript

npm i -D typescript ts-node @types/react @types/react-dom @types/webpack @types/webpack-dev-server @types/node

typescript 관련 패키지들을 설치해준다.

  • typescript: typescript 사용을 위해 설치해준다.
  • ts-node: node.js에서 typescript를 실행하기 위해 사용한다.
  • @types/react: react에 대한 타입 정의를 해주기 위해서 사용한다.
  • @types/react-dom: reactDOM에 대한 타입 정의를 해주기 위해서 사용한다.
  • @types/webpack: webpack에 대한 타입 정의를 해주기 위해서 사용한다.
  • @types/webpack-dev-server: webpack dev server에 대한 타입 정의를 해주기 위해서 사용한다.
  • @types/node: node에 대한 타입을 정의해주기 위해서 사용한다.

typescript 관련 패키지들을 설치해준 후 tsconfig.json의 설정을 아래와 같이 해주면 된다.
tsconfig.json안에 주석으로 자세하게 설명을 달아두었으니 참고 하실 분은 참고 바람!

{
  "compilerOptions": {
    "outDir": "./dist", // 컴파일러가 컴파일된 javascript 파일을 배치하는 위치를 지정. => 컴파일된 파일을 ./dist 경로에 저장
    "target": "ES5", // 컴파일러가 코드들을 지정한 버전으로 변환. => ES5 Javascript 구문으로 변환
    "module": "ESNext", // 사용하려는 모듈 시스템을 정함. => ES6 스타일 모듈 구문을 사용함.
    "jsx": "react-jsx", // JSX 구문을 처리하는 방법을 typescript에 알려줌. => react-jsx로 설정 시 React 17 부터 도입된 새로운 JSX Transform을 사용하게된다. 이 때문에 jsx 구문을 사용하는 파일 맨 위에 "import React from 'react'"를 불러오지 않아도 jsx문법을 사용할 수 있다.
    "noImplicitAny": true, // true로 설정 시 typescript가 유추할 수 없는 모든 변수가 컴파일 오류를 발생 시킴.
    "allowSyntheticDefaultImports": true, //
    "lib": ["dom", "dom.Iterable", "esnext"], //
    "allowJs": true, // true로 설정 시 컴파일에 javascript 파일을 포함시킬 수 있음.
    "skipLibCheck": true, // true로 설정 시 모든 선언 파일(.d.ts)에 대한 타입 검사를 건너 뜀.
    "esModuleInterop": true, // true로 설정 시 ES6 모듈 가져오기/내보내기의 상호 운용성을 활성화하여 모든 가져오기에 대한 네임스페이스 개체를 생성.
    "strict": true, // 타입 검사를 더 강력하게 시킴.
    "forceConsistentCasingInFileNames": true, // 대소문자를 구분하는 파일 시스템에서 문제를 방지하기위해 파일이 동일한 대소문자로 일관되게 참조되도록 함.
    "moduleResolution": "node", //
    "resolveJsonModule": true, // json 모듈을 가져올 수 있게 함.
    "isolatedModules": true, //

    // 절대 경로 설정
    "baseUrl": ".", // paths의 base가 되는 url경로
    "paths": {
      // 절대 경로를 설정하기 위해 선언
      "@src/*": ["src/*"] // ex) ../../../components/button -> @src/components/button
    }
  },
  "include": ["src"], // 컴파일에 포함할 파일 및 폴더를 지정하는 glob 패턴의 배열. => src 폴더 안에 있는 파일만 컴파일에 포함 됌.
  "exclude": ["node_modules", "dist"], // 컴파일에서 제외할 파일 또는 폴더를 지정하는 glob 패턴의 배열. => node_modules, dist 폴더 내에 파일들을 컴파일 하는 것을 제외

  // webpack 설정 파일들을 typescript로 처리하기 위해 사용
  "ts-node": {
    "compilerOptions": {
      "module": "CommonJS"
    }
  }
}

위의 설정까지 전부 끝났다면, 이제 public 폴더안에 index.html파일을 생성하고 src폴더안에 App.tsx, index.tsx를 만들어 주고 아래와 같이 코드를 작성해주면 된다.

  • public/index.html
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
    />
    <title>Title</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

  • src/App.tsx
const App = () => <div>Hello, Webpack!</div>;

export default App;

  • src/index.tsx
import { createRoot } from "react-dom/client";
import App from "@src/App";

const container = document.getElementById("root");
const root = createRoot(container as Element);

root.render(<App />);

여기까지 설정이 끝났다면 디렉토리 구조가 아래와 같이 나오게 된다.

├── babel.config.js
├── config
│   ├── webpack.common.ts
│   ├── webpack.dev.ts
│   └── webpack.prod.ts
├── package.json
├── public
│   └── index.html
├── src
│   ├── App.tsx
│   └── index.tsx
├── tsconfig.json
└── yarn.lock

이제 yarn dev or npm run dev 명령어를 사용해 실행시켜보면 아주 잘 작동한다!

profile
건데브

1개의 댓글

comment-user-thumbnail
2023년 8월 11일

정리가 잘 된 글이네요. 도움이 됐습니다.

답글 달기