모듈은 세부 사항을 캡슐화하고, 공개가 필요한 API만을 외부에 노출한다. 일반적으로, 각 모듈은 파일 단위로 분리되어 있고, 애플리케이션은 필요에 따라 모듈을 로드하여 재사용한다. 즉, 모듈은 애플리케이션에 분리되어 개별적으로 존재하다가 애플리케이션의 로드에 의해 비로소 애플리케이션의 로드에 의해 비로소 애플리케이션의 일원이 된다.
모듈은 기능별로 분리되어 작성되므로, 코드의 단위를 명확히 분리하여 애플리케이션을 구성할 수 있고, 재사용에 좋기 때문에, 개발을 효율적으로 할 수 있고, 유지보수 또한 좋다.
ES6에서 모듈을 사용할 때, export와 import 두 개의 키워드를 사용한다.
Module 사용의 이점
Javascript 애플리케이션을 여러 개의 파일로 분할하여 작성할 수 있다.
재사용이 가능한 코드를 작성하기 때문에, 개발 효율과 유지보수에 좋다.
Export
모듈은 기본적으로 독립적인 스코프를 가지고 있어서, 모듈안에 선언된 모든 것들은 해당 모듈 내에서만 참조 할 수 있다.
어떤 모듈안에서 선언한 항목(변수/함수/클래스 등)을 외부에 공개하여 다른 모듈들이 사용할 수 있게 하려면, export를 해야한다. 이때, 선언된 변수, 함수, 그리고 클래스 모두를 export 할 수 있고, 키워드는 export를 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// module.js // 변수의 공개 export const num = 10;
// 함수의 공개 export function square(x) { return x * x; }
// 클래스의 공개 export class Person { constructor(name) { this.name = name; } }
각 선언문(변수/함수/클래스) 앞에 export를 붙이는 대신, export 대상을 하나의 객체로 구성하여, 한번에 export를 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// module.js const num = 10;
function square(x) { return x * x; }
class Person { constructor(name) { this.name = name; } }
// 변수, 함수 클래스를 하나의 객체로 구성하여 공개 export { num, square, Person };
Import
다른 모듈에서 어떤 export된 모듈을 가져와 사용하려면, 키워드 import를 사용해 로드하여 사용할 수 있다.
1 2 3 4 5 6 7
// main.js // 같은 폴더 내의 module.js 모듈을 로드. 확장자 js는 생략 가능. import { num, square, Person } from './module';
생성자 함수를 사용할 때는, 화살표 함수를 사용하면 안된다. 생성자 함수는 prototype 프로퍼티를 가지며 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor를 사용한다. 그러나, 화살표 함수는 prototype 프로퍼티를 가지고 있지 않다.
1 2 3 4 5 6
const Foo = () => {};
// 화살표 함수는 prototype 프로퍼티가 없다 console.log(Foo.hasOwnProperty('prototype')); // false
const foo = new Foo(); // TypeError: Foo is not a constructor
<4> addEventListener 함수의 콜백 함수4>
addEventListener 함수의 콜백 함수를 사용할 때는, 화살표 함수를 사용하면 안된다.
만약, addEventListener 함수의 콜백 함수를 화살표 함수로 정의하면, 화살표 함수 내의 this는 상위 컨택스트인 전역 객체 window를 가리킨다.
그래서, addEventListener 함수의 콜백 함수를 사용할 때는, 일반 함수인 function 키워드를 사용해야 한다. 일반 함수로 정의된 addEventListener 함수의 콜백 함수 내부의 this는 이벤트 리스너에 바인딩된 요소(currentTarget)를 가리킨다.
라우팅이란, 웹 페이지간의 이동 방법을 말하며, 웹 앱의 형식 중 하나인 SPA에서 주로 사용한다.
라우팅의 장점은 화면 간의 전환이 매끄러워, UX를 향상 시킬 수 있다.
일반적인 웹(MPA)에서는 특정 행동(링크 클릭, 또는 특정 이벤트)를 한 후, 페이지를 이동하게 되면, 흰색의 빈 페이지가 깜박거린 후, 이동된 화면이 나타나게 된다. 이런 부분들을 라우팅으로 처리하면 깜빡러미 없이 매끄럽게 화면이 전환될 수 있다.
Vue에서의 라우팅
Vue 라우터는 Vue에서 라우팅을 할 수 있도록 지원하는 공식 라이브러리이다.
Vue에서 라우팅 설치 및 등록
<설치>
Npm install vue-router
<등록>
1 2 3
Import VueRouter from ‘vue-router’
Vue.use(VueRouter)
기본적인 Vue Routing 사용법
Vue 라우터 라이브러리를 사용하면, 2개의 tag를 사용한다.
<router-link to=“url">: 페이지 이동 태그로, 화면에서는 <a>로 표시되며 클릭하게되면, to속성에 입력한 URL로 이동
<router-view>: 페이지 표시 태그로, 변경되는 URL에 따라 해당 컴포넌트를 출력해주는 영역
기본적인 라우팅을 만들어보자. 위에 버튼 두개가 있는데, 왼쪽은 todo 페이지로 이동, 오른쪽은 user 페이지로 이동하는 라우팅을 만들어 보자.
(1) vue-router를 설치하고, router.js를 만들어 다음과 같이 작성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// router.js import Vue from 'vue' import VueRouter from 'vue-router' import Todo from './components/Todo.vue' import User from './components/User.vue'
Todo와 User 컴포넌트를 만들고, import 한뒤, router 인스턴스를 생성하고, path와 path에 맞는 컴포넌트를 맵핑한다.
‘History’ mode는 기본적으로 url에 #이 들어가 있는데, 이를 제거해 준다.
(2) main.js에 등록하여, 전역에서 사용할 수 있게 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13
// main.js import Vue from 'vue' import App from './App.vue' import store from './store' import router from './router'
Vue.config.productionTip = false
new Vue({ store, router, render: h => h(App), }).$mount('#app')
(3) App.vue에 라우터 링크를 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12
// App.vue <section> <div> <router-link to="/"><button>Go to Todo page</button></router-link> <br> <router-link to="/user"><button>Go to User page</button></router-link> </div> </section>
<br>
<router-view></router-view>
이렇게 되면, 각 버튼을 클릭하면, 각 버튼에 해당하는 path로 이동하여, path에 맵핑된 컴포넌트를 화면에 뿌려준다.
Nested 라우팅
Nested(중첩된) 라우터는 라우터로 페이지를 이동할 때, 최소 2개 이상의 컴포넌트를 화면에 나타내는 것을 말한다. 상위 컴포넌트 1개와 하위 컴포넌트 N개로 구성할 수 있다.
예를 들어, User 페이지에서 url에 따라, User의 Profile과 User의 Post를 각각 보여주는 라우터를 만들어보자.
(1) 두 개의 컴포넌트 UserProfile과 UserPost를 만들고, 상위 컴포넌트인 User에 그 하위 컴포넌트로 UserProfile과 UserPost를 라우팅으로 등록한다.
// router.js import Vue from 'vue' import VueRouter from 'vue-router' import Todo from './components/Todo.vue' import User from './components/User.vue' import UserProfile from './components/UserProfile.vue' import UserPost from './components/UserPost.vue'
동적 라우팅이란, 일정한 패턴의 URI 경로를 하나의 컴포넌트에 연결하는 방법이다. 일정한 패턴의 URI란, 예를 들어, projects 페이지에서 각 페이지별로 상세하게 보고 싶은데, 이때 URI를 projects/1, projects/2 이런식으로 각 프로젝트를 id로 구별하는 것을 말한다. 이를 하나의 컴포넌트(Projects.vue)로 연결하여 사용한다.
하나의 메인 페이지에서 어떤 링크(버튼 등)를 클릭하면, projects 페이지가 나오고, 그 projects 페이지에는 여러개의 프로젝트들이 보여진다. 그리고, 각 프로젝트를 클릭하면, 각 프로젝트에 대한 상세 정보가 나오게 한다. 이때, 클릭 시 라우팅의 URI는 projects/1 이런식으로 지정하여 만든다.
// router.js import Vue from 'vue' import VueRouter from 'vue-router' import Projects from './components/Projects.vue' import Project from './components/Project.vue'
(3) App.vue에서 Projects 페이지로 이동하는 라우터 링크를 작성한다. 그리고, 링크 클릭 시, Projects 페이지의 정보를 보여주기 위해, router-view를 아래에 작성한다.
1 2 3 4 5 6 7 8 9 10 11 12
// App.vue <template> <div id="app"> <p>This is main page</p>
<router-link to="/projects"><button>Go to Project page</button></router-link>
<br>
<router-view></router-view> </div> </template>
(4) Projects 페이지는 프로젝트가 나열되어 나오게 한다. 그리고, 각 프로젝트에 라우터 링크를 걸어, 그 링크를 클릭시, 클릭한 프로젝트에 대한 상세 정보가 나오는 Project 페이지가 나오게 한다. 이때, router-link에는 name과 params를 같이 넣어 주는데, 이는 router.js에서 등록한 그대로 작성하면 된다.
// router.js import Vue from 'vue' import VueRouter from 'vue-router' import Body from './components/Body.vue' import Header from './components/Header.vue' import Footer from './components/Footer.vue'
Update는 클라이언트단에서 기존에 있던 데이터를 서버에 요청하여, 데이터를 변경하는 것이다. todo 앱에서는 toggle이 된다.
Vuex(store.js)의 actions에 메소드 등록할 때, axios의 patch를 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// store.js actions: { toggle: function(context, payload) { // patch를 사용할 때는, 요청할 uri에 변경할 데이터의 uri를 붙여서 보낸다. 예를 들어, /todos/id가 올 수 있다. axios.patch(`http://localhost:4000/todos/${payload}`) // 요청이 성공하면, mutations에 commit을 하여, 특정 데이터가 변경된 상태로 데이터가 변경된다. .then((res) => { console.log(res); context.commit('toggle', payload) }) .catch((err) => { console.log(err); }) }, }
Update는 클라이언트단에서 기존에 있던 데이터를 서버에 요청하여, 데이터를 변경하는 것이다. todo 앱에서는 toggle이 된다.
Vuex(store.js)의 actions에 메소드 등록할 때, axios의 patch를 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// store.js actions: { deleteTodo: function(context, payload) { // delete를 사용할 때는, 요청할 uri에 제거할 데이터의 uri를 붙여서 보낸다. 예를 들어, /todos/id가 올 수 있다. axios.delete(`http://localhost:4000/todos/${payload}`) // 요청이 성공하면, mutations에 commit을 하여, 특정 데이터가 지워진 상태로 데이터가 변경된다. .then((res => { console.log(res); context.commit('deleteTodo', payload) })) .catch((err) => { console.log(err); }) } }
<script> // mapGetters를 import import { mapGetters } from 'vuex'
// computed에서 mapGetters를 사용 export default { // Vuex 의 Getters 메서드 명과 App.vue 메서드 명이 동일할 때, [] 사용 computed: mapGetters([ 'doneTodos' ]),
// Vuex 의 Mutations 메서드 명과 App.vue 메서드 명을 다르게 매칭할 때, {} 사용 computed: mapGetters({ // 앞 doneTodos 해당 컴포넌트의 메서드를, 뒤 'doneTodos'는 Vuex의 getters를 의미 doneTodos: 'doneTodos' }) } </script>
Mutations란 무엇인가?
Mutations(변이)란, Vuex의 데이터, 즉 state 값을 변경하는 로직들을 의미한다. Vuex 저장소에서 실제로 상태를 변경하는 유일한 방법이다.
methods: { // Vuex 의 Mutations 메서드 명과 App.vue 메서드 명이 동일할 때, [] 사용 ...mapMutations([ 'addTodo' ]),
// Vuex 의 Mutations 메서드 명과 App.vue 메서드 명을 다르게 매칭할 때, {} 사용 ...mapMutations({ addTodo: 'addTodo' // 앞 addTodo는 해당 컴포넌트의 메서드를, 뒤 'addTodo'는 Vuex의 mutations를 의미 }) }
Actions란 무엇인가?
actions란, Vue에서 비동기적으로 처리되야 하는 것들을 관리하기 위한것이다. 즉, 서버와의 http 통신이나 setTimeout() 등과 같이, 결과를 받아올 타이밍이 예측되지 않은 로직을 actions에 선언하고 처리한다.
// App.vue methods: { addTodo() { // 'addTodo'는 actions에 있는 'addTodo', this.inputField는 Vuex(store.js)에 넘겨줄 데이터 this.$store.dispatch('addTodo', this.inputField); } },
mapActions 사용하기
Vuex에 내장된 helper 함수 중, mapActions를 사용하여, 좀 더 간단하게 작성할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// App.vue import {mapActions} from 'vuex';
export default { methods: { // Vuex 의 Actions 메서드 명과 App.vue 메서드 명이 동일할 때, [] 사용 ...mapActions([ 'addTodo', 'getTodo' ])
// Vuex 의 Actions 메서드 명과 App.vue 메서드 명을 다르게 매칭할 때, {} 사용 ...mapActions({ addTodo: 'addTodo' // 앞 addTodo는 해당 컴포넌트의 메서드를, 뒤 'addTodo'는 Vuex의 actions를 의미 }) }, }
<2> setTimeout() 사용2>
Actions 등록하기
먼저 Vuex(store.js)에 actions를 등록해야 한다.
1 2 3 4 5 6 7 8 9 10
// store.js export default new Vuex.Store({
delayTime: function (context, payload) { return setTimeout(function () { commit('addTodo', payload); // mutations에 있는 'addTodo' 호출 }, 5000); }
})
Mutations 등록하기
actions에 등록을 했어도, 상태가 변경된 것을 추적하기 위해서는 mutations의 메소드를 호출(commit) 해야한다. 그리고, mutations에 actions에 등록한 메소드를 작성한다.
Vuex란, 상태관리 패턴에 대한 라이브러리로써, 애플리케이션의 모든 컴포넌트에 대한 중앙집중식 저장소 역할을 하며, 예측 가능한 방식으로 상태를 변경할 수 있다.
Vuex는 왜 사용하는가?
Vuex는 상태관리(State Management)를 위해 사용한다. 여기서 말하는 상태(State)란, 데이터를 말하며, 컴포넌트간에 데이터 통신 및 전달을 효율적으로 관리를 쉽게 하기 위해 Vuex를 사용한다.
Vuex는 중대형 규모의 앱 컴포넌트들을 관리할 때 좋은데, 일반적으로 앱의 규모가 커지면 다음과 같은 문제가 생긴다.
단순 부모-자식간의 데이터 통신이 아닌, 그 중간에 많은 컴포넌트들이 있을 경우, 데이터 통신 방식이 복잡해짐
EventBus를 사용하여 상하위 관계가 아닌 컴포넌트들간의 통신시에 관리가 안됨
이런 문제를 해결하기 위해, Vuex를 사용하여 모든 데이터(State)를 한 곳에서 중앙 집중식으로 관리한다. 즉, Vuex는 공통의 상태를 공유하는 여러 컴포넌트가 있을 경우, 이를 Vuex 한 곳(전역)에서 집중적으로 관리하는 역할을 한다. 그러나, 만약 공통으로 공유하는 것이 아닌, 개별의 로컬 컴포넌트에서 사용하는 경우에는, 꼭 Vuex에 저장할 필요가 없다.
Vuex Architecture
위에서, Vuex는 상태관리 패턴에 대한 라이브러리라고 했다. 즉, 상태관리 패턴이란, 상태를 관리하기 위한 하나의 패턴방식을 말한다. Vuex에서의 패턴방식은 단반향 데이터 흐름을 따른다.
Vue는 컴포넌트 단위로 개발할 수 있다. 컴포넌트(Component)란, html, css, javascript를 1개의 파일 단위로 나눠서 개발하는 것을 말한다. Vue에서는 이를 싱글 파일 컴포넌트라고 하는데, UI 단위로 나눠서 개발하여, 코드의 재사용 측면에서 좋다.
즉, Vue.js는 컴포넌트를 조합해 전체 애플리케이션을 만든다. 이때, 컴포넌트들은 부모-자식 관계를 갖는 트리구조로 작성할 수 있다.
Vue 프로젝트를 생성하게 되면, 다양한 파일이 만들어진다.
index.html
index.html: 어플리케이션 전체의 뼈대가 되는 html 파일이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title>what</title> </head> <body> <noscript> <strong>We're sorry but what doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
main.js
main.js: Vue 인스턴스를 생성해주는 파일이다.
1 2 3 4 5 6 7 8
import Vue from 'vue' import App from './App.vue'
Vue.config.productionTip = false
new Vue({ render: h => h(App), }).$mount('#app')
이 자바스크립트 파일은 new 연산자를 사용해서 Vue 인스턴스를 생성해준다. Vue는 기본적으로 인스턴스를 생성하여 개발을 하는데, 컴포넌트 단위로 개발을 할 경우, main.js에서 new 연산자로 Vue 인스턴스를 생성하게 해주고, 각 컴포넌트에서 각 UI에 맞게 인스턴스를 활용해서 컴포넌트를 개발한다.
render에서 h() 안에 있는 ‘App’은 첫 화면에 뿌려질 Vue 컴포넌트를 의미한다.
1
render: h => h(App)
$mount() 안에 있는 ‘#app’은 id를 ‘app’으로 가지고 있는 html 태그를 의미한다. 이 태그로 마운트한다는 것인데, 마운트한다는 것은 위 App 컴포넌트를 이 html 태그(id를 ‘app’으로 가지고 있는 태그)에 연결시킨다는 의미이다.
1
$mount('#app')
즉, 위 index.html에 있는 <div id="app"></div> 안으로 App 컴포넌트를 연결(넣는다)한다는 뜻이다.
Vue.js에서 모든 Vue 인스턴스는 ViewModel인데, 이 ViewModel은 Model과 View 사이에서 양방향 데이터 바인딩을 해줌으로써, 동기화 역할을 한다. ViewModel은 Vue 생성자 혹은 하위 클래스들에 의해 인스턴스화된다.
1
var vm = new Vue({ /* options */ })
2. View
Vue.js는 DOM 기반의 템플릿을 사용하는데, View의 실제 돔(DOM)은 Vue instance에 의해 관리된다. 각 Vue 인스턴스는 해당 DOM 요소에 연관된다. Vue 인스턴스가 생성되면 필요한 데이터 바인딩을 설정하는 동안 루트 요소의 모든 자식 노드를 반복적으로 탐색한다. 뷰가 컴파일이 되면 데이터 변경에 반응할 수 있게 된다.
즉, 해당 HTML 컴포넌트 범위(해당 DOM 요소가 담긴 템플릿)를 Vue 인스턴스의 vm.$el에 설정한다. 뷰는 Vue 인스턴스가 생성 될 때, 컴파일 되며 뷰모델의 다양한 동작 기능들이 바인딩 된다. 그렇게 됨으로써 뷰의 변경이 감지되면 뷰모델이 이를 감지하여 반응할 수 있게된다. 또한, 뷰의 변경은 집단적으로, 비동기로 실행되기 때문에 높은 성능을 발휘한다.
3. Model
Vue.js에서 모델은 단순히 자바스크립트 객체 혹은 데이터 객체로 표현할 수 있다. 데이터 객체의 프로퍼티를 조작하면 이를 관찰하는 뷰 인스턴스가 변경을 알린다. Vue.js는 ES5 getter/setters로 데이터 객체의 프로퍼티를 변환한다. 그렇기에 뷰를 변경하기 위해 Vue에 명시적으로 신호를 보낼 필요가 없다. 그리고, 각 뷰 인스턴스는 데이터 객체에 있는 모든 프로퍼티들을 프록시한다.
서버: 네트워크를 통해 클라이언트에 정보나 서비스를 제공하는 컴퓨터 또는 프로그램을 말한다.
자바스크립트 런타임: 자바스크립트 프로그램을 컴퓨터에서 실행할 수 있게 하는 환경을 말한다.
npm에서의 노드는 자바스크립트 런타임을 의미한다. 즉, 대부분의 자바스크립트 프로그램은 패키지라는 이름으로 npm에 등록되어 특정 기능을 하는 패키지가 필요하면, npm에서 찾아 설치하면 된다. npm에 업로드된 노드 모듈(자바스크립트 기반으로 만들어진 프로그램 파일)을 패키지라고 부른다. 그래서, npm을 자바스크립트 패키지 매니저라고도 부른다.
모듈이 다른 모듈을 사용할 수 있는 것처럼, 어떤 패키지가 다른 패키지를 사용할 수 있다. 이런 관계를 의존 관계(dependencies)라고 부른다.
이런 패키지 매니저가 npm 말고도, 페이스북에서 만든 yarn도 있다.
npm은 왜 사용하는가?
npm에는 약 60만 개(2018년 6월 기준)의 패키지가 등록되어 있는데, 이는 대부분 오픈소스로 등록되어 있어 웹개발을 할 때 많은 도움이 된다.
또한, 새로운 모듈(패키지)를 설치하거나 관리를 위해, npm은 npm cli를 제공해서 cli 명령어로 설치, 업데이트 및 삭제를 쉽게 할 수 있다.
package.json이란 무엇인가?
package.json이란, 생성한 프로젝트의 메타정보와 이 프로젝트가 의존하고 있는(설치한) 모듈들에 대한 정보들을 json 형태로 모아놓은 파일이다.
만약 이 package.json 파일을 사용하지 않을 경우 다음과 같은 문제가 발생할 수 있다.
프로젝트에서 사용하는 외부 모듈들이 많아지게 되면, 관리하기가 어려워진다.
각 패키지들은 고유한 버전이 있기 때문에, 따로 기록해 두어야 한다. 왜냐하면, 패키지의 버전도 빈번하게 업데이트가 되기 때문이다.
새로운 프로젝트를 진행할 때, 필요한 모듈들이 많다면 매번 npm 명령으로 설치해야 하는 번거로움이 있다.
이런 경우, 필요한 패키지들의 목록을 파일로 정리해놓고, 목록 파일을 이용하여 단 한번의 명령어로 필요한 패키지들을 모두 설치할 수 있다. 이러한 패키지 정의 파일을 package.json 파일이라고 한다. 즉, package.json은 프로젝트에 대한 메타정보, 그리고 설치한 패키지의 의존성 및 버전을 관리하는 파일이다.
vue cli로 Vue 프로젝트를 생성하면, 자동으로 package.json 파일이 만들어 지지만, npm init을 사용하면, package.json 파일을 직접 만들 수도 있다. 그리고, 팀 내에서 동일한 개발환경을 구축하려고 할 때, 이미 작성된 package.json 파일이 있다면, 팀 내에 배포하여 동일한 개발환경을 빠르게 구축할 수 있다.
node_modules란 무엇인가?
Vue 프로젝트를 생성하면, package.json 파일 뿐만 아니라, node_modules 디렉토리가 같이 생성된다. package.json에는 프로젝트가 의존하고 있는 모듈들에 대한 정보가 나와있고, node_modules 디렉토리에는 package.json에 있는 모듈 뿐만 아니라, package.json에 있는 모듈이 의존하고 있는 모듈 전부를 포함하고 있다. 그래서 node_modules 디렉토리안에는 정말 많은 모듈들이 들어가 있다. npm으로 새로운 모듈(패키지)를 설치하게 되면, package.json과 node_modules에 추가된다.
참고로, git에 커밋할 때, node_modules을 제외해도 된다. 왜냐하면, node_modules가 없어도, package.json에 설치한 패키지들이 모두 있기 때문에, npm install로 node_modules를 언제든지 설치가 가능하기 때문이다.
package-lock.json이란 무엇인가?
package-lock.json은 이 package-lock.json이 생성되는 시점의 의존성 트리(node_modules)에 대한 정보를 가지고 있는 파일을 말한다. 의존성 트리는 package.json에 등록된 모듈과 그 모듈들이 의존하고 있는 모듈 전부를 포함하고 있기 때문에, 결국 package-lock.json도 이 모든 모듈들을 가지고 있다. npm을 사용해서 node_modules나 package.json을 수정하게 되면, package-lock.json 또한 자동으로 업데이트가 된다.
package-lock.json은 왜 사용하는가?
package.json 파일에는 의존성 모듈(dependencies)의 version range가 사용된다. version range란, 특정 버전이 아닌, 버전의 범위를 의미한다. 예를 들어, npm install express로 express를 설치하면, package.json에는 ‘^4.10.3’(Caret Ranges)과 같이 버전 범위가 추가된다. 이 버전의 express가 추가된 package.json을 가지고 npm install을 실행하면, 현재는 4.10.3 버전이 설치되지만, express의 버전이 업데이트된 상태로 publish가 된 후에, 동일한 package.json 파일로 npm install을 실행했을 경우, 원래 버전이 아닌, 새로 업데이트된 버전으로 express가 변경된다. 이럴 경우, 기존에 가지고 있던 node_modules(의존성 트리)에 있던 모듈의 버전과 충돌이 일어나, 오류를 발생시킬 수 있다. 이 문제를 해결하기 위해, package-lock.json을 사용하는 것이다.
package-lock.json은 node_modules(의존성 트리)에 대한 정보를 가지고 있는데, package-lock.json이 업데이트가 되는 시점에 node_modules(의존성 트리)을 재생성할 수 있다. 그래서, package-lock.json 파일이 있다면, npm install로 package.json과 package-lock.json에 있는 모듈이 새로 업데이트되는 동시에, node_modules(의존성 트리)가 새로 생성되어, 각 파일이 가지고 있는 모듈의 버전을 동일하게 맞출 수가 있게 된다.
즉, package.json에 있는 모듈의 버전은 npm install을 수행하는 시점에 따라 달라진다. 이 말은, npm install을 수행하는 시점에 publish 되어있는 모듈의 버전으로 업데이트가 된다는 뜻이다. 이렇게 되면, package.json과 package-lock.json에 있는 모듈이 같은 버전으로 업데이트가 되고, 이때 package-lock.json 때문에 node_modules(의존성 트리)가 재생성되어, 3개의 파일에 있는 모듈이 모두 같은 버전으로 맞춰지게 되어 오류가 안나게 된다.
이런 이유로, git에 커밋할 때, package.json 파일 뿐만 아니라, packge-lock.json 파일 또한 같이 커밋을 해야 한다.
패키지 version
노드 패키지들의 버전은 세 자리로 되어있는데, 이는 SemVer 방식의 버전 넘버링을 따르기 때문이다. SemVer는 Semantic Versioning(유의적 버전)의 약어인데, 이는 버전을 구성하는 세 자리가 모두 의미가 있다는 뜻이다.
서비스를 개발하다 보면, 정말 많은 패키지들을 사용하게 되는데, 이런 많은 패키지들이 서로 얽히다 보면 문제가 생길 수 있다. 예를 들어, 어떤 패키지의 버전을 업그레이드 했는데, 그것을 사용하는 다른 패키지에서 에러가 발생한다면 문제가 된다. 따라서 버전 번호를 어떻게 정하고, 올려야 하는지를 명시하는 규칙을 만들었는데, 이것이 바로 SemVer 이다.
버전은 세 자리로 구성되어 있다.
major 버전: 하위 호환이 안될 정도로 패키지의 내용이 수정되었을 때 올린다.
주 버전이 0이면, 초기 개발 중이라는 뜻이다.
1부터는 정식 버전이다.
만약, 1.7.1에서 2.0.0으로 올렸다면, 1.7.1 버전 패키지를 사용하고 있던 사람들이 2.0.0으로 업데이트 했을 때, 에러가 발생할 확률이 크다.
minor 버전: 하위 호환이 되는 업데이트 시에 올린다.
만약, 1.7.1에서 1.8.0으로 올렸다면, 1.7.1 사용자가 1.8.0으로 업데이트 했을 때, 아무 문제가 없어야 한다.
patch 버전: 새로운 기능이 추가되었다기 보다는, 기존 기능에 문제가 있어 수정한 것을 내놓았을 때, patch 버전을 올린다.
1.7.0에서 1.7.1으로 올렸다면, 업데이트 후 문제가 없어야 한다.
새 버전을 배포한 후에는, 그 버전의 내용을 절대 수정하면 안된다. 만약, 수정 사항이 생기면, major버전, minor 버전, patch버전 중 하나를 의미에 맞게 올려서 새로운 버전으로 배포해야 한다.
버전의 숫자마다 의미가 부여되어 있기 때문에, 다른 패키지를 사용할 때도 버전만 보고 에러 발생 여부를 판단할 수 있다.
만약, 의존하는 패키지의 major 버전이 업데이트 되었다면, 기존 코드와 호환이 되지 않을 확률이 크기 때문에, 미리 주의를 기울여야 한다. 만약, minor나 patch 버전으로 업데이트 되었다면, 상대적으로 안심하고 버전을 올리 수 있다.
package.json에는 버전 말고도, 다른 기호(‘^’, ‘~’ 등)들이 있다. 이런 기호들은 버전에는 포함되지 않지만, 설치 또는 업데이트 시 어떤 버전의 범위를 설치해야 하는지 알 수 있다.
^(캐럿): minor 버전까지만 설치 또는 업데이트한다.
예를 들어, npm install express@^1.7.1 이라면, 1.7.1 <= 버전 < 2.0.0까지 설치가 되고, 2.0.0은 설치되지 않는다.
~(틸트): patch 버전까지만 설치 또는 업데이트한다.
npm install express@~1.7.1 이라면, 1.7.1 <= 버전 < 1.8.0까지 설치가 된다.
참고로, npm install 명령어의 패키명 뒤에 @버전을 추가하면 패키지 버전을 지정하여 설치할 수 있다.
nodemon이란?
nodemon은 프로젝트 폴더의 파일들을 모니터링하고 있다가, 파일이 수정될 경우 자동으로 서버를 재시작을 해준다. nodemon을 위해서 소스에 다른 설정을 추가할 필요도 없기 때문에 상당히 편리하게 사용할 수 있습니다.
–save와 –dev
npm install 모듈명 --save 을 입력하면, 설치하는 모듈을 package.json에 등록할 수 있다.(npm@5 부터는 –save는 기본옵션이다.) 그래서, npm@5 부터는 –save 옵션을 사용하지 않더라도, 모든 install 명령은 package.json의 dependencies에 설치되어 관리된다. 그리고, npm install 모듈명 --save --dev을 입력하면, 설치하는 모듈을 package.json에 등록할 수 있을 뿐만 아니라, –dev 때문에, package.json 파일에서 devDepencies에 등록된다. devDepencies에서 관리하는 모듈들은 개발용 모듈들이고, depencies에서 관리하는 모듈들은 배포용 모듈들이다.
package.json에 명시된 모든 의존 패키지를 한번에 설치하기 위해서는, npm install 명령어를 사용하면 된다.
vue-cli 2.x 버전에서는 프로젝트 생성시, 선택가능한 템플릿(simple, webpack 등)이 있었으나, vue-cli 3 버전부터는 기본적인 프로젝트를 먼저 생성하고, 필요한 플러그인을 그때마다 추가하여 자신이 원하는 구조로 프로젝트를 변경할 수 있게 되었다.
플러그인 설치
기본적으로, 프로젝트를 생성할 때 pre-installed 플러그인을 추가하여 설치할 수 있다. 이후, 필요한 플러그인이 있을 때마다 다음과 같은 명령어로 이미 생성된 프로젝트에 플러그인을 추가할 수 있다.
1
vue add @vue/eslint
@vue/eslint 명령어는 @vue/cli-plugin-eslint 명령어의 약어다.
플러그인 중에서 vue-roter와 vuex는 특별한 케이스로 다음과 같이 설치한다.
1 2
vue add router vue add vuex
Vue 프로젝트 파일과 구조
Vue 프로젝트 생성시, 생성되는 파일과 구조는 다음과 같다.
Directory/File
Description
dist
Production용으로 빌드를 하면, 최종 결과물이 나오는 Directory
public
공용으로 접근 가능한 Directory
public/index.html
어플리케이션의 뼈대가 되는 html 파일
public/favicon.io
웹브라우저 주소창에 표시되는 웹사이트를 대표하는 아이콘
src
애플리케이션의 소스코드가 있는 Directory
src/main.js
Vue 인스턴스를 생성하는 javascript 파일
src/App.vue
Vue 애플리케이션의 가장 최상위 컴포넌트
src/assets
이미지 등 static assets이 모여 있는 Directory
src/components
Vue 컴포넌트들이 모여 있는 Directory
src/router.js
Vue Router로 설정한 라우팅 정보
src/views
화면을 전체 구성하는 View와 관련된 파일(라우팅과 관련된 컴포넌트들이 모여 있는 Directory)
test
테스트 관련된 코드가 있는 Directory
babel.config.js
babel을 위한 설정 파일
node_modules
yarn 또는 npm으로 설치한 의존성 모듈들이 있는 Directory
package.json
Vue 프로젝트의 메타 정보와 해당 프로젝트에서 사용하는 패키지(모듈)(의존성관련 정보)들의 정보가 저장되는 파일
package-lock.json
node_modules(의존성 트리) Directory에 대한 모든 정보를 가지고 있는 파일