自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

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

本文將分成以下兩部分:

  1.  簡(jiǎn)述 Vue SSR 過(guò)程
  2.  從零開(kāi)始搭建 SSR 項(xiàng)目

好了,下面開(kāi)始正文。

簡(jiǎn)述 Vue SSR 過(guò)程

客戶端渲染過(guò)程

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

服務(wù)端渲染過(guò)程

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

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

從上述兩個(gè)過(guò)程中,可以看出,區(qū)別就在于第二步??蛻舳虽秩镜木W(wǎng)站會(huì)直接返回 HTML 文件,而服務(wù)端渲染的網(wǎng)站則會(huì)渲染完頁(yè)面再返回這個(gè) HTML 文件。

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

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

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

客戶端接管頁(yè)面

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

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

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

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

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

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

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

小結(jié)

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

從零開(kāi)始搭建 SSR 項(xiàng)目

配置 weback

webpack 配置文件共有 3 個(gè):

  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 同屬一個(gè) chunk 中的文件修改了,文件名會(huì)發(fā)生變化   
  14.         // contenthash 只有文件自己的內(nèi)容變化了,文件名才會(huì)變化  
  15.         filename: '[name].[contenthash].js',  
  16.         // 此選項(xiàng)給打包后的非入口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` 會(huì)使用到 document。  
  29.         // 由于 node 環(huán)境中不存在 document 對(duì)象,所以報(bào)錯(cuò)。  
  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ǔ)配置文件比較簡(jiǎn)單,output 屬性的意思是打包時(shí)根據(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.         // 開(kāi)啟 gzip 壓縮 https://github.com/woai3c/node-blog/blob/master/doc/optimize.md  
  25.         new CompressionPlugin(),  
  26.         // 該插件會(huì)根據(jù)模塊的相對(duì)路徑生成一個(gè)四位數(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 語(yǔ)法  
  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 屬性是打包時(shí)分割代碼用的。它的作用是將第三方庫(kù)都打包在一起。

其他插件作用:

  1.  MiniCssExtractPlugin 插件, 將 css 提取出來(lái)單獨(dú)打包。
  2.  CssMinimizerPlugin 插件,壓縮 css。
  3.  CompressionPlugin 插件,將資源壓縮成 gzip 格式(大大提升傳輸效率)。另外還需要在 node 服務(wù)器上引入 compression 插件配合使用。
  4.  WebpackBar 插件,打包時(shí)顯示進(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)行時(shí)(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ù)端打包和客戶端不同,它將所有文件一起打包成一個(gè)文件 server-bundle.js。同時(shí)解析 css 需要使用 vue-style-loader,這一點(diǎn)在官方指南中有說(shuō)明:

配置服務(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. // 開(kāi)啟 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 天后過(guò)期。  
  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 測(cè)試', // 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文件注入到頁(yè)面中  
  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)首次加載頁(yè)面時(shí),需要調(diào)用 createBundleRenderer() 生成一個(gè) renderer,它的參數(shù)是打包生成的 vue-SSR-server-bundle.json 和 vue-SSR-client-manifest.json 文件。當(dāng)返回 HTML 文件后,頁(yè)面將會(huì)被客戶端接管。

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

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

否則你的頁(yè)面會(huì)打不開(kāi)。

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

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

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

由于 SSR 開(kāi)發(fā)環(huán)境服務(wù)器的配置文件 setup-dev-server.js 代碼太多,我對(duì)其進(jìn)行簡(jiǎ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. })) 

同時(shí)需要在 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') // 這個(gè)配置和熱更新無(wú)關(guān),可忽略  
  4. module.exports = merge(webpackBaseConfig, {  
  5.     mode: 'development',  
  6.     entry: {  
  7.         app: ['webpack-hot-middleware/client?reload=true' , './client/main.js'] // 開(kāi)啟熱模塊更新  
  8.     },  
  9.     plugins: [new webpack.HotModuleReplacementPlugin()]  
  10. }) 

然后使用 node dev-server.js 來(lái)開(kāi)啟前端代碼熱更新。

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

webpack-dev-middleware 的作用是生成一個(gè)與 webpack 的 compiler 綁定的中間件,然后在 express 啟動(dòng)的 app 中調(diào)用這個(gè)中間件。

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

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

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

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

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

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

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

  1.  all,表示這是打包的所有資源。
  2.  initial,表示首頁(yè)加載必須的資源。
  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

由于在一些文件或第三方文件中可能會(huì)用到 window 對(duì)象,并且 node 中不存在 window 對(duì)象,所以會(huì)報(bào)錯(cuò)。

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

  1. // 在 app.js 文件添加上這段代碼,對(duì)環(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, 會(huì)使用到 document。由于 node 環(huán)境中不存在 document 對(duì)象,所以報(bào)錯(cuò)。

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

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

解決方法是執(zhí)行 npm run dev 時(shí),刪除 dist 文件夾。所以要在 npm run dev 對(duì)應(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ù)端渲染的頁(yè)面訪問(wèn) DOM,如果有這種操作請(qǐng)放在 mounted() 鉤子函數(shù)里。

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

  1. <script>  
  2. // 原來(lái)報(bào)錯(cuò)的操作,這個(gè)接口有 DOM 操作,所以這樣使用的時(shí)候在服務(wù)端會(huì)報(bào)錯(cuò)。 
  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. // 先定義一個(gè)外部變量,在 mounted() 鉤子里賦值  
  3. let fetchArticles  
  4. export default {  
  5.   mounted() {  
  6.     // 由于服務(wù)端渲染不會(huì)有 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. 開(kāi)發(fā)環(huán)境下,開(kāi)啟服務(wù)器后無(wú)任何反應(yīng),也沒(méi)見(jiàn)控制臺(tái)輸出報(bào)錯(cuò)信息。

這個(gè)坑其實(shí)是有報(bào)錯(cuò)信息的,但是沒(méi)有輸出,導(dǎo)致以為沒(méi)有錯(cuò)誤。

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

小結(jié)

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

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

 

責(zé)任編輯:龐桂玉 來(lái)源: 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:38:51

搭建私有云

2010-07-06 09:43:57

搭建私有云

2022-01-04 08:52:14

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

2019-08-26 09:25:23

RedisJavaLinux

2010-10-29 14:04:49

2020-06-17 07:35:57

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

2021-07-14 09:00:00

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

2011-01-10 14:41:26

2011-05-03 15:59:00

黑盒打印機(jī)

2024-01-26 08:16:48

Exporter開(kāi)源cprobe

2021-05-27 11:10:42

Python開(kāi)源包代碼

2025-02-26 07:40:25

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

2011-02-22 17:42:26

2022-01-08 20:04:20

攔截系統(tǒng)調(diào)用

2023-04-26 12:46:43

DockerSpringKubernetes

2022-12-07 08:42:35

2022-07-27 08:16:22

搜索引擎Lucene
點(diǎn)贊
收藏

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