最新消息: 新版网站上线了!!!

vue ssr服务端渲染(小白解惑)

>³õѧssrÈë¿Ó

³õѧvue·þÎñ¶ËäÖȾÒÉ»ó·Ç³£¶à£¬ÎÒÃǴ󲿷ÖÇ°¶Ë¶¼ÊÇ°ë·³ö¼Ò£¬ÉÏÊÖ¶¼ÊÇÇ°ºó¶Ë·ÖÀ룬¶Ô·þÎñ¶Ë²¢²»Á˽⣬²»Ëµjava¡¢phpÓïÑÔÁË£¬Á¬node·þÎñ¶¼»¹Ã»¸ãÃ÷°×£¬Àí½â·þÎñ¶ËäÖȾ»¹ÊÇÓÐЩÀ§Äѵģ»

ÍøÉÏÓзdz£¶àµÄvue·þÎñäÖȾµÄÈëÃÅ°¸Àý£¬µ«¿´Á˺ܾ㬺ܶ࣬»¹ÊÇһͷÎíË®£¬¸ã²»Ã÷°×ÕâЩÎļþºÍ¹Ø¼ü×ÖµÄÁªÏµºÍÒâ˼£º

  • server.js
  • entrt-client.js
  • server-js
  • built-server-bundle.js
  • vue-ssr-server-bundle.json
  • vue-ssrclientmanifest.json
  • createBundleRenderer
  • clientManifest

ÕâƪÄÚÈݻᰴÕÕ »ù´¡·þÎñ¶ËäÖȾ--vueʵÀýäÖȾ--¼ÓÈëvueRouter--¼ÓÈëvueXµÄ˳ÐòÈë¿Ó£¬ºóÐøÓ¦¸Ã»¹ÓÐ--¿ª·¢Ä£Ê½--seoÓÅ»¯--²¿·ÖäÖȾ£¬ÕâÀïÏȲ»ÍÚÄÇô¶à¿ÓÁË£»

>»ù´¡·þÎñ¶ËäÖȾ

¹ËÃû˼Ò壬µÃÆô¸ö·þÎñ£º(½¨¸öÐÂÏîÄ¿£¬²»ÒªÓÃvue-cli)

//server.js
const express = require('express');
const chalk = require('chalk');//¼Ó¸öchalk¾ÍÊÇconsoleºÃ¿´µã¡£¡£

const server = express();

server.get('*', (req, res) => {
res.set('content-type', "text/html");
res.end(`
<!DOCTYPE html>
<html lang="en">
 <head><title>Hello</title></head>
 <body>ÄãºÃ</body>
</html>
`)
})

