成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Vue 服務(wù)端渲染實踐 ——Web應(yīng)用首屏耗時最優(yōu)化方案

terasum / 1762人閱讀

摘要:好在后是支持服務(wù)端渲染的,零零散散花費了兩三周事件,通過改造現(xiàn)有項目,基本完成了在現(xiàn)有項目中實踐了服務(wù)端渲染。在服務(wù)端生成對應(yīng)的字符串,客戶端接收到對應(yīng)的字符串,能立即渲染,最高效的首屏耗時。服務(wù)端渲染的原理是虛擬。實現(xiàn)前后端同構(gòu)應(yīng)用。

隨著各大前端框架的誕生和演變,SPA開始流行,單頁面應(yīng)用的優(yōu)勢在于可以不重新加載整個頁面的情況下,通過ajax和服務(wù)器通信,實現(xiàn)整個Web應(yīng)用拒不更新,帶來了極致的用戶體驗。然而,對于需要SEO、追求極致的首屏性能的應(yīng)用,前端渲染的SPA是糟糕的。好在Vue 2.0后是支持服務(wù)端渲染的,零零散散花費了兩三周事件,通過改造現(xiàn)有項目,基本完成了在現(xiàn)有項目中實踐了Vue服務(wù)端渲染。

關(guān)于Vue服務(wù)端渲染的原理、搭建,官方文檔已經(jīng)講的比較詳細(xì)了,因此,本文不是抄襲文檔,而是文檔的補充。特別是對于如何與現(xiàn)有項目進(jìn)行很好的結(jié)合,還是需要費很大功夫的。本文主要對我所在的項目中進(jìn)行Vue服務(wù)端渲染的改造過程進(jìn)行闡述,加上一些個人的理解,作為分享與學(xué)習(xí)。

概述

本文主要分以下幾個方面:

什么是服務(wù)端渲染?服務(wù)端渲染的原理是什么?

如何在基于KoaWeb Server Frame上配置服務(wù)端渲染?

基本用法

Webpack配置

開發(fā)環(huán)境搭建

渲染中間件配置

如何對現(xiàn)有項目進(jìn)行改造?

基本目錄改造;

在服務(wù)端用vue-router分割代碼;

在服務(wù)端預(yù)拉取數(shù)據(jù);

客戶端托管全局狀態(tài);

常見問題的解決方案;

什么是服務(wù)端渲染?服務(wù)端渲染的原理是什么?
Vue.js是構(gòu)建客戶端應(yīng)用程序的框架。默認(rèn)情況下,可以在瀏覽器中輸出Vue組件,進(jìn)行生成DOM和操作DOM。然而,也可以將同一個組件渲染為服務(wù)器端的HTML字符串,將它們直接發(fā)送到瀏覽器,最后將這些靜態(tài)標(biāo)記"激活"為客戶端上完全可交互的應(yīng)用程序。

上面這段話是源自Vue服務(wù)端渲染文檔的解釋,用通俗的話來說,大概可以這么理解:

服務(wù)端渲染的目的是:性能優(yōu)勢。 在服務(wù)端生成對應(yīng)的HTML字符串,客戶端接收到對應(yīng)的HTML字符串,能立即渲染DOM,最高效的首屏耗時。此外,由于服務(wù)端直接生成了對應(yīng)的HTML字符串,對SEO也非常友好;

服務(wù)端渲染的本質(zhì)是:生成應(yīng)用程序的“快照”。將Vue及對應(yīng)庫運行在服務(wù)端,此時,Web Server Frame實際上是作為代理服務(wù)器去訪問接口服務(wù)器來預(yù)拉取數(shù)據(jù),從而將拉取到的數(shù)據(jù)作為Vue組件的初始狀態(tài)。

服務(wù)端渲染的原理是:虛擬DOM。在Web Server Frame作為代理服務(wù)器去訪問接口服務(wù)器來預(yù)拉取數(shù)據(jù)后,這是服務(wù)端初始化組件需要用到的數(shù)據(jù),此后,組件的beforeCreatecreated生命周期會在服務(wù)端調(diào)用,初始化對應(yīng)的組件后,Vue啟用虛擬DOM形成初始化的HTML字符串。之后,交由客戶端托管。實現(xiàn)前后端同構(gòu)應(yīng)用。

