본문으로 건너뛰기

"webpack" 태그로 연결된 4개 게시물개의 게시물이 있습니다.

모든 태그 보기

Webpack5 설정

· 약 31분

개요

  • no config 가 유행이지만 적용할 체계에 맞게 튜닝하려면 모든 옵션을 꿰고 있어야할 것이다.

전체 설정

/* eslint-disable no-dupe-keys */
const path = require("path");

module.exports = {
// 모드 설정
// production 일 경우 실환경용 플러그인을 활성화한다.
// https://webpack.js.org/configuration/mode/#mode-production
mode: "production", // "production" | "development" | "none"

// 어플리케이션 구조에 따라 웹팩이 빌드를 시작할 진입점 설정
entry: "./app/entry", // string | object | array

// 웹팩으로 빌드될 파일에 대한 출력 설정
output: {
// 경로 설정 (절대경로)
path: path.resolve(__dirname, "dist"), // string

// 파일명 설정
// https://webpack.js.org/configuration/output/#template-strings
// [id] [name] [fullhash] [contenthash] 등을 사용하여 구성 가능
filename: "[name].js", // string

// public 경로 설정
// 앱 내부에서 사용하는 asset 에 대해 기본 경로 지정 (이미지 등 public dir)
publicPath: "auto", // string

// 웹팩으로 라이브러리를 만드는 경우 사용하는 설정
// 기본값은 undefined
// https://github.com/webpack/webpack/tree/master/examples/multi-part-library
library: {
// 라이브러리 타입 정의
type: "var", // "umd2" | "commonjs-module" | "commonjs2" | "commonjs" | "amd" | "amd-require" | "system" | "this" | "var" | "assign" | "global" | "window" | "self" | "jsonp" | "module"

// 노출할 라이브러리의 이름 설정
name: undefined, // string | string[]

/* 라이브러리 고급설정 */
// 노출 되어야하는 엔트리 모듈 설정
export: undefined, // string | string[]

// UMD 래퍼에 추가할 코멘트
auxiliaryComment: "comment", // { amd: "comment", commonjs: "comment", commonjs2: "comment", root: "comment" },

// umd build 에서 amd define 함수에 이름 설정
umdNamedDefine: undefined,
},

// 빌드의 고유값 설정
// 이는 같은 HTML 에 대해 충돌을 방지한다.
uniqueName: "기본값은 package.json 파일의 name 속성",

// 여러 웹팩 설정을 사용 시에 확인할 이름 설정
name: undefined, // string

/* 고급 출력 설정 */
// 청크파일에 대한 파일명 설정
// long term cache 시에는 [contenthash].js
chunkFilename: "[id].js", // string | (pathData, assetInfo) => string

// 에셋 모듈에 대한 파일명 설정
assetModuleFilename: "[hash][ext][query]", // string

// 웹 어셈블리 모듈에 대한 파일명 설정
webassemblyModuleFilename: "[hash].module.wasm", // string

// 소스 맵 파일명 설정
// devtool: source-map 인 경우만 동작
sourceMapFilename: "[file].map[query]", // "sourcemaps/[file].map"

// 웹팩 devtool 에 대한 템플릿 설정
devtoolModuleFilenameTemplate:
"webpack://[namespace]/[resource-path]?[loaders]", // string | (info) => string

// 웹팩 devtool 에 대한 템플릿 지정 (충돌 방지용)
devtoolFallbackModuleFilenameTemplate: undefined, // string | (info) => string

// 웹 에서 JSONP 로 청크를 로드하는 경우 CORS 설정
crossOriginLoading: false, // "use-credentials" | "anonymous" | false

// import 함수명 (polyfill 사용시 변경)
// dynamic-import-polyfill 의 경우 __import__
importFunctionName: "import", // string

// import meta 명 (polyfill 사용시 변경)
importMetaName: "import.meta", // string

/* 전문가용 출력 설정 1 (위험) */
// 번들에 pathinfo 정보 추가 (production 에서 비활성화)
pathinfo: true, // boolean

// script tag 에 charset=utf-8 속성 추가
// 모던 브라우저에서 deprecated 되었지만 호환성을 위해 웹팩에서 기본으로 추가
charset: true, // string

// chunk 파일 타임아웃 설정
chunkLoadTimeout: 120000, // number (ms)

// 생성된 애셋을 디스크에 쓰기 전에 비교하여 일치할 경우 덮어쓰지 않음
compareBeforeEmit: true, // boolean

// require 시에 에러 발생을 추적할지 설정 (퍼포먼스 이슈로 비활성화가 기본값)
strictModuleExceptionHandling: false, // boolean

// devtools 의 소스 네임스페이스
devtoolNamespace: output.uniqueName, // string

// 출력 환경 설정
environment: {
// 화살표 함수 지원
arrowFunction: true,
// 123n 과 같은 bigInt 지원
bigIntLiteral: false,
// const 지원
const: true,
// destructing 연산자 지원
destructuring: true,
// import() 지원
dynamicImport: false,
// for of 문 지원
forOf: true,
// import / export 지원
module: false,
},

// umd 와 같은 라이브러리의 경우 어느 전역 개체에 마운트할 지 설정
globalObject: "self", // string,

// 번들을 IIFE 로 감싸 isolation 을 줄지 설정
iife: true, // boolean

// 모듈 유형의 자바스크립트 파일로 생성할지 설정
// experiments.outputModule: true 로 실험 기능을 켜야하며 사용시 iife: false 로 설정된다.
module: false, // boolean

// 스크립트 타입 설정
// output.module 이 true 일 경우 이 값도 module 로 설정됨
scriptType: false, // boolean | "module" | "text/javascript"

/* 전문가용 출력 설정 2 (위험) */
// 청크 파일을 로드 방법 설정
// web: jsonp, worker: importScritps, sync node.js: require, async node.js: async-node
chunkLoading: "jsonp", // "jsonp" | "import-scripts" | "require" | "async-node" | false

// 청크 파일을 등록할 전역변수 설정
chunkLoadingGlobal: "webpackChunkwebpack", // string

// 엔트리포인트에서 사용할 수 있는 청크 로딩 타입 설정
// 웹팩에 의해 자동으로 설정됨
enabledChunkLoadingTypes: ["jsonp", "import-scripts"], // string[]

// 엔트리포인트에서 사용할 라이브러리 타입 설정
enabledLibraryTypes: [], // string[]

// 엔트리포인트에서 사용할 wasm 로딩 타입 설정
enabledWasmLoadingTypes: ["fetch"], // string[]

// 청크 포맷 설정
// web: array-push, worker: array-push, node.js: commonjs
chunkFormat: "array-push",

// HMR manifest 파일명 설정 (비권장)
hotUpdateMainFilename: "[runtime].[fullhash].hot-update.json", // string

// HMR 청크의 파일명 설정
hotUpdateChunkFilename: "[id].[fullhash].hot-update.js", // string

// HMR 청크를 로드할 시 JSONP 함수명 설정
hotUpdateGlobal: "webpackHotUpdatewebpack", // string

// 출력의 각 라인 앞에 붙을 prefix 를 설정
sourcePrefix: undefined, // string

// 사용할 해싱 알고리즘 설정
hashFunction: "md4", // string

// 해시를 생성할 때 사용할 인코딩 설정
hashDigest: "hex", // string

// 사용할 해시의 prefix 길이 설정
hashDigestLength: 20, // number

// 해시 솔트 설정 (해시 관련 이슈 발생시)
hashSalt: undefined, // string | Buffer.

// 워커 내에서 청크 로딩 방식 설정
workerChunkLoading: "import-scripts",

// 워커 내에서 wasm 로딩 방식 설정
workerWasmLoading: "fetch",
},

// 사용할 모듈 설정
module: {
// 모듈 규칙 설정
rules: [
{
// 조건
test: /\.jsx?$/,

// 포함할 경로 (exclude 보다 사용을 권장)
include: [path.resolve(__dirname, "app")],

// 제외할 경로 (test 보다 높은 우선순위)
exclude: [path.resolve(__dirname, "app/demo-files")],

// 어디서 import 되는지에 따라 모듈을 사용할지 설정
// 파일에 따라 raw-loader, babel-loader 등 import 방식을 다르게 쓰는 경우 사용한다.
issuer: { or: [/\.css$/, path.resolve(__dirname, "app")] },

/* 고급 조건 설정 */
// 모듈의 리소스와 일치하는지 테스트 (test, include 와 동일)
resource: /\.css$/,

// 하위 컴파일러 이름과 일치하는지 테스트
compiler: /html-webpack-plugin/,

// dependency 타입이 일치하는지 테스트
dependency: "esm", // import-style dependencies
dependency: "commonjs", // require-style dependencies
dependency: "amd", // AMD-style dependency
dependency: "wasm", // WebAssembly imports section
dependency: "url", // new URL(), url() and similar
dependency: "worker", // new Worker() and similar
dependency: "loader", // this.loadModule in loaders

// package.json 의 정보와 일치하는지 테스트
descriptionData: {
type: "module",
},

// 리소스의 mimetype 이 일치하는지 테스트
mimetype: "text/javascript",

// resource 와 같지만 리소스명이 변경된 경우 무시
realResource: /\.css$/,

// 리소스의 Fragment 가 일치하는지 테스트
resourceFragment: "#blah",

// 리소스의 쿼리스트링이 일치하는지 테스트
resourceQuery: "?blah",

// 적용할 로더를 설정
// use: [ { loader } ] 의 shortcut
loader: "babel-loader",
// 로더 옵션을 설정
options: {
presets: ["es2015"],
},

// 여러 로더를 한 번에 설정
use: [
"htmllint-loader",
{
loader: "html-loader",
options: {},
},
],

// 일치하는 모듈의 타입을 설정
// 설정 시 defaultRules 및 기본 import 기능은 우회된다.
// https://webpack.js.org/configuration/module/#ruletype
type: "javascript/auto", // 'javascript/auto' | 'javascript/dynamic' | 'javascript/esm' | 'json' | 'webassembly/sync' | 'webassembly/async' | 'asset' | 'asset/source' | 'asset/resource' | 'asset/inline'

/* 고급 액션 설정 */
// 로더 순서 설정
// 미설정시 normal loader 로 호출된다.
enforce: "pre", // "pre" | "post"

// 모듈 타입에 따른 제네레이터 설정
generator: {
dataUrl: {
encoding: "base64", // "base64" | false
mimetype: undefined,
},
// output.assetModuleFilename 를 override 하며 asset, asset/resource 타입의 경우만 동작
filename: "",
},

// 모듈 타입에 따른 파서 설정
parser: {
amd: false, // disable AMD
commonjs: false, // disable CommonJS
system: false, // disable SystemJS
harmony: false, // disable ES2015 Harmony import/export
requireInclude: false, // disable require.include
requireEnsure: false, // disable require.ensure
requireContext: false, // disable require.context
browserify: false, // disable special handling of Browserify bundles
requireJs: false, // disable requirejs.*
node: false, // disable __dirname, __filename, module, require.extensions, require.main, etc.
node: {
// reconfigure node layer on module level
},
worker: ["default from web-worker", "..."], // Customize the WebWorker handling for javascript files, "..." refers to the defaults.
},

// 모듈별 리졸브 설정
resolve: {
// 해당 key 를 리졸브 할시 script.js 로 대체
alias: {
key: "script.js",
},

// package.json 의 type: "module" 인 경우 파일 확장자와 파일명을 명시해야한다.
fullySpecified: true,
},

// 스코프를 벗어나 사이드이펙트를 발생시키는지 명시적으로 설정
// package.json 의 sideEffects 를 override
sideEffects: false, // boolean
},
{
// 일치하는 하나의 규칙만 사용
oneOf: [
// ... (rules)
],
},
{
// 중첩된 규칙 모두 사용
rules: [
// ... (rules)
],
},
],

/* 고급 모듈 설정 */
// 이 모듈에서 파싱하지 않을 경로 설정
noParse: [/special-library\.js$/],

// 동적 요청에 대한 모듈 컨텍스트 기본 설정
// 곧 deprecated 될 예정으로 사용 비권장
unknownContextRequest: ".",
unknownContextRecursive: true,
unknownContextRegExp: /^\.\/.*$/,
unknownContextCritical: true,
exprContextRequest: ".",
exprContextRegExp: /^\.\/.*$/,
exprContextRecursive: true,
exprContextCritical: true,
wrappedContextRegExp: /.*/,
wrappedContextRecursive: true,
wrappedContextCritical: false,
},

// 모듈 리졸브 설정
// (로더 리졸브 시에는 사용되지 않음)
resolve: {
// 모듈을 찾을 디렉토리
// 상대 경로일 경우 현재 디렉토리와 부모 디렉토리까지 확인
modules: ["node_modules"],

// 사용할 확장자
// 이름이 같고 확장자만 다를 경우 첫 번째 확장자를 사용
extensions: [".wasm", ".mjs", ".js", ".json"],

// 특정 모듈을 더 쉽게 리졸브하기 위해 별칭 설정
alias: {
// e.g. "module/path/file" -> "new-module/path/file"
module: "new-module",

// e.g. "only-module" -> "new-module", "only-module/path/file" -> "new-module/path/file" 는 불가
"only-module$": "new-module",

// e.g. "module" -> "./app/third/module.js", "module/file" 은 에러
module: path.resolve(__dirname, "app/third/module.js"),

// e.g. "module/file" -> "./app/third/file"
module: path.resolve(__dirname, "app/third"),

// e.g. "./app/module.js" -> "./app/alternative-module.js"
[path.resolve(__dirname, "app/module.js")]: path.resolve(
__dirname,
"app/alternative-module.js",
),
},

/* 고급 리졸브 설정 */
// package.json 의 imports, exports 에 사용되는 조건
conditionNames: ["webpack", "production", "browser"],

// 서버 관련 요청이 리졸브되는 경로
// context 가 기본값이며 요청이 절대 경로로 리졸브 되지 않는 경우만 동작한다.
roots: [context],

// 리졸브 실패시 모듈 fallback
fallback: { events: path.resolve(__dirname, "events.js") },

// 패키지를 가져올 때 package.json 에서 검사할 main 필드 설정
mainFields: ["main"],

// 리졸브 경로 제한
restrictions: [/\.js$/, path.resolve(__dirname, "app")],

// 리졸브 캐시
cache: false,

// 공격적이지만 안전하지 않은 리졸브 캐시
// 라이브러리가 안정적인 경우 퍼포먼스 향상이 가능하다고 한다.
unsafeCache: false,
unsafeCache: {},

// 리졸브용 플러그인
plugins: [
// ...
],

/* 전문가용 리졸브 설정 */
// 심볼링 링크일 경우 실제 경로로 확인
// 심볼릭 사용하지 않을 경우 false 가 성능에 좋다.
symlinks: true, // boolean

// package description 에 사용할 json 파일 경로
descriptionFiles: ["package.json"],

// package.json 에서 읽을 속성
// https://github.com/defunctzombie/package-browser-field-spec
aliasFields: ["browser"],

// 외부 요청을 위해 확인할 필드
// https://webpack.js.org/guides/package-exports/
exportsFields: ["exports"], // (default)

// 내부 요청을 위해 확인할 필드
importsFields: ["imports"], // (default)

// 디렉토리를 리졸브할 때 사용할 파일
mainFiles: ["index"],

// package.json 의 type: "module" 인 경우 파일 확장자와 파일명을 명시해야한다.
fullySpecified: true, // boolean

// 모듈 리졸브를 상대경로로 요청
preferRelative: true, // boolean

// 리졸브에 확장자 강제
enforceExtension: false, // boolean

// 리졸브 캐싱 필터
cachePredicate: ({ path, request }) => true,

// context 정보를 캐시키에 포함
// false 가 성능에 좋다.
cacheWithContext: false, // boolean

// 비동기 fs 대신 동기 fs 사용
useSyncFileSystemCalls: false, // boolean

// issuer 에 따라 리졸브 옵션 설정
// https://github.com/webpack/webpack/blob/master/lib/config/defaults.js#L992-L1009
byDependency: {},
},

// 웹팩 퍼포먼스 힌트 표시 설정
performance: {
// 힌트설정
hints: "warning", // "warning" | "error" | false

// 경고를 내보낼 최대 에셋 크기
maxAssetSize: 250000, // number

// 경고를 내보낼 최대 엔트리 크기
maxEntrypointSize: 250000, // number

// 퍼포먼스 힌트를 계산할 파일 필터 설정
assetFilter: (assetFilename) => {
return !/\.map$/.test(assetFilename);
},
},

// 브라우저 devtools 에 대한 소스맵 스타일 설정
// 설정에 따라 빌드 성능에 영향을 미칠 수 있다.
// https://webpack.js.org/configuration/devtool/#devtool
devtool: false, // enum, 위 링크 참조

// 설정에서 엔트리 및 로더를 확인하기 위한 기본 홈 경로 (절대경로)
context: __dirname, // string

// 번들이 실행되어야할 환경 설정
// web 이 기본이며 browserslist 환경에서는 browserslist 가 기본이다.
// https://webpack.js.org/configuration/target/#string
target: "web", // enum

// 번들링시 해당 모듈의 종속성을 제거한다.
// 주로 외부 라이브러리 종속성 제거에 사용된다.
// https://webpack.js.org/configuration/externals/#combining-syntaxes
externals: undefined, // string | [string] | object | function | RegExp

// externals 타입 설정
externalsType: "var", // 기본값은 output.library.type

// 특정 대상에 대한 externals 프리셋을 활성화한다.
externalsPresets: {
electron: false,
electronMain: false,
electronPreload: false,
electronRenderer: false,
node: false,
nwjs: false,
web: true,
webAsync: true,
},

// 경고를 무시할 패턴 설정
ignoreWarnings: undefined, // RegExp | (WebpackError, Compilation) => boolean | {module?: RegExp, file?: RegExp, message?: RegExp}

// 통계 설정
stats: "errors-only",
stats: {
// 프리셋
preset: "errors-only", // "error-only" | "error-warnings" | "minimal" | "none" | "normal" | "verbose" | "detailed"

/* 고급 전역 설정 */
// 옵션이 설정되지 않은 경우 대체값
all: false,

// 색상 설정
colors: true,

// 상대경로 표시를 위해 context 디렉토리 설정
context: "../src/",

// 출력에 모듈 및 청크 id 포함
ids: true,

// 출력에 env 포함
env: true,

// 출력에 절대 경로 포함
outputPath: true,

// 출력에 publicPath 포함
publicPath: true,
// include public path in the output

// assets 목록 표시
assets: true,

/* 고급 에셋 설정 */
// 에셋 정렬 설정
// !size 처럼 역순 가능
assetsSort: "id",

// 표시될 에셋 라인
assetsSpace: 15,

// 캐시된 에셋에 대한 정보 포함
cachedAssets: true,

// 제외할 에셋 경로
excludeAssets: false, // string | RegExp | (assetName) => boolean

// 에셋을 출력 경로별로 그룹화
groupAssetsByPath: true,

// 에셋을 확장자별로 그룹화
groupAssetsByExtension: true,

// 에셋을 상태별로 그룹화 (emitted, compared for emit, cached)
groupAssetsByEmitStatus: true,

// 에셋을 청크별로 그룹화
groupAssetsByChunk: true,

// 에셋을 정보별로 그룹화 (immutable, development, hmr 등)
groupAssetsByInfo: true,

// 관련 에셋 정보 포함 (sourcemap, compressed version 등)
relatedAssets: true,

// 퍼포먼스 힌트 포함
performance: true,

// 엔트리포인트 포함
entrypoints: true,

// namedChunkGroups 에 대한 정보 포함
chunkGroups: true,

/* 고급 청크 그룹 설정 */
// 엔트리포인트, 청크 그룹에 대해 보조 에셋 포함
chunkGroupAuxiliary: true,

// 하위 청크 그룹 포함 (prefetched, preloaded)
chunkGroupChildren: true,

// 청크 그룹 에셋 목록 제한
chunkGroupMaxAssets: 5,

// 청크 목록 표시
chunks: true,

/* 고급 청크 설정 */
// 청크 정렬 설정
chunksSort: "id",

// 빌드된 모듈에 대한 정보를 청크에 포함
chunkModules: true,

// 청크 출처 포함
chunkOrigins: true,

// 청크 관계 포함 (parents, children, sibilings)
chunkRelations: true,

// 청크 종속성 포함
dependentModules: true,

// 모듈 목록 표시
modules: true,

/* 고급 모듈 설정 */
// 표시될 모듈 라인
modulesSpace: 15,

// 중첩 모듈 포함
nestedModules: true,

// 캐시된 모듈 포함
cachedModules: true,

// 최적화 그래프에서 참조되지 않는 모듈 포함
orphanModules: false,

// 제외할 모듈 경로
excludeModules: false, // string | RegExp | (assetName) => boolean

// 모듈이 포함된 이유 추가
reasons: true,

// 모듈의 소스코드 포함
source: false,

/* 전문가용 모듈 설정 */
// 모듈 정렬 설정
modulesSort: "id",

// 모듈을 경로별로 그룹화
groupModulesByPath: true,

// 모듈을 확장자별로 그룹화
groupModulesByExtension: true,

// 모듈을 속성별로 그룹화 (errors, wanings, assets, optional, orphan, dependent)
groupModulesByAttributes: true,

// 모듈을 캐시 상태별로 그룹화
groupModulesByCacheStatus: true,

// 각 엔트리에서의 depth 포함
depth: false,

// 모듈 내 에셋에 대한 정보 포함
moduleAssets: true,

// 런타임 모듈에 대한 정보 포함
runtimeModules: true,

/* 고급 최적화 설정 */
// 모듈 exports 포함
providedExports: false,

// 사용되는 모듈의 exports 포함
usedExports: false,

// bailout 사유 포함
// https://webpack.js.org/plugins/module-concatenation-plugin/
optimizationBailout: false,

// chilren 정보 포함
children: true,

// 로그 레벨
logging: true,

// 특정 로거의 디버그 정보 포함
loggingDebug: /webpack/,

// 에러 스택 포함
loggingTrace: true,

// 경고 표시
warnings: true,

// 에러 표시
errors: true,

// 세부 에러 표시
errorDetails: true,

// 에러 스택 표시
errorStack: true,

// 에러와 관련된 모듈 스택 포함
moduleTrace: true,

// 빌드 시간 표시
builtAt: true,

// 에러 카운트 표시
errorsCount: true,

// 경고 카운트 표시
warningsCount: true,

// 빌드 소요시간 표시
timings: true,

// 웹팩 버전 정보 포함
version: true,

// 컴파일 해시 포함
hash: true,
},

// https://webpack.js.org/configuration/dev-server/
devServer: {
// 백엔드 개발 서버 프록시
proxy: {
"/api": "http://localhost:3000",
},

// static 파일 경로
// 절대 경로 사용 권장
contentBase: path.join(__dirname, "public"), // boolean | string | array

// gzip 설정
compress: true,

// history api 사용 시에 index.html 을 fallback 으로 설정
historyApiFallback: false,

// HMR 활성화
hot: true,

// 개발 서버를 https 로 서빙
// key, cert, ca 설정 필요
https: false,

// hot reload 시 에러와 경고만 표시
noInfo: true,
// ...
},

// 실험 기능 설정
experiments: {
// wasm 모듈을 비동기로 설정
// https://github.com/WebAssembly/esm-integration
asyncWebAssembly: true,

// deprecated (webpack4)
syncWebAssembly: true,

// ES module 허용
// output.libraryTarget 을 module 로 설정
outputModule: true,

// top-level await 를 허용
topLevelAwait: true,
},

// 빌드시 사용할 플러그인 설정
plugins: [
// ...
],

// 최적화 설정
optimization: {
// 청크 아이디를 생성할 때에 사용할 알고리즘
// production: "deterministic", development: "named", fallback: "natural"
// https://webpack.js.org/configuration/optimization/#optimizationchunkids
chunkIds: "deterministic", // false | "natural" | "named" | "size" | "total-size" | "deterministic"

// 모듈 아이디를 생성할 때에 사용할 알고리즘
// production: "deterministic", development: "named", fallback: "natural"
moduleIds: "deterministic", // false | "natural" | "named" | "deterministic"

// exports 명을 mangle 할지 설정
// production: "deterministic", fallback: false
mangleExports: "deterministic", // false | "deterministic" | "size"

// 출력 파일을 압축할지 설정
// production: true, fallback: false
minimize: true, // boolean

// 사용할 압축 플러그인 설정
minimizer: [],

/* 고급 최적화 */
// concatenate multiple modules into a single one
// production: true, fallback: false
concatenateModules: true, // boolean

// 빌드 에러가 있어도 출력을 내보낼지 설정
// production: false, fallback: true
emitOnErrors: false, // boolean

// 이미 로드된 청크에 포함되어있을 경우 청크를 다운로드하지 않게 플래그 설정
// production: true, fallback: false
flagIncludedChunks: true, // boolean

// 사용하지 않는 exports 에 대해 내부 그래프 분석 수행 설정
// production: true, fallback: false
innerGraph: true, // boolean

// 동일한 모듈을 포함하는 청크를 병합하게 설정
mergeDuplicateChunks: true, // boolean

// 웹팩 process.env.NODE_ENV 설정
// mode 값을 바라보고, mode: "none" 일 경우 false 와 동일
nodeEnv: "production", // string | boolean

// 레코드 생성시 상대경로를 사용할지 설정
// recordsPath, recordsInputPath, recordsOutputPath 사용시에 자동으로 활성화
portableRecords: false, // boolean

// 모듈에서 export * from 구문에 대해 효율적인 코드를 생성하게 설정
providedExports: true, // boolean

// 사용하지 않는 exports 를 제거
// production: true, fallback: false
usedExports: true, // boolean | "global"

// 파일 내용에 기반하여 contenthash 계산
// production: true, fallback: false
realContentHash: true, // boolean

// 모듈이 이미 상위 청크에 포함되어 있을경우 감지하여 제거
// 빌드 성능을 위해서는 비활성화하는 것이 좋다.
removeAvailableModules: false, // boolean

// 빈 청크파일 제거
removeEmptyChunks: true,

// 런타임 청크 설정
// 다중 엔트리의 경우 "single" 로 변경 후 런타임 청크를 공유할 수 있다.
runtimeChunk: false, // object | string | boolean

// exports 를 중복으로 사용할 때에 사이드이펙트가 없는 모듈 건너뛰기
// optimization.providedExports 가 활성화되어야 사용 가능
// production: true, fallback: "flag"
sideEffects: true,

splitChunks: {
cacheGroups: {
// 모듈별 세부 캐시 설정
"my-name": {
test: /\.sass$/,
type: "css/mini-extract",

/* 고급 셀렉터 */
chunks: "async",
minChunks: 1,
enforceSizeThreshold: 100000,
minSize: 0,
minRemainingSize: 0,
usedExports: true,
maxAsyncRequests: 30,
maxInitialRequests: 30,

/* 고급 이펙트 설정 */
maxAsyncSize: 200000,
maxInitialSize: 100000,
maxSize: 200000,
filename: "my-name-[contenthash].js",
idHint: "my-name",
name: false,
hidePathInfo: true,
automaticNameDelimiter: "-",
},
},

fallbackCacheGroup: {
automaticNameDelimiter: "-",
minSize: 20000,
maxAsyncSize: 200000,
maxInitialSize: 100000,
maxSize: 200000,
},

/* 고급 셀렉터 설정 */
// 최적화할 청크 선택
chunks: "all", // "async" | "all" | "initial"

// exports 명을 mangle 하거나 사용하지 않는 exports 를 삭제하기 위하여
// exports 를 분석할지 설정
usedExports: true,

// 모듈이 가져야할 최소 청크 수
minChunks: 1,

// 스플리팅이 강제되고
// minRemainingSize, maxAsyncRequests, maxInitialRequests 가 무시되는 사이즈 임계치
enforceSizeThreshold: 50000,
// ignore when following criterias when size of modules is above this threshold

// 생성할 청크의 최소 바이트
minSize: 20000,

// 남아있을 청크의 최소 바이트
// development: 0, production: minSize
minRemainingSize: 20000,

// 온디맨드 로드 시에 최대 병렬 요청 수
maxAsyncRequests: 30,

// 엔트리포인트의 최대 병렬 요청 수
maxInitialRequests: 30,

/* 고급 이펙트 설정 */
// 아래 사이즈보다 더 큰 사이즈를 스플리팅하여 청크 생성
// 우선순위: minSize > maxSize > maxInitialRequest === maxAsyncRequests
// 온디맨드만 적용
maxAsyncSize: 200000,
// 초기 로드 청크에만 적용
maxInitialSize: 100000,
maxSize: 200000,

// 청크 파일명 설정
filename: "[contenthash].js",

// 청크명 설정
// production: false 권장
name: false, // false | string | (module, chunks, key) => string

// maxSize 로 스플리팅된 청크에서 경로 노출 방지
hidePathInfo: true,

// 청크명에 들어갈 구분자
// e.g. vendor~main.js
automaticNameDelimiter: "~",

/* 전문가용 설정 */
// 사이즈를 설정할 때에 사용할 사이즈 유형 설정
defaultSizeTypes: ["javascript", "unknown"],
},
},

/* 고급 설정 */
// 로더 컨텍스트에 사용자 정의 API 또는 속성 추가
loader: {
/* ... */
},

// 로더에 대한 별도의 리졸브 옵션
// 웹팩의 로더 패키지를 확인하는 데만 사용
resolveLoader: {
/* same as resolve */
},

// node.js 기능 폴리필, 모킹 추가
node: {
// global 을 output.globalObject 로 치환
// 전역 변수가 필요한 모듈이라면 ProvidePlugin 를 권장
// https://nodejs.org/api/globals.html#globals_global
global: true, // boolean

// https://webpack.js.org/configuration/node/#node__filename
__filename: "mock", // boolean | "mock" | "eval-only"
__dirname: "mock", // boolean | "mock" | "eval-only"
},

// 빌드 간 모듈이 변경되는 방식을 추적하기 위해 레코드 JSON 파일 생성
recordsPath: path.resolve(__dirname, "build/records.json"),
recordsInputPath: path.resolve(__dirname, "build/records.json"),
recordsOutputPath: path.resolve(__dirname, "build/records.json"),

/* 고급 캐시설정 */
// 캐시 설정
// 개발 모드에서는 cache: true 이며 { type: "memory" } 와 동일
// 프로덕션 모드에서는 비활성화
// https://webpack.js.org/configuration/other-options/#cache
cache: false, // boolean | object
cache: {
type: "filesystem", // "memory" | "filesystem"

// 캐시 기본 폴더 설정
cacheDirectory: "node_modules/.cache/webpack", // string

// 캐시 경로 설정
cacheLocation: path.resolve(cache.cacheDriectory, cache.name), // string

// 무효화를 위한 캐시 의존성 추가
buildDependencies: {
defaultWebpack: ["webpack/lib"],
// 최신 웹팩 설정에 대한 캐시 의존성을 설정하려면 아래 설정 권장
// config: [ __filename ],
},

// 캐시에서 사용할 해시 알고리즘 설정
hashAlgorithm: "md4", // string

// 캐시명 설정
// 여러 웹팩 설정별로 독립된 캐시를 가져야할 때 변경할 수 있다.
name: `${config.name}-${config.mode}`, // string

// 파일시스템에 캐시를 저장할 시점 설정
// pack: 컴파일러가 idle 상태일 경우 단일 파일에 데이터 저장
store: "pack", // "pack"

// 파일 캐시를 무효화하기 위한 버전 설정
version: "", // string

// store: pack 인 경우 캐시를 저장할 주기 설정
idleTimeout: 10000, // number (ms)

// store: pack 인 경우 캐시를 초기화할 시간 설정
idleTimeoutForInitialStore: 0, // number (ms)
},

// 파일시스템 스냅샷을 생성하고 무효화하는 방법 설정
snapshot: {
// package.json 에서 관리되는 경로
managedPaths: [path.resolve(__dirname, "node_modules")], // string[]

// immutable 하여 스냅샷일 필요가 없는 경로
// path.resolve(__dirname, ".yarn/cache")
immutablePaths: [], // string[]

// 모듈 빌드시의 스냅샷 설정
module: {
// 타임스탬프를 비교하여 무효화 확인
timestamp: true,
// 해시 비교로 무효화 확인
// timestamp 보다 무겁지만 자주 변경되지 않음
hash: true,
},

// 리졸브시 스냅샷 설정
resolve: {
timestamp: true,
hash: true,
},

// 캐시를 사용시 빌드 종속성 리졸브시 스냅샷
resolveBuildDependencies: {
timestamp: true,
hash: true,
},

// 캐시 사용시 빌드 종속성 스냅샷
buildDependencies: {
timestamp: true,
// CI 환경에 적합
hash: true,
},
},

// watch 설정
watch: true, // boolean

// watch option 설정
watchOptions: {
// 파일 변경시에 지연시간 설정
aggregateTimeout: 200, // number (ms)

// watch 를 하지 않을 경로 설정
ignored: /node_modules/, // RegExp | string | [string, RegExp]

// poll 방식으로 watch 할지 설정
// 주로 nfs 사용으로 파일시스템에서 변경을 감지할 수 없을 경우
poll: false, // boolean | number (ms)
},

/* 고급 빌드 설정 */
// 인프라 수준 로깅 설정
infrastructureLogging: {
level: "info", // "none" | "error" | "warn" | "info" | "log" | "verbose"
debug: undefined, // true | string | RegExp | (name) => boolean | [string, RegExp, (name) => boolean]
},

// 병렬 처리할 모듈의 수 제한
// 성능을 미세하게 조정하거나 안정적인 결과를 얻는 데에 사용 가능
parallelism: 100, // number

// 통계 및 힌트를 포함하여 분석 도구에서 사용할 수 있게 프로필 제한
// 더 나은 결과를 위해 parallelism: 1 로 설정해야한다.
profile: true, // boolean

// 첫 오류 발생시 종료 설정
// 웹팩은 HMR 사용 시에 브라우저 콘솔, 터미널에 오류를 기록하지만 번들링을 게속하는데 이를 방지한다.
bail: false, // boolean

// 여러 웹팩 설정에 대한 빌드 의존성 설정
dependencies: ["name"],
};

