환경 변수 란 ?

어떠한 프로세스가 실행될 때 영향을 미치는 동적인 값

OS 단에 선언되어있는 배포 환경에 따라 값을 동적으로 변경하기 위한 변수

내가 하고 싶었던 건?

환경변수인 NODE_ENV 값을 local, development, production으로 각각 나누어 의도한 바에 따라 .env파일을 분기하여 사용하고 싶었다

시행착오

  1. process.env 환경변수를 vue.파일의 script 태그 안에서 콘솔로 로그를 찍으니 계속 undefined를 얻었다.. → client side에서는 process.env를 사용 할 수 없다는 사실을 모르고 있었다. 알고보니 process.env는 server side에서만 사용이 가능했고, clientside에서도 활용이 가능하게 하기 위해 nuxt에서는 nuxt.config.ts 파일 안에 config 설정을 따로 두고 있었다.
  2. cmd 에서 환경변수를 set하는 방법을 몰라서 한참을 헤맸다. package.json 의 scripts 부분 안에 있는 명령어들을 지정할 때이런 방식으로 환경변수를 셋팅 하니 잘 동작했다.
  3. SET 환경변수명=값 & nuxt build
  4. SET NODE_ENV를 변경해도 저절로 .env 파일을 찾아가지 못했다. 예전 Vue CLI를 이용해서 프로젝트를 생성했을때는 뒤에 --mode local 이런 방식으로 지정하면 .env.local 파일의 변수값을 바라보게 자동으로 셋팅이 되었는데, Nuxt3는 그렇게 되지 않았고const phase = process.env.PHASE!!주의할 점!!따라서 nuxt.config.ts 안에서 process.env값을 runtimeConfig로 정의하여 컴포넌트 안에서 사용해야한다.
  5. nuxt에서는 process.env 값을 component안에서 console로 찍어 확인하면 nuxt.config.ts 안에서의 process.env 값과 다르다.
  6. require(’dotenv’).config({path:./.env${phase}})
  7. 해결방법으로 스크립트에 PHASE라는 환경변수값을 셋팅하고, 그값을 찾아와 nuxt.config.ts 파일 에서 dotenv의 파일 path값을 동적으로 바꿔주었다. ex) SET PHASE=local&&nuxi dev

현재 상황

현재 vue.js를 이용한 SPA을 만들어 배포하고 있는데, Code Splitting을 적용한 이후로 새로 앱을 배포할 때

ChunkLoadError라는 에러를 만나게 되었다..

배포를 해야 에러를 테스트 할 수 있기 때문에 테스트도 굉장히 힘들었는데

이 에러를 해결한 과정을 정리하여 써보려고 한다.

 

 

Code Splitting이란?

webpack을 이용해 번들링을 하면, 초기 로딩시 번들링을 통해 합쳐진 js파일을 로딩하기 때문에 앱이 점점 커지면서 초기로딩이 길어지는 문제가 생긴다.

이를 해결하기 위한 것이 바로 Code Splitting, Lazy Loading이다.

 

문제점?

사용자가 앱을 사용하는 도중에 웹앱 재배포가 일어날 때, index.html은 새로 배포된 파일이 아닌 이전 파일의 정보를 담고 있기 때문에 배포 이전 파일들을 요청하게 되어 이 파일들을 찾지 못하는 상황이 일어난다. (파일은 새롭게 배포되어있는 상태이기 때문에 이전 파일들은 찾아도 찾을 수 없게 된다)

 

디버깅 테스트 과정

우리 프로젝트는 vue2+ vue router + vue CLI + webpack 4 로 구성되어 있는 상태였다.

테스트 과정에서 새롭게 안 사실은 개발모드로 빌드를 한 파일들은 파일 끝에 해시값이 붙지 않아서 위와 같은 오류가 애초에 발생하지 않는 다는 사실이다. 그래서 개발환경에서 앱을 재배포하는 과정에서는 발견되지 않은 에러가 운영 환경에서는 발견 되었던 것이다.

 

이와 같은 사실은, 개발환경에서 아이폰에서 웹앱 개발시 앱을 껐다가 켜도 계속해서 변경사항이 반영되지 않는 현상도 설명이 가능했다. 개발버전으로 빌드 시 청크파일에 해시값이 애초에 붙지 않기 때문에 이전에 배포된 파일의 이름과 새롭게 빌드되어 배포된 파일의 이름이 같아 새롭게 index.html을 배포해도 브라우저에 캐시된 새로 배포되기 이전 파일을 가져오기 때문이었다. 

 

production 모드로 빌드를 진행하면 각 chunk파일 이름 뒤에 랜덤의 해시 값이 붙게 되는데, 해시값 설정은 webpack의 config 파일에 여러가지 변경이 가능하다.

 

우리는 vue CLI를 이용하고 있었기 때문에 vue.config.js 파일의 웹팩 설정 밑에

 output: {
            filename: `[name].[chunkhash].js`,
            chunkFilename: `[name].[chunkhash].js`,
        },
 
