웹 역사

👀·3일 전
0
post-thumbnail

1. 브라우저와 HTML 등장 (1989~1995)

등장 이유

1980년대 말, 전 세계 대학과 연구소의 과학자들 간의 자동화된 정보 공유에 대한 수요를 충족하기 위해 고안되고 개발되었다.

해결

  1. 영국의 과학자 팀 버너스-리는 CERN에서 일하는 동안 1989년에 최초의 웹 브라우저인 월드 와이드 웹(World Wide Web)을 개발했다.

  2. HTML(Hyper Text Markup Language)이 문서 구조화와 하이퍼링크를 위해 설계되었다.

결과

  1. 서로 다른 시스템에서 문서를 쉽게 공유할 수 있게 되었다.
  2. HTML은 어떤 운영체제에서 동일하게 표시할 수 있어, 정보의 호환성을 해결했다.
  3. 월드 와이드 웹의 개방적 구조는 전 세계적인 정보 공유 네트워크의 급속한 성장에 기여했다.

2. 인터랙티브 웹 (1995~2005)

등장 이유

사용자가 웹과 상호작용할 수 있는 수단이 제한적이며 단순한 하이퍼링크를 통한 페이지 이동이 전부였다.

해결

  1. 브랜든 아이크 (Brendan Eich)Netscape Navigator를 위해 Javascript개발. (1995)

  2. Javascript를 통해 DOM(Document Object Model) 을 조작하여 웹 페이지를 동적으로 변경할 수 있게 되었다.

  • Netscape Navigator: 1994년 넷스케이프 회사가 개발한 웹 브라우저. 월드 와이드 웹의 태동기를 대표하는 웹 브라우저이자 오픈소스 웹 브라우저계에 지대한 공헌을 했다. 현재는 파이어폭스로 승계

추가 문제점

웹 페이지의 동적 요소의 증가로 성능 최적화 필요.

해결

AJAX 등장 (2005년)

  • AJAX(Asynchronous Javascript and XML): JavaScript와 XML을 이용한 비동기적 정보 교환 기법
    -> 요즘은 XML보다 JSON을 주로 사용.
  • XMLHttpRequest(XHR): XHR을 사용하면 페이지의 새로고침 없이도 URL에서 데이터를 가져올 수 있습니다.
  • HTTP 프로토콜을 이용한 비동기 통신으로 Javascript를 통해 HTML과 CSS의 골격을 정하고 AJAX를 통해 데이터를 받아 페이지 가공 후, 렌더링한다.

예시

	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();

responseType 속성

결과

  • 필요한 부분만 데이터를 가져와 업데이트 할 수 있게 되었다.
  • 웹 애플리케이션 등장. ex) Gmail(2004년 출시)과 Google Maps(2005년 출시)
  • 웹2.0 시대_ 사용자 참여형 웹 서비스와 소셜 미디어 급성장

3. 브라우저 표준화(2000년대 중반)

브라우저 전쟁

  • 넷 스케이프(Netscape)와 마이크로소프트의 익스플로러(IE)의 경쟁 => 각자 비표준 개발.
  • 이로 인해 개발자들은 각 브라우저에 맞는 코드를 별도 개발이 필요했다.
  1. 크롬 : -webkit-
  2. 사파리 : -webkit-
  3. 파이어폭스 : moz- (mozila 라는 단체가 파이어폭스를 만들었기 때문에)
  4. 오페라 : -o-, -webkit-
  5. 익스플로러 : -ms-

해결

  • W3C(World Wide Web Consortium)에서 HTML,CSS,Javascript의 표준을 제정 및 권장.
    *JQuery(2006)의 등장으로 크로스 브라우저 호환성 문제 해결 기여.
    (선택자, 이벤트 객체,애니메이션_fadeIn()등 )
크로스 브라우저 호환성: jQuery는 다양한 브라우저에서 일관된 동작을 보장합니다.
코드 간소화: 긴 JavaScript 코드를 짧고 간결하게 작성할 수 있습니다.
플러그인 확장성: 수많은 jQuery 플러그인이 있어 다양한 기능을 쉽게 추가할 수 있습니다.
  $('body').css({
    'backgroundColor' : '#fff',
    'color':'#000'
  });

결과

  • 간결한 문법과 크로스 브라우저 호환성으로 개발 시간 단축
  • JQuery는 표준 기반 동작 => 웹 표준 준수

4. 모바일 시대 (2007년~2012년)

아이폰(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;
  }
}

결과

  • 모든 기기에서 일관된 디자인 적용.
  • 개발 효율성 증가. (별도의 모바일 사이트를 만들 필요가 없어졌다.)

성능 최적화, 이벤트 처리

  1. 성능 : 모바일에서 로딩 시간이 길어진다.
  2. 터치 이벤트: 터치 스크린에서 사용자 입력이 까다로워졌다.
<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') });

결과

  • 디바이스 최적화된 리소스 제공 => 로딩 속도 개선
  • 터치 기반 인터랙션 최적화 => UX 개선

5. 대규모 애플리케이션 개발(2010~2015)

코드의 복잡성

SPA(Single Page Application)이 보편화 되면서 로직과 데이터가 복잡해졌다.

  • 상태 관리 어려움: 컴포넌트 간 데이터 전달과 상태 동기화 복잡.
  • 코드 유지보수: 코드 구조 관리가 어려워졌다.
  • 성능 이슈: 불필요한 렌더링과 메모리 누수 발생.

