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

資訊專欄INFORMATION COLUMN

高舉 Vue-SSR

Codeing_ls / 3258人閱讀

摘要:三個(gè)配置文件以此如下改配置只是簡(jiǎn)單的配置等的使用,接著提取資源文件,指定輸出的目錄,而入口文件則分別在和的中配置。將指向應(yīng)用程序的文件允許適用方式處理動(dòng)態(tài)導(dǎo)入,提供支持使用風(fēng)格導(dǎo)出模塊不要外置化需要處理的依賴模塊。

將同一個(gè)組件渲染為服務(wù)器端的 HTML 字符串,將它們直接發(fā)送到瀏覽器,最后將靜態(tài)標(biāo)記"混合"為客戶端上完全交互的應(yīng)用程序。
SSR的目的

To solve

首屏渲染問題

SEO問題

項(xiàng)目結(jié)構(gòu)
vue-ssr
├── build                 (webapck編譯配置)
├── components            (vue 頁面) 
├── dist                  (編譯后的靜態(tài)資源目錄)
├── api.js                (請(qǐng)求接口,模擬異步請(qǐng)求)
├── app.js                (創(chuàng)建Vue實(shí)例入口)
├── App.vue               (Vue頁面入口)
├── entry-client.js       (前端執(zhí)行入口)
├── entry-server.js       (后端執(zhí)行入口)
├── index.template.html   (前端渲染模板)
├── router.js             (Vue路由配置)
├── server.js             (Koa服務(wù))
├── store.js              (Vuex數(shù)據(jù)狀態(tài)中心配置)
原理概覽

這張圖相信很多大佬們都看過N遍了,每個(gè)人理解不同,我發(fā)表一下自己個(gè)人的理解,如果有什么理解錯(cuò)誤請(qǐng)?jiān)徫摇?/p>

先看Source部分,Source部分先由app.js引入Vue全家桶,至于Vue全家桶如何配置后面會(huì)說明。app.js其實(shí)就是創(chuàng)建一個(gè)注冊(cè)好各種依賴的Vue對(duì)象實(shí)例,在SPA單頁環(huán)境下,我們只需要拿到這個(gè)Vue實(shí)例,然后指定掛載到模板特定的dom結(jié)點(diǎn),然后丟給webpack處理就完事了。但是SSR在此分為兩部分,一部分是前端單頁,一部分是后端直出。于是,Client entry的作用是掛載Vue對(duì)象實(shí)例,并由webpack進(jìn)行編譯打包,最后在瀏覽器渲染。Server entry的作用是拿到Vue對(duì)象實(shí)例,并處理收集頁面中的asynData,獲取對(duì)應(yīng)的數(shù)據(jù)上下文,然后再由webpack解析處理。最后Node Server端中使用weback編譯好的兩個(gè)bundle文件( 服務(wù)器需要「服務(wù)器 bundle」然后用于服務(wù)器端渲染(SSR),而「客戶端 bundle」會(huì)發(fā)送給瀏覽器,用于混合靜態(tài)標(biāo)記。),當(dāng)用戶請(qǐng)求頁面時(shí)候,這時(shí)候服務(wù)端會(huì)先使用SSR來生成對(duì)應(yīng)的頁面文檔結(jié)構(gòu),而在用戶切換路由則是使用了SPA的模式。

搭建環(huán)境 項(xiàng)目依賴說明

Koa2 + Vue2 + Vue-router + Vuex

一切都從路由開始

先來配置vue-router, 生成router.js

import Vue from "vue"
import Router from "vue-router"
import Bar from "./components/Bar.vue"
import Baz from "./components/Baz.vue"
import Foo from "./components/Foo.vue"
import Item from "./components/Item.vue"

Vue.use(Router)

export const createRouter = () => {
  return new Router({
    mode: "history",
    routes: [
      { path: "/item/:id", component: Item },
      { path: "/bar", component: Bar },
      { path: "/baz", component: Baz },
      { path: "/foo", component: Foo }
    ]
  })
}

為每個(gè)請(qǐng)求創(chuàng)建一個(gè)新的Vue實(shí)例,路由也是如此,通過一個(gè)工廠函數(shù)來保證每次都是新創(chuàng)建一個(gè)Vue路由的新實(shí)例。

Vuex 配置

配置Vuex, 生成store.js

import Vue from "vue"
import Vuex from "vuex"
import { fetchItem } from "./api"

Vue.use(Vuex)

export const createStore = () => {
  return new Vuex.Store({
    state: {
      items: {}
    },
    actions: {
      fetchItem ({ commit }, id) {
        return fetchItem(id).then(item => {
          commit("setItem", { id, item })
        })
      }
    },
    mutations: {
      setItem (state, { id, item }) {
        Vue.set(state.items, id, item)
      }
    }
  })
}

