Vueサーバー側のレンダリングとVueブラウザー側のレンダリング(例PK)のパフォーマンス比較

1 Star2 Stars3 Stars4 Stars5 Stars (まだ評価されていません)
Loading...

Vue 2.0はサーバー側のレンダリングをサポートし始めたので、この記事もvue 2.0以上をベースにしています。 Vueの著者Yu Yuxiのvue-hacker-newsは、サーバーサイドレンダリングのためのインターネットに関する情報はまだほとんどありません。 私が会社でVueプロジェクトをやっていたとき、私はその製品に苦しんでいました。最初の画面読み込みのリクエストとSEOの魅力がありました。私は多くの解決策を考えましたが、今度はサーバーサイドのレンダリングを使ってブラウザをレンダリングし、 2つの同一のデモを比較して、Vueの前面と背面のレンダリングをより直感的に比較します。

トークは安く、私たちにコードを見せてください!あまり言わないで、2つのデモを別々に見てみましょう:(ウェルカムスターのウェルカムプルリクエスト)

1.ブラウザレンダリングデモ: https : //github.com/monkeyWangs/doubanMovie

2.サーバーはデモをレンダリングします: https : //github.com/monkeyWangs/doubanMovie-SSR

2つのコードセットの結果は、すべてDoubanムービーを表示するためのものであり、実行効果は似ています。プロジェクトのメカニズムを簡単に説明しましょう。

まず、Doubanムービーをレンダリングするブラウザ側

まず、公式サイトの足場を使ってvueプロジェクトを構築しました。


npm install -g vue-cli
vue init webpack doubanMovie
cd doubanMovie
npm install
npm run dev

次は、vue-router、vuexを設定し、Douban APIへのプロキシアクセスをサポートするようにwebpack proxyTableを設定する必要があります。

1. Vue-routerを設定する

3つのナビゲーションページが必要です:リリースされる、すぐに来る、Top250、詳細ページ、検索ページ。 ここではそれぞれのルートを設定しました。 router / index.jsの下に以下の情報を設定します。


import Vue from 'vue'
import Router from 'vue-router'
import Moving from '@/components/moving'
import Upcoming from '@/components/upcoming'
import Top250 from '@/components/top250'
import MoviesDetail from '@/components/common/moviesDetail'
import Search from '@/components/searchList'
Vue.use(Router)
/**
* 路由信息配置
*/
export default new Router({
routes: [
{
path: '/',
name: 'Moving',
component: Moving
},
{
path: '/upcoming',
name: 'upcoming',
component: Upcoming
},
{
path: '/top250',
name: 'Top250',
component: Top250
},
{
path: '/search',
name: 'Search',
component: Search
},
{
path: '/moviesDetail',
name: 'moviesDetail',
component: MoviesDetail
}
]
})

このようにして、ルーティング情報が設定され、ルートを切り替えるたびにリクエストデータが繰り返されないようにするため、app.vueコンポーネントでコンポーネントのキープアライブを設定する必要があります。


<keep-alive exclude="moviesDetail">
<router-view></router-view>
</keep-alive>

このような基本的な仮想ルータが構成されています。

2.vuexの紹介

Vuexは、Vue.jsアプリケーション専用に開発された状態管理モデルです。 これは、集中ストレージを使用して、アプリケーションのすべてのコンポーネントの状態と対応するルールを管理して、状態が予測可能な方法で変化することを保証します。 Vuexはまた、Vueの公式のデバッグツールdevtools拡張にも統合されています。これは、ゼロコンフィギュレーションタイムトラベルデバッグ、状態スナップショットのインポートとエクスポートなどの高度なデバッグ機能を提供します。

要するに、Vuexはある意味では読み取りと書き込みの権限を持つグローバル変数に相当します。データを「グローバル変数」に保存し、特定の方法でデータを読み書きします。

Vuexはコード構造を制限しません。 しかし、それは従う必要があるいくつかのルールを設定します:

アプリケーション階層の状態は、単一のストアオブジェクトに集中させる必要があります。

突然変異の送信は状態を変更する唯一の方法であり、プロセスは同期的です。

