์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŒ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ๊ณผ ํด๋ฆฌํ•„์„ ํ™œ์šฉํ•œ ๊ด‘๋ฒ”์œ„ํ•œ ์ง€์› ์ „๋žต ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๐ŸŒ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ๊ณผ ํด๋ฆฌํ•„์„ ํ™œ์šฉํ•œ ๊ด‘๋ฒ”์œ„ํ•œ ์ง€์› ์ „๋žต ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋Š” ์›น์„ ๋งŒ๋“œ๋Š” ์‹ค์ „ ๋…ธํ•˜์šฐ

Chrome Firefox Safari Polyfill Layer ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ•˜๋‚˜๋กœ 85% ํ˜ธํ™˜์„ฑ ๋‹ฌ์„ฑ

๐ŸŽฏ ์™œ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์ด ์ค‘์š”ํ• ๊นŒ?

์นœ๊ตฌ์•ผ, ์ƒ์ƒํ•ด๋ด. ๋„ˆ๊ฐ€ ๋ฉฐ์น  ๋ฐค์„ ์ƒˆ์›Œ์„œ ์™„์„ฑํ•œ ๋ฉ‹์ง„ ์›น์‚ฌ์ดํŠธ๊ฐ€ ์žˆ์–ด. Chrome์—์„œ๋Š” ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋Š”๋ฐ, ๊ฐ‘์ž๊ธฐ ๊ณ ๊ฐ์ด ์ „ํ™”๋ฅผ ๊ฑธ์–ด์™€์„œ "Safari์—์„œ ๋ฒ„ํŠผ์ด ์•ˆ ๋ˆŒ๋ ค์š”!"๋ผ๊ณ  ํ•˜๋ฉด ์–ด๋–ค ๊ธฐ๋ถ„์ผ๊นŒ? ๐Ÿ˜ฑ

์ด๊ฒŒ ๋ฐ”๋กœ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ์•ผ. 2024๋…„ ํ˜„์žฌ, ์ „ ์„ธ๊ณ„ ์‚ฌ์šฉ์ž๋“ค์€ Chrome, Safari, Firefox, Edge, Opera ๋“ฑ ๋‹ค์–‘ํ•œ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด. ๊ฐ ๋ธŒ๋ผ์šฐ์ €๋Š” ์›น ํ‘œ์ค€์„ ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅด๊ฒŒ ํ•ด์„ํ•˜๊ณ , ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๋‹ค๋ฅธ ์†๋„๋กœ ์ง€์›ํ•˜์ง€.

๐Ÿ“Š 2024๋…„ ๋ธŒ๋ผ์šฐ์ € ์‹œ์žฅ ์ ์œ ์œจ โ€ข Chrome: ์•ฝ 63%
โ€ข Safari: ์•ฝ 20%
โ€ข Edge: ์•ฝ 5%
โ€ข Firefox: ์•ฝ 3%
โ€ข ๊ธฐํƒ€: ์•ฝ 9%

์ด ์ˆซ์ž๋“ค์ด ๋งํ•ด์ฃผ๋Š” ๊ฑด ๋ญ˜๊นŒ? ๋ฐ”๋กœ 37%์˜ ์‚ฌ์šฉ์ž๊ฐ€ Chrome์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฑฐ์•ผ! ๋งŒ์•ฝ ๋„ค ์›น์‚ฌ์ดํŠธ๊ฐ€ Chrome์—์„œ๋งŒ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•œ๋‹ค๋ฉด, ์ž ์žฌ์ ์œผ๋กœ 3๋ช… ์ค‘ 1๋ช… ์ด์ƒ์˜ ์‚ฌ์šฉ์ž๋ฅผ ์žƒ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ด์ง€. ๐Ÿ’ธ

๐Ÿ” ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ์˜ ์ฃผ์š” ์›์ธ๋“ค

์ž, ์ด์ œ ์™œ ๋ธŒ๋ผ์šฐ์ €๋งˆ๋‹ค ๋‹ค๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ํŒŒํ—ค์ณ๋ณด์ž. ์ฃผ์š” ์›์ธ๋“ค์„ ์•Œ๋ฉด ๋ฌธ์ œ๋ฅผ ์˜ˆ๋ฐฉํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ !

1๏ธโƒฃ JavaScript API ์ง€์› ์ฐจ์ด

์ตœ์‹  JavaScript ๊ธฐ๋Šฅ๋“ค์€ ๋ธŒ๋ผ์šฐ์ €๋งˆ๋‹ค ์ง€์› ์‹œ๊ธฐ๊ฐ€ ๋‹ฌ๋ผ. ์˜ˆ๋ฅผ ๋“ค์–ด Promise.allSettled()๋Š” Chrome 76์—์„œ ์ง€์›๋˜๊ธฐ ์‹œ์ž‘ํ–ˆ์ง€๋งŒ, Safari๋Š” 13 ๋ฒ„์ „๋ถ€ํ„ฐ ์ง€์›ํ–ˆ์–ด. ์ด๋Ÿฐ ์‹œ๊ฐ„์ฐจ๊ฐ€ ๋ฌธ์ œ๋ฅผ ๋งŒ๋“ค์ง€.

// ์ด ์ฝ”๋“œ๋Š” ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด
const results = await Promise.allSettled([
  fetch('/api/user'),
  fetch('/api/posts'),
  fetch('/api/comments')
]);

// ๊ฐ ๊ฒฐ๊ณผ์˜ ์ƒํƒœ๋ฅผ ํ™•์ธ
results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('์„ฑ๊ณต:', result.value);
  } else {
    console.log('์‹คํŒจ:', result.reason);
  }
});
2๏ธโƒฃ CSS ์†์„ฑ ์ง€์› ์ฐจ์ด

CSS๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์•ผ. gap ์†์„ฑ, aspect-ratio, :has() ์„ ํƒ์ž ๊ฐ™์€ ์ตœ์‹  ๊ธฐ๋Šฅ๋“ค์€ ๋ธŒ๋ผ์šฐ์ €๋งˆ๋‹ค ์ง€์› ์‹œ์ ์ด ๋‹ค๋ฅด๊ฑฐ๋“ .

/* Flexbox gap - ๊ตฌํ˜• Safari์—์„œ ๋ฌธ์ œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ */
.container {
  display: flex;
  gap: 20px; /* Safari 14.1 ๋ฏธ๋งŒ์—์„œ ์ž‘๋™ ์•ˆ ํ•จ */
}

/* ๋Œ€์•ˆ: margin์„ ์‚ฌ์šฉํ•œ ํ˜ธํ™˜์„ฑ ํ™•๋ณด */
.container {
  display: flex;
}

.container > * {
  margin-right: 20px;
}

.container > *:last-child {
  margin-right: 0;
}
3๏ธโƒฃ ๋ Œ๋”๋ง ์—”์ง„์˜ ์ฐจ์ด

๊ฐ ๋ธŒ๋ผ์šฐ์ €๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ๋ Œ๋”๋ง ์—”์ง„์„ ์‚ฌ์šฉํ•ด:

๐Ÿ”ง ์ฃผ์š” ๋ Œ๋”๋ง ์—”์ง„ Blink (Chrome, Edge, Opera)
โ†’ ๊ฐ€์žฅ ๋น ๋ฅธ ์—…๋ฐ์ดํŠธ, ์ตœ์‹  ๊ธฐ๋Šฅ ์„ ๋„

WebKit (Safari)
โ†’ ๋ณด์ˆ˜์ ์ธ ์ ‘๊ทผ, ๋ณด์•ˆ๊ณผ ์„ฑ๋Šฅ ์ค‘์‹œ

Gecko (Firefox)
โ†’ ์›น ํ‘œ์ค€ ์ค€์ˆ˜์— ์ง‘์ค‘, ๋…๋ฆฝ์ ์ธ ๊ฐœ๋ฐœ
4๏ธโƒฃ ๋ฒค๋” ํ”„๋ฆฌํ”ฝ์Šค (Vendor Prefix)

์‹คํ—˜์ ์ธ CSS ๊ธฐ๋Šฅ๋“ค์€ ๋ธŒ๋ผ์šฐ์ € ๋ฒค๋” ํ”„๋ฆฌํ”ฝ์Šค๊ฐ€ ํ•„์š”ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด. ์ด๊ฒŒ ๋˜ ๊ณจ์น˜ ์•„ํ”ˆ ๋ถ€๋ถ„์ด์ง€.

/* ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ง€์›ํ•˜๋ ค๋ฉด ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•ด์•ผ ํ•ด */
.element {
  -webkit-transform: rotate(45deg); /* Chrome, Safari */
  -moz-transform: rotate(45deg);    /* Firefox */
  -ms-transform: rotate(45deg);     /* IE */
  -o-transform: rotate(45deg);      /* Opera */
  transform: rotate(45deg);         /* ํ‘œ์ค€ */
}
5๏ธโƒฃ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์˜ ์ฐจ์ด

๋ธŒ๋ผ์šฐ์ €๋งˆ๋‹ค ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋‹ฌ๋ผ. ํŠนํžˆ ํ„ฐ์น˜ ์ด๋ฒคํŠธ, ์Šคํฌ๋กค ์ด๋ฒคํŠธ, ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ์—์„œ ์ฐจ์ด๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€.

// ํœ  ์ด๋ฒคํŠธ - ๋ธŒ๋ผ์šฐ์ €๋งˆ๋‹ค ๋‹ค๋ฅธ ์†์„ฑ ์‚ฌ์šฉ
element.addEventListener('wheel', (e) => {
  // Firefox๋Š” deltaY๋ฅผ ์‚ฌ์šฉ
  // ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €๋Š” wheelDelta๋ฅผ ์‚ฌ์šฉ
  const delta = e.deltaY || e.wheelDelta;
  
  // ์ •๊ทœํ™” ํ•„์š”
  const normalizedDelta = delta > 0 ? 1 : -1;
  
  console.log('์Šคํฌ๋กค ๋ฐฉํ–ฅ:', normalizedDelta);
});

๐Ÿ› ๏ธ ํด๋ฆฌํ•„(Polyfill)์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

์ž, ์ด์ œ ์šฐ๋ฆฌ์˜ ๊ตฌ์„ธ์ฃผ ํด๋ฆฌํ•„์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž! ํด๋ฆฌํ•„์€ ์‰ฝ๊ฒŒ ๋งํ•ด์„œ "๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ตœ์‹  ๊ธฐ๋Šฅ์„ ๊ฐ€๋ฅด์ณ์ฃผ๋Š” ์„ ์ƒ๋‹˜" ๊ฐ™์€ ๊ฑฐ์•ผ. ๐ŸŽ“

๐Ÿ’ก ํด๋ฆฌํ•„์˜ ์ •์˜ ํด๋ฆฌํ•„(Polyfill)์€ ์›น ๊ฐœ๋ฐœ์—์„œ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ตœ์‹  ๊ธฐ๋Šฅ์„ JavaScript๋กœ ๊ตฌํ˜„ํ•˜์—ฌ ์ œ๊ณตํ•˜๋Š” ์ฝ”๋“œ ์กฐ๊ฐ์ด์•ผ. ๋ธŒ๋ผ์šฐ์ €์˜ "๋นˆ ๊ตฌ๋ฉ(gap)"์„ ๋ฉ”์›Œ์ค€๋‹ค๊ณ  ํ•ด์„œ ํด๋ฆฌํ•„์ด๋ผ๋Š” ์ด๋ฆ„์ด ๋ถ™์—ˆ์ง€.

์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ. Array.prototype.includes()๋Š” ES2016์—์„œ ์ถ”๊ฐ€๋œ ๊ธฐ๋Šฅ์ธ๋ฐ, IE11์—์„œ๋Š” ์ง€์›ํ•˜์ง€ ์•Š์•„. ์ด๋Ÿด ๋•Œ ํด๋ฆฌํ•„์„ ์‚ฌ์šฉํ•˜๋ฉด IE11์—์„œ๋„ ์ด ๊ธฐ๋Šฅ์„ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฑฐ์ง€!

// Array.includes() ํด๋ฆฌํ•„ ์˜ˆ์‹œ
if (!Array.prototype.includes) {
  Array.prototype.includes = function(searchElement, fromIndex) {
    // 'this'๊ฐ€ null์ด๋‚˜ undefined์ธ์ง€ ํ™•์ธ
    if (this == null) {
      throw new TypeError('"this" is null or not defined');
    }

    const o = Object(this);
    const len = o.length >>> 0;

    // ๋ฐฐ์—ด ๊ธธ์ด๊ฐ€ 0์ด๋ฉด false ๋ฐ˜ํ™˜
    if (len === 0) {
      return false;
    }

    const n = fromIndex | 0;
    let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

    // ๊ฒ€์ƒ‰ ์‹œ์ž‘
    while (k < len) {
      if (o[k] === searchElement) {
        return true;
      }
      k++;
    }
    return false;
  };
}

