
肝就完了!!!!
Vue SSRVue SSR原理基本与React SSR类似,但是Vue比React更为简单一些,下面开始介绍下Vue SSR
Webpack配置// client端
    const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
    module.exports = merge(baseConfig, {
      entry: './src/entry-client.js',
      plugins: [
        // 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
        // 以便可以在之后正确注入异步 chunk。
        // 这也为你的 应用程序/vendor 代码提供了更好的缓存。
        new webpack.optimize.CommonsChunkPlugin({
          name: "manifest",
          minChunks: Infinity
        }),
        // 此插件在输出目录中
        // 生成 `vue-ssr-client-manifest.json`。
        new VueSSRClientPlugin()
      ]
    })
// server端
    const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
    module.exports = merge(baseConfig, {
      // 将 entry 指向应用程序的 server entry 文件
      entry: './src/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',
      output: {
        libraryTarget: 'commonjs2'
      },
      externals: nodeExternals({
        // 不要外置化 webpack 需要处理的依赖模块。
        // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
        // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
        whitelist: /\.css$/
      }),
      // 这是将服务器的整个输出
      // 构建为单个 JSON 文件的插件。
      // 默认文件名为 `vue-ssr-server-bundle.json`
      plugins: [
        new VueSSRServerPlugin()
      ]
    })
经过以上两步,出口文件夹在正常输出文件以外多出两个json文件
- vue-ssr-client-manifest.json
 - vue-ssr-server-bundle.json
 
同构同样是核心理念,在两端书写一套共有的代码
实例化
实例化Vue, 这里要主要的是上面讲到的是交叉渲染污染,所以设计成函数形式, 每次请求都会实例化一个新的app出来
 // app.js
    import { sync } from 'vuex-router-sync'
    ...
    export function createApp() {
      const router = createRouter()
      const store = createStore()
      sync(store, router) // 将vuex与vue-router状态共享
      const app = new Vue({
        store,
        router,
        render: h => h(App)
      })
      return {
        app,
        store,
        router
      }
    }路由同构
    // router.js
    export default function createRouter() {
        return new Router({
            ... // 正常配置路由
        })
    }
浏览器正常使用, 在app.js实例化的时候就已经同步塞入Vue里了
服务端由于涉及到一些数据预期等等额外的操作, 需要进行简单处理下
根据官方定义的一个调用方法asyncData, 在服务端获取到客户端请求的时候,会先检测组件配置里是否定义了asyncData, 若有的话, 则进行调用, 在这里声明下asyncData是在Vue实例化之前调用的, 也就是说此时是无法获取到执行上下文的, 所以需要用到公共库Vuex来存储数据(当然自己写一个类似的也可以), asyncData会传入store与router的参数给到组件
import { createApp } from './app'
    export default context => {
      return new Promise((resolve, reject) => {
        const { app, router, store } = createApp(context)
        // 服务端与客户端路径确保一致
        router.push(context.url)
        // 保证路由全部加载完
        router.onReady(() => {    
          const matchedComponents = router.getMatchedComponents()
          // 匹配不到的路由,执行 reject 函数,并返回 404
          if (!matchedComponents.length) {
            return reject({ code: 404 })
          }
          Promise.all(matchedComponents.map(Component => {
            if (Component.asyncData) {
              return Component.asyncData({
                store,
                route: router.currentRoute
              })
            }
          })).then(() => {
            // 在所有预取钩子(preFetch hook) resolve 后,
            // 我们的 store 现在已经填充入渲染应用程序所需的状态。
            // 当我们将状态附加到上下文,
            // 并且 `template` 选项用于 renderer 时,
            // 状态将自动序列化为 `window.__INITIAL_state__`,并注入 html。
            context.state = store.state
            resolve(app)
          }).catch(reject)
            // 对所有匹配的路由组件调用 `asyncData()`
        }, reject)
      }).catch(err => {
      })
    }
