成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

手把手教你搭建Vue服務(wù)端渲染項目

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

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

本文將分成以下兩部分:

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

好了,下面開始正文。

簡述 Vue SSR 過程

客戶端渲染過程

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

服務(wù)端渲染過程

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

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

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

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

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

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

客戶端接管頁面

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

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

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

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

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

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

如果客戶端構(gòu)建的虛擬 DOM 樹與服務(wù)器渲染返回的HTML結(jié)構(gòu)不一致,這時候,客戶端會請求一次服務(wù)器再渲染整個應(yīng)用程序,這使得SSR失效了,達(dá)不到服務(wù)端渲染的目的了

小結(jié)

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

從零開始搭建 SSR 項目

配置 weback

webpack 配置文件共有 3 個:

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

webpack.base.config.js 基礎(chǔ)配置文件 

  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 中的文件修改了,文件名會發(fā)生變化   
  14.         // contenthash 只有文件自己的內(nèi)容變化了,文件名才會變化  
  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 環(huán)境中不存在 document 對象,所以報錯。  
  30.         // 解決方案:樣式相關(guān)的 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()],  

基礎(chǔ)配置文件比較簡單,output 屬性的意思是打包時根據(jù)文件內(nèi)容生成文件名稱。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.         // 該插件會根據(jù)模塊的相對路徑生成一個四位數(shù)的hash作為模塊id, 用于生產(chǎn)環(huán)境。  
  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 提取出來單獨(dú)打包。
  2.  CssMinimizerPlugin 插件,壓縮 css。
  3.  CompressionPlugin 插件,將資源壓縮成 gzip 格式(大大提升傳輸效率)。另外還需要在 node 服務(wù)器上引入 compression 插件配合使用。
  4.  WebpackBar 插件,打包時顯示進(jìn)度條。

webpack.server.config.js 服務(wù)端配置文件 

  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 中,而是在運(yùn)行時(runtime)再去從外部獲取這些擴(kuò)展依賴  
  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. }) 

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

配置服務(wù)器

生產(chǎn)環(huán)境

pro-server.js 生產(chǎn)環(huán)境服務(wù)器配置文件 

  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. // 設(shè)置 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) 

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

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

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

否則你的頁面會打不開。

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

開發(fā)環(huán)境的服務(wù)器配置和生產(chǎn)環(huán)境沒什么不同,區(qū)別在于開發(fā)環(huán)境下的服務(wù)器有熱更新。

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

由于 SSR 開發(fā)環(huán)境服務(wù)器的配置文件 setup-dev-server.js 代碼太多,我對其進(jìn)行簡化后,大致代碼如下: 

  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') // 這個配置和熱更新無關(guān),可忽略  
  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 中調(diào)用這個中間件。

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

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

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

webpack 需要對源碼打包兩次,一次是為客戶端環(huán)境打包的,一次是為服務(wù)端環(huán)境打包的。

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

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

從上圖中可以看到有三個關(guān)鍵詞:

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

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

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

填坑記錄

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

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

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

  1. // 在 app.js 文件添加上這段代碼,對環(huán)境進(jìn)行判斷  
  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 環(huán)境中不存在 document 對象,所以報錯。

解決方案:樣式相關(guān)的 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. 開發(fā)環(huán)境下跳轉(zhuǎn)頁面樣式不生效,但生產(chǎn)環(huán)境正常。

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

解決方法是執(zhí)行 npm run dev 時,刪除 dist 文件夾。所以要在 npm run dev 對應(yīng)的腳本中加上 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

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

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

  1. <script>  
  2. // 原來報錯的操作,這個接口有 DOM 操作,所以這樣使用的時候在服務(wù)端會報錯。 
  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.     // 由于服務(wù)端渲染不會有 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. 開發(fā)環(huán)境下,開啟服務(wù)器后無任何反應(yīng),也沒見控制臺輸出報錯信息。

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

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

小結(jié)

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

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

 

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2022-03-14 14:47:21

HarmonyOS操作系統(tǒng)鴻蒙

2010-01-20 10:44:01

linux DHCP服務(wù)器

2011-03-25 12:45:49

Oracle SOA

2010-07-06 09:43:57

搭建私有云

2010-07-06 09:38:51

搭建私有云

2022-01-04 08:52:14

博客網(wǎng)站Linux 系統(tǒng)開源

2021-07-14 09:00:00

JavaFX開發(fā)應(yīng)用

2011-01-10 14:41:26

2025-05-07 00:31:30

2011-05-03 15:59:00

黑盒打印機(jī)

2019-08-26 09:25:23

RedisJavaLinux

2010-10-29 14:04:49

2020-06-17 07:35:57

虛擬機(jī)部署微服務(wù)

2021-05-27 11:10:42

Python開源包代碼

2024-01-26 08:16:48

Exporter開源cprobe

2011-02-22 17:42:26

2025-02-26 07:40:25

運(yùn)營分析體系運(yùn)營策略

2025-05-27 08:05:00

Spring開發(fā)服務(wù)調(diào)用

2023-04-26 12:46:43

DockerSpringKubernetes

2022-01-08 20:04:20

攔截系統(tǒng)調(diào)用
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 精品国产乱码久久久久久蜜退臀 | 精品国产乱码久久久久久图片 | 99在线精品视频 | 欧美日韩国产在线观看 | 国产欧美精品 | 成人a在线 | 毛片在线免费 | 国产精品欧美一区喷水 | 伊人电影院av | av男人的天堂在线 | 精品影院 | 日韩成人在线观看 | 看片地址| 午夜男人视频 | 国产伦精品一区二区三区照片91 | 午夜码电影 | 欧美精品在线观看 | 国产美女黄色 | 一区二区在线不卡 | 美女张开腿露出尿口 | 亚洲福利精品 | 欧美激情黄色 | 国产精品一卡二卡三卡 | 国产成人网 | 美女黄网站 | 热re99久久精品国99热观看 | a黄视频| 成人国产在线视频 | 久久久久国产视频 | 色婷婷影院| 毛片免费观看 | 亚洲视频www | av一二三四 | 99久久精品一区二区毛片吞精 | 久久精品亚洲一区二区三区浴池 | www.日本在线观看 | 国产精品久久久久久久模特 | 91福利影院 | 日韩精品久久久久 | 一区二区福利视频 | 久久y|