server.listen(8080,function(){
let ip = getIPAdress();
console.log(`·þÎñÆ÷¿ªÔÚ£ºhttp://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})

function getIPAdress(){//nodeϵÄosÄ£¿é¿ÉÒÔÄõ½Æô¶¯¸ÃÎļþµÄ·þÎñ¶ËµÄ²¿·ÖÐÅÏ¢
var interfaces = require('os').networkInterfaces();
for (var devName in interfaces) {
 var iface = interfaces[devName];
 for (var i = 0; i < iface.length; i++) {
  var alias = iface[i];
  if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
   return alias.address;
  }
 }
}
}

Æô¶¯ node server.js

ÔÙ¿´Ò³Ãæ Õý³££¬Õâ¾ÍÊÇ×î»ù´¡µÄ·þÎñ¶ËäÖȾ


Æäʵ¾ÍÊÇÒ»¸ögetÇëÇ󣬷µ»ØÒ»¸ö×Ö·û´®£¬ä¯ÀÀÆ÷ĬÈÏչʾ·µ»Ø½á¹û£»

È»¶ø¶ÔÓÚÕâ¸ö×Ö·û´®µÄ½âÎö»¹²»Ã÷È·£¬Ê²Ã´Òâ˼£¬±ÈÈ磺

È¥µôÕâ¾ä»°£¬Ò³Ãæ¾Í³ÉÁËÕâÑù£¬Ô­Òò²»É£¬×Ô¼º°Ù¶È

>¼ÓÈëvueʵÀý

Ìø¹ý¹ÙÍø˵µÄbuilt-server-bundle.jsÓ¦Óã¬Òâ˼¾ÍÊDz»ÓùÜÕâ¸öÎļþÁË£¬Ö»ÊÇÒ»¸ö¹ý¶ÉÎļþ£¬ÏîÄ¿ÖÐÒ²²»»áÓõ½¡£Ö±½ÓʹÓÃcreateBundleRenderer·½·¨£¬Ö±½ÓÓÃvue-ssr-server-bundle.json£»

¿´ÏÂÏÖÔÚµÄĿ¼½á¹¹£º


ÐÂÔöÁË5¸öÎļþ£»Óйؿͻ§¶ËµÄÅäÖÃentry-client.js²»ÊDZØÐëµÄ£¬ÕâÀïÏȲ»¹Ü£»

app.jsÊÇÓÃÀ´´´½¨vueʵÀýµÄ£»

entry-server.jsÊÇÓÃÀ´´´½¨Éú³Évue-ssr-server-bundle.json£¨ÐèÒªÓõ½app.js£©ËùÐèµÄÅäÖÃÅä¼þ£»ÊǸøwebpack.server.config.jsÓõģ»

webpack.server.config.jsÊÇÓÃÀ´Éú³Évue-ssr-server-bundle.jsonµÄ£»

vue-ssr-server-bundle.jsonÊǸøserver.jsÖеÄcreateBundleRendererÓõġ£

//app.js 
import Vue from 'vue'
import Vue from './App.vue'//ÕâÀïÒ»¶¨ÒªÐ´ÉÏ.vue,²»È»»áÆ¥Åäµ½app.js,require²»Çø·Ö´óСд0.0
export default createApp=function(){
return new Vue({
 render:h => h(App)
})
}

Ò»¸öcreateAppÉú³ÉÒ»¸övueʵÀý£»

//App.vue
<template>
<div id='app'>
 ÕâÊǸöapp
</div>
</template>
<script>
export default {}
</script>

»¹Ã»Óõ½<router-view>

//weback-base.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
output:{
 path:path.resolve(__dirname,'./dist'),
 filename:'build.js',
},
module: {
 rules: [
  {
   test:/\.js$/,
   use: {
    loader: 'babel-loader',
    options: {
     presets: ['@babel/preset-env']
    }
   },
   exclude:[/node_modules/,/assets/]
  },
  {
   test:/\.vue$/,
   use:['vue-loader']
  }
 ]
},
resolve: {
 alias:{
  '@':path.resolve(__dirname,'../')
 },
 extensions:['.js','.vue','.json']
},
plugins:[
 new VueLoaderPlugin()
]
}

ÓйØwebpackÅäÖò»†ªàÂ

//webpack.server.config.jsÓÃÀ´Éú³Évue-ssr-server-bundle.json
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')

module.exports = merge(baseConfig, {
 entry: './entry-server.js',

 // ÕâÔÊÐí webpack ÒÔ Node ÊÊÓ÷½Ê½(Node-appropriate fashion)´¦Àí¶¯Ì¬µ¼Èë(dynamic import)£¬
 // ²¢ÇÒ»¹»áÔÚ±àÒë Vue ×é¼þʱ£¬
 // ¸æÖª `vue-loader` ÊäËÍÃæÏò·þÎñÆ÷´úÂë(server-oriented code)¡£
 target: 'node',

 // ¶Ô bundle renderer Ìṩ source map Ö§³Ö
 devtool: 'source-map',

 // ´Ë´¦¸æÖª server bundle ʹÓà Node ·ç¸ñµ¼³öÄ£¿é(Node-style exports)
 output: {
 libraryTarget: 'commonjs2'
 },


 // ÕâÊǽ«·þÎñÆ÷µÄÕû¸öÊä³ö
 // ¹¹½¨Îªµ¥¸ö JSON ÎļþµÄ²å¼þ¡£
 // ĬÈÏÎļþÃûΪ `vue-ssr-server-bundle.json`
 plugins: [
 new VueSSRServerPlugin()
 ]
})

Õâ¸öÅäÖÃÄĶ¼ÄÜÕÒµ½£¬ÖصãÊÇVueSSRServerPluginÕâ¸ö²å¼þ£¬Éú³Évue-ssr-server-bundle.jsonÈ«¿¿Ëü£¬È¥µôµÄ»°Éú³ÉµÄÊÇbuilt-server-bundle.js£»¹ØÓÚmerge²å¼þ£¬libraryTarget,targetÅäÖÃÎÊÌâ×Ô¼º°Ù¶ÈwebpackÈ¥0.0£»

//entry-server.js
import { createApp } from './src/app'

export default context => {
 return createApp()
}

¹Ì¶¨Ð´·¨£¬·µ»ØÒ»¸öº¯Êý¹©createBundleRendererʹÓã»

Éú³Évue-ssr-server-bundle.json

µ½Ä¿Ç°ÎªÖ¹°²×°µÄ²å¼þÓУº

×Ô¼ºÊÖ¶¯Ò»¸öÒ»¸ö×°¾ÍÐÐÁË¡£

Éú³Évue-ssr-server-bundle.json£¬Ê¹ÓÃwebpackÃüÁî

Ò»Çж¼ÊÖ¶¯£¬ÊìϤwebpack;

ÐÞ¸Äserver.js

const express = require('express');
const chalk = require('chalk');

const server = express();
const serverBundle = require('./dist/vue-ssr-server-bundle.json')//**ÐÂÔö**//
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
 runInNewContext: false, // ¿´Ãû×ÖÒ²ÖªµÀÊÇÉú³Éij¸öеÄContext¶ÔÏó,ĬÈÏÊÇtrue,¸Ä³ÉfalseÀí½âΪijÖÖ»º´æ»úÖÆ£¬Ìá¸ß·þÎñÆ÷ЧÂÊ
 template: require('fs').readFileSync('./index.html', 'utf-8'),
 })//**ÐÂÔö**//
server.get('*', (req, res) => {
 //res.set('content-type', "text/html");
 //res.end(`
 //<!DOCTYPE html>
 //<html lang="en">
 // <head><title>Hello</title></head>
 // <body >
 // <div style='color:red'>ÄãºÃ</div>
 // </body>
 // </html>
 //¸Ä³ÉÏÂÃæÕâÑù
 const context = {//ÕâÀïµÄ²ÎÊýÏÖÔÚ»¹Ã»Ó㬵«Õâ¸ö¶ÔÏó»¹ÊǵÃÓã¬Òª×örenderToStringµÄ²ÎÊý
 url:req.url
 }
 renderer.renderToString(context, (err, html) => {
  if (err) {
  res.status(500).end('Internal Server Error')
  return
  } else {
  res.end(html)
  }
 })
 `)
 })

server.listen(8080,function(){
 let ip = getIPAdress();
 console.log(`·þÎñÆ÷¿ªÔÚ£ºhttp://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})

function getIPAdress(){//nodeϵÄosÄ£¿é¿ÉÒÔÄõ½Æô¶¯¸ÃÎļþµÄ·þÎñ¶ËµÄ²¿·ÖÐÅÏ¢£¬Ï¸½Ú×Ô¼ºÈ¥nodeÉÏÃæ²é
 var interfaces = require('os').networkInterfaces();
 for (var devName in interfaces) {
  var iface = interfaces[devName];
  for (var i = 0; i < iface.length; i++) {
   var alias = iface[i];
   if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
    return alias.address;
   }
  }
 }
}

ÊÔÒ»¸ò£ºnode server.js

Õý³££¬¼ýÍ·Ö¸µÄµØ·½¹ÙÍøÓнâÊÍ¡£±ðÍüÁËinde.htmlÖмÓÈëÒ»ÐÐ×¢ÊÍ£º

ºóÐøÐÞ¸Ätitle,metaÍ·²¿¶¼ÊÇͨ¹ýÀàËƵÄ×¢ÊÍ·½Ê½£¬Ô­Àí¾ÍÊÇÕýÔòÆ¥ÅäÌæ»»×Ö·û´®-¡£-£»

>¼ÓÈë·ÓÉvue-router

ÐÂÔö¼¸¸öÎļþ


ÐèÒªÐ޸ĵÄÎļþÓУº

App.vue//¼Ó¸örouter-view¾ÍÐÐ

//app.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
export function createApp(){
 const app = new Vue({
  router,
  render:h => h(App)
 })
 return {app,router}
}

°ÑappʵÀýºÍrouter¶¼Å׳öÈ¥£¬¸øentry-server.jsÓÃ

// entry-server.js
import { createApp } from './src/app'

export default context => {
 //ÕâÀïÓÃpromiseµÄÔ­ÒòÓкܶ࣬ÆäÖÐÓÐÒ»¸ö¾ÍÊÇÏÂÃæÕâ¸öonReady·½·¨ÊÇÒì²½µÄ¡£createBundleRendererÖ§³Öpromise
 return new Promise((resolve, reject) => {
 const { app, router } = createApp()

 router.push(context.url)

 router.onReady(() => {//onReady·½·¨»¹ÓÐgetMatchedComponents·½·¨»¹ÊÇÐèÒªÁ˽âÒ»ÏÂ
  const matchedComponents = router.getMatchedComponents()
  if (!matchedComponents.length) {
  return reject({ code: 404 })
  }
  resolve(app)
 }, reject)
 })
}

×îºó¿´Ò»ÏÂrouter.js

//router.js
 import Vue from 'vue'
 import VueRouter from 'vue-router'
//Ò³ÃæÒªÏÈÉùÃ÷ºóʹÓ㬲»ÒªÎÊΪʲô
import home from './pages/home'
import store from './pages/store'

Vue.use(VueRouter)
export default new VueRouter({
 mode: 'history',
 routes:[
  {path:'/',name:'home',component:home},
  {path:'/store',name:'store',component:store},
 ]
})

ÔÙ¿´Ò»ÏÂÁ½¸öÒ³ÃæµÄ´úÂ룻

 //store.vue 
 <template>
 <div>this is store</div>
 </template>
 <script>
   export default {}
 </script>

¸ÄµÄ²î²»¶àÁË£¬ÊÔÒ»¹þ£º

ÖØдò¸ö°üwebpack --config webpack.server.js

Æô¶¯node server

>entry-client.jsÊǸÉɶµÄ

µ½Ä¿Ç°ÎªÖ¹»¹Ã»Óõ½entry-client.js½Ð¿Í»§¶ËÅäÖ㬲»×ż±Ê¹Óã¬ÏÈ×ö¸ö²âÊÔ£¬Ð´µãÂß¼­ÊÔÊÔ£º
ÐÞ¸ÄÏÂstore.vue

//store.vue
<template>
<div @click='run'>{{msg}}</div>
</template>
<script>
 export default {
  data(){
   msg:'this is store'
  },
  created(){
   this.msg = 'this is created'
  },
  mounted(){
   this.msg = 'this is mounted'
  },
  methods: {
   run(){
    alert('this is methods')
   }
  }
 }
</script>

¿´Õâ¸öÑù×ÓÒ³Ãæ×îÖÕչʾµÄ½á¹ûÓ¦¸ÃÊÇthis is mounted£¬È»¶ø½á¹ûÊÇÕâÑùµÄ£º


ºÜºÃ½âÊÍ£¬·þÎñ¶Ë¶ÔÓÚ¹³×Óº¯ÊýµÄÀí½âÒ²ÊǺÜÕýÈ·µÄ£¬created»áÔÚÒ³Ãæ·µ»Ø֮ǰִÐУ¬¶ømountedÊÇÔÚvueʵÀý³ÉÐÍÖ®ºóÖ´ÐУ¬¾ÍÊÇÒ³ÃæäÖȾºó£¬Õâ¸öÊÇÒªÔÚ¿Í»§¶Ë²Å»áÖ´ÐУ¬¿ÉÊÇΪʲôҳÃæ³öÀ´ÁËûÓÐÖ´ÐÐmounted£¬¶øÇÒrunµÄµã»÷ʼþûÓÐÉúЧ£»

¿´¿´Ò³Ã棺


Ò»¸öjsÎļþ¶¼Ã»¼ÓÔØ£¬ÔõôִÐÐÂß¼­£¬¾ÍÊǸö¾²Ì¬Ò³Ãæ0.0£»

Õâʱºòentry-client.js¾Í³ö³¡ÁË


ÐÂÔöÁ½¸öÎļþ

//entry-client.js 
import { createApp } from './src/app.js';

const { app } = createApp();

app.$mount('#app');

»ù±¾ÅäÖã»

//webpack.client.config.js

const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')

module.exports = merge(baseConfig, {
 entry: './entry-client.js',
 optimization:{
 runtimeChunk:true
 },
 plugins: [
 // ´Ë²å¼þÔÚÊä³öĿ¼ÖÐ
 // Éú³É `vue-ssr-client-manifest.json`¡£
 new VueSSRClientPlugin(),
 ]
})

Õâ¸öµØ·½Öصã³ýÁËVueSSRClientPluginÉú³Évue-ssr-client-manifest.jsonÍ⣬optimizationÊÇwebpack4²úÎÓÃÀ´·ÖÀëÉú³É¹²¹«chunk,ÅäÖû¹Ë㸴ÔÓ£¬¿ÉÒÔ¿´ÏÂÕâÀïwebpack4 optimization×ܽá

ÐÞ¸ÄÏÂserver.js

//server.js

 const express = require('express');
 const chalk = require('chalk');

 const server = express();

 const serverBundle = require('./dist/vue-ssr-server-bundle.json')
 const clientManifest = require('./dist/vue-ssr-client-manifest.json')//ÐÂÔö
 const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
  runInNewContext: false, // ÍƼö
  template: require('fs').readFileSync('./index.html', 'utf-8'),
  clientManifest // //ÐÂÔö
  })
 server.get('*', (req, res) => {
  res.set('content-type', "text/html");
  const context = {
   url:req.url
   }

   renderer.renderToString(context, (err, html) => {
    if (err) {
    res.status(500).end('Internal Server Error')
    return
    } else {
    res.end(html)
    }
   })

  })

 server.listen(8080,function(){
  let ip = getIPAdress();
  console.log(`·þÎñÆ÷¿ªÔÚ£ºhttp://${chalk.green(ip)}:${chalk.yellow(8080)}`)
 })

 function getIPAdress(){//nodeϵÄosÄ£¿é¿ÉÒÔÄõ½Æô¶¯¸ÃÎļþµÄ·þÎñ¶ËµÄ²¿·ÖÐÅÏ¢£¬Ï¸½Ú×Ô¼ºÈ¥nodeÉÏÃæ²é
  var interfaces = require('os').networkInterfaces();
  for (var devName in interfaces) {
   var iface = interfaces[devName];
   for (var i = 0; i < iface.length; i++) {
    var alias = iface[i];
    if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
     return alias.address;
    }
   }
  }
 }