如何在基于KoaWeb Server Frame上配置服務(wù)端渲染? 基本用法

需要用到Vue服務(wù)端渲染對應(yīng)庫vue-server-renderer,通過npm安裝:

npm install vue vue-server-renderer --save

最簡單的,首先渲染一個Vue實例:

// 第 1 步:創(chuàng)建一個 Vue 實例
const Vue = require("vue");

const app = new Vue({
  template: `
Hello World
` }); // 第 2 步:創(chuàng)建一個 renderer const renderer = require("vue-server-renderer").createRenderer(); // 第 3 步:將 Vue 實例渲染為 HTML renderer.renderToString(app, (err, html) => { if (err) { throw err; } console.log(html); // =>
Hello World
});

與服務(wù)器集成:

module.exports = async function(ctx) {
    ctx.status = 200;
    let html = "";
    try {
        // ...
        html = await renderer.renderToString(app, ctx);
    } catch (err) {
        ctx.logger("Vue SSR Render error", JSON.stringify(err));
        html = await ctx.getErrorPage(err); // 渲染出錯的頁面
    }
    

    ctx.body = html;
}

使用頁面模板:

當(dāng)你在渲染Vue應(yīng)用程序時,renderer只從應(yīng)用程序生成HTML標(biāo)記。在這個示例中,我們必須用一個額外的HTML頁面包裹容器,來包裹生成的HTML標(biāo)記。

為了簡化這些,你可以直接在創(chuàng)建renderer時提供一個頁面模板。多數(shù)時候,我們會將頁面模板放在特有的文件中:



  Hello
  
    
  

然后,我們可以讀取和傳輸文件到Vue renderer中:

const tpl = fs.readFileSync(path.resolve(__dirname, "./index.html"), "utf-8");
const renderer = vssr.createRenderer({
    template: tpl,
});
Webpack配置

然而在實際項目中,不止上述例子那么簡單,需要考慮很多方面:路由、數(shù)據(jù)預(yù)取、組件化、全局狀態(tài)等,所以服務(wù)端渲染不是只用一個簡單的模板,然后加上使用vue-server-renderer完成的,如下面的示意圖所示:

如示意圖所示,一般的Vue服務(wù)端渲染項目,有兩個項目入口文件,分別為entry-client.jsentry-server.js,一個僅運行在客戶端,一個僅運行在服務(wù)端,經(jīng)過Webpack打包后,會生成兩個Bundle,服務(wù)端的Bundle會用于在服務(wù)端使用虛擬DOM生成應(yīng)用程序的“快照”,客戶端的Bundle會在瀏覽器執(zhí)行。

因此,我們需要兩個Webpack配置,分別命名為webpack.client.config.jswebpack.server.config.js,分別用于生成客戶端Bundle與服務(wù)端Bundle,分別命名為vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json,關(guān)于如何配置,Vue官方有相關(guān)示例vue-hackernews-2.0

開發(fā)環(huán)境搭建

我所在的項目使用Koa作為Web Server Frame,項目使用koa-webpack進(jìn)行開發(fā)環(huán)境的構(gòu)建。如果是在產(chǎn)品環(huán)境下,會生成vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json,包含對應(yīng)的Bundle,提供客戶端和服務(wù)端引用,而在開發(fā)環(huán)境下,一般情況下放在內(nèi)存中。使用memory-fs模塊進(jìn)行讀取。

const fs = require("fs")
const path = require( "path" );
const webpack = require( "webpack" );
const koaWpDevMiddleware = require( "koa-webpack" );
const MFS = require("memory-fs");
const appSSR = require("./../../app.ssr.js");

let wpConfig;
let clientConfig, serverConfig;
let wpCompiler;
let clientCompiler, serverCompiler;

let clientManifest;
let bundle;

