브라우저는 화면을 어떻게 그릴까?
프론트엔드 면접 질문이라고 하면 거의 100% 포함되어 있는 내용이다.
그렇다면 웹 개발을 하기에 정말 중요한 내용이라는 뜻인데, 왜 그럴까?
사실 브라우저가 어떻게 동작하는지를 이해하는 것은 당연히 중요하다.
컴퓨터 프로그램은 컴퓨터에서 동작하기 때문에 개발자라면 컴퓨터가 어떻게 동작해야 하는지 아는 것이 중요한 것 처럼 웹 개발자라면 개발한 웹이 브라우저에서 동작하기 때문에 동작 원리를 아는 것은 매우 중요하다. 그 중에서 화면에 렌더링이 어떻게 되는지를 알게 된다면 특히 웹 최적화 관련하여 많은 아이디어를 얻을 수 있을 것 같다.
서버, 인프라, 클라이언트까지 하나의 싸이클을 모두 설명할 수 있다면 가장 좋을 것이고, 프론트엔드 개발자라면 적어도 클라이언트에 대해서는 설명할 수 있어야 할 것이다.
우선 서버에 HTML 파일을 요청하는 것 부터 시작한다. / 와 같은 index 경로로 서버에 요청을 보내면 서버는 클라이언트에 index.html 파일을 전달하게 된다. 브라우저는 index.html 파일을 읽는다. HTML 파서가 위에서부터 아래로 순서대로 HTML 파일을 읽어서 DOM Tree를 구성하게 된다. 이 과정에서 link 태그를 만나거나, script 태그를 만나게 되면 css와 js 파일을 서버에 요청하여 다운로드 받게 된다. css를 받아오면 CSS Parser가 CSSOM Tree를 만들게 된다. DOM Tree와 CSSOM Tree가 합쳐져서 Render Tree가 생성이 되는데 이를 가지고 브라우저에 어떻게 그려져야 할 지 Layout을 잡는다. 그 후에 Painting 과정을 거쳐서 우리가 볼 수 있는 웹 화면이 나타나게 된다.
왜 jQuery 대신 React를 사용해야 할까?
1. 명령형(imperative)에서 선언형(Declarative)으로
2. Re-flow 과정을 줄인다. 성능 최적화.
NodeJS와 NPM
예전의 자바스크립트는 오직 브라우저에서만 동작하고 정말 단순한 동작을 위해 만들어진 스크립트 언어였다.
그 후에 크롬의 V8 엔진을 브라우저 외부로 꺼내서 NodeJS라는 런타임 환경이 나오게 되었다.
NPM은 NodeJS로 만들어진 패키지들을 관리해주는 매니저다.
Babel과 Webpack
Babel은 자바스크립트 코드를 변환해주는 트랜스파일러이다. 공식 문서에는 Compiler라고 나와있긴 하다.(Babel is a JavaScript compiler.) 대표적으로는 ES6 코드를 ES5로 바꿔주는데 이는 구형 브라우저에서는 ES6를 지원하지 않기 때문에 구형 브라우저에서 ES6 문법을 사용하기 위해 사용하고 또 리액트 코드를 훨씬 편리하게 작성하기 위해 사용하는 JSX를 JS로 변경하는 역할을 한다.
Webpack은 모듈 번들러이다. 기본적으로 Create React App에 Babel과 Webpack이 포함되어 있다. 최근에는 ESBuild, Rollup, swc, Turbopack등이 많이 사용이 되는 것 같다. JS 파일을 압축하고, 난도고하하고, 불필요한 파일을 제거하는 등(트리쉐이킹)의 동작을 한다.
React
React는 컴포넌트 단위로(Component Driven Development) UI 구성을 도와주는 라이브러리다.
State(상태)값을 기반으로 동작하고 가상돔(Virtual Dom)을 이용합니다.
프로젝트 구성
1. 프로젝트 생성
원하는 경로에 프로젝트 폴더를 생성합니다.
2. package.json 설정
프로젝트에 package.json 파일을 구성합니다.
$ npm init
package.json의 옵션을 특별하게 잡아주지 않고 빠르게 생성하려면 아래 명령어를 사용하면 됩니다.
$ npm init -y
3. eslint, prettier 설정
먼저 vscode를 사용한다면 eslint와 prettier 익스텐션을 설치해줍니다. vscode와 연동하기 위함입니다.
eslint를 설정하기 위해 아래의 패키지를 설치해줍니다.
$ npm i -D babel-eslint eslint@7 eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard
프로젝트의 root(최상위) 경로에 .eslintrc 파일을 생성해주고 아래 옵션을 적용해줍니다.
{
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": ["import", "node", "promise", "standard"],
"env": {
"browser": true,
"node": true,
},
"extends": [
"eslint:recommended",
],
"rules": {
"no-console": "error"
}
}
- parser: eslint 의 입맛에 맞게 코드를 읽고 변환해준다.
- parserOptions: 읽어올 코드에 대한 정보
- plugins: 기본 규칙외에 부가적인 규칙
- env: 개발할 코드의 환경
- extends: 베이스가 되는 룰
- rules: 유저가 정의한 룰
기본 추천룰("eslint:recommended") + rules 에 기반하여 작성한 코드를 검사한다.
CI 단계에서 lint를 돌려서 코드를 확인하고 만약 린트 룰을 어겼을시 통합을 시키면 안된다.
prettier를 설정하기 위해 아래의 패키지를 설치해줍니다.
$ npm i -D eslint-config-prettier eslint-plugin-prettier prettier
먼저 .eslintrc 파일에 eslint-plugin-prettier 정보를 추가해줍니다.
{
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": ["import", "node", "promise", "standard"],
"env": {
"browser": true,
"node": true,
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"rules": {
"no-console": "error"
}
}
eslint와 동일하게 root 경로에 .prettierrc 파일을 만들어서 아래의 옵션을 설정해줍니다.
{
"useTabs": false,
"printWidth": 80,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"semi": false,
"arrowParens": "always"
}
prettier도 CI 단계에서 돌려주어 코드를 포맷팅해주면 좋고 vscode에서 코드를 저장할 때 마다 prettier를 실행시키기 위해 User Settings에 아래 코드를 추가해준다. 또는 root 경로에 .vscode/settings.json 파일에 아래 코드를 추가해준다.
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"files.eol": "\n",
}
eslint나 prettier는 모든 파일에 적용할 필요는 없기 때문에 .gitignore과 동일하게 ignore 파일을 만들어서 제외할 수 있는 파일을 표기해줄 수 있다. eslint는 .eslintignore, prettier는 .prettierignore 파일을 생성하고 아래의 코드를 추가해준다.
node_modules
dist
4. babel, webpack 설정
Babel은 JavaScript Transpiler이고 특정 타겟 버전으로 코드를 변환해준다.(ES6 -> ES5)
필수적인 모듈은 아래와 같다.
- babel/core: 바벨이 동작하기 위한 핵심 모듈을 담고 있다.
- preset: 어떻게 트랜스파일링을 할 지 규칙을 담고 있는 상자다.
$ npm i -D @babel/core @babel/preset-env
프로젝트의 root 경로에 .babelrc 파일을 만들어 아래 옵션을 설정해준다.
{
"presets": ["@babel/preset-env"]
}
Webpack은 모듈 번들러입니다. JS 파일들을 묶거나 난독화를 이용하여 압축하는등의 일을 할 수 있게 해줍니다.
필수적인 모듈은 아래와 같다.
- babel-loader: webpack은 파일 단위를 다루는 모듈 번들러이기 때문에 코드를 읽고 변환하는 역할은 babel이 담당할 수 있도록 babel-loader를 함께 사용한다.
- webpack-cli: 커맨드 라인에서 웹팩 명령어를 사용할 수 있게 도와준다.
- webpack-dev-server: 개발환경을 편하게 만들어주는 개발 서버이다.
$ npm i -D webpack webpack-cli webpack-dev-server babel-loader
프로젝트 root 경로에 webpack.config.js 파일을 생성하고 아래 옵션을 설정한다.
- entry: 빌드의 시작점
- output: 빌드된 파일이 어디에 위치할지
- module: 웹팩의 동작에 도움을 주는 loader 들이 위치 (ex. babel-loader, style-loader 등)
- extensions: import 시 생략가능한 확장자들을 정의
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, './src'), // src 내부의 index.js 를 바라본다
output: { // 빌드한 결과물을 어디에 생성할 것 인가
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [ // 어떤 파일들을 어떤 loader 를 이용하여 해석 할 것 인가
{
test: /\.(js)$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
resolve: {
extensions: ['.js'], // .js 확장자 생략 가능
},
}
output의 hash가 중요한데, 동일한 파일명으로 번들 파일을 만들게 되면 브라우저가 자체적으로 캐싱을 하기 때문에 배포를 해도 이름이 같아서 서버로부터 새로운 js 파일을 다운로드 받지 않고 캐싱되어 있는 js 파일을 사용하게 된다. 이러한 문제를 해결하기 위해 [hash]를 사용하여 번들링을 할 때 마다 unique한 파일명을 만든다. 매번 번들 파일명이 달라지기 때문에 번들링을 할 때마다 html 파일에 js 파일명을 수정해줘야 하는데 이를 자동화하기 위해 html-webpack-plugin을 사용한다.
$ npm i -D html-webpack-plugin
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: path.resolve(__dirname, './src'),
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
resolve: {
extensions: ['.js'],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: './index.html',
}),
],
}
개발을 할 때 매번 빌드를 해서 결과물을 확인하는 것은 매우 비효율적이기 때문에 webpack-dev-server를 이용하여 이를 간편하게 한다.
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: path.resolve(__dirname, './src'),
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
resolve: {
extensions: ['.js'],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html', // 기준이되는 html 파일
filename: './index.html', // 빌드 후 사용할 html 파일 이름
}),
],
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
inline: true,
hot: true,
open: true
},
}
마지막으로 아래 스크립트를 작성한다.
"scripts": {
"lint": "eslint .",
"prettier": "prettier .",
"build": "webpack --mode production",
"dev": "webpack serve"
}
'TIL > 개발' 카테고리의 다른 글
요즘 개발자 베타리딩 - 2주차 (0) | 2023.11.09 |
---|---|
프론트엔드에서의 비즈니스 로직은 어떻게 분리할 수 있을까요? (0) | 2023.11.09 |
React에서 useCallback은 언제 사용해야 할까? (0) | 2023.11.08 |
BFF(Backend For Frontend)는 어떤 문제를 해결하나? (0) | 2023.11.07 |
[Astro] Astro 3.0에서 달라진 것들 (0) | 2023.09.01 |