´ò°üÏ£ºwebpack --config webpack.client.config.js

node server һϣ¬¿´¿´Ò³Ãæ


jsÓÐÁË£¬¿ÉÊÇΪʲô»¹²»ÐУ¬²»Äܵã0.0£»

¿´¿´¡£°Â±¨´íÁË


¶ÁÈ¡²»µ½¾²Ì¬Îļþ£»

ÐÞ¸Äserver.js¼Ó¸ö¾²Ì¬ÎļþÍйܣº


ÔÙ¿´¿´


ʼþÒ²ÓÐÁË£¬Ò³Ãæû±ä»¯£¬consoleһϣ¬·¢ÏÖÖµÆäʵÒѾ­±äÁË£¬Ö»ÊÇʧȥÁËÏìӦʽ£»Õâ¾ÍÊÇΪʲôҪÓÃvuexµÄÔµ¹Ê£»

>¼ÓÈëvuex

¿ªÊ¼ÏëÔÚÒ³ÃæÖÐÓÃthis.$set·½·¨£¬È»¶øÐв»Í¨£¬¶øÇÒ²»¿ÉÄܸøÿ¸öÖµ¶¼ÖØÐÂдһ¸öÕâ¸ö·½·¨£»


¼Ó¸ösotre.js

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

 export default new Vuex.Store({
 state: {
  msg: ''
 },
 actions: {
  setMsg ({ commit }, val) {
   commit('setMsg', val)
  }
 },
 mutations: {
  setMsg (state, val) {
  Vue.set(state, 'msg', val)//¹Ø¼ü
  }
 }
 })