기본 값 확인

참조

웹팩이 모듈을 불러오는 슈도코드

· 약 15분

잊기 전에 슈도코드를 정리해놓자.

var 전체모듈 = [
function () {
const 합계함수 = (a, b) => a + b;
return 합계함수;
},

function () {
const 내부_합계함수 = 전체모듈[0]();
const 합계 = 내부_합계함수(10, 20);
console.log(합계);
return 합계;
},
];

const 시작모듈_인덱스 = 1;
전체모듈[시작모듈_인덱스]();

해석

배열에 다 때려넣고 호출해서 사용하는 방법이다 물론 내부는 더 복잡하다, 코드 스플리팅이 된다면 더더욱.

복잡한 내부

https://github.com/hg-pyun/minipack-kr/blob/master/src/minipack.js
/**
* @source https://github.com/hg-pyun/minipack-kr/blob/master/src/minipack.js
*
* 모듈 번들러들은 작은 코드 조각들을 웹 브라우저에서 실행될 수 있는 크고 복잡한 파일로 컴파일합니다.
* 이 작은 조각들은 단지 자바스크립트 파일들일 뿐이며, 이들 사이의 종속성은 모듈 시스템에 의해 표현됩니다
* (https://webpack.js.org/concepts/modules).
*
* 모듈 번들러들은 entry file 이라는 개념을 가지고 있습니다. 브라우저에 스크립트 태그를 몇개 추가하여
* 실행하는 대신, 번들 담당자에게 응용 프로그램의 메인 파일이 무엇인지 알려 줍니다. 이 파일이 어플리케이션을
* 실행하는 진입점이 됩니다.
*
* 번들러는 entry file의 의존성을 분석합니다. 그리고 그 다음 파일의 의존성을 파악합니다.
* 이 작업은 애플리케이션의 모든 모듈과 각 모듈이 서로 어떻게 의존하는지 파악할 때까지 반복됩니다.
*
* 이러한 프로젝트에 대한 이해를 종속성 그래프라 부릅니다.
*
* 이 예제에서는 종속성 그래프를 만들고 이 그래프를 사용하여 모든 모듈들을 하나의 번들로 패키징 합니다.
* 그럼 시작해 보겠습니다 :)
*
* 참고: 이 예제는 매우 단순화되어 있습니다. 순환 참조, 캐싱 모듈, 파싱 최적화 등에 대한 내용은 생략
* 하여 가능한가 단순하게 만들었습니다.
*/

