精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

手把手教你搭建Vue服務端渲染項目

開發 前端
不管是客戶端渲染還是服務端渲染,都需要等待客戶端執行 new Vue() 之后,用戶才能進行交互操作。

 建議先閱讀官方指南——SSR.vuejs.org/zh/" _fcksavedurl="https://SSR.vuejs.org/zh/">Vue.js 服務器端渲染指南,再回到本文開始閱讀。

本文將分成以下兩部分:

  1.  簡述 Vue SSR 過程
  2.  從零開始搭建 SSR 項目

好了,下面開始正文。

簡述 Vue SSR 過程

客戶端渲染過程

  1.  訪問客戶端渲染的網站。
  2.  服務器返回一個包含了引入資源語句和 <div id="app"></div> 的 HTML 文件。
  3.  客戶端通過 HTTP 向服務器請求資源,當必要的資源都加載完畢后,執行 new Vue() 開始實例化并渲染頁面。

服務端渲染過程

  1.  訪問服務端渲染的網站。
  2.  服務器會查看當前路由組件需要哪些資源文件,然后將這些文件的內容填充到 HTML 文件。如果有 asyncData() 函數,就會執行它進行數據預取并填充到 HTML 文件里,最后返回這個 HTML 頁面。

     3.  當客戶端接收到這個 HTML 頁面時,可以馬上就開始渲染頁面。與此同時,頁面也會加載資源,當必要的資源都加載完畢后,開始執行 new Vue() 開始實例化并接管頁面。

從上述兩個過程中,可以看出,區別就在于第二步。客戶端渲染的網站會直接返回 HTML 文件,而服務端渲染的網站則會渲染完頁面再返回這個 HTML 文件。

這樣做的好處是什么?是更快的內容到達時間 (time-to-content)。

假設你的網站需要加載完 abcd 四個文件才能渲染完畢。并且每個文件大小為 1 M。

這樣一算:客戶端渲染的網站需要加載 4 個文件和 HTML 文件才能完成首頁渲染,總計大小為 4M(忽略 HTML 文件大小)。而服務端渲染的網站只需要加載一個渲染完畢的 HTML 文件就能完成首頁渲染,總計大小為已經渲染完畢的 HTML 文件(這種文件不會太大,一般為幾百K,我的個人博客網站(SSR)加載的 HTML 文件為 400K)。這就是服務端渲染更快的原因。

客戶端接管頁面

對于服務端返回來的 HTML 文件,客戶端必須進行接管,對其進行 new Vue() 實例化,用戶才能正常使用頁面。

如果不對其進行激活的話,里面的內容只是一串字符串而已,例如下面的代碼,點擊是無效的:

  1. <button @click="sayHi">如果不進行激活,點我是不會觸發事件的</button> 

那客戶端如何接管頁面呢?下面引用一篇文章中的內容:

