Vue SRR 잘라먹기 - 소스 코드 구조

Stateful Singletons 피하기


우리가 클라이언트 코드를 쓸 때, 우리는 우리의 코드가 매번 새로운 context로 evalutated될 것이라 기대한다. 그러나 node.js 서버는 긴- 작동의- 프로세스이다. 우리의 코드가 프로세스에서 요구도면, 그것은 메모리에 남아 evaluated 된다. 이는 네가 singleton object를 만들면, 모든 request에 사이에서 이 object가 공유될 것이란 거다.
우리는 새로운 루트 Vue 인스턴스를 각각의 요청에 대해 만들어야 한다. 이건 각각의 브라우저에서 유저가 새로운 인스턴스를 사용하는 것과 비슷하다. 우리가 다중의 request에서 교차해서 인스턴스를 공유하면 cross-request state pollution을 피할 수 없다.
그렇기에, 직접적으로 앱 인스턴스를 만드는 것 대신, 우리는 각 request를 위한 신선한 앱 인스턴스를 만드는 반복적인 작업을 하는 factory function을 노출 시켜야 한다.
// app.js
const Vue = require('vue')

module.exports = function createApp (context) {
  return new Vue({
    data: {
      url: context.url
    },
    template: `<div>The visited URL is: {{ url }}</div>`
  })
}
그리고 우리의 서버 코드는 이렇게 바뀐다:
// server.js
const createApp = require('./app')

server.get('*', (req, res) => {
  const context = { url: req.url }
  const app = createApp(context)

  renderer.renderToString(app, (err, html) => {
    // handle error...
    res.end(html)
  })
})
같은 법칙이 라우터, 스토어 그리고 이번트 버스 인스턴스에 적용된다. 모듈에서 부터 직접적으로 노출하거나 당신의 앱을 건너서 가져오는 것보다, 새로운 인스턴스를 createApp안에서 만들고 root Vue instance에서 주입해야 한다?!
이 제약 조건은 번들 렌더러를 {runInNewContext : true}와 함께 사용할 때 제거 할 수 있지만 각 요청에 대해 새 VM 컨텍스트를 만들어야하기 때문에 상당한 성능 비용이 발생합니다. )







빌딩 스텝 소개하기


아직 우리는 같은 vue app을 클라이언트로 전달할지 이야기 하지 않았다. 이를 위해, 우리는 웹팩을 앱을 번들링하기 위해 사용한다. 사실은, 우리는 서버위의 vue app을 번들링 하는데 웹팩을 사용하길 원한다. 왜냐면:
*  일반적인 Vue 앱은 webpack 및 vue-loader로 제작되며 file-loader를 통해 파일을 가져 오는 것과 css-loader를 통해 CSS를 가져 오는 것과 같은 많은 webpack 관련 기능은 Node.js에서 직접 작동하지 않습니다.
최신 Node.js 버전은 ES2015 기능을 완벽하게 지원하지만 이전 버전의 브라우저를 수용하기 위해 클라이언트 측 코드를 필요로합니다. 이것은 다시 빌드 단계를 포함합니다.
따라서 우리는 webpack을 사용하여 클라이언트와 서버 모두에 대해 응용 프로그램을 번들로 제공 할 예정입니다. 서버 번들은 서버에서 필요하며 SSR에 사용되며 클라이언트 번들은 정적 마크 업을 hydration하기 위해 브라우저로 전송됩니다.
이후 섹션에서 설정의 세부 사항을 논의 할 것입니다 - 이제는 빌드 설정을 알아 냈다고 가정하고 webpack을 활성화하여 Vue 앱 코드를 작성할 수 있습니다.






웹팩과 코드 구조


이제는 webpack을 사용하여 서버와 클라이언트 모두에서 응용 프로그램을 처리하므로 대부분의 소스 코드는 모든 웹팩 기반 기능에 액세스 할 수있는 보편적 인 방식으로 작성 될 수 있습니다. 동시에 범용 코드를 작성할 때 명심해야 할 몇 가지 사항이 있습니다.
간단한 프로젝트 구조는 다음과 같습니다:
src
├── components
│   ├── Foo.vue
│   ├── Bar.vue
│   └── Baz.vue
├── App.vue
├── app.js # universal entry
├── entry-client.js # runs in browser only
└── entry-server.js # runs on server only





app.js

app.js는 우리 앱의 universal entry이다. 클라이언트 only app이라면, 우리는 root vue instatnce를 파일에서 바로 만들고 DOM에 바로 마운트 할 것이다. 그러나 SSR의 경우 책임은 클라이언트 only app 파일로 이동됩니다. app.js는 단순히 createApp 함수를 내보냅니다.
import Vue from 'vue'
import App from './App.vue'

// export a factory function for creating fresh app, router and store
// instances
export function createApp () {
  const app = new Vue({
    // the root instance simply renders the App component.
    render: h => h(App)
  })
  return { app }
}






entry-client.js

클라이언트 entry는 간단히 app을 만들고 DOM에 마운트한다.
import { createApp } from './app'

// client-specific bootstrapping logic...

const { app } = createApp()

// this assumes App.vue template root element has `id="app"`
app.$mount('#app')






entry-server.js

서버 entry는 각 렌더에 반복적으로 불릴 수 있는 함수를  default export로 사용한다.  AT THIS MOMENT, 함수는 앱 인스턴스를 생성하고 출력만 한다.  그러나 나중에 우리는 이 부분에서 서버-사이드 루트(라우트) 매칭과 데이터 pre-fetching logic의 perform할 것 이다.
import { createApp } from './app'

export default context => {
  const { app } = createApp()
  return app
}

댓글

이 블로그의 인기 게시물

서버에 파일 저장하기 - blob

후지필름 XC 50-230mm f4.5-6.7 OIS II

Nuxt를 사용해야하는 10가지 이유 - 번역