const fs = require("fs");
const path = require("path");
const babylon = require("babylon");
const traverse = require("babel-traverse").default;
const { transformFromAst } = require("babel-core");

let ID = 0;

// 우선 file path를 받는 함수를 생성하고
// 파일을 내용을 읽고, 종속성을 추출합니다.
function createAsset(filename) {
// 파일의 내용을 문자열로 읽습니다.
const content = fs.readFileSync(filename, "utf-8");

// 이제 이 파일이 어떤 파일에 종속되는지 알아보겠습니다. 우리는 import 문자열을 보고 의존성을
// 파악할 수 있습니다 하지만, 이것은 단순한 접근법이어서, 대신에 자바스크립트 파서를 사용하겠습니다.

// 자바스크립트 파서들은 자바스크립트 코드를 읽고 이해할 수 있도록 도와주는 툴입니다.
// 파서는 AST(abstract syntax tree)라는 좀더 추상화된 모델을 생성합니다.
//
// AST에 대해 이해하려면 AST Explorer(https://astexplorer.net)을 꼭 보기를 강력하게 추천합니다.
// AST가 어떻게 이루어져 있는지 확인할 수 있습니다.
//
// AST는 우리의 코드에 대해 많은 정보를 가지고 있습니다. 우리는 쿼리를 이용하여
// 우리의 코드가 하려는 일에 대해 이해할 수 있습니다.
const ast = babylon.parse(content, {
sourceType: "module",
});

// 이 배열은 현재 모듈의 의존성을 상대 경로로 가지고 있을 것입니다.
const dependencies = [];

// 우리는 AST 순회를 통해 각각의 모듈들이 어떤 의존성을 가지고 있는지 이해하려 합니다.
// 이것을 통해 AST안에서 모든 import keyword 선언을 파악할 수 있습니다.
traverse(ast, {
// ECMAScript 모듈들은 정적이므로 매우 파악하기 쉽습니다.이는 변수를 가져올 수 없거나 조건부로
// 다른 모듈을 가져올 수 없음을 의미합니다. import 구분을 볼 때 마다 카운팅을 하고 의존성을 가지고
// 있는 것으로 간주 할 수 있습니다.
ImportDeclaration: ({ node }) => {
// import 구문마다 dependencies 배열에 값을 추가합니다.
dependencies.push(node.source.value);
},
});

// 또한 간단한 카운터를 이용하여 이 모듈에 고유 식별자를 할당합니다.
const id = ID++;

// 우리는 일부 브라우저에서만 지원하는 ECMAScript module들이나 기능들을 사용할 가능성도 있습니다.
// 우리가 만드는 번들이 모든 브라우저에서 돌아가도록 Babel을 이용해서 transpile할 수 있습니다
// (https://babeljs.io 참고).
//
// `presets` 옵션은 Babel이 어떻게 우리 코드를 바꿀지에 대해 결정합니다. 우리는 `babel-preset-env`
// 를 이용하여 대부분의 브라우저에서 우리의 코드를 사용할 수 있도록 바꾸도록 하겠습니다.
const { code } = transformFromAst(ast, null, {
presets: ["env"],
});

// 이 모듈에 대한 정보를 return 합니다.
return {
id,
filename,
dependencies,
code,
};
}