을 추가하여 해시값을 개발모드에서도 붙여주었다.
 
참고로 해시값은 변경된 파일에 한해서만 변경이 되고, 만약 그 파일에 변경사항이 없다면 해시값을 유지함으로써 캐싱된 화면에 대해서 새롭게 로딩을 할 필요성을 줄여준다.
 
테스트는 
 
1.앱을 실행한다.

2. 특정 화면에 해당하는 파일에 변경사항을 추가한다.

3. 앱을 재 배포한다.

4. 변경사항을 추가했던 화면에 접근한다.
 
순으로 진행 되었고, 드디어 개발 모드에서 ChunkError를 만나게 될 수 있었다
 

해결해야 하는 문제점

1. 당시 우리 서비스는 쇼핑몰 아이템을 클릭 후 뒤로가기 시 다시 서버에 요청을 보내 데이터를 받아와 화면을 갱신해야하는 요구사항이 있었기 때문에 앞서 소개했던 keep-alive라던가, web storage에 모든 데이터를 넣는 방법이라던가, scrollBehavior을 통해 스크롤 위치를 return하는 방법을 그대로 사용하기 어려웠다.

 

2. 또, 알 수 없이 페이지를 렌더링 할 때 스크롤이 맨 아래로 향해있는 이슈가 있어 window.scrollTo(0,0) 코드를 넣어 가장 상위로 스크롤을 강제로 위치시키고 있었다.

 

어떻게 해결했는가?

1. Vue Router meta 정보에 savePosition, preservePosition 이라는 속성(boolean)을 할당하여, 페이지가 넘어갈 시 스크롤 위치를 저장하는 페이지와 다시 돌아올 때 저장했던 스크롤 위치를 불러오는 페이지를 설정할 수 있게 했다.

 

2. scrollBehavior에서 뒤로가기를 할 시, 이전 scroll위치를 받아와 sessionStorage에 저장하였고, 뒤로가기가 아닐시에는 (0,0)을 반환하여 처음 위치로 가게 하였다.

 

3. 라이프사이클 updated 훅에서 nextTick을 이용하여 모든 화면(자식 컴포넌트 포함)이 렌더링 된 후 window.scrollTo를 이용하여 sessionStorage에서 저장한 이전 scroll위치로 스크롤을 위치시켰다.

*자식 컴포넌트를 포함한 모든 화면이 update가 된 이후에 메서드를 호출하려면 mounted가 아닌 updated의 nextTick 안에서 해야한다*

 

4. 알 수 없이 페이지의 스크롤이 맨 아래로 향해있었던 이슈는 DOM에서 computed로 계산된 유저의 이름을 바로 사용하고 있었는데, 그 부분을 data의 변수에 할당하고 computed가 아닌 data안의 변수를 DOM에서 사용하는 방식으로 변경하니 이슈가 해결되었다. 그런데 정확한 원인은 아직 제대로 파악하지 못하였다..

 

 

쇼핑몰 프로젝트를 진행하면서, 뒤로가기 시 스크롤 유지 관련한 이슈 해결 히스토리를 기록해보고자 이 글을 작성하게 되었다. 현재 프로젝트에서는 Vue + Vue Router + Vuex 를 사용한다.

 

 

개요

현재 진행하던 프로젝트에서는 기존에 sessionStorage에 스크롤 위치를 저장하고, updated 훅에서 모든 화면 update가 끝난 후 sessionStorage에 있는 scroll값을 가져와 scroll을 이동시키는 스크롤을 컨트롤 하고 있었다. 이과정에서 서버 통신 후 데이터를 렌더링하는 시점과, 스크롤을 이동시키는 시점에 오류가 생겨 스크롤을 잘 잡지 못하는경우가 발생하여 로직을 수정하게 되었다.

 

고려해본 방법들 

1. keep-alive 적용

2. web storage 사용

3. router savedPosition 사용

 

Keep-alive 적용

keep-alive는 컴포넌트를 전환할 때, 성능상의 이유로 상태를 유지하거나, 재 렌더링을 피할 수 있게 해주는 element입니다. 재 렌더링을 피할 수 있기 때문에, 뒤로가기 시 별다른 장치 없이 그대로 화면을 유지할 수 있는 장점이 있다. 

<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>

 

사용은 이런식으로 원하는 컴포넌트를 <keep-alive> 태그로 감싸주면 된다.

여기서 유의할점은 화면 전체의 재 렌더링을 피하고 싶다면 router-view 전체를 keep-alive 태그로 감싸야 한다는 것이다. (이것때문에 여러번 실패를 했었네요..) 

뒤로가기를 누를때만 적용 되야 하기 때문에 동적으로 할당 해야한다. 또한 재렌더링이 안되기 때문에 신중히 사용해야한다.

 

Web Storage 사용

