1. Webpack과 Loader
Webpack은 모든 의존성 리소스를 동등하게 대우하고, 서로 다른 유형의 리소스 간 처리 차이를 없애고자 합니다:
Unlike most bundlers out there, the motivation behind Webpack is to gather all your dependencies (not just code, but other assets as well) and generate a dependency graph.
그리고 이러한 차이를 없애는 역할을 담당하는 것이 바로 Loader입니다.
Webpack: 무엇을 가져오든 나에게는 JS 모듈로 변환해서 전달하세요. 그렇지 않으면 오류를 발생시킵니다.
Loaders: 알겠습니다.
설정 구조
Entry
의존성 그래프의 진입점
entry: './src/index.js'
// 또는 다중 진입점 형태
entry: {
main: './src/index.js'
}
Output
출력 결과물
output: {
filename: 'main.js',
path: path.resolve('./build')
}
// 다중 진입점에 대응하는 형태
output: {
filename: '[name].js',
path: path.resolve('./build')
}
Loaders
의존성 처리기, 의존성 항목을 가로채서 전처리를 수행합니다.
예를 들어 이런 시나리오가 있습니다:
// index.js file
import helpers from '/helpers/main.js';
// Hey Webpack! I will need these styles:
import 'main.css';
Webpack은 CSS를 인식하지 못하므로(직접 처리 불가), 먼저 Loader에 의해 가공(전처리)되어야 합니다.
일반적인 Loader 설정:
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'less-loader' }
]
}
]
}
Loader 적용 순서는 less-loader, css-loader, style-loader입니다.
P.S. 설정 파일을 통해 Loader와 리소스 유형의 관계를 지정하는 것 외에도, 리소스를 가져올 때 loadername! 접두사를 붙일 수 있습니다. 예를 들어 import image from 'file-loader!./my-img.png'와 같습니다.
Plugins
Loader가 부족하거나 불편할 때, 혹은 Loader가 할 수 없는 일을 수행할 때 사용자 정의 플러그인을 통해 확장합니다.
예를 들어 extract-text-webpack-plugin은 스타일 규칙이 번들에 포함되는 style-loader의 기본 동작을 변경하여, CSS를 별도로 수집하고 <link> 태그를 통해 외부 리소스로 참조하게 합니다.
var ExtractTextPlugin = require('extract-text-webpack-plugin');
plugins: [
new ExtractTextPlugin('main.css')
]
html-webpack-plugin은 진입 HTML 파일을 생성하는 데 사용됩니다.
var HTMLWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HTMLWebpackPlugin()
]
내장된 DefinePlugin은 환경을 구분하는 데 사용됩니다.
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(ENV)
})
내장된 UglifyJsPlugin은 소스 코드를 압축하는 데 사용됩니다.
new webpack.optimize.UglifyJsPlugin()
참고로 이름이 비슷한 uglifyjs-webpack-plugin도 있는데, 내장된 UglifyJsPlugin과 미세한 버전 차이가 있습니다. 자세한 내용은 Is webpack.optimize.UglifyJsPlugin() UglifyJsPlugin() ?을 확인하세요.
2. Loader
Loader는 주로 JS가 아닌 리소스 의존성을 처리하는 데 사용됩니다.
webpack enables use of loaders to preprocess files. This allows you to bundle any static resource way beyond JavaScript.
Plugin과의 차이점
Loader는 특정 유형의 의존성만 처리합니다. '처리'에는 파싱, 변환 등이 포함되며, Webpack이 인식하지 못하는 것들(각종 비 JS 의존성)을 번들에 포함할 수 있는 JS로 변환합니다.
Plugin은 더 강력하여, 여러 Loader를 거친 출력을 추가로 가공하거나 빌드 전/후 또는 과정 중에 기능을 확장하고 강화할 수 있습니다.
실체
A loader is a node module that exports a function. This function is called when a resource should be transformed by this loader. The given function will have access to the Loader API using the this context provided to it.
Loader는 의존성 리소스를 변환하는 함수입니다. 이 함수는 Loader API를 통해 번들링 과정의 컨텍스트 정보(대상 원본 리소스 내용 또는 이전 loader의 출력, loader 설정 항목 등)를 가져올 수 있으며, Webpack 내부 메서드를 호출(예: this.resolve()를 통해 2차 의존성 처리)할 수 있습니다.
특징
-
단일 책임
-
조합 가능
-
상태 없음
Loader는 조합 가능해야 하므로 단일 책임을 가져야 하며(작업 공정을 유연하게 설정 가능), 사이드 이펙트가 자유로운 조합을 방해하지 않도록 상태가 없어야 합니다.
3. 체이닝 Loader (Chaining loaders)
Webpack 1은 Loader 적용 순서를 선언하는 세 가지 방식을 지원했습니다.
loaders: [
{ test: /\.scss$/, loader: 'style' },
{ test: /\.scss$/, loader: 'css' },
{
test: /\.scss$/, loader: 'autoprefixer',
query: { browsers: 'last 2 version' }
},
{
test: /\.scss$/, loader: 'sass',
query: { outputStyle: 'expanded' }
}
]
또는
loaders: [
{
test: /\.scss$/,
loaders: [
'style',
'css',
'autoprefixer?browsers=last 2 version',
'sass?outputStyle=expanded',
]
}
]
또는
loaders: [
{
test: /\.scss$/,
loader: 'style!css!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded',
},
]
오른쪽에서 왼쪽으로 적용되며(grunt/gulp의 task 정의 순서와 반대), 함수 조합 형태인 style(css(file))과 유사하며 파이프라인과도 비슷합니다. 예를 들어 last!second!first는 다음을 의미합니다.
last Loader가 가장 먼저 적용되며, 원본 리소스 내용을 받을 수 있습니다.
second Loader는 이전에 실행된 Loader의 반환 결과를 받을 수 있습니다.
first Loader가 마지막으로 적용되며, JS 모듈과 선택적으로 소스 맵(source map)을 반환해야 합니다.
이는 echo $resource_content | first | second | last와 같으며, 원본 리소스 내용을 입력받아 JS 모듈(CMD 모듈 또는 ES 모듈)을 출력하고 그 사이에 n개의 Loader를 거칠 수 있습니다.
이후 버전에서는 다음과 같이 변경되었습니다.
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'less-loader',
options: {
noIeCompat: true
}
}
]
하지만 호환성을 위해 query 속성도 유지되고 있으며, options의 별칭으로 존재합니다. 자세한 내용은 UseEntry를 참조하세요.
P.S. 체이닝 Loader에 대한 더 자세한 내용은 Chaining loaders를 확인하세요.
4. Loader 예시
예를 들어, JSONC(JavaScript 스타일 주석이 있는 JSON)에서 주석을 제거하고 일반 JSON처럼 로드해야 하는 시나리오가 있다고 가정해 봅시다.
// settings.json
{
// Format a file on save. A formatter must be available, the file must not be auto-saved, and editor must not be shutting down.
"editor.formatOnSave": false
}
기본적인 JSON 의존성 처리는 주석을 지원하지 않습니다.
Module build failed: SyntaxError: Unexpected token / in JSON at position 0
이때 적당한 것을 찾지 못했다면 간단한 Loader를 직접 만들 수 있습니다.
import { getOptions } from 'loader-utils';
import { parse as parseJSONC, stripComments } from 'jsonc-parser';
export default function parse(source) {
const opts = getOptions(this);
let json = stripComments(source);
if (opts.extRule) {
const filePath = this.resourcePath;
const matched = opts.extRule.test(filePath);
if (!matched) {
let errors = [];
json = parseJSONC(source, errors, {
disallowComments: true
});
if (errors.length) {
throw new Error(`Unexpected COMMENTS at ${filePath}`);
}
}
}
return `export default ${json}`;
}
MS의 jsonc-parser를 사용하여 JSONC/JSON을 파싱하며, 확장자 규칙을 검사하는 설정 항목 extRule도 지원합니다.
{
test: /\.json$/,
use: {
loader: path.join(__dirname, './loaders/jsonc-loader.js'),
options: {
extRule: /.jsonc?$/i
}
}
}
P.S. 동기 방식의 Loader 외에도 콜백 주입 방식의 Asynchronous Loaders도 있습니다.
P.S. 더 많은 Loader API는 The Loader Context를 참조하세요.
5. 자주 사용되는 Loader
실제 응용 시나리오에는 다음과 같은 전형적인 조합이 많습니다.
-
style-loader!css-loader: 앱이 의존하는 CSS를 수집하고, 런타임에<style>태그를 통해 페이지에 삽입합니다. -
file-loader: 여러 파일을 생성하는 방식 (특이하게도 이 작업이 메인 설정이 아닌 Loader를 통해 이루어집니다) -
file-loader!html-loader: HTML을 가져와서 템플릿 교체 등의 전처리를 수행한 후 출력 파일을 생성합니다.
공식적으로는 7가지 카테고리의 Loader를 소개합니다.
파일
-
raw-loader: 파일 내용을 직접 가져옵니다.
-
val-loader: JS 코드를 로드하며, CMD 모듈 형식을 요구합니다.
-
url-loader: file-loader와 유사하지만, 작은 파일에 대해 data URL 반환을 지원합니다.
-
file-loader: 파일을
output디렉토리에 복사하고 상대 URL을 반환합니다.
JSON
-
json-loader: 기본적으로 내장되어 있으며, JSON 파일을 로드합니다.
-
json5-loader: JSON 5 파일(ES5.1 JSON 문법)을 로드하고 트랜스파일합니다.
-
cson-loader: CSON 파일을 로드하고 트랜스파일합니다.
트랜스파일
-
script-loader:
global환경에서 JS 파일을 실행합니다(script태그와 유사). 내부의require는 변환되지 않습니다. -
babel-loader: ES2015+ 코드를 로드하고 Babel을 사용하여 ES5로 변환합니다.
-
buble-loader: ES2015+ 코드를 로드하고 Bublé를 사용하여 ES5로 변환합니다.
-
traceur-loader: ES2015+ 코드를 로드하고 Traceur를 사용하여 ES5로 변환합니다.
-
ts-loader 또는 awesome-typescript-loader: TypeScript 2.0+ 코드를 로드합니다.
-
coffee-loader: CoffeeScript 코드를 로드합니다.
템플릿
-
html-loader:
require로 참조된 HTML 정적 리소스를 문자열로 내보냅니다. -
pug-loader: Pug 템플릿을 로드하고 함수를 반환합니다.
-
jade-loader: Jade 템플릿을 로드하고 함수를 반환합니다.
-
markdown-loader: Markdown을 HTML로 컴파일합니다.
-
react-markdown-loader:
markdown-parse파서를 사용하여 Markdown을 React 컴포넌트로 컴파일합니다. -
posthtml-loader: HTML 파일을 로드하고 PostHTML을 사용하여 변환합니다.
-
handlebars-loader: Handlebars를 HTML로 컴파일합니다.
-
markup-inline-loader: SVG/MathML 파일 내용을 HTML에 삽입합니다. 아이콘 폰트나 SVG에 CSS 애니메이션을 적용할 때 유용합니다.
스타일
-
style-loader: 모듈 출력을 style 태그로 DOM에 삽입합니다.
-
css-loader: CSS 파일을 로드하여 CSS를 반환하며, imports를 지원합니다.
-
less-loader: LESS 파일을 로드하고 컴파일합니다.
-
sass-loader: SASS/SCSS 파일을 로드하고 컴파일합니다.
-
postcss-loader: CSS/SSS 파일을 로드하고 PostCSS를 사용하여 변환합니다.
-
stylus-loader: Stylus 파일을 로드하고 컴파일합니다.
Lint 검사 및 테스트
-
mocha-loader: mocha를 사용하여 브라우저/NodeJS 환경에서 테스트를 수행합니다.
-
eslint-loader: 프리로더(preload)로, ESLint를 사용하여 Lint 검사를 수행합니다.
-
jshint-loader: 프리로더로, JSHint를 사용하여 Lint 검사를 수행합니다.
-
jscs-loader: 프리로더로, JSCS를 사용하여 코드 스타일을 검사합니다.
-
coverjs-loader: 프리로더로, CoverJS를 사용하여 테스트 커버리지를 측정합니다.
프레임워크
-
vue-loader: Vue 컴포넌트를 로드하고 컴파일합니다.
-
polymer-loader: 설정 가능한 전처리기를 사용하여 HTML과 CSS를 처리하며, 일반 모듈처럼 Web Components를
require()할 수 있게 지원합니다. -
angular2-template-loader: Angular 컴포넌트를 로드하고 컴파일합니다.
P.S. 더 많은 서드파티 Loader는 awesome-webpack을 참조하세요.
아직 댓글이 없습니다