非同期ロジックをアクションにカプセル化する必要があります。

大規模なアプリケーションでは、Vuex関連のコードをモジュールに分割したいと考えています。 プロジェクト構造の例を次に示します。


├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js  # 我们组装模块并导出 store 的地方
└── moving  # 电影模块
├── index.js # 模块内组装,并导出模块的地方
├── actions.js # 模块基本 action
├── getters.js # 模块级别 getters
├── mutations.js # 模块级别 mutations
└── types.js # 模块级别 types

私たちは、srcディレクトリにstoreという名前の新しいフォルダを作成しました。後で検討するため、ムービーを整理するための新しい移動フォルダを作成しました。すべてのアクション、ゲッター、および突然変異を考慮すると、ファイルはあまりにも面倒です。だから私はそれらを別々に与えた。

ストロークフォルダが構築されているので、main.jsのvuexインスタンスを参照する必要があります:


import store from './store'
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})

このようにして、すべてのサブコンポーネントに$ storeを格納してvuexを使用できます。

3.webpack proxyTableプロキシクロスドメイン

Webpack開発環境では、proxyTableを使用してクロスドメインをプロキシすることができます。プロダクション環境が利用できる場合は、それぞれのサーバーに応じてプロキシクロスドメインを設定できます。 プロジェクトのconfig / index.jsファイルには、proxyTableプロパティがあることがわかります。単にそれを書き直すだけです


proxyTable: {
'/api': {
target: 'http://api.douban.com/v2',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}

だから我々が訪れるとき

ローカルホスト:8080 / api / movie

実際、私たちは訪問しています

http://api.douban.com/v2/movie

これにより、クロスドメイン要求のシナリオが達成されます。

この時点で、ブラウザの主な構成が導入されました。実行結果を見てみましょう:

ブラウザのレンダリングの仕組みを紹介するために、npmの実行ビルドを実行して、リリース版のファイルが何であるか、それが何であるかを見てみましょう…

実行ビルドにdistディレクトリがあると、内部にindex.htmlがあることがわかります。これは最終ページを表示するhtmlです。次のページが表示されます。

観察された良き友人は、余分なdom要素を持たないことが分かります。divが1つしかなく、ページをどのようにレンダリングするのですか? 答えはjs appendです、はい、次のjsはinnerHTMLを担当します。 jsはブラウザによって解釈されるので、私たちはブラウザレンダリングと呼びます。これにはいくつかの致命的な欠点があります。

Jsはdomの最後に置かれます.jsファイルが大きすぎると、必然的にページがブロックされます。 ユーザーエクスペリエンスは明らかに良くありません(これは、会社の製品によって繰り返し尋ねられるものです)

SEOには役に立たない

クライアントは古いJavaScriptエンジンで動作します

世界の何人かの人々にとって、コンピュータは、1998年に作られたコンピュータがインターネットによってアクセスされる方法でのみ使用されることがあります。 VueはIE9以上のブラウザでしか実行できませんが、古いブラウザやLynxをコマンドラインで使っている流行のハッカーには、基本的なコンテンツを提供することもできます。

上記の問題のいくつかに基づいて、サーバーレンダリングが出ています…

第二に、Doubanムービーをレンダリングするサーバー側

Vueの公式ウェブサイトのレンダリングレンダリングを見てください

図から分かるように、ssrには、アプリケーションコードを含むclient.jsとserver.jsの2つのエントリファイルがあります。webpackは、サーバ用のサーババンドルとクライアント用のクライアントに2つのエントリファイルでパッケージ化されています。クライアントバンドル:サーバがクライアントからリクエストを受信すると、レンダラbundleRendererが作成され、bundleRendererが生成されたサーババンドルファイルを読み込み、コードを実行し、生成されたhtmlをブラウザに送信し、クライアントがクライアントバンドルをロードした後、サーバーによって生成されたDOMでハイドレーションを実行します(DOMが生成しようとしているDOMと同じかどうかを判断します) 。

特定の実装:

私たちはvuexが必要です、私たちはルータが必要です、我々はサーバーが必要です、我々はサービスキャッシュが必要です、私たちはプロキシのクロスドメインを必要とします。

1. nodejsサービスを確立する

最初にサーバーが必要ですが、nodejsの場合はexpressが良い選択です。 server.jsを構築しましょう


const port = process.env.PORT || 8080
app.listen(port, () => {
console.log(`server started at localhost:${port}`)
})

これは、サービスリスニングポート8080を開始するために使用されます。

次に、すべてのgetリクエストを処理し始めます。ページをリクエストすると、ページをレンダリングする必要があります


app.get('*', (req, res) => {
if (!renderer) {
return res.end('waiting for compilation... refresh in a moment.')
}
const s = Date.now()
res.setHeader("Content-Type", "text/html")
res.setHeader("Server", serverInfo)
const errorHandler = err => {
if (err && err.code === 404) {
res.status(404).end('404 | Page Not Found')
} else {
// Render Error Page or Redirect
res.status(500).end('500 | Internal Server Error')
console.error(`error during render : ${req.url}`)
console.error(err)
}
}
renderer.renderToStream({ url: req.url })
.on('error', errorHandler)
.on('end', () => console.log(`whole request: ${Date.now() - s}ms`))
.pipe(res)
})

次に、我々はクロスドメインすることができるように、我々はHTTPプロキシミドルウェアモジュールを紹介するために、我々はプロキシ要求が必要です:


const proxy = require('http-proxy-middleware');//引入代理中间件
/**
* proxy middleware options
* 代理跨域配置
* @type {{target: string, changeOrigin: boolean, pathRewrite: {^/api: string}}}
*/
var options = {
target: 'http://api.douban.com/v2', // target host
changeOrigin: true,  // needed for virtual hosted sites
pathRewrite: {
'^/api': ''
}
};
var exampleProxy = proxy(options);
app.use('/api', exampleProxy);

このようにして、私たちのサーバーserver.jsが構成されます。 次に、サーバー側のエントリファイルとクライアント側のエントリファイルを構成し、最初にクライアントファイルを構成し、新しいsrc / entry-client.jsを作成する必要があります


import 'es6-promise/auto'
import { app, store, router } from './app'
// prime the store with server-initialized state.
// the state is determined during SSR and inlined in the page markup.
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
/**
* 异步组件
*/
router.onReady(() => {
// 开始挂载到dom上
app.$mount('#app')
})
// service worker
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
}

