欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何構(gòu)建 vue-ssr 項目的方法步驟

 更新時間:2020年08月04日 09:17:40   作者:三只萌新  
這篇文章主要介紹了如何構(gòu)建 vue-ssr 項目的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

如何通過 web 服務(wù)器去渲染一個 vue 實例

構(gòu)建一個極簡的服務(wù)端渲染需要什么

  • web 服務(wù)器
  • vue-server-renderer
  • vue
const Vue = require('vue')
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const router = new Router()
const renderer = require('vue-server-renderer').createRenderer()
router.get(/./, (ctx)=>{
 const app = new Vue({
 data: {
 url: ctx.request.url
 },
 template: `<div>訪問的 URL 是: {{ url }}</div>`
 })

 renderer.renderToString(app, (err, html) => {
 if (err) {
 ctx.status = 500 
 ctx.body = err.toString()
 }
 ctx.body = `
 <!DOCTYPE html>
 <html lang="en">
 <head><title>Hello</title></head>
 <body>${html}</body>
 </html>
 `
 })
})
app.use(router.routes())
app.listen(4000,()=>{
 console.log('listen 4000')
})
  • 首先通過 koa、koa-router 快速起了一個 web 服務(wù)器,這個服務(wù)器接受任何路徑
  • 創(chuàng)建了一個renderer對象,創(chuàng)建一個 vue 實例
  • renderer.renderToString 將 vue 實例解析為 html 字符串
  • 通過 ctx.body ,拼接成一個完整的 html 字符串模版返回。

相信經(jīng)過上面的代碼實例可得知,即使你沒有使用過 vue-ssr 的經(jīng)歷,但是你簡單地使用過 vue 和 koa 的同學(xué)都可以看出來這個代碼非常明了。

唯一要注意的地方就是,我們是通過 require('vue-server-renderer').createRenderer() 來創(chuàng)建一個 renderer 對象 . 這個renderer 對象有一個 renderToString 的方法

renderer.renderToString(app,(err,html)=>{})

  •  app 就是創(chuàng)建的 vue 實例
  • callback, 解析 app 后執(zhí)行的回調(diào),回調(diào)的第二個參數(shù)就是解析完實例得到的 html 字符串,這個的 html 字符串是掛載到 #app 那部分,是不包含 head、body 的,所以我們需要將它拼接成完整的 html 字符串返回給客戶端。 

使用 template 用法

上面方法中 ctx.body 的部分需要手動去拼接模版,vue-ssr 支持使用模版的方式。

來看下模版長啥樣,發(fā)現(xiàn)出來多一行 <!--vue-ssr-outlet--> 注釋,和普通的html文件沒有差別

<!--vue-ssr-outlet--> 注釋 -- 這里將是應(yīng)用程序 HTML 標(biāo)記注入的地方。也就是 renderToString 回調(diào)中的 html 會被注入到這里。

<!DOCTYPE html>
<html lang="en">
 <head><title>Hello</title></head>
 <body>
 <!--vue-ssr-outlet-->
 </body>
</html>

有了模版該如何使用它呢?

只需要在創(chuàng)建 renderer 之前給 createRenderer 函數(shù)傳遞 template 參數(shù)即可。

看下使用模版和自定義模版的區(qū)別,可以看到通過其他部分都相同,只是我們指定了 template 后,ctx.body 返回的地方我們不需要手動去拼接一個完整的 html 結(jié)構(gòu)了。

const renderer = require('vue-server-renderer').createRenderer({
 template: fs.readFileSync('./index.template.html','utf-8')
})
router.get(/./, (ctx)=>{
 const app = new Vue({
 data: {
 url: ctx.request.url
 },
 template:"<div>訪問路徑{{url}}</div>"
 })
 renderer.renderToString(app, (err, html) => {
 if (err) {
 ctx.status = 500 
 ctx.body = err.toString()
 }
 ctx.body = html
 })
})

項目級

上面的實例是 demo 的展示,在實際項目中開發(fā)的話我們會根據(jù)客戶端和服務(wù)端將它們分別劃分在不同的區(qū)塊中。

項目結(jié)構(gòu)