客戶端 new Vue() 時,客戶端會和服務端生成的DOM進行Hydration對比(判斷這個DOM和自己即將生成的DOM是否相同(vuex store 數據同步才能保持一致)

如果相同就調用app.$mount('#app')將客戶端的vue實例掛載到這個DOM上,即去“激活”這些服務端渲染的HTML之后,其變成了由Vue動態管理的DOM,以便響應后續數據的變化,即之后所有的交互和vue-router不同頁面之間的跳轉將全部在瀏覽器端運行。

如果客戶端構建的虛擬 DOM 樹與服務器渲染返回的HTML結構不一致,這時候,客戶端會請求一次服務器再渲染整個應用程序,這使得SSR失效了,達不到服務端渲染的目的了

小結

不管是客戶端渲染還是服務端渲染,都需要等待客戶端執行 new Vue() 之后,用戶才能進行交互操作。但服務端渲染的網站能讓用戶更快的看見頁面。

從零開始搭建 SSR 項目

配置 weback

webpack 配置文件共有 3 個:

  1.  webpack.base.config.js,基礎配置文件,客戶端與服務端都需要它。
  2.  webpack.client.config.js,客戶端配置文件,用于生成客戶端所需的資源。
  3.  webpack.server.config.js,服務端配置文件,用于生成服務端所需的資源。

webpack.base.config.js 基礎配置文件 

  1. const path = require('path')  
  2. const { VueLoaderPlugin } = require('vue-loader')  
  3. const isProd = process.env.NODE_ENV === 'production'  
  4. function resolve(dir) {  
  5.     return path.join(__dirname, '..', dir)  
  6.  
  7. module.exports = {  
  8.     context: path.resolve(__dirname, '../'),  
  9.     devtool: isProd ? 'source-map' : '#cheap-module-source-map',  
  10.     output: {  
  11.         path: path.resolve(__dirname, '../dist'),  
  12.         publicPath: '/dist/',  
  13.         // chunkhash 同屬一個 chunk 中的文件修改了,文件名會發生變化   
  14.         // contenthash 只有文件自己的內容變化了,文件名才會變化  
  15.         filename: '[name].[contenthash].js',  
  16.         // 此選項給打包后的非入口js文件命名,與 SplitChunksPlugin 配合使用  
  17.         chunkFilename: '[name].[contenthash].js',  
  18.     },  
  19.     resolve: {  
  20.         extensions: ['.js', '.vue', '.json', '.css'],  
  21.         alias: {  
  22.             public: resolve('public'),  
  23.             '@': resolve('src')  
  24.         }  
  25.     },  
  26.     module: {  
  27.         // https://juejin.im/post/6844903689103081485  
  28.         // 使用 `mini-css-extract-plugin` 插件打包的的 `server bundle` 會使用到 document。  
  29.         // 由于 node 環境中不存在 document 對象,所以報錯。  
  30.         // 解決方案:樣式相關的 loader 不要放在 `webpack.base.config.js` 文件  
  31.         // 將其分拆到 `webpack.client.config.js` 和 `webpack.client.server.js` 文件  
  32.         // 其中 `mini-css-extract-plugin` 插件要放在 `webpack.client.config.js` 文件配置。  
  33.         rules: [  
  34.             {  
  35.                 test: /\.vue$/,  
  36.                 loader: 'vue-loader',  
  37.                 options: {  
  38.                     compilerOptions: {  
  39.                         preserveWhitespace: false  
  40.                     }  
  41.                 }  
  42.             },  
  43.             {  
  44.                 test: /\.js$/,  
  45.                 loader: 'babel-loader',  
  46.                 exclude: /node_modules/  
  47.             },  
  48.             {  
  49.                 test: /\.(png|svg|jpg|gif|ico)$/,  
  50.                 use: ['file-loader']  
  51.             },  
  52.             {  
  53.                 test: /\.(woff|eot|ttf)\??.*$/,  
  54.                 loader: 'url-loader?name=fonts/[name].[md5:hash:hex:7].[ext]'  
  55.             },  
  56.         ]  
  57.     },  
  58.     plugins: [new VueLoaderPlugin()],  

基礎配置文件比較簡單,output 屬性的意思是打包時根據文件內容生成文件名稱。module 屬性配置不同文件的解析 loader。

webpack.client.config.js 客戶端配置文件 

  1. const webpack = require('webpack')  
  2. const merge = require('webpack-merge')  
  3. const base = require('./webpack.base.config')  
  4. const CompressionPlugin = require('compression-webpack-plugin')  
  5. const WebpackBar = require('webpackbar')  
  6. const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')  
  7. const MiniCssExtractPlugin = require('mini-css-extract-plugin')  
  8. const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')  
  9. const isProd = process.env.NODE_ENV === 'production'  
  10. const plugins = [  
  11.     new webpack.DefinePlugin({  
  12.         'process.env.NODE_ENV': JSON.stringify(  
  13.             process.env.NODE_ENV || 'development'  
  14.         ),  
  15.         'process.env.VUE_ENV': '"client"'  
  16.     }),  
  17.     new VueSSRClientPlugin(),  
  18.     new MiniCssExtractPlugin({  
  19.         filename: 'style.css'  
  20.     })  
  21.  
  22. if (isProd) {  
  23.     plugins.push(  
  24.         // 開啟 gzip 壓縮 https://github.com/woai3c/node-blog/blob/master/doc/optimize.md  
  25.         new CompressionPlugin(),  
  26.         // 該插件會根據模塊的相對路徑生成一個四位數的hash作為模塊id, 用于生產環境。  
  27.         new webpack.HashedModuleIdsPlugin(),  
  28.         new WebpackBar(),  
  29.     )  
  30.  
  31. const config = {  
  32.     entry: {  
  33.         app: './src/entry-client.js'  
  34.     },  
  35.     plugins,  
  36.     optimization: {  
  37.         runtimeChunk: {  
  38.             name: 'manifest'  
  39.         },  
  40.         splitChunks: {  
  41.             cacheGroups: {  
  42.                 vendor: {  
  43.                     name: 'chunk-vendors',  
  44.                     test: /[\\/]node_modules[\\/]/,  
  45.                     priority: -10,  
  46.                     chunks: 'initial',  
  47.                 },  
  48.                 common: {  
  49.                     name: 'chunk-common',  
  50.                     minChunks: 2,  
  51.                     priority: -20,  
  52.                     chunks: 'initial',  
  53.                     reuseExistingChunk: true  
  54.                 }  
  55.             },  
  56.         }  
  57.     },  
  58.     module: {  
  59.         rules: [  
  60.             {  
  61.                 test: /\.css$/,  
  62.                 use: [  
  63.                     {  
  64.                         loader: MiniCssExtractPlugin.loader,  
  65.                         options: {  
  66.                             // 解決 export 'default' (imported as 'mod') was not found  
  67.                             // 啟用 CommonJS 語法  
  68.                             esModule: false,  
  69.                         },  
  70.                     },  
  71.                     'css-loader'  
  72.                 ]  
  73.             }  
  74.         ]  
  75.     },  
  76.  
  77. if (isProd) {  
  78.     // 壓縮 css  
  79.     config.optimization.minimizer = [  
  80.         new CssMinimizerPlugin(),  
  81.     ]  
  82.  
  83. module.exports = merge(base, config) 

客戶端配置文件中的 config.optimization 屬性是打包時分割代碼用的。它的作用是將第三方庫都打包在一起。

其他插件作用:

  1.  MiniCssExtractPlugin 插件, 將 css 提取出來單獨打包。
  2.  CssMinimizerPlugin 插件,壓縮 css。
  3.  CompressionPlugin 插件,將資源壓縮成 gzip 格式(大大提升傳輸效率)。另外還需要在 node 服務器上引入 compression 插件配合使用。
  4.  WebpackBar 插件,打包時顯示進度條。

webpack.server.config.js 服務端配置文件 

  1. const webpack = require('webpack')  
  2. const merge = require('webpack-merge')  
  3. const base = require('./webpack.base.config')  
  4. const nodeExternals = require('webpack-node-externals') // Webpack allows you to define externals - modules that should not be bundled.  
  5. const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')  
  6. const WebpackBar = require('webpackbar')  
  7. const plugins = [  
  8.     new webpack.DefinePlugin({  
  9.         'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),  
  10.         'process.env.VUE_ENV': '"server"'  
  11.     }),  
  12.     new VueSSRServerPlugin()  
  13.  
  14. if (process.env.NODE_ENV == 'production') {  
  15.     plugins.push(  
  16.         new WebpackBar() 
  17.      )  
  18.  
  19. module.exports = merge(base, {  
  20.     target: 'node',  
  21.     devtool: '#source-map',  
  22.     entry: './src/entry-server.js',  
  23.     output: {  
  24.         filename: 'server-bundle.js',  
  25.         libraryTarget: 'commonjs2'  
  26.     },  
  27.     externals: nodeExternals({  
  28.         allowlist: /\.css$/ // 防止將某些 import 的包(package)打包到 bundle 中,而是在運行時(runtime)再去從外部獲取這些擴展依賴  
  29.     }),  
  30.     plugins,  
  31.     module: {  
  32.         rules: [  
  33.             {  
  34.                 test: /\.css$/,  
  35.                 use: [ 
  36.                      'vue-style-loader',  
  37.                     'css-loader'  
  38.                 ]  
  39.             }  
  40.         ]  
  41.     },  
  42. }) 

服務端打包和客戶端不同,它將所有文件一起打包成一個文件 server-bundle.js。同時解析 css 需要使用 vue-style-loader,這一點在官方指南中有說明:

配置服務器

生產環境

pro-server.js 生產環境服務器配置文件 

  1. const fs = require('fs')  
  2. const path = require('path')  
  3. const express = require('express')  
  4. const setApi = require('./api')  
  5. const LRU = require('lru-cache') // 緩存  
  6. const { createBundleRenderer } = require('vue-server-renderer')  
  7. const favicon = require('serve-favicon')  
  8. const resolve = file => path.resolve(__dirname, file)  
  9. const app = express()  
  10. // 開啟 gzip 壓縮 https://github.com/woai3c/node-blog/blob/master/doc/optimize.md  
  11. const compression = require('compression')  
  12. app.use(compression())  
  13. // 設置 favicon  
  14. app.use(favicon(resolve('../public/favicon.ico')))  
  15. // 新版本 需要加 new,舊版本不用  
  16. const microCache = new LRU({  
  17.     max: 100,  
  18.     maxAge: 60 * 60 * 24 * 1000 // 重要提示:緩存資源將在 1 天后過期。  
  19. })  
  20. const serve = (path) => {  
  21.     return express.static(resolve(path), {  
  22.         maxAge: 1000 * 60 * 60 * 24 * 30  
  23.     }) 
  24.  
  25. app.use('/dist', serve('../dist', true))  
  26. function createRenderer(bundle, options) {  
  27.     return createBundleRenderer(  
  28.         bundle,  
  29.         Object.assign(options, {  
  30.             basedir: resolve('../dist'),  
  31.             runInNewContext: false  
  32.         })  
  33.     )  
  34. function render(req, res) {  
  35.     const hit = microCache.get(req.url)  
  36.     if (hit) {  
  37.         console.log('Response from cache')  
  38.         return res.end(hit)  
  39.     }  
  40.     res.setHeader('Content-Type', 'text/html')  
  41.     const handleError = err => {  
  42.         if (err.url) {  
  43.             res.redirect(err.url)  
  44.         } else if (err.code === 404) {  
  45.             res.status(404).send('404 | Page Not Found')  
  46.         } else {  
  47.             res.status(500).send('500 | Internal Server Error~')  
  48.             console.log(err)  
  49.         }  
  50.     }  
  51.     const context = {  
  52.         title: 'SSR 測試', // default title  
  53.         url: req.url 
  54.     }  
  55.     renderer.renderToString(context, (err, html) => {  
  56.         if (err) {  
  57.             return handleError(err)  
  58.         }  
  59.         microCache.set(req.url, html)  
  60.         res.send(html)  
  61.     })  
  62.  
  63. const templatePath = resolve('../public/index.template.html')  
  64. const template = fs.readFileSync(templatePath, 'utf-8')  
  65. const bundle = require('../dist/vue-SSR-server-bundle.json')  
  66. const clientManifest = require('../dist/vue-SSR-client-manifest.json') // 將js文件注入到頁面中  
  67. const renderer = createRenderer(bundle, {  
  68.     template,  
  69.     clientManifest  
  70. })  
  71. const port = 8080  
  72. app.listen(port, () => {  
  73.     console.log(`server started at localhost:${ port }`)  
  74. })  
  75. setApi(app)  
  76. app.get('*', render) 

從代碼中可以看到,當首次加載頁面時,需要調用 createBundleRenderer() 生成一個 renderer,它的參數是打包生成的 vue-SSR-server-bundle.json 和 vue-SSR-client-manifest.json 文件。當返回 HTML 文件后,頁面將會被客戶端接管。

在文件的最后有一行代碼 app.get('*', render),它表示所有匹配不到的請求都交給它處理。所以如果你寫了 ajax 請求處理函數必須放在前面,就像下面這樣: 

  1. app.get('/fetchData', (req, res) => { ... })  
  2. app.post('/changeData', (req, res) => { ... })  
  3. app.get('*', render) 

否則你的頁面會打不開。

開發環境

開發環境的服務器配置和生產環境沒什么不同,區別在于開發環境下的服務器有熱更新。

一般用 webpack 進行開發時,簡單的配置一下 dev server 參數就可以使用熱更新了,但是 SSR 項目需要自己配置。

由于 SSR 開發環境服務器的配置文件 setup-dev-server.js 代碼太多,我對其進行簡化后,大致代碼如下: 

  1. // dev-server.js  
  2. const express = require('express')  
  3. const webpack = require('webpack')  
  4. const webpackConfig = require('../build/webpack.dev') // 獲取 webpack 配置文件  
  5. const compiler = webpack(webpackConfig)  
  6. const app = express()  
  7. app.use(require('webpack-hot-middleware')(compiler))  
  8. app.use(require('webpack-dev-middleware')(compiler, {  
  9.     noInfo: true,  
  10.     stats: {  
  11.         colors: true  
  12.     }  
  13. })) 

同時需要在 webpack 的入口文件加上這一行代碼 webpack-hot-middleware/client?reload=true。 

  1. // webpack.dev.js  
  2. const merge = require('webpack-merge')  
  3. const webpackBaseConfig = require('./webpack.base.config.js') // 這個配置和熱更新無關,可忽略  
  4. module.exports = merge(webpackBaseConfig, {  
  5.     mode: 'development',  
  6.     entry: {  
  7.         app: ['webpack-hot-middleware/client?reload=true' , './client/main.js'] // 開啟熱模塊更新  
  8.     },  
  9.     plugins: [new webpack.HotModuleReplacementPlugin()]  
  10. }) 

然后使用 node dev-server.js 來開啟前端代碼熱更新。

熱更新主要使用了兩個插件:webpack-dev-middleware 和 webpack-hot-middleware。顧名思義,看名稱就知道它們的作用,

webpack-dev-middleware 的作用是生成一個與 webpack 的 compiler 綁定的中間件,然后在 express 啟動的 app 中調用這個中間件。

這個中間件的作用呢,簡單總結為以下三點:通過watch mode,監聽資源的變更,然后自動打包; 快速編譯,走內存;返回中間件,支持express 的 use 格式。

webpack-hot-middleware 插件的作用就是熱更新,它需要配合 HotModuleReplacementPlugin 和 webpack-dev-middleware 一起使用。

打包文件 vue-SSR-client-manifest.json 和 vue-SSR-server-bundle.json

webpack 需要對源碼打包兩次,一次是為客戶端環境打包的,一次是為服務端環境打包的。

為客戶端環境打包的文件,和以前我們打包的資源一樣,不過多出了一個 vue-SSR-client-manifest.json 文件。服務端環境打包只輸出一個 vue-SSR-server-bundle.json 文件。

vue-SSR-client-manifest.json 包含了客戶端環境所需的資源名稱:

從上圖中可以看到有三個關鍵詞:

  1.  all,表示這是打包的所有資源。
  2.  initial,表示首頁加載必須的資源。
  3.  async,表示需要異步加載的資源。

vue-SSR-server-bundle.json 文件:   

  1. entry, 服務端入口文件。
  2. files,服務端依賴的資源。

填坑記錄

1. [vue-router] failed to resolve async component default: referenceerror: window is not defined

由于在一些文件或第三方文件中可能會用到 window 對象,并且 node 中不存在 window 對象,所以會報錯。

此時可在 src/app.js 文件加上以下代碼進行判斷: 

  1. // 在 app.js 文件添加上這段代碼,對環境進行判斷  
  2. if (typeof window === 'undefined') {  
  3.     global.window = {}  

2. mini-css-extract-plugin 插件造成 ReferenceError: document is not defined

使用 mini-css-extract-plugin 插件打包的的 server bundle, 會使用到 document。由于 node 環境中不存在 document 對象,所以報錯。

解決方案:樣式相關的 loader 不要放在 webpack.base.config.js 文件,將其分拆到 webpack.client.config.js 和 webpack.client.server.js 文件。其中 mini-css-extract-plugin 插件要放在 webpack.client.config.js 文件配置。

base 

  1. module: {  
  2.     rules: [  
  3.         {  
  4.             test: /\.vue$/,  
  5.             loader: 'vue-loader',  
  6.             options: {  
  7.                 compilerOptions: {  
  8.                     preserveWhitespace: false  
  9.                 }  
  10.             }  
  11.         },  
  12.         {  
  13.             test: /\.js$/,  
  14.             loader: 'babel-loader',  
  15.             exclude: /node_modules/  
  16.         },  
  17.         {  
  18.             test: /\.(png|svg|jpg|gif|ico)$/,  
  19.             use: ['file-loader']  
  20.         },  
  21.         {  
  22.             test: /\.(woff|eot|ttf)\??.*$/,  
  23.             loader: 'url-loader?name=fonts/[name].[md5:hash:hex:7].[ext]'  
  24.         },  
  25.     ]  

client 

  1. module: {  
  2.     rules: [  
  3.         {  
  4.             test: /\.css$/,  
  5.             use: [  
  6.                 {  
  7.                     loader: MiniCssExtractPlugin.loader,  
  8.                     options: {  
  9.                         // 解決 export 'default' (imported as 'mod') was not found  
  10.                         esModule: false,  
  11.                     },  
  12.                 },  
  13.                 'css-loader'  
  14.             ] 
  15.          }  
  16.     ]  

server 

  1. module: {  
  2.     rules: [  
  3.         {  
  4.             test: /\.css$/,  
  5.             use: [  
  6.                 'vue-style-loader',  
  7.                 'css-loader' 
  8.              ]  
  9.         }  
  10.     ]  

3. 開發環境下跳轉頁面樣式不生效,但生產環境正常。

由于開發環境使用的是 memory-fs 插件,打包文件是放在內存中的。如果此時 dist 文件夾有剛才打包留下的資源,就會使用 dist 文件夾中的資源,而不是內存中的資源。并且開發環境和打包環境生成的資源名稱是不一樣的,所以就造成了這個 BUG。

解決方法是執行 npm run dev 時,刪除 dist 文件夾。所以要在 npm run dev 對應的腳本中加上 rimraf dist。

  1. "dev": "rimraf dist && node ./server/dev-server.js --mode development", 

4. [vue-router] Failed to resolve async component default: ReferenceError: document is not defined

不要在有可能使用到服務端渲染的頁面訪問 DOM,如果有這種操作請放在 mounted() 鉤子函數里。

如果你引入的數據或者接口有訪問 DOM 的操作也會報這種錯,在這種情況下可以使用 require()。因為 require() 是運行時加載的,所以可以這樣使用: 

  1. <script>  
  2. // 原來報錯的操作,這個接口有 DOM 操作,所以這樣使用的時候在服務端會報錯。 
  3. import { fetchArticles } from '@/api/client'  
  4. export default {  
  5.   methods: {  
  6.     getAppointArticles() {  
  7.       fetchArticles({  
  8.         tags: this.tags,  
  9.         pageSize: this.pageSize,  
  10.         pageIndex: this.pageIndex,  
  11.       })  
  12.       .then(res => {  
  13.           this.$store.commit('setArticles', res)  
  14.       })  
  15.     },  
  16.   }  
  17.  
  18. </script> 

修改后: 

  1. <script>  
  2. // 先定義一個外部變量,在 mounted() 鉤子里賦值  
  3. let fetchArticles  
  4. export default {  
  5.   mounted() {  
  6.     // 由于服務端渲染不會有 mounted() 鉤子,所以在這里可以保證是在客戶端的情況下引入接口  
  7.       fetchArticles = require('@/api/client').fetchArticles  
  8.   },  
  9.   methods: {  
  10.     getAppointArticles() {  
  11.       fetchArticles({  
  12.         tags: this.tags,  
  13.         pageSize: this.pageSize,  
  14.         pageIndex: this.pageIndex,  
  15.       })  
  16.       .then(res => {  
  17.           this.$store.commit('setArticles', res)  
  18.       })  
  19.     },  
  20.   } 
  21.   
  22. </script> 

修改后可以正常使用。

5. 開發環境下,開啟服務器后無任何反應,也沒見控制臺輸出報錯信息。

這個坑其實是有報錯信息的,但是沒有輸出,導致以為沒有錯誤。

在 setup-dev-server.js 文件中有一行代碼 if (stats.errors.length) return,如果有報錯就直接返回,不執行后續的操作。導致服務器沒任何反應,所以我們可以在這打一個 console.log 語句,打印報錯信息。

小結

這個 DEMO 是基于官方 DEMO vue-hackernews-2.0 改造的。不過官方 DEMO 發表于 4 年前,最近修改時間是 2 年前,很多選項參數已經過時了。并且官方 DEMO 需要翻墻才能使用。所以我在此基礎上對其進行了改造,改造后的 DEMO 放在 SSR-demo" _fcksavedurl="https://github.com/woai3c/vue-SSR-demo">Github 上,它是一個比較完善的 DEMO,可以在此基礎上進行二次開發。

如果你不僅僅滿足于一個 DEMO,建議看一看我的個人博客項目,它原來是客戶端渲染的項目,后來重構為服務端渲染,絕對實戰。 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2022-03-14 14:47:21

HarmonyOS操作系統鴻蒙

2010-01-20 10:44:01

linux DHCP服務器

2011-03-25 12:45:49

Oracle SOA

2010-07-06 09:38:51

搭建私有云

2022-01-04 08:52:14

博客網站Linux 系統開源

2010-07-06 09:43:57

搭建私有云

2021-07-14 09:00:00

JavaFX開發應用

2011-05-03 15:59:00

黑盒打印機

2011-01-10 14:41:26

2025-05-07 00:31:30

2019-08-26 09:25:23

RedisJavaLinux

2010-10-29 14:04:49

2020-06-17 07:35:57

虛擬機部署微服務

2024-01-26 08:16:48

Exporter開源cprobe

2021-05-27 11:10:42

Python開源包代碼

2011-02-22 17:42:26

2025-02-26 07:40:25

運營分析體系運營策略

2025-05-27 08:05:00

Spring開發服務調用

2023-04-26 12:46:43

DockerSpringKubernetes

2022-12-07 08:42:35

點贊
收藏

51CTO技術棧公眾號

欧美一区二视频在线免费观看| 国产精品超碰| 97精品国产| 亚洲一区在线视频| 国产成人一区二区| 97中文字幕在线观看| 伊人成年综合网| 97品白浆高清久久久久久| 国产日韩欧美制服另类| 欧美—级高清免费播放| 超碰人人草人人| 免费动漫网站在线观看| jizz性欧美23| 日韩欧美国产免费播放| 国产精品视频500部| 婷婷伊人五月天| 电影亚洲一区| 久久精品在线观看| 人体精品一二三区| 免费成人深夜夜行p站| 国产www视频在线观看| 精品一区免费av| 正在播放亚洲1区| 波多野结衣作品集| 四虎在线免费看| 亚洲二区免费| 欧美成人a视频| 久久国产精品免费观看| 国产精品福利电影| 国产精品99视频| 在线成人高清不卡| 免费观看中文字幕| 国产女人18毛片水真多| 欧美顶级大胆免费视频| 欧美精品一区二区三区很污很色的| 中文字幕第50页| 免费一级在线观看| 高清国产一区二区| 高清欧美性猛交| 欧美大喷水吹潮合集在线观看| free性护士videos欧美| 97久久精品人人澡人人爽| 9.1国产丝袜在线观看| 双性尿奴穿贞c带憋尿| 国产亚洲字幕| 亚洲高清免费在线| 精品999在线观看| 久草手机在线视频| 国产一区二区电影在线观看| 欧美性高清videossexo| 香蕉精品视频在线| www.com在线观看| 一本色道久久精品| 在线精品91av| 精品国产午夜福利在线观看| 久久www人成免费看片中文| 99久久精品国产一区| 成人av网站观看| 97久久久久久久| 波多野结衣一区| 欧美片在线播放| 超碰超碰超碰超碰超碰| 久草免费在线| 91在线看国产| 久久久久久久久久久久久久一区 | 国产精品100| 亚洲视频观看| 亚洲欧美日韩久久久久久| 色哟哟精品视频| 性欧美videos高清hd4k| 久久影院电视剧免费观看| 国产精品日本精品| 欧美日韩在线观看免费| 精品中文一区| 日韩色在线观看| 50路60路老熟妇啪啪| 欧美freesex| 亚洲一二三专区| 欧美一级视频免费看| 91精彩视频在线播放| 粉嫩aⅴ一区二区三区四区五区 | 国产精品露脸视频| 美女视频网站久久| 久久久久久久久久久免费| 日韩黄色精品视频| 亚洲xxx拳头交| 亚洲欧洲日产国产网站| 韩国三级丰满少妇高潮| 亚洲第一影院| 欧美日韩国产一二三| 99九九精品视频| 日本综合久久| 婷婷国产v国产偷v亚洲高清| 99re99热| 成人免费高清在线播放| 91天堂素人约啪| 国产精品久久久一区二区三区| 日本波多野结衣在线| 韩国午夜理伦三级不卡影院| 国产精品扒开腿做爽爽爽的视频| 久久久久99精品成人片毛片| 91精品观看| 久久免费视频在线观看| jizz国产在线| 老司机精品福利视频| 韩日精品中文字幕| 久久综合成人网| 久久综合九色综合欧美狠狠| 91九色精品视频| 亚洲一区 中文字幕| 三级欧美在线一区| 亚州欧美日韩中文视频| 波多野结衣大片| 国产成人啪午夜精品网站男同| 成人日韩av在线| 国产又粗又黄又爽的视频| 蜜桃视频在线一区| 好吊色欧美一区二区三区| 日本视频在线观看| 国产精品成人网| 午夜啪啪免费视频| xx欧美xxx| 日本福利一区二区| 中文字幕在线观看第三页| 亚洲天堂av资源在线观看| 日韩欧美www| 好吊操视频这里只有精品| 97一区二区国产好的精华液| 一区二区欧美在线| 中文字幕在线观看视频网站| 国产精品中文欧美| 国产欧美日韩视频一区二区三区| 欧美一级特黄aaaaaa| 国产精品毛片久久久久久久| 艳母动漫在线免费观看| 人人鲁人人莫人人爱精品| 亚洲福利视频网| 国产精品探花一区二区在线观看| 中文在线播放一区二区| 欧美激情精品久久久久| 日韩免费一级片| 日韩高清在线观看| 国产在线欧美日韩| 美女航空一级毛片在线播放| 777xxx欧美| 三级视频网站在线观看| 欧美日韩岛国| 欧美怡红院视频一区二区三区| 亚洲 欧美 日韩 在线| 麻豆精品在线观看| av日韩免费电影| 国产写真视频在线观看| 欧美色成人综合| 韩国黄色一级片| 欧美精品国产| 99一区二区三区| 色呦呦在线观看视频| 欧美电视剧在线看免费| 久久久久无码国产精品不卡| 国产成人午夜精品5599| 日本阿v视频在线观看| av在线不卡精品| 一区二区日韩精品| 亚洲图片小说视频| 最新成人av在线| 欧美日韩中文在线视频| 亚洲二区av| 日韩精品在线播放| 国产精品久久久精品四季影院| 性久久久久久| 97人人干人人| www.亚洲免费| 欧美人动与zoxxxx乱| 少妇人妻丰满做爰xxx| 国产很黄免费观看久久| 国产手机免费视频| 亚欧日韩另类中文欧美| 欧美精品一区三区| 国产精品传媒在线观看| 国产精品嫩草影院com| 天堂av.com| 亚洲狼人精品一区二区三区| 成人av在线网址| jizz性欧美10| 欧美色中文字幕| 老女人性淫交视频| 99视频精品在线| 成人免费看片视频在线观看| 成人爽a毛片免费啪啪红桃视频| 97在线观看视频国产| 麻豆导航在线观看| 538在线一区二区精品国产| 国产一级aa大片毛片| 久久先锋影音av| xx欧美撒尿嘘撒尿xx| 国产一区二区三区四区大秀| 国产一区二区丝袜| 91xxx在线观看| 欧美成人精品二区三区99精品| 久久国产黄色片| 成人免费在线视频| www.88av| 亚洲福利免费| 亚洲成人自拍| 素人啪啪色综合| 欧美国产中文字幕| 岛国视频免费在线观看| 欧美性精品220| 免费看一级大片| 久久日一线二线三线suv| 992kp免费看片| 首页国产欧美日韩丝袜| www.国产亚洲| va天堂va亚洲va影视| 日韩在线视频免费观看| 一区二区美女视频| 精品国产91久久久久久| 波多野结衣亚洲一区二区| 久久久久免费观看| 亚洲激情 欧美| 国产老妇另类xxxxx| 成人在线免费播放视频| 亚洲高清成人| 91成人在线视频观看| 波多野结衣在线观看一区二区| 国产有色视频色综合| 中文在线免费一区三区| 91精品免费视频| 国产福利一区二区三区在线播放| 最近2019中文字幕一页二页| 五月天激情婷婷| 欧美小视频在线| 国产亚洲欧美精品久久久久久| 国产精品女主播av| 人妻视频一区二区| 久久精品国产在热久久| aa在线免费观看| 波多野结衣一区| 欧美日韩高清在线一区| 精品国产黄a∨片高清在线| 麻豆乱码国产一区二区三区| 韩国av在线免费观看| 精品久久久精品| 国产亚洲精品久久久久久无几年桃| 亚洲色图丝袜美腿| 人妻体内射精一区二区三区| 久久久久国产精品一区三寸| 一区二区免费在线观看| 日本高清精品| 欧美中文字幕第一页| 2018av在线| 国内精品久久久久久影视8| 亚洲丝袜精品| 欧美另类高清videos| 日韩福利一区二区| 在线不卡的av| 国产伦理一区二区| 91精品国产福利在线观看 | 亚洲va天堂va欧美ⅴa在线| 精品国产1区2区| 在线观看亚洲天堂| 日韩欧美成人免费视频| 国产又粗又猛又爽又| 欧美无乱码久久久免费午夜一区| www.av88| 制服视频三区第一页精品| 99国产成人精品| 欧美xxxx在线观看| 香蕉视频黄在线观看| 亚洲天堂免费在线| 99精品人妻无码专区在线视频区| 91精品国产综合久久国产大片 | 妞干网免费在线视频| 国产一区二区三区日韩欧美| 91露出在线| 欧美日韩国产91| 91超碰免费在线| 国产v综合v亚洲欧美久久| 九九九精品视频| 亚洲伊人久久综合| www.精品国产| 亚洲一区亚洲二区亚洲三区| 国产精品超碰| 日韩欧美一区二区三区四区| 欧美成a人免费观看久久| 91色琪琪电影亚洲精品久久| 亚洲综合影院| 九色91国产| 日韩国产欧美一区二区| 免费亚洲精品视频| 精品综合久久88少妇激情| 亚洲bt天天射| 少妇高潮一区二区三区| 亚洲午夜激情| 亚洲国产一区二区三区a毛片| 日本一极黄色片| 国产精选一区二区三区| 亚洲人人夜夜澡人人爽| 18欧美亚洲精品| 好吊妞视频一区二区三区| 欧美日韩一区成人| 日韩专区第一页| 亚洲第一精品夜夜躁人人爽| 男人天堂亚洲二区| 欧美日韩福利电影| av在线播放一区| 国产一区二区高清视频| 久久亚洲国产| 久久精品国产精品亚洲色婷婷| 久久99精品久久久久婷婷| 超碰男人的天堂| 亚洲欧美二区三区| 69av.com| 精品视频1区2区3区| 天天干天天草天天射| 久久影院中文字幕| 日本成人福利| 欧美精品亚洲| 最新成人av网站| 欧美体内she精高潮| 国产精品午夜在线观看| 毛片视频免费播放| 亚洲人亚洲人成电影网站色| 影音先锋在线国产| 精品国产区一区| 好吊日视频在线观看| 国产精品高清免费在线观看| 久9re热视频这里只有精品| 国产对白在线播放| 日韩av一区二区三区四区| 色婷婷.com| 国产欧美一区二区精品婷婷| 任我爽在线视频| 一区二区三区国产精品| 日韩久久精品视频| 日韩欧美一区在线观看| 精品孕妇一区二区三区| 国产在线久久久| 日韩一区亚洲二区| 三年中国国语在线播放免费| 国产午夜精品一区二区| 亚洲欧美综合另类| 亚洲人成电影在线播放| 天堂中文在线播放| 国产美女被下药99| 91麻豆精品激情在线观看最新 | 今天的高清视频免费播放成人| 成人av毛片在线观看| 国产成人精品综合在线观看| 国产精品成人69xxx免费视频| 欧美日本国产视频| 免费看a在线观看| 国内久久久精品| 给我免费播放日韩视频| 久久国产午夜精品理论片最新版本| 国产激情一区二区三区桃花岛亚洲| 国产福利视频网站| 91麻豆精品国产自产在线观看一区| 麻豆传媒视频在线| 91系列在线观看| 国产精品99免费看| 国产大尺度视频| 午夜欧美视频在线观看| 亚洲无码精品在线播放| 中文在线不卡视频| 91精品国产一区二区在线观看| 中文字幕一区二区中文字幕| 国产一区二区精品久久| 精品久久久久久中文字幕人妻最新| 国产精品国产a| 国产女人18毛片18精品| 欧美高清在线视频观看不卡| 免费看久久久| 亚洲一区二区蜜桃| 中文字幕视频一区| 亚洲精品久久久久久久久久| 亚州成人av在线| 成人在线免费视频观看| 色网站在线视频| 午夜精品一区二区三区电影天堂| 欧美精品少妇| 成人激情视频网| 亚洲午夜黄色| 亚欧洲乱码视频| 精品久久久久久中文字幕一区奶水| 爽爽视频在线观看| 国产狼人综合免费视频| 国产精品theporn| 女女互磨互喷水高潮les呻吟| 亚洲成年人影院| 国产三级电影在线观看| 91啪国产在线| 亚洲欧美成人综合| 国产精品国产精品88| 亚洲精品乱码久久久久久金桔影视 | 日本免费高清不卡| 国产伦理精品不卡| 国产欧美一区二区三区在线看蜜臂| 最新91在线视频| 国产精品久av福利在线观看|