// 이제 단일 모듈의 종속성을 추출할 수 있으므로, entry file의 의존성을 추출하는 것부터 시작하겠습니다.
// 이 작업은 애플리케이션의 모든 모듈과 각 모듈이 서로 어떻게 의존하는지를 파악할 때까지 계속 진행할 것입니다.
// 이 작업의 결과물을 의존성 그래프라 부릅니다.
function createGraph(entry) {
// entry file부터 분석을 시작합니다.
const mainAsset = createAsset(entry);

// queue를 사용해서 모든 asset의 의존성을 분석하도록 하겠습니다. 이 작업을 위해
// entry asset을 가지고 있는 배열을 정의합니다.
const queue = [mainAsset];

// 여기서 queue의 반복을 위해 `for ... of` 반복문을 사용합니다. 처음에는 queue가 asset을 하나만
// 가지고 있지만 작업이 반복되는 동안에 새로운 asset들을 queue에 추가합니다. 이 반복문은 queue가
// 비어질 때 까지 계속됩니다.
for (const asset of queue) {
// 모든 asset들은 의존성이 있는 모듈에 대한 상대경로들을 리스트로 가지고 있습니다. 우리는 그 리스트를
// 순회하면서 `createAsset()`함수로 분석하고, 아래 객체를 통하여 모듈들의 의존성을 추척할 것입니다.
asset.mapping = {};

// 이것은 이 모듈이 있는 디렉토리입니다.
const dirname = path.dirname(asset.filename);

// 종속성에 대한 상대 경로 리스트를 순회합니다.
asset.dependencies.forEach((relativePath) => {
// `createAsset()` 함수는 절대 경로가 필요합니다. dependencies 배열은 상대 경로를 가지고
// 있는 배열입니다. 이러한 경로들은 모듈이 import된 file에 따라 상대적입니다. 따라서 부모 asset의
// 경로를 이용해서 상대 경로를 절대경로로 바꿔야 합니다.
const absolutePath = path.join(dirname, relativePath);

// asset의 내용울 분석하고, 내용을 읽고, 의존성을 추출합니다.
const child = createAsset(absolutePath);

// `asset`의 의존성은 `child`에게 달려있습니다. 우리는 `mapping` 객체에 relativePath와 child.id를
// 이용해서 관계를 표현할 수 있습니다.
asset.mapping[relativePath] = child.id;

// 마지막으로 child asset을 queue에 추가하여 구문 분석이 반복되도록 합니다.
queue.push(child);
});
}

// 이 시점에서 queue는 애플리케이션의 모든 모듈이 포함된 배열입니다. 이것이 우리가 그래프를 표현하는 방법입니다.
return queue;
}