// 生成服務(wù)端bundle的webpack配置
if ((fs.existsSync(path.resolve(cwd,"webpack.server.config.js")))) {
  serverConfig = require(path.resolve(cwd, "webpack.server.config.js"));
  serverCompiler = webpack( serverConfig );
}

// 生成客戶端clientManifest的webpack配置
if ((fs.existsSync(path.resolve(cwd,"webpack.client.config.js")))) {
  clientConfig = require(path.resolve(cwd, "webpack.client.config.js"));
  clientCompiler = webpack(clientConfig);
}

if (serverCompiler && clientCompiler) {
  let publicPath = clientCompiler.output && clientCompiler.output.publicPath;

  const koaDevMiddleware = await koaWpDevMiddleware({
    compiler: clientCompiler,
    devMiddleware: {
      publicPath,
      serverSideRender: true
    },
  });

  app.use(koaDevMiddleware);

  // 服務(wù)端渲染生成clientManifest

  app.use(async (ctx, next) => {
    const stats = ctx.state.webpackStats.toJson();
    const assetsByChunkName = stats.assetsByChunkName;
    stats.errors.forEach(err => console.error(err));
    stats.warnings.forEach(err => console.warn(err));
    if (stats.errors.length) {
      console.error(stats.errors);
      return;
    }
    // 生成的clientManifest放到appSSR模塊,應(yīng)用程序可以直接讀取
    let fileSystem = koaDevMiddleware.devMiddleware.fileSystem;
    clientManifest = JSON.parse(fileSystem.readFileSync(path.resolve(cwd,"./dist/vue-ssr-client-manifest.json"), "utf-8"));
    appSSR.clientManifest = clientManifest;
    await next();
  });

  // 服務(wù)端渲染的server bundle 存儲到內(nèi)存里
  const mfs = new MFS();
  serverCompiler.outputFileSystem = mfs;
  serverCompiler.watch({}, (err, stats) => {
    if (err) {
      throw err;
    }
    stats = stats.toJson();
    if (stats.errors.length) {
      console.error(stats.errors);
      return;
    }
    // 生成的bundle放到appSSR模塊,應(yīng)用程序可以直接讀取
    bundle = JSON.parse(mfs.readFileSync(path.resolve(cwd,"./dist/vue-ssr-server-bundle.json"), "utf-8"));
    appSSR.bundle = bundle;
  });
}
渲染中間件配置

產(chǎn)品環(huán)境下,打包后的客戶端和服務(wù)端的Bundle會存儲為vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json,通過文件流模塊fs讀取即可,但在開發(fā)環(huán)境下,我創(chuàng)建了一個appSSR模塊,在發(fā)生代碼更改時,會觸發(fā)Webpack熱更新,appSSR對應(yīng)的bundle也會更新,appSSR模塊代碼如下所示:

let clientManifest;
let bundle;

const appSSR = {
  get bundle() {
    return bundle;
  },
  set bundle(val) {
    bundle = val;
  },
  get clientManifest() {
    return clientManifest;
  },
  set clientManifest(val) {
    clientManifest = val;
  }
};

module.exports = appSSR;

通過引入appSSR模塊,在開發(fā)環(huán)境下,就可以拿到clientManifestssrBundle,項目的渲染中間件如下:

const fs = require("fs");
const path = require("path");
const ejs = require("ejs");
const vue = require("vue");
const vssr = require("vue-server-renderer");
const createBundleRenderer = vssr.createBundleRenderer;
const dirname = process.cwd();

const env = process.env.RUN_ENVIRONMENT;

let bundle;
let clientManifest;

if (env === "development") {
  // 開發(fā)環(huán)境下,通過appSSR模塊,拿到clientManifest和ssrBundle
  let appSSR = require("./../../core/app.ssr.js");
  bundle = appSSR.bundle;
  clientManifest = appSSR.clientManifest;
} else {
  bundle = JSON.parse(fs.readFileSync(path.resolve(__dirname, "./dist/vue-ssr-server-bundle.json"), "utf-8"));
  clientManifest = JSON.parse(fs.readFileSync(path.resolve(__dirname, "./dist/vue-ssr-client-manifest.json"), "utf-8"));
}


