Daehyunii's Dev-blog

[데브코스] TIL-122 Vue, Node.js, Parcel, Webpack, 컴포넌트 본문

✏️ 2022. TIL/December (데브코스)

[데브코스] TIL-122 Vue, Node.js, Parcel, Webpack, 컴포넌트

Daehyunii 2022. 12. 15. 18:25
Node.js와 NPM

1. node.js

  자바스크립트 런타임이다. 브라우저에서 자바스크립트가 동작할 수 있는 환경을 만들어주는 것이다. 더 안정적인 LTS 버전을 사용하자.

npm

  node.js 패키지 매니저이다. 여러 패키지를 설치하여 우리가 개발하기 용이하게 해준다.

nvm

  node.js 버전을 바꾸면서 사용할 수 있도록 도와주는 패키지이다. 이 때, node.js가 설치되기 전에 설치를 해야 한다. nvm-setup 또는 명령어로 설치할 수 있다.

n

  이미 node.js가 설치되어 있는 상태이고 삭제하기 어렵다면 nvm 대신 n 패키지를 설치하면 좋다. npm으로 설치할 수 있고, 원하는 node.js 버전을 설치할 수 있게 도와주는 패키지이다. 프로젝트를 npm으로 관리하고 싶으면 아래 명령어로 시작할 수 있다.

npm init -y

원하는 패키지를 설치한다. 우리는 vue 3버전 패키지를 설치할 것이다.

npm install vue@next

만약 install한 패키지를 삭제하고 싶다면 아래와 같은 명령어를 사용할 수 있다. uninstall을 대신하여 un도 사용 가능하다.

npm uninstall vue@next

npx

  패키지를 실행시킨다는 의미를 가진다. 개발 서버를 실행하기 위해 serve라는 패키지를 실행시키는 아래와 같은 명령어를 입력하면 된다.

npx serve

package-lock.json

  package.json보다 버전을 완벽히 정확하게 가지고 있는 파일이다. 완전히 동일한 환경으로 개발할 수 있게 정보를 가지고 있다.

vue 패키지는 node.js 기반 common.js 방식으로 모듈을 가져오게 되어 있다. nodes.exports와 require을 사용한다. 그래서 esm 방식인 export/import를 사용하기 위해서는 아래와 같이 작성하면 된다. vue의 모든 내용을 Vue라는 이름으로 가져오겠다는 뜻이다.

import * as Vue from 'vue'

2. parcel

  파일들을 묶어주는 번들러이다. index.html을 번들러의 진입점으로 설정해주면 된다. parcel은 node.js 14버전 이상에서 사용할 수 있다.

3. Webpack

  엔트리 포인트를 기준으로 여러 파일들을 묶음 처리 해주는 번들러이다.

npm install -D webpack webpack-cli

webpack.config.js

  node.js 환경에서 webpack의 환경 설정을 해주는 파일이다. 아래와 같이 entry 포인트와 output의 경로를 지정해줄 수 있다. 어느 파일로 들어가서 해석하고 어떤 파일, 어느 위치로 결과를 만들지를 지정한다고 볼 수 있다.

const path = require('path');

module.exports = {
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        clean: true
    }
}

vue-loader

  vue라는 확장자를 가진 파일을 해석할 수 있게 도움을 주는 로더이다. 로더가 직접 해석을 하는 것은 아니다. vue라는 확장자를 가진 파일을 찾아 읽을 준비까지만 한다.

...

module: {
    rules: [
        {
            test: /\.vue$/,
            use: 'vue-loader'
        }
    ]
}

파일을 해석하기 위해 @vue/compiler-sfc를 설치해야한다. 이는 vue와 버전이 완전 일치 해야한다. plugins 옵션에 vue-loader를 사용하겠다는 것을 코드에 작성하자.

const { VueLoaderPlugin } = require('vue-loader');

...

plugins: [
    new VueLoaderPlugin()
]

