webpack 기본 이해하기

webpack은 서로 연관 관계가 있는 웹 자원들을 js, css, img와 같은 스태틱한 자원으로 변환해주는 모듈 번들러입니다. 자원들을 최적화해서 압축 등 웹페이지의 성능을 끌어올려줍니다.

webpack 철학

  1. 모든것이 모듈이다. - 모든 웹 자원(js, css, html)이 모듈 형태로 로딩 가능
require("base.css");
require("main.js");
  1. 불필요한 것을 로딩하지 않는다. - 필요할 때 필요한 것만 로딩!

CLI

install

npm i webpack webpack-cli -g
npm i webpack webpack-cli

build

webpack
webpack src/index.js -o dist/bundle.js
webpack src/index.js --output dist/bundle.js --mode=development --display-modules
webpack --display-modules // 숨겨진 모듈 표시
webpack --watch // webpack에서 파일의 변경이 일어나면 자동으로 번들링을 다시 진행
webpack -d      // sourcemap 포함하여 빌드
webpack --display-error-details // error 발생 시 디버깅 정보를 상세히 출력

webpack.config.js

// default
var path = require("path");

module.exports = {
	mode: "", // 빌드 모드. development || production || none
	entry: {}, // 시작점
	output: {}, // 결과물
	module: {}, // loader. 특정 파일 형식을 인식
	plugins: [], // Output 시점에 관여하는 커스텀 기능
	resolve: {}, // 모듈, chunk 해석 방식을 정의
};

entry

  • 시작점
// 여러가지 Entry 유형
var config = {
    // #1 - 간단한 entry 설정
    entry: './src/index.js'
    // #2 - 앱 로직용, 외부 라이브러리용
    entry: {
        app: './src/app.js',
        vendors: './src/vendors/js' //third-party libarary
    }
    // #3 - 페이지당 불러오는 js 설정
    entry: {
        pageOne: './src/pageOne/index.js',
        pageTwo: './src/pageTwo/index.js',
        pageThree: './src/pageThree/index.js',
    }
}

output

  • 결과물
// entry에서 설정하고 묶은 파일의 결과값을 설정
var path = require("path");
module.exports = {
	entry: {
		//...
	},
	output: {
		path: path.resolve(__dirname, "dist"), //__dirname은 현재 모듈의 현재 폴더 위치
		filename: "bundle.js",
		// filename: '[name].js'
	},
};

path.join() & path.resolve()

path.join("/foo", "bar", "baz/asdf"); // 해당 OS의 파일구분자를 이용하여 위치 조합
// Returns: '/foo/bar/baz/asdf'

// resolve는 오른쪽 인자부터 시작해서 절대 경로가 만들어질 때까지 명시된 경로를 합쳐나감
// 만약 결과값이 유효하지 않으면 "현재 디렉토리"가 사용. 반환되는 위치값은 항상 absolute URL.
// 사용하는 이유 : 여러 로더들이 복잡하게 되면, 유효하지 않은 위치가 반환될때 해결해주고, 검증된 url을 반환.
path.resolve("/foo/bar", "./baz");
// Returns: '/foo/bar/baz'

path.resolve("/foo/bar", "/tmp/file/");
// Returns: '/tmp/file'

path.resolve("wwwroot", "static_files/png/", "../gif/image.gif");
// if the current working directory is /home/myself/node,
// this returns '/home/myself/node/wwwroot/static_files/gif/image.gif'

https://nodejs.org/api/path.html

Output filename options

// 복수개 entry point에 대한 [name] 예시
module.exports = {
	entry: {
		Profile: "./profile.js",
		Feed: "./feed.js",
	},
	output: {
		path: "build",
		filename: "[name].js", // 위에 지정한 entry 키의 이름에 맞춰서 결과 산출
		filename: "[hash].js", //특정 빌드에 따른 output 파일명 생성
		filename: "[chunkHash].js", //chunk에 따른 output 파일명 생성 (공식사이트 권고 - 특정 빌드에 연연하지 않아도 되고, chunk에 따라 추적이 쉬워짐)
	},
};
// 번들파일 Profile.js를 <script src="build/Profile.js"></script>로 html 삽입한다.

loader

  • 웹팩은 자바스크립트 파일만 처리가 가능하도록 되어 있다.
  • 다른 형태의 웹 자원들 (img, css, …) js로 변환하여 로딩하기 위해 loader가 필요하다.
module.exports = {
	mode: "",
	entry: {},
	output: {},
	module: {
		rules: [
			{
				test: /\.css$/,
				use: ["style-loader", "css-loader"],
			},
			{
				test: /backbone/,
				use: [
					"expose-loader?Backbone",
					"imports-loader?_=underscore,jquery",
					// 순서대로 (1) jquery, (2) underscore 로딩
				],
			},
			{
				test: /\.js$/,
				use: [
					{
						loader: "bable-loader",
						options: {
							presets: [
								["es2015", "react", { modules: false }], // Tree shaking : 쓰지 않는 모듈은 추가하지 않음
							],
						},
					},
				],
			},
			{
				test: /\.css$/,
				use: [{ loader: MiniCssExtractPlugin.loader }, "css-loader"],
			},
		],
	},
};

plugins

  • Output 시점에 관여하는 커스텀 기능
var MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
	mode: "",
	entry: {},
	output: {},
	module: {},
	plugins: [
		// CSS 파일을 따로 분리하여 번들링
		new MiniCssExtractPlugin({
			filename: "style.css",
		}),
		// 모든 모듈에서 사용할 수 있도록 해당 모듈을 변수로 변환
		new webpack.ProvidePlugin({
			$: "jquery",
		}),
		// 번들링 시작 시점에 사용 가능한 상수들을 정의
		// 일반적으로 개발계 & 테스트계에 따라 다른 설정을 적용할 때 유용
		new webpack.DefinePlugin({
			PRODUCTION: JSON.stringify(true),
			VERSION: JSON.stringify("5fa3b9"),
			BROWSER_SUPPORTS_HTML5: true,
			TWO: "1+1",
			"typeof window": JSON.stringify("object"),
		}),
		// 번들링시 생성되는 코드 (라이브러리)에 대한 정보를 json 파일로 저장하여 관리
		new ManifestPlugin({
			fileName: "manifest.json",
			basePath: "./dist/",
		}),
		// ...
	],
};

resolve

  • 모듈 번들링 관점에서 봤을 때, 모듈 간의 의존성을 고려하여 로딩해야 함.
  • 따라서, 모듈을 어떤 위치에서 어떻게 로딩할지에 관해 정의하는 것
// webpack.config.js
module.exports = {
	mode: "",
	entry: {},
	output: {},
	module: {},
	plugins: [],
	resolve: {
		// 별칭 지정
		alias: {
			Utilities: path.resolve(__dirname, "src/path/utilities/"),
		},
		// require(), import '' 등의 모듈 로딩 시에 어느 폴더를 기준할 것인지 정하는 옵션
		modules: ["node_modules"], // defaults
		modules: [path.resolve(__dirname, "src"), "node_modules"], // src/node_modules
	},
};
// index.js
// 일반
import Utility from "../../src/path/utilities/utility";
// alias 사용 시
import Utility from "Utilities/utility";

webpack-dev-server

  • 페이지 자동고침을 제공하는 webpack 개발용 node.js 서버
  • 노드 인스턴스를 하나 띄운 다음에 서버 프로토타이핑이 가능 (개인 프로젝트 권고)

특징

  1. 프로토타입이 정말 빠르다.
  2. 서버 인메모리 컴파일 : 물리적인 파일패스로 접근할 수 없다. (가시적으로 볼 수 있는 파일 결과물이 없다.)

설치

npm i --save-dev webpack-dev-server

실행

webpack-dev-server --open
// 또는 package.json scripts에 명령어 등록하여 간편 실행. cli 커스텀 명령어.
"scripts": { "start": "webpack-dev-server --open --watch-content-base" }
// webpack.config.js
module.exports = {
    ...
    devServer: {
        contentBase: path.resolve(__dirname),
        publicPath: "/dist/",
        watchContentBase: true,
        port: 9000
    },
}

path vs public path

webpack dev server의 path와 publicPath를 반드시 구분하여 파악해야 함

  • output.path : 번들링한 결과가 위치할 번들링 파일의 절대 경로
  • output.publicPath : 브라우저가 참고할 번들링 결과 파일의 url 주소를 지정. (CDN을 사용하는 경우 CDN 호스트 지정)
// publicPath 예제 #1
output: {
    path: "/home/proj/public/assets",
    publicPath: "/assets/"
}

// publicPath 예제 #2
output: {
    path: "/home/proj/public/assets/[hash]",
    publicPath: "http://cdn.example.com/assets/[hash]/"
}

// Development: both server and the image are on localhost
.image {
    background-image: url('./test.png');
}
// Production: Server is on XXX but the image is on CDN
.image {
    background-image: url('https://someCDN/test.png');
}

devtool

sourcemap 활용

module.exports = {
    ...
    devtool: '#inline-source-map'
}

// 크롬 브라우저 : 개발자도구 -> 설정 -> preferences -> 소스 -> 소스맵 활성화

gulp 연동

// gulp와 webpack 모두 node.js 기반이기 때문에 통합해서 사용하기 쉽다
var gulp = require("gulp");
var webpack = require("webpack-stream");
var webpackConfig = require("./webpack.config.js");

gulp.task("default", function () {
	return gulp.src("src/entry.js").pipe(webpack(webpackConfig)).pipe(gulp.dest("dist/"));
});

webpack-dev-middleware

  • 기존에 구성한 서버에 webpack에서 컴파일한 파일을 전달하는 middleware wrapper
  • webpack에 설정한 파일을 변경시, 파일에 직접 변경 내역을 저장하지 않고 메모리 공간을 활용한다.
  • 따라서, 변경된 파일 내역을 파일 디렉토리 구조안에서는 확인이 불가능하다.
  • 이미 노드를 쓰고 있을때, 웹팩을 붙이는 방법
// Options

// webpack 번들링한 파일들이 위치하는곳. 디폴트값은 /
publicPath: "/assets/",
// 항상 '/'를 앞뒤에 붙여야 한다.

// 서버가 로딩할 static 파일 경로를 지정. default 값은 working directory
// 절대 경로를 사용할 것
contentBase: path.join(__dirname, "publick"),
// 비활성화
contentBase: false,

//gzip 압축방식을 이용하여 웹 자원의 사이즈를 줄인다.
compress: true

추가옵션