module.exports = async function(ctx) {
  ctx.status = 200;
  let html;
  let context = await ctx.getTplContext();
  ctx.logger("進(jìn)入SSR,context為: ", JSON.stringify(context));
  const tpl = fs.readFileSync(path.resolve(__dirname, "./newTemplate.html"), "utf-8");
  const renderer = createBundleRenderer(bundle, {
    runInNewContext: false,
    template: tpl, // (可選)頁面模板
    clientManifest: clientManifest // (可選)客戶端構(gòu)建 manifest
  });
  ctx.logger("createBundleRenderer  renderer:", JSON.stringify(renderer));
  try {
    html = await renderer.renderToString({
      ...context,
      url: context.CTX.url,
    });
  } catch(err) {
    ctx.logger("SSR renderToString 失?。?", JSON.stringify(err));
    console.error(err);
  }

  ctx.body = html;
};
如何對現(xiàn)有項目進(jìn)行改造? 基本目錄改造

使用Webpack來處理服務(wù)器和客戶端的應(yīng)用程序,大部分源碼可以使用通用方式編寫,可以使用Webpack支持的所有功能。

一個基本項目可能像是這樣:

src
├── components
│   ├── Foo.vue
│   ├── Bar.vue
│   └── Baz.vue
├── frame
│   ├── app.js # 通用 entry(universal entry)
│   ├── entry-client.js # 僅運行于瀏覽器
│   ├── entry-server.js # 僅運行于服務(wù)器
│   └── index.vue # 項目入口組件
├── pages
├── routers
└── store

app.js是我們應(yīng)用程序的「通用entry」。在純客戶端應(yīng)用程序中,我們將在此文件中創(chuàng)建根Vue實例,并直接掛載到DOM。但是,對于服務(wù)器端渲染(SSR),責(zé)任轉(zhuǎn)移到純客戶端entry文件。app.js簡單地使用export導(dǎo)出一個createApp函數(shù):

import Router from "~ut/router";
import { sync } from "vuex-router-sync";
import Vue from "vue";
import { createStore } from "./../store";

import Frame from "./index.vue";
import myRouter from "./../routers/myRouter";

function createVueInstance(routes, ctx) {
    const router = Router({
        base: "/base",
        mode: "history",
        routes: [routes],
    });
    const store = createStore({ ctx });
    // 把路由注入到vuex中
    sync(store, router);
    const app = new Vue({
        router,
        render: function(h) {
            return h(Frame);
        },
        store,
    });
    return { app, router, store };
}

module.exports = function createApp(ctx) {
    return createVueInstance(myRouter, ctx); 
}
注:在我所在的項目中,需要動態(tài)判斷是否需要注冊DicomView,只有在客戶端才初始化DicomView,由于Node.js環(huán)境沒有window對象,對于代碼運行環(huán)境的判斷,可以通過typeof window === "undefined"來進(jìn)行判斷。
避免創(chuàng)建單例

Vue SSR文檔所述:

當(dāng)編寫純客戶端 (client-only) 代碼時,我們習(xí)慣于每次在新的上下文中對代碼進(jìn)行取值。但是,Node.js 服務(wù)器是一個長期運行的進(jìn)程。當(dāng)我們的代碼進(jìn)入該進(jìn)程時,它將進(jìn)行一次取值并留存在內(nèi)存中。這意味著如果創(chuàng)建一個單例對象,它將在每個傳入的請求之間共享。如基本示例所示,我們?yōu)槊總€請求創(chuàng)建一個新的根 Vue 實例。這與每個用戶在自己的瀏覽器中使用新應(yīng)用程序的實例類似。如果我們在多個請求之間使用一個共享的實例,很容易導(dǎo)致交叉請求狀態(tài)污染 (cross-request state pollution)。因此,我們不應(yīng)該直接創(chuàng)建一個應(yīng)用程序?qū)嵗?,而是?yīng)該暴露一個可以重復(fù)執(zhí)行的工廠函數(shù),為每個請求創(chuàng)建新的應(yīng)用程序?qū)嵗?。同樣的?guī)則也適用于 router、store 和 event bus 實例。你不應(yīng)該直接從模塊導(dǎo)出并將其導(dǎo)入到應(yīng)用程序中,而是需要在 createApp 中創(chuàng)建一個新的實例,并從根 Vue 實例注入。

