프론트엔드 개발환경의 이해와 실습 4
4️⃣
🖥 웹팩(Webpack) - 심화편
5 - 1. 웹팩 개발 서버
- webpack-dev-server : 개발용 서버 제공
- $ npm i webpack-dev-server
- 이후 package.json 에서도 start:”webpack-dev-server”를 추가해준다.
- 코드 변화를 감지해서 결과물을 브라우저에 실시간으로 보여준다.
- 😶기본설정
- webpack.config.js
- contentBase: 정적파일을 제공할 경로, 기본값은 웹팩에서 설정한 아웃풋
output / path: path.resolve(“./dist”) 이기 때문에 빌드된 폴더를 contentBase로 하고있다
- publicPath: 브라우저를 통해 접근하는 경로, 기본값은 ‘/’
- host: 개발환경에서 특정 도메인을 맞춰야하는 경우에 사용
쿠키기반의 인증은 도메인이 같아야 한다. 그럴경우 host를 이용해서 도메인을 등록
- overlay: 빌드시 에러나 경고문구 브라우저에 출력
devServer:{overlay: true}
- port: 개발서버포트 설정 (기본값은 8080)
- stats: 메세지 수준을 정할 수 있다 -> 메세지: 웹팩 서버를 돌리고 났을때 나오는 메세지 / ‘none’, ‘errors-only’, ‘minimal’, ‘normal’, ‘verbose’ 로 메세지 수준을 조절
- historyApiFallBack: SPA 개발할때 라우팅을 프론트엔드에서 관리함, 그때 404가 발생하면 index.html로 리다이렉트 할수있도록 설정 / SPA 개발할때 사용할 수 있는 옵션
- package.json / start : –progress 추가
- –progress : 빌드상태를 0~100%까지 볼 수 있다.
5 - 2. API 서버 연동
- 😶목업 API 1
- webpack dev server는 가짜데이터를 ( Mockup API) 제공하는 기능이 있다.
// webpack.config.js
devServer: {
...,
before: (app) => {
// app : 개발서버를 받는다
app.get("/api/users", (req, res) => {
// app은 서버어플리케이션에서 많이 사용하는 expressjs 라는 프레임워크
// expressjs의 서버인스턴스를 app 이라는 이름으로 웹팩데브서버가 넣어준다.
// before함수 안에서 웹팩데브서버의 기능을 확장 할 수 있다.
// app.get 메소드로 이루어진 api를 만들어내는 함수.
// "/api/users" 라는 api 를 만들고 이쪽으로 요청이 들어오면 req 부터의 함수를 실행한다.
// 응답을 위한 res 객체로 api 응답을 줄 수 있다.
res.json([
{ id: 1, name: "Bill" },
{ id: 2, name: "James" },
{ id: 3, name: "mia" },
]);
});
},
},
- 위와 같이 api를 만들수있다.
- 만든 api 를 프론트엔드에 사용하려면 ..
- ajax 요청을 호출하는 라이브러리 중에 많이 사용되는것 -> axios
- $ npm i axios
//app.js
import axios from "axios";
document.addEventListener("DOMContentLoaded", async () => {
const res = await axios.get("/api/users");
console.log(res);
document.body.innerHTML = (res.data || [])
.map((user) => {
return `<div>${user.id}: ${user.name}</div>`;
})
.join("");
});
- 만든 api를 잘 받아온다.
- 😶목업 API 2
- 목업 API가 많이 필요할때는 전용 패키지를 사용하는 것이 방법일 수 도 있다. : connect-api-mocker
- API 응답 값을 JSON 파일로 만들어 놓고 개발서버에 연동시키는 방법
- $ npm i connect-api-mocker
- 기존에는 webpack dev server 에서 관리했다면 ( webpack.config.js before) 이제는 파일로 관리할수있다.
- 루트에 mocks/api/users 폴더를 만든 뒤, 메소드명인 GET.json 으로 파일을 생성
//GET.json
// 아까만든 json 배열을 가져온다.
[
{ "id": 1, "name": "Bill" },
...
]
// webpack.config.js
const apiMocker = require("connect-api-mocker");
devServer: {
...,
before: (app) => {
app.use(apiMocker("/api", "mocks/api"));
// apiMocker(urlRoot: any, pathRoot: any): (req: any, res: any, next: any) => any
//"/api" -> api 로 요청오는건 모커가 처리하는 것.
},
},
- 이렇게 처리해주면 아까와 똑같이 동일하게 화면에 랜더링되는것을 확인할 수 있다.
- 이렇듯 실제 api 가 나오기 전에 목업 API를 이용하여 사용할 수 있다.
- 😶실제 API 연동
- 같은 도메인에서만 API를 받아올 수 있다 ( CORS 정책 )
-
도메인을 같게 하려면 서버 브라우저에서 설정해 줄 수 있다. - 프록시(proxy)
// webpack.config.js
module.exports = {
devServer: {
proxy: {
"/api": "http://localhost:8081", // 프록시
},
},
}
// src/model.js
const model = {
async get() {
// const { data } = await axios.get('http://localhost:8081/api/keywords');
const { data } = await axios.get("/api/keywords")
return data
// 프록시에서 api주소를 바꿨기때문에 위와같이 작성하면 잘 받아온다
},
}
5 - 3. API 서버 연동(실습)
5 - 4. API 서버 연동(풀이)
- 현재 문제는 서버는 API서버는 8081 / 내가 켜놓은 서버는 8080
- 아래처럼 쉽게 해결 할 수 있다.
// webpack.config.js
module.exports = {
devServer: {
proxy: {
"/api": "http://localhost:8081", // 프록시
},
},
}
5 - 5. 핫로딩
- 😶핫 모듈 리플레이스먼트 : 변경된 모듈만 갈아치우는 것 -> 이전 데이터 유지 ( 전체화면을 리플래쉬하지 않고 )
- 😶설정
devServer: {
...
hot: true,
},
- 제대로 사용하기 위해서는 hmr 인터페이스를 맞춰주어야 한다.
// entry 포인트인 src/app.js
if (module.hot) {
console.log("hot module on!");
// module.hot.accept -> 변경을 감지할 모듈을 정할 수 있다.
// result.js 파일이 변경되었을 때만 해당 콘솔을 찍는다.
// 감지하고자 하는 모듈이 변경되었음을 인지하고 콜백함수 실행
// 변경된 모듈만 변경된다.
module.hot.accept("./result", async () => {
console.log("result module change!");
resultEl.innerHTML = await result.render();
// result가 변경되면
// 변경된 result모듈의 랜더함수 실행
});
module.hot.accept("./form", async () => {
console.log("form module change!");
formEl.innerHTML = await form.render();
});
}
- 😶핫 로딩을 지원하는 로더
- 모듈 자체가 핫 모듈 리플레이스먼트를 지원해야 가능. -> 인터페이스를 맞춰줘야 한다 -> hmr 인터페이스
- 대표적으로 스타일로더가 핫로딩을 지원 / 파일로더 / 리액트 / 뷰 등등
5 - 6. 핫로딩(실습)
5 - 7. 핫로딩(풀이)
5 - 8. 최적화
- 웹팩으로 번들된 결과를 어떻게 하면 최적화 할 수 있을까?
- 😶production 모드
- 가장 쉬운 방법 : mode 설정
- webpack.config.js mode에 production 을 주면 운영환경에 적합한 모드를 주는 것.
- 기존에 사용했던 devleopment 는 디버깅의 편의를 위해 플러그인을 두개 사용한다. ( NamedChunksPlugin, NamedModulesPlugin)
- 반면 production 으로 설정하면 js 결과물을 최소화 하기 위해 7개의 플러그인을 사용한다.( FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin, TerserPlugin)
//webpack.config.js
const mode = process.env.NODE_ENV || "development";
// 외부환경변수에 따라서 웹팩을 devleopment || production 으로 빌드할수있다.
module.exports = {
// module.exports -> node 의 모듈 시스템
mode,
...
}
//package.json
"scripts": {
"build": "NODE_ENV=production webpack --progress",
},
// "NODE_ENV=production webpack --progress" 또는 "NODE_ENV=development webpack --progress",
// 로 빌드시 어떻게 파일이 변화하는지 확인할 수 있다.
- production 으로 빌드시 플러그인에 의해서 결과물이 난독화 된다.
- development 로 빌드시 코드를 알아볼 수 있게 빌드된다.
- 😶optimization 설정
- HtmlWebpackPlugin이 html 파일을 압축한것 처럼 css 파일도 빈칸을 없애는 압축을 하려면 -> optimize-css-assets-webpack-plugin 사용
- $ npm i -D optimize-css-assets-webpack-plugin
- 다른 플러그인과 다르게 plugins 안에서 설정하는것이 아닌 optimization에서 설정
//webpack.config.js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
devserver: {...},
optimization: {
// minimizer - 압축할수있는 플러그인을 추가
minimizer: mode === "production" ? [new OptimizeCSSAssetsPlugin()] : [],
},
}
- 이외에도 TerserWebpackPlugin -> 자바스크립트 코드를 난독화 하고 디버거 구문을 제거 : 기본설정
- 이외에도 콘솔로그를 제거할수도 있음.
- $ npm i -D terser-webpack-plugin
//webpack.config.js
const TeserWebpackPlugin = require("terser-webpack-plugin");
module.exports = {
devserver: {...},
optimization: {
minimizer:
mode === "production"
? [
new OptimizeCSSAssetsPlugin(),
new TeserWebpackPlugin({
// 생성자에 옵션값을 전달
terserOptions: {
compress: {
drop_console: true, // 콘솔로그를 제거
},
},
}),
]
: [],
},
}
- 😶코드 분할
- 프로젝트가 커지면 파일 자체가 커지기때문에 파일을 분할하는 방법
- entry 포인트를 두개로하고 파일을 두개로 분할하면 빌드는 두개의 파일이 되지만, 중복되는 내용이 있다.
- 이때 splitChunks 사용.
//webpack.config.js
optimization: {
minimizer:
mode === "production"
? [
new OptimizeCSSAssetsPlugin(),
new TeserWebpackPlugin({
// 생성자에 옵션값을 전달
terserOptions: {
compress: {
drop_console: true, // 콘솔로그를 제거
},
},
}),
]
: [],
splitChunks: {
Chunks: "all",
},
//중복부분을 지워준다.
},
- build 후 vendors~main~result -> main.js 와 result.js 의 중복되는 코드들
- 위와 같은 방법은 엔트리포인트를 적절히 분리해야하기 때문에 자동화 방법이 필요한데 -> 다이나믹 임포트 ( 동적 임포트 )
- 😶다이나믹 임포트
// app.js
import(/* webpackChunkName: "result" */ "./result.js").then(async (m) => {
// 해당 주석을 보고 result라는 번들 결과를 따로 만들어준다.
const result = m.default;
const resultEl = document.createElement("div");
resultEl.innerHTML = await resultEl.render();
document.body.appendChild(resultEl);
});
- webpackChunkName 지정한 파일명으로 verdors~파일명.js 번들을 추가로 만든다.
- 코드스플리팅은 개발 초기에가 아닌, 커졌을때 분리해도 늦지 않음!
- 😶externals
- 번들하지 말아야 할 대상(ex axios)들을 빌드 범위에서 빼주는 것
//webpack.config.js
externals: {
axios: "axios",
// 웹팩으로 빌드할때 axios 모듈을 사용하는 부분이있으면 전역변수 axios를 사용하는것으로 간주해라.
// 전역에 axios를 가지고 있어야함 ( 이왕이면 dist 안에 )
},
- 웹팩이 실행될때 파일을 복사 -> 복사할 라이브러리 : copy webpack plugin
- $ npm i copy-webpack-plugin
//webpack.config.js
const CopyPlugin = require("copy-webpack-plugin");
plugins: [
new CopyPlugin({
patterns: [
{
from: "./node_modules/axios/dist/axios.min.js",
to: "./axios.min.js",
//from 어디에 있는걸 복사할지
// to: ./axios.min.js ~로 받아온다. (dist)}
},
],
}),
]
// index.html
<body>
<script type="text/javascript src="axios.min.js"></script>
</body>
- 웹팩에 빌드할 필요없는 외부 라이브러리는 externals로 빼는것이 좋다. ( 웹팩 빌드시간 단축 )
5 - 9. 최적화(실습)
5 - 10. 최적화(풀이)
- $ ls -lh dist
- dist 용량확인 명령어
TerserWebpackPlugin 관련 자꾸 오류가 난다 ㅠㅠ 빌드한 주소가 localhost 8081 에서 보여야하는데, .. 그것도 실패 ㅠ