// ์ด์ œ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ!
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits.includes('banana')); // true
ํด๋ฆฌํ•„ vs ํŠธ๋žœ์ŠคํŒŒ์ผ๋Ÿฌ

์—ฌ๊ธฐ์„œ ์ž ๊น! ํด๋ฆฌํ•„๊ณผ ํŠธ๋žœ์ŠคํŒŒ์ผ๋Ÿฌ๋ฅผ ํ—ท๊ฐˆ๋ฆฌ๋Š” ์นœ๊ตฌ๋“ค์ด ๋งŽ์€๋ฐ, ๋‘˜์€ ์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ฐœ๋…์ด์•ผ. ๐Ÿค”

๊ตฌ๋ถ„ ํด๋ฆฌํ•„ (Polyfill) ํŠธ๋žœ์ŠคํŒŒ์ผ๋Ÿฌ (Transpiler)
์—ญํ•  ๋Ÿฐํƒ€์ž„์— ๋ˆ„๋ฝ๋œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๋นŒ๋“œ ํƒ€์ž„์— ์ฝ”๋“œ ๋ณ€ํ™˜
๋Œ€์ƒ API, ๋ฉ”์„œ๋“œ, ๊ฐ์ฒด ๋ฌธ๋ฒ•, ๊ตฌ์กฐ
์˜ˆ์‹œ Promise, fetch, includes ํ™”์‚ดํ‘œ ํ•จ์ˆ˜, async/await, ํด๋ž˜์Šค
๋„๊ตฌ core-js, polyfill.io Babel, TypeScript

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด, ํŠธ๋žœ์ŠคํŒŒ์ผ๋Ÿฌ๋Š” ์ตœ์‹  ๋ฌธ๋ฒ•์„ ๊ตฌํ˜• ๋ฌธ๋ฒ•์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๊ณ , ํด๋ฆฌํ•„์€ ์—†๋Š” ๊ธฐ๋Šฅ์„ ์ง์ ‘ ๋งŒ๋“ค์–ด์„œ ์ œ๊ณตํ•˜๋Š” ๊ฑฐ์•ผ!

๐ŸŽจ ์‹ค์ „! ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ํ™•๋ณด ์ „๋žต

์ด๋ก ์€ ์ด์ œ ์ถฉ๋ถ„ํ•ด! ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์„ ํ™•๋ณดํ•˜๋Š”์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ์•Œ์•„๋ณด์ž. ๐Ÿ’ช

์ „๋žต 1: ๊ธฐ๋Šฅ ๊ฐ์ง€ (Feature Detection)

์ฒซ ๋ฒˆ์งธ ์ „๋žต์€ "๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๋Š”์ง€ ๋จผ์ € ๋ฌผ์–ด๋ณด๊ธฐ"์•ผ. ์ด๊ฑธ ๊ธฐ๋Šฅ ๊ฐ์ง€๋ผ๊ณ  ํ•ด.

// ๋‚˜์œ ์˜ˆ: ๋ธŒ๋ผ์šฐ์ € ๊ฐ์ง€ (User Agent Sniffing)
if (navigator.userAgent.indexOf('MSIE') !== -1) {
  // IE ์ „์šฉ ์ฝ”๋“œ
}

// ์ข‹์€ ์˜ˆ: ๊ธฐ๋Šฅ ๊ฐ์ง€
if ('IntersectionObserver' in window) {
  // IntersectionObserver ์‚ฌ์šฉ
  const observer = new IntersectionObserver(callback);
  observer.observe(element);
} else {
  // ๋Œ€์ฒด ๋ฐฉ๋ฒ• ์‚ฌ์šฉ
  window.addEventListener('scroll', handleScroll);
}
๐ŸŽฏ ์™œ ๊ธฐ๋Šฅ ๊ฐ์ง€๊ฐ€ ๋” ์ข‹์„๊นŒ? ๋ธŒ๋ผ์šฐ์ € ๊ฐ์ง€๋Š” User Agent ๋ฌธ์ž์—ด์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†์–ด. ํ•˜์ง€๋งŒ ๊ธฐ๋Šฅ ๊ฐ์ง€๋Š” ์‹ค์ œ๋กœ ๊ทธ ๊ธฐ๋Šฅ์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ์•ˆ์ „ํ•˜๊ณ  ์ •ํ™•ํ•˜์ง€!
์ „๋žต 2: Modernizr ํ™œ์šฉํ•˜๊ธฐ

Modernizr๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋Šฅ ์ง€์› ์—ฌ๋ถ€๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์•ผ. ์ผ์ผ์ด ์ฒดํฌํ•˜๊ธฐ ๊ท€์ฐฎ์„ ๋•Œ ์ •๋ง ์œ ์šฉํ•˜์ง€!

// Modernizr ์‚ฌ์šฉ ์˜ˆ์‹œ
if (Modernizr.flexbox) {
  // Flexbox ์‚ฌ์šฉ
  document.body.classList.add('flexbox-layout');
} else {
  // Float ๊ธฐ๋ฐ˜ ๋ ˆ์ด์•„์›ƒ ์‚ฌ์šฉ
  document.body.classList.add('float-layout');
}

// CSS์—์„œ๋„ ํ™œ์šฉ ๊ฐ€๋Šฅ
// .flexbox-layout .container { display: flex; }
// .no-flexbox .container { float: left; }
์ „๋žต 3: Core-js๋กœ ํด๋ฆฌํ•„ ์ ์šฉํ•˜๊ธฐ

Core-js๋Š” ๊ฐ€์žฅ ํฌ๊ด„์ ์ธ ํด๋ฆฌํ•„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์•ผ. ES5๋ถ€ํ„ฐ ์ตœ์‹  ES2024๊นŒ์ง€ ๊ฑฐ์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ํด๋ฆฌํ•„๋กœ ์ œ๊ณตํ•˜์ง€!

// npm์œผ๋กœ ์„ค์น˜
// npm install core-js

// ์ „์ฒด ํด๋ฆฌํ•„ ๊ฐ€์ ธ์˜ค๊ธฐ (๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ - ์šฉ๋Ÿ‰์ด ์ปค์ง)
import 'core-js';

// ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์„ ํƒ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ (๊ถŒ์žฅ)
import 'core-js/features/promise';
import 'core-js/features/array/includes';
import 'core-js/features/string/starts-with';

// ์ด์ œ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ!
const promise = new Promise((resolve) => {
  resolve('Hello!');
});

const arr = [1, 2, 3];
console.log(arr.includes(2)); // true

const str = 'Hello World';
console.log(str.startsWith('Hello')); // true
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ ๋ชจ๋“  ํด๋ฆฌํ•„์„ ํ•œ๊บผ๋ฒˆ์— ๋กœ๋“œํ•˜๋ฉด ๋ฒˆ๋“ค ํฌ๊ธฐ๊ฐ€ ์—„์ฒญ๋‚˜๊ฒŒ ์ปค์ ธ! ๊ผญ ํ•„์š”ํ•œ ํด๋ฆฌํ•„๋งŒ ์„ ํƒ์ ์œผ๋กœ ๋กœ๋“œํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ๋ณดํ†ต 100KB ์ด์ƒ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ .
์ „๋žต 4: Polyfill.io ์„œ๋น„์Šค ํ™œ์šฉ

Polyfill.io๋Š” ์ •๋ง ๋˜‘๋˜‘ํ•œ ์„œ๋น„์Šค์•ผ. ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•ด์„œ ํ•„์š”ํ•œ ํด๋ฆฌํ•„๋งŒ ์ œ๊ณตํ•ด์ฃผ๊ฑฐ๋“ ! ๐Ÿง 

<!-- HTML head์— ์ถ”๊ฐ€ -->
<script src="https://polyfill.io/v3/polyfill.min.js"></script>

<!-- ํŠน์ • ๊ธฐ๋Šฅ๋งŒ ์š”์ฒญ -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=Promise,fetch,Array.prototype.includes"></script>

<!-- ํŠน์ • ๋ธŒ๋ผ์šฐ์ € ๋ฒ„์ „ ํƒ€๊ฒŸํŒ… -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=default&flags=gated"></script>

์ด ์„œ๋น„์Šค์˜ ์žฅ์ ์€ ๋ญ˜๊นŒ? Chrome ์ตœ์‹  ๋ฒ„์ „ ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ๊ฑฐ์˜ ๋นˆ ํŒŒ์ผ์„ ๋ณด๋‚ด๊ณ , IE11 ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ํ•„์š”ํ•œ ๋ชจ๋“  ํด๋ฆฌํ•„์„ ๋ณด๋‚ด๋Š” ๊ฑฐ์•ผ. ํšจ์œจ์ ์ด์ง€? ๐Ÿ˜Ž

์ „๋žต 5: Babel๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ

Babel์€ ์ตœ์‹  JavaScript ๋ฌธ๋ฒ•์„ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ํ•ด์ค˜. @babel/preset-env์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ž๋™์œผ๋กœ ํ•„์š”ํ•œ ํด๋ฆฌํ•„์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด!

// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // ํƒ€๊ฒŸ ๋ธŒ๋ผ์šฐ์ € ์ง€์ •
        targets: {
          browsers: [
            'last 2 versions',
            'ie >= 11',
            'safari >= 10'
          ]
        },
        // ํด๋ฆฌํ•„ ์ž๋™ ์ถ”๊ฐ€
        useBuiltIns: 'usage',
        corejs: 3
      }
    ]
  ]
};

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด Babel์ด ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด์„œ ํ•„์š”ํ•œ ํด๋ฆฌํ•„๋งŒ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•ด์ค˜. ๊ฐœ๋ฐœ์ž๋Š” ์ตœ์‹  ๋ฌธ๋ฒ•์œผ๋กœ ํŽธํ•˜๊ฒŒ ์ฝ”๋”ฉํ•˜๊ณ , Babel์ด ์•Œ์•„์„œ ํ˜ธํ™˜์„ฑ์„ ์ฑ™๊ฒจ์ฃผ๋Š” ๊ฑฐ์ง€!

๐ŸŽฏ ์ฃผ์š” ๊ธฐ๋Šฅ๋ณ„ ํด๋ฆฌํ•„ ๊ฐ€์ด๋“œ

์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์˜ ํด๋ฆฌํ•„์„ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด์ž. ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ๋‚ด์šฉ๋“ค์ด์•ผ! ๐Ÿ”ฅ

Promise ํด๋ฆฌํ•„

Promise๋Š” ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ•ต์‹ฌ์ธ๋ฐ, IE11์—์„œ๋Š” ์ง€์›ํ•˜์ง€ ์•Š์•„. ํ•˜์ง€๋งŒ ํด๋ฆฌํ•„๋กœ ์™„๋ฒฝํ•˜๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€!

// Promise ํด๋ฆฌํ•„ (๊ฐ„๋‹จ ๋ฒ„์ „)
if (typeof Promise === 'undefined') {
  window.Promise = function(executor) {
    const self = this;
    self.status = 'pending';
    self.value = undefined;
    self.callbacks = [];

    function resolve(value) {
      if (self.status === 'pending') {
        self.status = 'fulfilled';
        self.value = value;
        self.callbacks.forEach(cb => cb.onFulfilled(value));
      }
    }

    function reject(reason) {
      if (self.status === 'pending') {
        self.status = 'rejected';
        self.value = reason;
        self.callbacks.forEach(cb => cb.onRejected(reason));
      }
    }

    self.then = function(onFulfilled, onRejected) {
      return new Promise((resolve, reject) => {
        function handle() {
          if (self.status === 'fulfilled') {
            try {
              const result = onFulfilled(self.value);
              resolve(result);
            } catch (e) {
              reject(e);
            }
          } else if (self.status === 'rejected') {
            try {
              const result = onRejected(self.value);
              resolve(result);
            } catch (e) {
              reject(e);
            }
          }
        }

        if (self.status === 'pending') {
          self.callbacks.push({
            onFulfilled: () => handle(),
            onRejected: () => handle()
          });
        } else {
          setTimeout(handle, 0);
        }
      });
    };

    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('์„ฑ๊ณต!');
  }, 1000);
});

myPromise.then(result => {
  console.log(result); // "์„ฑ๊ณต!"
});
๐Ÿ’ก ์‹ค๋ฌด ํŒ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์œ„์ฒ˜๋Ÿผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ธฐ๋ณด๋‹ค๋Š” 'promise-polyfill' ๊ฐ™์€ ๊ฒ€์ฆ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์•ˆ์ „ํ•ด. ์—ฃ์ง€ ์ผ€์ด์Šค๋“ค์ด ๋งŽ์ด ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ๊ฑฐ๋“ !
Fetch API ํด๋ฆฌํ•„

