wuyuedefeng / blogs

博客文章在issue中
5 stars 0 forks source link

vue ssr 服务端渲染 #68

Open wuyuedefeng opened 4 years ago

wuyuedefeng commented 4 years ago

安装插件

$ yarn add webpack-node-externals lodash.merge -D
$ yarn add koa koa-static vue-server-renderer 

改造文件

/src/main.js

import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
// import router from './router'
import { createRouter } from './router'
// import store from './store'
import { createStore } from './store'

import api from './utils/api'
import directives from './utils/directives'
import filters from './utils/filters'

import './assets/stylesheets/application.scss'

import i18n from './i18n'
console.log(i18n.t('hello'))

Vue.config.productionTip = false

Vue.use(api)
Vue.use(directives)
Vue.use(filters)

// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {
  const router = createRouter()
  const store = createStore()
  const app = new Vue({
    router,
    store,
    i18n,
    render: h => h(App)
  })
  // app.$mount('#app')
  return { app, router, store }
}

/src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  { path: '/', name: 'Home', component: () => import('../pages/home.vue') },
  {
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    path: '/about', name: 'About', component: () => import(/* webpackChunkName: "about" */ '../pages/about.vue')
  }
]

export function createRouter () {
  return new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
  })
}

/src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import user from './modules/user'
import app from './modules/app'
Vue.use(Vuex)

export function createStore () {
  return new Vuex.Store({
    // state 持久化,防止f5刷新,导致数据消失
    plugins: [createPersistedState({
      storage: window.localStorage,
      reducer (val) {
        return {
          // 只保存module user内部所有变量持久化
          user: val.user
        }
      }
    })],
    state: {},
    mutations: {},
    actions: {},
    modules: {
      app,
      user
    }
  })
}

创建新文件

/src/entry-server.js

import { createApp } from './main'
/* eslint-disable */
export default context => {
  // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
  // 以便服务器能够等待所有的内容在渲染前,
  // 就已经准备就绪。
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()
    // 设置服务器端 router 的位置
    router.push(context.url)

    // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      // 匹配不到的路由,执行 reject 函数,并返回 404
      if (!matchedComponents.length) {
        return reject({
          code: 404
        })
      }
      // Promise 应该 resolve 应用程序实例,以便它可以渲染
      resolve(app)
    }, reject)
  })
}

/src/entry-client.js

import { createApp } from './main'

// 客户端特定引导逻辑……
const { app } = createApp()

// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')

/public/index.temp.html

<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>

package.json

"build:client": "vue-cli-service build",
"build:server": "WEBPACK_TARGET=node vue-cli-service build --mode server",
"build:win": "npm run build:server && mv dist/vue-ssr-server-bundle.json bundle && npm run build:client && mv bundle dist/vue-ssr-server-bundle.json",

/server.js

Webpack

const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const nodeExternals = require('webpack-node-externals')
const merge = require("lodash.merge")
const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
const target = TARGET_NODE ? 'server' : 'client'

const path = require('path')
function resolve (dir) {
  return path.join(__dirname, dir)
}

console.log(`\n --- target:${target} ---\n`)

module.exports = {
  // transpileDependencies: [/zzp-ui/],
  productionSourceMap: false,

  // publicPath: process.env.NODE_ENV !== 'development' ? './' : '/',
  // outputDir: 'dist',
  devServer: {
    open: true,
    host: '0.0.0.0',
    port: '8080',
    disableHostCheck: true, // 解决127.0.0.1指向其他域名时出现"Invalid Host header"问题
    proxy: {
      '/api': {
        target: '<url>',
        changOrigin: true // 配置跨域
        // ws: true, // 配置ws跨域
        // secure: false, // https协议才设置
      }
    },
    before: app => {
      require('./mock')(app)
    }
  },

  // 自定义入口和模板
  // pages: {
  //   index: {
  //     entry: 'src/main.js',
  //     template: 'public/index.html',
  //     filename: 'index.html'
  //   }
  // },
  chainWebpack: config => {
    config.resolve.alias.set('@pages', resolve('src/pages'))
    // eg: 更改编译忽略某些svg文件的加载
    config.module.rule('svg').exclude.add(resolve('src/components/shared/SvgIcon')).end()
    config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/components/shared/SvgIcon')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({ symbolId: 'icon-[name]' }).end()
    // 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中
    config.plugin('html').tap(args => {
      args[0].title = '前后端分离'
      return args
    })
    // ssr
    config.module.rule('vue').use('vue-loader').tap(options => {
      merge(options, {
        optimizeSSR: false
      })
    })
  },
  configureWebpack: () => ({
    // 将 entry 指向应用程序的 server / client 文件
    entry: `./src/entry-${target}.js`,
    // 对 bundle renderer 提供 source map 支持
    devtool: 'source-map',
    target: TARGET_NODE ? 'node' : 'web',
    node: TARGET_NODE ? undefined : false,
    output: {
      libraryTarget: TARGET_NODE ? 'commonjs2' : undefined
    },
    // https://webpack.js.org/configuration/externals/#function
    // https://github.com/liady/webpack-node-externals
    // 外置化应用程序依赖模块。可以使服务器构建速度更快,
    // 并生成较小的 bundle 文件。
    externals: TARGET_NODE
      ? nodeExternals({
        // 不要外置化 webpack 需要处理的依赖模块。
        // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
        // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
        whitelist: [/\.css$/]
      })
      : undefined,
    optimization: {
      splitChunks: TARGET_NODE ? false : undefined
    },
    plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
  }),
  // configureWebpack: config => {
  //   config.module.rules.push({
  //     test: /.md$/,
  //     use: [{
  //       loader: 'text-loader'
  //     }]
  //   })
  // },
  css: {
    loaderOptions: {
      // pass options to sass-loader
      sass: {
        // @/ is an alias to src/
        // before key is 'data', now change to 'prependData'  so this assumes you have a file named `src/variables.scss`
        prependData: '@import "@/assets/stylesheets/_variables.scss";'
      },
      postcss: {
        plugins: [
          require('postcss-pxtorem')({
            // rootValue: 37.5, // 效果图375
            rootValue: (input) => {
              // eslint-disable-next-line no-useless-escape
              if (/@media\sonly\sscreen\sand\s\(min\-width:\s1024px\)/i.test(input && input.css)) {
                return 144
              }
              return 37.5
            },
            propList: ['*'], // 属性的选择器,*表示通用
            selectorBlackList: ['.px-'] //   忽略的选择器   .ig-  表示 .ig- 开头的都不会转换
            // exclude: /node_modules/i
          })
        ],
        /* You can extend webpack config here */
        extend (config, ctx) {}
      }
    }
  },

  pluginOptions: {
    i18n: {
      locale: 'en',
      fallbackLocale: 'en',
      localeDir: 'locales',
      enableInSFC: true
    }
  }
}

npm run build:win & node server 是服务端渲染启动部署流程 npm run server 是本地开发环境启动流程

配置代码: https://github.com/vue-quick-framework/vue-base-structrue/tree/feature/ssr

参考资料库

liuxy0551 commented 4 years ago

Learning requires teaching by hand to hand.

wuyuedefeng commented 1 year ago

vue3 ssr

vue3 + vue-router + pinia + vite + koa + koa-router 项目代码: https://gitee.com/zezeping/zhuliba/tree/master/frontend/user-web