同樣也是通過一個(gè)工廠函數(shù),來創(chuàng)建一個(gè)新的Vuex實(shí)例并暴露該方法

生成一個(gè)Vue的根實(shí)例

創(chuàng)建Vue實(shí)例,生成app.js

import Vue from "vue"
import App from "./App.vue"
import { createRouter } from "./router"
import { createStore } from "./store"
import { sync } from "vuex-router-sync"

export const createApp = ssrContext => {
  const router = createRouter()
  const store = createStore()

  sync(store, router)

  const app = new Vue({
    router,
    store,
    ssrContext,
    render: h => h(App)
  })
  return { 
    app, 
    store, 
    router 
  }
}

通過使用我們編寫的createRouter, createStore來每次都創(chuàng)建新的Vue-router和Vuex實(shí)例,保證和Vue的實(shí)例一樣都是重新創(chuàng)建過的,接著掛載注冊(cè)router和store到Vue的實(shí)例中,提供createApp傳入服務(wù)端渲染對(duì)應(yīng)的數(shù)據(jù)上下文。

到此我們已經(jīng)基本完成source部分的工作了。接著就要考慮如何去編譯打包這些文件,讓瀏覽器和Node服務(wù)端去運(yùn)行解析。

先從前端入口文件開始

前端打包入口文件: entry-client.js

import { createApp } from "./app"

const { 
  app, 
  store,
  router 
} = createApp()
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
  router.beforeResolve((to, from, next) => {
    const matched = router.getMatchedComponents(to)
    const prevMatched = router.getMatchedComponents(from)
    let diffed = false
    const activated = matched.filter((c, i) => {
      return diffed || (diffed = (prevMatched[i] !== c))
    })
    if (!activated.length) {
      return next()
    }
    Promise.all(activated.map(c => {
      if (c.asyncData) {
        return c.asyncData({ store, route: to })
      }
    })).then(() => {
      next()
    }).catch(next)
  })
  app.$mount("#app")
})

客戶端的entry只需創(chuàng)建應(yīng)用程序,并且將其掛載到 DOM 中, 需要注意的是,任然需要在掛載 app 之前調(diào)用 router.onReady,因?yàn)槁酚善鞅仨氁崆敖馕雎酚膳渲弥械漠惒浇M件,(如果你有使用異步組件的話,本項(xiàng)目沒有使用到異步組件,但后續(xù)考慮加入) 才能正確地調(diào)用組件中可能存在的路由鉤子。通過添加路由鉤子函數(shù),用于處理 asyncData,在初始路由 resolve 后執(zhí)行,以便我們不會(huì)二次預(yù)取(double-fetch)已有的數(shù)據(jù)。使用 router.beforeResolve(),以便確保所有異步組件都 resolve,并對(duì)比之前沒有渲染的組件找出兩個(gè)匹配列表的差異組件,如果沒有差異表示無需處理直接next輸出。

再看服務(wù)端渲染解析入口文件

服務(wù)端渲染的執(zhí)行入口文件: entry-server.js

import { createApp } from "./app"

export default context => {
  return new Promise((resolve, reject) => {
    const { 
      app, 
      store,
      router 
    } = createApp(context)

    router.push(context.url)

    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }

      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute
          })
        }
      })).then(() => {
        context.state = store.state
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

服務(wù)器 entry 使用 default export 導(dǎo)出函數(shù),并在每次渲染中重復(fù)調(diào)用此函數(shù)。此時(shí),創(chuàng)建和返回應(yīng)用程序?qū)嵗?,還在此執(zhí)行服務(wù)器端路由匹配(server-side route matching)和數(shù)據(jù)預(yù)取邏輯(data pre-fetching logic)。在所有預(yù)取鉤子(preFetch hook) resolve 后,我們的 store 現(xiàn)在已經(jīng)填充入渲染應(yīng)用程序所需的狀態(tài)。當(dāng)我們將狀態(tài)附加到上下文,并且 template 選項(xiàng)用于 renderer 時(shí),狀態(tài)將自動(dòng)序列化為 window.__INITIAL_STATE__,并注入 HTML。

激動(dòng)人心的來寫webpack

直接上手weback4.x版本

webpack配置分為3個(gè)配置,公用配置,客戶端配置,服務(wù)端配置。

三個(gè)配置文件以此如下:

base config:

const path = require("path")
const webpack = require("webpack")
const ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
  devtool: "#cheap-module-source-map",
  output: {
    path: path.resolve(__dirname, "../dist"),
    publicPath: "/",
    filename: "[name]-[chunkhash].js"
  },
  resolve: {
    alias: {
      "public": path.resolve(__dirname, "../public"),
      "components": path.resolve(__dirname, "../components")
    },
    extensions: [".js", ".vue"]
  },
  module: {
    rules: [
      {
        test: /.vue$/,
        use: {
          loader: "vue-loader"
        }
      },
      {
        test: /.js$/,
        use: "babel-loader",
        exclude: /node_modules/
      },
      {
        test: /.css$/,
        use: "css-loader"
      }
    ]
  },
  performance: {
    maxEntrypointSize: 300000,
    hints: "warning"
  },
  plugins: [
    new ExtractTextPlugin({
      filename: "common.[chunkhash].css"
    })
  ]
}