Fetch API๋Š” AJAX ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ํ˜„๋Œ€์ ์ธ ๋ฐฉ๋ฒ•์ด์•ผ. XMLHttpRequest๋ณด๋‹ค ํ›จ์”ฌ ๊น”๋”ํ•˜์ง€๋งŒ, ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ง€์›ํ•˜์ง€ ์•Š์•„.

// fetch ํด๋ฆฌํ•„ ์‚ฌ์šฉ (whatwg-fetch ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)
// npm install whatwg-fetch
import 'whatwg-fetch';

// ์ด์ œ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ fetch ์‚ฌ์šฉ ๊ฐ€๋Šฅ!
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then(data => {
    console.log('๋ฐ์ดํ„ฐ:', data);
  })
  .catch(error => {
    console.error('์—๋Ÿฌ ๋ฐœ์ƒ:', error);
  });

// async/await์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('์—๋Ÿฌ:', error);
    throw error;
  }
}
IntersectionObserver ํด๋ฆฌํ•„

IntersectionObserver๋Š” ์š”์†Œ๊ฐ€ ๋ทฐํฌํŠธ์— ๋“ค์–ด์™”๋Š”์ง€ ๊ฐ์ง€ํ•˜๋Š” API์•ผ. ๋ฌดํ•œ ์Šคํฌ๋กค, ๋ ˆ์ด์ง€ ๋กœ๋”ฉ์— ํ•„์ˆ˜์ ์ด์ง€!

// IntersectionObserver ํด๋ฆฌํ•„
// npm install intersection-observer
import 'intersection-observer';

// ์ด๋ฏธ์ง€ ๋ ˆ์ด์ง€ ๋กœ๋”ฉ ๊ตฌํ˜„
const images = document.querySelectorAll('img[data-src]');

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      
      // data-src๋ฅผ ์‹ค์ œ src๋กœ ๋ณ€๊ฒฝ
      img.src = img.dataset.src;
      
      // ๋กœ๋”ฉ ์™„๋ฃŒ ํ›„ ํด๋ž˜์Šค ์ถ”๊ฐ€
      img.addEventListener('load', () => {
        img.classList.add('loaded');
      });
      
      // ๊ด€์ฐฐ ์ค‘์ง€
      observer.unobserve(img);
    }
  });
}, {
  // ๋ทฐํฌํŠธ ํ•˜๋‹จ 200px ์ „๋ถ€ํ„ฐ ๋กœ๋”ฉ ์‹œ์ž‘
  rootMargin: '0px 0px 200px 0px',
  threshold: 0.01
});

// ๋ชจ๋“  ์ด๋ฏธ์ง€ ๊ด€์ฐฐ ์‹œ์ž‘
images.forEach(img => imageObserver.observe(img));
Object.assign ํด๋ฆฌํ•„

Object.assign์€ ๊ฐ์ฒด๋ฅผ ๋ณ‘ํ•ฉํ•  ๋•Œ ์ •๋ง ์œ ์šฉํ•œ๋ฐ, IE์—์„œ๋Š” ์ง€์›ํ•˜์ง€ ์•Š์•„. ๊ฐ„๋‹จํ•œ ํด๋ฆฌํ•„๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด!

