Typescript - 12(webpack)

hoin_lee·2023년 8월 3일
0

TypeScript

목록 보기
13/14

아마 13까지 진행하면 typescript의 기본적인 건 거진 끝내는 것 같다!

Webpack

항상 그냥 만들어주는 CRA라던지 그런 걸 사용해서 웹팩을 직접적으로 다루지 않았었는데 한번 잡고 가야 되긴 하는 것 같다

webpack은 의존성을 가진 수십, 수백 개의 파일로 구성된 복잡한 애플리케이션 처리를 돕는다
해당 파일들은 내보내기, 가져오기나 다른 파일로 나누기가 이루어지고 제 3자 라이브러리를 갖는다
Webpack은 이 모든 걸 모아서 번들을 만든다
모두 합치고 압축해서 브라우저에 넣을 수 있는 작은 번들로 만드는 것

가면 갈수록 javascript의 맡는 영역이 넓어지는데 백여개의 각기 다른 스크립트를 어떻게 모두 불러와서 올바른 순서대로 정렬해야 할까?
아니면 가능한 한 작게 번들로 만들어서 최대한 적은 요청을 보내는 게 나을까?

Webpack은 각기 다른 에셋 처리를 도와줌으로 우리는 TS파일과 JS파일만 다루면 된다
다양한 에셋을 모아 정적 에셋으로 번들링 해준다
수많은 라이브러리를 포함하는 백여개의 Javascript 파일이 하나로 합쳐져서 한 스크립트로 불러올 수 잇는 번들 파일이 된다

이게 Webpack의 기본 개념이다

세팅하기

연습할 디렉토리를 만든 후

  • tsc --init 입력하고 npm init -y 입력
  • light server 설치

이후 tsconfig.json파일로 이동한다
include 조건과 outDir조건으로 컴파일을 포함할 폴더랑 컴파일 된 파일이 나올 폴더를 구분
target은 es2015 또는 es6지정, module 또한 ES로 맞춰준다

이후 소스 폴더에 index.tsutils.ts를 만들고 함수를 작성해준다

// utils.ts
export function add(x: number, y: number) {
  return x + y;
}

export function multiply(x: number, y: number) {
  return x * y;
}

export function divide(x: number, y: number) {
  return x / y;
}

이 파일은 연산 함수들을 내보내기 한 곳
그리고 Dog.ts란 파일을 만들어서 클래스를 하나 만들어준다

//Dog.ts
export default class Dog {
    constructor(public name:string, public breed:string, public age:number){}
    bark(){
        console.log("WOOF WOOF !!!")
    }
}

이번엔 Dog클래스를 상속받는 ShelterDog를 만들어보자

// ShelterDog.ts
import Dog from "./Dog"

export default class ShelterDog extends Dog {
  constructor(
    name: string,
    breed: string,
    age: number,
    public shelter: string
  ) {
    super(name, breed, age);
  }
}

이제 클래스와 연산함수들을 index.ts 파일에서 가져오기를 해보면

//index.ts
import Dog from "./Dog"
import ShelterDog from "./ShelterDog"
import {add, multiply, divide} from "./utils"

const elton = new Dog("Elton","Aussie",0.5)
elton.bark(); // "WOOF WOOF !!!"

console.log(add(4,2)) // 6
console.log(multiply(4,2)) // 8
console.log(divide(4,2)) // 2

const buff = new ShelterDog("Buffy","Pitbull",5,"Desert Springs Shelter")

이로써 서로서로의 의존성이 만들어졋다

이제 index.html을 만든 후 타입 모듈로 스크립트 파일을 연결시켜준다

이후 실행을해보면 정상 작동하는 것을 알 수 있는데 Network 탭을 확인해보면 각 파일이 전부 개별 요청에 해당된다
하나하나가 로딩 되었어야 하는 개별 스크립트인 것이다
만약 파일이 여러개이고 제 3자 라이브러리까지 추가된다면 엄청나게 많은 파일들이 로딩 될 것이다

Webpack 의존성 설치

webpack을 사용하기 위해선 패키지 몇 개와 webpack을 설치해야 하는데

npm install --save-dev webpack webpack-cli typescript ts-loader
이것만 먼저 설치를 해보자

일단 Webpack은 모듈 번들러이지만 명령줄에서 사용하거나 package.json파일 내에서 호출하려면 webpack-cli가 필요하다.(둘은 함께 사용됨)
webpack-cli는 webpack의 명령줄 인터페이스로 webpack의 없이는 사용할 수 없으나 webpack의에 포함이 되어 있지는 않는 개별 패키지인 것

여기서 의문 Typescript가 이미 설치되어 있는데도 왜 다운로드를 할까?
이는 package.json에 Typescript를 포함시키는 것이 올바르기 때문이다. 작업하고 있는 버전을 보여주기 때문에 누군가가 이 파일을 다운로드 하게 되면 package.json을 통해 typescript가 필요하다는 사실을 확실히 알려주게 된다
이미 Typescript가 설치되어 있을 거라 추측하는 것보다 안전하게 가능하다