// 一個基本項目可能像是這樣:
build     -- webpack配置
|——- client.config.js 
|——- server.config.js
|——- webpack.base.config.js
src
├── components
│ ├── Foo.vue
│ ├── Bar.vue
│ └── Baz.vue
├── App.vue
├── app.js # 通用 entry(universal entry) -- 生成 vue 的工廠函數(shù)
├── entry-client.js # 僅運行于瀏覽器 -- 將 vue 實例掛載,作為 webpack 的入口
|── entry-server.js # 僅運行于服務(wù)器 -- 數(shù)據(jù)預(yù)處理邏輯,作為 webpack 的入口
|-- server.js    -- web 服務(wù)器啟動入口 
|-- store.js    -- 服務(wù)端數(shù)據(jù)預(yù)處理存儲容器
|-- router.js    -- vue 路由表

加載一個vue-ssr應(yīng)用整體流程

首先根據(jù)上面的項目結(jié)構(gòu)我們可以大概知道,我們的服務(wù)端和客戶端分別以 entry-client.js 和 entry-server.js 為入口,通過 webpack 打包出對應(yīng)的 bundle.js 文件。

首先不考慮 entry-client.js 和 entry-server.js 做了什么(后續(xù)會補充),我們需要知道,它們經(jīng)過 webpack 打包后生成了我們需要的創(chuàng)建 ssr 的依賴 .js 文件。 可以看下圖打包出來的文件,.json 文件是用來關(guān)聯(lián) .js 文件的,就是一個輔助文件,真正起作用的還是兩個 .js 文件。

假設(shè)我們以及打包好了這兩份文件,我們來看 server.js 中做了什么。

server.js

// ... 省略不重要步驟
const renderer = require('vue-server-renderer').createBundleRenderer(require('./dist/vue-ssr-server-bundle.json'),{
 runInNewContext:false,
 template: fs.readFileSync('./index.template.html','utf-8'),
 // 客戶端構(gòu)建
 clientManifest:require('./dist/vue-ssr-client-manifest.json')
})
router.get('/home', async (ctx)=>{
 ctx.res.setHeader('Content-Type', 'text/html')
 const html = await renderer.renderToString()
 ctx.body = html
})
app.listen(4000,()=>{
})

省略了一些不重要的步驟,來看 server.js,其實它和我們上面創(chuàng)建一個簡單的服務(wù)端渲染步驟基本相同

  • 創(chuàng)建一個 renderer 對象,不同點在于創(chuàng)建這個對象是根據(jù)已經(jīng)打包好的 .json 文件去找到真正起作用.js 文件去生成的。
  • 由于在 createBunldeRenderer 創(chuàng)建 renderer 對象的時候同時傳入了 server.json 和 client-mainfest.json 兩個部分,所以我們在使用 renderer.renderToString() 的時候也不需要去傳入 vue實例了。
  • 最終得到 html 字符串和上面相同,返回客戶端就完成了服務(wù)端渲染的部分。接下來就是客戶端解析渲染 dom 的過程。

 流程梳理

有了對項目結(jié)構(gòu)的了解,和 server.js 的基本了解后來梳理下 vue-ssr 整個工作流程是怎么樣的?

首先我們會啟動一個 web 服務(wù),也就上面的 server.js ,來查看一個服務(wù)端路徑

router.get('/home', async (ctx)=>{
 const context = {
 title:'template render',
 url:ctx.request.url
 }
 ctx.res.setHeader('Content-Type', 'text/html')
 const html = await renderer.renderToString(context)
 ctx.body = html
})
app.listen(4000,()=>{
 console.log('listen 4000')
})

當(dāng)我們訪問 http://localhost:4000/home 就會命中該路由,執(zhí)行 renderer.renderToString(context) ,renderer 是根據(jù)我們已經(jīng)打包好的 bundle 文件生成的 renderer對象。相當(dāng)于去執(zhí)行 entry-server.js 服務(wù)端數(shù)據(jù)處理和存儲的操作

根據(jù)模版文件,得到 html 文件后返回給客戶端,Vue 在瀏覽器端接管由服務(wù)端發(fā)送的靜態(tài) HTML,使其變?yōu)橛?Vue 管理的動態(tài) DOM 的過程。相當(dāng)于去執(zhí)行 entry-client.js 客戶端的邏輯

由于服務(wù)器已經(jīng)渲染好了 HTML,我們顯然無需將其丟棄再重新創(chuàng)建所有的 DOM 元素。相反,我們需要"激活"這些靜態(tài)的 HTML,然后使他們成為動態(tài)的(能夠響應(yīng)后續(xù)的數(shù)據(jù)變化)。 如果你檢查服務(wù)器渲染的輸出結(jié)果,你會注意到應(yīng)用程序的根元素上添加了一個特殊的屬性:

<div id="app" data-server-rendered="true">

entry-client.js 和 entry-server.js