// Object.assign ํด๋ฆฌํ•„
if (typeof Object.assign !== 'function') {
  Object.assign = function(target) {
    'use strict';
    if (target === null || target === undefined) {
      throw new TypeError('Cannot convert undefined or null to object');
    }

    const to = Object(target);

    for (let index = 1; index < arguments.length; index++) {
      const nextSource = arguments[index];

      if (nextSource !== null && nextSource !== undefined) {
        for (const nextKey in nextSource) {
          if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
    }
    return to;
  };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { c: 5, d: 6 };

const merged = Object.assign({}, obj1, obj2, obj3);
console.log(merged); // { a: 1, b: 3, c: 5, d: 6 }
Array ๋ฉ”์„œ๋“œ ํด๋ฆฌํ•„

์ตœ์‹  Array ๋ฉ”์„œ๋“œ๋“ค์€ ์ฝ”๋“œ๋ฅผ ์ •๋ง ๊น”๋”ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ค˜. ํ•˜์ง€๋งŒ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„.

// Array.prototype.find ํด๋ฆฌํ•„
if (!Array.prototype.find) {
  Array.prototype.find = function(predicate) {
    if (this == null) {
      throw new TypeError('Array.prototype.find called on null or undefined');
    }
    if (typeof predicate !== 'function') {
      throw new TypeError('predicate must be a function');
    }
    
    const list = Object(this);
    const length = list.length >>> 0;
    const thisArg = arguments[1];

    for (let i = 0; i < length; i++) {
      const value = list[i];
      if (predicate.call(thisArg, value, i, list)) {
        return value;
      }
    }
    return undefined;
  };
}

// Array.prototype.findIndex ํด๋ฆฌํ•„
if (!Array.prototype.findIndex) {
  Array.prototype.findIndex = function(predicate) {
    if (this == null) {
      throw new TypeError('Array.prototype.findIndex called on null or undefined');
    }
    if (typeof predicate !== 'function') {
      throw new TypeError('predicate must be a function');
    }
    
    const list = Object(this);
    const length = list.length >>> 0;
    const thisArg = arguments[1];

    for (let i = 0; i < length; i++) {
      if (predicate.call(thisArg, list[i], i, list)) {
        return i;
      }
    }
    return -1;
  };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 },
  { id: 3, name: 'Charlie', age: 35 }
];

const user = users.find(u => u.age > 28);
console.log(user); // { id: 2, name: 'Bob', age: 30 }

const index = users.findIndex(u => u.name === 'Charlie');
console.log(index); // 2
String ๋ฉ”์„œ๋“œ ํด๋ฆฌํ•„

String ๋ฉ”์„œ๋“œ๋“ค๋„ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ํŠนํžˆ startsWith, endsWith, includes ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋“ค์ด ์œ ์šฉํ•˜์ง€!

// String.prototype.startsWith ํด๋ฆฌํ•„
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(search, pos) {
    pos = !pos || pos < 0 ? 0 : +pos;
    return this.substring(pos, pos + search.length) === search;
  };
}

// String.prototype.endsWith ํด๋ฆฌํ•„
if (!String.prototype.endsWith) {
  String.prototype.endsWith = function(search, this_len) {
    if (this_len === undefined || this_len > this.length) {
      this_len = this.length;
    }
    return this.substring(this_len - search.length, this_len) === search;
  };
}

// String.prototype.includes ํด๋ฆฌํ•„
if (!String.prototype.includes) {
  String.prototype.includes = function(search, start) {
    if (typeof start !== 'number') {
      start = 0;
    }
    
    if (start + search.length > this.length) {
      return false;
    } else {
      return this.indexOf(search, start) !== -1;
    }
  };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const filename = 'document.pdf';
console.log(filename.startsWith('doc')); // true
console.log(filename.endsWith('.pdf')); // true
console.log(filename.includes('ment')); // true

๐ŸŽจ CSS ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ์ „๋žต

JavaScript๋งŒํผ์ด๋‚˜ CSS๋„ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์ด ์ค‘์š”ํ•ด! CSS๋Š” ์‹œ๊ฐ์ ์œผ๋กœ ๋ฐ”๋กœ ๋“œ๋Ÿฌ๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ๋” ์‹ ๊ฒฝ ์จ์•ผ ํ•˜์ง€. ๐ŸŽจ

CSS Reset๊ณผ Normalize.css

๋ธŒ๋ผ์šฐ์ €๋งˆ๋‹ค ๊ธฐ๋ณธ ์Šคํƒ€์ผ์ด ๋‹ฌ๋ผ์„œ ๊ฐ™์€ HTML์ด๋ผ๋„ ๋‹ค๋ฅด๊ฒŒ ๋ณด์ผ ์ˆ˜ ์žˆ์–ด. ์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์ด CSS Reset์ด์•ผ!

/* ๊ฐ„๋‹จํ•œ CSS Reset */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* ๋˜๋Š” Normalize.css ์‚ฌ์šฉ (๊ถŒ์žฅ) */
/* npm install normalize.css */
/* @import 'normalize.css'; */

/* Normalize.css๋Š” ๋ธŒ๋ผ์šฐ์ € ๊ฐ„ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„
   ์œ ์šฉํ•œ ๊ธฐ๋ณธ ์Šคํƒ€์ผ์€ ๋ณด์กดํ•ด์ค˜ */
๐Ÿ’ก Reset vs Normalize CSS Reset: ๋ชจ๋“  ๊ธฐ๋ณธ ์Šคํƒ€์ผ์„ ์ œ๊ฑฐ (๊ทน๋‹จ์ )
Normalize.css: ๋ธŒ๋ผ์šฐ์ € ๊ฐ„ ์ฐจ์ด๋งŒ ์ •๊ทœํ™” (์‹ค์šฉ์ )

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ Normalize.css๊ฐ€ ๋” ์ข‹์€ ์„ ํƒ์ด์•ผ!
Autoprefixer๋กœ ๋ฒค๋” ํ”„๋ฆฌํ”ฝ์Šค ์ž๋™ํ™”

๋ฒค๋” ํ”„๋ฆฌํ”ฝ์Šค๋ฅผ ์ผ์ผ์ด ์ž‘์„ฑํ•˜๋Š” ๊ฑด ์ •๋ง ๊ท€์ฐฎ์€ ์ผ์ด์•ผ. Autoprefixer๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•ด์ค˜!

/* ์ž‘์„ฑํ•œ CSS */
.box {
  display: flex;
  transition: all 0.3s ease;
  user-select: none;
}

/* Autoprefixer๊ฐ€ ๋ณ€ํ™˜ํ•œ CSS */
.box {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-transition: all 0.3s ease;
  transition: all 0.3s ease;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

Webpack์ด๋‚˜ PostCSS์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋นŒ๋“œ ๊ณผ์ •์—์„œ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋ผ. ๊ฐœ๋ฐœ์ž๋Š” ํ‘œ์ค€ CSS๋งŒ ์ž‘์„ฑํ•˜๋ฉด ๋˜๋Š” ๊ฑฐ์ง€!

// postcss.config.js
module.exports = {
  plugins: [
    require('autoprefixer')({
      browsers: [
        'last 2 versions',
        'ie >= 11',
        'iOS >= 10',
        'Android >= 5'
      ]
    })
  ]
};
Flexbox ํ˜ธํ™˜์„ฑ ์ฒ˜๋ฆฌ

Flexbox๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋ ˆ์ด์•„์›ƒ ๋„๊ตฌ์ธ๋ฐ, ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ IE10-11์—์„œ ๋ฒ„๊ทธ๊ฐ€ ๋งŽ์ง€.

/* Flexbox ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ */
.container {
  display: -webkit-box;      /* ๊ตฌํ˜• Safari */
  display: -ms-flexbox;      /* IE 10 */
  display: flex;             /* ํ‘œ์ค€ */
  
  /* IE 10-11 ๋ฒ„๊ทธ ์ˆ˜์ •: flex-basis ๋ช…์‹œ */
  -ms-flex-preferred-size: auto;
}

.item {
  -webkit-box-flex: 1;
  -ms-flex: 1 1 auto;
  flex: 1 1 auto;
  
  /* IE 10-11: min-width ๋ช…์‹œ (flex ์•„์ดํ…œ ์ถ•์†Œ ๋ฒ„๊ทธ ๋ฐฉ์ง€) */
  min-width: 0;
}

/* Flexbox gap ๋Œ€์ฒด (๊ตฌํ˜• Safari ๋Œ€์‘) */
.container-with-gap {
  display: flex;
  /* gap: 20px; */ /* Safari 14.1 ๋ฏธ๋งŒ์—์„œ ์ž‘๋™ ์•ˆ ํ•จ */
}

.container-with-gap > * {
  margin-right: 20px;
}

.container-with-gap > *:last-child {
  margin-right: 0;
}
Grid Layout ํด๋ฐฑ

CSS Grid๋Š” ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์™„๋ฒฝํ•˜์ง€๋งŒ, IE11์—์„œ๋Š” ์ œํ•œ์ ์œผ๋กœ๋งŒ ์ง€์›๋ผ. ํด๋ฐฑ ์ „๋žต์ด ํ•„์š”ํ•ด!

/* Grid with Flexbox fallback */
.grid-container {
  /* Flexbox ํด๋ฐฑ */
  display: flex;
  flex-wrap: wrap;
  margin: -10px; /* ์Œ์ˆ˜ ๋งˆ์ง„์œผ๋กœ ๊ฐ„๊ฒฉ ์กฐ์ • */
}

.grid-item {
  /* Flexbox ํด๋ฐฑ */
  flex: 0 0 calc(33.333% - 20px);
  margin: 10px;
}

/* Grid ์ง€์› ๋ธŒ๋ผ์šฐ์ €์šฉ */
@supports (display: grid) {
  .grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    margin: 0;
  }
  
  .grid-item {
    flex: none;
    margin: 0;
  }
}
๐Ÿ’ก @supports ๊ทœ์น™ @supports๋Š” CSS์˜ ๊ธฐ๋Šฅ ๊ฐ์ง€ ๋„๊ตฌ์•ผ. ํŠน์ • CSS ์†์„ฑ์„ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง€์›ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ•ด๋‹น ์Šคํƒ€์ผ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด!
CSS ๋ณ€์ˆ˜ (Custom Properties) ํด๋ฐฑ

CSS ๋ณ€์ˆ˜๋Š” ์ •๋ง ํŽธ๋ฆฌํ•œ๋ฐ, IE์—์„œ๋Š” ์ „ํ˜€ ์ง€์›ํ•˜์ง€ ์•Š์•„. ํด๋ฐฑ ๊ฐ’์„ ์ œ๊ณตํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด!

:root {
  --primary-color: #6c5ce7;
  --secondary-color: #a29bfe;
  --spacing: 20px;
}

.element {
  /* IE ํด๋ฐฑ: ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฐ’ ๋จผ์ € */
  color: #6c5ce7;
  /* CSS ๋ณ€์ˆ˜ (์ง€์›ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฎ์–ด์”€) */
  color: var(--primary-color);
  
  padding: 20px;
  padding: var(--spacing);
}

/* JavaScript๋กœ ๋™์  ํด๋ฐฑ ์ œ๊ณต */
// CSS ๋ณ€์ˆ˜ ์ง€์› ํ™•์ธ ๋ฐ ํด๋ฐฑ
if (!window.CSS || !window.CSS.supports || 
    !window.CSS.supports('(--test: 0)')) {
  // CSS ๋ณ€์ˆ˜ ๋ฏธ์ง€์› ๋ธŒ๋ผ์šฐ์ €
  const root = document.documentElement;
  
  // ์ธ๋ผ์ธ ์Šคํƒ€์ผ๋กœ ๋Œ€์ฒด
  const elements = document.querySelectorAll('[style*="var("]');
  elements.forEach(el => {
    const style = el.getAttribute('style');
    // var(--variable) ํŒจํ„ด์„ ์‹ค์ œ ๊ฐ’์œผ๋กœ ์น˜ํ™˜
    const newStyle = style.replace(
      /var\(--primary-color\)/g, 
      '#6c5ce7'
    );
    el.setAttribute('style', newStyle);
  });
}

๐Ÿงช ํ…Œ์ŠคํŠธ์™€ ๋””๋ฒ„๊น… ์ „๋žต

ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์„ ํ™•๋ณดํ–ˆ๋‹ค๋ฉด, ์ด์ œ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•ด! ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒŒ ๊ฐ€์žฅ ํ™•์‹คํ•˜์ง€. ๐Ÿ”ฌ

๋ธŒ๋ผ์šฐ์ € ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋“ค
๐Ÿ› ๏ธ ํ•„์ˆ˜ ํ…Œ์ŠคํŠธ ๋„๊ตฌ 1. BrowserStack
โ†’ ์‹ค์ œ ๋””๋ฐ”์ด์Šค์™€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ…Œ์ŠคํŠธ
โ†’ 2000๊ฐœ ์ด์ƒ์˜ ๋ธŒ๋ผ์šฐ์ €/๋””๋ฐ”์ด์Šค ์กฐํ•ฉ
โ†’ ์œ ๋ฃŒ์ง€๋งŒ ๋ฌด๋ฃŒ ์ฒดํ—˜ ๊ฐ€๋Šฅ

2. CrossBrowserTesting
โ†’ ๋ผ์ด๋ธŒ ํ…Œ์ŠคํŠธ์™€ ์ž๋™ํ™” ํ…Œ์ŠคํŠธ ์ง€์›
โ†’ ์Šคํฌ๋ฆฐ์ƒท ๋น„๊ต ๊ธฐ๋Šฅ
โ†’ Selenium ํ†ตํ•ฉ

3. LambdaTest
โ†’ ํด๋ผ์šฐ๋“œ ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ ํ”Œ๋žซํผ
โ†’ ์‹ค์‹œ๊ฐ„ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํ…Œ์ŠคํŠธ
โ†’ ์ž๋™ํ™”๋œ ์Šคํฌ๋ฆฐ์ƒท ํ…Œ์ŠคํŠธ

4. Sauce Labs
โ†’ ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰ ํ…Œ์ŠคํŠธ ํ”Œ๋žซํผ
โ†’ CI/CD ํ†ตํ•ฉ
โ†’ ์„ฑ๋Šฅ ๋ถ„์„ ๊ธฐ๋Šฅ
๋กœ์ปฌ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ตฌ์ถ•

ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋„ ์ข‹์ง€๋งŒ, ๋กœ์ปฌ์—์„œ ๋น ๋ฅด๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ๋„ ํ•„์š”ํ•ด!

// package.json - ๋‹ค์–‘ํ•œ ๋ธŒ๋ผ์šฐ์ €๋กœ ํ…Œ์ŠคํŠธ
{
  "scripts": {
    "test:chrome": "karma start --browsers Chrome",
    "test:firefox": "karma start --browsers Firefox",
    "test:safari": "karma start --browsers Safari",
    "test:all": "karma start --browsers Chrome,Firefox,Safari"
  },
  "devDependencies": {
    "karma": "^6.4.0",
    "karma-chrome-launcher": "^3.1.1",
    "karma-firefox-launcher": "^2.1.2",
    "karma-safari-launcher": "^1.0.0"
  }
}
Can I Use ํ™œ์šฉํ•˜๊ธฐ

์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ํ•ญ์ƒ Can I Use(caniuse.com)์—์„œ ๋ธŒ๋ผ์šฐ์ € ์ง€์› ํ˜„ํ™ฉ์„ ํ™•์ธํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์ด์ž!

// ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ Can I Use ๋ฐ์ดํ„ฐ ํ™œ์šฉ
// npm install caniuse-api

const caniuse = require('caniuse-api');

// ํŠน์ • ๊ธฐ๋Šฅ์˜ ์ง€์› ์—ฌ๋ถ€ ํ™•์ธ
console.log(caniuse.isSupported('flexbox', 'ie 11')); // true
console.log(caniuse.isSupported('css-grid', 'ie 11')); // false (partial)

// ๋ธŒ๋ผ์šฐ์ € ์ง€์› ํ†ต๊ณ„
console.log(caniuse.getSupport('promise'));
// {
//   chrome: { "32": "y", "33": "y", ... },
//   firefox: { "29": "y", "30": "y", ... },
//   ...
// }

// ํŠน์ • ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ € ๋ชฉ๋ก
console.log(caniuse.getBrowsers('IntersectionObserver'));
์ž๋™ํ™”๋œ ํ˜ธํ™˜์„ฑ ์ฒดํฌ

ESLint ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ ์ž‘์„ฑ ์ค‘์— ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๋ฅผ ๋ฏธ๋ฆฌ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์–ด!

// .eslintrc.js
module.exports = {
  plugins: ['compat'],
  extends: ['plugin:compat/recommended'],
  env: {
    browser: true
  },
  settings: {
    // ํƒ€๊ฒŸ ๋ธŒ๋ผ์šฐ์ € ์„ค์ •
    browsers: [
      'last 2 versions',
      'ie >= 11',
      'not dead'
    ]
  },
  rules: {
    'compat/compat': 'error'
  }
};

// ์ด์ œ ์ง€์›ํ•˜์ง€ ์•Š๋Š” API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒฝ๊ณ ๊ฐ€ ํ‘œ์‹œ๋ผ!
// ์˜ˆ: fetch() ์‚ฌ์šฉ ์‹œ IE11์—์„œ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ๊ฒฝ๊ณ 
์‹ค์ œ ๋””๋ฐ”์ด์Šค ํ…Œ์ŠคํŠธ

์—๋ฎฌ๋ ˆ์ดํ„ฐ๋„ ์ข‹์ง€๋งŒ, ์‹ค์ œ ๋””๋ฐ”์ด์Šค์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒŒ ๊ฐ€์žฅ ํ™•์‹คํ•ด. ํŠนํžˆ ๋ชจ๋ฐ”์ผ Safari๋Š” ๊ผญ ์‹ค์ œ iPhone์—์„œ ํ…Œ์ŠคํŠธํ•ด๋ด์•ผ ํ•ด!

๐Ÿ“ฑ ์‹ค์ œ ๋””๋ฐ”์ด์Šค ํ…Œ์ŠคํŠธ ํŒ 1. ๋กœ์ปฌ ๋„คํŠธ์›Œํฌ ํ™œ์šฉ
โ†’ ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ๋กœ์ปฌ ๋„คํŠธ์›Œํฌ์— ๋…ธ์ถœ
โ†’ ๋ชจ๋ฐ”์ผ ๋””๋ฐ”์ด์Šค์—์„œ ์ ‘์†

2. Remote Debugging
โ†’ Chrome DevTools: chrome://inspect
โ†’ Safari: ๊ฐœ๋ฐœ์ž ๋ฉ”๋‰ด ํ™œ์„ฑํ™”

3. ์ฃผ์š” ํ…Œ์ŠคํŠธ ํ•ญ๋ชฉ
โ†’ ํ„ฐ์น˜ ์ธํ„ฐ๋ž™์…˜
โ†’ ์Šคํฌ๋กค ์„ฑ๋Šฅ
โ†’ ํฐํŠธ ๋ Œ๋”๋ง
โ†’ ์ด๋ฏธ์ง€ ๋กœ๋”ฉ
โ†’ ํผ ์ž…๋ ฅ
// ๋กœ์ปฌ ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ๋„คํŠธ์›Œํฌ์— ๋…ธ์ถœ
// package.json
{
  "scripts": {
    "dev": "webpack-dev-server --host 0.0.0.0 --port 3000",
    "dev:mobile": "webpack-dev-server --host 0.0.0.0 --https"
  }
}

// ์ด์ œ ๊ฐ™์€ ๋„คํŠธ์›Œํฌ์˜ ๋ชจ๋ฐ”์ผ ๋””๋ฐ”์ด์Šค์—์„œ
// http://[๋‚ด-์ปดํ“จํ„ฐ-IP]:3000 ์œผ๋กœ ์ ‘์† ๊ฐ€๋Šฅ!

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™”์™€ ํ˜ธํ™˜์„ฑ์˜ ๊ท ํ˜•

ํ˜ธํ™˜์„ฑ์„ ํ™•๋ณดํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ์„ฑ๋Šฅ์„ ํฌ์ƒํ•ด์„œ๋Š” ์•ˆ ๋ผ! ๋˜‘๋˜‘ํ•˜๊ฒŒ ๊ท ํ˜•์„ ๋งž์ถ”๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž. โš–๏ธ

์กฐ๊ฑด๋ถ€ ํด๋ฆฌํ•„ ๋กœ๋”ฉ

๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ํด๋ฆฌํ•„์„ ์ œ๊ณตํ•  ํ•„์š”๋Š” ์—†์–ด. ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ € ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ํด๋ฆฌํ•„์„ ๋กœ๋“œํ•˜์ง€ ์•Š๋Š” ๊ฒŒ ํšจ์œจ์ ์ด์ง€!

// ์กฐ๊ฑด๋ถ€ ํด๋ฆฌํ•„ ๋กœ๋”ฉ ์ „๋žต
(function() {
  // ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค ์ฒดํฌ
  const needsPolyfills = (
    !window.Promise ||
    !window.fetch ||
    !Array.prototype.includes ||
    !Object.assign
  );

  if (needsPolyfills) {
    // ํด๋ฆฌํ•„ ๋™์  ๋กœ๋”ฉ
    const script = document.createElement('script');
    script.src = '/polyfills.bundle.js';
    script.async = false;
    document.head.appendChild(script);
    
    // ํด๋ฆฌํ•„ ๋กœ๋”ฉ ์™„๋ฃŒ ํ›„ ์•ฑ ์ดˆ๊ธฐํ™”
    script.onload = function() {
      initApp();
    };
  } else {
    // ํด๋ฆฌํ•„ ๋ถˆํ•„์š”, ๋ฐ”๋กœ ์•ฑ ์ดˆ๊ธฐํ™”
    initApp();
  }

  function initApp() {
    console.log('์•ฑ ์‹œ์ž‘!');
    // ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ์‹คํ–‰
  }
})();
Differential Loading

Differential Loading์€ ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์™€ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์— ๊ฐ๊ฐ ๋‹ค๋ฅธ ๋ฒˆ๋“ค์„ ์ œ๊ณตํ•˜๋Š” ์ „๋žต์ด์•ผ. Angular CLI๊ฐ€ ์ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์ง€!

<!-- ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์šฉ (ES2015+) -->
<script type="module" src="app.modern.js"></script>

<!-- ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์šฉ (ES5) -->
<script nomodule src="app.legacy.js"></script>

<!-- 
  type="module"์„ ์ง€์›ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €๋Š” modern.js๋งŒ ๋กœ๋“œ
  ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ธŒ๋ผ์šฐ์ €๋Š” legacy.js๋งŒ ๋กœ๋“œ
  โ†’ ๊ฐ ๋ธŒ๋ผ์šฐ์ €์— ์ตœ์ ํ™”๋œ ์ฝ”๋“œ ์ œ๊ณต!
-->

์ด ๋ฐฉ์‹์˜ ์žฅ์ ์€ ๋ญ˜๊นŒ? ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ € ์‚ฌ์šฉ์ž๋Š” ์ž‘๊ณ  ๋น ๋ฅธ ์ฝ”๋“œ๋ฅผ ๋ฐ›๊ณ , ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ € ์‚ฌ์šฉ์ž๋งŒ ํฐ ํด๋ฆฌํ•„ ๋ฒˆ๋“ค์„ ๋ฐ›๋Š” ๊ฑฐ์•ผ!

// Webpack ์„ค์ •์œผ๋กœ ๋‘ ๊ฐœ์˜ ๋ฒˆ๋“ค ์ƒ์„ฑ
// webpack.config.js
module.exports = [
  {
    // ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €์šฉ
    entry: './src/index.js',
    output: {
      filename: 'app.modern.js'
    },
    target: ['web', 'es2015'],
    module: {
      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: [
                ['@babel/preset-env', {
                  targets: { esmodules: true }
                }]
              ]
            }
          }
        }
      ]
    }
  },
  {
    // ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์šฉ
    entry: './src/index.js',
    output: {
      filename: 'app.legacy.js'
    },
    target: ['web', 'es5'],
    module: {
      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: [
                ['@babel/preset-env', {
                  targets: { ie: 11 }
                }]
              ]
            }
          }
        }
      ]
    }
  }
];
๋ฒˆ๋“ค ํฌ๊ธฐ ์ตœ์ ํ™”