해결

  • React (2013)
  • facebook에서 개발 (Component 기반 아케텍쳐, Virtual Dom 도입)
  • JavaScript기반의 라이브러리
  class HomePage extends React.Component {
    render() {
      return <h1>{this.props.name}`Page</h1>;
    }
  }

  ReactDOM.render(
    <Component name="my name" />,
    document.getElementById('root')
  );
  • Flux 아키텍쳐
  • Facebook에서 클라이언트-사이드 웹 어플리케이션을 만들기 위해 사용하는 어플리케이션 아키텍쳐
  • 단방향 데이터 흐름을 활용해 뷰 컴포넌트를 구성하는 React를 보완하는 역할

    fLUX 패턴
    fLUX 패턴

React Lifecycle
React Lifecycle

결과

  • 코드 구조 개선: 컴포넌트 단위 코드 분할 => 재사용성, 유지보수 효율 향상
  • 성능 최적화: 가상 DOM 활용 => 렌더링 효율 향상
  • 상태 관리 용이: 단방향 데이터 흐름으로 상태 변경 추적이 쉬워졌다.

상태관리 복잡성

해결

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

Redux Flow

상태 변경이 순수 함수로만 이뤄져 디버깅이 용이해졌다.
상태 변경 이력을 추적하여 이전 상태로 돌아갈 수 있게 되었다.
미들웨어 지원 => 비동기 작업, 로깅 등을 쉽게 구현할 수 있게 되었다.

6. 네이티브 웹, 하이브리드 접근 (2015~2018)

네이티브 앱과 웹앱의 장단점

  • 네이티브 앱은 성능과 기능면에서 우수하지만 개발 비용이 높고, 웹앱은 개발이 쉽지만 기능에 제한이 있다.
  • 네이티브 앱: 각 플랫폼 별도 개발 필요.
  • 웹앱: 디바이스 API접근의 제한과 성능 이슈

React Native와 PWA(Progressive Web App) 등장

React Native(2015)

  • React 문법으로 네이티브 앱을 개발 가능 => 웹 개발자들이 모바일 앱에 접근 용이.
	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;

PWA

	// 서비스 워커 등록
	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);
    }
  • 하나의 코드로 여러 플랫폼 개발 가능
  • 네이티브에 가까운 성능과 기능 제공
  • 오프라인에서도 작동하는 웹앱 구현 가능.

7.모던 도구와 방법론 (2015~)

  • 빌드 도구의 다양성: Webpack, Gulp, Grunt 등
  • 트랜스파일러의 필요성: ES6+ 문법 사용을 위한 Babel 등 도입
  • 패키지 관리의 복잡성: npm, yarn 등 의존성 충돌

통합 도구와 Typescript 도입

  • 복잡한 설정 없이 프로젝트를 시작할 수 있게 해주는 도구
	npm create-react-app my-app
	cd my-app
	npm start

Typescript

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')
  • 개발 환경 표준화 => 프로젝트 설정과 빌드 과정 최소화
  • 코드 품질 향상: 정적 타입 검사를 통한 버그 감소
  • 생산성 증가: 자동 완성, 리팩토링 도구 지원

빌드 속도와 UX

Vite, esbuild

  • Vite는 빠른 개발 서버와 빌드 도구 제공
  • esbuild => 빠른 Javascript 번들러 ( Go로 작성 )

8. 성능과 UX (2018~)

UX 최적화와 SEO

  • 로딩 속도 => 사용자 이탈률 증가, SEO에 부정적 영향
  • 모바일 환경 최적화 => 다양한 네트워크 조건과 디바이스 성능 대응 필요.
  • 대규모 Javascript 번들: 초기 로딩 시간 증가.

코드 스플리팅(Code Splitting)과 SSR (Server Side Rendering)

코드 스플리팅

  • 필요한 코드만 로드하여 초기 로딩을 단축시킨다.

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;
}

결과

  • 초기 로딩 성능 개선: 필요한 코드만 로드하여 로딩 단축
  • SEO 향상: 검색 엔진 크롤링 용이
  • UX 개선: 빠른 초기 로딩과 인터랙티브 상호작용 제공

Core Web Vitals와 사용자 중심의 성능 지표

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>
  • Core Web Vitals를 통한 객관적 성능 측정 및 개선
  • Edge 컴퓨팅을 통한 전 세계 사용자에게 빠른 응답 제공
  • 마이크로 프론트엔드를 통한 독립적인 개발 및 배포.

9. 접근성과 국제화

장애인을 위한 웹 접근성
다국어 지원

웹 접근성 표준과 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;

결과

  • 접근성 개선으로 더 많은 사용자 웹 접근 가능
  • 다국어 및 지역화 지원으로 글로벌 사용자 확보 용이

10. 보안과 품질

웹 보안 위협

  • XSS (Cross-Site Scripting): 사용자로부터 입력을 받는 웹 페이지에서 나타나며, 세션 탈취, 악성 스크립트 실행, 데이터 탈취 등을 유발
  • CSRF(Cross-Site Request Forgery): 공격자는 피해자의 브라우저를 이용해 의도치 않은 요청을 서버에 보내도록 하여, 공격자는 피해자가 인지하지 못한 채 악성 행위를 유도

보안

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);
      });
    });

CI/CD 파이프라인의 도입

	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

0개의 댓글