經(jīng)過上面的流程梳理我們知道了當(dāng)訪問一個 vue-ssr 的整個流程: 訪問 web 服務(wù)器地址 > 執(zhí)行 renderer.renderToString(context) 解析已經(jīng)打包的 bunlde 返回 html 字符串 > 在客戶端激活這些靜態(tài)的 html,使它們成為動態(tài)的。

接下來我們需要看看 entry-client.js 和 entry-server.js 做了什么。

entry-server.js

  • 這里的 context 就是 renderer.renderToString(context) 傳遞的值,至于你想傳遞什么是你在 web 服務(wù)器中自定義的,可以傳遞任何你想給客戶端的值。
  • 這里我們可以通過 context 來獲取到客戶端返回 web 服務(wù)器的地址,通過 context.url (需要你在服務(wù)端傳遞該值)獲取到該路徑,并且通過 router.push(context.url) 實例來訪問相同的路徑。
  • context.url 對應(yīng)的組件中會定義一個 asyncData 的靜態(tài)方法,并且將服務(wù)端存儲在 store 的值傳遞給該方法。
  • 將 store 中的值存儲給 context.state ,context.state 將作為 window. INITIAL_STATE 狀態(tài),自動嵌入到最終的 HTML 中。就是一個全局變量。
import { createApp } from './app'

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

 // 設(shè)置服務(wù)器端 router 的位置
 router.push(context.url)
 // 等到 router 將可能的異步組件和鉤子函數(shù)解析完
 router.onReady(() => {
  const matchedComponents = router.getMatchedComponents()
  // 匹配不到的路由,執(zhí)行 reject 函數(shù),并返回 404
  if (!matchedComponents.length) {
  return reject({ code: 404 })
  }
  // 對所有匹配的路由組件調(diào)用 asyncData
  // Promise.all([p1,p2,p3])
  const allSyncData = matchedComponents.map(Component => {
  if(Component.asyncData) {
   return Component.asyncData({
   store,route:router.currentRoute
   })
  }
  })
  Promise.all(allSyncData).then(() => {
  // 當(dāng)使用 template 時,context.state 將作為 window.__INITIAL_STATE__ 狀態(tài),自動嵌入到最終的 HTML 中。
  context.state = store.state
  resolve(app)
  }).catch(reject)
 }, reject)
 })
}

entry-client.js

執(zhí)行匹配到的組件中定義的 asyncData 靜態(tài)方法,將 store 中的值取出來作為客戶端的數(shù)據(jù)。

import { createApp } from './app'
// 你仍然需要在掛載 app 之前調(diào)用 router.onReady,因為路由器必須要提前解析路由配置中的異步組件,才能正確地調(diào)用組件中可能存在的路由鉤子。
const { app,router,store } = createApp()