ºÜ»ù´¡µÄÂß¼­£¬¹Ø¼üÔÚVue.setÕâ¸ö·½·¨£¬ÖØÐÂÔö¼ÓÁËÏìӦʽ£»
ÐÞ¸ÄÏÂapp.js

//app.js
 import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'//¼Ó¸östore¾ÍÐÐÁË
export function createApp(){
 const app = new Vue({
  router,
  store,
  render:h => h(App)
 })
 return {app,router}
}

store.vue¸Ä³ÉÕâÑù

<template>
 <div @click='run'>{{msg}}</div>
</template>
<script>
 export default {
  data(){},
  created(){
   this.$store.dispatch('setMsg','this is created')
  },
  computed:{
   msg(){
    return this.$store.state.msg;
   }
  },
  mounted(){
   this.$store.dispatch('setMsg','this is mounted')
  },
  methods: {
   run(){
    alert('this is methods')
   }
  }
 }
</script>

ÖØдò¸ö°ü£¬Ïëһϣ¬ÐÞ¸ÄÒ³ÃæµÄ»°Ö»ÐèÒªÖØдò°üclient,Èç¹ûÐÞ¸ÄÁËapp.jsÁ½¸ö¾ÍÒª¶¼ÖØдò°üÁË£»

node server Ò»ÏÂ