화면 데이터와 스크롤 위치를 localStorage나 sessionStorage에 담아놓고, 돌아올 때 그대로 다시 불러오는 방법. 새롭게 서버통신을 해서 다시 리스트를 불러오지 않기 때문에 infinity scroll 사용 시 스크롤 위치를 잡기가 용이하다. 또 라이프사이클 훅에서 이루어지는 모든 것들이 정상 동작하므로 keep-alive를 사용할 수 없는 경우(라이프사이클을 거쳐야 하는 경우) 사용하기 좋다.

 

router savedPosition 사용

vue router는 scroll을 컨트롤 하기 위한 scrollBehavior 라는 함수를 제공하고 있다. 이는 HTML 히스토리 모드에서만 작동하는데, 라우터 객체를 만들때

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // 원하는 위치로 돌아가기
  }
})

이 안에 함수를 넣어서 사용하면 된다.

 

함수 안에서 리턴 값에 따라 스크롤의 위치가 변경되는데, 세번째 전달인자 (여기서는 savedPosition)은 브라우저가 앞/뒤 버튼으로 트리거 될때만 사용 가능하다

scrollBehavior (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}

이런식으로 사용하면 되는데, 무조건 맨 위로 올리고 싶을 때는 

scrollBehavior (to, from, savedPosition) {
  return { x: 0, y: 0 }
}

이렇게 항상 0을 리턴하면 된다.

 

또 부드럽게 스크롤을 하도록 동작을 컨트롤 할 수도 있는데,

scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash,
      behavior: 'smooth',
    }
  }
}

 이렇게 behavior에 스크롤 옵션을 입력 하면 된다. (selector는 위에서 설명했던 스크롤 위치이다)

 

 

이 이슈를 어떻게 해결하게 되었는지 전체적인 플로우는 다음장에서 설명하겠다.

SPA앱 프로젝트를 진행하면서 앱의 기능이 많아져 bundle의 크기가 점점 증가해 초기로딩이 오래걸리는 문제가 생겼고, 이를 해결 하기위해 code splitting을 적용했습니다.

 

현재 제가 하고있는 프로젝트는 vue2 + CLI 3 입니다.

 

개봘 환경 조건은 

1. 싱글 파일 컴포넌트 체계(.vue)

2. 웹팩 - 모듈 번들러 (2.x이상)

3.바벨 Syntax-dynamic-import입니다

 

CLI로 생성한 경우 1,2번은 만족하고 있지만 3번은 별도 설치가 필요합니다.

 

1. npm으로 설치해 줍니다.

npm install --save-dev babel-plugin-syntax-dynamic-import

2. .babelrc, 혹은 babel.config 파일에 플러그인을 추가합니다.

{
  "plugins": ["syntax-dynamic-import"]
}
3. CLI3를 사용하고 계신다면 webpack의 prefetch preload 플러그인을 삭제해야합니다. 그렇지 않으면 분리된 chunk파일들을 미리 받아오기 때문입니다.
저는 vue.config.js를 사용중이며
  chainWebpack: (config) => {
        config.plugins.delete("prefetch");
        config.plugins.delete("preload");
    },

를 추가하여 prefetch 와 preload 기능을 껐습니다.

 

4. 라우터 파일에 들어가 코드를 수정합니다.

 

기본으로 import 했던 아래의 방식과는 다르게

import MainMenu from "@/views/main/Main.vue";
동적으로 import 하는 아래의 방식으로 변경합니다!
{
	path: 'url 이름',
	component: () => import('컴포넌트 이름')
}
 
여기서 chunk파일들을 load하는것을 조절하고 싶다면 아래처럼 webpackChunkName에 공통적으로 같은 이름을 부여하면 이 이름을 가진 chunk가 최초로 로드되는 순간 같은 이름을 설정한 chunk들이 함께 로드되게 됩니다.
또한 chunk파일 이름이 여기서 설정한 이름으로 만들어집니다. 

const MainMenu = () => import(/* webpackChunkName: "MainMenu" */ "@/views/main/Main.vue");

 

최근 프로젝트가 점점 커지면서 초기 로딩시간이 너무 지연되어 번들 크기를 최적화 하려는 노력을 몇가지 해보았다.

그중 하나는 webpack-bundle-analyzer 사용하여 번들의 크기를 크게 만드는 원인을 파악하여 사이즈를 줄이는 것이다.

 

현재 사용하는 프로젝트는 vue버전 2와 vue cli 버전3을 이용하고 있다. 패키지매니저는 npm 을 사용한다.

 

일단 npm을 이용하여 webpack-bundle-analyzer을 설치한다. 이때 dev모드로 설치 함을 잊지말자

 

 

npm installl --save-webpack-bundle-analyzer

 

이후 webpack.config.js 파일에 들어가서

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

이렇게 입력 해 주면된다.

 

나는 vue.config.js 를 사용하기 때문에 vue.config.js 밑에

 configureWebpack: {
        plugins: [new BundleAnalyzerPlugin()],
    },

를 추가하였다.

 

이후 npm 으로 run을 시키면 다음과 같이 현재 번들이 어떻게 이루어져 있는지 시각적으로 볼 수 있다.

 

+ Recent posts