精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

亚洲精品久久久久久无码色欲四季 | 久久久99久久| 日韩av片电影专区| 国产精品视频一区二区在线观看| 亚洲高清在线一区| 天天综合色天天| 青青草原成人| 国产三级自拍视频| 亚洲一区二区三区高清| 亚洲视频在线播放| 波多野结衣在线免费观看| 国产理论在线| 国产精品午夜电影| 国产精品国产一区二区| 五月天中文字幕| 亚洲性人人天天夜夜摸| 永久免费毛片在线播放不卡| 能看毛片的网站| 五月激情久久| 亚洲在线免费播放| 亚洲欧美日韩不卡一区二区三区| 亚洲国产av一区二区| 日产国产欧美视频一区精品| 色综合久久精品亚洲国产| 中文字幕在线观看免费高清| 91成人在线精品视频| 欧美日韩成人综合天天影院| 国产精品专区在线| gogogogo高清视频在线| 久久久国产精品不卡| 国产日韩二区| 国产av一区二区三区| 免费在线观看一区二区三区| 国产91|九色| 国产在线视频你懂的| 欧美gayvideo| 在线视频亚洲欧美| 3d动漫精品啪啪一区二区下载| 亚洲免费一区三区| 在线电影一区二区三区| 天天干天天干天天干天天干天天干| 91九色国产在线播放| 亚洲精品国产精品乱码不99 | 国产女同无遮挡互慰高潮91| 国产精品久久一区二区三区| 五月天丁香花婷婷| 黑人巨大精品| 欧美精品第一区| 精品福利一区二区三区| 午夜av中文字幕| jizz免费一区二区三区| 在线视频综合导航| 久热免费在线观看| 欧美一区国产| 色先锋久久av资源部| 777久久久精品一区二区三区| 超免费在线视频| 亚洲午夜羞羞片| 欧美中文字幕在线观看视频| 污影院在线观看| 尤物av一区二区| 欧美国产综合在线| 91九色美女在线视频| 精品久久久免费| 黑森林福利视频导航| 自拍视频在线看| 日本高清成人免费播放| 亚洲免费av一区二区三区| 国产精品扒开腿做爽爽爽视频软件| 欧美日韩国产一区二区| 麻豆传传媒久久久爱| 99久久伊人| 欧美日韩成人综合天天影院 | 亚洲国产精品va在线观看黑人| www男人天堂| 天堂网av成人| 国产一区二区三区毛片| 中文国语毛片高清视频| 欧美在线黄色| 97免费中文视频在线观看| av资源免费观看| 日韩av一区二区三区| 成人久久久久爱| 你懂的网站在线| 久久久久国产成人精品亚洲午夜| 亚洲国产高清国产精品| a毛片在线播放| 欧美日韩国产激情| 亚洲成人天堂网| 一区二区中文字幕在线观看| 日韩精品日韩在线观看| 女人裸体性做爰全过| 欧美成人日本| 日本欧美一级片| 国产三级在线观看视频| 不卡av在线网| 一本久道久久综合| 国产丝袜在线观看视频| 91黄色免费观看| 免费黄视频在线观看| 色爱综合av| 日韩在线观看免费高清| 日韩欧美中文字幕一区二区| 日本美女一区二区| 国产精品国产精品国产专区不卡| 色婷婷av一区二区三区之红樱桃 | 日韩欧美在线观看强乱免费| 超碰在线免费播放| 色婷婷亚洲婷婷| 色婷婷狠狠18禁久久| 成人羞羞在线观看网站| 久久久久久久久久久免费 | 午夜欧美性电影| a级片在线免费观看| 欧美日韩一区二区三区四区 | 97欧美精品一区二区三区| 一级久久久久久久| 2014亚洲片线观看视频免费| 日本三级中文字幕在线观看| 中文字幕av一区二区三区佐山爱| 精品国产3级a| 国产精品视频一区二区在线观看| 国产欧美激情| 俄罗斯精品一区二区| 2021av在线| 一本久道久久综合中文字幕| 日本在线不卡一区二区| 亚洲一区色图| 国产欧美日韩专区发布| 国产在线91| 狠狠躁夜夜躁人人爽超碰91| 99免费观看视频| 欧美jizzhd精品欧美巨大免费| 国产精品吹潮在线观看| 精品电影在线| 一本色道a无线码一区v| 亚洲精品中文字幕在线播放| 亚洲视频福利| 99热最新在线| 日韩av大片| 久久影院中文字幕| 最新国产中文字幕| 久久精品视频在线看| 欧美 日韩 国产 高清| 国产成人一二| 午夜精品美女自拍福到在线| 精品久久久久中文慕人妻| 亚洲欧美偷拍三级| 国产chinesehd精品露脸| 亚洲一区在线| 国产精成人品localhost| 久草在线视频资源| 欧美精品一区二区三区蜜臀| 国产一国产二国产三| 成人免费视频视频| 久久久性生活视频| 日韩mv欧美mv国产网站| 91精品国产成人www| 午夜一区在线观看| 色天天综合久久久久综合片| 精品无码人妻一区二区免费蜜桃| 青青国产91久久久久久| 亚洲春色在线| 亚瑟国产精品| 免费av一区二区| a级片免费观看| 夜夜揉揉日日人人青青一国产精品| 手机在线视频一区| 欧美日韩在线网站| 国产精品久久久久av免费| 成人高清免费在线播放| 欧洲国内综合视频| 日本裸体美女视频| 韩国三级在线一区| www.xxx麻豆| 久久九九热re6这里有精品| 久久久久久久久久久免费 | 爱爱的免费视频| 国产日韩欧美一区| 欧美日韩一区综合| 成人精品国产亚洲| 久久这里只有精品视频首页| 曰批又黄又爽免费视频| 亚洲人123区| 国产伦理在线观看| 亚洲国产三级| 日本黑人久久| 亚洲国产一区二区三区网站| 午夜免费久久久久| 国产精品久久一区二区三区不卡| 欧美自拍丝袜亚洲| 99精品中文字幕| 成人性生交大片| 国产福利视频在线播放| 日韩精品久久久久久久电影99爱| 亚洲va久久久噜噜噜久久天堂| 日韩伦理精品| 日韩在线视频观看正片免费网站| 精品人妻一区二区三区四区不卡 | 18黄暴禁片在线观看| 美女精品一区最新中文字幕一区二区三区 | 日本电影在线观看网站| 日韩欧美高清一区| 亚洲精品无码久久久久| 一区二区三区91| 男生草女生视频| 国产一区二区三区黄视频| 成人观看免费完整观看| 久久精品免费一区二区三区| 国产一区二区久久久| 国产综合色激情| 久久在线精品视频| 国产精品一区在线看| 日韩一级黄色片| 午夜精品久久久久久久蜜桃| 亚洲免费观看高清在线观看| 国产精品久久免费观看| 福利一区在线观看| 中文字幕国内自拍| 99亚洲一区二区| 亚洲人一区二区| 国产精品片aa在线观看| 国产精品毛片va一区二区三区| 福利视频一区| 欧美国产日韩一区二区在线观看 | 麻豆成人久久精品二区三区小说| 东北少妇不带套对白| 久久日文中文字幕乱码| 久久国产精品高清| 亚洲**毛片| 成人免费观看a| 视频在线日韩| 8x海外华人永久免费日韩内陆视频| 日本在线观看高清完整版| 中文字幕欧美专区| 免费在线看v| 亚洲国产成人av在线| 黄色片一区二区| 精品视频在线免费| 国产免费一区二区三区四区五区 | 波多野结衣av一区二区全免费观看| 日韩成人精品一区| 美脚丝袜一区二区三区在线观看| 天堂精品久久久久| 91精品视频网站| 九九久久国产| 国产97在线|亚洲| 97成人资源| 秋霞av国产精品一区| 91白丝在线| 欧美与欧洲交xxxx免费观看| 久久香蕉av| 欧美疯狂做受xxxx高潮| a视频在线观看| 九九精品视频在线观看| 综合久久2019| 久久国产色av| 在线免费观看污| 欧美丰满老妇厨房牲生活 | 亚洲自拍电影| 欧美一区二区高清在线观看| 国产精品免费不| 日本精品二区| 日本不卡电影| 少妇精品久久久久久久久久| 狠狠色丁香婷婷综合影院| 欧美日韩在线播放一区二区| 蜜桃一区二区三区| 亚洲午夜精品一区二区三区| 欧美aaaa视频| 日韩一二区视频| 亚洲人体偷拍| 国内外成人激情视频| 免费国产自线拍一欧美视频| 六月激情综合网| 久久成人免费网站| 九九九久久久久久久| 国产91丝袜在线播放九色| 成熟妇人a片免费看网站| 久久久久久久久久美女| 成人性视频免费看| 亚洲女人****多毛耸耸8| 精品91久久久| 欧美最新大片在线看| 国产男女无套免费网站| 亚洲精品美女久久| 成人欧美一区| 国产亚洲欧美日韩美女| 二区在线观看| 一区二区三区动漫| 欧美wwww| 国产精品第2页| 日韩在线观看一区二区三区| 欧美高清视频一区| 久久麻豆精品| 蜜桃传媒一区二区三区| 妖精视频成人观看www| 欧美成人福利在线观看| 粉嫩蜜臀av国产精品网站| 无码熟妇人妻av| 一区二区三区在线观看欧美| 东京热一区二区三区四区| 欧美情侣在线播放| 天天操天天干天天爽| 日韩在线小视频| 国产在线88av| 成人a在线视频| 少妇精品久久久一区二区| 一区中文字幕在线观看| 亚洲高清成人| 色婷婷综合久久久久中文字幕 | 91成人免费观看| 深夜福利久久| 99久热在线精品视频| 久久国产精品区| 国产极品一区二区| 日韩一区中文字幕| 无码日韩精品一区二区| 亚洲精品在线电影| 无遮挡动作视频在线观看免费入口| 久久久久五月天| 精品国产乱码一区二区三区| 欧美高清一区二区| 在线国产精品一区| 狠狠操狠狠干视频| 中文字幕 久热精品 视频在线| 日本学生初尝黑人巨免费视频| 欧美日韩在线播放一区| 日本一二三区在线视频| 久久精品国产成人精品| 欧美电影网站| 国模一区二区三区私拍视频| 欧美涩涩视频| 99精品999| 中文字幕免费一区| 日韩人妻无码一区二区三区99| 精品剧情v国产在线观看在线| 午夜小视频在线| 国产成人精品免高潮费视频| 三级小说欧洲区亚洲区| 草草视频在线免费观看| 国产剧情在线观看一区二区| 国产一区二区视频在线观看免费| 欧美日韩精品一区二区三区| 国产香蕉视频在线看| 久久久午夜视频| 国产欧美自拍一区| 日本a级片在线播放| 精品无人码麻豆乱码1区2区| 男人的午夜天堂| 欧美日本国产视频| 无遮挡动作视频在线观看免费入口| 国产精品视频中文字幕91| 精品国产视频| 欧美一级特黄a| 亚洲天堂成人在线观看| 久久久久久久久久成人| 国产一区二区三区在线视频 | 欧美精品大片| 国产欧美精品一二三| 一区二区三区四区在线免费观看 | 欧美国产视频在线观看| 奶水喷射视频一区| 国产中年熟女高潮大集合| 色综合久久中文字幕综合网| 天天射,天天干| 九九热r在线视频精品| 精品国产影院| 免费在线a视频| 久久精品一区二区| 91亚洲国产成人精品一区| 久久韩剧网电视剧| 精品欧美视频| 97超碰在线人人| 91视频观看视频| 姑娘第5集在线观看免费好剧| 不卡毛片在线看| 亚洲一区电影| 97国产在线播放| 国产精品麻豆一区二区| 国产男男gay网站| 久久全球大尺度高清视频| 国产免费av一区二区三区| 97超碰成人在线| 亚洲激情在线激情| 久久久久久久久亚洲精品| 国产精品入口尤物| 久久精品高清| 中文字幕三级电影| 91福利在线观看| 永久免费av片在线观看全网站| wwwxx欧美| 久久久久久9| 久久久久久久久久97| 日韩精品极品在线观看播放免费视频| 三级成人在线| av在线免费观看国产| 久久久精品2019中文字幕之3| 国产一区二区三区四区视频 | 久久深夜福利|