改配置只是簡(jiǎn)單的配置vue, css, babel等loader的使用,接著ExtractTextPlugin提取css資源文件,指定輸出的目錄,而入口文件則分別在client和server的config中配置。

client config

const webpack = require("webpack")
const merge = require("webpack-merge")
const path = require("path")
const baseConfig = require("./webpack.base.config.js")
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin")


module.exports = merge(baseConfig, {
  entry: path.resolve(__dirname, "../entry-client.js"),
  plugins: [
    new VueSSRClientPlugin()
  ],
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          chunks: "initial",
          minChunks: 2, maxInitialRequests: 5,
          minSize: 0
        },
        vendor: {
          test: /node_modules/,
          chunks: "initial",
          name: "vendor",
          priority: 10,
          enforce: true
        }
      }
    },
    runtimeChunk: true
  }
})

客戶端的入口文件,使用VueSSRClientPlugin生成對(duì)應(yīng)的vue-ssr-client-manifest.json的映射文件,然后添加vendor的chunk分離。

server config

const merge = require("webpack-merge")
const path = require("path")
const nodeExternals = require("webpack-node-externals")
const baseConfig = require("./webpack.base.config.js")
const VueSSRServerPlugin = require("vue-server-renderer/server-plugin")


module.exports = merge(baseConfig, {
  // 將 entry 指向應(yīng)用程序的 server entry 文件
  entry: path.resolve(__dirname, "../entry-server.js"),
  // 允許 webpack Node 適用方式(Node-appropriate fashion)處理動(dòng)態(tài)導(dǎo)入(dynamic import),
  target: "node",
  // 提供 source map 支持
  devtool: "source-map",
  // 使用 Node 風(fēng)格導(dǎo)出模塊(Node-style exports)
  output: {
    filename: "server-bundle.js",
    libraryTarget: "commonjs2"
  },
  externals: nodeExternals({
    // 不要外置化 webpack 需要處理的依賴模塊。
    // 你可以在這里添加更多的文件類型。例如,未處理 *.vue 原始文件,
    // 你還應(yīng)該將修改 `global`(例如 polyfill)的依賴模塊列入白名單
    whitelist: /.css$/
  }),
  // 這是將服務(wù)器的整個(gè)輸出
  // 構(gòu)建為單個(gè) JSON 文件的插件。
  // 默認(rèn)文件名為 `vue-ssr-server-bundle.json`
  plugins: [
    new VueSSRServerPlugin()
  ]
})

到此打包的流程已經(jīng)結(jié)束了,server端配置參考了官網(wǎng)的注釋。

使用Koa2
const { createBundleRenderer } = require("vue-server-renderer")
const serverBundle = require("./dist/vue-ssr-server-bundle.json")
const clientManifest = require("./dist/vue-ssr-client-manifest.json")
const fs = require("fs")
const path = require("path")

const Koa = require("koa")
const KoaRuoter = require("koa-router")
const serve = require("koa-static")

const app = new Koa()
const router = new KoaRuoter()

const template = fs.readFileSync(path.resolve("./index.template.html"), "utf-8")

const renderer = createBundleRenderer(serverBundle, {
  // 推薦
  runInNewContext: false,
  // (可選)頁面模板
  template, 
  // (可選)客戶端構(gòu)建 manifest
  clientManifest 
})

app.use(serve(path.resolve(__dirname, "./dist")))

router.get("*", (ctx, next) => {
  ctx.set("Content-Type", "text/html")
  return new Promise((resolve, reject) => {
    const handleError = err => {
      if (err && err.code === 404) {
          ctx.status = 404
          ctx.body = "404 | Page Not Found"
      } else {
          ctx.status = 500
          ctx.body = "500 | Internal Server Error"
          console.error(`error during render : ${ctx.url}`)
          console.error(err.stack)
      }
      resolve()
    }
    console.log(ctx.url)
    const context = { url: ctx.url, title: "Vue SSR" }
  
    // 這里無需傳入一個(gè)應(yīng)用程序,因?yàn)樵趫?zhí)行 bundle 時(shí)已經(jīng)自動(dòng)創(chuàng)建過。
    // 現(xiàn)在我們的服務(wù)器與應(yīng)用程序已經(jīng)解耦!
    renderer.renderToString(context, (err, html) => {
      // 處理異?!?      if (err) {
        handleError(err)
      }
      ctx.body = html
      resolve()
    })
  })
})

