Ver3.0 ๐ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ๊ณผ ํด๋ฆฌํ์ ํ์ฉํ ๊ด๋ฒ์ํ ์ง์ ์ ๋ต ์๋ฒฝ ๊ฐ์ด๋

๐ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ๊ณผ ํด๋ฆฌํ์ ํ์ฉํ ๊ด๋ฒ์ํ ์ง์ ์ ๋ต ์๋ฒฝ ๊ฐ์ด๋
๋ชจ๋ ๋ธ๋ผ์ฐ์ ์์ ์๋ฒฝํ๊ฒ ์๋ํ๋ ์น์ ๋ง๋๋ ์ค์ ๋ ธํ์ฐ
๐ฏ ์ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ์ด ์ค์ํ ๊น?
์น๊ตฌ์ผ, ์์ํด๋ด. ๋๊ฐ ๋ฉฐ์น ๋ฐค์ ์์์ ์์ฑํ ๋ฉ์ง ์น์ฌ์ดํธ๊ฐ ์์ด. Chrome์์๋ ์๋ฒฝํ๊ฒ ์๋ํ๋๋ฐ, ๊ฐ์๊ธฐ ๊ณ ๊ฐ์ด ์ ํ๋ฅผ ๊ฑธ์ด์์ "Safari์์ ๋ฒํผ์ด ์ ๋๋ ค์!"๋ผ๊ณ ํ๋ฉด ์ด๋ค ๊ธฐ๋ถ์ผ๊น? ๐ฑ
์ด๊ฒ ๋ฐ๋ก ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ ๋ฌธ์ ์ผ. 2024๋ ํ์ฌ, ์ ์ธ๊ณ ์ฌ์ฉ์๋ค์ Chrome, Safari, Firefox, Edge, Opera ๋ฑ ๋ค์ํ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ๊ณ ์์ด. ๊ฐ ๋ธ๋ผ์ฐ์ ๋ ์น ํ์ค์ ์กฐ๊ธ์ฉ ๋ค๋ฅด๊ฒ ํด์ํ๊ณ , ์๋ก์ด ๊ธฐ๋ฅ์ ๋ค๋ฅธ ์๋๋ก ์ง์ํ์ง.
โข Safari: ์ฝ 20%
โข Edge: ์ฝ 5%
โข Firefox: ์ฝ 3%
โข ๊ธฐํ: ์ฝ 9%
์ด ์ซ์๋ค์ด ๋งํด์ฃผ๋ ๊ฑด ๋ญ๊น? ๋ฐ๋ก 37%์ ์ฌ์ฉ์๊ฐ Chrome์ด ์๋ ๋ค๋ฅธ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ๋ค๋ ๊ฑฐ์ผ! ๋ง์ฝ ๋ค ์น์ฌ์ดํธ๊ฐ Chrome์์๋ง ์๋ฒฝํ๊ฒ ์๋ํ๋ค๋ฉด, ์ ์ฌ์ ์ผ๋ก 3๋ช ์ค 1๋ช ์ด์์ ์ฌ์ฉ์๋ฅผ ์์ ์ ์๋ค๋ ๋ป์ด์ง. ๐ธ
๐ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ ๋ฌธ์ ์ ์ฃผ์ ์์ธ๋ค
์, ์ด์ ์ ๋ธ๋ผ์ฐ์ ๋ง๋ค ๋ค๋ฅด๊ฒ ์๋ํ๋์ง ํํค์ณ๋ณด์. ์ฃผ์ ์์ธ๋ค์ ์๋ฉด ๋ฌธ์ ๋ฅผ ์๋ฐฉํ ์ ์๊ฑฐ๋ !
์ต์ 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);
}
});
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;
}
๊ฐ ๋ธ๋ผ์ฐ์ ๋ ์๋ก ๋ค๋ฅธ ๋ ๋๋ง ์์ง์ ์ฌ์ฉํด:
โ ๊ฐ์ฅ ๋น ๋ฅธ ์ ๋ฐ์ดํธ, ์ต์ ๊ธฐ๋ฅ ์ ๋
WebKit (Safari)
โ ๋ณด์์ ์ธ ์ ๊ทผ, ๋ณด์๊ณผ ์ฑ๋ฅ ์ค์
Gecko (Firefox)
โ ์น ํ์ค ์ค์์ ์ง์ค, ๋ ๋ฆฝ์ ์ธ ๊ฐ๋ฐ
์คํ์ ์ธ 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); /* ํ์ค */
}
๋ธ๋ผ์ฐ์ ๋ง๋ค ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด ๋ฏธ๋ฌํ๊ฒ ๋ฌ๋ผ. ํนํ ํฐ์น ์ด๋ฒคํธ, ์คํฌ๋กค ์ด๋ฒคํธ, ํค๋ณด๋ ์ด๋ฒคํธ์์ ์ฐจ์ด๊ฐ ๋ํ๋์ง.
// ํ ์ด๋ฒคํธ - ๋ธ๋ผ์ฐ์ ๋ง๋ค ๋ค๋ฅธ ์์ฑ ์ฌ์ฉ
element.addEventListener('wheel', (e) => {
// Firefox๋ deltaY๋ฅผ ์ฌ์ฉ
// ๊ตฌํ ๋ธ๋ผ์ฐ์ ๋ wheelDelta๋ฅผ ์ฌ์ฉ
const delta = e.deltaY || e.wheelDelta;
// ์ ๊ทํ ํ์
const normalizedDelta = delta > 0 ? 1 : -1;
console.log('์คํฌ๋กค ๋ฐฉํฅ:', normalizedDelta);
});
๐ ๏ธ ํด๋ฆฌํ(Polyfill)์ด๋ ๋ฌด์์ธ๊ฐ?
์, ์ด์ ์ฐ๋ฆฌ์ ๊ตฌ์ธ์ฃผ ํด๋ฆฌํ์ ๋ํด ์์๋ณด์! ํด๋ฆฌํ์ ์ฝ๊ฒ ๋งํด์ "๊ตฌํ ๋ธ๋ผ์ฐ์ ์๊ฒ ์ต์ ๊ธฐ๋ฅ์ ๊ฐ๋ฅด์ณ์ฃผ๋ ์ ์๋" ๊ฐ์ ๊ฑฐ์ผ. ๐
์๋ฅผ ๋ค์ด๋ณผ๊ฒ. 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
์ฌ๊ธฐ์ ์ ๊น! ํด๋ฆฌํ๊ณผ ํธ๋์คํ์ผ๋ฌ๋ฅผ ํท๊ฐ๋ฆฌ๋ ์น๊ตฌ๋ค์ด ๋ง์๋ฐ, ๋์ ์์ ํ ๋ค๋ฅธ ๊ฐ๋ ์ด์ผ. ๐ค
| ๊ตฌ๋ถ | ํด๋ฆฌํ (Polyfill) | ํธ๋์คํ์ผ๋ฌ (Transpiler) |
|---|---|---|
| ์ญํ | ๋ฐํ์์ ๋๋ฝ๋ ๊ธฐ๋ฅ ์ถ๊ฐ | ๋น๋ ํ์์ ์ฝ๋ ๋ณํ |
| ๋์ | API, ๋ฉ์๋, ๊ฐ์ฒด | ๋ฌธ๋ฒ, ๊ตฌ์กฐ |
| ์์ | Promise, fetch, includes | ํ์ดํ ํจ์, async/await, ํด๋์ค |
| ๋๊ตฌ | core-js, polyfill.io | Babel, TypeScript |
์ฝ๊ฒ ๋งํ๋ฉด, ํธ๋์คํ์ผ๋ฌ๋ ์ต์ ๋ฌธ๋ฒ์ ๊ตฌํ ๋ฌธ๋ฒ์ผ๋ก ๋ฐ๊ฟ์ฃผ๊ณ , ํด๋ฆฌํ์ ์๋ ๊ธฐ๋ฅ์ ์ง์ ๋ง๋ค์ด์ ์ ๊ณตํ๋ ๊ฑฐ์ผ!
๐จ ์ค์ ! ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ ํ๋ณด ์ ๋ต
์ด๋ก ์ ์ด์ ์ถฉ๋ถํด! ์ค์ ๋ก ์ด๋ป๊ฒ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ์ ํ๋ณดํ๋์ง ๋จ๊ณ๋ณ๋ก ์์๋ณด์. ๐ช
์ฒซ ๋ฒ์งธ ์ ๋ต์ "๋ธ๋ผ์ฐ์ ๊ฐ ์ด ๊ธฐ๋ฅ์ ์ง์ํ๋์ง ๋จผ์ ๋ฌผ์ด๋ณด๊ธฐ"์ผ. ์ด๊ฑธ ๊ธฐ๋ฅ ๊ฐ์ง๋ผ๊ณ ํด.
// ๋์ ์: ๋ธ๋ผ์ฐ์ ๊ฐ์ง (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);
}
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; }
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
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 ์ฌ์ฉ์์๊ฒ๋ ํ์ํ ๋ชจ๋ ํด๋ฆฌํ์ ๋ณด๋ด๋ ๊ฑฐ์ผ. ํจ์จ์ ์ด์ง? ๐
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๋ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์ ํต์ฌ์ธ๋ฐ, 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); // "์ฑ๊ณต!"
});
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๋ ์์๊ฐ ๋ทฐํฌํธ์ ๋ค์ด์๋์ง ๊ฐ์งํ๋ 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์ ๊ฐ์ฒด๋ฅผ ๋ณํฉํ ๋ ์ ๋ง ์ ์ฉํ๋ฐ, 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.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 ๋ฉ์๋๋ค๋ ์์ฃผ ์ฌ์ฉํ๋๋ฐ, ํนํ 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๋ ์๊ฐ์ ์ผ๋ก ๋ฐ๋ก ๋๋ฌ๋๊ธฐ ๋๋ฌธ์ ๋ ์ ๊ฒฝ ์จ์ผ ํ์ง. ๐จ
๋ธ๋ผ์ฐ์ ๋ง๋ค ๊ธฐ๋ณธ ์คํ์ผ์ด ๋ฌ๋ผ์ ๊ฐ์ HTML์ด๋ผ๋ ๋ค๋ฅด๊ฒ ๋ณด์ผ ์ ์์ด. ์ด๊ฑธ ํด๊ฒฐํ๋ ์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ด CSS Reset์ด์ผ!
/* ๊ฐ๋จํ CSS Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* ๋๋ Normalize.css ์ฌ์ฉ (๊ถ์ฅ) */
/* npm install normalize.css */
/* @import 'normalize.css'; */
/* Normalize.css๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์ผ๊ด์ฑ์ ์ ์งํ๋ฉด์๋
์ ์ฉํ ๊ธฐ๋ณธ ์คํ์ผ์ ๋ณด์กดํด์ค */
Normalize.css: ๋ธ๋ผ์ฐ์ ๊ฐ ์ฐจ์ด๋ง ์ ๊ทํ (์ค์ฉ์ )
๋๋ถ๋ถ์ ๊ฒฝ์ฐ Normalize.css๊ฐ ๋ ์ข์ ์ ํ์ด์ผ!
๋ฒค๋ ํ๋ฆฌํฝ์ค๋ฅผ ์ผ์ผ์ด ์์ฑํ๋ ๊ฑด ์ ๋ง ๊ท์ฐฎ์ ์ผ์ด์ผ. 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๋ ์ ๋ง ๊ฐ๋ ฅํ ๋ ์ด์์ ๋๊ตฌ์ธ๋ฐ, ๊ตฌํ ๋ธ๋ผ์ฐ์ ์์๋ ๋ฌธ์ ๊ฐ ์์ ์ ์์ด. ํนํ 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;
}
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;
}
}
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);
});
}
๐งช ํ ์คํธ์ ๋๋ฒ๊น ์ ๋ต
ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ์ ํ๋ณดํ๋ค๋ฉด, ์ด์ ์ ๋๋ก ์๋ํ๋์ง ํ ์คํธํด์ผ ํด! ์ค์ ํ๊ฒฝ์์ ํ ์คํธํ๋ ๊ฒ ๊ฐ์ฅ ํ์คํ์ง. ๐ฌ
โ ์ค์ ๋๋ฐ์ด์ค์ ๋ธ๋ผ์ฐ์ ์์ ํ ์คํธ
โ 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(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์์ ํ ์คํธํด๋ด์ผ ํด!
โ ๊ฐ๋ฐ ์๋ฒ๋ฅผ ๋ก์ปฌ ๋คํธ์ํฌ์ ๋ ธ์ถ
โ ๋ชจ๋ฐ์ผ ๋๋ฐ์ด์ค์์ ์ ์
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์ ์ต์ ๋ธ๋ผ์ฐ์ ์ ๊ตฌํ ๋ธ๋ผ์ฐ์ ์ ๊ฐ๊ฐ ๋ค๋ฅธ ๋ฒ๋ค์ ์ ๊ณตํ๋ ์ ๋ต์ด์ผ. 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 }
}]
]
}
}
}
]
}
}
];
ํด๋ฆฌํ์ ์ถ๊ฐํ๋ฉด ๋ฒ๋ค ํฌ๊ธฐ๊ฐ ์ปค์ง๋ ๊ฑด ํผํ ์ ์์ด. ํ์ง๋ง ์ต์ํํ ์๋ ์์ง!
โ ์ฌ์ฉํ์ง ์๋ ์ฝ๋ ์ ๊ฑฐ
โ 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์์
// ์ด๋ค ํด๋ฆฌํ์ด ์ฉ๋์ ๋ง์ด ์ฐจ์งํ๋์ง ํ์ธ!
๋ชจ๋ ํด๋ฆฌํ์ ์ด๊ธฐ์ ๋ก๋ํ ํ์๋ ์์ด. ํ์ํ ์์ ์ ๋์ ์ผ๋ก ๋ก๋ํ๋ ๊ฒ๋ ์ข์ ์ ๋ต์ด์ผ!
// ๋์ ํด๋ฆฌํ ๋ก๋ฉ ํจ์
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 });
๐ ์ค์ ํ๋ก์ ํธ ์ ์ฉ ์ฌ๋ก
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ํ๋ก์ ํธ์ ์ด๋ป๊ฒ ์ ์ฉํ๋์ง ์ดํด๋ณด์! ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์ด๋ฐ ์ ๋ต๋ค์ด ํ์์ ์ด๊ฑฐ๋ . ๐ผ
์จ๋ผ์ธ ์ผํ๋ชฐ์ ๋ค์ํ ์ฌ์ฉ์์ธต์ ๋์์ผ๋ก ํ๊ธฐ ๋๋ฌธ์ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ์ด ์ ๋ง ์ค์ํด!
// 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();
});
๊ด๋ฆฌ์ ๋์๋ณด๋๋ ๋ณต์กํ ๋ฐ์ดํฐ ์๊ฐํ์ ์ธํฐ๋์ ์ด ๋ง์์ ํธํ์ฑ ํ๋ณด๊ฐ ๊น๋ค๋ก์.
// ๋์๋ณด๋ ํ๋ก์ ํธ ์ค์
// 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;
์ฌ๋ฅ๋ท์ฒ๋ผ ๋ค์ํ ์ฌ์ฉ์๊ฐ ์ ์ํ๋ ํ๋ซํผ์์๋ ๊ด๋ฒ์ํ ๋ธ๋ผ์ฐ์ ์ง์์ด ํ์์ผ!
// ์ฌ๋ฅ ๊ณต์ ํ๋ซํผ์ ํธํ์ฑ ์ ๋ต
// 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" ๋ธ๋ผ์ฐ์ ์ผ. ์ด๊ฒ ํธํ์ฑ ๋ฌธ์ ๋ฅผ ๋ง์ด ์ค์ฌ์ฃผ๊ณ ์์ง!
โข ๋ณด์ ํจ์น ์ ์ํ ๋ฐฐํฌ
โข ๊ฐ๋ฐ์๋ ์ต์ ๊ธฐ๋ฅ ๋ ๋นจ๋ฆฌ ์ฌ์ฉ ๊ฐ๋ฅ
โข ๊ตฌํ ๋ธ๋ผ์ฐ์ ์ง์ ๋ถ๋ด ๊ฐ์
์ฃผ์ Evergreen Browsers:
Chrome, Firefox, Edge, Safari (iOS ์ ์ธ)
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 ์์
// 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๋ 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;
}
}
}
AI์ ๋จธ์ ๋ฌ๋์ด ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํ ์คํธ์๋ ํ์ฉ๋๊ณ ์์ด. ์๋์ผ๋ก ํธํ์ฑ ๋ฌธ์ ๋ฅผ ๊ฐ์งํ๊ณ ํด๊ฒฐ์ฑ ์ ์ ์ํ๋ ๋๊ตฌ๋ค์ด ๋์ค๊ณ ์์ง!
โ ์๊ฐ์ AI ํ ์คํ
โ ๋ ์ด์์ ์ฐจ์ด ์๋ ๊ฐ์ง
Testim
โ AI ๊ธฐ๋ฐ ํ ์คํธ ์๋ํ
โ ์๊ฐ ์น์ ํ ์คํธ
Mabl
โ ๋จธ์ ๋ฌ๋ ๊ธฐ๋ฐ ํ ์คํธ
โ ์๋ ํ๊ท ํ ์คํธ
๐ ์ฒดํฌ๋ฆฌ์คํธ: ์๋ฒฝํ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ
ํ๋ก์ ํธ๋ฅผ ์์ํ๊ธฐ ์ ์, ๊ทธ๋ฆฌ๊ณ ๋ฐฐํฌํ๊ธฐ ์ ์ ๊ผญ ํ์ธํด์ผ ํ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์ ๋ฆฌํด๋ดค์ด! โ
โก ์ง์ํ ๋ธ๋ผ์ฐ์ ๋ชฉ๋ก ์์ฑ
โก ์ต์ ์ง์ ๋ฒ์ ๊ฒฐ์
โก ์์ฅ ์ ์ ์จ ๋ถ์
โก ์ฌ์ฉ์ ํต๊ณ ํ์ธ
2. ๊ฐ๋ฐ ํ๊ฒฝ ์ค์
โก Babel ์ค์ ์๋ฃ
โก Autoprefixer ์ค์
โก ํด๋ฆฌํ ์ ๋ต ์๋ฆฝ
โก ๋ฒ๋ค๋ฌ ์ค์ (Webpack/Vite)
3. ์ฝ๋ ํ์ง ๋๊ตฌ
โก ESLint + compat ํ๋ฌ๊ทธ์ธ
โก Stylelint ์ค์
โก Prettier ์ค์
โก Git hooks ์ค์
4. ํ ์คํธ ํ๊ฒฝ
โก ๋ก์ปฌ ํ ์คํธ ๋ธ๋ผ์ฐ์ ์ค์น
โก ํด๋ผ์ฐ๋ ํ ์คํธ ์๋น์ค ์ ํ
โก ์ค์ ๋๋ฐ์ด์ค ํ๋ณด
โก CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
โก ๋ชจ๋ ์ธํฐ๋์ ๋์ ํ์ธ
โก ํผ ์ ์ถ ๋ฐ ๊ฒ์ฆ
โก 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();
}
๐ก ์ค๋ฌด ํ๊ณผ ๋ฒ ์คํธ ํ๋ํฐ์ค
๋ง์ง๋ง์ผ๋ก, ์ค๋ฌด์์ ์ ๋ง ์ ์ฉํ ํ๋ค์ ๊ณต์ ํ ๊ฒ! ์ด๊ฑด ๊ฒฝํ์์ ๋์จ ์ง์ง ๋ ธํ์ฐ๋ค์ด์ผ. ๐
์ฒ์๋ถํฐ ๋ชจ๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ์๋ฒฝํ๊ฒ ์ง์ํ๋ ค๊ณ ํ์ง ๋ง. ํต์ฌ ๊ธฐ๋ฅ๋ถํฐ ์์ํด์ ์ ์ง์ ์ผ๋ก ํ์ฅํ๋ ๊ฒ ํ๋ช ํด!
// ์ ์ง์ ๊ธฐ๋ฅ ์ถ๊ฐ ํจํด
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');
ํด๋ฆฌํ์ด ์ ๋๋ก ๋ก๋๋์ง ์๊ฑฐ๋ ์์์น ๋ชปํ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์์ด. ์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ก ์์ ํ๊ฒ ์ฒ๋ฆฌํ์!
// ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ
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();
ํด๋ฆฌํ์ด ์ฑ๋ฅ์ ๋ฏธ์น๋ ์ํฅ์ ๋ชจ๋ํฐ๋งํ๋ ๊ฒ๋ ์ค์ํด!
// ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง ์ ํธ๋ฆฌํฐ
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();
});
์ด๋ค ๋ธ๋ผ์ฐ์ ๋ฅผ ์ง์ํ๊ณ , ์ด๋ค ํด๋ฆฌํ์ ์ฌ์ฉํ๋์ง ๋ฌธ์ํํ๋ ๊ฒ ์ ๋ง ์ค์ํด. ํ์๋ค๊ณผ ๋ฏธ๋์ ๋๋ฅผ ์ํด์!
// 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% ๋ฏธ๋ง ๋ธ๋ผ์ฐ์ ๋ ์ง์ ์ค๋จ ๊ณ ๋ ค
์์:



๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ๊ณผ ํด๋ฆฌํ์ ๋ํด ์ ๋ง ๋ง์ ๊ฑธ ๋ฐฐ์ ์ง? ๐
ํต์ฌ์ ๋ค์ ํ๋ฒ ์ ๋ฆฌํด๋ณผ๊ฒ:
โ ๋ค์ํ ์ฌ์ฉ์ ํ๊ฒฝ ๊ณ ๋ ค
โ ๋น์ฆ๋์ค ๊ธฐํ ํ๋
2. ํด๋ฆฌํ์ ๊ฐ๋ ฅํ ๋๊ตฌ
โ ๊ตฌํ ๋ธ๋ผ์ฐ์ ์ ์ต์ ๊ธฐ๋ฅ ์ ๊ณต
โ ์ ํ์ ๋ก๋ฉ์ผ๋ก ์ฑ๋ฅ ์ต์ ํ
3. ํ ์คํธ๋ ํ์
โ ์๋ํ + ์๋ ํ ์คํธ
โ ์ค์ ๋๋ฐ์ด์ค ๊ฒ์ฆ
4. ์ฑ๋ฅ๊ณผ ํธํ์ฑ์ ๊ท ํ
โ ์กฐ๊ฑด๋ถ ๋ก๋ฉ
โ Differential Loading
5. ์ง์์ ์ธ ๋ชจ๋ํฐ๋ง๊ณผ ์ ๋ฐ์ดํธ
โ ๋ธ๋ผ์ฐ์ ํธ๋ ๋ ์ถ์
โ ์ ๊ธฐ์ ์ธ ํธํ์ฑ ๊ฒํ
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์น ๊ฐ๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ๊ฑฐ๋ ๋ฐฐ์ธ ๋๋, ์ด๋ฐ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ ์ง์์ ์ ๋ง ์ค์ํด. ์ค์ ๋ก ํด๋ผ์ด์ธํธ๋ค์ด ๊ฐ์ฅ ๋ง์ด ์๊ตฌํ๋ ๋ถ๋ถ ์ค ํ๋๊ฑฐ๋ !
๊ธฐ์ตํด! ์๋ฒฝํ ํธํ์ฑ์ ํ๋ฃจ์์นจ์ ์ด๋ค์ง์ง ์์. ์ ์ง์ ์ผ๋ก ๊ฐ์ ํ๊ณ , ์ฌ์ฉ์ ํผ๋๋ฐฑ์ ๋ฃ๊ณ , ๊ณ์ ํ์ตํ๋ ๊ฒ ์ค์ํด. ๐
์ฌ๋ฅ๋ท์ '์ง์์ธ์ ์ฒ'์์ ๋ ๋ง์ ์น ๊ฐ๋ฐ ์ง์์ ํํํด๋ณด๋ ๊ฒ๋ ์ถ์ฒํด! ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๊ฐ ๋์! ๐ช
์ด ๊ธ์ด ๋์์ด ๋์๋ค๋ฉด, ์ฌ๋ฅ๋ท์์ ๋ ๋ง์ ์น ๊ฐ๋ฐ ์ง์์ ํํํด๋ณด์ธ์! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.