ts-loader 즉, Typescript 로더는 Typescript와 Webpack 사이의 중간자 역할을 한다.
비교적 작은 패키지로 Typescript를 호출해서 이를 모두 번들링하게 될 Webpack으로 전달하는 역할을 한다

Webpack 기초구성

먼저 webpack.config.js란 파일을 만든다(json아니라js다)
이후 module.exports={}안에 엄청 많은 것들을 적을건데 webpack의 공식문서에서 typescript 섹션을 확인해서 예제 파일을 확인하자
하나하나 한번 따져보며 파고들자면

1.가장 먼저 할 작업은 엔트리 포인트의 설정이다
엔트리 포인트는 Webpack에게 번들링을 시작할 애플리케이션의 시작점을 지정해준다
위에서 만든 dog부터 연산함수까지 src폴더를 사용했기에
entry:"./src/index.ts"를 적어준다

2.module프로퍼티추가
해당 프로퍼티는 객체인데 배열이 될 rules 프로퍼티가 포함되며, 여러 개의 규칙을 추가할 수 있다
현재는 Typescript 규칙 하나만 포함하면 되지만, Typescript 혹은 Webpack이 sass파일이나 일반 javascript파일, CSS 파일이나 정적 에셋을 만났을 때의 규칙을 정해줄 수 있다

module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
};

module은 객체고 rules는 배열이다. 먼저 우리가 설치한 ts-loader를 사용하라는 규칙을 만든다
.ts, 혹은 .tsx로 끝나는 파일이 있을시를 표기해준다
/\.tsx?$/ 정규식에서 $는 이 패턴이 파일의 맨 마지막에 와야 한다는 의미이며 파일은 ts로 끝나거나, 혹은 뒤에 선택적으로 tsx가 올 수 있다는 의미이다(?가 선택적이기에 x는 선택적)

그럼 test를 통해 파일 형식을 정규식으로 전달했고 webpack이 해당 파일을 찾게 되면 무엇을 사용할 지 지시한다
여기선 당연 ts-loader를 사용하라고 지시하고 마지막으로 exclude를 추가하는데 이역시 정규식으로/node_modules 입력하면 해당 디렉터리는 건드리지 말라고 얘기하는 것이다

다른 로더를 사용하는 경우 다른 규칙을 추가할 수 있다

3.resolve작성
module 다음에 resolve가 나오고 확장자 목록이 들어간다
프로퍼티중 extensions에는 Webpack이 리졸브 할 수 있는 확장자의 리스트가 들어간다

module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
};

4.output작성
webpack으로 생성하려는 출력이다

const path = require("path");
module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};

bundle.js를 생성해야 할 건데 파일네임을 그대로 적어주자 이후 path가 등장하는데 공식 문서 예시를 보면 상단에서 requirepath에서 가져온 path.resolve메서드를 사용하고 있다
resolve메서드는 하드코딩 되어 있지 않은 path name을 구축할 수 있도록 해주는 메서드로 다른 디렉터리에서 실행을 해도 작동이 되도록 도와준다!
그래서 사용하는 것이다

  • bundel.js라는 파일을 생성하고
  • 디렉터리 안에 있는 dist 폴더 안에 넣으라고 지시를 하는 것
  • 그럼 디렉터리 이름을 통해 경로를 resolve 할 것이다
  • __dirname으로 지정되어 있고 'dist'로 첨부를 하는 것

5.package.json에서 스크립트 추가하기
"script"부분에 "build"라고 하고 "build":"webpack"을 입력해서 cli에 build를 입력하면 webpack을 실행시키도록 한다
npm run build를 호출해 보면 오류가 발생하지만 Webpack은 정상적으로 실행이 된다

위에서 만든 Dog 클래스등이 있는 파일들을 그대로 사용했는데 먼저 import에 있는 파일 확장자 .js를 모두 지웠다

그러니 정상적으로 실행되었고 모든 의존성이 있는 bundle.js파일이 생겼다

이후 index.html파일로 가서 원래 index.js로 받아오고 있던 스크립트를 bundle.js로 가져오게 변경한다. (모듈타입도 필요없다)
이후 서버를 실행해보면 정상 동작을 확인해볼 수 있다

소스 맵 추가

소스 맵은 압축되어 있는 번들 코드를 디버깅하고 이해하는 데 도움을 준다
역매핑을 통해 빌드 전의 상태를 보여줌으로써 번들을 구성하고 있는 코드가 어디서 오는지를 보여준다

  • 먼저 Typescript에게 tsconfig.json 파일 내에 sourceMaptrue로 설정되어야 한다고 지시한다
  • 다음 webpack.config.js 파일 내의 Webpack에게 해당 소스 맵을 추출해 최종 번들에 포함하라고 지시한다

typescript는 json파일 안에서 주석으로 들어가 있으니 찾기를 이용해 검색하면 해당 프로퍼티를 찾아서 바꿔주면 된다
webpakc에서는 하나를 추가해주면 되는데 devtool: "inline-source-map"를 추가해주자

const path = require("path");
module.exports = {
  entry: "./src/index.ts",
  devtool: "inline-source-map", //추가한 코드
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};