如上代碼所述,createApp方法通過返回一個返回值創(chuàng)建Vue實例的對象的函數(shù)調(diào)用,在函數(shù)createVueInstance中,為每一個請求創(chuàng)建了Vue,Vue RouterVuex實例。并暴露給entry-cliententry-server模塊。

在客戶端entry-client.js只需創(chuàng)建應(yīng)用程序,并且將其掛載到DOM中:

import { createApp } from "./app";

// 客戶端特定引導(dǎo)邏輯……

const { app } = createApp();

// 這里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount("#app");

服務(wù)端entry-server.js使用default export 導(dǎo)出函數(shù),并在每次渲染中重復(fù)調(diào)用此函數(shù)。此時,除了創(chuàng)建和返回應(yīng)用程序?qū)嵗猓粫鎏嗍虑?- 但是稍后我們將在此執(zhí)行服務(wù)器端路由匹配和數(shù)據(jù)預(yù)取邏輯:

import { createApp } from "./app";

export default context => {
  const { app } = createApp();
  return app;
}
在服務(wù)端用vue-router分割代碼

Vue實例一樣,也需要創(chuàng)建單例的vueRouter對象。對于每個請求,都需要創(chuàng)建一個新的vueRouter實例:

function createVueInstance(routes, ctx) {
    const router = Router({
        base: "/base",
        mode: "history",
        routes: [routes],
    });
    const store = createStore({ ctx });
    // 把路由注入到vuex中
    sync(store, router);
    const app = new Vue({
        router,
        render: function(h) {
            return h(Frame);
        },
        store,
    });
    return { app, router, store };
}

同時,需要在entry-server.js中實現(xiàn)服務(wù)器端路由邏輯,使用router.getMatchedComponents方法獲取到當(dāng)前路由匹配的組件,如果當(dāng)前路由沒有匹配到相應(yīng)的組件,則reject404頁面,否則resolve整個app,用于Vue渲染虛擬DOM,并使用對應(yīng)模板生成對應(yīng)的HTML字符串。

const createApp = require("./app");

module.exports = context => {
  return new Promise((resolve, reject) => {
    // ...
    // 設(shè)置服務(wù)器端 router 的位置
    router.push(context.url);
    // 等到 router 將可能的異步組件和鉤子函數(shù)解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents();
      // 匹配不到的路由,執(zhí)行 reject 函數(shù),并返回 404
      if (!matchedComponents.length) {
        return reject("匹配不到的路由,執(zhí)行 reject 函數(shù),并返回 404");
      }
      // Promise 應(yīng)該 resolve 應(yīng)用程序?qū)嵗?,以便它可以渲?      resolve(app);
    }, reject);
  });

}
在服務(wù)端預(yù)拉取數(shù)據(jù)

Vue服務(wù)端渲染,本質(zhì)上是在渲染我們應(yīng)用程序的"快照",所以如果應(yīng)用程序依賴于一些異步數(shù)據(jù),那么在開始渲染過程之前,需要先預(yù)取和解析好這些數(shù)據(jù)。服務(wù)端Web Server Frame作為代理服務(wù)器,在服務(wù)端對接口服務(wù)發(fā)起請求,并將數(shù)據(jù)拼裝到全局Vuex狀態(tài)中。

另一個需要關(guān)注的問題是在客戶端,在掛載到客戶端應(yīng)用程序之前,需要獲取到與服務(wù)器端應(yīng)用程序完全相同的數(shù)據(jù) - 否則,客戶端應(yīng)用程序會因為使用與服務(wù)器端應(yīng)用程序不同的狀態(tài),然后導(dǎo)致混合失敗。