// 다음으로, 그래프를 이용하여 브라우저에서 실행할 수 있는 번들을 반환하는 함수를 정의합니다.
//
// 우리의 번들은 self-invoking(자신을 부를수 있는)함수를 가지고 있습니다.
//
// (function() {})()
//
// 이 함수는 하나의 인자만 받을 수 있습니다: 모든 모듈의 정보를 가지고 있는 그래프.
function bundle(graph) {
let modules = "";

// 이 함수를 구성하기 전에 매개 변수로 전달할 객체를 만들겠습니다. 반드시 알아둬야할 것은 우리가 만드는
// 스트링은 2개의 중괄호({})로 감싸져 있어야 한다는 것입니다. 우리는 다음과 같은 포멧으로 추가할
// 것입니다: `key: value,`.
graph.forEach((mod) => {
// 그래프안에 있는 모든 모듈들은 entry를 객체로 가지고 있습니다. 우리는 module의 id를
// 값에 대한 키로 사용합니다.(각 모듈마다 2개의 값이 있습니다.)
//
// 찻번째 값은 함수로 감싼 각 모듈의 코드입니다. 그 이유는 모듈의 scope를 지정해야 하기 때문입니다.
// 한 모듈에서 변수를 정의하면 다른 모듈이나 글로벌 scope에 영향을 주지 않아야 합니다.
//
// transpiled된 모듈들은 CommonJS 모듈 시스템을 사용합니다:
// 해당 모듈 시스템은 `require`, `module`, 그리고 `exports`를 통해 모듈화 합니다.
// 이 키워드들은 일반적으로 브라우저에서 사용할수 없으므로, 우리의 함수를 이용하여 주입해야 합니다.
//
// 두번째 값은 모듈간의 의존성 매핑을 stringify하는 것입니다. 다음과 같은 객체입니다.
// { './relative/path': 1 }.
//
// transpiled된 우리의 모듈들이 상대경로와 합께 `require()`를 호출하기 때문입니다. 이 함수를 호출하면
// 그래프에서 이 모듈의 상대 경로에 해당하는 모듈을 확인할 수 있습니다.
modules += `${mod.id}: [
function (require, module, exports) { ${mod.code} },
${JSON.stringify(mod.mapping)},
],`;
});

// 마지막으로 self-invoking 함수의 body를 만듭니다.
//
// `require()` 함수를 만들며 시작하겠습니다: 모듈 id를 받아 앞서 만든 모듈 오브젝트에서 `module`을
// 찾습니다. function wrapper와 맵핑 객체를 얻기위해 two-value 객체를 이용합니다.
//
// 모듈의 코드는 모듈의 id들 대신 상대경로와 함께 `require()`함수를 호출합니다. 우리가 만든 require 함수는
// id 받습니다. 또한 두개의 모듈은 동일한 상대 경로를 요구할 수 있지만, 실제론 두개의 다른 모듈들을
// 의미하게 됩니다.
//
// 이 문제를 해결하기 위해 별도의 `require` 함수를 제공합니다. 모듈의 맵핑 오브젝트를 이용하여 상대경로를 ids에 할당합니다.
// 맵핑 오브젝트는 구체적인 모듈을 가져오기 위한 용도로, 상대 경로와 모듈 ids를 맵핑합니다.
//
// 마지막으로, 모듈이 require 되었을 때 exports 객체의 값이 노출되어야 합니다. 따라서 모듈 코드에 의해 변환 된
// `exports` 객체는 `require()`로 반환됩니다.
const result = `
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id];
function localRequire(name) {
return require(mapping[name]);
}
const module = { exports : {} };
fn(localRequire, module, module.exports);
return module.exports;
}
require(0);
})({${modules}})
`;

// 결과를 반환합니다. 만세! :)
return result;
}

