36 |
37 |
--------------------------------------------------------------------------------
/fundamentals/bundling/.vitepress/theme/custom.css:
--------------------------------------------------------------------------------
1 | html.dark .light-only {
2 | display: none !important;
3 | }
4 |
5 | html:not(.dark) .dark-only {
6 | display: none !important;
7 | }
8 |
9 | :root {
10 | --vp-font-family-base: "Toss Product Sans", ui-sans-serif, system-ui,
11 | sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
12 | "Noto Color Emoji";
13 | }
14 |
15 | :root[lang="ko"] {
16 | word-break: keep-all;
17 | }
18 |
--------------------------------------------------------------------------------
/fundamentals/bundling/.vitepress/theme/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useLocale";
2 | export * from "./useGiscusTheme";
3 |
--------------------------------------------------------------------------------
/fundamentals/bundling/.vitepress/theme/hooks/useGiscusTheme.tsx:
--------------------------------------------------------------------------------
1 | import { ref, watch, computed, onMounted } from "vue";
2 | import { useData } from "vitepress";
3 | import { GISCUS_ORIGIN, GISCUS_THEME, sendGiscusMessage } from "../utils";
4 |
5 | export function useGiscusTheme() {
6 | const { isDark } = useData();
7 | const isIframeLoaded = ref(false);
8 |
9 | const syncTheme = () => {
10 | sendGiscusMessage({
11 | setConfig: {
12 | theme: isDark.value ? GISCUS_THEME.dark : GISCUS_THEME.light
13 | }
14 | });
15 | };
16 |
17 | const setupThemeHandler = () => {
18 | window.addEventListener("message", (event) => {
19 | if (event.origin === GISCUS_ORIGIN && event.data?.giscus != null) {
20 | isIframeLoaded.value = true;
21 | syncTheme();
22 | }
23 | });
24 | };
25 |
26 | onMounted(() => {
27 | setupThemeHandler();
28 | });
29 |
30 | watch(isDark, () => {
31 | if (isIframeLoaded.value) {
32 | syncTheme();
33 | }
34 | });
35 |
36 | return {
37 | theme: computed(() =>
38 | isDark.value ? GISCUS_THEME.dark : GISCUS_THEME.light
39 | )
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/fundamentals/bundling/.vitepress/theme/hooks/useLocale.ts:
--------------------------------------------------------------------------------
1 | import { useData } from "vitepress";
2 | import { computed } from "vue";
3 |
4 | export function useLocale() {
5 | const { lang } = useData();
6 |
7 | const isKorean = computed(() => lang.value === "ko" || lang.value === "root");
8 |
9 | return {
10 | isKorean
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/fundamentals/bundling/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | import DefaultTheme from "vitepress/theme";
2 | import Layout from "./Layout.vue";
3 | import * as amplitude from "@amplitude/analytics-browser";
4 | import "./custom.css";
5 | import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client';
6 |
7 | export default {
8 | extends: DefaultTheme,
9 | Layout,
10 | async enhanceApp({ app }) {
11 | if (typeof window !== "undefined") {
12 | const amplitudeApiKey = (import.meta as any).env.VITE_AMPLITUDE_API_KEY;
13 | amplitude.init(amplitudeApiKey, { autocapture: true });
14 | enhanceAppWithTabs(app)
15 | }
16 | }
17 | };
--------------------------------------------------------------------------------
/fundamentals/bundling/.vitepress/theme/utils/index.ts:
--------------------------------------------------------------------------------
1 | export const GISCUS_ORIGIN = "https://giscus.app" as const;
2 |
3 | export const GISCUS_LANG_MAP = {
4 | ko: "ko",
5 | en: "en",
6 | ja: "ja",
7 | "zh-hans": "zh-CN"
8 | } as const;
9 |
10 | export const GISCUS_THEME = {
11 | light: "light_tritanopia",
12 | dark: "dark_tritanopia"
13 | };
14 |
15 | export function getGiscusLang(lang) {
16 | return GISCUS_LANG_MAP[lang] || "en";
17 | }
18 |
19 | export function sendGiscusMessage(message: T) {
20 | const iframe = document.querySelector(
21 | "iframe.giscus-frame"
22 | );
23 | if (!iframe) return;
24 |
25 | iframe.contentWindow?.postMessage({ giscus: message }, GISCUS_ORIGIN);
26 | }
27 |
--------------------------------------------------------------------------------
/fundamentals/bundling/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "arcanis.vscode-zipfs"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/fundamentals/bundling/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll": "explicit"
4 | },
5 | "editor.formatOnSave": true,
6 | "eslint.nodePath": "../../.yarn/sdks",
7 | "prettier.prettierPath": "../../.yarn/sdks/prettier/index.cjs",
8 | "typescript.tsdk": "../../.yarn/sdks/typescript/lib",
9 | "typescript.enablePromptUseWorkspaceTsdk": true,
10 | "search.exclude": {
11 | "**/.yarn": true,
12 | "**/.pnp.*": true,
13 | "**/.next": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/fundamentals/bundling/deep-dive/bundling-process/overview.md:
--------------------------------------------------------------------------------
1 | # 번들링, 꼭 필요할까요?
2 |
3 | 프로젝트 규모가 커지면 모듈 구조가 복잡해져요. 동일한 모듈을 여러 곳에서 참조하거나, 순환 참조가 발생해 실행 순서를 예측하기 어려워질 수 있어요. 만약 브라우저에서 각 모듈을 개별로 불러온다면, 모든 의존성을 직접 관리해야 하고, 이는 개발과 유지보수를 어렵게 만들어요.
4 |
5 | 번들링은 이런 복잡성을 해결하기 위한 방법이에요.
6 | 모듈 간 연결을 정리하고, 필요한 코드만 묶어 실행 흐름을 최적화해요. 덕분에 브라우저는 효율적으로 코드를 불러오고 안정적으로 실행할 수 있어요.
7 |
8 | ## 번들링 과정
9 |
10 | 번들링은 프로젝트에 흩어져 있는 모듈을 분석해, 실행에 필요한 순서와 구조를 정리하는 작업이에요.
11 | 번들러는 코드의 연결 관계를 파악하고, 이를 하나의 파일로 묶어 브라우저가 효율적으로 코드를 실행할 수 있도록 도와줘요.
12 |
13 | 이 과정은 다음 세 단계로 이루어져요.
14 |
15 | ### 1. 모듈 탐색 (Module Resolution)
16 |
17 | 번들러는 진입점(Entry Point)부터 시작해, import나 require 구문을 따라가며 연결된 모든 모듈을 탐색해요. 이 과정에서 애플리케이션 전체의 참조 구조를 파악하죠.
18 |
19 | 예를 들어, index.js에서 math.js 모듈을 가져와 사용하는 경우를 살펴볼게요.
20 |
21 | ```javascript
22 | // index.js
23 | import { add } from './math.js';
24 | console.log(add(2, 3));
25 |
26 | // math.js
27 | export function add(a, b) {
28 | return a + b;
29 | }
30 | ```
31 |
32 | 번들러는 진입점(Entry Point)인 index.js에서 시작해, import나 require를 따라가며 연결된 모듈들을 하나씩 찾아가요.
33 |
34 | 코드 속 참조를 따라 이동하며 전체 애플리케이션이 어떤 식으로 구성되어 있는지 탐색해요.
35 |
36 | ### 2. 의존성 구조 정리
37 |
38 | 탐색한 정보를 바탕으로, 번들러는 의존성 그래프(Dependency Graph)를 만들어요.
39 | 이 그래프는 각 모듈이 어떤 다른 모듈에 의존하는지를 보여주고, 번들 파일 생성의 설계도가 돼요.
40 |
41 | 탐색 결과를 아래와 같은 형태로 정리할 수 있어요.
42 | ```json
43 | {
44 | "index.js": {
45 | "dependencies": ["math.js"],
46 | "code": "console.log(add(2, 3));"
47 | },
48 | "math.js": {
49 | "dependencies": [],
50 | "code": "function add(a, b) { return a + b; }"
51 | }
52 | }
53 | ```
54 | 정리된 객체를 보면, index.js는 math.js에 의존하고, math.js는 다른 의존성이 없음을 확인할 수 있어요.
55 |
56 | ### 3. 번들 파일 생성
57 |
58 | 마지막으로 번들러는 의존성 그래프를 바탕으로 필요한 모든 코드를 하나로 합쳐요.
59 | 이를 통해 브라우저가 여러 파일을 따로 요청하지 않고도 한 번에 코드를 불러올 수 있게 돼요.
60 |
61 | ```javascript
62 | (function(modules) {
63 | function require(file) {
64 | const exports = {};
65 | modules[file](exports, require);
66 | return exports;
67 | }
68 |
69 | require('index.js');
70 | })({
71 | 'index.js': function(exports, require) {
72 | const { add } = require('math.js');
73 | console.log(add(2, 3));
74 | },
75 | 'math.js': function(exports) {
76 | exports.add = function(a, b) {
77 | return a + b;
78 | };
79 | }
80 | });
81 | ```
82 |
83 | 번들 파일은 각각의 모듈을 객체로 관리하고, require 함수를 통해 모듈 간 의존성을 연결해요.
84 | 덕분에 브라우저는 별도의 파일 요청 없이 모든 코드를 순서대로 실행할 수 있어요.
85 |
86 | :::details Q. 번들 파일에서는 왜 즉시 실행 함수(IIFE)를 사용할까요?
87 | 1. **스코프를 분리할 수 있어요**
88 |
89 | IIFE는 자체 스코프를 가지기 때문에, 내부 변수나 함수가 전역 스코프를 오염시키지 않아요.
90 |
91 | 2. **모듈 시스템을 안전하게 구성할 수 있어요**
92 |
93 | `require` 함수나 `modules` 객체 같은 모듈 로딩 로직을 IIFE 내부에서 정의하면 외부와 격리된 안전한 실행 환경을 만들 수 있어요.
94 | :::
--------------------------------------------------------------------------------
/fundamentals/bundling/deep-dive/dev/overview.md:
--------------------------------------------------------------------------------
1 | # 웹팩으로 효율적인 개발환경 구성하기
2 |
3 | 현대 프론트엔드 개발 환경에서는 소스 코드를 수정했을 때 브라우저에 즉시 반영되거나, 에러가 발생했을 때 원본 소스 코드를 쉽게 확인할 수 있어요. 또, 빌드 환경에 따라 환경 변수를 사용해 빌드 결과물을 다르게 만들 수도 있어요.
4 |
5 | 웹팩은 단순한 코드 번들러를 넘어, 이렇게 개발 편의성을 높이는 다양한 기능을 제공해요. 예를 들어 HMR(Hot Module Replacement)로 코드 변경 사항을 브라우저에 즉시 반영하거나, 소스 맵(Source Map)을 사용해 빌드된 코드에서도 원본 소스를 쉽게 추적할 수 있어요.
6 |
7 | ## 실시간으로 코드를 반영하는 개발 서버
8 |
9 | 브라우저에서는 기본적으로 HTTP 요청을 통해서만 에셋(이미지, JavaScript 파일 등)을 가져올 수 있어요. 그래서 로컬에서 작업 중인 JavaScript 코드도 반드시 `localhost`로 요청을 보내야 사용할 수 있죠. 이를 위해 작업 중인 파일을 제공할 웹 서버가 필요해요.
10 |
11 | 웹팩은 기본적으로 에셋을 제공(서빙)하는 개발 서버를 제공해요. 리액트 프로젝트를 `Create React App(CRA)`나 `Vite`로 생성하면, `start` 스크립트를 실행해 바로 개발 환경을 구성할 수 있는 것도 이 개발 서버 덕분이에요. 이 역할을 담당하는 것이 웹팩의 개발 서버(`webpack-dev-server`)예요.
12 |
13 | `webpack-dev-server`는 다음과 같은 기능을 제공해요.
14 |
15 | - **에셋 서빙**: 웹팩으로 번들링된 파일을 브라우저에 제공해요.
16 | - **HMR(Hot Module Replacement)**: 소스 코드가 수정되면 브라우저를 새로고침하지 않아도 변경 사항을 실시간으로 반영해요.
17 | - **프록시 설정**: 특정 API 요청을 다른 서버로 전달하거나 응답을 조작할 수 있어요.
18 |
19 | ## 디버깅을 쉽게 만드는 소스맵
20 |
21 | 소스맵은 다음과 같은 기능을 제공해요.
22 |
23 | - **디버깅 편의성**: 브라우저 개발자 도구에서 원본 코드를 기준으로 중단점(Breakpoint)을 설정할 수 있어요.
24 | - **에러 위치 확인**: 에러가 발생했을 때 난독화된 번들 코드가 아니라, 원본 코드의 에러 위치를 바로 확인할 수 있어요.
25 |
26 | 소스맵이 있으면 브라우저에서 디버깅할 때도 원본 소스를 기준으로 중단점을 설정할 수 있어요. 소스맵이 없다면 난독화된 코드를 보면서 디버깅해야 하므로 개발이 매우 어려워져요.
27 |
28 | ## 환경별로 동작을 설정하는 환경 변수
29 |
30 | 환경 변수는 애플리케이션이 실행될 환경(개발, 스테이징, 운영)에 따라 동작을 달리하기 위해 사용돼요.
31 |
32 | 환경 변수는 다음과 같은 기능을 제공해요.
33 |
34 | - **환경 맞춤 설정**: 개발 환경에서는 로컬 서버를, 운영 환경에서는 배포된 서버를 사용하도록 쉽게 전환할 수 있어요.
35 | - **성능 최적화**: 런타임 중 환경 변수 로딩 없이 미리 치환된 값을 사용하므로 코드 크기를 줄이고 실행 성능을 높일 수 있어요.
36 |
--------------------------------------------------------------------------------
/fundamentals/bundling/deep-dive/overview.md:
--------------------------------------------------------------------------------
1 | # 소개
2 |
3 | 튜토리얼을 통해 번들러의 기본 사용법을 익히셨다면, 이제는 한 단계 더 깊이 들어가볼 차례예요.
4 |
5 |
6 | 심화 학습에서는 단순히 도구를 사용하는 것을 넘어서, 번들러가 내부적으로 어떻게 작동하는지, 구성 방식과 설정이 실제 개발 효율과 성능에 어떤 영향을 주는지 함께 살펴볼게요.
7 |
8 | ## 번들러 동작 이해하기
9 |
10 | 번들러가 어떤 과정을 거쳐 작동하는지 단계별로 살펴보며, 각 단계가 어떤 문제를 해결하는지 함께 살펴봐요.
11 |
12 | 1. [진입점](../deep-dive/bundling-process/entry): 번들링이 시작되는 파일이에요. 여기서부터 모든 의존성을 따라가며 필요한 자원을 수집해요.
13 |
14 | 2. [경로 탐색](../deep-dive/bundling-process/resolution): `import`나 `require` 문을 보고, 실제 어떤 파일을 불러올지 결정하는 과정이에요. 이 흐름을 이해하면 경로 오류를 줄이고 빌드 속도도 높일 수 있어요.
15 |
16 | 3. [로더](../deep-dive/bundling-process/loader): 다양한 파일 형식(JS, CSS, 이미지 등)을 번들러가 이해할 수 있는 코드로 바꿔줘요.
17 |
18 | 4. [플러그인](../deep-dive/bundling-process/plugin): 빌드 과정을 확장하거나 제어하고 싶을 때 사용하는 도구예요. 예를 들어, HTML 파일을 자동으로 생성하거나, 환경 변수 주입처럼 전체 빌드 흐름을 바꿔줄 수 있어요.
19 |
20 | 5. [출력](../deep-dive/bundling-process/output): 최종 번들이 어떤 이름과 경로로 저장될지 결정하는 부분이에요. 여러 개의 파일로 나뉘거나, 해시가 붙는 등의 설정도 여기서 할 수 있어요.
21 |
22 | ## 개발 환경 구성하기
23 |
24 | 번들러는 개발 속도와 편의성을 높이기 위해 다양한 기능들을 제공해요.
25 | 각각의 설정을 활용하면 디버깅도 쉬워지고, 더 빠르고 안정적인 개발을 할 수 있어요.
26 |
27 | 1. [개발 서버](../deep-dive/dev/dev-server): 코드 변경 사항을 실시간으로 반영해서 개발 속도를 높일 수 있어요. 개발 중 페이지를 새로고침하지 않아도 최신 상태를 유지할 수 있어요.
28 |
29 | 2. [HMR(Hot Module Replacement)](../deep-dive/dev/hmr): 전체 페이지를 새로고침하지 않고, 수정된 모듈만 빠르게 업데이트해서 상태가 초기화되지 않고 작업을 지속할 수 있어요.
30 |
31 | 3. [소스맵(Source Map)](../deep-dive/dev/source-map): 코드와 빌드된 파일 간의 매핑 정보를 제공해서, 브라우저 디버거에서 원본 코드를 보며 디버깅할 수 있어요.
32 |
33 | ## 번들 최적화 전략
34 |
35 | 애플리케이션 성능은 사용자 경험에 직접적인 영향을 미쳐요.
36 | 최적화 기능을 잘 활용하면 더 빠르고 효율적인 애플리케이션을 만들 수 있어요.
37 |
38 | 1. [코드 스플리팅](../deep-dive/optimization/code-splitting): 하나의 큰 번들을 여러 개의 작은 번들로 나누는 기법이에요. 이를 통해 초기 로딩 속도를 줄이고 필요에 따라 추가 번들을 불러올 수 있어요.
39 |
40 | 2. [트리셰이킹](../deep-dive/optimization/tree-shaking): 실제로 사용하지 않는 코드를 분석하고 제거하여 번들 크기를 줄여요.
41 |
42 | 3. [번들 분석](../deep-dive/optimization/code-splitting): 번들 결과물을 시각적으로 분석하여 어떤 모듈이 용량을 많이 차지하는지 파악하고 개선 방향을 찾아요.
43 |
44 |
--------------------------------------------------------------------------------
/fundamentals/bundling/files/example.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/files/example.zip
--------------------------------------------------------------------------------
/fundamentals/bundling/get-started.md:
--------------------------------------------------------------------------------
1 | # 시작하기
2 |
3 | 프론트엔드 서비스를 만들 때, 개발 서버를 띄우고 프로덕션 빌드를 준비하는 모든 과정에서 번들링(Bundling) 은 빠질 수 없어요.
4 | 작은 프로젝트부터 대형 서비스까지, 파일을 효율적으로 묶고 최적화하는 과정은 웹 성능과 직결되기 때문이에요.
5 |
6 | 어느 정도 프론트엔드 개발 경험이 쌓였다면 번들링이 “어디서 어떻게” 일어나는지 이해하는 것이 프로젝트 완성도를 높이는 데 큰 도움이 돼요.
7 |
8 | **Bundling Fundamentals**는 복잡하고 어렵게 느껴지는 번들링을 쉽게 이해할 수 있게 설명해요. 번들링이란 무엇인지부터, 라이브러리 번들링 설정 튜토리얼, 그리고 대표적인 도구인 웹팩(Webpack)의 동작 원리와 최적화 방법까지, 하나씩 탄탄하게 이해할 수 있도록 도와줄 거예요.
9 |
10 | ## 이런 분들에게 추천해요
11 |
12 | - 🤔 번들링이 왜 필요한지 막연하게만 알고 있는 개발자
13 | - 🔍 번들링 도구(특히 Webpack)의 동작 원리를 정확히 이해하고 싶은 개발자
14 | - 🧰 번들 크기나 성능 이슈가 생겼을 때 뭘 고쳐야 할지 막막했던 경험이 있는 개발자
15 | - 🧱 라이브러리나 컴포넌트를 직접 만들며 번들 설정을 손봐야 했던 개발자
16 | - 🧠 "빌드만 잘 되면 됐지"에서 벗어나 번들링을 진짜 내 지식으로 만들고 싶은 개발자
17 |
18 | ## 저작자
19 |
20 | - [mycolki](https://github.com/mycolki)
21 | - [helloworld-hellohyeon](https://github.com/helloworld-hellohyeon)
22 | - [raon0211](https://github.com/raon0211)
23 | - [donghyeon](https://github.com/Kimbangg)
24 | - [milooy](https://github.com/milooy)
25 | - [jennybehan](https://github.com/jennybehan)
--------------------------------------------------------------------------------
/fundamentals/bundling/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "Bundling Fundamentals"
7 | tagline: "프론트엔드 번들링의 모든 것"
8 | image:
9 | loading: eager
10 | fetchpriority: high
11 | decoding: async
12 | src: /images/bf-symbol.webp
13 | alt: Frontend Fundamentals symbol
14 | actions:
15 | - text: 시작하기
16 | link: /get-started
17 | - theme: alt
18 | text: 번들링이란
19 | link: /overview
20 |
21 | features:
22 | - icon: 📦
23 | title: 번들링, 왜 필요할까요?
24 | details: 번들링이 무엇이고 왜 중요한지 쉽게 알려드려요.
25 | link: /overview
26 | - icon: 🚀
27 | title: 웹팩으로 실습해봐요
28 | details: 웹팩을 직접 따라 하며 번들링 과정을 익혀봐요.
29 | link: /webpack-tutorial/intro
30 | - icon: 🔍
31 | title: 더 깊이 배우고 싶다면
32 | details: 웹팩의 핵심 개념과 고급 기법까지 단계별로 배워봐요.
33 | link: /deep-dive/bundling-process/overview
34 | ---
35 |
--------------------------------------------------------------------------------
/fundamentals/bundling/overview.md:
--------------------------------------------------------------------------------
1 | # 번들링이란
2 |
3 | 번들링(Bundling)은 여러 개의 파일(특히 JavaScript, CSS, 이미지 등 웹 개발에 필요한 리소스 파일들)을 하나 또는 몇 개의 파일로 묶는 작업이에요. 이렇게 묶인 파일을 번들(Bundle)이라고 불러요.
4 |
5 | 
6 |
7 |
8 | 웹 애플리케이션을 만들다 보면 JavaScript 파일이 수십, 수백 개로 쪼개지게 돼요. 각각의 파일은 기능 단위로 잘게 나눠지기 때문에 개발하기는 편하지만, 브라우저가 서버로부터 파일의 수만큼 요청을 보내게 되어 네트워크 비용이 커지고 로딩 속도가 느려질 수 있어요.
9 |
10 | 번들링은 이 문제를 해결하기 위한 방법이에요.
11 |
12 | ## 번들링의 목적
13 |
14 | 1. **요청 수 감소**: 수십, 수백개 파일을 하나로 묶어서 브라우저가 요청해야 할 파일 수를 줄여 로딩 속도를 향상시켜요.
15 | 2. **캐싱 최적화**: 묶인 파일 하나만 캐시하면 되어 효율적이에요.
16 | 3. **유지보수성과 배포 효율성**: 개발할 때는 모듈화를 유지하면서, 배포할 때는 성능 최적화를 할 수 있어요.
17 |
18 |
19 | ## 번들링 과정 한눈에 보기
20 |
21 | 간단한 예시로 번들링의 흐름을 이해해볼게요.
22 |
23 | ### 1. 여러 개의 JavaScript 파일이 있어요
24 |
25 | 개발할 때는 기능별로 파일을 잘게 나눠서 관리해요.
26 |
27 | ```
28 | ├── index.js
29 | ├── utils.js
30 | ├── auth.js
31 | └── dashboard.js
32 | ```
33 |
34 | ### 2. 파일들이 서로 의존하고 있어요
35 |
36 | 예를 들면, `index.js`가 `auth.js`와 `dashboard.js`를 불러오고, `auth.js`는 `utils.js`를 사용하고 있을 수 있어요.
37 | 즉, 파일 간에 의존성(dependency) 관계가 생겨요.
38 |
39 | ### 3. 번들러가 파일 관계를 분석해요
40 |
41 | 번들러는 프로젝트 안의 파일들을 스캔하면서 누가 누구를 쓰는지 분석해요.
42 | 시작 지점(예: `index.js`)부터 출발해서 모든 필요한 파일을 찾고, 의존성 그래프를 그려요.
43 |
44 | ### 4. 하나의 파일로 묶어요 (Bundling)
45 |
46 | 필요한 파일들을 의존성 순서에 맞춰서 하나의 파일로 합쳐요.
47 |
48 | ```
49 | └── bundle.js
50 | ```
51 |
52 | ### 5. 번들러가 추가 최적화 작업도 해줘요
53 | - 사용되지 않는 코드는 제거해요. ([트리 셰이킹](/deep-dive/optimization/tree-shaking.md))
54 | - 필요한 경우, 여러 개의 작은 번들로 나누기도 해요. ([코드 스플리팅](/deep-dive/optimization/code-splitting.md))
55 | - 코드에서 공백, 주석을 없애서 크기를 줄여요. (Minification)
56 |
57 | ### 6. 최종 결과물을 배포해요
58 |
59 | 최적화된 `bundle.js` 파일을 서버에 올리고, 사용자는 브라우저로 빠르게 접근할 수 있어요.
60 |
61 | ## 다음 단계
62 |
63 | 위의 예시처럼, 현대 번들링 도구들은 단순히 여러 파일을 하나로 합치는 것에 그치지 않고, 코드를 더 빠르게 동작하도록 압축하거나, 사용하지 않는 코드를 제거하는 등 다양한 최적화도 자동으로 해줘요.
64 |
65 | 번들러의 다양한 기능이 궁금하다면 [번들러란](/bundler) 문서를 확인해 보세요.
66 |
--------------------------------------------------------------------------------
/fundamentals/bundling/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@frontend-fundamentals/bundling",
3 | "version": "1.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "docs:dev": "vitepress dev",
8 | "docs:build": "vitepress build",
9 | "docs:preview": "vitepress preview"
10 | },
11 | "dependencies": {
12 | "@amplitude/analytics-browser": "^2.11.11",
13 | "markdown-it-footnote": "^4.0.0",
14 | "typescript": "^5.6.3",
15 | "vitepress": "^1.4.1",
16 | "vitepress-plugin-tabs": "^0.7.1"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bf-symbol.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bf-symbol.webp
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/browser-thinking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/browser-thinking.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/build-result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/build-result.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundle-analyzer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundle-analyzer.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundle-dev-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundle-dev-server.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundle-img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundle-img.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundler.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundling/array-entry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundling/array-entry.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundling/depend-on-before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundling/depend-on-before.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundling/depend-on-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundling/depend-on-example.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundling/depend-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundling/depend-on.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundling/dependency-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundling/dependency-graph.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundling/module-resolution.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundling/module-resolution.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundling/multiple-entry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundling/multiple-entry.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/bundling/single-entry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/bundling/single-entry.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/code-insert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/code-insert.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/emoji-of-the-day.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/emoji-of-the-day.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/entry_object-dependon-shared-after.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/entry_object-dependon-shared-after.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/entry_object-dependon-shared-before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/entry_object-dependon-shared-before.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/entry_object-network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/entry_object-network.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/entry_single-network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/entry_single-network.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/esm-dev-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/esm-dev-server.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/favicon.ico
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/hmr-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/hmr-1.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/hmr-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/hmr-2.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/mode-compare1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/mode-compare1.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/mode-compare2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/mode-compare2.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/network-minified.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/network-minified.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/project-reset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/project-reset.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/react-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/react-app.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/resolve_compile-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/resolve_compile-error.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/source-map-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/source-map-1.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/source-map-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/source-map-2.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/source-map-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/source-map-3.png
--------------------------------------------------------------------------------
/fundamentals/bundling/public/images/style-less.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/bundling/public/images/style-less.png
--------------------------------------------------------------------------------
/fundamentals/bundling/rollup-tutorial/intro.md:
--------------------------------------------------------------------------------
1 | # 튜토리얼 소개
2 |
3 | 롤업(rollup)은 라이브러리 제작에 최적화된 번들러예요.
4 | 이 튜토리얼에서는 간단한 예제 라이브러리를 만들고 npm으로 배포하는 과정을 살펴볼거예요.
5 |
6 | 곧 출시됩니다. 기다려주세요!
--------------------------------------------------------------------------------
/fundamentals/bundling/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "composite": true
7 | },
8 | "include": [
9 | ".vitepress/**/*",
10 | "**/*.ts",
11 | "**/*.tsx",
12 | "**/*.vue"
13 | ]
14 | }
--------------------------------------------------------------------------------
/fundamentals/bundling/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | {
4 | "source": "/(.*)",
5 | "destination": "/index.html"
6 | }
7 | ]
8 | }
--------------------------------------------------------------------------------
/fundamentals/bundling/webpack-tutorial/dev-server.md:
--------------------------------------------------------------------------------
1 | # 개발 서버로 생산성 높이기
2 |
3 | 지금까지는 코드를 수정할 때마다 매번 `npm run build`를 돌리고, 브라우저 새로고침을 직접 눌러야 했죠.
4 |
5 | 이제는 그럴 필요 없어요. **웹팩 개발 서버**를 도입하면 코드를 저장하는 순간 자동으로 브라우저에 반영돼요.
6 | 게다가 화면이 리로드되지 않아도 컴포넌트가 실시간으로 바뀌는 `Hot Module Replacement` 기능까지 쓸 수 있어요.
7 |
8 | ## 웹팩 개발 서버(`webpack-dev-server`)
9 |
10 | 웹팩 개발 서저는 우리가 작성한 코드를 메모리에 번들링해서, 브라우저에 빠르게 반영해주는 가상의 개발용 서버로, 다음과 같은 기능을 제공해요.
11 |
12 | - 코드 저장시 자동 새로고침
13 | - 스타일이나 컴포넌트 수정 시 상태를 유지하면서 실시간 반영(Hot Module Replacement)
14 | - 브라우저에서 에러 메시지를 오버레이로 띄워주기
15 | - API 서버와 연동 시 프록시 설정 가능
16 |
17 | ## 1. 개발 서버 설치하기
18 |
19 | 먼저 다음 명령어로 개발 서버 패키지를 설치할게요.
20 |
21 | ```bash
22 | npm install --save-dev webpack-dev-server
23 | ```
24 |
25 | ## 2. 웹팩 설정에 devServer 추가하기
26 |
27 | 다음으로 `webpack.config.js` 파일에 개발 서버 설정을 추가해 주세요.
28 |
29 | ```js{5-16}
30 | const path = require("path");
31 |
32 | module.exports = {
33 | // ... 기존 설정
34 | devServer: {
35 | static: {
36 | directory: path.join(__dirname, "dist") // 빌드된 파일을 이 경로에서 서빙해요
37 | },
38 | port: 3000, // localhost:3000에서 실행
39 | open: true, // 서버 실행 시 브라우저 자동 열기
40 | hot: true, // HMR 사용
41 | historyApiFallback: true, // SPA 라우팅 지원
42 | client: {
43 | overlay: true // 에러 발생 시 브라우저에 띄워줘요
44 | }
45 | }
46 | };
47 | ```
48 |
49 | ## 3. package.json에 실행 스크립트 추가하기
50 |
51 | `npm start`로 바로 실행할 수 있도록 `package.json`에 스크립트를 추가해요.
52 |
53 | ```json
54 | {
55 | "scripts": {
56 | "start": "webpack serve --mode development",
57 | "build": "webpack --mode production"
58 | }
59 | }
60 | ```
61 |
62 | 이제 아래 명령어로 개발 서버를 실행해볼 수 있어요.
63 |
64 | ```bash
65 | npm start
66 | ```
67 |
68 | 브라우저가 자동으로 열리고, 코드를 수정하면 바로바로 반영되는 걸 확인할 수 있어요.
69 |
70 | ## 정리
71 |
72 | 지금까지는 코드를 수정할 때마다 `npm run build`를 직접 실행하고, 브라우저를 새로고침해가며 웹팩의 동작을 직접 경험해봤죠.
73 |
74 | 개발 서버를 마지막에 소개한 건 의도적인 선택이었어요. 이 과정을 거쳤기 때문에, 이제 웹팩 개발 서버의 편리함이 훨씬 더 와닿을 거예요. 마치 수동 변속기로 운전을 배운 뒤 자동 변속기의 고마움을 느끼는 것처럼요. 😊
75 |
76 | ---
77 |
78 | 지금까지 웹팩의 핵심 개념부터 실전 적용까지 한 걸음씩 밟아왔어요. 단계마다 실제 '오늘의 이모지' 프로젝트를 개선하면서 웹팩이 어떤 역할을 하고, 어떤 문제를 해결해주는지 몸으로 익힐 수 있었을 거예요.
79 |
80 | 이제 여러분은 웹팩을 단순한 설정 도구가 아니라 프로젝트를 더 잘 구조화하고 유지보수하기 위한 도구로 활용할 수 있을 거예요.
81 |
82 | 앞으로 새로운 프로젝트를 시작할 때 자신만의 웹팩 설정을 만들어보세요. 이 튜토리얼이 그 출발점이 되어주길 바래요!
--------------------------------------------------------------------------------
/fundamentals/bundling/webpack-tutorial/example-project/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/fundamentals/bundling/webpack-tutorial/example-project/emoji.js:
--------------------------------------------------------------------------------
1 | const emojis = [
2 | { icon: '😊', name: 'Smiling Face' },
3 | { icon: '🚀', name: 'Rocket' },
4 | { icon: '🍕', name: 'Pizza' },
5 | { icon: '🐱', name: 'Cat' },
6 | { icon: '🌈', name: 'Rainbow' },
7 | { icon: '🎸', name: 'Guitar' }
8 | ];
--------------------------------------------------------------------------------
/fundamentals/bundling/webpack-tutorial/example-project/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Emoji of the Day
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Emoji of the Day
17 |
18 |
19 |
😊
20 |
Today's Emoji
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/fundamentals/bundling/webpack-tutorial/example-project/main.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', function() {
2 | const today = new Date();
3 | const formattedDate = dateFns.format(today, 'MMMM d, yyyy');
4 | document.getElementById('dateDisplay').textContent = formattedDate;
5 |
6 | showRandomEmoji();
7 | });
8 |
9 | function showRandomEmoji() {
10 | const randomIndex = Math.floor(Math.random() * emojis.length);
11 | const selectedEmoji = emojis[randomIndex];
12 |
13 | document.getElementById('emojiDisplay').textContent = selectedEmoji.icon;
14 | document.getElementById('emojiName').textContent = selectedEmoji.name;
15 | }
--------------------------------------------------------------------------------
/fundamentals/bundling/webpack-tutorial/example-project/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Inter', sans-serif;
3 | margin: 0;
4 | padding: 0;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | min-height: 100vh;
9 | background-color: #f5f5f5;
10 | }
11 |
12 | .container {
13 | background-color: white;
14 | border-radius: 10px;
15 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
16 | padding: 2rem;
17 | text-align: center;
18 | max-width: 500px;
19 | width: 100%;
20 | }
21 |
22 | .logo {
23 | max-width: 100px;
24 | margin-bottom: 1rem;
25 | }
26 |
27 | h1 {
28 | color: #333;
29 | margin-bottom: 1rem;
30 | }
31 |
32 | .date-display {
33 | color: #666;
34 | margin-bottom: 2rem;
35 | }
36 |
37 | .emoji-container {
38 | margin: 2rem 0;
39 | }
40 |
41 | .emoji {
42 | font-size: 4rem;
43 | margin-bottom: 0.5rem;
44 | }
45 |
46 | .emoji-name {
47 | font-size: 1.2rem;
48 | color: #555;
49 | }
--------------------------------------------------------------------------------
/fundamentals/bundling/webpack-tutorial/intro.md:
--------------------------------------------------------------------------------
1 | # 소개
2 |
3 | 이 튜토리얼에서는 외부 라이브러리, 이미지, 폰트, CSS 같은 다양한 자원을 웹팩(Webpack)으로 한데 묶어보면서, 웹 개발에서 번들링이 왜 중요한지 직접 체험해볼 수 있어요.
4 | 간단한 예제 프로젝트인 **오늘의 이모지**를 만들어가며, 웹팩의 기본 개념부터 실전 사용법까지 차근차근 익혀볼 거예요.
5 |
6 |
7 | ## 준비사항
8 | - 코드 에디터를 준비해요.
9 | - HTML과 JavaScript의 기본 개념만 알고 있으면 충분해요.
10 |
11 | ## 이런 순서로 진행돼요
12 |
13 | 1. **웹 프로젝트의 시작을 준비해요**
14 | - Node.js와 npm으로 프로젝트를 시작해요.
15 | - 웹팩을 설치하고 첫 번째 번들을 만들어요.
16 |
17 | 2. **모듈 시스템과 번들링을 이해해요**
18 | - npm으로 라이브러리를 설치하고 모듈로 불러오는 방법을 배워요.
19 | - 웹팩으로 모듈을 하나로 합치는 과정을 이해해요.
20 |
21 | 3. **현대 프론트엔드 개발 방식에 익숙해져요**
22 | - TypeScript로 타입 안전성을 높여요.
23 | - React로 컴포넌트 기반 개발을 해요.
24 | - CSS와 이미지, 폰트를 모듈처럼 다뤄요.
25 |
26 | 4. **개발 환경을 효율적으로 구성해요**
27 | - 플러그인으로 기능을 확장해요.
28 | - 개발 서버로 생산성을 높여요.
29 |
30 | ## 예제 프로젝트 세팅하기
31 | 1. [오늘의 이모지 예제 프로젝트 ZIP 파일](https://github.com/toss/frontend-fundamentals/blob/main/public/files/bundling-example-project.zip)를 다운로드하고 압축을 풀어주세요.
32 | 2. 압축을 풀면 이런 폴더 구조가 보여요.
33 | ```
34 | example-project/
35 | ├── index.html
36 | ├── style.css
37 | ├── main.js
38 | ├── emoji.js
39 | └── assets/
40 | ├── logo.svg
41 | └── Inter-Regular.woff2
42 | ```
43 | 3. `index.html` 파일을 브라우저에서 열어 예제 프로젝트를 확인해보세요.
44 |
45 | 
46 |
47 | :::details HTML 파일을 어떻게 여나요?
48 | 파일 탐색기에서 `index.html` 파일을 더블클릭하거나 브라우저에 파일을 드래그 앤 드롭하면 열 수 있어요. 이 방식은 파일을 로컬에서 직접 불러오는 방식으로, 개발 초기 단계에서 간단히 결과를 확인하기 좋아요.
49 |
50 | 나중에 웹팩 개발 서버를 사용하게 되면 자동 새로고침, 모듈 핫 리로딩 등 개발 생산성을 높이는 기능들을 활용할 수 있게 되니, 기대해주세요.
51 | :::
52 |
53 | ## 다음 단계
54 | 이제 프로젝트 환경을 설정하고 첫 번째 번들을 만들어 볼게요. 차근차근 따라오시면 어느새 웹 개발의 핵심 도구인 웹팩을 자유롭게 활용할 수 있게 될 거예요.
55 |
--------------------------------------------------------------------------------
/fundamentals/bundling/webpack-tutorial/style.md:
--------------------------------------------------------------------------------
1 | # 스타일 관리하기
2 |
3 | 이번 단계에서는 '오늘의 이모지' 프로젝트의 스타일을 웹팩으로 관리하는 방법을 배워볼게요. 웹팩의 로더를 사용하면 CSS 파일도 JavaScript 모듈처럼 import해서 사용할 수 있어요.
4 |
5 | ## CSS를 모듈처럼 사용하기
6 |
7 | 기존에는 HTML 파일에서 `` 태그로 CSS를 불러왔어요. 하지만 웹팩을 사용하면 JavaScript에서 CSS를 import할 수 있어요. 이렇게 하면 CSS도 모듈처럼 관리할 수 있고, 필요한 스타일만 번들에 포함시킬 수 있어요.
8 |
9 | ```javascript
10 | // CSS를 import하면 웹팩이 알아서 처리해요
11 | import './style.css';
12 | ```
13 |
14 | ## 1. CSS 로더 설치하기
15 |
16 | 웹팩이 CSS를 처리할 수 있도록 필요한 로더들을 설치해볼게요.
17 |
18 | ```bash
19 | npm install --save-dev style-loader css-loader
20 | ```
21 |
22 | - `css-loader`: CSS 파일을 JavaScript에서 import할 수 있는 형태로 바꿔줘요.
23 | - `style-loader`: 변환된 CSS를 브라우저 실행 시 `
47 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/.vitepress/theme/components/Comments.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
11 |
12 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/community.md:
--------------------------------------------------------------------------------
1 | ---
2 | comments: false
3 | ---
4 |
5 | # 함께 만들기
6 |
7 | `Frontend Fundamentals`(FF)는 커뮤니티와 함께 좋은 코드의 기준을 만들어 가고 있어요.
8 |
9 | 지금은 토스 프론트엔드 챕터가 운영하고 있어요.
10 |
11 |
12 |
13 | ## 🙋 고민되는 코드에 대해 논의하기
14 |
15 | 고민되는 코드가 있다면 깃허브 디스커션에 글을 올려 보세요.
16 | 내 코드에 대해서 커뮤니티에서 다각도로 리뷰를 받을 수 있고, 좋은 코드의 기준에 대해 커뮤니티와 함께 고민할 수 있어요.
17 |
18 | 많은 공감을 받은 사례는 직접 Frontend Fundamentals 문서에 올릴 수 있어요. 기여 방법은 추후 공개될 예정이에요.
19 |
20 | - [깃허브 디스커션에 글 올리기](https://github.com/toss/frontend-fundamentals/discussions)
21 |
22 | ## 🗣️ 좋은 코드의 기준에 의견 더하기
23 |
24 | 좋은 코드의 기준에 대해 의견이 있거나, 새로운 의견을 더하고 싶다면 더 좋은 코드가 어떤 코드인지 투표하고, 의견을 남겨 보세요.
25 | 커뮤니티와 소통하며 더욱 풍부하고 깊이 있는 기준을 만들어 나가요.
26 |
27 | 이 코드가 좋을까? 저 코드가 좋을까? 에 대해서 나만의 기준을 확립하는 계기가 될 수 있어요.
28 |
29 | - [A vs B에 올라온 코드 보기](https://github.com/toss/frontend-fundamentals/discussions/categories/a-vs-b)
30 |
31 | ## 🏆 명예의 전당
32 |
33 | 커뮤니티에서 있었던 좋은 토론을 살펴보세요. Frontend Fundamentals 문서에 나오는 내용을 넘어, 좋은 코드에 대한 생각을 넓힐 수 있어요.
34 |
35 | - [명예의 전당](/code/community/good-discussions)
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/detail/index.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/dicussions/index.md:
--------------------------------------------------------------------------------
1 | # 토론하기
2 |
3 |
4 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/code-directory.md:
--------------------------------------------------------------------------------
1 | # 함께 수정되는 파일을 같은 디렉토리에 두기
2 |
3 |
4 |
5 |
6 |
7 | 프로젝트에서 코드를 작성하다 보면 Hook, 컴포넌트, 유틸리티 함수 등을 여러 파일로 나누어서 관리하게 돼요. 이런 파일들을 쉽게 만들고, 찾고, 삭제할 수 있도록 올바른 디렉토리 구조를 갖추는 것이 중요해요.
8 |
9 | 함께 수정되는 소스 파일을 하나의 디렉토리에 배치하면 코드의 의존 관계를 명확하게 드러낼 수 있어요. 그래서 참조하면 안 되는 파일을 함부로 참조하는 것을 막고, 연관된 파일들을 한 번에 삭제할 수 있어요.
10 |
11 | ## 📝 코드 예시
12 |
13 | 다음 코드는 프로젝트의 모든 파일을 모듈의 종류(Presentational 컴포넌트, Container 컴포넌트, Hook, 상수 등)에 따라 분류한 디렉토리 구조예요.
14 |
15 | ```text
16 | └─ src
17 | ├─ components
18 | ├─ constants
19 | ├─ containers
20 | ├─ contexts
21 | ├─ remotes
22 | ├─ hooks
23 | ├─ utils
24 | └─ ...
25 | ```
26 |
27 | ## 👃 코드 냄새 맡아보기
28 |
29 | ### 응집도
30 |
31 | 파일을 이렇게 종류별로 나누면 어떤 코드가 어떤 코드를 참조하는지 쉽게 확인할 수 없어요. 코드 파일 사이의 의존 관계는 개발자가 스스로 코드를 분석하면서 챙겨야 해요.
32 | 또한 더 이상 특정 컴포넌트나 Hook, 유틸리티 함수가 사용되지 않아서 삭제된다고 했을 때, 연관된 코드가 함께 삭제되지 못해서 사용되지 않는 코드가 남아있게 될 수도 있어요.
33 |
34 | 프로젝트의 크기는 점점 커지기 마련인데, 프로젝트의 크기가 2배, 10배, 100배 커짐에 따라서 코드 사이의 의존관계도 크게 복잡해질 수 있어요. 디렉토리 하나가 100개가 넘는 파일을 담고 있게 될 수도 있어요.
35 |
36 | ## ✏️ 개선해보기
37 |
38 | 다음은 함께 수정되는 코드 파일끼리 하나의 디렉토리를 이루도록 구조를 개선한 예시예요.
39 |
40 | ```text
41 | └─ src
42 | │ // 전체 프로젝트에서 사용되는 코드
43 | ├─ components
44 | ├─ containers
45 | ├─ hooks
46 | ├─ utils
47 | ├─ ...
48 | │
49 | └─ domains
50 | │ // Domain1에서만 사용되는 코드
51 | ├─ Domain1
52 | │ ├─ components
53 | │ ├─ containers
54 | │ ├─ hooks
55 | │ ├─ utils
56 | │ └─ ...
57 | │
58 | │ // Domain2에서만 사용되는 코드
59 | └─ Domain2
60 | ├─ components
61 | ├─ containers
62 | ├─ hooks
63 | ├─ utils
64 | └─ ...
65 | ```
66 |
67 | 함께 수정되는 코드 파일을 하나의 디렉토리 아래에 둔다면, 코드 사이의 의존 관계를 파악하기 쉬워요.
68 |
69 | 예를 들어, 다음과 같이 한 도메인(`Domain1`)의 하위 코드에서 다른 도메인(`Domain2`)의 소스 코드를 참조한다고 생각해 볼게요.
70 |
71 | ```typescript
72 | import { useFoo } from "../../../Domain2/hooks/useFoo";
73 | ```
74 |
75 | 이런 import 문을 만난다면 잘못된 파일을 참조하고 있다는 것을 쉽게 인지할 수 있게 돼요.
76 |
77 | 또한, 특정 기능과 관련된 코드를 삭제할 때 한 디렉토리 전체를 삭제하면 깔끔하게 모든 코드가 삭제되므로, 프로젝트 내부에 더 이상 사용되지 않는 코드가 없도록 할 수 있어요.
78 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/condition-name.md:
--------------------------------------------------------------------------------
1 | # 복잡한 조건에 이름 붙이기
2 |
3 |
4 |
5 |
6 |
7 | 복잡한 조건식이 특별한 이름 없이 사용되면, 조건이 뜻하는 바를 한눈에 파악하기 어려워요.
8 |
9 | ## 📝 코드 예시
10 |
11 | 다음 코드는 상품 중에서 카테고리와 가격 범위가 일치하는 상품만 필터링하는 로직이에요.
12 |
13 | ```typescript
14 | const result = products.filter((product) =>
15 | product.categories.some(
16 | (category) =>
17 | category.id === targetCategory.id &&
18 | product.prices.some(
19 | (price) => price >= minPrice && price <= maxPrice
20 | )
21 | )
22 | );
23 | ```
24 |
25 | ## 👃 코드 냄새 맡아보기
26 |
27 | ### 가독성
28 |
29 | 이 코드에서는 익명 함수와 조건이 복잡하게 얽혀 있어요. `filter`와 `some`, `&&` 같은 로직이 여러 단계로 중첩되어 있어서 정확한 조건을 파악하기 어려워졌어요.
30 |
31 | 코드를 읽는 사람이 한 번에 고려해야 하는 맥락이 많아서, 가독성이 떨어져요. [^1]
32 |
33 | [^1]: [프로그래머의 뇌](https://www.yes24.com/product/goods/105911017)에 따르면, 사람의 뇌가 한 번에 저장할 수 있는 정보의 숫자는 6개라고 해요.
34 |
35 | ## ✏️ 개선해보기
36 |
37 | 다음 코드와 같이 조건에 명시적인 이름을 붙이면, 코드를 읽는 사람이 한 번에 고려해야 할 맥락을 줄일 수 있어요.
38 |
39 | ```typescript
40 | const matchedProducts = products.filter((product) => {
41 | return product.categories.some((category) => {
42 | const isSameCategory = category.id === targetCategory.id;
43 | const isPriceInRange = product.prices.some(
44 | (price) => price >= minPrice && price <= maxPrice
45 | );
46 |
47 | return isSameCategory && isPriceInRange;
48 | });
49 | });
50 | ```
51 |
52 | 명시적으로 같은 카테고리 안에 속해 있고, 가격 범위가 맞는 제품들로 필터링한다고 작성함으로써, 복잡한 조건식을 따라가지 않고도 코드의 의도를 명확히 드러낼 수 있어요.
53 |
54 | ## 🔍 더 알아보기: 조건식에 이름을 붙이는 기준
55 |
56 | 언제 조건식이나 함수에 이름을 붙이고 분리하는 것이 좋을까요?
57 |
58 | ### 조건에 이름을 붙이는 것이 좋을 때
59 |
60 | - **복잡한 로직을 다룰 때**: 조건문이나 함수에서 복잡한 로직이 여러 줄에 걸쳐 처리되면, 이름을 붙여 함수의 역할을 명확히 드러내는 것이 좋아요. 이렇게 하면 코드 가독성이 높아지고, 유지보수나 코드 리뷰가 더 쉬워져요.
61 |
62 | - **재사용성이 필요할 때**: 동일한 로직을 여러 곳에서 반복적으로 사용할 가능성이 있으면, 변수나 함수를 선언해 재사용할 수 있어요. 이를 통해 코드 중복을 줄이고 유지보수가 더 쉬워져요.
63 |
64 | - **단위 테스트가 필요할 때**: 함수를 분리하면 독립적으로 단위 테스트를 작성할 수 있어요. 단위 테스트는 함수가 올바르게 동작하는지 쉽게 확인할 수 있어, 복잡한 로직을 테스트할 때 특히 유용해요.
65 |
66 | ### 조건에 이름을 붙이지 않아도 괜찮을 때
67 |
68 | - **로직이 간단할 때**: 로직이 매우 간단하면, 굳이 이름을 붙이지 않아도 돼요. 예를 들어, 배열의 요소를 단순히 두 배로 만드는 `arr.map(x => x * 2)`와 같은 코드는 이름을 붙이지 않아도 직관적이에요.
69 |
70 | - **한 번만 사용될 때**: 특정 로직이 코드 내에서 한 번만 사용되며, 그 로직이 복잡하지 않으면 익명 함수에서 직접 로직을 처리하는 것이 더 직관적일 수 있어요.
71 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/error-boundary.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/code-quality/code/examples/error-boundary.md
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/hidden-logic.md:
--------------------------------------------------------------------------------
1 | # 숨은 로직 드러내기
2 |
3 |
4 |
5 |
6 |
7 | 함수나 컴포넌트의 이름, 파라미터, 반환 값에 드러나지 않는 숨은 로직이 있다면, 함께 협업하는 동료들이 동작을 예측하는 데에 어려움을 겪을 수 있어요.
8 |
9 | ## 📝 코드 예시
10 |
11 | 다음 코드는 사용자의 계좌 잔액을 조회할 때 사용할 수 있는 `fetchBalance` 함수예요. 함수를 호출할 때마다 암시적으로 `balance_fetched`라는 로깅이 이루어지고 있어요.
12 |
13 | ```typescript 4
14 | async function fetchBalance(): Promise {
15 | const balance = await http.get("...");
16 |
17 | logging.log("balance_fetched");
18 |
19 | return balance;
20 | }
21 | ```
22 |
23 | ## 👃 코드 냄새 맡아보기
24 |
25 | ### 예측 가능성
26 |
27 | `fetchBalance` 함수의 이름과 반환 타입만을 가지고는 `balance_fetched` 라는 로깅이 이루어지는지 알 수 없어요. 그래서 로깅을 원하지 않는 곳에서도 로깅이 이루어질 수 있어요.
28 |
29 | 또, 로깅 로직에 오류가 발생했을 때 갑자기 계좌 잔액을 가져오는 로직이 망가질 수도 있죠.
30 |
31 | ## ✏️ 개선해보기
32 |
33 | 함수의 이름과 파라미터, 반환 타입으로 예측할 수 있는 로직만 구현 부분에 남기세요.
34 |
35 | ```typescript
36 | async function fetchBalance(): Promise {
37 | const balance = await http.get("...");
38 |
39 | return balance;
40 | }
41 | ```
42 |
43 | 로깅을 하는 코드는 별도로 분리하세요.
44 |
45 | ```tsx
46 |
56 | ```
57 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/http.md:
--------------------------------------------------------------------------------
1 | # 이름 겹치지 않게 관리하기
2 |
3 |
4 |
5 |
6 |
7 | 같은 이름을 가지는 함수나 변수는 동일한 동작을 해야 해요. 작은 동작 차이가 코드의 예측 가능성을 낮추고, 코드를 읽는 사람에게 혼란을 줄 수 있어요.
8 |
9 | ## 📝 코드 예시
10 |
11 | 어떤 프론트엔드 서비스에서 원래 사용하던 HTTP 라이브러리를 감싸서 새로운 형태로 HTTP 요청을 보내는 모듈을 만들었어요.
12 | 공교롭게 원래 HTTP 라이브러리와 새로 만든 HTTP 모듈의 이름은 `http`로 같아요.
13 |
14 | ::: code-group
15 |
16 | ```typescript [http.ts]
17 | // 이 서비스는 `http`라는 라이브러리를 쓰고 있어요
18 | import { http as httpLibrary } from "@some-library/http";
19 |
20 | export const http = {
21 | async get(url: string) {
22 | const token = await fetchToken();
23 |
24 | return httpLibrary.get(url, {
25 | headers: { Authorization: `Bearer ${token}` }
26 | });
27 | }
28 | };
29 | ```
30 |
31 | ```typescript [fetchUser.ts]
32 | // http.ts에서 정의한 http를 가져오는 코드
33 | import { http } from "./http";
34 |
35 | export async function fetchUser() {
36 | return http.get("...");
37 | }
38 | ```
39 |
40 | :::
41 |
42 | ## 👃 코드 냄새 맡아보기
43 |
44 | ### 예측 가능성
45 |
46 | 이 코드는 기능적으로 문제가 없지만, 읽는 사람에게 혼란을 줄 수 있어요. `http.get`을 호출하는 개발자는 이 함수가 원래의 HTTP 라이브러리가 하는 것처럼 단순한 GET 요청을 보내는 것으로 예상하지만, 실제로는 토큰을 가져오는 추가 작업이 수행돼요.
47 |
48 | 오해로 인해서 기대 동작과 실제 동작의 차이가 생기고, 버그가 발생하거나, 디버깅 과정을 복잡하고 혼란스럽게 만들 수 있어요.
49 |
50 | ## ✏️ 개선해보기
51 |
52 | 서비스에서 만든 함수에는 라이브러리의 함수명과 구분되는 명확한 이름을 사용해서 함수의 동작을 예측 가능하게 만들 수 있어요.
53 |
54 | ::: code-group
55 |
56 | ```typescript [httpService.ts]
57 | // 이 서비스는 `http`라는 라이브러리를 쓰고 있어요
58 | import { http as httpLibrary } from "@some-library/http";
59 |
60 | // 라이브러리 함수명과 구분되도록 명칭을 변경했어요.
61 | export const httpService = {
62 | async getWithAuth(url: string) {
63 | const token = await fetchToken();
64 |
65 | // 토큰을 헤더에 추가하는 등 인증 로직을 추가해요.
66 | return httpLibrary.get(url, {
67 | headers: { Authorization: `Bearer ${token}` }
68 | });
69 | }
70 | };
71 | ```
72 |
73 | ```typescript [fetchUser.ts]
74 | // httpService.ts에서 정의한 httpService를 가져오는 코드
75 | import { httpService } from "./httpService";
76 |
77 | export async function fetchUser() {
78 | // 함수명을 통해 이 함수가 인증된 요청을 보내는 것을 알 수 있어요.
79 | return await httpService.getWithAuth("...");
80 | }
81 | ```
82 |
83 | :::
84 |
85 | 이렇게 해서 함수의 이름을 봤을 때 동작을 오해할 수 있는 가능성을 줄일 수 있어요.
86 | 다른 개발자가 이 함수를 사용할 때, 서비스에서 정의한 함수라는 것을 인지하고 올바르게 사용할 수 있어요.
87 |
88 | 또한, `getWithAuth`라는 이름으로 이 함수가 인증된 요청을 보낸다는 것을 명확하게 전달할 수 있어요.
89 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/magic-number-cohesion.md:
--------------------------------------------------------------------------------
1 | # 매직 넘버 없애기
2 |
3 |
4 |
5 |
6 |
7 | **매직 넘버**(Magic Number)란 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것을 말해요.
8 |
9 | 예를 들어, 찾을 수 없음(Not Found)을 나타내는 HTTP 상태 코드로 `404` 값을 바로 사용하는 것이나,
10 | 하루를 나타내는 `86400`초를 그대로 사용하는 것이 있어요.
11 |
12 | ## 📝 코드 예시
13 |
14 | 다음 코드는 좋아요 버튼을 눌렀을 때 좋아요 개수를 새로 내려받는 함수예요.
15 |
16 | ```typescript 3
17 | async function onLikeClick() {
18 | await postLike(url);
19 | await delay(300);
20 | await refetchPostLike();
21 | }
22 | ```
23 |
24 | ## 👃 코드 냄새 맡아보기
25 |
26 | ### 응집도
27 |
28 | `300`이라고 하는 숫자를 애니메이션 완료를 기다리려고 사용했다면, 재생하는 애니메이션을 바꿨을 때 조용히 서비스가 깨질 수 있는 위험성이 있어요.
29 | 충분한 시간동안 애니메이션을 기다리지 않고 바로 다음 로직이 시작될 수도 있죠.
30 |
31 | 같이 수정되어야 할 코드 중 한쪽만 수정된다는 점에서, 응집도가 낮은 코드라고도 할 수 있어요.
32 |
33 | ::: info
34 |
35 | 이 Hook은 [가독성](./magic-number-readability.md) 관점으로도 볼 수 있어요.
36 |
37 | :::
38 |
39 | ## ✏️ 개선해보기
40 |
41 | 숫자 `300`의 맥락을 정확하게 표시하기 위해서 상수 `ANIMATION_DELAY_MS`로 선언할 수 있어요.
42 |
43 | ```typescript 1,5
44 | const ANIMATION_DELAY_MS = 300;
45 |
46 | async function onLikeClick() {
47 | await postLike(url);
48 | await delay(ANIMATION_DELAY_MS);
49 | await refetchPostLike();
50 | }
51 | ```
52 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/magic-number-readability.md:
--------------------------------------------------------------------------------
1 | # 매직 넘버에 이름 붙이기
2 |
3 |
4 |
5 |
6 |
7 | **매직 넘버**(Magic Number)란 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것을 말해요.
8 |
9 | 예를 들어, 찾을 수 없음(Not Found)을 나타내는 HTTP 상태 코드로 `404` 값을 바로 사용하는 것이나,
10 | 하루를 나타내는 `86400`초를 그대로 사용하는 것이 있어요.
11 |
12 | ## 📝 코드 예시
13 |
14 | 다음 코드는 좋아요 버튼을 눌렀을 때 좋아요 개수를 새로 내려받는 함수예요.
15 |
16 | ```typescript 3
17 | async function onLikeClick() {
18 | await postLike(url);
19 | await delay(300);
20 | await refetchPostLike();
21 | }
22 | ```
23 |
24 | ## 👃 코드 냄새 맡아보기
25 |
26 | ### 가독성
27 |
28 | 이 코드는 `delay` 함수에 전달된 `300`이라고 하는 값이 어떤 맥락으로 쓰였는지 알 수 없어요.
29 | 원래 코드를 작성한 개발자가 아니라면, 어떤 목적으로 300ms동안 기다리는지 알 수 없죠.
30 |
31 | - 애니메이션이 완료될 때까지 기다리는 걸까?
32 | - 좋아요 반영에 시간이 걸려서 기다리는 걸까?
33 | - 테스트 코드였는데, 깜빡하고 안 지운 걸까?
34 |
35 | 하나의 코드를 여러 명의 개발자가 함께 수정하다 보면 의도를 정확히 알 수 없어서 코드가 원하지 않는 방향으로 수정될 수도 있어요.
36 |
37 | ::: info
38 |
39 | 이 Hook은 [응집도](./magic-number-cohesion.md) 관점으로도 볼 수 있어요.
40 |
41 | :::
42 |
43 | ## ✏️ 개선해보기
44 |
45 | 숫자 `300`의 맥락을 정확하게 표시하기 위해서 상수 `ANIMATION_DELAY_MS`로 선언할 수 있어요.
46 |
47 | ```typescript 1,5
48 | const ANIMATION_DELAY_MS = 300;
49 |
50 | async function onLikeClick() {
51 | await postLike(url);
52 | await delay(ANIMATION_DELAY_MS);
53 | await refetchPostLike();
54 | }
55 | ```
56 |
57 | ## 🔍 더 알아보기
58 |
59 | 매직 넘버는 응집도 관점에서도 살펴볼 수 있어요. [매직 넘버 없애서 응집도 높이기](./magic-number-cohesion.md) 문서도 참고해 보세요.
60 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/submit-button.md:
--------------------------------------------------------------------------------
1 | # 같이 실행되지 않는 코드 분리하기
2 |
3 |
4 |
5 |
6 |
7 | 동시에 실행되지 않는 코드가 하나의 함수 또는 컴포넌트에 있으면, 동작을 한눈에 파악하기 어려워요.
8 | 구현 부분에 많은 숫자의 분기가 들어가서, 어떤 역할을 하는지 이해하기 어렵기도 해요.
9 |
10 | ## 📝 코드 예시
11 |
12 | 다음 `` 컴포넌트는 사용자의 권한에 따라서 다르게 동작해요.
13 |
14 | - 사용자의 권한이 보기 전용(`"viewer"`)이면, 초대 버튼은 비활성화되어 있고, 애니메이션도 재생하지 않아요.
15 | - 사용자가 일반 사용자이면, 초대 버튼을 사용할 수 있고, 애니메이션도 재생해요.
16 |
17 | ```tsx
18 | function SubmitButton() {
19 | const isViewer = useRole() === "viewer";
20 |
21 | useEffect(() => {
22 | if (isViewer) {
23 | return;
24 | }
25 | showButtonAnimation();
26 | }, [isViewer]);
27 |
28 | return isViewer ? (
29 | Submit
30 | ) : (
31 |
32 | );
33 | }
34 | ```
35 |
36 | ## 👃 코드 냄새 맡아보기
37 |
38 | ### 가독성
39 |
40 | `` 컴포넌트에서는 사용자가 가질 수 있는 2가지의 권한 상태를 하나의 컴포넌트 안에서 한 번에 처리하고 있어요.
41 | 그래서 코드를 읽는 사람이 한 번에 고려해야 하는 맥락이 많아요.
42 |
43 | 예를 들어, 다음 코드에서 파란색은 사용자가 보기 전용 권한(`'viewer'`)을 가지고 있을 때, 빨간색은 일반 사용자일 때 실행되는 코드예요.
44 | 동시에 실행되지 않는 코드가 교차되어서 나타나서 코드를 이해할 때 부담을 줘요.
45 |
46 | {.light-only}
47 | {.dark-only}
48 |
49 | ## ✏️ 개선해보기
50 |
51 | 다음 코드는 사용자가 보기 전용 권한을 가질 때와 일반 사용자일 때를 완전히 나누어서 관리하도록 하는 코드예요.
52 |
53 | ```tsx
54 | function SubmitButton() {
55 | const isViewer = useRole() === "viewer";
56 |
57 | return isViewer ? : ;
58 | }
59 |
60 | function ViewerSubmitButton() {
61 | return Submit;
62 | }
63 |
64 | function AdminSubmitButton() {
65 | useEffect(() => {
66 | showAnimation();
67 | }, []);
68 |
69 | return ;
70 | }
71 | ```
72 |
73 | - `` 코드 곳곳에 있던 분기가 단 하나로 합쳐지면서, 분기가 줄어들었어요.
74 | - ``과 `` 에서는 하나의 분기만 관리하기 때문에, 코드를 읽는 사람이 한 번에 고려해야 할 맥락이 적어요.
75 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/submit1.md:
--------------------------------------------------------------------------------
1 | # 같이 실행되지 않는 코드 분리하기
2 |
3 |
4 |
5 |
6 |
7 | 동시에 실행되지 않는 코드가 하나의 함수 또는 컴포넌트에 있으면, 동작을 한눈에 파악하기 어려워요.
8 | 구현 부분에 많은 숫자의 분기가 들어가서, 어떤 역할을 하는지 이해하기 어렵기도 해요.
9 |
10 | ## 📝 코드 예시
11 |
12 | 다음 `` 컴포넌트는 사용자의 권한에 따라서 다르게 동작해요.
13 |
14 | - 사용자의 권한이 보기 전용(`"viewer"`)이면, 초대 버튼은 비활성화되어 있고, 애니메이션도 재생하지 않아요.
15 | - 사용자가 일반 사용자이면, 초대 버튼을 사용할 수 있고, 애니메이션도 재생해요.
16 |
17 | ```tsx
18 | function SubmitButton() {
19 | const isViewer = useRole() === "viewer";
20 |
21 | useEffect(() => {
22 | if (isViewer) {
23 | return;
24 | }
25 | showButtonAnimation();
26 | }, [isViewer]);
27 |
28 | return isViewer ? (
29 | Submit
30 | ) : (
31 |
32 | );
33 | }
34 | ```
35 |
36 | ## 👃 코드 냄새 맡아보기
37 |
38 | ### 가독성
39 |
40 | `` 컴포넌트에서는 사용자가 가질 수 있는 2가지의 권한 상태를 하나의 컴포넌트 안에서 한 번에 처리하고 있어요.
41 | 그래서 코드를 읽는 사람이 한 번에 고려해야 하는 맥락이 많아요.
42 |
43 | 예를 들어, 다음 코드에서 파란색은 사용자가 보기 전용 권한(`'viewer'`)을 가지고 있을 때, 빨간색은 일반 사용자일 때 실행되는 코드예요.
44 | 동시에 실행되지 않는 코드가 교차되어서 나타나서 코드를 이해할 때 부담을 줘요.
45 |
46 | {.light-only}
47 | {.dark-only}
48 |
49 | ## ✏️ 개선해보기
50 |
51 | 다음 코드는 사용자가 보기 전용 권한을 가질 때와 일반 사용자일 때를 완전히 나누어서 관리하도록 하는 코드예요.
52 |
53 | ```tsx
54 | function SubmitButton() {
55 | const isViewer = useRole() === "viewer";
56 |
57 | return isViewer ? : ;
58 | }
59 |
60 | function ViewerSubmitButton() {
61 | return Submit;
62 | }
63 |
64 | function AdminSubmitButton() {
65 | useEffect(() => {
66 | showAnimation();
67 | }, []);
68 |
69 | return ;
70 | }
71 | ```
72 |
73 | - `` 코드 곳곳에 있던 분기가 단 하나로 합쳐지면서, 분기가 줄어들었어요.
74 | - ``과 `` 에서는 하나의 분기만 관리하기 때문에, 코드를 읽는 사람이 한 번에 고려해야 할 맥락이 적어요.
75 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/ternary-operator.md:
--------------------------------------------------------------------------------
1 | # 삼항 연산자 단순하게 하기
2 |
3 |
4 |
5 |
6 |
7 | 삼항 연산자를 복잡하게 사용하면 조건의 구조가 명확하게 보이지 않아서 코드를 읽기 어려울 수 있어요.
8 |
9 | ## 📝 코드 예시
10 |
11 | 다음 코드는 `A조건`과 `B조건`에 따라서 `"BOTH"`, `"A"`, `"B"` 또는 `"NONE"` 중 하나를 `status`에 지정하는 코드예요.
12 |
13 | ```typescript
14 | const status =
15 | (A조건 && B조건) ? "BOTH" : (A조건 || B조건) ? (A조건 ? "A" : "B") : "NONE";
16 | ```
17 |
18 | ## 👃 코드 냄새 맡아보기
19 |
20 | ### 가독성
21 |
22 | 이 코드는 여러 삼항 연산자가 중첩되어 사용되어서, 정확하게 어떤 조건으로 값이 계산되는지 한눈에 파악하기 어려워요.
23 |
24 | ## ✏️ 개선해보기
25 |
26 | 다음과 같이 조건을 `if` 문으로 풀어서 사용하면 보다 명확하고 간단하게 조건을 드러낼 수 있어요.
27 |
28 | ```typescript
29 | const status = (() => {
30 | if (A조건 && B조건) return "BOTH";
31 | if (A조건) return "A";
32 | if (B조건) return "B";
33 | return "NONE";
34 | })();
35 | ```
36 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/examples/use-bottom-sheet.md:
--------------------------------------------------------------------------------
1 | # 중복 코드 허용하기
2 |
3 |
4 |
5 |
6 |
7 | 개발자로서 여러 페이지나 컴포넌트에 걸친 중복 코드를 하나의 Hook이나 컴포넌트로 공통화하는 경우가 많아요.
8 | 중복 코드를 하나의 컴포넌트나 Hook으로 공통화하면, 좋은 코드의 특징 중 하나인 응집도를 챙겨서, 함께 수정되어야 할 코드들을 한꺼번에 수정할 수 있어요.
9 |
10 | 그렇지만, 불필요한 결합도가 생겨서, 공통 컴포넌트나 Hook을 수정함에 따라 영향을 받는 코드의 범위가 넓어져서, 오히려 수정이 어려워질 수도 있어요.
11 |
12 | 처음에는 비슷하게 동작한다고 생각해서 공통화한 코드가, 이후 페이지마다 다른 특이한 요구사항이 생겨서, 점점 복잡해질 수 있어요.
13 | 동시에 공통 코드를 수정할 때마다, 그 코드에 의존하는 코드들을 일일이 제대로 테스트해야 해서, 오히려 코드 수정이 어려워지기도 하죠.
14 |
15 | ## 📝 코드 예시
16 |
17 | 아래와 같이 점검 정보를 인자로 받아서, 점검 중이라면 점검 바텀시트를 열고, 사용자가 알림 받기에 동의하면 이를 로깅하고, 현재 화면을 닫는 Hook을 살펴볼게요.
18 |
19 | ```typescript
20 | export const useOpenMaintenanceBottomSheet = () => {
21 | const maintenanceBottomSheet = useMaintenanceBottomSheet();
22 | const logger = useLogger();
23 |
24 | return async (maintainingInfo: TelecomMaintenanceInfo) => {
25 | logger.log("점검 바텀시트 열림");
26 | const result = await maintenanceBottomSheet.open(maintainingInfo);
27 | if (result) {
28 | logger.log("점검 바텀시트 알림받기 클릭");
29 | }
30 | closeView();
31 | };
32 | };
33 | ```
34 |
35 | 이 코드는 여러 페이지에서 반복적으로 사용되었기에 공통 Hook으로 분리되었어요.
36 |
37 | ## 👃 코드 냄새 맡아보기
38 |
39 | ### 결합도
40 |
41 | 이 Hook은 여러 페이지에서 반복적으로 보이는 로직이기에 공통화되었어요. 그렇지만 앞으로 생길 수 있는 다양한 코드 변경의 가능성을 생각해볼 수 있어요.
42 |
43 | - 만약에 페이지마다 로깅하는 값이 달라진다면?
44 | - 만약에 어떤 페이지에서는 점검 바텀시트를 닫더라도 화면을 닫을 필요가 없다면?
45 | - 바텀시트에서 보여지는 텍스트나 이미지를 다르게 해야 한다면?
46 |
47 | 위 Hook은 이런 코드 변경사항에 유연하게 대응하기 위해서 복잡하게 인자를 받아야 할 거예요.
48 | 이 Hook의 구현을 수정할 때마다, 이 Hook을 쓰는 모든 페이지들이 잘 작동하는지 테스트도 해야 할 것이고요.
49 |
50 | ## ✏️ 개선해보기
51 |
52 | 다소 반복되어 보이는 코드일지 몰라도, 중복 코드를 허용하는 것이 좋은 방향일 수 있어요.
53 |
54 | 함께 일하는 동료들과 적극적으로 소통하며 점검 바텀시트의 동작을 정확하게 이해해야 해요.
55 | 페이지에서 로깅하는 값이 같고, 점검 바텀시트의 동작이 동일하고, 바텀시트의 모양이 동일하다면, 그리고 앞으로도 그럴 예정이라면, 공통화를 통해 코드의 응집도를 높일 수 있어요.
56 |
57 | 그렇지만 페이지마다 동작이 달라질 여지가 있다면, 공통화 없이 중복 코드를 허용하는 것이 더 좋은 선택이에요.
58 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | comments: false
3 | ---
4 |
5 | # 좋은 코드를 위한 4가지 기준
6 |
7 | 좋은 프론트엔드 코드는 **변경하기 쉬운** 코드예요.
8 | 새로운 요구사항을 구현하고자 할 때, 기존 코드를 수정하고 배포하기 수월한 코드가 좋은 코드죠.
9 | 코드가 변경하기 쉬운지는 4가지 기준으로 판단할 수 있어요.
10 |
11 | ## 1. 가독성
12 |
13 | **가독성**(Readability)은 코드가 읽기 쉬운 정도를 말해요.
14 | 코드가 변경하기 쉬우려면 먼저 코드가 어떤 동작을 하는지 이해할 수 있어야 해요.
15 |
16 | 읽기 좋은 코드는 읽는 사람이 한 번에 머릿속에서 고려하는 맥락이 적고, 위에서 아래로 자연스럽게 이어져요.
17 |
18 | ### 가독성을 높이는 전략
19 |
20 | - **맥락 줄이기**
21 | - [같이 실행되지 않는 코드 분리하기](./examples/submit-button.md)
22 | - [구현 상세 추상화하기](./examples/login-start-page.md)
23 | - [로직 종류에 따라 합쳐진 함수 쪼개기](./examples/use-page-state-readability.md)
24 | - **이름 붙이기**
25 | - [복잡한 조건에 이름 붙이기](./examples/condition-name.md)
26 | - [매직 넘버에 이름 붙이기](./examples/magic-number-readability.md)
27 | - **위에서 아래로 읽히게 하기**
28 | - [시점 이동 줄이기](./examples/user-policy.md)
29 | - [삼항 연산자 단순하게 하기](./examples/ternary-operator.md)
30 |
31 | ## 2. 예측 가능성
32 |
33 | **예측 가능성**(Predictability)이란, 함께 협업하는 동료들이 함수나 컴포넌트의 동작을 얼마나 예측할 수 있는지를 말해요.
34 | 예측 가능성이 높은 코드는 일관적인 규칙을 따르고, 함수나 컴포넌트의 이름과 파라미터, 반환 값만 보고도 어떤 동작을 하는지 알 수 있어요.
35 |
36 | ### 예측 가능성을 높이는 전략
37 |
38 | - [이름 겹치지 않게 관리하기](./examples/http.md)
39 | - [같은 종류의 함수는 반환 타입 통일하기](./examples/use-user.md)
40 | - [숨은 로직 드러내기](./examples/hidden-logic.md)
41 |
42 | ## 3. 응집도
43 |
44 | **응집도**(Cohesion)란, 수정되어야 할 코드가 항상 같이 수정되는지를 말해요.
45 | 응집도가 높은 코드는 코드의 한 부분을 수정해도 의도치 않게 다른 부분에서 오류가 발생하지 않아요.
46 | 함께 수정되어야 할 부분이 반드시 함께 수정되도록 구조적으로 뒷받침되기 때문이죠.
47 |
48 | ::: info 가독성과 응집도는 서로 상충할 수 있어요
49 |
50 | 일반적으로 응집도를 높이기 위해서는 변수나 함수를 추상화하는 등 가독성을 떨어뜨리는 결정을 해야 해요.
51 | 함께 수정되지 않으면 오류가 발생할 수 있는 경우에는, 응집도를 우선해서 코드를 공통화, 추상화하세요.
52 | 위험성이 높지 않은 경우에는, 가독성을 우선하여 코드 중복을 허용하세요.
53 |
54 | :::
55 |
56 | ### 응집도를 높이는 전략
57 |
58 | - [함께 수정되는 파일을 같은 디렉토리에 두기](./examples/code-directory.md)
59 | - [매직 넘버 없애기](./examples/magic-number-cohesion.md)
60 | - [폼의 응집도 생각하기](./examples/form-fields.md)
61 |
62 | ## 4. 결합도
63 |
64 | **결합도**(Coupling)란, 코드를 수정했을 때의 영향범위를 말해요.
65 | 코드를 수정했을 때 영향범위가 적어서, 변경에 따른 범위를 예측할 수 있는 코드가 수정하기 쉬운 코드예요.
66 |
67 | ### 결합도를 낮추는 전략
68 |
69 | - [책임을 하나씩 관리하기](./examples/use-page-state-coupling.md)
70 | - [중복 코드 허용하기](./examples/use-bottom-sheet.md)
71 | - [Props Drilling 지우기](./examples/item-edit-modal.md)
72 |
73 | ## 코드 품질 여러 각도로 보기
74 |
75 | 아쉽게도 이 4가지 기준을 모두 한꺼번에 충족하기는 어려워요.
76 |
77 | 예를 들어서, 함수나 변수가 항상 같이 수정되기 위해서 공통화 및 추상화하면, 응집도가 높아지죠. 그렇지만 코드가 한 차례 추상화되기 때문에 가독성이 떨어져요.
78 |
79 | 중복 코드를 허용하면, 코드의 영향범위를 줄일 수 있어서, 결합도를 낮출 수 있어요. 그렇지만 한쪽을 수정했을 때 다른 한쪽을 실수로 수정하지 못할 수 있어서, 응집도가 떨어지죠.
80 |
81 | 프론트엔드 개발자는 현재 직면한 상황을 바탕으로, 깊이 있게 고민하면서, 장기적으로 코드가 수정하기 쉽게 하기 위해서 어떤 가치를 우선해야 하는지 고민해야 해요.
82 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/code/start.md:
--------------------------------------------------------------------------------
1 | ---
2 | comments: false
3 | ---
4 |
5 | # 시작하기
6 |
7 | `Frontend Fundamentals`(FF)는 좋은 프론트엔드 코드의 기준을 제공해요.
8 | 프론트엔드 개발자로서 코드 품질을 높이고자 할 때 방향을 찾는 나침반처럼 활용해 보세요.
9 |
10 | 좋은 코드에 대한 [4개 원칙](./index.md)과 함께, 구체적인 예시 및 해결 방안을 제시해요.
11 |
12 | ## 이런 분들에게 추천해요
13 |
14 | - 🦨 코드에 대해서 고민되는데 **논리적으로 설명하기 어려운 개발자**
15 | - 👀 **나쁜 코드를 빠르게 감지**하고 개선하는 방법을 공부하고 싶은 개발자
16 | - 🤓 코드 리뷰 등에서 누가 전달해준 링크를 타고 들어와 "내 코드가 이랬구나"를 **객관적 시각으로 인지**하게 될 개발자
17 | - 👥 **팀과 함께** 공통의 코딩 스타일과 코드 품질의 기준을 세워보고 싶은 개발자
18 |
19 | ## 저작자
20 |
21 | - [milooy](https://github.com/milooy)
22 | - [donghyeon](https://github.com/kimbangg)
23 | - [chkim116](https://github.com/chkim116)
24 | - [inseong.you](https://github.com/inseong.you)
25 | - [raon0211](https://github.com/raon0211)
26 | - [bigsaigon333](https://github.com/bigsaigon333)
27 | - [jho2301](https://github.com/jho2301)
28 | - [KimChunsick](https://github.com/KimChunsick)
29 | - [jennybehan](https://github.com/jennybehan)
30 |
31 | ## 문서 기여자
32 |
33 | - [andy0414](https://github.com/andy0414)
34 | - [pumpkiinbell](https://github.com/pumpkiinbell)
35 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/en/code/coming-soon.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | title: Bundling
4 | description: Frontend Bundling Guide (Coming Soon)
5 | ---
6 |
7 |
8 |
✨ Coming Soon
9 |
Stay tuned! We're working on something awesome for you.
10 |
11 |
12 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/en/code/community.md:
--------------------------------------------------------------------------------
1 | ---
2 | comments: false
3 | ---
4 |
5 | # Contributing Together
6 |
7 | `Frontend Fundamentals` (FF) is developing standards for good code together with the community.
8 |
9 | It's currently maintained by the Toss Frontend chapter.
10 |
11 | ## Featured Discussions
12 |
13 | Explore some of the best discussions in the community. Expand your thinking about good code beyond what's in the Frontend Fundamentals documentation.
14 |
15 | - [Featured Discussions](https://github.com/toss/frontend-fundamentals/discussions?discussions_q=is%3Aopen+label%3A%22%EC%84%B1%EC%A7%80+%E2%9B%B2%22)
16 |
17 | ## Discussing Code Concerns
18 |
19 | If you have code that you're concerned about, post it on the GitHub discussions.
20 | You can receive reviews from the community from various perspectives on your code and discuss the standards for good code with the community.
21 |
22 | Cases that receive a lot of support can be directly added to the Frontend Fundamentals documentation. The contribution method will be announced later.
23 |
24 | - [Post on GitHub Discussions](https://github.com/toss/frontend-fundamentals/discussions)
25 |
26 | ## Adding Opinions on Good Code Standards
27 |
28 | If you have opinions on the standards for good code or want to add new opinions, vote on what makes better code and leave your thoughts.
29 | Communicate with the community to create richer and deeper standards.
30 |
31 | This can be an opportunity to establish your own criteria on whether this code is good or that code is good.
32 |
33 | - [View Code on A vs B](https://github.com/toss/frontend-fundamentals/discussions/categories/a-vs-b)
34 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/en/code/examples/hidden-logic.md:
--------------------------------------------------------------------------------
1 | # Revealing Hidden Logic
2 |
3 |
4 |
5 |
6 | If there is hidden logic that is not revealed in the name, parameters, or return value of a function or component, it can be difficult for collaborating colleagues to predict its behavior.
7 |
8 | ## 📝 Code Example
9 |
10 | The following code is a `fetchBalance` function that can be used to check a user's account balance. Each time the function is called, it implicitly logs `balance_fetched`.
11 |
12 | ```typescript 4
13 | async function fetchBalance(): Promise {
14 | const balance = await http.get("...");
15 |
16 | logging.log("balance_fetched");
17 |
18 | return balance;
19 | }
20 | ```
21 |
22 | ## 👃 Smell the Code
23 |
24 | ### Predictability
25 |
26 | From the name and return type of the `fetchBalance` function, it is not clear that logging of `balance_fetched` is taking place. Therefore, logging might occur even in places where it is not desired.
27 |
28 | Additionally, if an error occurs in the logging logic, the logic for fetching the account balance might suddenly break.
29 |
30 | ## ✏️ Work on Improving
31 |
32 | Leave only the logic that can be predicted by the function's name, parameters, and return type in the implementation.
33 |
34 | ```typescript
35 | async function fetchBalance(): Promise {
36 | const balance = await http.get("...");
37 |
38 | return balance;
39 | }
40 | ```
41 |
42 | Separate the logging code.
43 |
44 | ```tsx
45 |
55 | ```
56 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/en/code/examples/magic-number-cohesion.md:
--------------------------------------------------------------------------------
1 | # Eliminating Magic Numbers
2 |
3 |
4 |
5 |
6 |
7 | **Magic Number** refers to directly inserting numerical values into the source code without explicitly stating their meaning.
8 |
9 | For example, using the value `404` directly as the HTTP status code for Not Found, or using `86400` seconds directly to represent a day.
10 |
11 | ## 📝 Code Example
12 |
13 | The following code is a function that retrieves the new like count when the like button is clicked.
14 |
15 | ```typescript 3
16 | async function onLikeClick() {
17 | await postLike(url);
18 | await delay(300);
19 | await refetchPostLike();
20 | }
21 | ```
22 |
23 | ## 👃 Smell the Code
24 |
25 | ### Cohesion
26 |
27 | If you used the number `300` to wait for the animation to complete, there is a risk that the service may quietly break when the animation being played is changed. Additionally, the next logic may start immediately without waiting for a sufficient amount of time for the animation.
28 |
29 | From the perspective that only one side of the code that needs to be modified together is modified, it can also be said to be code with low cohesion.
30 |
31 | ::: info
32 |
33 | This hook can also be viewed from the perspective of [readability](./magic-number-readability.md).
34 |
35 | :::
36 |
37 | ## ✏️ Work on Improving
38 |
39 | To accurately represent the context of the number `300`, you can declare it as a constant `ANIMATION_DELAY_MS`.
40 |
41 | ```typescript 1,5
42 | const ANIMATION_DELAY_MS = 300;
43 |
44 | async function onLikeClick() {
45 | await postLike(url);
46 | await delay(ANIMATION_DELAY_MS);
47 | await refetchPostLike();
48 | }
49 | ```
50 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/en/code/examples/magic-number-readability.md:
--------------------------------------------------------------------------------
1 | # Naming Magic Numbers
2 |
3 |
4 |
5 |
6 |
7 | **Magic Number** refers to directly inserting numerical values into the source code without explicitly stating their meaning.
8 |
9 | For example, using the value `404` directly as the HTTP status code for Not Found, or using `86400` seconds directly to represent a day.
10 |
11 | ## 📝 Code Example
12 |
13 | The following code is a function that retrieves the new like count when the like button is clicked.
14 |
15 | ```typescript 3
16 | async function onLikeClick() {
17 | await postLike(url);
18 | await delay(300);
19 | await refetchPostLike();
20 | }
21 | ```
22 |
23 | ## 👃 Smell the Code
24 |
25 | ### Readability
26 |
27 | The value `300` passed to the `delay` function is used in an unclear context.
28 | If you are not the original developer of the code, you may not know why it waits for 300ms.
29 |
30 | - Is it waiting for the animation to complete?
31 | - Is it waiting for the like to be reflected?
32 | - Was it a test code that was forgotten to be removed?
33 |
34 | When multiple developers work on the same code, the intention may not be accurately understood, leading to unintended modifications.
35 |
36 | ::: info
37 |
38 | This Hook can also be viewed from the perspective of [cohesion](./magic-number-cohesion.md).
39 |
40 | :::
41 |
42 | ## ✏️ Work on Improving
43 |
44 | To accurately represent the context of the number `300`, you can declare it as a constant `ANIMATION_DELAY_MS`.
45 |
46 | ```typescript 1,5
47 | const ANIMATION_DELAY_MS = 300;
48 |
49 | async function onLikeClick() {
50 | await postLike(url);
51 | await delay(ANIMATION_DELAY_MS);
52 | await refetchPostLike();
53 | }
54 | ```
55 |
56 | ## 🔍 Learn More
57 |
58 | Magic numbers can also be viewed from the perspective of cohesion. Please also refer to the document [Eliminating Magic Numbers to Increase Cohesion](./magic-number-cohesion.md).
59 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/en/code/examples/ternary-operator.md:
--------------------------------------------------------------------------------
1 | # Simplifying Ternary Operators
2 |
3 |
4 |
5 |
6 |
7 | Using complex ternary operators can obscure the structure of the conditions, making the code harder to read.
8 |
9 | ## 📝 Code Example
10 |
11 | The following code assigns `"BOTH"`, `"A"`, `"B"`, or `"NONE"` to `status` based on `ACondition` and `BCondition`.
12 |
13 | ```typescript
14 | const status =
15 | (ACondition && BCondition)
16 | ? "BOTH"
17 | : (ACondition || BCondition)
18 | ? (ACondition
19 | ? "A"
20 | : "B")
21 | : "NONE";
22 | ```
23 |
24 | ## 👃 Smell the Code
25 |
26 | ### Readability
27 |
28 | This code uses multiple nested ternary operators, making it difficult to quickly understand the exact conditions under which values are calculated.
29 |
30 | ## ✏️ Work on Improving
31 |
32 | You can rewrite the conditions using `if` statements, as shown below, to make the logic clearer and easier to follow.
33 |
34 | ```typescript
35 | const status = (() => {
36 | if (ACondition && BCondition) return "BOTH";
37 | if (ACondition) return "A";
38 | if (BCondition) return "B";
39 | return "NONE";
40 | })();
41 | ```
42 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/en/code/start.md:
--------------------------------------------------------------------------------
1 | ---
2 | comments: false
3 | ---
4 |
5 | # Getting Started
6 |
7 | `Frontend Fundamentals` (FF) provides standards for good frontend code.
8 | Use it as a compass to find direction when you want to improve code quality as a frontend developer.
9 |
10 | It presents [four principles](./index.md) of good code, along with specific examples and solutions.
11 |
12 | ## Who Is This For?
13 |
14 | - 🦨 Developers who are concerned about code but find it **difficult to explain logically**
15 | - 👀 Developers who want to learn how to **quickly detect and improve bad code**
16 | - 🤓 Developers who, during code reviews, follow a link provided by someone and come to **objectively recognize** "Oh, this is how my code was"
17 | - 👥 Developers who want to establish a common coding style and code quality standards **with their team**
18 |
19 | ## Authors
20 |
21 | - [milooy](https://github.com/milooy)
22 | - [donghyeon](https://github.com/kimbangg)
23 | - [chkim116](https://github.com/chkim116)
24 | - [inseong.you](https://github.com/inseong.you)
25 | - [raon0211](https://github.com/raon0211)
26 | - [bigsaigon333](https://github.com/bigsaigon333)
27 | - [jho2301](https://github.com/jho2301)
28 | - [KimChunsick](https://github.com/KimChunsick)
29 | - [jennybehan](https://github.com/jennybehan)
30 |
31 | ## Document Contributors
32 |
33 | - [andy0414](https://github.com/andy0414)
34 | - [pumpkiinbell](https://github.com/pumpkiinbell)
35 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/en/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "Frontend Fundamentals"
7 | tagline: "Guidelines for easily modifiable frontend code"
8 | image:
9 | src: /images/ff-symbol-gradient-webp-80.webp
10 | alt: Frontend Fundamentals symbol
11 | actions:
12 | - text: Learn about good code
13 | link: /en/code/
14 | - theme: alt
15 | text: Communicate
16 | link: /en/code/community
17 |
18 | features:
19 | - icon: 🤓
20 | title: Improve your code review skills
21 | details: Explore principles to determine if the code is easily changeable.
22 | - icon: 🤝
23 | title: Conduct better code reviews
24 | details: Actively explore various code improvement cases.
25 | - icon: 📝
26 | title: Concerned about your code?
27 | details: Communicate with other developers in the GitHub discussions.
28 | ---
29 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/images/examples/submit-button-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/code-quality/images/examples/submit-button-dark.png
--------------------------------------------------------------------------------
/fundamentals/code-quality/images/examples/submit-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toss/frontend-fundamentals/f65af82abcd45edc1062b2826bc693e612a579f4/fundamentals/code-quality/images/examples/submit-button.png
--------------------------------------------------------------------------------
/fundamentals/code-quality/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "Frontend Fundamentals"
7 | tagline: "변경하기 쉬운 프론트엔드 코드를 위한 지침서"
8 | image:
9 | loading: eager
10 | fetchpriority: high
11 | decoding: async
12 | src: /images/ff-symbol-gradient-webp-80.webp
13 | alt: Frontend Fundamentals symbol
14 | actions:
15 | - text: 좋은 코드의 기준 알아보기
16 | link: /code/
17 | - theme: alt
18 | text: 소통하기
19 | link: /code/community
20 |
21 | features:
22 | - icon: 🤓
23 | title: 코드를 보는 눈을 키우고 싶다면
24 | details: 변경하기 쉬운 코드인지 판단하기 위한 원칙을 살펴보세요.
25 | - icon: 🤝
26 | title: 코드 리뷰를 잘하고 싶다면
27 | details: 다양한 코드 개선 사례를 능동적으로 탐색해 보세요.
28 | - icon: 📝
29 | title: 내 코드가 고민된다면
30 | details: 깃허브 디스커션에서 다른 개발자들과 소통해 보세요.
31 | ---
32 |
--------------------------------------------------------------------------------
/fundamentals/code-quality/ja/code/coming-soon.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | title: Bundling
4 | description: Frontend Bundling Guide (Coming Soon)
5 | ---
6 |
7 |
8 |
✨ Coming Soon
9 |
Stay tuned! We're working on something awesome for you.