目前較好的解決方案是,給路由匹配的一級子組件一個asyncData,在asyncData方法中,dispatch對應(yīng)的action。asyncData是我們約定的函數(shù)名,表示渲染組件需要預(yù)先執(zhí)行它獲取初始數(shù)據(jù),它返回一個Promise,以便我們在后端渲染的時候可以知道什么時候該操作完成。注意,由于此函數(shù)會在組件實例化之前調(diào)用,所以它無法訪問this。需要將store和路由信息作為參數(shù)傳遞進(jìn)去:

舉個例子:




entry-server.js中,我們可以通過路由獲得與router.getMatchedComponents()相匹配的組件,如果組件暴露出asyncData,我們就調(diào)用這個方法。然后我們需要將解析完成的狀態(tài),附加到渲染上下文中。

const createApp = require("./app");

module.exports = context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp(context);
    // 針對沒有Vue router 的Vue實例,在項目中為列表頁,直接resolve app
    if (!router) {
      resolve(app);
    }
    // 設(shè)置服務(wù)器端 router 的位置
      router.push(context.url.replace("/base", ""));
    // 等到 router 將可能的異步組件和鉤子函數(shù)解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents();
      // 匹配不到的路由,執(zhí)行 reject 函數(shù),并返回 404
      if (!matchedComponents.length) {
        return reject("匹配不到的路由,執(zhí)行 reject 函數(shù),并返回 404");
      }
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute,
          });
        }
      })).then(() => {
        // 在所有預(yù)取鉤子(preFetch hook) resolve 后,
        // 我們的 store 現(xiàn)在已經(jīng)填充入渲染應(yīng)用程序所需的狀態(tài)。
        // 當(dāng)我們將狀態(tài)附加到上下文,并且 `template` 選項用于 renderer 時,
        // 狀態(tài)將自動序列化為 `window.__INITIAL_STATE__`,并注入 HTML。
        context.state = store.state;
        resolve(app);
      }).catch(reject);
    }, reject);
  });
}
客戶端托管全局狀態(tài)

當(dāng)服務(wù)端使用模板進(jìn)行渲染時,context.state將作為window.__INITIAL_STATE__狀態(tài),自動嵌入到最終的HTML 中。而在客戶端,在掛載到應(yīng)用程序之前,store就應(yīng)該獲取到狀態(tài),最終我們的entry-client.js被改造為如下所示:

import createApp from "./app";

const { app, router, store } = createApp();

// 客戶端把初始化的store替換為window.__INITIAL_STATE__
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__);
}

if (router) {
  router.onReady(() => {
    app.$mount("#app")
  });
} else {
  app.$mount("#app");
}
常見問題的解決方案

至此,基本的代碼改造也已經(jīng)完成了,下面說的是一些常見問題的解決方案:

在服務(wù)端沒有window、location對象:

對于舊項目遷移到SSR肯定會經(jīng)歷的問題,一般為在項目入口處或是createdbeforeCreate生命周期使用了DOM操作,或是獲取了location對象,通用的解決方案一般為判斷執(zhí)行環(huán)境,通過typeof window是否為"undefined",如果遇到必須使用location對象的地方用于獲取url中的相關(guān)參數(shù),在ctx對象中也可以找到對應(yīng)參數(shù)。

vue-router報錯Uncaught TypeError: _Vue.extend is not _Vue function,沒有找到_Vue實例的問題:

通過查看Vue-router源碼發(fā)現(xiàn)沒有手動調(diào)用Vue.use(Vue-Router);。沒有調(diào)用Vue.use(Vue-Router);在瀏覽器端沒有出現(xiàn)問題,但在服務(wù)端就會出現(xiàn)問題。對應(yīng)的Vue-router源碼所示:

VueRouter.prototype.init = function init (app /* Vue component instance */) {
    var this$1 = this;

  process.env.NODE_ENV !== "production" && assert(
    install.installed,
    "not installed. Make sure to call `Vue.use(VueRouter)` " +
    "before creating root instance."
  );
  // ...
}