const graph = createGraph("./example/entry.js");
const result = bundle(graph);

console.log(result);

여기의 번역된 내용을 확인해보자.

Angular2 with angular-cli

· 약 6분

지난시간에는 Angular with Webpack으로 ng2 의 기본 실행 틀에 대해 알아봤다. 매번 이렇게 세팅을하려면 아무도 ng2 를 쉽게 사용하지 못할 것이다. 버전별 충돌문제도 해결해야되고 컴포넌트를 생성할 때마다 주입해줘야되고 third party 라이브러리를 쓸 때는 typings 를 사용해 타입 인터페이스를 넣어줘야하고 웹팩 로더에 대한 정보도 찾아봐야하며... (지난시간에 해봤던 것)

이걸 모두 해결한 정말 멋진 모듈인 Angular-cli 로 ng2 project 를 시작해보자.

설치

Angular-cli를 참조해도 되지만 하나씩 해보자.

먼저 npm 으로 angular/cli 를 전역으로 설치한다.

npm install -g @angular/cli

설치가 완료되면 ng 라는 명령어를 사용할 수 있다.

ng --version

image from hexo

프로젝트 생성

ng new 프로젝트명 명령어로 프로젝트를 생성하면 된다.

ng new 프로젝트명

ng new ng2-cli-test --routing

--routing 명령어는 기본으로 angular 라우팅을 app module 에 넣어준다. angular routing 을 사용하지 않을 경우 옵션을 제외시키면 된다.

프로젝트 실행

생성한 프로젝트로 이동해 프로젝트를 실행해보자

패키지 설치

# cd ng-cli-test
$ npm install

1~3 분정도 걸리니 느긋하게 기다리면 된다.

웹서버 실행

$ npm start
# 또는
$ ng serve

ng2-cli 는 기본 포트 4200 을 사용한다. 이 포트가 사용 중이라면 --port 옵션으로 포트를 변경해주면 된다.

package.json 을 열어 start 명령어 실행시 브라우져가 바로 뜨게 --open 옵션을 주자.

package.json
{
"name": "ng2-cli-test",
"version": "0.0.0",
"license": "MIT",
"angular-cli": {},
"scripts": {
"ng": "ng",
"start": "ng serve --open",
...
},
...
}

자세한 옵션은 여기서 확인할 수 있다.

image from hexo 쉽게 실행되었다!

컴포넌트 추가

컴포넌트도 쉽게 생성할 수 있다.

ng generate component 컴포넌트명

ng g c 컴포넌트명

ng g c sub 명령어로 서브 컴포넌트를 생성해보자. image from hexo 서브라는 폴더로 ng2 컴포넌트 명명 규칙에 맞게 예쁘게 생성되었다.