ํด๋ฆฌํ•„์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฒˆ๋“ค ํฌ๊ธฐ๊ฐ€ ์ปค์ง€๋Š” ๊ฑด ํ”ผํ•  ์ˆ˜ ์—†์–ด. ํ•˜์ง€๋งŒ ์ตœ์†Œํ™”ํ•  ์ˆ˜๋Š” ์žˆ์ง€!

๐Ÿ“ฆ ๋ฒˆ๋“ค ํฌ๊ธฐ ์ค„์ด๊ธฐ ์ „๋žต 1. Tree Shaking
โ†’ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ฝ”๋“œ ์ œ๊ฑฐ
โ†’ ES6 ๋ชจ๋“ˆ ์‚ฌ์šฉ ํ•„์ˆ˜

2. Code Splitting
โ†’ ๋ผ์šฐํŠธ๋ณ„๋กœ ์ฝ”๋“œ ๋ถ„ํ• 
โ†’ ํ•„์š”ํ•  ๋•Œ๋งŒ ๋กœ๋“œ

3. ์„ ํƒ์  ํด๋ฆฌํ•„
โ†’ ์ „์ฒด core-js ๋Œ€์‹  ํ•„์š”ํ•œ ๊ฒƒ๋งŒ
โ†’ ์‚ฌ์šฉ ํ†ต๊ณ„ ๊ธฐ๋ฐ˜ ๊ฒฐ์ •

4. ์••์ถ•๊ณผ ์ตœ์†Œํ™”
โ†’ Terser๋กœ ์ฝ”๋“œ ์••์ถ•
โ†’ Gzip/Brotli ์••์ถ• ํ™œ์„ฑํ™”
// ๋ฒˆ๋“ค ๋ถ„์„์œผ๋กœ ์ตœ์ ํ™” ํฌ์ธํŠธ ์ฐพ๊ธฐ
// npm install --save-dev webpack-bundle-analyzer

// webpack.config.js
const BundleAnalyzerPlugin = 
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  // ... ๊ธฐํƒ€ ์„ค์ •
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
      reportFilename: 'bundle-report.html'
    })
  ]
};

// ๋นŒ๋“œ ํ›„ bundle-report.html์—์„œ 
// ์–ด๋–ค ํด๋ฆฌํ•„์ด ์šฉ๋Ÿ‰์„ ๋งŽ์ด ์ฐจ์ง€ํ•˜๋Š”์ง€ ํ™•์ธ!
์ง€์—ฐ ๋กœ๋”ฉ (Lazy Loading)

๋ชจ๋“  ํด๋ฆฌํ•„์„ ์ดˆ๊ธฐ์— ๋กœ๋“œํ•  ํ•„์š”๋Š” ์—†์–ด. ํ•„์š”ํ•œ ์‹œ์ ์— ๋™์ ์œผ๋กœ ๋กœ๋“œํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ์ „๋žต์ด์•ผ!

// ๋™์  ํด๋ฆฌํ•„ ๋กœ๋”ฉ ํ•จ์ˆ˜
async function loadPolyfillIfNeeded(feature, polyfillUrl) {
  // ๊ธฐ๋Šฅ ์ง€์› ์—ฌ๋ถ€ ํ™•์ธ
  if (!window[feature]) {
    console.log(`${feature} ํด๋ฆฌํ•„ ๋กœ๋”ฉ ์ค‘...`);
    
    // ๋™์ ์œผ๋กœ ์Šคํฌ๋ฆฝํŠธ ๋กœ๋“œ
    await new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = polyfillUrl;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
    
    console.log(`${feature} ํด๋ฆฌํ•„ ๋กœ๋“œ ์™„๋ฃŒ!`);
  }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ: IntersectionObserver๊ฐ€ ํ•„์š”ํ•œ ์‹œ์ ์— ๋กœ๋“œ
async function initLazyLoading() {
  await loadPolyfillIfNeeded(
    'IntersectionObserver',
    '/polyfills/intersection-observer.js'
  );
  
  // ์ด์ œ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  const observer = new IntersectionObserver(callback);
  observer.observe(element);
}

// ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ์ดˆ๊ธฐํ™”
let initialized = false;
window.addEventListener('scroll', function initOnce() {
  if (!initialized) {
    initialized = true;
    initLazyLoading();
    window.removeEventListener('scroll', initOnce);
  }
}, { passive: true });

๐ŸŽ“ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์ ์šฉ ์‚ฌ๋ก€

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์–ด๋–ป๊ฒŒ ์ ์šฉํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด์ž! ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์ด๋Ÿฐ ์ „๋žต๋“ค์ด ํ•„์ˆ˜์ ์ด๊ฑฐ๋“ . ๐Ÿ’ผ

์‚ฌ๋ก€ 1: E-์ปค๋จธ์Šค ์›น์‚ฌ์ดํŠธ

์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ์€ ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ์ž์ธต์„ ๋Œ€์ƒ์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์ด ์ •๋ง ์ค‘์š”ํ•ด!

// E-์ปค๋จธ์Šค ํ”„๋กœ์ ํŠธ์˜ ํด๋ฆฌํ•„ ์ „๋žต
// polyfills.js

// 1. ํ•„์ˆ˜ ํด๋ฆฌํ•„ (๋ชจ๋“  ์‚ฌ์šฉ์ž)
import 'core-js/stable';
import 'regenerator-runtime/runtime';

// 2. ๊ฒฐ์ œ ๊ด€๋ จ (์ค‘์š”๋„ ๋†’์Œ)
if (!window.Promise) {
  require('promise-polyfill/src/polyfill');
}

if (!window.fetch) {
  require('whatwg-fetch');
}

// 3. UI ๊ด€๋ จ (์‚ฌ์šฉ์ž ๊ฒฝํ—˜)
if (!('IntersectionObserver' in window)) {
  require('intersection-observer');
}

// 4. ํผ ๊ฒ€์ฆ
if (!Element.prototype.matches) {
  Element.prototype.matches = 
    Element.prototype.msMatchesSelector ||
    Element.prototype.webkitMatchesSelector;
}

// 5. ์ด๋ฏธ์ง€ ๋ ˆ์ด์ง€ ๋กœ๋”ฉ
class LazyImageLoader {
  constructor() {
    this.images = document.querySelectorAll('img[data-src]');
    this.init();
  }

  init() {
    if ('IntersectionObserver' in window) {
      this.observerInit();
    } else {
      this.fallbackInit();
    }
  }

  observerInit() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          this.loadImage(entry.target);
          observer.unobserve(entry.target);
        }
      });
    });

    this.images.forEach(img => observer.observe(img));
  }

  fallbackInit() {
    // ํด๋ฐฑ: ๋ชจ๋“  ์ด๋ฏธ์ง€ ์ฆ‰์‹œ ๋กœ๋“œ
    this.images.forEach(img => this.loadImage(img));
  }

  loadImage(img) {
    img.src = img.dataset.src;
    img.removeAttribute('data-src');
  }
}

// ์ดˆ๊ธฐํ™”
document.addEventListener('DOMContentLoaded', () => {
  new LazyImageLoader();
});
์‚ฌ๋ก€ 2: ๋Œ€์‹œ๋ณด๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

๊ด€๋ฆฌ์ž ๋Œ€์‹œ๋ณด๋“œ๋Š” ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”์™€ ์ธํ„ฐ๋ž™์…˜์ด ๋งŽ์•„์„œ ํ˜ธํ™˜์„ฑ ํ™•๋ณด๊ฐ€ ๊นŒ๋‹ค๋กœ์›Œ.

// ๋Œ€์‹œ๋ณด๋“œ ํ”„๋กœ์ ํŠธ ์„ค์ •
// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          browsers: [
            'last 2 Chrome versions',
            'last 2 Firefox versions',
            'last 2 Safari versions',
            'last 2 Edge versions'
          ]
        },
        useBuiltIns: 'usage',
        corejs: 3
      }
    ],
    '@babel/preset-react'
  ],
  plugins: [
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-proposal-optional-chaining',
    '@babel/plugin-proposal-nullish-coalescing-operator'
  ]
};

// ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜ธํ™˜์„ฑ ์ฒ˜๋ฆฌ
// ChartComponent.js
import React, { useEffect, useRef } from 'react';

function ChartComponent({ data }) {
  const chartRef = useRef(null);
  const chartInstance = useRef(null);

  useEffect(() => {
    // ResizeObserver ํด๋ฆฌํ•„ ์ฒดํฌ
    const initChart = async () => {
      if (!window.ResizeObserver) {
        await import('resize-observer-polyfill');
      }

      // ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋™์  ๋กœ๋“œ
      const Chart = await import('chart.js/auto');
      
      if (chartInstance.current) {
        chartInstance.current.destroy();
      }

      chartInstance.current = new Chart.default(
        chartRef.current,
        {
          type: 'line',
          data: data,
          options: {
            responsive: true,
            maintainAspectRatio: false
          }
        }
      );
    };

    initChart();

    return () => {
      if (chartInstance.current) {
        chartInstance.current.destroy();
      }
    };
  }, [data]);

  return (
    
); } export default ChartComponent;
์‚ฌ๋ก€ 3: ์žฌ๋Šฅ ๊ณต์œ  ํ”Œ๋žซํผ (์žฌ๋Šฅ๋„ท ์Šคํƒ€์ผ)

์žฌ๋Šฅ๋„ท์ฒ˜๋Ÿผ ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘์†ํ•˜๋Š” ํ”Œ๋žซํผ์—์„œ๋Š” ๊ด‘๋ฒ”์œ„ํ•œ ๋ธŒ๋ผ์šฐ์ € ์ง€์›์ด ํ•„์ˆ˜์•ผ!

// ์žฌ๋Šฅ ๊ณต์œ  ํ”Œ๋žซํผ์˜ ํ˜ธํ™˜์„ฑ ์ „๋žต
// compatibility-layer.js

class CompatibilityManager {
  constructor() {
    this.browserInfo = this.detectBrowser();
    this.initPolyfills();
    this.setupFallbacks();
  }