服務(wù)端無法獲取hash路由的參數(shù)

由于hash路由的參數(shù),會導(dǎo)致vue-router不起效果,對于使用了vue-router的前后端同構(gòu)應(yīng)用,必須換為history路由。

接口處獲取不到cookie的問題:

由于客戶端每次請求都會對應(yīng)地把cookie帶給接口側(cè),而服務(wù)端Web Server Frame作為代理服務(wù)器,并不會每次維持cookie,所以需要我們手動把
cookie透傳給接口側(cè),常用的解決方案是,將ctx掛載到全局狀態(tài)中,當(dāng)發(fā)起異步請求時,手動帶上cookie,如下代碼所示:

// createStore.js
// 在創(chuàng)建全局狀態(tài)的函數(shù)`createStore`時,將`ctx`掛載到全局狀態(tài)
export function createStore({ ctx }) {
    return new Vuex.Store({
        state: {
            ...state,
            ctx,
        },
        getters,
        actions,
        mutations,
        modules: {
            // ...
        },
        plugins: debug ? [createLogger()] : [],
    });
}

當(dāng)發(fā)起異步請求時,手動帶上cookie,項目中使用的是Axios

// actions.js

// ...
const actions = {
  async getUserInfo({ commit, state }) {
    let requestParams = {
      params: {
        random: tool.createRandomString(8, true),
      },
      headers: {
        "X-Requested-With": "XMLHttpRequest",
      },
    };

    // 手動帶上cookie
    if (state.ctx.request.headers.cookie) {
      requestParams.headers.Cookie = state.ctx.request.headers.cookie;
    }

    // ...

    let res = await Axios.get(`${requestUrlOrigin}${url.GET_A}`, requestParams);
    commit(globalTypes.SET_A, {
      res: res.data,
    });
  }
};

// ...

接口請求時報connect ECONNREFUSED 127.0.0.1:80的問題

原因是改造之前,使用客戶端渲染時,使用了devServer.proxy代理配置來解決跨域問題,而服務(wù)端作為代理服務(wù)器對接口發(fā)起異步請求時,不會讀取對應(yīng)的webpack配置,對于服務(wù)端而言會對應(yīng)請求當(dāng)前域下的對應(yīng)path下的接口。

解決方案為去除webpackdevServer.proxy配置,對于接口請求帶上對應(yīng)的origin即可:

const requestUrlOrigin = requestUrlOrigin = state.ctx.URL.origin;
const res = await Axios.get(`${requestUrlOrigin}${url.GET_A}`, requestParams);

對于vue-router配置項有base參數(shù)時,初始化時匹配不到對應(yīng)路由的問題

在官方示例中的entry-server.js

// entry-server.js
import { createApp } from "./app";

export default context => {
  // 因為有可能會是異步路由鉤子函數(shù)或組件,所以我們將返回一個 Promise,
  // 以便服務(wù)器能夠等待所有的內(nèi)容在渲染前,
  // 就已經(jīng)準(zhǔn)備就緒。
  return new Promise((resolve, reject) => {
    const { app, router } = createApp();

    // 設(shè)置服務(wù)器端 router 的位置
    router.push(context.url);

    // ...
  });
}

原因是設(shè)置服務(wù)器端router的位置時,context.url為訪問頁面的url,并帶上了base,在router.push時應(yīng)該去除base,如下所示:

router.push(context.url.replace("/base", ""));
小結(jié)

本文為筆者通過對現(xiàn)有項目進(jìn)行改造,給現(xiàn)有項目加上Vue服務(wù)端渲染的實踐過程的總結(jié)。

首先闡述了什么是Vue服務(wù)端渲染,其目的、本質(zhì)及原理,通過在服務(wù)端使用Vue的虛擬DOM,形成初始化的HTML字符串,即應(yīng)用程序的“快照”。帶來極大的性能優(yōu)勢,包括SEO優(yōu)勢和首屏渲染的極速體驗。之后闡述了Vue服務(wù)端渲染的基本用法,即兩個入口、兩個webpack配置,分別作用于客戶端和服務(wù)端,分別生成vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json作為打包結(jié)果。最后通過對現(xiàn)有項目的改造過程,包括對路由進(jìn)行改造、數(shù)據(jù)預(yù)獲取和狀態(tài)初始化,并解釋了在Vue服務(wù)端渲染項目改造過程中的常見問題,幫助我們進(jìn)行現(xiàn)有項目往Vue服務(wù)端渲染的遷移。