以上有个特别要指明的地方:
Vue有两种形式,一个是vue文件, 另一个是直接在Vue实例上进行视图驱动, 两种数据填充是不同的
- Vue文件
与React一样,会填充到window下的INITIAL_STATE属性, 并且在entry-client.js里,挂载DOM之前, 会取到这个数据 
import { createApp } from './app'
    // 客户端特定引导逻辑……
    const { app, store } = createApp()
    if (window.__INITIAL_STATE__) {
      store.replaceState(window.__INITIAL_STATE__)
    }
    // 这里假定 App.vue 模板中根元素具有 `id="app"`
    app.$mount('#app')
实例
Vue会直接在data上定义一个相应数据, 可以直接使用
   // store.js
     mutations: {
      setItem (state, { id, item }) {
        Vue.set(state.items, id, item) // 直接定义一个响应数据
      }
    }启动服务
Vue提供了一个api, createBundleRenderer, 用于处理官方介绍的问题
    每次编辑过应用程序源代码之后,都必须停止并重启服务。这在开发过程中会影响开发效率。此外,Node.js 本身不支持 source map
好处:
- 内置的 source map 支持(在 webpack 配置中使用 devtool: 'source-map')
 - 在开发环境甚至部署过程中热重载(通过读取更新后的 bundle,然后重新创建 renderer 实例)
 - 关键 CSS(critical CSS) 注入(在使用 *.vue 文件时):自动内联在渲染过程中用到的组件所需的CSS。更多细节请查看 CSS 章节。
 - 使用 clientManifest 进行资源注入:自动推断出最佳的预加载(preload)和预取(prefetch)指令,以及初始渲染所需的代码分割 chunk。
 
const express = require('express')
    const server = express()
    const path = require('path')
    const { createBundleRenderer  } = require('vue-server-renderer')
    const serverMainfest = path.resolve(__dirname, './dist/vue-ssr-server-bundle.json')
    const renderer = createBundleRenderer(serverMainfest, {
      runInNewContext: false, // true的时候  每次请求Vue会自己去实例化一个App, 这个主要就是避免交叉污染,但是其实我们已经避免了这个问题,所以可以设置为false, 毕竟这是一个性能消耗的过程
      template: require('fs').readFileSync(path.join(__dirname, './index.html'), 'utf-8'), // html模板, 
      // <div id="app">
      // <!--vue-ssr-outlet--> Vue会将内容自动填充到此处
      // </div>
      clientManifest: require(path.resolve(__dirname, './dist/vue-ssr-client-manifest.json')) // 这里就是之前webpack打包好的
    })
    server.use(express.static(path.resolve(__dirname, './dist')))
    server.get('*', (req, res) => {
      const context = {
        url: req.url
      }
      renderer.renderToString(context, (err, html) => {
        if (err) {
          res.status(500).end('Internal Server Error')
          return
        }
        res.end(`
          ${html}
        `)
      })
    })
    server.listen(8080)遇到的问题
- 在app.js实例化之前之前会有一些依赖于浏览器环境的第三方包, 例如FastClick, 或者是在组件引入第三方组件时也有依赖浏览器环境的包, 此时会报错window is not defined
 
解决: 由于服务端只会执行beforeCreate与created生命周期(因为服务端不会去挂载DOM), 所以可以在beforeMount方法里去动态加载模块
beforeMount() {
        if (typeof window !== 'undefined') {
          const { swiper, swiperSlide } = require('vue-awesome-swiper').default
          this.$options.components.swiper = swiper
          this.$options.components.swiperSlide = swiperSlide
        }
    },
    mounted() {
        if (typeof window !== "undefined") {
          const Fastclick = require('fastclick')
          Fastclick.attach(document.body)
        }
    }
- 由于数据预取完全是取决于数据请求回来的时间,可能数据加载过慢导致页面出现白屏, 此时需要做下loading状态
 
后言
不知道我说的够不够清楚,总之,SSR核心理念就是同构, 写一套代码通用于两端
服务端职责
- 数据预取
 - 转换HTML字符串模板返回给客户端
 
浏览器职责
- 获取服务器预取的数据
 - 挂载DOM
 
















