준비 1. 배포 & 번들러의 기초 | 시나브로 자바스크립트
번들러와 html, javascript, css 파일이 어떤식으로 웹서버에서 제공되는지 기본적인 부분 다루기.
예전엔 아파치 서버를 썼는데 실제 서버가 컴퓨터 어딘가에 있고 그걸 웹 서버라 부르지만, 사실은 컴퓨터가 웹의 서버로 돌아가기 위해선 그 안에 웹서버 프로그램이 있어야 함. 컴퓨터 자체가 서버일 순 없으니까. 그 중 하나가 아파치!
이런 경로로 들어오면 index.html을 읽어서 응답해주자. /php로 끝나면 이 파일을 php로 실행시켜서 그 결과를 응답으로 내보내자. 대부분의 것들을 직접 다룰 일은 없고 배포하는 걸 도와주는 netlify 같은 호스팅 플랫폼에서 처리해주고 우리는 모른 채로 사용함.
사실상 path는 /
로 끝나지만 일반적인 관행으론 /
로만 끝나면 index.html 파일을 열어준다. 라는 내부적인 설정이 되어있음.
npm 모듈 중에 statkc-server 라는 것이 있음. global로 설치를 하거나 npx를 사용
npm -g install static-server
npx static-server
static server를 실행해서 파일을 열면 아까랑 약간의 차이가 있는게, 파인더에 있는 파일을 바로 열었을 때는 프로토콜이 파일 프로토콜이고,
브라우저가 그 프로토콜에 따라서 작동하는 것이 조금씩 다름.
실제 웹환경을 생각하면서 개발해보려면 파일을 바로 여는 것이 아닌 로컬호스트에서 특정 호트번호로 열음. 로컬웹서버를 띄어서 작업을 하게 되는 워크플로우를 갖고 있음.
번들러는 왜 사용하는 이유는 많지만, 우리가 직접 할 필요없이 쉽게 할 수 있도록 도와주고 css의 frefix를 붙여준다던가 그냥 css가 아닌 sass를 쓸 수 있게 도와주는 등 다양한 작업들을 해줌.
리액트를 쓴다면 jsx 확장자로 돼있는 파일을 트랜스파일 해서 실제 브라우저가 알아들을 수 있는 js 파일로 바꿔줌. jsx 파일은 브라우저가 알 수 없는 문법이기 때문이.
그런 것들을 처리해주는 것들을 bundler 라고 부름. 하나로 묶어준다는 bundle 단어에서 나온 표현. jsx -> js 변환 과정을 "transpile 한다" 해서 transpiler라고 부름. 두 용어는 의미가 다른데 비슷한 맥락으로 섞어 사용하는 경향이 있어서 정리!
netlify나 vercel 이런 서비스들은 레파지토리를 import해서 그걸 가지고 서버(서비스)를 배포하는 걸 도와줌
const fs = require('fs');
fs.mkdirSync('dist');
// index.html이란 파일을 만들건데, 그 파일의 내용물은 이런 스크립트
fs.writeFileSync(
'dist/index.html',
`
<html>
<body>
<h1>hello, world</h1>
</body>
</html>
`
);
node index.js
로 시작하면 dist 폴더에 index.html파일이 생김.
npm run build
와 비슷
깃허브에서 레포를 만드는 방법이 있고, gh repo create
라고 gh
라는 깃허브 cli?를 사용하는 방법이 있음.
vercel에서 import 하면 되는데, 호스팅 플랫폼들이 똑똑하게 해주고 있는 것들이 우리가 사용하고 있는 레파지토리에 어떤 프레임워크를 써서 배포할지 결정할 수 있게 해주는 방식을 지원함
Build and Output Settings
Build Command를node index.js
로 override
Output Directory : dist
-> Deploying...
거의 모든 자바스크립트 프레임워크들이 작동하고 빌드해서 배포하는 흐름을 따라하는 것. html 파일이 없었지만 어떤 script를 실행하면 결과물이 html, css, js로 떨궈지도록 하는 작업.
어떤 명령을 써서 빌드할지 호스팅 플랫폼에 얘기하고 빌드를 하고나면 생성되는 output이 어떤 폴더인지 지정해주면 다음부턴 플랫폼에서 그 폴더에 있는 정적으로 생성된 html, css, js, 이미지, 폰트 이런 것들을 서빙해줌
다양한 번들러들이 있는데 parcel이 유명한 것 중 하나!
zero configuration에서 최근에 버전2가 나왔는데, 에코시스템에서 꽤 많은 역할을 해온 툴임
Building a web app with Parcel
yarn add --dev parcel
// src/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>My First Parcel App</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
yarn parcel src/index.html
yarn parcel src/index.html
이 뭔 뜻이고, 왜 yarn이 붙을까?node_modules
폴더를 들어가보면, .bin
이라는 폴더에 우리가 설치한 npm 모듈들 중에서 실행시킬 수 있는 명령어를 제공하는 라이브러리들인데 그런 것들이 다 .bin
에 들어가고 그 중 하나가 parcel.
parcel
명령어를 치면 실행이 안 되는데, 사실 경로가 들어가있지 않기 때문임.
yarn parcel
이라는 것은 yarn에서 제공하는 기능 중 하나로 node_modules 아래 .bin 아래 parcel을 실행시킨다는 뜻이 됨
yarn dev
도 마찬가지로 이 경로에 있는 parcel이란 명령어를 실행시켜라 라고 하는 것
// package.json
{
"scripts" : {
"dev" : "parcel src/index.html",
},
...
}
parcel도 build 명령어를 제공함. index.html을 생략하려는 파셀의 컨벤션이라고 보기
{
"name": "my-project",
"source": "src/index.html",
"scripts": {
"start": "parcel",
"build": "parcel build"
},
"devDependencies": {
"parcel": "latest"
}
}
yarn build
를 하면 dist 폴더에 여러 파일들이 생김
그래서 파셀같은 번들러를 사용하면 우리가 여기에 넣은 css 파일을 한 번 처리해줄게있나 처리하고 다른 것들도 처리하고, 브라우저에서 캐시가 먹혀버리면 파일이 바뀌었음에도 예전 거를 로드할 수 있기 때문에 파일명에 해시를 집어넣어 바꾸는 식으로 번들러들이 작동함.
파셀이 돌아가는 방식이다!
또 Vite라는 번들러도 있는데, 얘는 Vue.js 쪽에서 만들어져 나온 툴로 다른 자바스크립트 에코시스템 전반적으로 많이 쓰이는 툴임.
뭐가 좋은지 따지긴 어렵고 이 강좌에선 Vite를 사용할 예정!
yarn dev -> parcel -> 파일저장
요즘 나오는 번들러들이 해주는 건 뭐가 있냐면 저장을 하자마자 브라우저에 리프레시 시켜줌. 이거를 HMR(Hot module reloading) 이라고 부름.
dev 서버가 돌고있는 중인데 이 서버가 하는 일중하나가 이 폴더에 있는 파일들이 바뀌는지 지켜보고 바뀌었을 때 바뀐 사항을 이 페이징 리프레시 시켜줌.
예전에도 있었지만 예전엔 페이지를 전체를 리프레시 했던 방식이었다면, 요즘엔 대부분 그들 나름대로 최선을 다해 바뀐 부분만 동적으로 바꿔치기함!
yarn create vite
Project name: ...
Select a framework: Vanilla
Select a variant: TypeScript
typescript는 브라우저가 이해할 수 있는 언어가 아님. 그냥 타입스크립트일 뿐이고 브라우저에서 쓸 수 있는 언어가 아님. 그래서 Transpiling(하나의 프로그래밍 언어를 다른 언어로 컴파일하는 특별한 형태) 과정을 거쳐야 함
yarn build
를 하면 dist 폴더가 생기고 index.html을 열어보면 아까 우리가 봤던 src 파일과는 달리 index.js 파일이 들어가있음. 번들러가 빌드 프로세스 도중 ts를 js로 만들어서 즉, 브라우저가 이해할 수 있는 코드로 바꿔줌
결과물엔 타입스크립트들의 타입정의 같은 것들도 다 빠져있음. 그런 과정을 거쳐서 돌아간다
vercel과 마찬가지로 파일이 저장되면 바로 바뀌는 것을 볼 수 있음. HMR이 기본적으로 지원이 되는 번들러다!
Tailwind CSS는 클래스에 자잘한 여러 클래스이름들을 입력하면 스타일링해주는 라이브러리. 각각의 클래스네임 하나하나가 아주 조그마한 역할들을 함
우리는 Vite를 선택해서 설치
Instllation - Framework Guides
Tailwind CSS 를 이번 강좌에서 사용하는데, 왜 번들러와 함께 사용해야 할까?
우리가 사용할 버전은 3이지만 2로 되돌아가보면 CDN과 함께 Tailwind CSS를 쓸 수 있음. 근데 얘는 각각의 클래스네임이 특정한 속성을 갖게 되는데 예를 들어 width에서 w-0 이런 세트가 전부가 들어있는데 cdn을 검색해보면 쓸 순 있지만 이런이런 한계가 나와있음. (Using Tailwind via CDN)
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
이 파일을 제공하는데 압축 버전이 꽤 큰 용량을 차지하고, 미디어쿼리 스크린사이즈 별로 모든 설정이 다 들어가 있는 어마어마한 css 파일임. 이미지 한 장도 이 정도 사이즈지만 이미지와 다르게 js와 css 파일은 브라우저가 해석해서 렌더링하고 적용을 하는데 이미지 72kB와 CSS 72k는 무게가 전혀 다름
그렇기 때문에 tailwind css가 cdn을 제공했지만 제약사항이 많고 사용하는 걸 추천하지 않았음. 근데 다 제공하지만 너희가 사용하지 않는 클래스네임들은 이 css 파일에서 제거해주겠다. 그래서 빌드된 조그마한 아웃풋을 사용해라. 라는 그런 프로세스를 가이드했고 릴드미에 나와있음.
purge라는 명령어로 이 경로에 있는 클래스네임들을 쭉 살펴보고 거기에 사용된것만 남긴다. 프로세스를 PostCSS를 통해 거쳤는데
// tailwind.config.js
module.exports = {
purge: [
'./src/**/*.html',
'./src/**/*.js',
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {},
plugins: [],
}
버전3로 오면서 전략이 바뀜! 2의 말미부터 적용되긴 했는데,
Tailwind CSS에서 JIT(Just-in-Time engine)라는 표현을 쓰는데 TWC가 개발하는 npm run dev
이런 명령어를 써서 개발하는 상황에서 우리가 사용하는 클래스네임을 보고 그걸가지고 사용하는 CSS파일을 만들어주는것. 아까랑 반대!
아까는 전체가 다 있고 사용하지 않는 것을 PURGE 지우는 방식이라면, 지금은 네가 쓰는 걸 그때그때 우리가 추가해주겠다. 이런 방식으로 바뀜
클래스명을 아무것도 안 쓴다면 아웃풋으로 생성되는 style.css 이런 파일은 텅 빈 파일이 됨. 정확하겐 리셋해주는 그런 css 지만 이런 차이가 있어서 tailwind css는 번들러와 함께 사용할 수밖에 없다.
얘를 왜 써야 하는가?
In Defense of Utility-First CSS
정답은 없지만 Utility-First CSS를 선호하는 편이고 굉장히 좋은 방식이라 생각.
Node.js를 사용해 작업을 할 때 버전이 많고 계속 새로운 버전이 나오는데, 예전에 만든 프로젝트를 열어보면 예전 버전일 때가 많음. 내가 무슨 버전으로 작업했는지 기억해두지 않으면 npm i
를 하는 순간 갑자기 어떤 라이브러리가 노드 버전이 호환되지 않는 등 많은 일들이 일어남. 노드 버전을 관리하는 것은 매우 중요!
안정화된 정체된 언어마다 조금 다르겠지만 그런 경우엔 버전이 확확 안 올라가는데, node.js는 올라가는 속도가 빠르기 때문에 관리를 잘 해줘야 함. 이걸 해주는 버전 매니저 툴들이 많이 있음
다 비슷비슷한데 대표적으론 nodenv, nvm, 가 있음. Ruby를 쓰는 사람은 RVM, rubyenv라는 게 있고 이런 툴들이 언어마다 있음.
방식이 어떻게 되냐면 환경 변수 중에 PATH
라는 것이 있는데, 터미널에서 echo $PATH
를 쳤을 때 나오는 경로들이 있는데, 이건 code
를 치면 vscode가 열리는데 이 code는 어딘가 실행파일이 있어서 나오는 것임.
근데 그 위치를 루트에서부터 모든 걸 다 찾아다닐 수 없는 건데 이 콜론으로 연결된 문자열. 여러 폴더들이 명시돼있는데 각 폴더들을 돌면서 그 폴더에 코드라는 명령어가 있는지 찾는다. 이런 식으로 돼있음
이런 버전 매니저들이 하는 역할은, 여러 가지 버전에서 원하는 버전을 버전별로 각기 다른 폴더에 node.js를 다 설치함. which node
를 입력하면 이런 경로에 있는 노드다. 를 보여줌. 이 경로를 툴들이 조작해서 node라고 치고 있지만 실제 14버전 밑에 있는 실행파일인지 바꿔치기해줌
nvm install 버전
를 치기만 하면 .nvmrc
파일을 만들어 놓는데, 이 파일에 버전이 들어가 있음. 그 폴더에서 nvm use
를 치면 "이 경로에서 이 버전을 찾았으니 이 폴더에선 이 버전을 쓰겠다." 하고 세팅을 해주는 것. 근데 프로젝트를 열 때마다 계속 터미널에서 nvm use
를 실행시켜줘야 그게 적용됨
그럼 여러 프로젝트를 왔다 갔다 하다 보면, 어떤 프로젝트엔 버전이 12고 어떤 건 14를 다루는 다른 프로젝트에서 쓰다 왔더니 여전히 14가 써지는 상황이 발생함. nvm에는 자동으로 교체되는 기능이 자체적으로 없어서 사람들이 자동으로 바꿔주는 스크립트를 만들어놨음. 그래서 bash를 사용하는 사람들은 ~/.zshrc
이런 파일 같은데 스크립트를 넣어서 폴더가 바뀔 때마다 nvm use
를 실행시키는 이런 스크립트를 썼는데 이게 좀 느림..
그래서 은재 님이 발견한 건 asdf라는 툴!
얘는 nodenv, nvm과 달리 언어에 국한되지 않는 모든 언어를 총괄하는 그런 메타 툴로, 그 안에 플러그인으로써 다양한 언어가 지원되고 있음. 그 안에 asdf-nodejs 플러그인을 보면 되고, 설치는
Install a Version
asdf list all nodejs 14
# or
asdf install nodejs latest
Set할 때는 두 가지가 있는데, global 버전과 local 버전이 있음.
글로벌로 치고 나면 HOME 폴더에 .tool-versions 라는 파일이 만들어져서 nodejs 16.5.0
이런 내용이 들어감. 내가 웬만해선 모든 폴더에서 이 버전을 쓰겠다.라는 것
asdf global nodejs latest
$HOME/.tool-versions
will then look like:
nodejs 16.5.0
특정 프로젝트 내에서 이 버전을 쓰려면 로컬 키워드르 사용해 세팅. 그럼 그 폴더 내에 툴버전 파일이 생김
asdf local nodejs latest
폴더를 이동할 때 버전을 스위칭 해주는 기능이 asdf는 기본으로 장착돼있다!
WES BOS 무료 강좌 중에 Command Line Power User 들어보기!
ZSH가 macOS 특정 버전 이후부턴 기본 탑재가 되기 시작했는데 더 잘 쓸 수 있게 해주는 oh-my-zsh라는 플러그인이 있음. 얘는 자기만의 플러그인 에코시스템을 갖고 있어서 테마랑 플러그인 이것저것 많이 있음. 강사님은 zsh spaceship-prompt을 사용
어떤 번들러든 크게 상관없는 공통적인 것과 vite 만의 특징 알아보기
$ npm create vite@latest
Ok to proceed? (y) y
√ Project name: .
√ Select a framework: » React
√ Select a variant: » JavaScript
# and
$ npm i
$ npm run dev
build를 했을 때 무슨 일이 생기는가?
dist 폴더에 뭔가 빌드된 결과물이 생김. 이걸 살짝 뜯어보면 어떤 일이 생기냐면,,
일단 vite.config.js에 가서 minify 옵션 끄기
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
minify: false,
}
})
다시 build 명령어 실행
어떤 번들러든 간에 minify가 번들링 과정에 기본으로 껴있거나 옵션으로 사용자가 넣도록 하게돼있음. 저 파일을 쭉 내려보면
App.jsx 코드가 맨 아래에 가보면 익숙한 코드가 있음
// App.jsx
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
)
}
export default App
App은 react.exports.useState를 실행시키는데, dlreturn 하는 부분의 jsx 확장자는 브라우저가 이해할 수 있는 것이 아님. 브라우저는 jsx라는 것이 뭔지 몰라서 사용하려면 js 파일로 바꿔야 함. 트렌스파일 과정이 번들러에 포함되어있음
className이 App인 div와
children의 배열 안에, h1과 div가 있는데
첫 번째 children h1의 children은 "Vite + React" 라는 string이 들어가있는 형태
두 번째 children div의 children은 button이고 onCLick은 setCount를 실행하고, 그 버튼의 자식은 "count is " 문자열과, count 변수 두 가지가 들어가는 구조
jsxs
와 jsx
함수가 실제로 뭔가 이 element를 만들고 값을 세팅하고 이런 onClick 이벤트 리스터를 달아주는 기능을 하는 함수겠구나. 를 이해할 수 있음.
그리고 번들러가 해주는 것들 중 또 다른 건 우리가 env(environment variable 환경 변수)을 자주 쓰게 되는데, 그 값을 치환해 주는 일도 함.
작성 후 다시 빌드
function App() {
const [count, setCount] = useState(0);
console.log(process.env.NODE_ENV);
...
}
결과물은 production으로 치환돼있음. 이것도 번들러가 해준 것
function App() {
const [count, setCount] = react.exports.useState(0);
console.log("production");
Vite가 왜 좋은지는 Why Vite 문서에 다 쓰여있음. (사실 개발자에겐 어떤 프로그래밍을 공부하는 것보다 영어를 잘하는 것이 정말 중요.. 부담감 줄이는 것도!)
기존에 이런 프로젝트를 만들고 yarn dev or npm run dev
하고 엔터를 치면 이게 뜨기까지 굉장히 오랜 시간이 걸렸었음. 그 이유는 프론트엔드 프로젝트를 배포할 때 build를 치면 dist 폴더에 생기는 static한 파일들을 브라우저에 로드해야 되기 때문임. 왜냐면 jsx는 브라우저가 알 수 없는 형태의 컨텐츠기 때문에 알 수 있게 transfile을 해줘야 함.
그럼 yarn dev
를 했을 때 벌어지는 일은, 이 프로젝트 전체를 빌드하게됨. yarn dev를 하는 것과 거의 같은 상황. yarn dev
를 실행시키면 모든 걸 다 트랜스파일해서 dist에 컨텐츠가 만들어지고 그거를 띄어주는 것이므로 당연히 오래걸림.
그리고 뭔가를 수정하면 그 해당 파일을 다시 부분적으로 트랜스파일해서 자동으로 화면에서 리로드하는 일들이 백그라운드에서 돌아가는데, vite 같은 경우는 차이가 조금 있음. Slow Sever Start 문제를 해결해주는데, 기존 번들러 같은 경우 entry가 있으면 모든 route를 다 돌면서 모든 module을 다 땡겨서 Bundle을 함. 예를 들어 페이지가 10개 있다면 전부 다 한꺼번에 통채로 배포할 때처럼 번들을 해서 오래걸림.
vite과 같은 Native ESM based sev server 불리는 것들은 어떤 차이가 있냐면, 127.0.0.1:5173
이 경로로 들어가는 순간 그냥 리액트 앱을 했지만 next.js나 라우팅이 잘 돼있는 상황을 했을 때, 예를 들어 /about에 들어가면 어떤 컴포넌트가 마운팅 돼야 할지 알수있는데 그 컴포넌트에서 사용하는 것들만 추려서 실시간으로 서빙을 해줌. 그렇다는 건 확실히 번들링 해야 될 것들이 적어지고 빠르게 실행할 수 있게 됨.
그런 것들이 vite이 해결한 문제 중 하나.
HMR 같은 경우에도 어떤 번들러 같은 경우는 우리가 수정한 파일만 transfile 해서 브라우저에서 리프래시해주면 되는데 경우에 따라 더 많은 것들을 한꺼번에 다 번들링 해야 하는 비효율적인 것들이 있었음. 이런 걸 해결했다. 사용되는 것들만 각각 로드. 그런 것들만 각각 리로드 할 수 있는 상황이 됐다.
vite의 기능으로 hmr을 하기 위해 이런 코드가 변동 사항을 dev server와 같이 맞물려서 브라우저 사이에서 중계를 해준다.
브라우저에서 여러 개의 파일로 로드를 했는데, 프로덕션에서 yarn build를 하면 index.js 큰 하나의 파일로 뭉쳤는지에 대한 설명.
현재 기본 ESM이 널리 지원되고 있지만, 프로덕션에서 번들로 제공되지 않는 ESM을 제공하는 것은 중첩된 가져오기로 인한 추가 네트워크 왕복으로 인해(HTTP/2에서도) 여전히 비효율적입니다. 프로덕션 환경에서 최적의 로딩 성능을 얻으려면 코드를 tree-shaking, lazy-loading 및 common chunk splitting(더 나은 캐싱을 위해)로 묶는 것이 여전히 좋습니다.
dev server와 production build 간에 최적의 출력 및 동작 일관성을 보장하는 것은 쉽지 않습니다. 이것이 Vite가 기본적으로 많은 performance optimizations를 수행하는 pre-configured build command와 함께 제공되는 이유입니다.
알고 쓰는 것과 모르고 쓰는 것은 큰 차이가 있음. 느리더라도 영어로 보는 것을 추천!. 어휘를 자꾸 접할수록 앞으로 개발 문서들을 보는 게 수월해지고 부담감이 줄어듦