1980년대 말, 전 세계 대학과 연구소의 과학자들 간의 자동화된 정보 공유에 대한 수요를 충족하기 위해 고안되고 개발되었다.
영국의 과학자 팀 버너스-리는 CERN에서 일하는 동안 1989년에 최초의 웹 브라우저인 월드 와이드 웹(World Wide Web)을 개발했다.
HTML(Hyper Text Markup Language)이 문서 구조화와 하이퍼링크를 위해 설계되었다.
사용자가 웹과 상호작용할 수 있는 수단이 제한적이며 단순한 하이퍼링크를 통한 페이지 이동이 전부였다.
브랜든 아이크 (Brendan Eich)가 Netscape Navigator를 위해 Javascript개발. (1995)
Javascript를 통해 DOM(Document Object Model) 을 조작하여 웹 페이지를 동적으로 변경할 수 있게 되었다.
웹 페이지의 동적 요소의 증가로 성능 최적화 필요.
AJAX 등장 (2005년)
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200){
const response = JSON.parse(xhr.response)
const responseText = xhr.responseText
console.log( { response, responseText })
}
}
xhr.open("GET", "<https://api.example.com/data>", true);
xhr.send();
- 크롬 : -webkit-
- 사파리 : -webkit-
- 파이어폭스 : moz- (mozila 라는 단체가 파이어폭스를 만들었기 때문에)
- 오페라 : -o-, -webkit-
- 익스플로러 : -ms-
크로스 브라우저 호환성: jQuery는 다양한 브라우저에서 일관된 동작을 보장합니다.
코드 간소화: 긴 JavaScript 코드를 짧고 간결하게 작성할 수 있습니다.
플러그인 확장성: 수많은 jQuery 플러그인이 있어 다양한 기능을 쉽게 추가할 수 있습니다.
$('body').css({
'backgroundColor' : '#fff',
'color':'#000'
});
아이폰(2007)출시와 안드로이드 기기의 확산으로 모바일 웹 수요 급증.
// mobile
@media (max-width: 600px) {
.container {
width: 100%;
padding: 10px;
}
}
// tablet
@media (min-width: 601px) and (max-width: 1024px) {
.container {
width: 90%;
padding: 20px;
}
}
// pc
@media (min-width: 1025px) {
.container {
width: 80%;
max-width: 1200px;
padding: 30px;
}
}
<picture>
<source media="(max-width: 799px)" srcset="small-image.jpg">
<source media="(min-width: 800px)" srcset="large-image.jpg">
<img src="default-image.jpg" alt="Responsive image">
</picture>
element.addEventListener('touchstart', (e) => console.log('Touch started') });
element.addEventListener('touchmove', (e) => console.log('Touch started') });
element.addEventListener('touchend', (e) => console.log('Touch started') });
SPA(Single Page Application)이 보편화 되면서 로직과 데이터가 복잡해졌다.
class HomePage extends React.Component {
render() {
return <h1>{this.props.name}`Page</h1>;
}
}
ReactDOM.render(
<Component name="my name" />,
document.getElementById('root')
);
fLUX 패턴
React Lifecycle
Redux는 상태 컨테이너로 애플리케이션의 상태를 중앙 집중식 관리
type TypeAction {
type: 'INCREMENT'|'DECREMENT'
}
const countReducer = (state: number=0, action: TypeAction) =>{
switch(action.type){
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default
return state
}
}
const store = Redux.createStore(countReducer);
store.subscribe(()=> console.log(store.getState()) );
store.dispatch({type:'INCREMENT'}) //1
store.dispatch({type:'DECREMENT'}) //0
store.dispatch({type:'INCREMENT'}) //1
상태 변경이 순수 함수로만 이뤄져 디버깅이 용이해졌다.
상태 변경 이력을 추적하여 이전 상태로 돌아갈 수 있게 되었다.
미들웨어 지원 => 비동기 작업, 로깅 등을 쉽게 구현할 수 있게 되었다.
import React from 'react';
import { Text, View } from 'react-native';
const App = () =>{
return (
<View style={{ flex: 1. justifyContent: 'center', alignItems: 'center' }}>
<Text>
My APP
</Text>
</View>
)
}
export default App;
// 서비스 워커 등록
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("sw.js");
}
// 버큰 클릭 => 알림 권한 요청
const button = document.getElementById("notification");
button.addEventListener("click", function (e) {
Notification.requestPermission().then(function (result) {
if (result === "granted") {
sendNotification();
}
});
});
function sendNotification() {
var notifTitle = '알림 타이틀';
var notifBody = "알림 바디";
var notifImg = "data/img/example.jpg";
var options = {
body: notifBody,
icon: notifImg,
};
var notif = new Notification(notifTitle, options);
setTimeout(sendNotification, 30000);
}
npm create-react-app my-app
cd my-app
npm start
Javascript에 정적 타입을 추가하여 코드 안정성과 가독성 향상
interface TypeUserData{
id: string;
name: string;
}
class User {
id: string;
name: string;
construntor(userData: TypeUserData){
const { id, name } = userData
this.id = id
this.name = name
}
}
const user: User = new User('My Id','My Name')
코드 스플리팅
React.lazy
컴포넌트를 렌더링하는 시점에 비동기적으로 로딩할 수 있게 해주는 유틸 함수
Suspense
리액트 내장 컴포넌트로 코드 스플리팅 된 컴포넌트를 로딩하고, 로딩이 끝나지 않았을 때 보여줄 UI를 설정할 수 있다.
fallback이라는 props를 통해 로딩 중에 보여줄 JSX 문법을 지정
코드 스플리팅 예시
import React, { Suspense } from 'react';
const Component = React.lazy(()=> import('./Component') );
function App() {
return (
<div className="relative max-w-[1280px] w-full m-auto">
<Suspense fallback={<Loading />}>
<Component />
</Suspense>
</div>
)
}
SEO
SEO 예시
// app/products/[id]/page.tsx
import { Metadata } from 'next';
interface ProductPageProps {
params: { id: string };
}
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
const product = await fetchProduct(params.id); // id에 기반한 제품 정보 가져오기
return {
title: `${product.name} | My Website`,
description: `Buy ${product.name} at the best price.`,
openGraph: {
title: product.name,
description: product.description,
url: `https://www.example.com/products/${product.id}`,
images: [
{
url: product.image,
width: 800,
height: 600,
alt: product.name,
},
],
},
};
}
export default function ProductPage({ params }: ProductPageProps) {
const product = await fetchProduct(params.id);
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
</main>
);
}
// utils/fetchProduct.ts
export interface Product {
id: string;
name: string;
description: string;
price: number;
image: string;
}
export async function fetchProduct(id: string): Promise<Product> {
// 가상의 API URL입니다. 실제 API URL로 변경하세요.
const res = await fetch(`https://api.example.com/products/${id}`);
if (!res.ok) {
throw new Error('Failed to fetch product');
}
const product: Product = await res.json();
return product;
}
Core Web Vitals
- CLS (Cumulative Layout Shift): 레이아웃의 비정상적인 변화가 얼마나 발생했는지를 측정합니다.
- FID (First Input Delay): 사용자가 처음으로 상호작용할 때까지 걸린 시간을 측정합니다.
- LCP (Largest Contentful Paint): 페이지에서 가장 큰 요소가 렌더링될 때까지 걸린 시간을 측정합니다.
- TTFB (Time to First Byte): 서버가 첫 번째 바이트를 반환할 때까지의 시간을 측정합니다.
예시
// pages/_app.tsx
import { AppProps } from 'next/app';
import { reportWebVitals } from '@/utils/reportWebVitals';
interface TypeAnalitics {
id: string;
name: string;
value: number
}
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
function printLog({id, name, value}: TypeAnalitics
console.log({id, name, value})
}
export function reportWebVitals(metric: TypeAnalitics) {
if (metric.label === 'web-vital') {
// 웹 바이탈 측정 결과 전송 로직
sendToAnalytics(metric);
// 웹 바이탈 측정 결과 콘솔 출력
getCLS(printLog);
getFID(printLog);
getLCP(printLog);
getTTFB(printLog);
}
}
export default MyApp;
// utils/reportWebVitals.ts
interface TypeAnalitics {
id: string;
name: string;
value: number
}
export function sendToAnalytics(metric: TypeAnalitics) {
const { id, name, value } = metric;
// Google Analytics로 전송하는 코드
window.gtag('event', name, {
event_category: 'Web Vitals',
event_action: name,
value: Math.round(name === 'CLS' ? value * 1000 : value), // CLS는 1000배로 전송
event_label: id, // ID로 웹 바이탈 항목을 구분
non_interaction: true, // 사용자 상호작용이 아님을 명시
});
}
Edge 컴퓨팅
- 데이터를 중앙 서버(클라우드 또는 데이터 센터)가 아닌, 사용자 가까운 엣지에서 처리하는 컴퓨팅 모델
- 가까운 위치에서 데이터를 처리하고 저장하여 지연 시간을 줄이고 성능을 향상
Edge 서버
- 엣지 컴퓨팅을 구현하는 물리적 또는 가상 서버
- 엣지 서버는 엣지 컴퓨팅을 수행하는 인프라 중 하나일 뿐
// Cloudflare Workers를 사용한 Edge 컴퓨팅 예시
addEventListener('fetch', (event: FetchEvent) => {
event.respondWith(handleRequest(event.request))
});
async function handleRequest(request: Request): Promise<Response> {
const response = await fetch('https://api.example.com', {
cf: { cacheTtl: 300, cacheEverything: true } as RequestInitCfProperties,
});
return new Response(response.body, {
...response, // 기존 응답 헤더와 상태를 유지
});
}
마이크로 프론트엔드
- 대규모 애플리케이션을 여러 개의 독립적인 소규모 애플리케이션으로 나누어 각각 별도로 개발, 배포, 관리할 수 있게 하는 아키텍처
- 각 모듈은 자체적으로 동작하며 여러 마이크로 프론트엔드가 통합되어 최종 사용자에게 하나의 애플리케이션처럼 보이게 한다.
<html> <head> <title>마이크로 프론트엔드 예시</title> </head> <body> <header> <!-- 팀 A의 마이크로 프론트엔드 --> <div id="header"></div> </header> <main> <!-- 팀 B의 마이크로 프론트엔드 --> <div id="product-list"></div> </main> <footer> <!-- 팀 C의 마이크로 프론트엔드 --> <div id="footer"></div> </footer> <script src="<http://team-a-app.com/header.js>"></script> <script src="<http://team-b-app.com/product-list.js>"></script> <script src="<http://team-c-app.com/footer.js>"></script> </body> </html>
장애인을 위한 웹 접근성
다국어 지원
웹 접근성 표준과 ARIA 도입
WCAG(Web Content Accessibilty Guidelines): 웹 콘텐츠 접근성을 개선시키기 위한 가이드 라인ARIA (Accessible Rich Internet Applications)
동적 콘텐츠와 사용자 인터페이스 컴트롤의 접근성을 향상시키는 방법 제공
aria-label
시각적으로 보이지 않는 레이블 제공<button aria-label="닫기 버튼">✖</button>
aria-label
을 통해 스크린 리더는 이 버튼을 "닫기 버튼"으로 읽는다.
aria-labelledby
다른 요소의 텍스트를 참조하여 해당 요소의 레이블을 제공<div> <h2 id="dialog-title">로그인</h2> <div role="dialog" aria-labelledby="dialog-title"> <p>로그인 정보를 입력해주세요.</p> <label for="username">아이디</label> <input type="text" id="username" /> <label for="password">비밀번호</label> <input type="password" id="password" /> </div> </div>
aria-labelledby="dialog-title"
스크린 리더에게div
의 제목이 "로그인"이라 읽는다.
aria-live
실시간 업데이트가 이뤄지는 요소에 사용<div aria-live="polite" id="status-message"></div> <script> setTimeout(() => { document.getElementById('status-message').textContent = '데이터가 업데이트되었습니다.'; }, 2000); </script>
aria-live="polite"
의 속성으로 2초 후에 스크린 리더가 현재 작업에 방해하지 않고 변경된 내용을 읽어준다.
aria-live
속성:
1. off: 무시 (default)
2. polite: 현재 작업을 방해하지 않고 변경된 콘텐츠를 읽어준다.
3. assertive: 콘텐츠가 변경되면 즉시 읽어준다.
aria-expanded
해당 요소가 확장되었는지 접혀 있는지 상태<button aria-expanded="false" aria-controls="menu" id="example-toggle"> 토글 </button>
<ul id="menu" hidden> <li><a href="#first">1</a></li> <li><a href="#second">2</a></li> <li><a href="#third">3</a></li> </ul>
<script> const button = document.getElementById('example-toggle'); const menu = document.getElementById('menu'); button.addEventListener('click', () => { const expanded = button.getAttribute('aria-expanded') === 'true'; button.setAttribute('aria-expanded', !expanded); menu.hidden = expanded; }); </script>
aria-expanded
속성이 true 또는 false로 변경되고, 이에 따라 메뉴가 열리거나 닫힙니다. 스크린 리더는 이 상태를 인식하고 사용자에게 알려줍니다.
aria-hidden
시각적으로 보이지 않는 레이블 제공<div aria-hidden="true"> <img src="loading.gif" alt="로딩 중" /> </div>
aria-hidden="true"
를 통해 스크린 리더는 이 요소를 무시한다.
국제화(i18n)과 지역화(l10n)
///src/locales/en.json { "greeting": "Hello, {name}!", "todayDate": "Today's date is {date, date, ::yyyyMMdd}", "example": "example code" }
///src/locales/ko.json { "greeting": "안녕, {name}!", "todayDate": "오늘 날짜는 {date, date, ::yyyyMMdd}", "example": "예시 코드" }
import React, { useState } from 'react'; import { IntlProvider, FormattedMessage, FormattedDate } from 'react-intl'; import en from './locales/en.json'; import ko from './locales/ko.json'; const messages = { en, ko}; const App: React.FC = () => { const [locale, setLocale] = useState('en'); // 기본 언어는 영어 // 언어 변경 핸들러 const changeLanguage = (e: React.ChangeEvent<HTMLSelectElement>) => { setLocale(e.target.value); }; return ( <IntlProvider locale={locale} messages={messages[locale]}> <div> <h1> <FormattedMessage id="welcomeMessage" /> </h1> <p> <FormattedMessage id="greeting" values={{ name: 'lsw' }} // 이름을 변수로 전달 /> </p> <p> <FormattedMessage id="todayDate" values={{ date: new Date() }} // 현재 날짜 전달 /> </p> {/* 언어 선택 드롭다운 */} <select onChange={changeLanguage} value={locale}> <option value="en">English</option> <option value="ko">한국어</option> </select> </div> </IntlProvider> ); }; export default App;
CSP(Content Security Policy)
<!-- CSP 헤더 예시 -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' <https://apis.google.com>;"
>
자동화된 테스트 도구 활용
// Jest를 사용한 단위 테스트 예시
describe('Math operations', () => {
test('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
test('multiplies 2 * 3 to equal 6', () => {
expect(2 * 3).toBe(6);
});
});
name: Node.js CI/CD Pipeline
# 워크플로가 실행되는 조건: main 브랜치로의 푸시 또는 풀 리퀘스트 시
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
# Ubuntu 최신 버전에서 워크플로 실행
runs-on: ubuntu-latest
# 여러 작업들을 정의
steps:
# GitHub에서 자동으로 코드 체크아웃
- name: Checkout code
uses: actions/checkout@v3
# Node.js를 설정 (버전 16.x 사용)
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
# 의존성 설치 (npm install 실행)
- name: Install dependencies
run: npm install
# 테스트 실행
- name: Run tests
run: npm test
deploy:
# 모든 테스트가 성공한 경우에만 실행
runs-on: ubuntu-latest
needs: build
<출처>
1. 브라우저와 HTML 등장
https://first-website.web.cern.ch/first-website/
https://info.cern.ch/
2. 인터랙티브 웹
https://dudurian.tistory.com/149
https://jbground.tistory.com/4
https://namu.wiki/w/Netscape%20Navigator
https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest
3. 브라우저 표준화
https://ko.wikipedia.org/wiki/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80_%EC%A0%84%EC%9F%81
https://teamsparta.notion.site/1-c24e66eef9ef4ce0b52e2ba4d0a85364
4. 모바일 시대
https://haruair.github.io/flux/docs/overview.html
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
5. 대규모 애플리케이션 개발
6. 네이티브 웹, 하이브리드
https://developer.mozilla.org/ko/docs/Web/Progressive_web_apps/Tutorials/js13kGames/App_structure
7. 모던 도구와 방법론
https://velog.io/@s_sangs/%EC%BD%94%EB%93%9C-%EC%8A%A4%ED%94%8C%EB%A6%AC%ED%8C%85-Code-Splitting
8. 성능과 UX
https://velog.io/@s_sangs/%EC%BD%94%EB%93%9C-%EC%8A%A4%ED%94%8C%EB%A6%AC%ED%8C%85-Code-Splitting