文章最后,打個廣告:騰訊醫(yī)療部門招前端工程師啦,HC無限多,社招、校招均可內(nèi)推。如果有想來騰訊的小伙伴,可以添加我的微信:xingbofeng001,如果有想交朋友、交流技術(shù)的小伙伴也歡迎添加我的微信~

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/102700.html

相關(guān)文章

  • React同構(gòu)直出優(yōu)化總結(jié)

    摘要:同構(gòu)的關(guān)鍵要素完善的屬性及生命周期與客戶端的時機是同構(gòu)的關(guān)鍵。的一致性在前后端渲染相同的,將輸出一致的結(jié)構(gòu)。以上便是在同構(gòu)服務(wù)端渲染的提供的基礎(chǔ)條件??梢詫⒎庋b至的中,在服務(wù)端上生成隨機數(shù)并傳入到這個中,從而保證隨機數(shù)在客戶端和服務(wù)端一致。 原文地址 React 的實踐從去年在 PC QQ家校群開始,由于 PC 上的網(wǎng)絡(luò)及環(huán)境都相當(dāng)好,所以在使用時可謂一帆風(fēng)順,偶爾遇到點小磕絆,也能夠...

    alaege 評論0 收藏0
  • 黑科技:美團(tuán)網(wǎng)頁首幀優(yōu)化實踐

    摘要:在美團(tuán)支付的前端技術(shù)體系里,通過預(yù)渲染提升網(wǎng)頁首幀優(yōu)化,從而優(yōu)化了白屏問題,提升用戶體驗,并形成了最佳實踐。我們團(tuán)隊主要負(fù)責(zé)美團(tuán)支付相關(guān)的業(yè)務(wù),如果網(wǎng)站太慢會影響用戶的支付體驗,會造成客訴或資損。 前言 自JavaScript誕生以來,前端技術(shù)發(fā)展非常迅速。移動端白屏優(yōu)化是前端界面體驗的一個重要優(yōu)化方向,Web 前端誕生了 SSR 、CSR、預(yù)渲染等技術(shù)。在美團(tuán)支付的前端技術(shù)體系里,通...

    mrli2016 評論0 收藏0
  • 讓老板虎軀一震的前技術(shù),KPI殺手

    摘要:我們的目標(biāo)是讓的頁面也能夠擁有般的體驗,如果你還在尋求什么技術(shù)能夠讓老板虎軀一震拯救你的,那么這篇文章或許能夠幫助到你。這是一個使用編寫的頁面,運行于多端,包括企鵝輔導(dǎo)手機手機瀏覽器。經(jīng)過我們的測試發(fā)現(xiàn)安卓基本上都是支持的,需要以上才支持。 本文由云+社區(qū)發(fā)表作者:思衍Jax showImg(https://segmentfault.com/img/remote/1460000017...

    PiscesYE 評論0 收藏0
  • 讓老板虎軀一震的前技術(shù),KPI殺手

    摘要:我們的目標(biāo)是讓的頁面也能夠擁有般的體驗,如果你還在尋求什么技術(shù)能夠讓老板虎軀一震拯救你的,那么這篇文章或許能夠幫助到你。這是一個使用編寫的頁面,運行于多端,包括企鵝輔導(dǎo)手機手機瀏覽器。經(jīng)過我們的測試發(fā)現(xiàn)安卓基本上都是支持的,需要以上才支持。 本文由云+社區(qū)發(fā)表 作者:思衍Jax 天下武功,唯 (wei) 快(fu) 不(bu) 破(po)。 隨著近幾年的前端技術(shù)的高速發(fā)展,越來越多的...

    Jaden 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<