クライアントエントリーファイルは非常にシンプルで、サーバーから送信されたデータを同期させてから、サーバーによってレンダリングされたDOMにvueインスタンスをマウントします。

次に、サーバー側エントリファイルを設定します。src / entry-server.js


import { app, router, store } from './app'
const isDev = process.env.NODE_ENV !== 'production'
// This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
export default context => {
const s = isDev && Date.now()
return new Promise((resolve, reject) => {
// set router's location
router.push(context.url)
// wait until router has resolved possible async hooks
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
// no matched routes
if (!matchedComponents.length) {
reject({ code: 404 })
}
// Call preFetch hooks on components matched by the route.
// A preFetch hook dispatches a store action and returns a Promise,
// which is resolved when the action is complete and store state has been
// updated.
Promise.all(matchedComponents.map(component => {
return component.preFetch && component.preFetch(store)
})).then(() => {
isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
// After all preFetch hooks are resolved, our store is now
// filled with the state needed to render the app.
// Expose the state on the render context, and let the request handler
// inline the state in the HTML response. This allows the client-side
// store to pick-up the server-side state without having to duplicate
// the initial data fetching on the client.
context.state = store.state
resolve(app)
}).catch(reject)
})
})
}

Server.jsは、サーバーから渡されたコンテキストのパラメータを受け取り、vueインスタンスをpromise経由で返す関数を返します。 まず最初に、vue-routerのrouter.push(url)を呼び出して対応するルートに切り替え、getMatchedComponentsメソッドを呼び出して、対応するコンポーネントを返すようにします。これによりコンポーネントにfetchServerDataメソッドがあるかどうかがチェックされます。実行されます。