app.module.ts를 확인해보면 자동으로 import 가 되어있다.

src/app/app.module.ts
// ...
import { SubComponent } from "./sub/sub.component";

@NgModule({
declarations: [AppComponent, SubComponent],
// ...
})
export class MyModule {}

정말 영롱하다. 자세한 generate component 옵션은 여기서 확인할 수 있다.

라우팅

이제 app-routing.module.ts 파일을 열어 sub.component 로 라우팅이 되게 해보자.

src/app/app-routing.module.ts
import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
// 서브 컴포넌트 import
import { SubComponent } from "./sub/sub.component";

// sub로 접속시 SubComponent 사용
const routes: Routes = [
{
path: "",
children: [],
},
{ path: "sub", component: SubComponent },
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [],
})
export class AppRoutingModule {}

메인 컴포넌트의 뷰를 수정한다.

src/app/app.component.html
<h1>{{title}}</h1>
<a routerLink="">home</a>
<a routerLink="/sub">sub page</a>
<router-outlet></router-outlet>

브라우저에서 확인해보자. image from hexo subpage 버튼 링크를 클릭시 sub work! 라는 sub component 의 뷰가 보이는 것을 확인 할 수 있다.

ng2 의 routing 은 router-outlet directive 바로 다음에 생성된다.

라이브러리

polyfills

하위 버전 브라우저를 위해 polyfills 를 활성화 해준다. polyfils.ts의 core-js/es6 구문들의 주석을 해제만 해주면 된다.

image from hexo

global script

전역에서 사용해야할 스크립트가 있다면 .angular-cli.json 파일의 apps.scripts 안에 넣어주면 된다.

angular-cli.json
{
...
"apps":[{
"scripts": [
"../node_modules/jquery/dist/jquery.js",
"../node_modules/hammerjs/hammer.min.js"
]
}]
}

global css

global script 와 마찬가지로 angular-cli.json 에 넣는 방식이 있지만 src/style.css에 import 방식으로 넣어줘도 된다.

src/style.css
/* You can add global styles to this file, and also import other style files */
@import "~https://fonts.googleapis.com/icon?family=Material+Icons";

third party

third party library 를 사용해야한다면 라이브러리와 @types 를 설치해 사용하고 싶은 컴포넌트에서 import 구문으로 사용하면 된다.

npm install lodash --save
npm install @types/lodash --save-dev
any.component.ts
import * as _ from "lodash";

빌드

웹 브라우저에서 실행할 수 있게 프로젝트를 빌드해보자.

$ ng build
# minify 옵션 추가
$ ng build --prod

빌드를 실행하면 .angular-cli.json 파일에 있는 root 와 outDir 경로를 이용해 진행된다.

여담

이번 시간을 통해 로컬에 ng2 프로젝트를 쉽게 생성하고, 관리할 수 있게 되었습니다.

👏👏👏 고생하셨습니다.

Angular2 with Webpack

· 약 14분

정말 angular2 를 배우고 싶었다.

대세는 angular2 와 react 가 되었지만 angular2 를 선택한건 angular1 에 반했었고 구글이기 때문이었다. 근데 angular2 를 사용하려면 typescript 를 알아야하고, systemjs 또는 webpack 을 알아야하며 rxjs, corejs, zonejs, karma, e2e 등 새로운 기술을 너무 많이 알아야되었다.

대부분이 여기서 좌절(?)해 react 나 vue 로 가려고 하는 것 같다. 러닝 커브가 상당했던 이유는 이러했다.

  • angular2 의 포스트는 이론만 많았다.
  • 실전을 찾으면 버전이 알파 또는 베타 버전이라 현재와는 호환이 안된다.
  • 설치법은 알려주지도 않는다. (다 nodejs 개발자라 생각하는 것 같다.)
  • 어떤 패키지가 무슨 기능에 사용되는지 하나도 알려주지 않는다.
  • 심지어 공식 홈페이지의 starter-kit 을 clone 하면 오만가지의 테스팅 모듈도 다 딸려와 정신이 혼미하다.

2016 년에 자바스크립트를 배우는 기분은 대부분이 이런 것 같다.

하나하나 차근차근 알아가며 angular2 로 빠져보자.

npm

먼저 angular2 (이하 ng2)는 npm 으로 설치를 해야한다.

npm 이 무엇인가? bower, composer, maven 같은 패키지 다운로드 매니저이다. 더 쉽게 말하면 자바스크립트 라이브러리를 다운로드하고 관리해주는 프로그램이라 생각하자.

nodejs 다운로드에서 맞는 윈도우 버전을 다운로드해 설치하자.

package.json

npm 으로 자바스크립트 라이브러리를 다운받기 위해선 package.json(설정파일)이 필요하다. 원하는 위치에 폴더를 만들자. (D:\workspace\ng-test)

그리고 package.json 파일을 폴더 하위에 만든다.

package.json
{
"name": "ng2-webpack-start",
"version": "0.1.0",
"dependencies": {
"@angular/common": "^2.4.6",
"@angular/compiler": "^2.4.6",
"@angular/core": "^2.4.6",
"@angular/forms": "^2.4.6",
"@angular/http": "^2.4.6",
"@angular/platform-browser": "^2.4.6",
"@angular/platform-browser-dynamic": "^2.4.6",
"@angular/router": "^3.4.6",
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.9",
"rxjs": "^5.1.0",
"zone.js": "^0.7.6"
}
}

설명

  • name : 프로젝트의 이름
  • version : 버전 기법에 맞게 원하는대로 적는다.
  • dependencies : ng2 프로젝트에 사용할 js library 의 이름과 버전을 적는다.
    • @angular/common : ng2 의 기본 모듈
    • @angular/compiler : ng2 의 template 을 위해 필요한 모듈
    • @angular/core : ng2 의 기본 모듈
    • @angular/forms : ng2 로 form 을 다루기 위한 모듈
    • @angular/http : 비동기 서버 통신을 위한 모듈
    • @angular/platform-browser : ng2 를 브라우저로 표시하기 위한 모듈
    • @angular/platform-browser-dynamic : ng2 를 브라우저로 표시하기 위한 모듈
    • @angular/router : 라우팅 기능 모듈
    • core-js : js 의 최신 문법을 사용하기 위함
    • reflect-metadata : metadata 문법을 사용하기 위함
    • rxjs : observables 기능을 사용하기 위함
    • zone.js : async 함수의 도착 지점을 알기 위함

ng2 의 기능들과 그 기능을 하위버전 브라우저에서도 사용하기 위한 라이브러리들을 포함했다.

설치

프로젝트에서 쉘을 실행시켜 설치를 진행하자.

$ npm install

node_modules 폴더가 생성된 걸 확인할 수 있다.

Hello World

  • src 라는 폴더를 새로 만든다. (D:\workspace\ng-test\src)
  • index.html 과 main.ts 파일을 생성한다.

index.html

index.html
<!doctype html>
<html>
<head>
<title>Hi Angular2</title>
</head>
<body>
<main>Loading...</main>
</body>
</html>

main.ts

main.ts 는 ng2 의 기능을 하나로 통합시켜주는 시작 스크립트이다.

main.ts
import "core-js";
import "reflect-metadata";
import "zone.js/dist/zone";

import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app/app.module";

platformBrowserDynamic().bootstrapModule(AppModule);

app.module.ts

  • src 밑에 app 폴더를 만든다.
  • src\app\ 아래에 app.module.ts 파일을 생성한다. (D:\workspace\ng-test\src\app\app.module.ts)
app.module.ts
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { AppComponent } from "./app.component";

@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}

이 파일은 ng2 에서 어떤 모듈을 사용할지 알려준다.

설명

  • imports : 이 모듈에 필요한 다른 모듈
  • declarations : 모듈에 속하는 뷰 클래스, 앵귤러에는 components, directives, pipes 라는 세가지 뷰 클래스가 있다.
  • bootstrap : 메인 Component 지정 (root 모듈만 지정해야함)

컴포넌트란 무엇인가? 컴포넌트는 화면(뷰)를 제어하는 자바스크립트 클래스이다.

app.component.ts

app.module.ts 에서 사용할 메인 컴포넌트를 생성하자.

app.component.ts
import { Component } from "@angular/core";