mode 옵션은 필수적으로 작성하는 것이 좋다. webpack.config.js에 작성할 수도 있지만, package.json에도 작성할 수 있다.

...

"scripts": {
    "build": "webpack --mode production"
},

이대로만 실행시키면 index.html이 dist 폴더 안에 생성되지 않는다. 하지만 우리가 웹을 보기 위해서는 index.html이 dist 폴더 안에 생성되야 한다. 따라서 html-webpack-plugin 플러그인을 설치하고 적용해보자.

const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');

...

plugins: [
    new VueLoaderPlugin(),
    new HtmlPlugin({
        template: './src/index.html' // 템플릿 경로를 지정
    })
]

CSS 코드를 해석할 수 있도록 와 vue-style-loader , css-loader , sass-loader로더를 설치하고 적용해보자.

...,
module: {
    rules: [
        {
            test: /\.vue$/,
            use: 'vue-loader'
        },
        {
            test: /\.s?css$/,
            use: ['vue-style-loader',
                        'css-loader',
                        'sass-loader']
        }
    ]
}

resolve 객체 내 extensions에 생략하고 싶은 확장자를 명시해줄 수 있다. alias에는 경로 별칭을 설정해줄 수 있다. 시작하고 싶은 경로를 명시해주면 절대경로처럼 사용할 수 있다.

module.exports = {	
    resolve: {
        extensions: ['.vue', '.js'],
        alias: {
            '~': path.resolve(__dirname, 'src')
        }
    },

...

copy-webpack-plugin 플러그인을 사용하면 파일을 복사하여 붙여넣기 할 폴더를 지정해줄 수 있다.

const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const copyPlugin = require('copy-webpack-plugin');

...

plugins: [
    new VueLoaderPlugin(),
    new HtmlPlugin(
        template: './src/index.html' // 템플릿 경로를 지정
    ),
    new copyPlugin({
        patterns: [
            { from: 'static' } // to: 'dist' 는 생략
        ]
    })
]

4. ESlint

  코드 스타일 혹은 규칙을 설정할 수 있다.

 

.eslintrc.json

{
    "env": {
        "browser": true,
        "node": true,
    },
    "extends": [
        "eslint:recommended",
        "plugin:vue/vue3-recommended"
	],
    "rules": {
        "semi": ["error", "never"], // 세미 콜론 없애기
        "quotes": ["error", "single"], // 작은 따옴표로 설정 
        "vue/html-closing-bracket-newline": ["error", {
            "singleline": "never",
            "multiline": "never"
        }],
        "vue/html-self-closing": ["error", {
            "html": {
                "void": "always",
                "normal": "never",
                "component": "always"
            },
            "svg": "always",
            "math": "always"
        }]
    }
}

eslint 규칙에 따라 코드도 저장될 수 있도록 아래의 코드를 작성해준다. 이 파일은 View > Command Palette > json 검색 > User Setting 을 클릭하면 확인할 수 있다.

 

setting.json

"editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
 },

컴포넌트 등록

  컴포넌트를 등록할 때는 항상 컴포넌트 이름을 지정해주어야 한다. cdn 프로젝트를 제외하고는 camelCase로 지정해주는 것이 좋다.

컴포넌트를 모두 전역 등록하면 관리하기 힘들 수 있기 때문에 웬만하면 컴포넌트를 지역 등록하는 것이 좋다.

1. 컴포넌트 Props

1.1 정적/동적 Props 전달

  정적 값을 전달할 수도 있고 v-bind를 사용하여 동적으로도 전달할 수 있다. 다양한 데이터를 props로 전달할 수 있다.

1.2 객체 속성 전달

  post라는 객체를 전체로 전달해줘도 각 key와 value에 접근이 가능하다.

<Hello v-bind="post" />