Õâ»Ø×ÜËãÍê³ÉÁË£»

>×ܽá

·þÎñ¶ËäÖȾ¶«Î÷»¹ÊÇͦ¶àµÄ£¬Éæ¼°ÁìÓòÒ²·Ç³£¹ã£¬±ÈÈçvue£¬webpack,node£¬ËüÃǵÄÉú̬Ȧ¶¼´óµÄ¿ÉÅ£¬ÐèҪѧϰ¶«Î÷·Ç³£¶à£¬
¿ÓÓֶ࣬ÓÖ´ó£¬ÓÖÉºóÃ滹ÓкܶàÎÊÌâÒª½â¾ö£º

Òì²½Êý¾Ý¼ÓÔØ£»//html·µ»ØÇ°ÏÈäÖȾһ²¿·Ö½Ó¿ÚÄõ½µÄÊý¾Ý
Ôõô×öseoÓÅ»¯£»//×ö·þÎñ¶ËäÖȾµÄÖØÒªÔ­Òò£¬´¦ÀíÒì²½Êý¾Ý¼ÓÔØÎÊÌâÒ²ÊÇΪÁËÕâ¸ö
»º´æÔõô¼Ó£»
¿ª·¢»·¾³´î½¨£»//Äã²¢²»Ï£Íûÿ¸ÄÒ»ÐдúÂë¾ÍÖØÐÂÊÖ¶¯´ò¸ö°ü£¬ÖØÆôÏ·þÎñ°É0.0
»¹ÓÐÔõôʵÏÖ²¿·ÖÒ³Ãæssr£»//Ò»¸öÏîÄ¿²»¿ÉÄÜËùÓÐÒ³Ã涼·þÎñ¶ËäÖȾ£¬Ì«ºÄÐÔÄÜ£¬·þÎñÆ÷ѹÁ¦´óѽ£»

»¹ÓкܶàÒÉ»ó£º

±ÈÈçΪʲô»áʧȥÏìӦʽ£¬webpackµ½µ×¸ÃÔõôÅäÖᣡ£

ÒÔÉϾÍÊDZ¾ÎĵÄÈ«²¿ÄÚÈÝ£¬Ï£Íû¶Ô´ó¼ÒµÄѧϰÓÐËù°ïÖú£¬Ò²Ï£Íû´ó¼Ò¶à¶àÖ§³Ö½Å±¾Ö®¼Ò¡£

.....

转载请注明:谷谷点程序 » vue ssr服务端渲染(小白解惑)