app.use(router.routes()).use(router.allowedMethods())

const port = 3000
app.listen(port, "127.0.0.1", () => {
    console.log(`server running at localhost:${port}`)
})

最后效果當(dāng)然是這樣的了:

參考文檔:

vue-ssr官方文檔

代碼倉庫:

github鏈接

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

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

相關(guān)文章

  • 從零開始搭建一個(gè)vue-ssr(下)

    摘要:開始改建補(bǔ)充安裝依賴與上一次不同,這次我們基于進(jìn)行改建,已經(jīng)有了很多依賴庫了,但我們?nèi)涡枰a(bǔ)充一個(gè)核心修改客戶端的配置修改文件,添加插件添加了這個(gè)配置以后,重新啟動(dòng)項(xiàng)目通過地址就可以訪問到,頁面中出現(xiàn)的內(nèi)容就是所需要的。 從零開始搭建一個(gè)vue-ssr 前言 上次我們已經(jīng)實(shí)現(xiàn)了從零開始,搭建一個(gè)簡(jiǎn)單的vue-ssr的demo:從零開始搭建一個(gè)vue-ssr(上)。那么這次呢,我們基于v...

    Jochen 評(píng)論0 收藏0
  • php,vue,vue-ssr 做出來的頁面有什么區(qū)別?

    摘要:靜態(tài)頁面的或者明顯最短,原因是模板幾乎沒什么內(nèi)容。靜態(tài)頁面生成的白屏?xí)r間中,大部分是首屏數(shù)據(jù)請(qǐng)求消耗的時(shí)間,,同時(shí)也可以對(duì)比出,服務(wù)器渲染的對(duì)首屏?xí)r間的確有很明顯的效果。 歡迎大家前往騰訊云+社區(qū),獲取更多騰訊海量技術(shù)實(shí)踐干貨哦~ 本文由shirishiyue發(fā)表于云+社區(qū)專欄 目前我這邊的web頁面,都是采用php+smarty模板生成的,是一種比較早期的開發(fā)模式。好處是沒有現(xiàn)階段...

    yibinnn 評(píng)論0 收藏0
  • php,vue,vue-ssr 做出來的頁面有什么區(qū)別?

    摘要:靜態(tài)頁面的或者明顯最短,原因是模板幾乎沒什么內(nèi)容。靜態(tài)頁面生成的白屏?xí)r間中,大部分是首屏數(shù)據(jù)請(qǐng)求消耗的時(shí)間,,同時(shí)也可以對(duì)比出,服務(wù)器渲染的對(duì)首屏?xí)r間的確有很明顯的效果。歡迎大家前往騰訊云+社區(qū),獲取更多騰訊海量技術(shù)實(shí)踐干貨哦~ 本文由shirishiyue發(fā)表于云+社區(qū)專欄 目前我這邊的web頁面,都是采用php+smarty模板生成的,是一種比較早期的開發(fā)模式。好處是沒有現(xiàn)階段常用的...

    vpants 評(píng)論0 收藏0
  • 從零開始搭建一個(gè)vue-ssr(上)

    摘要:從零開始搭建一個(gè)背景是什么全拼是,服務(wù)端渲染。大家不妨可以打開一些頁面或者一些公司的網(wǎng)站,查看源代碼,你會(huì)發(fā)現(xiàn),也是有這個(gè)標(biāo)記。這時(shí)候,我們發(fā)現(xiàn)頁面的路由切換生效了,并且不同頁面的源代碼也不一樣了。從零開始搭建一個(gè)下項(xiàng)目源碼 從零開始搭建一個(gè)vue-ssr 背景 What?SSR是什么? SSR全拼是Server-Side Rendering,服務(wù)端渲染。 所謂服務(wù)端渲染,指的是把...

    Winer 評(píng)論0 收藏0
  • 用vue搭建的個(gè)人博客介紹----mapblog小站

    摘要:后端主要使用的框架,數(shù)據(jù)庫采用。后臺(tái)管理登錄采用與后端進(jìn)行登陸狀態(tài)的確認(rèn)。本文首發(fā)于小站,這是一個(gè)積累和分享知識(shí)的個(gè)人博客 這篇文章擱置了很長(zhǎng)時(shí)間,最終決定還是把它寫出來,給剛開始學(xué)習(xí)vue并且想用vue寫個(gè)人博客的同學(xué)一個(gè)參考。因?yàn)楫?dāng)初我也是參考了其他人分享的知識(shí),從一個(gè)vue小白變成了一個(gè)入門級(jí)選手,并最終完成了這個(gè)個(gè)人博客的搭建工作,代碼已托管在Github-justJokee。...

    Ashin 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<