  detectBrowser() {
    const ua = navigator.userAgent;
    return {
      isIE: /MSIE|Trident/.test(ua),
      isEdge: /Edge/.test(ua),
      isChrome: /Chrome/.test(ua) && !/Edge/.test(ua),
      isFirefox: /Firefox/.test(ua),
      isSafari: /Safari/.test(ua) && !/Chrome/.test(ua),
      isMobile: /Mobile|Android|iPhone/.test(ua)
    };
  }

  async initPolyfills() {
    const polyfills = [];

    // Promise ํด๋ฆฌํ•„
    if (!window.Promise) {
      polyfills.push(import('promise-polyfill'));
    }

    // Fetch ํด๋ฆฌํ•„
    if (!window.fetch) {
      polyfills.push(import('whatwg-fetch'));
    }

    // IntersectionObserver ํด๋ฆฌํ•„
    if (!('IntersectionObserver' in window)) {
      polyfills.push(import('intersection-observer'));
    }

    // ๋ชจ๋“  ํด๋ฆฌํ•„ ๋กœ๋“œ ๋Œ€๊ธฐ
    await Promise.all(polyfills);
    console.log('ํด๋ฆฌํ•„ ๋กœ๋“œ ์™„๋ฃŒ');
  }

  setupFallbacks() {
    // CSS Grid ํด๋ฐฑ
    if (!this.supportsGrid()) {
      document.documentElement.classList.add('no-grid');
    }

    // Flexbox ํด๋ฐฑ
    if (!this.supportsFlexbox()) {
      document.documentElement.classList.add('no-flexbox');
    }

    // Custom Properties ํด๋ฐฑ
    if (!this.supportsCSSVariables()) {
      this.applyCSSVariablesFallback();
    }
  }

  supportsGrid() {
    return CSS.supports('display', 'grid');
  }

  supportsFlexbox() {
    return CSS.supports('display', 'flex');
  }

  supportsCSSVariables() {
    return window.CSS && 
           window.CSS.supports && 
           window.CSS.supports('(--test: 0)');
  }

  applyCSSVariablesFallback() {
    // CSS ๋ณ€์ˆ˜๋ฅผ ์ธ๋ผ์ธ ์Šคํƒ€์ผ๋กœ ๋ณ€ํ™˜
    const root = document.documentElement;
    const styles = getComputedStyle(root);
    
    const variables = {
      '--primary-color': '#6c5ce7',
      '--secondary-color': '#a29bfe',
      '--text-color': '#2d3436',
      '--background-color': '#ffffff',
      '--border-radius': '8px',
      '--spacing': '20px'
    };

    // ๋ชจ๋“  ์š”์†Œ์— ํด๋ฐฑ ์ ์šฉ
    document.querySelectorAll('[style*="var("]').forEach(el => {
      let style = el.getAttribute('style');
      Object.keys(variables).forEach(varName => {
        const regex = new RegExp(`var\\(${varName}\\)`, 'g');
        style = style.replace(regex, variables[varName]);
      });
      el.setAttribute('style', style);
    });
  }
}

// ์•ฑ ์ดˆ๊ธฐํ™” ์ „์— ํ˜ธํ™˜์„ฑ ๋ ˆ์ด์–ด ์„ค์ •
const compatManager = new CompatibilityManager();

// ํ˜ธํ™˜์„ฑ ์ฒดํฌ ์™„๋ฃŒ ํ›„ ์•ฑ ์‹œ์ž‘
compatManager.initPolyfills().then(() => {
  console.log('ํ˜ธํ™˜์„ฑ ๋ ˆ์ด์–ด ์ค€๋น„ ์™„๋ฃŒ');
  // ๋ฉ”์ธ ์•ฑ ์ดˆ๊ธฐํ™”
  initApp();
});

function initApp() {
  console.log('์žฌ๋Šฅ๋„ท ์•ฑ ์‹œ์ž‘!');
  // ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ์‹คํ–‰
}
๐Ÿ’ก ์žฌ๋Šฅ๋„ท์—์„œ์˜ ์‹ค์ „ ํŒ ์žฌ๋Šฅ ๊ณต์œ  ํ”Œ๋žซํผ์—์„œ๋Š” ๋‹ค์–‘ํ•œ ์—ฐ๋ น๋Œ€์™€ ๊ธฐ์ˆ  ์ˆ˜์ค€์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘์†ํ•ด. ํŠนํžˆ ๋ชจ๋ฐ”์ผ ์‚ฌ์šฉ์ž ๋น„์œจ์ด ๋†’๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋ฐ”์ผ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์— ๋” ์‹ ๊ฒฝ ์จ์•ผ ํ•ด!

ํ•ต์‹ฌ ํฌ์ธํŠธ:
โ€ข ๊ฒฐ์ œ ํ”„๋กœ์„ธ์Šค๋Š” 100% ํ˜ธํ™˜์„ฑ ํ•„์ˆ˜
โ€ข ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๊ธฐ๋Šฅ์˜ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ…Œ์ŠคํŠธ
โ€ข ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ๊ธฐ๋Šฅ์˜ WebSocket ํด๋ฐฑ
โ€ข ๋ฐ˜์‘ํ˜• ๋””์ž์ธ์˜ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ € ๊ฒ€์ฆ

๐Ÿš€ ์ตœ์‹  ํŠธ๋ Œ๋“œ์™€ ๋ฏธ๋ž˜ ์ „๋ง

์›น ๊ธฐ์ˆ ์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์•ž์œผ๋กœ์˜ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์€ ์–ด๋–ป๊ฒŒ ๋ณ€ํ• ๊นŒ? ๐Ÿ”ฎ

Evergreen Browsers์˜ ์‹œ๋Œ€

์š”์ฆ˜ ๋Œ€๋ถ€๋ถ„์˜ ๋ธŒ๋ผ์šฐ์ €๋Š” ์ž๋™ ์—…๋ฐ์ดํŠธ๋˜๋Š” "Evergreen" ๋ธŒ๋ผ์šฐ์ €์•ผ. ์ด๊ฒŒ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๋ฅผ ๋งŽ์ด ์ค„์—ฌ์ฃผ๊ณ  ์žˆ์ง€!

๐ŸŒฑ Evergreen Browsers์˜ ์žฅ์  โ€ข ์ž๋™ ์—…๋ฐ์ดํŠธ๋กœ ์ตœ์‹  ํ‘œ์ค€ ๋น ๋ฅด๊ฒŒ ์ ์šฉ
โ€ข ๋ณด์•ˆ ํŒจ์น˜ ์‹ ์†ํ•œ ๋ฐฐํฌ
โ€ข ๊ฐœ๋ฐœ์ž๋Š” ์ตœ์‹  ๊ธฐ๋Šฅ ๋” ๋นจ๋ฆฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
โ€ข ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ € ์ง€์› ๋ถ€๋‹ด ๊ฐ์†Œ

์ฃผ์š” Evergreen Browsers:
Chrome, Firefox, Edge, Safari (iOS ์ œ์™ธ)
WebAssembly์˜ ๋ถ€์ƒ

WebAssembly(Wasm)๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋„ค์ดํ‹ฐ๋ธŒ์— ๊ฐ€๊นŒ์šด ์„ฑ๋Šฅ์„ ์ œ๊ณตํ•ด. ํ˜ธํ™˜์„ฑ ์ธก๋ฉด์—์„œ๋„ ํฅ๋ฏธ๋กœ์šด ๋ณ€ํ™”๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ์–ด!

// WebAssembly ๊ธฐ๋ณธ ์‚ฌ์šฉ ์˜ˆ์‹œ
async function loadWasm() {
  // .wasm ํŒŒ์ผ ๋กœ๋“œ
  const response = await fetch('module.wasm');
  const buffer = await response.arrayBuffer();
  
  // WebAssembly ๋ชจ๋“ˆ ์ปดํŒŒ์ผ ๋ฐ ์ธ์Šคํ„ด์Šคํ™”
  const module = await WebAssembly.compile(buffer);
  const instance = await WebAssembly.instantiate(module);
  
  // Wasm ํ•จ์ˆ˜ ํ˜ธ์ถœ
  const result = instance.exports.calculate(10, 20);
  console.log('๊ฒฐ๊ณผ:', result);
}

// WebAssembly ์ง€์› ํ™•์ธ
if (typeof WebAssembly === 'object') {
  loadWasm();
} else {
  console.log('WebAssembly ๋ฏธ์ง€์› ๋ธŒ๋ผ์šฐ์ €');
  // JavaScript ํด๋ฐฑ ์‚ฌ์šฉ
}
Progressive Enhancement์˜ ์žฌ์กฐ๋ช…

Progressive Enhancement๋Š” ๊ธฐ๋ณธ ๊ธฐ๋Šฅ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์ง„์ ์œผ๋กœ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ์ „๋žต์ด์•ผ. ์ตœ๊ทผ ๋‹ค์‹œ ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ์ง€!

// Progressive Enhancement ์˜ˆ์‹œ
// 1๋‹จ๊ณ„: ๊ธฐ๋ณธ HTML (๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €)
<form action="/submit" method="POST">
  <input type="text" name="username" required>
  <button type="submit">์ œ์ถœ</button>
</form>

// 2๋‹จ๊ณ„: CSS ํ–ฅ์ƒ (CSS ์ง€์› ๋ธŒ๋ผ์šฐ์ €)
// ๊ธฐ๋ณธ ์Šคํƒ€์ผ ์ ์šฉ, ๋ฐ˜์‘ํ˜• ๋””์ž์ธ

// 3๋‹จ๊ณ„: JavaScript ํ–ฅ์ƒ (JS ์ง€์› ๋ธŒ๋ผ์šฐ์ €)
document.querySelector('form').addEventListener('submit', async (e) => {
  e.preventDefault();
  
  // AJAX๋กœ ์ œ์ถœ (fetch ์ง€์› ์‹œ)
  if (window.fetch) {
    const formData = new FormData(e.target);
    const response = await fetch('/submit', {
      method: 'POST',
      body: formData
    });
    
    if (response.ok) {
      showSuccessMessage();
    }
  } else {
    // ํด๋ฐฑ: ์ผ๋ฐ˜ ํผ ์ œ์ถœ
    e.target.submit();
  }
});

// 4๋‹จ๊ณ„: ์ตœ์‹  ๊ธฐ๋Šฅ ํ™œ์šฉ (์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €)
// Web Animations API, IntersectionObserver ๋“ฑ
Container Queries์˜ ๋“ฑ์žฅ

Container Queries๋Š” CSS์˜ ๊ฒŒ์ž„ ์ฒด์ธ์ €์•ผ! ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ ํฌ๊ธฐ์— ๋”ฐ๋ผ ์Šคํƒ€์ผ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์–ด.

/* Container Queries ์˜ˆ์‹œ */
.card-container {
  container-type: inline-size;
  container-name: card;
}

.card {
  padding: 1rem;
}

/* ์ปจํ…Œ์ด๋„ˆ๊ฐ€ 400px ์ด์ƒ์ผ ๋•Œ */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: 2rem;
  }
  
  .card-image {
    aspect-ratio: 1;
  }
}

/* ํด๋ฐฑ: @supports๋กœ ์ง€์› ์—ฌ๋ถ€ ํ™•์ธ */
@supports not (container-type: inline-size) {
  /* ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ ํด๋ฐฑ */
  @media (min-width: 400px) {
    .card {
      display: grid;
      grid-template-columns: 1fr 2fr;
    }
  }
}
โš ๏ธ ์ฃผ์˜: ์ตœ์‹  ๊ธฐ๋Šฅ ์‚ฌ์šฉ ์‹œ Container Queries ๊ฐ™์€ ์ตœ์‹  ๊ธฐ๋Šฅ์€ ์•„์ง ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›ํ•˜์ง€ ์•Š์•„. ํ•ญ์ƒ ํด๋ฐฑ์„ ์ค€๋น„ํ•˜๊ณ , Can I Use์—์„œ ์ง€์› ํ˜„ํ™ฉ์„ ํ™•์ธํ•˜์ž!
AI ๊ธฐ๋ฐ˜ ํ˜ธํ™˜์„ฑ ํ…Œ์ŠคํŠธ

AI์™€ ๋จธ์‹ ๋Ÿฌ๋‹์ด ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ…Œ์ŠคํŠธ์—๋„ ํ™œ์šฉ๋˜๊ณ  ์žˆ์–ด. ์ž๋™์œผ๋กœ ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ํ•ด๊ฒฐ์ฑ…์„ ์ œ์•ˆํ•˜๋Š” ๋„๊ตฌ๋“ค์ด ๋‚˜์˜ค๊ณ  ์žˆ์ง€!