1.3 단방향 데이터 흐름

  부모 속성이 업데이트되면 자식으로 흐르지만 반대 방향은 안된다. 일반적으로 부모 컴포넌트에서 받은 데이터는 하위 컴포넌트에서 수정해서 사용할 수 없다. 만약 props를 초기 값을 전달하는데만 사용한다면 prop를 초기 값으로 사용하는 로컬 data 속성을 정의하는 것이 가장 좋다.

1.4 Props 유효성 검사

  기본 타입을 체크하거나, 배열을 통해 여러 타입을 허용할 수도 있다. 그리고 기본 값을 선언해줄 수도 있다.

data() {
    props: {
        type: [ Number, String ],
        default: 100
    }
}

1.5 Prop 대소문자 구분

  script 부분에서는 prop 네임을 camelCase로, HTML 부분에서는 kebab-case로 작성하여 사용해야 한다.


컴포넌트 Non-Prop 속성

1. props가 지정되어 있지 않을 때

  최상위 요소가 1개이면 컴포넌트에 속성을 작성하면 잘 적용되는데 그렇지 않은 경우 아무 속성도 적용되지 않는다. 이런 경우 아래와 같이 작성하여 어느 요소에 속성을 적용할지를 지정해줄 수 있다.

 

상위 컴포넌트

<Hello class="hello" style="font-size: 100px" @click="msg += !" />

하위 컴포넌트

<template>
    <h1 v-bind="$attrs">hello</h1>
</template>

최상위 요소가 1개이면 컴포넌트에 속성을 작성하면 속성이 상속되는데 이를 원치 않는다면 script 부분에 inheritAttrs: false를 작성해주면 된다.

 

2. 컴포넌트 커스텀 이벤트

  자식 컴포넌트에서 부모 컴포넌트에 이벤트를 요청할 때 $emit을 사용할 수 있다. 커스텀 이벤트를 아래와 같이 사용할 수 있다.

 

상위 컴포넌트

<Hello @click="msg += '?'" @please="msg += '~'"/>

하위 컴포넌트

<template>
    <div>
        <h1 @click="@emit('please')">please</h1>
        <h2 @click="@emit('click')">click</h2>
    </div>
</template>

<script>
    export default {
        emits: {
            click: null,
            please: number => {
                if (number > 10) {
                    return true
                } else {
                    console.error('number is not greater than 10')
                    return false
                }
            }	
        }
    }
</script>

아래는 상위 컴포넌트에서 하위 컴포넌트로 단방향 데이터 message를 내려주고, 하위 컴포넌트에서 이를 갱신할 수 있는 이벤트를 올려주어 처리하는 방식이다.

 

상위 컴포넌트

<Hello :message="msg" @update="msg = $event"/>

하위 컴포넌트

<template>
    <label>
        <input :value="message" @input="@emit('update', $event.target.value)" />
    </label>
</template>

<script>
    export default {
        props: {
            message: {
                type: String,
                default: ''
            }	
        }
    }
</script>

위의 내용을 더 간단하게 작성하는 방법이 있다. 상위 컴포넌트에 v-model을 사용하면 위에 작성한 내용을 포함하는 코드가 된다. 즉, 단축기능 같은 개념이다. 양방향 데이터 바인딩이 가능하기 때문이다.

 

상위 컴포넌트

<Hello v-model="msg" />

v-model과 props가 연결되었다는 것을 나타내기 위해 modelValue라는 내부적으로 선언된 예약어를 사용하면 된다. 다른 이름을 사용하고 싶다면 상위 컴포넌트에도 v-model:사용하고자하는이름 처럼 사용하면 된다.

 

하위 컴포넌트

<template>
    <label>
        <input :value="modelValue" @input="@emit('update:modelValue', $event.target.value)" />
    </label>
</template>

<script>
    export default {
        props: {
            modelValue: {
                type: String,
                default: ''
            }	
        }
    }
</script>

 

 

오늘을 마무리 하며

 

  아...쉽지 않네?...새로운 개념들이 너무 많다... 아 오늘은... 일찍 자야겠다..