@Component({
selector: "main",
template: ` <h1>Hello World</h1> `,
})
export class AppComponent {}

설명

  • selector : 어떤 위치에 삽입할지 attribute 이름을 적는다.
  • template : 어떤 템플릿을 사용할지 ` 기호를 사용해 적는다.

여기까지가 ng2 의 가장 기본적인 틀이다. 이제 실행을 하기 위해 만만치 않은 작업이 남았다.

TypeScript

ng2 는 typescript 를 주 언어로 사용한다. typescript 는 javascript 의 상위 집합이다. 상위집합이란 말이 어렵다면.. 그냥 javascript 랑 똑같다고 생각해도 된다. 똑같이 코딩해도 된다. 거기에 java 처럼 type 을 곁들여 코드를 짤 수 있다.

하지만 브라우저에서 실행하려면 javascript 로 compile 을 해줘야한다. 쉽게 java(ts)로 짜고 class(js)로 컴파일해야 실행되는 구조라 이해하자. 그러기 위해 몇가지 라이브러리를 npm 에서 추가로 설치해줘야한다.

# 타입스크립트 다운로드
$ npm install --save-dev typescript

위 명령어를 실행하면 개발버전(save-dev)으로 typescript 라이브러리가 설치된다. package.json 을 보면 devDependencies 옵션 밑에 의존성이 추가 된 것을 볼 수 있다.

tsconfig.json

typescript 를 javascript 로 컴파일하기 위해 기본 옵션을 설정해줘야한다. root 에 tsconfig.json 파일을 만들자.

tsconfig.json
{
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
  • target : javascript es5 버전으로 컴파일을 한다.
  • experimentalDecorators : 데코레이터 기능을 사용하기 위해 true 로 설정한다.
  • emitDecoratorMetadata : 데코레이터 기능을 사용하기 위해 true 로 설정한다.

여기까지가 ng2 의 typescript 기본 설정이다.

compile

아래 명령어를 실행한다.

$ $(npm bin)/tsc --rootDir src --outDir dist

image from hexo 실행이 되고 dist 폴더 아래 typescript 가 javascript 로 컴파일된 게 보인다. 근데 아직까진 여러 오류가 보인다. es6 의 기능을 사용할 수 없다는 오류인데 우리에겐 core-js 라이브러리가 있으니 typescript 에 core-js 를 사용하고 있다고 알려주자.

typings

라이브러리를 사용하고 있다고 알려주기 위해선 typings 를 설치해야한다.

$ npm install --save-dev typings

core-js

core-js 에 type 이 들어간 interface 를 typings 로 추가한다.

$ $(npm bin)/typings install --global --save dt~core-js

typings 폴더와 typings.json 파일이 추가된 것을 확인할 수 있다. 다시 컴파일을 해보면 오류 없이 js 로 컴파일 된다.

custom scripts

매번 $(npm bin)/... 명령어를 치기는 너무 귀찮다. package.json 을 열어 명령어를 줄인 script 기능을 사용해보자.

{
"name": "ng2-webpack-start",
"version": "0.1.0",
"scripts": {
"build": "tsc --rootDir src --outDir dist",
"postinstall": "typings install"
}
...
}

이렇게 추가하면 쉘에서 npm run build 명령어로 컴파일을 할 수 있다. 또한 postinstall 스크립트를 활성화하면 npm install 명령어 후에 바로 postinstall 명령어가 실행되어 한 번에 typings 모듈까지 설치를 할 수 있다.

../dist/main.js 를 index.html 에 추가하고 브라우저에서 열어보자.

index.html
<!doctype html>
<html>
<head>
<title>Hi Angular2</title>
</head>
<body>
<main></main>
<script src="../dist/main.js"></script>
</body>
</html>

index.html 을 열면 아래와 같은 오류가 나온다. image from hexo

이 오류는 commonjs 환경이 아니여서 발생한다.

commonjs 는 무엇인가? nodejs 와 같이 require 함수를 사용해 javascript 를 가져오는(import) 환경을 말한다.

해결하기 위해 Webpack 을 설치하자.

Webpack

Webpack 은 무엇인가? 내가 원하는 모든 파일을 하나의 javascript 파일로 불러올 수 있게 하는 모듈 번들러다.

설치

$ npm install --save-dev webpack

webpack 이 typescript 파일을 로드하기 위해선 typescript loader 모듈을 설치해줘야한다.

typescript-loader

$ npm install --save-dev awesome-typescript-loader

설치 후에 tsconfig.json 파일을 열어 webpack 을 사용한다는 옵션을 줘야한다.

tsconfig.json
{
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"awesomeTypescriptLoaderOptions": {
"useWebpackText": true
}
}

설정

root 에 webpack.config.js 파일을 만들자.

webpack.config.js
const webpack = require("webpack");
const path = require("path");

module.exports = {
entry: "./src/main.ts",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "app.bundle.js",
},
plugins: [
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)src(\\|\/)linker/,
path.resolve(__dirname, "./src"),
{},
),
],
module: {
loaders: [{ test: /\.ts$/, loaders: ["awesome-typescript-loader"] }],
},
resolve: {
extensions: [".ts", ".js"],
modules: [path.resolve(__dirname, "node_modules")],
},
};

설명

  • entry : 웹팩이 읽을 파일
  • output : 어디로 파일을 내보낼지
  • plugins : 어느 추가 플러그인을 사용할지
  • module : 파일을 가져오는데 어떤 모듈을 사용할지
  • resolve : 모듈을 어디서 찾을지

plugins 에 angular 설정을 주지 않으면 오류가 발생한다.

실행

package.json 에서 build script 를 변경한다.

package.json
{
"name": "ng2-webpack-start",
"version": "0.1.0",
"scripts": {
"build": "webpack --progress"
...
}
...
}

dist 폴더를 삭제한 뒤 빌드 스크립트를 실행한다.

# 쉘에서
$ rm -rf dist
# 터미널에서
$ rmdir dist

# 빌드 실행
$ npm run build

dist/app.bundle.js 가 생성된 것을 확인할 수 있다. index.html 에서 app.bundle.js 를 가져오게 추가한 뒤 실행해보자 image from hexo

구조

현재까지의 폴더 구조는 이렇다. image from hexo

webpack-dev

매번 컴파일할 수 없으니 자동으로 컴파일이 되고 브라우저로 볼 수 있게 해보자.

설치

webpack-dev-serverhtml-webpack-plugin을 설치한다.

$ npm install --save-dev webpack-dev-server
$ npm install --save-dev html-webpack-plugin

html-webpack-plugin

webpack.config.js에 html plugin 설정을 추가한다.

webpack.config.js
const webpack = require("webpack");
const path = require("path");
// 여기를 추가
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
plugins: [
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)src(\\|\/)linker/,
path.resolve(__dirname, "./src"),
{},
),
// 여기를 추가
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
};

index.html 에서 스크립트 삽입부분을 지운다.

index.html
<!doctype html>
<html>
<head>
<title>Hi Angular2</title>
</head>
<body>
<main></main>
</body>
</html>

webpack-dev-server

package.json 에 start 스크립트를 추가한다.

package.json
    "start": "webpack-dev-server --inline --progress"

실행

# 다시 빌드
$ npm run build
# 서버 시작
$ npm start

다시 빌드하면 dist/index.html 이 생성되고 webpack 이 생성해준 script 가 자동으로 들어가 있는걸 확인할 수 있다.

dist/index.html
<!doctype html>
<html>
<head>
<title>Hi Angular2</title>
</head>
<body>
<main></main>
<script type="text/javascript" src="app.bundle.js"></script>
</body>
</html>

서버를 시작하고 localhost:8080/dist/index.html 로 접속해보자. image from hexo

localhost:8080으로 접속해도 동일한 화면이 보인다. 이제 app.component.ts 에서 Hello World 구문을 조금 수정해보자.

바로 반영되어 브라우저에 보여지는 걸 확인할 수 있다. image from hexo

여담

이로써 ng2-webpack 기본틀이 완성되었습니다.

github에서 통소스를 보실 수 있습니다. css-loader, style-loader, file-loader, template-loader 등 webpack 에 로더 플러그인을 더 추가해야 실서비스에 사용할 수 있습니다.

Angular2 with Angular-cli로 이어집니다.

👏👏👏 고생하셨습니다.