if (window.__INITIAL_STATE__) {
 store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
 // 使用 `router.beforeResolve()`,以便確保所有異步組件都 resolve。
 router.beforeResolve((to,from,next) => {
 const matched = router.getMatchedComponents(to)
 const prevMatched = router.getMatchedComponents(from)

 // 我們只關(guān)心非預(yù)渲染的組件
 // 所以我們對比它們,找出兩個匹配列表的差異組件
 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')
})

構(gòu)建配置

 webpack.base.config.js

服務(wù)端和客戶端相同的配置一些通用配置,和我們平時使用的 webpack 配置相同,截取部分展示

module.exports = {
 mode:isProd ? 'production' : 'development',
 devtool: isProd
 ? false
 : '#cheap-module-source-map',
 output: {
 path: path.resolve(__dirname, '../dist'),
 publicPath: '/dist/',
 filename: '[name].[chunkhash].js'
 },
 module: {
 rules: [
  {
  test: /\.vue$/,
  loader: 'vue-loader',
  options: {
   compilerOptions: {
   preserveWhitespace: false
   }
  }
  },
  {
  test: /\.js$/,
  loader: 'babel-loader',
  exclude: /node_modules/
  },
  {
  test: /\.(png|jpg|gif|svg)$/,
  loader: 'url-loader',
  options: {
   limit: 10000,
   name: '[name].[ext]?[hash]'
  }
  },
  {
  test: /\.styl(us)?$/,
  use: isProd
   ? ExtractTextPlugin.extract({
    use: [
    {
     loader: 'css-loader',
     options: { minimize: true }
    },
    'stylus-loader'
    ],
    fallback: 'vue-style-loader'
   })
   : ['vue-style-loader', 'css-loader', 'stylus-loader']
  },
 ]
 },
 plugins: [
  new VueLoaderPlugin()
  ]
}

client.config.js

const webpack = require('webpack')
const {merge} = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const path = require('path')
module.exports = merge(baseConfig,{
 entry:path.resolve('__dirname','../entry-client.js'),
 plugins:[
 // 生成 `vue-ssr-client-manifest.json`。
 new VueSSRClientPlugin()
 ]
})

server.config.js

const { merge } = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const path = require('path')
module.exports = merge(baseConfig,{
 entry:path.resolve('__dirname','../entry-server.js'),
 target:'node',
 devtool:'source-map',
 // 告知 server bundle 使用 node 風(fēng)格導(dǎo)出模塊
 output:{
 libraryTarget:'commonjs2'
 },
 externals: nodeExternals({
 allowlist:/\.css$/
 }),
 plugins:[
 new VueSSRServerPlugin()
 ]
})

開發(fā)環(huán)境配置

webpack 提供 node api可以在 node 運行時使用。

修改 server.js

server.js 作為 web 服務(wù)器的入口文件,我們需要判斷當(dāng)前運行的環(huán)境是開發(fā)環(huán)境還是生產(chǎn)環(huán)境。

const isProd = process.env.NODE_ENV === 'production'
async function prdServer(ctx) {
 // ...生產(chǎn)環(huán)境去讀取 dist/ 下的 bundle 文件
}
async function devServer(ctx){
 // 開發(fā)環(huán)境
}
router.get('/home',isProd ? prdServer : devServer)
app.use(router.routes())
app.listen(4000,()=>{
 console.log('listen 4000')
})

dev-server.js

生產(chǎn)環(huán)境中是通過讀取內(nèi)存中 dist/ 文件夾下的 bundle 來解析生成 html 字符串的。在開發(fā)環(huán)境中我們該怎么拿到 bundle 文件呢?

  • webpack function 讀取 webpack 配置來獲取編譯后的文件
  • memory-fs 來讀取內(nèi)存中的文件
  • koa-webpack-dev-middleware 將 bundle 寫入內(nèi)存中,當(dāng)客戶端文件發(fā)生變化可以支持熱更新

 webpack 函數(shù)使用

導(dǎo)入的 webpack 函數(shù)會將 配置對象 傳給 webpack,如果同時傳入回調(diào)函數(shù)會在 webpack compiler 運行時被執(zhí)行:

• 方式一:添加回調(diào)函數(shù)

const webpackConfig = {
 // ...配置項
}
const callback = (err,stats) => {}
webpack(webpackConfig, callback)

err對象 不包含 編譯錯誤,必須使用 stats.hasErrors() 單獨處理,文檔的 錯誤處理 將對這部分將對此進行詳細(xì)介紹。err 對象只包含 webpack 相關(guān)的問題,例如配置錯誤等。

方式二:得到一個 compiler 實例

你可以通過手動執(zhí)行它或者為它的構(gòu)建時添加一個監(jiān)聽器,compiler 提供以下方法

compiler.run(callback)

compiler.watch(watchOptions,handler) 啟動所有編譯工作

const webpackConfig = {
 // ...配置項
}
const compiler = webpack(webpackConfig)

客戶端配置

 const clientCompiler = webpack(clientConfig)
  const devMiddleware = require('koa-webpack-dev-middleware')(clientCompiler,{
  publicPath:clientConfig.output.publicPath,
  noInfo:true,
  stats:{
   colors:true
  }
  })

  app.use(devMiddleware)
  // 編譯完成時觸發(fā)
  clientCompiler.hooks.done.tap('koa-webpack-dev-middleware', stats => {
  stats = stats.toJson()
  stats.errors.forEach(err => console.error(err))
  stats.warnings.forEach(err => console.warn(err))
  if (stats.errors.length) return
  clientManifest = JSON.parse(readFile(
   devMiddleware.fileSystem,
   'vue-ssr-client-manifest.json'
  ))
  update()
  })

默認(rèn)情況下,webpack 使用普通文件系統(tǒng)來讀取文件并將文件寫入磁盤。但是,還可以使用不同類型的文件系統(tǒng)(內(nèi)存(memory), webDAV 等)來更改輸入或輸出行為。為了實現(xiàn)這一點,可以改變 inputFileSystem 或 outputFileSystem。例如,可以使用 memory-fs 替換默認(rèn)的 outputFileSystem,以將文件寫入到內(nèi)存中。

koa-webpack-dev-middleware 內(nèi)部就是用 memory-fs 來替換 webpack 默認(rèn)的 outputFileSystem 將文件寫入內(nèi)存中的。

讀取內(nèi)存中的 vue-ssr-client-mainfest.json

調(diào)用 update 封裝好的更新方法

服務(wù)端配置

讀取內(nèi)存中的vue-ssr-server-bundle.json文件

調(diào)用 update 封裝好的更新方法

 // hot middleware
  app.use(require('koa-webpack-hot-middleware')(clientCompiler, { heartbeat: 5000 }))
  // watch and update server renderer
  const serverCompiler = webpack(serverConfig)
  serverCompiler.outputFileSystem = mfs
  serverCompiler.watch({}, (err, stats) => {
  if (err) throw err
  stats = stats.toJson()
  if (stats.errors.length) return

  // read bundle generated by vue-ssr-webpack-plugin
  bundle = JSON.parse(readFile(mfs, 'vue-ssr-server-bundle.json'))
  update()
  })

update 方法

const update = async () => {
  if(bundle && clientManifest) {
   const renderer = createRenderer(bundle,{
   template:require('fs').readFileSync(templatePath,'utf-8'),
   clientManifest
   })
   // 自定義上下文
   html = await renderer.renderToString({url:ctx.url,title:'這里是標(biāo)題'})
   ready()
  }
  }

總結(jié)

本文將自己理解的 vue-ssr 構(gòu)建過程做了梳理,到此這篇關(guān)于如何構(gòu)建 vue-ssr 項目的文章就介紹到這了,更多相關(guān)如何構(gòu)建 vue-ssr 項目內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue組件實現(xiàn)旋轉(zhuǎn)木馬動畫

    Vue組件實現(xiàn)旋轉(zhuǎn)木馬動畫

    這篇文章主要為大家詳細(xì)介紹了Vue組件實現(xiàn)旋轉(zhuǎn)木馬動畫效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • 深入理解Vue 的條件渲染和列表渲染

    深入理解Vue 的條件渲染和列表渲染

    本篇文章主要介紹了深入理解Vue 的條件渲染和列表渲染,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • Vue組件庫ElementUI實現(xiàn)表格列表分頁效果

    Vue組件庫ElementUI實現(xiàn)表格列表分頁效果

    這篇文章主要為大家詳細(xì)介紹了Vue組件庫ElementUI實現(xiàn)表格列表分頁效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • vant之關(guān)于van-list的使用以及一些坑的解決方案

    vant之關(guān)于van-list的使用以及一些坑的解決方案

    這篇文章主要介紹了vant之關(guān)于van-list的使用以及一些坑的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 詳解vue?Router(v3.x)?路由傳參的三種方式

    詳解vue?Router(v3.x)?路由傳參的三種方式

    vue路由傳參的使用場景一般都是應(yīng)用在父路由跳轉(zhuǎn)到子路由時,攜帶參數(shù)跳轉(zhuǎn),本文將詳細(xì)介紹vue路由傳參的三種方式,這三種傳參方式只是針對vue?Router?V3版本的,需要的朋友可以參考下
    2023-07-07
  • vue如何根據(jù)網(wǎng)站路由判斷頁面主題色詳解

    vue如何根據(jù)網(wǎng)站路由判斷頁面主題色詳解

    這篇文章主要給大家介紹了關(guān)于vue如何根據(jù)網(wǎng)站路由判斷頁面主題色的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • vue 粒子特效的示例代碼

    vue 粒子特效的示例代碼

    本篇文章主要介紹了vue 粒子特效的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • el-menu如何根據(jù)多層樹形結(jié)構(gòu)遞歸遍歷展示菜單欄

    el-menu如何根據(jù)多層樹形結(jié)構(gòu)遞歸遍歷展示菜單欄

    這篇文章主要介紹了el-menu根據(jù)多層樹形結(jié)構(gòu)遞歸遍歷展示菜單欄,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-07-07
  • Vue項目打包部署的實戰(zhàn)過程記錄

    Vue項目打包部署的實戰(zhàn)過程記錄

    我們使用nginx部署Vue項目,實質(zhì)上就是將Vue項目打包后的內(nèi)容同步到nginx指向的文件夾,下面這篇文章主要給大家介紹了關(guān)于Vue項目打包部署的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • vue3-ace-editor如何配置語法

    vue3-ace-editor如何配置語法

    這篇文章主要介紹了vue3-ace-editor如何配置語法問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06

最新評論