Vue 는 어떻게 빌드될까
개요
Vue SFC 가 빌드타임에 어떻게 처리되는 지 알아봅니다.
SFC (Single-file components)
SFC 는 많은 이점을 제공합니다.
- html, css, js 를 사용한 모듈화된 컴포넌트 작성
- scoped css
- ide 지원(vetur, volar)
- HMR 지원
//source.vue
<template>
<p class="greeting">{{ greeting }}</p>
</template>
<script lang="ts">
export default {
data() {
return {
greeting: 'Hello World!'
}
}
}
</script >
<style lang="scss" scoped>
.greeting {
color: red;
font-weight: bold;
}
</style>
하지만 그 대가로 vue 파일을 파싱하기 위한 빌드단계가 필요합니다.
Vue-loader
우리의 모듈화된 코드는 브라우저가 다운로드할 수 있는 코드로 변환되기 위해 번들링 과정을 거쳐야합니다.
webpack 이 해석할 수 없는 파일(.js 이외의 파일)은 별도의 로더를 추가해서 처리해주어야 하는데요.
vue 컴포넌트 파일을 번들링하기위해 vue-loader(webpack 기준) 를 사용합니다
vue-loader 는 SFC 내의 각각의 language block 을 자체로더를 이용하여 처리합니다.
- SFC 로 작성된 vue 파일이 최초로 컴파일될 때, vue-loader는 SFC 코드를 SFC Descriptor 로 변환합니다.
// code returned from the main loader for 'source.vue'
// import the <template> block
import render from 'source.vue?vue&type=template'
// import the <script> block
import script from 'source.vue?vue&type=script&lang=ts'
export * from 'source.vue?vue&type=script&lang=ts'
// import <style> blocks
import 'source.vue?vue&type=style&index=1&scoped&lang=scss''
script.render = render
export default script
파싱된 결과물은 위와 같이 생겼습니다.
파싱을 위해 vue-loader 는 @vue/compiler-sfc 패키지를 사용합니다. (vue-loader 의 package.json 에 해당 의존성이 peerDependency 로 추가된 것을 확인할 수 있습니다. )
SFC 에 작성되어 있던 template, script, style 블록은 각각 다른 쿼리스트링을 가진 import 문으로 변환되었습니다.
하지만 아직 모듈 이름의 확장자가 .vue 입니다.
각각의 import 문은 블록 타입에 따라 각각 다른 번들링 작업이 필요할 텐데요.
이 작업을 수행해주는 플러그인이 VueLoaderPlugin 입니다.
VueLoaderPlugin
vue-loader 를 사용하였다면, 다음과 같이 VueLoaderPlugin 이 plugin 설정에 추가되어야합니다.
const VueLoaderPlugin = require('vue-loader/lib/plugin');
//... in webpack config
plugins: [
new VueLoaderPlugin(),
//...
VueLoaderPlugin 은 SFC descriptor 파일에 적용됩니다. 작성된 webpack 설정을 읽어서 각 블록에 적용할 rule 을 골라주는 역할을 합니다.
예를들어 vue-loader 가 한번 적용된 vue descriptor 파일에서 다음 import 문에 대해서는,
import script from 'source.vue?vue&type=script&lang=ts'
- 확장자가 vue 인 모듈이므로 vue-loader 을 선택하고
- language 가 typescript 이므려 ts 파일에 적용되는 loader (ex: ts-loader)를 선택하고
- script 타입의 블록이니 js 확장자 모듈에 적용되는 loader 선택하여
순차적으로 적용할 수 있도록 import 문을 확장합니다.
우리가 설정한 설정파일이 다음과 같다면
module: {
rules: [
{
test: /\\.vue$/,
loader: 'vue-loader',
options: vueConfig
},
{
test: /\\.js$|\\.ts$/,
exclude: transpileExcludes,
use: [
babelLoader,
tsLoader,
]
}
{
test: /\\.(svg|png|jpe?g|gif)(\\?.*)?$/,
loader: 'url-loader',
options: urlLoaderOptions
},
타입스크립트가 사용된 script 블록에는 vue-loader, ts-loader, babel-loader 가 순차적으로 적용되도록 처리합니다.
다음은 VueLoaderPlugin 이 SFC Descriptor 에 적용되었을 때 각 문이 확장되는 예시입니다.
script
기존
import script from 'source.vue?vue&type=script&lang=ts'
변환 후
import script from 'babel-loader!ts-loader!vue-loader!source.vue?vue&type=script'
vue-loader의 두번째 호출
💡 VueLoaderPlugin 이 적용된 이후 vue-loader 가 각 문에 추가된 것을 확인할 수 있습니다. vue 확장자의 모듈이기 때문입니다. 확장된 import 문은 webpack 에 의해 번들링됩니다. vue loader 는 다시 호출됩니다. 하지만 쿼리스트링에 type 이 명시되어 있는 경우 vue-loader는 SFC Descriptor 를 생성하는 대신, 모듈의 콘텐츠를 다음 로더에 전달합니다.
style, template
style 블록과 template 블록도 script 블록이 처리될때와 같이 동작하지만, 몇가지 특이사항이 있습니다.
- template 블록을 vue-template-compiler 로 컴파일해야합니다.
- <style scoped> 이 사용된 경우 별도의 처리가필요합니다(스타일이 지역적으로 적용되도록). 이 처리는 css-loader 가 적용된 이후이면서, style-loader 적용되기 이전에 처리되어야합니다.
- vue-loader/style-post-loader 적용이 필요합니다.
이같은 처리는 개발자의 빌드 설정에 의해 수행되지 않고, VueLoaderPlugin 이 자체적으로 지원합니다.
기존
import render from 'source.vue?vue&type=template'
// import <style> blocks
import 'source.vue?vue&type=style&index=1&scoped&lang=scss''
변환 후
// <template >
import 'vue-loader/template-loader!source.vue?vue&type=template'
// <style scoped lang="scss">
import 'style-loader!vue-loader/style-post-loader!css-loader!sass-loader!postcss-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'