vue ssrはwindow._を削除する方法INITIAL_STATE__ ちゅうにゅう

26462 ワード

本文を読む前に、vue ssrのドキュメントを完全に読んだことがあると仮定して、vue ssrのプロジェクトを構築する方法を知っています.問題のように、この需要を提出するのは、SEOの大物が収録に影響を与えると言っていることが多い.公式文書には次のような言葉がありますが、
2.5.0+バージョンでは、組み込みscriptを本番モードで自動的に削除することもできます.
しかし、実際には、デバッグコンソールには対応するscriptラベルがありませんが、ソースコードを表示するときにこの内容が表示されます.これは、サービス側がHTMLコードを生成するときに注入されていることを示しています.クライアントに到着した後、removeChildで削除しただけです.明らかにSEOのニーズを満たすことはできません.では、どうすればいいですか.クレイジーに検索したことがあるなら、この言葉を見たことがあるはずです.
両端のデータを同期できればwindowに注入しない.INITIAL_STATEでも大丈夫です
これがソリューションのようで、両端のデータを同期しますが、具体的にはどういう意味ですか?まず、サービス・エンド全体のレンダリングの手順を思い出します.
  • は、指定されたポートを傍受するサービスを開始し、クライアントからの要求
    // server.js
    const express = require('express')
    const app = express()
    const port = process.env.PORT || 80;
    
    //     ssr          
    
    app.get('*', isProd ? renderHandler : (req, res, next) => {
        readyPromise.then(() => renderHandler(req, res, next))
    })
    
    app.listen(port, () => {
        console.log(`server started at localhost:${port}`)
    })
    
  • を受信する.
  • クライアントは、要求を開始する、その後、サービス側が受信し、それを処理し、先にexpressのルーティングを要求し、ここでvueのルーティングの適合、解析、処理を渡す、その後、一致するコンポーネント要求データインタフェースをルーティングし、要求が完了するとvuex storeにデータを書き込む、これは基本的に
    renderHandler () {
    
    	//         entry-server.js      
    	renderer.renderToString(context, (err, html) => {
    	    if (err) {
    	        return handleError(err)
    	    }
    	
    	    res.send(html);
    	})
    }
    
    entry-serverを終了する.js基本は公式ドキュメントの内容とそっくり
    export default context => {
        return new Promise((resolve, reject) => {
            const { app, router, store } = createApp()
            
            //     
            router.push(context.url)
        
        	//                  ,          ,    
            router.onReady(() => {
                const matchedComponents = router.getMatchedComponents();
                
                if (!matchedComponents.length) {
                    return reject({ code: 404 })
                }
        
                //              `asyncData()`,      vuex store
                Promise.all(matchedComponents.map(Component => {
                    if (Component.asyncData) {
                        return Component.asyncData({
                            store,
                            hostname: context.hostname,
                            route: router.currentRoute
                        })
                    }
                })).then(() => {
                    context.state = store.state;
                    
                    resolve(app)
                }, err => {
                    reject(err);
                }).catch(() => {
                    
                })
            }, reject)
        })
    }
    
  • データの準備が完了するとrendererに戻り、vue単一ファイルの内容をデータに結合してHTML文字列に変換し、クライアントに戻り、entry-clientを注釈すると.jsのすべてのコードは、このときアクセスするとクライアントが設計原稿の内容と一致するページを表示する必要があります(ここでは、vue単一ファイルをレンダリングする際のデータは、すべてサービス側のvuex storeから取得することを説明する必要があります)
    renderer.renderToString(context, (err, html) => {
        //             
        res.send(html);
    })
    
  • ここでレンダリングプロセス全体がほぼ終了します.
    しかし、問題があります.私が書いたインタラクティブコードはどうして有効になっていませんか?私のクリックイベントは?私のクールな特効は?振り返ってみると、インタラクティブコードはどこに書いてありますか.vue単一ファイル内.なぜ有効にならないのですか?サービス側はHTML文字列を返しただけなので、vueコンポーネントを返さなかったらどうしますか?アクティブにしてコンポーネントに戻す
    具体的な方法は簡単です.entry-clientです.jsには簡単な数行のコードだけでいいです
    import { createApp } from '../app'
    const { app, router, store } = createApp()
    
    router.onReady(() => {
        app.$mount('#app')
    })
    
    //           vuex store    ,      vuex store
    if (window.__INITIAL_STATE__) {
      store.replaceState(window.__INITIAL_STATE__)
    }
    

    今は対話がまたよくなった.ここで、サービス側レンダリングプロセス全体が終了します.
    どうやってwindowを取り除くの?INITIAL_STATE注入は?これにはvue ssrの手動リソース注入が必要です
    デフォルトでは、templateレンダリングオプションが指定されている場合、リソース注入は自動的に実行されます.しかし、リソース注入テンプレートをより細かく制御する必要がある場合や、テンプレートをまったく使用しない場合があります.この場合、rendererを作成し、リソース注入を手動で実行するときにinject:falseに転送できます.
    次にテンプレートファイルを変更し、vuex storeが注入したコードを削除します.
    <html>
      <head>
        
        {{{ renderResourceHints() }}}
        {{{ renderStyles() }}}
      head>
      <body>
        
        
        {{{ renderState() }}}
        {{{ renderScripts() }}}
      body>
    html>
    

    確かに簡単なようで、コードを貼り付けたり、保存したり、サービスを再起動したりします.ページを更新します.私のページは?さっき何もなかったときは元気だったのに、あなたのコードにはBUGがあるのではないでしょうか.実はそうではありません.問題はentry-clientにあります.jsのコードにあります.entry-client.jsという簡単な数行のコードは、いったい何をしたのだろうか.実際にはよく理解されています.
  • vueコンポーネント
  • を初期化する
  • 仮想DOMツリー
  • の構築
  • は、次に、現在のページの既存のDOM構造を比較し、一致しない部分を仮想DOMツリーの内容に置き換えます.

  • 消えた内容は、すべて置き換えられた.クライアントが構築した仮想DOMツリーが、サービス側が返す構造と一致しないのはなぜですか?データは違いますからね.サービス側が取得したデータは、サービス側がインスタンス化したvuexオブジェクトに存在します.これまではHTMLコンテンツ(window.INITIAL_STATE)に文字列でデータを注入し、クライアントで取得していましたが、注入させないようにしていました.データがなければ、生成されたDOMコンテンツは異なるに違いありません.
    両端のデータを同期させるには、サービス側のvuex storeをクライアントに伝える方法が必要です.私のやり方:
  • データを取得した後、vuex storeを
  • にキャッシュする.
  • そしてキャッシュされたkeyをページに注入する(はい、やはり注入から逃れられませんが、注入内容は非常に少なく、少なくともSEOにOKを感じさせることができます)
  • クライアントは、リクエストによりインスタンスのデータを取得し、クライアントのvuex storeに注入する.

  • キャッシュ方式は2種類あります.1つはメモリに直接書くことです.もう1つはredisを使うことです.私はredisにあまり詳しくないので、これを採用していません.具体的な方法を見てみましょう.
  • には、lru-cacheを使用してデータを格納し、新しいファイルrouterDataCacheを作成するキャッシュコンテナが必要です.js、コードは以下の
    const LRU = require('lru-cache')
    
    const dataCache = new LRU({
        max: 1000,
        maxAge: 1000 * 60 * 15, //      ,         
    });
    
    //         ,      ,      
    module.exports = dataCache;
    
  • serverを変更します.jsのrender関数、ここでデータキャッシュ(キャッシュオブジェクトrouterDataCache.jsを導入することを覚えています)
    function render(req, res, next) {
    	// ...        
    	
    	//        key,key           ,                      
    	// md5       ,    
    	let cachekey = md5(`vuex state cache:${ req.hostname }${ req.url }`);
    	
    	//      
    	let context = {
            cachekey,
            url: req.url,
            hostname: req.hostname
        }
        
        //        ,             
        if (dataCache.has(cachekey)) {
            context = _.assign({}, dataCache.peek(cachekey), context);
        }
    
    	renderer.renderToString(context, (err, html) => {
            if (err) {
                return handleError(err)
            }
    		
    		//         ,  context      
            if(!dataCache.has(cachekey)) {
                dataCache.set(cachekey, context);
            }
    
            res.send(html)
        })
    }
    
  • 現在vuex storeはメモリに保存されていますが、クライアントはどのように取得しますか?インタフェースを書いて、サービス側がキャッシュしたデータをクライアントに返さなければなりません.新しいファイルjs
    const express = require("express");
    const router = express.Router();
    const cache = require('routerDataCache')
    
    router.get('/route-cache/:key', (req, res) => {
        let key = req.params.key;
        
        res.setHeader("Content-Type", "application/json")
        res.send(cache.peek(key));
    })
    
    module.exports = router
    
    はその後expressにマウントされ、server.jsに関連内容を加える:
    const apiRouter = require('router/CacheRouter')
    
    app.use('/apidata', apiRouter);
    
  • クライアントで取得し、クライアントvuex storeに注入するには、テンプレートページとclient-entryを変更する必要がある.jsのコードテンプレートは
  • です.