๐Ÿค– AI ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋“ค Applitools
โ†’ ์‹œ๊ฐ์  AI ํ…Œ์ŠคํŒ…
โ†’ ๋ ˆ์ด์•„์›ƒ ์ฐจ์ด ์ž๋™ ๊ฐ์ง€

Testim
โ†’ AI ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ ์ž๋™ํ™”
โ†’ ์ž๊ฐ€ ์น˜์œ  ํ…Œ์ŠคํŠธ

Mabl
โ†’ ๋จธ์‹ ๋Ÿฌ๋‹ ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ
โ†’ ์ž๋™ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ

๐Ÿ“š ์ฒดํฌ๋ฆฌ์ŠคํŠธ: ์™„๋ฒฝํ•œ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ

ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์—, ๊ทธ๋ฆฌ๊ณ  ๋ฐฐํฌํ•˜๊ธฐ ์ „์— ๊ผญ ํ™•์ธํ•ด์•ผ ํ•  ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ์ •๋ฆฌํ•ด๋ดค์–ด! โœ…

๐ŸŽฏ ๊ฐœ๋ฐœ ์‹œ์ž‘ ์ „ ์ฒดํฌ๋ฆฌ์ŠคํŠธ 1. ํƒ€๊ฒŸ ๋ธŒ๋ผ์šฐ์ € ์ •์˜
โ–ก ์ง€์›ํ•  ๋ธŒ๋ผ์šฐ์ € ๋ชฉ๋ก ์ž‘์„ฑ
โ–ก ์ตœ์†Œ ์ง€์› ๋ฒ„์ „ ๊ฒฐ์ •
โ–ก ์‹œ์žฅ ์ ์œ ์œจ ๋ถ„์„
โ–ก ์‚ฌ์šฉ์ž ํ†ต๊ณ„ ํ™•์ธ

2. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •
โ–ก Babel ์„ค์ • ์™„๋ฃŒ
โ–ก Autoprefixer ์„ค์ •
โ–ก ํด๋ฆฌํ•„ ์ „๋žต ์ˆ˜๋ฆฝ
โ–ก ๋ฒˆ๋“ค๋Ÿฌ ์„ค์ • (Webpack/Vite)

3. ์ฝ”๋“œ ํ’ˆ์งˆ ๋„๊ตฌ
โ–ก ESLint + compat ํ”Œ๋Ÿฌ๊ทธ์ธ
โ–ก Stylelint ์„ค์ •
โ–ก Prettier ์„ค์ •
โ–ก Git hooks ์„ค์ •

4. ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ
โ–ก ๋กœ์ปฌ ํ…Œ์ŠคํŠธ ๋ธŒ๋ผ์šฐ์ € ์„ค์น˜
โ–ก ํด๋ผ์šฐ๋“œ ํ…Œ์ŠคํŠธ ์„œ๋น„์Šค ์„ ํƒ
โ–ก ์‹ค์ œ ๋””๋ฐ”์ด์Šค ํ™•๋ณด
โ–ก CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•
๐Ÿš€ ๋ฐฐํฌ ์ „ ์ฒดํฌ๋ฆฌ์ŠคํŠธ 1. ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ
โ–ก ๋ชจ๋“  ์ธํ„ฐ๋ž™์…˜ ๋™์ž‘ ํ™•์ธ
โ–ก ํผ ์ œ์ถœ ๋ฐ ๊ฒ€์ฆ
โ–ก AJAX ์š”์ฒญ ์ฒ˜๋ฆฌ
โ–ก ์—๋Ÿฌ ํ•ธ๋“ค๋ง

2. ์‹œ๊ฐ์  ํ…Œ์ŠคํŠธ
โ–ก ๋ ˆ์ด์•„์›ƒ ์ผ๊ด€์„ฑ
โ–ก ํฐํŠธ ๋ Œ๋”๋ง
โ–ก ์ด๋ฏธ์ง€ ํ‘œ์‹œ
โ–ก ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋™์ž‘

3. ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ
โ–ก ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ๊ฐ„
โ–ก ๋ฒˆ๋“ค ํฌ๊ธฐ ํ™•์ธ
โ–ก ์ด๋ฏธ์ง€ ์ตœ์ ํ™”
โ–ก ์บ์‹ฑ ์ „๋žต

4. ์ ‘๊ทผ์„ฑ ํ…Œ์ŠคํŠธ
โ–ก ํ‚ค๋ณด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜
โ–ก ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ํ˜ธํ™˜์„ฑ
โ–ก ์ƒ‰์ƒ ๋Œ€๋น„
โ–ก ARIA ์†์„ฑ

5. ๋ชจ๋ฐ”์ผ ํ…Œ์ŠคํŠธ
โ–ก ํ„ฐ์น˜ ์ธํ„ฐ๋ž™์…˜
โ–ก ์Šคํฌ๋กค ์„ฑ๋Šฅ
โ–ก ๋ทฐํฌํŠธ ์„ค์ •
โ–ก ๋ชจ๋ฐ”์ผ Safari ํŠน์ด์‚ฌํ•ญ
// ์ž๋™ํ™”๋œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ
// check-compatibility.js

const checklist = {
  polyfills: [
    { name: 'Promise', check: () => typeof Promise !== 'undefined' },
    { name: 'fetch', check: () => typeof fetch !== 'undefined' },
    { name: 'IntersectionObserver', check: () => 'IntersectionObserver' in window },
    { name: 'Array.includes', check: () => Array.prototype.includes !== undefined }
  ],
  
  cssFeatures: [
    { name: 'Flexbox', check: () => CSS.supports('display', 'flex') },
    { name: 'Grid', check: () => CSS.supports('display', 'grid') },
    { name: 'CSS Variables', check: () => CSS.supports('(--test: 0)') },
    { name: 'gap', check: () => CSS.supports('gap', '1rem') }
  ],
  
  apis: [
    { name: 'localStorage', check: () => typeof localStorage !== 'undefined' },
    { name: 'sessionStorage', check: () => typeof sessionStorage !== 'undefined' },
    { name: 'WebSocket', check: () => typeof WebSocket !== 'undefined' },
    { name: 'Geolocation', check: () => 'geolocation' in navigator }
  ]
};

function runCompatibilityCheck() {
  console.log('๐Ÿ” ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ์ฒดํฌ ์‹œ์ž‘...\n');
  
  let allPassed = true;
  
  Object.keys(checklist).forEach(category => {
    console.log(`\n๐Ÿ“‹ ${category.toUpperCase()}`);
    
    checklist[category].forEach(item => {
      const passed = item.check();
      const icon = passed ? 'โœ…' : 'โŒ';
      console.log(`${icon} ${item.name}: ${passed ? '์ง€์›' : '๋ฏธ์ง€์›'}`);
      
      if (!passed) allPassed = false;
    });
  });
  
  console.log('\n' + '='.repeat(50));
  if (allPassed) {
    console.log('โœจ ๋ชจ๋“  ๊ธฐ๋Šฅ์ด ์ง€์›๋ฉ๋‹ˆ๋‹ค!');
  } else {
    console.log('โš ๏ธ ์ผ๋ถ€ ๊ธฐ๋Šฅ์ด ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํด๋ฆฌํ•„์„ ํ™•์ธํ•˜์„ธ์š”.');
  }
  
  return allPassed;
}

// ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ž๋™ ์‹คํ–‰
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', runCompatibilityCheck);
} else {
  runCompatibilityCheck();
}

๐Ÿ’ก ์‹ค๋ฌด ํŒ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

๋งˆ์ง€๋ง‰์œผ๋กœ, ์‹ค๋ฌด์—์„œ ์ •๋ง ์œ ์šฉํ•œ ํŒ๋“ค์„ ๊ณต์œ ํ• ๊ฒŒ! ์ด๊ฑด ๊ฒฝํ—˜์—์„œ ๋‚˜์˜จ ์ง„์งœ ๋…ธํ•˜์šฐ๋“ค์ด์•ผ. ๐ŸŽ“

ํŒ 1: ์ ์ง„์  ๊ธฐ๋Šฅ ์ถ”๊ฐ€

์ฒ˜์Œ๋ถ€ํ„ฐ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›ํ•˜๋ ค๊ณ  ํ•˜์ง€ ๋งˆ. ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์ง„์ ์œผ๋กœ ํ™•์žฅํ•˜๋Š” ๊ฒŒ ํ˜„๋ช…ํ•ด!

// ์ ์ง„์  ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ํŒจํ„ด
class FeatureManager {
  constructor() {
    this.features = {
      core: true,        // ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €
      enhanced: false,   // ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ €
      experimental: false // ์‹คํ—˜์  ๊ธฐ๋Šฅ
    };
    
    this.detectCapabilities();
  }

  detectCapabilities() {
    // Enhanced ๊ธฐ๋Šฅ ๊ฐ์ง€
    this.features.enhanced = (
      'IntersectionObserver' in window &&
      CSS.supports('display', 'grid') &&
      typeof Promise !== 'undefined'
    );

    // Experimental ๊ธฐ๋Šฅ ๊ฐ์ง€
    this.features.experimental = (
      'ResizeObserver' in window &&
      CSS.supports('container-type', 'inline-size') &&
      'requestIdleCallback' in window
    );
  }

  enableFeature(level) {
    switch(level) {
      case 'core':
        this.initCoreFeatures();
        break;
      case 'enhanced':
        if (this.features.enhanced) {
          this.initEnhancedFeatures();
        }
        break;
      case 'experimental':
        if (this.features.experimental) {
          this.initExperimentalFeatures();
        }
        break;
    }
  }

  initCoreFeatures() {
    console.log('ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์ดˆ๊ธฐํ™”');
    // ๊ธฐ๋ณธ ๊ธฐ๋Šฅ๋งŒ ์‚ฌ์šฉ
  }

  initEnhancedFeatures() {
    console.log('ํ–ฅ์ƒ๋œ ๊ธฐ๋Šฅ ์ดˆ๊ธฐํ™”');
    // ์ตœ์‹  API ํ™œ์šฉ
  }

  initExperimentalFeatures() {
    console.log('์‹คํ—˜์  ๊ธฐ๋Šฅ ์ดˆ๊ธฐํ™”');
    // ์ตœ์ฒจ๋‹จ ๊ธฐ๋Šฅ ํ™œ์šฉ
  }
}

const featureManager = new FeatureManager();
featureManager.enableFeature('core');
featureManager.enableFeature('enhanced');
featureManager.enableFeature('experimental');
ํŒ 2: ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ ์„ค์ •

ํด๋ฆฌํ•„์ด ์ œ๋Œ€๋กœ ๋กœ๋“œ๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด. ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜์ž!

// ์ „์—ญ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ
class ErrorBoundary {
  constructor() {
    this.setupErrorHandlers();
  }

  setupErrorHandlers() {
    // JavaScript ์—๋Ÿฌ ์บ์น˜
    window.addEventListener('error', (event) => {
      this.handleError({
        type: 'JavaScript Error',
        message: event.message,
        filename: event.filename,
        line: event.lineno,
        column: event.colno,
        error: event.error
      });
    });

    // Promise rejection ์บ์น˜
    window.addEventListener('unhandledrejection', (event) => {
      this.handleError({
        type: 'Unhandled Promise Rejection',
        message: event.reason,
        promise: event.promise
      });
    });

    // ๋ฆฌ์†Œ์Šค ๋กœ๋”ฉ ์—๋Ÿฌ ์บ์น˜
    window.addEventListener('error', (event) => {
      if (event.target !== window) {
        this.handleResourceError(event);
      }
    }, true);
  }

  handleError(errorInfo) {
    console.error('์—๋Ÿฌ ๋ฐœ์ƒ:', errorInfo);
    
    // ์‚ฌ์šฉ์ž์—๊ฒŒ ์นœํ™”์ ์ธ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
    this.showUserFriendlyMessage();
    
    // ์—๋Ÿฌ ๋กœ๊น… (์„œ๋ฒ„๋กœ ์ „์†ก)
    this.logError(errorInfo);
  }

  handleResourceError(event) {
    const target = event.target;
    console.error('๋ฆฌ์†Œ์Šค ๋กœ๋”ฉ ์‹คํŒจ:', target.src || target.href);
    
    // ํด๋ฆฌํ•„ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ ํด๋ฐฑ
    if (target.src && target.src.includes('polyfill')) {
      this.loadFallbackPolyfills();
    }
  }