次のコード行は、サーバーによって取得されたデータをコンテキストオブジェクトにマウントし、データをブラウザに直接送信して、クライアントのvueインスタンスとデータ(状態)を同期させます。


context.state = store.state

次に、クライアントとサーバーのwebpackを別々に設定します。ここで私のgithub forkの設定を参照できます。ここにコメントはありません。

次に、app.jsを作成する必要があります。


import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
import { sync } from 'vuex-router-sync'
import Element from 'element-ui'
Vue.use(Element)
// sync the router with the vuex store.
// this registers `store.state.route`
sync(store, router)
/**
* 创建vue实例
* 在这里注入 router store 到所有的子组件
* 这样就可以在任何地方使用 `this.$router` and `this.$store`
* @type {Vue$2}
*/
const app = new Vue({
router,
store,
render: h => h(App)
})
/**
* 导出 router and store.
* 在这里不需要挂载到app上。这里和浏览器渲染不一样
*/
export { app, router, store }

したがって、サーバー側のエントリファイルとクライアント側のエントリファイルには、以前に書いたvueインスタンスとあまり変わらないpublicインスタンスVueがありますが、このインスタンスもサーバー側にあるため、ここではDOMにはマウントしません。実行するには、ここに直接アプリを公開してください。

次に、ルートルーターを作成し、クライアントと同様のvuexを作成します。 詳細は、私のプロジェクトを参照してください…

この時点で、サーバーレンダリングの設定は簡単な紹介です。単にプロジェクトを見てみましょう:

ここではサーバーインターフェイスと同じですが、違いはURLが以前の#/ではなく、リクエストフォーム/

この方法では、ブラウザがページの要求を送信するたびに、サーバはdom文字列をレンダリングし、ブラウザセクションで直接返すので、ブラウザ側のレンダリングで多くの問題が発生しません。

実際には、SPA(Single Page Application)が出現するずっと前から、Webページはサーバー側でレンダリングされています。 クライアント要求を受信した後、サーバはデータとテンプレートをクライアントへの完全なページ応答につなぎます。 クライアントが直接レンダリングするこの時点で、ユーザーは新しいページを参照する必要があり、プロセスを繰り返してページをリフレッシュする必要があります。このような経験はWeb技術の開発ではほとんど受け入れられないので、ますます多くの技術的解決策が出てきます。優れたインタラクティブなエクスペリエンスを実現するため、ページをリフレッシュせずにリフレッシュすることもできます。 しかし、SEOは致命的なものなので、すべてがアプリケーションのシナリオに依存します。ここでは、すべての人に技術的なアイデアを提供し、さまざまな可能なvue開発ソリューションを提供します。

2つのレンダリングの結果をより明確に比較するために、私は実験を行い、2つのプロジェクトをビルドした後にプロダクション環境をシミュレートし、ブラウザのネットワークで3gのネットワーク速度をシミュレートし、まずサーバーレンダリングの結果を確認します。

総負荷DOMは合計832msだったことがわかりました;ネットワークが遅い場合、ユーザーは遠くからWebサイトにアクセスすることができます。または、不十分な帯域幅を比較します。 このような場合は、ページ要求の数を最小限に抑えて、できるだけ早くユーザーに基本コンテンツを確実に表示させるようにしてください。

次に、vendor.jsの1つが563KBに達し、全体のロード時間が8.19秒に達したことがわかります。これは、単一ページのファイルであるため、すべてのロジックコードがjsにパッケージされます。 分割Webpack分割コードを使用して、ユーザーに強制的にシングルページアプリケーション全体をダウンロードさせないようにすることができますが、プレレンダリングされたHTMLファイルを1つだけダウンロードすることは、

上記のVueサーバーのレンダリングとVueブラウザのレンダリングパフォーマンス(例PK)の小さなバージョンです、私は誰も助けることを願っています、あなたが質問があれば、メッセージを残してください、暁は時間内に返信しますみんな。 Script Houseのウェブサイトへのご支援をありがとうございます!


1 Star2 Stars3 Stars4 Stars5 Stars (まだ評価されていません)
Loading...
      この投稿は審査処理中  | 元のサイトへ