이제 다시 번들링을 하면 브라우저에 소스 맵이 작동한다
개발자 도구의 소스에 들어가서 구름 모양 아이콘의 webpack_ts를 확인하면 각 파일들을 확인해 볼 수 있다

webpack 개발서버

여기까지 했으면 추가적으로 볼게 build를 할때 WARNING을 확인할 수 있는데
빨간 줄로the 'mode' option has not been set,...으로 mode 옵션을 설정하지 않았으므로 webpack이 값을 프로덕션으로 풀백 하겠다는 뜻이다
우리는 개발을 해야하니 개발을 설정해야한다
webpack.config.js파일로 가서 또 하나를 추가해준다 mode: "development"

const path = require("path");
module.exports = {
  mode: "development",
  entry: "./src/index.ts",
  devtool: "inline-source-map", // 추가 코드
  //...some code
};

그러면 개발 단계에 있기 때문에 bundle 파일은 경량화 되지 않는다. 이는 프로덕션과의 차이점이다
그러면 현재 lite-server로 서버를 사용하는데 Webpack Dev서버로 바꾸고 싶다
먼저 webpack-dev-server를 설치해주자

npm install --save-dev webpack-dev-server

그리고 package.json에서 스크립트를 바꿔주자 "serve": "webpack serve"
그 다음으로 webpack.config.json으로 돌아와 output내에 publicPath프로퍼티를 추가하고 /dist로 설정한다

const path = require("path");
module.exports = {
  //...some code
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
    publicPath: "/dist", //추가 코드
  },
};

webpack 서버로 작동시킬 경우 bundle.js를 파일로 계속 만드는 것이 아닌 메모리에 저장해서 사용한다. 즉 서버에는 있고 dist라는 폴더에는 존재하지 않는다
이후 서버를 중단하고 build를 하면 webpack은 번들파일을 만들고dist폴더에 집어 넣는다

프로덕션 구성

현재는 항상 개발 모드로 설정되어 있고 항상 devServer를 사용한다
다만 개발 모드로 설정하면 프로덕션 모드일 때 가능한 경량화가 되지 않는다
두 옵션 모두 사용하고 싶다면 어떻게 할까?
webpack.config.js를 만들때 dev구성 파일과 prod 구성파일을 만든 다음 병합하면 된다.
위 예시에서 계속 그대로 써보자면

  • 먼저 작성한 구성을 복사해서 두번째 파일에 붙여넣는다(파일이름은 크게 중요하지 않기에 webpack.prod.js로 만들어보자)
    이름을 참조로 webpack을 가리킨다
//webpack.prod.js
const path = require("path");
module.exports = {
  mode: "production", // production으로 교체
  entry: "./src/index.ts",
  devtool: "inline-source-map",
  //...some code
};

이후 package.json으로 간다
"build"스크립트를 실행할 때 --config를 사용해 방금 생성한webpack.prod.js를 가리키면 해당 구성 파일을 사용하게 된다
"build" : "webpack --config webpack.prod.js"

webpack.config.js를 찾는 게 기본값이지만 해당 옵션을 사용하면 원하는 파일을 가리킬 수 있다



※여기서 자주 사용하는 게 있는데 현재는 build를 할때마다 bundle.js파일이 생성되기 때문에 파일이 쌓이지만 해당 파일들을 새롭게 build할 때마다 지워주는 플러그인이 있다

평군적으로 webpack.config.js에서 output 쪽에 filename 프로퍼티의 값을
[contenthash].bundle.js식으로 만들어서 해시가 되고 webpack이 자동으로 파일이름에 추가하게 된다
이 방법을 많이 사용하는 이유는 내용이 변경된 다른 파일이란 것을 브라우저가 인식하도록 돕기 때문이다. 캐싱 문제를 피할 수 있다
이러한 문제를 CleanWebpackPlugin으로 해결할 수 있는데 npm으로 설치하고 플러그인을 사용하라고 지시하면 된다
npm install --save-dev clean-webpack-plugin으로 설치한다

이후 번들 파일은 프로덕션 때만 생성되니 webpack.prod.js파일로 가서 마지막에 플러그인을 추가한다

//webpack.prod.js
const path = require("path");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  mode: "production",
  entry: "./src/index.ts",
  devtool: "inline-source-map",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
    publicPath: "/dist",
  },
  plugins: [new CleanWebpackPlugin()] //추가코드
};

이렇게 작성하면 가장 최신 번들만 남게 된다.(혼선방지를 위해 [contenthash]는 삭제)

이후 마지막으로 파일 구성 이름을 통일하게 위해 webpack.prod.js와 동일하게 webpack.config.json의 파일이름을 webpack.dev.json으로 변경하여 각각 프로덕션일때 개발일때 구성파일로 사람이 파일 이름만 읽어도 확인되게끔 해준다.
(이때 변경할 때 package.json파일로가서 --config로 구성파일을 특정하는 것 까먹지 말자! dev로 바꾼 파일도 마찬가지로 연결해줘야 한다)

profile
https://mo-i-programmers.tistory.com/

0개의 댓글