  showUserFriendlyMessage() {
    // ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ค„ ๋ฉ”์‹œ์ง€
    const message = document.createElement('div');
    message.className = 'error-message';
    message.innerHTML = `
      

์ผ์‹œ์ ์ธ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ด์ฃผ์„ธ์š”.

`; document.body.appendChild(message); } async loadFallbackPolyfills() { console.log('ํด๋ฐฑ ํด๋ฆฌํ•„ ๋กœ๋”ฉ ์ค‘...'); // CDN ํด๋ฐฑ ๋˜๋Š” ๋กœ์ปฌ ํด๋ฆฌํ•„ ๋กœ๋“œ } logError(errorInfo) { // ์—๋Ÿฌ ์ •๋ณด๋ฅผ ์„œ๋ฒ„๋กœ ์ „์†ก if (navigator.sendBeacon) { const data = JSON.stringify({ ...errorInfo, userAgent: navigator.userAgent, timestamp: new Date().toISOString(), url: window.location.href }); navigator.sendBeacon('/api/log-error', data); } } } // ์—๋Ÿฌ ๋ฐ”์šด๋”๋ฆฌ ์ดˆ๊ธฐํ™” new ErrorBoundary();
ํŒ 3: ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง

ํด๋ฆฌํ•„์ด ์„ฑ๋Šฅ์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ด!

// ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ์œ ํ‹ธ๋ฆฌํ‹ฐ
class PerformanceMonitor {
  constructor() {
    this.metrics = {};
    this.init();
  }

  init() {
    if ('PerformanceObserver' in window) {
      this.setupPerformanceObserver();
    } else {
      this.setupFallbackMonitoring();
    }
  }

  setupPerformanceObserver() {
    // ๋ฆฌ์†Œ์Šค ๋กœ๋”ฉ ๋ชจ๋‹ˆํ„ฐ๋ง
    const resourceObserver = new PerformanceObserver((list) => {
      list.getEntries().forEach(entry => {
        if (entry.name.includes('polyfill')) {
          this.metrics.polyfillLoadTime = entry.duration;
          console.log(`ํด๋ฆฌํ•„ ๋กœ๋“œ ์‹œ๊ฐ„: ${entry.duration}ms`);
        }
      });
    });
    
    resourceObserver.observe({ entryTypes: ['resource'] });

    // ํŽ˜์ธํŠธ ํƒ€์ด๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง
    const paintObserver = new PerformanceObserver((list) => {
      list.getEntries().forEach(entry => {
        this.metrics[entry.name] = entry.startTime;
        console.log(`${entry.name}: ${entry.startTime}ms`);
      });
    });
    
    paintObserver.observe({ entryTypes: ['paint'] });
  }

  setupFallbackMonitoring() {
    // ํด๋ฐฑ: ๊ธฐ๋ณธ ํƒ€์ด๋ฐ API ์‚ฌ์šฉ
    window.addEventListener('load', () => {
      const timing = performance.timing;
      this.metrics.pageLoadTime = 
        timing.loadEventEnd - timing.navigationStart;
      
      console.log(`ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ๊ฐ„: ${this.metrics.pageLoadTime}ms`);
    });
  }

  measureFunction(name, fn) {
    const start = performance.now();
    const result = fn();
    const end = performance.now();
    
    this.metrics[name] = end - start;
    console.log(`${name} ์‹คํ–‰ ์‹œ๊ฐ„: ${end - start}ms`);
    
    return result;
  }

  getMetrics() {
    return this.metrics;
  }

  sendMetrics() {
    // ๋ฉ”ํŠธ๋ฆญ์„ ๋ถ„์„ ์„œ๋ฒ„๋กœ ์ „์†ก
    if (navigator.sendBeacon) {
      const data = JSON.stringify({
        metrics: this.metrics,
        userAgent: navigator.userAgent,
        timestamp: new Date().toISOString()
      });
      
      navigator.sendBeacon('/api/metrics', data);
    }
  }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const monitor = new PerformanceMonitor();

// ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
monitor.measureFunction('dataProcessing', () => {
  // ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋กœ์ง
  return processData();
});

// ํŽ˜์ด์ง€ ์–ธ๋กœ๋“œ ์‹œ ๋ฉ”ํŠธ๋ฆญ ์ „์†ก
window.addEventListener('beforeunload', () => {
  monitor.sendMetrics();
});
ํŒ 4: ๋ฌธ์„œํ™”๋Š” ํ•„์ˆ˜!

์–ด๋–ค ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ง€์›ํ•˜๊ณ , ์–ด๋–ค ํด๋ฆฌํ•„์„ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๋ฌธ์„œํ™”ํ•˜๋Š” ๊ฒŒ ์ •๋ง ์ค‘์š”ํ•ด. ํŒ€์›๋“ค๊ณผ ๋ฏธ๋ž˜์˜ ๋‚˜๋ฅผ ์œ„ํ•ด์„œ!

// BROWSER-SUPPORT.md

# ๋ธŒ๋ผ์šฐ์ € ์ง€์› ์ •์ฑ…

## ์ง€์› ๋ธŒ๋ผ์šฐ์ €

### ๋ฐ์Šคํฌํ†ฑ
- Chrome: ์ตœ์‹  2๊ฐœ ๋ฒ„์ „
- Firefox: ์ตœ์‹  2๊ฐœ ๋ฒ„์ „
- Safari: ์ตœ์‹  2๊ฐœ ๋ฒ„์ „
- Edge: ์ตœ์‹  2๊ฐœ ๋ฒ„์ „

### ๋ชจ๋ฐ”์ผ
- iOS Safari: 12.0 ์ด์ƒ
- Chrome Android: ์ตœ์‹  2๊ฐœ ๋ฒ„์ „
- Samsung Internet: ์ตœ์‹  2๊ฐœ ๋ฒ„์ „

### ์ œํ•œ์  ์ง€์›
- IE 11: ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋งŒ ์ง€์›

## ์‚ฌ์šฉ ์ค‘์ธ ํด๋ฆฌํ•„

### JavaScript
- `core-js`: ES6+ ๊ธฐ๋Šฅ ํด๋ฆฌํ•„
- `whatwg-fetch`: Fetch API
- `promise-polyfill`: Promise
- `intersection-observer`: IntersectionObserver

### CSS
- `autoprefixer`: ๋ฒค๋” ํ”„๋ฆฌํ”ฝ์Šค ์ž๋™ ์ถ”๊ฐ€
- `postcss-preset-env`: ์ตœ์‹  CSS ๊ธฐ๋Šฅ

## ์•Œ๋ ค์ง„ ์ œํ•œ์‚ฌํ•ญ

### IE 11
- CSS Grid ๋ฏธ์ง€์› (Flexbox ํด๋ฐฑ ์‚ฌ์šฉ)
- CSS ๋ณ€์ˆ˜ ๋ฏธ์ง€์› (ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฐ’ ์‚ฌ์šฉ)
- Smooth scroll ๋ฏธ์ง€์›

### Safari < 14
- Flexbox gap ๋ฏธ์ง€์› (margin ํด๋ฐฑ)
- :has() ์„ ํƒ์ž ๋ฏธ์ง€์›

## ํ…Œ์ŠคํŠธ ์ „๋žต

1. ์ž๋™ํ™” ํ…Œ์ŠคํŠธ: Karma + ๋‹ค์ค‘ ๋ธŒ๋ผ์šฐ์ €
2. ์ˆ˜๋™ ํ…Œ์ŠคํŠธ: BrowserStack
3. ์‹ค์ œ ๋””๋ฐ”์ด์Šค: iPhone, Android ํฐ

## ์—…๋ฐ์ดํŠธ ์ •์ฑ…

- ๋ถ„๊ธฐ๋ณ„ ์ง€์› ๋ธŒ๋ผ์šฐ์ € ๋ชฉ๋ก ๊ฒ€ํ† 
- ์‹œ์žฅ ์ ์œ ์œจ 1% ๋ฏธ๋งŒ ๋ธŒ๋ผ์šฐ์ €๋Š” ์ง€์› ์ค‘๋‹จ ๊ณ ๋ ค
๐Ÿ’ก ๋ฌธ์„œํ™” ํŒ README.md์— ๋ธŒ๋ผ์šฐ์ € ์ง€์› ๋ฐฐ์ง€๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ํ•œ๋ˆˆ์— ์•Œ์•„๋ณด๊ธฐ ์‰ฌ์›Œ!

์˜ˆ์‹œ:
![Chrome](https://img.shields.io/badge/Chrome-latest%202-green)
![Firefox](https://img.shields.io/badge/Firefox-latest%202-green)
![Safari](https://img.shields.io/badge/Safari-latest%202-green)

๐ŸŽฌ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์–ด. ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ๊ณผ ํด๋ฆฌํ•„์— ๋Œ€ํ•ด ์ •๋ง ๋งŽ์€ ๊ฑธ ๋ฐฐ์› ์ง€? ๐Ÿ˜Š

ํ•ต์‹ฌ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ:

๐ŸŽฏ ํ•ต์‹ฌ ์š”์•ฝ 1. ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์€ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜
โ†’ ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ์ž ํ™˜๊ฒฝ ๊ณ ๋ ค
โ†’ ๋น„์ฆˆ๋‹ˆ์Šค ๊ธฐํšŒ ํ™•๋Œ€

2. ํด๋ฆฌํ•„์€ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ
โ†’ ๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €์— ์ตœ์‹  ๊ธฐ๋Šฅ ์ œ๊ณต
โ†’ ์„ ํƒ์  ๋กœ๋”ฉ์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”

3. ํ…Œ์ŠคํŠธ๋Š” ํ•„์ˆ˜
โ†’ ์ž๋™ํ™” + ์ˆ˜๋™ ํ…Œ์ŠคํŠธ
โ†’ ์‹ค์ œ ๋””๋ฐ”์ด์Šค ๊ฒ€์ฆ

4. ์„ฑ๋Šฅ๊ณผ ํ˜ธํ™˜์„ฑ์˜ ๊ท ํ˜•
โ†’ ์กฐ๊ฑด๋ถ€ ๋กœ๋”ฉ
โ†’ Differential Loading

5. ์ง€์†์ ์ธ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์—…๋ฐ์ดํŠธ
โ†’ ๋ธŒ๋ผ์šฐ์ € ํŠธ๋ Œ๋“œ ์ถ”์ 
โ†’ ์ •๊ธฐ์ ์ธ ํ˜ธํ™˜์„ฑ ๊ฒ€ํ† 

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์›น ๊ฐœ๋ฐœ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๋ฐฐ์šธ ๋•Œ๋„, ์ด๋Ÿฐ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ์ง€์‹์€ ์ •๋ง ์ค‘์š”ํ•ด. ์‹ค์ œ๋กœ ํด๋ผ์ด์–ธํŠธ๋“ค์ด ๊ฐ€์žฅ ๋งŽ์ด ์š”๊ตฌํ•˜๋Š” ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜๊ฑฐ๋“ !

๊ธฐ์–ตํ•ด! ์™„๋ฒฝํ•œ ํ˜ธํ™˜์„ฑ์€ ํ•˜๋ฃจ์•„์นจ์— ์ด๋ค„์ง€์ง€ ์•Š์•„. ์ ์ง„์ ์œผ๋กœ ๊ฐœ์„ ํ•˜๊ณ , ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ๋“ฃ๊ณ , ๊ณ„์† ํ•™์Šตํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ๐Ÿš€

๐ŸŒŸ ๋งˆ์ง€๋ง‰ ์กฐ์–ธ ์›น ๊ฐœ๋ฐœ์€ ๋Š์ž„์—†์ด ๋ณ€ํ™”ํ•˜๋Š” ๋ถ„์•ผ์•ผ. ์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ๋„ ๋ช‡ ๋…„ ํ›„์—๋Š” ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ๊ธฐ๋ณธ ์›์น™๊ณผ ๋ฌธ์ œ ํ•ด๊ฒฐ ์ ‘๊ทผ๋ฒ•์€ ๋ณ€ํ•˜์ง€ ์•Š์•„. ๊ณ„์† ๋ฐฐ์šฐ๊ณ , ์‹คํ—˜ํ•˜๊ณ , ๊ณต์œ ํ•˜์ž!

์žฌ๋Šฅ๋„ท์˜ '์ง€์‹์ธ์˜ ์ˆฒ'์—์„œ ๋” ๋งŽ์€ ์›น ๊ฐœ๋ฐœ ์ง€์‹์„ ํƒํ—˜ํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ถ”์ฒœํ•ด! ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ๋˜์ž! ๐Ÿ’ช
Happy Coding! ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์™„๋ฒฝํ•˜๊ฒŒ

์ด ๊ธ€์ด ๋„์›€์ด ๋˜์—ˆ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ๋” ๋งŽ์€ ์›น ๊ฐœ๋ฐœ ์ง€์‹์„ ํƒํ—˜ํ•ด๋ณด์„ธ์š”! ๐ŸŒŸ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.