ssr服务器 .json文件,服务端渲染 SSR

前言

在Java Web时代,由于前后端在一个服务,客户端向服务端发送请求,web服务器解析HTTP格式的数据,转发到指定的servlet,servlet根据参数从数据库拿到数据,放进域对象并返回指定的JSP页面,JSP跟域对象数据一起渲染成HTML,最后通过HTTP响应体返回给客户端,整个过程发生在服务器。

而随着前后端分离后,前端用前端框架开发,开发完成后把项目打包成静态文件(HTML,CSS,JS)放在静态Web服务器(Nginx),此时客户端向服务器发出请求,服务器就会返回这些静态文件,到达客户端后,客户端在模板上执行JS后,把整个项目构建成完整的单页面应用,并通过AJAX向服务端请求数据,再渲染在单页面应用上,整个过程都发生在客户端。

正文

配置基于vue3.0

vue.config.js

const VueSSRServerPlugin = require("vue-server-renderer/server-plugin");

const VueSSRClientPlugin = require("vue-server-renderer/client-plugin");

const nodeExternals = require("webpack-node-externals");

const env = process.env;

const isServer = env.RUN_ENV === "server";

const resolve = dir => require('path').join(__dirname, dir)

module.exports = {

lintOnSave: false,

publicPath: "/",

outputDir: `dist/${env.RUN_ENV}`,

devServer: {

port: 3001,

publicPath: "/"

},

configureWebpack: {

// 将 entry 指向应用程序的 server / client 文件,不写默认是main.js

entry: `./src/entry-${env.RUN_ENV}.js`,

devtool: "eval",

// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),

// 并且还会在编译 Vue 组件时,

// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。

target: isServer ? "node" : "web",

output: {

libraryTarget: isServer ? "commonjs2" : undefined,

},

// https://webpack.js.org/configuration/externals/#function

// https://github.com/liady/webpack-node-externals

// 外置化应用程序依赖模块。可以使服务器构建速度更快,

// 并生成较小的 bundle 文件。

externals: isServer

? nodeExternals({

// 不要外置化 webpack 需要处理的依赖模块。

// 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,

// 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单

whitelist: /\.css$/,

})

: undefined,

optimization: { splitChunks: isServer ? false : undefined },

// 这是将服务器的整个输出

// 构建为单个 JSON 文件的插件。

// 服务端默认文件名为 `vue-ssr-server-bundle.json`

// 客户端默认文件名为 `vue-ssr-client-manifest.json`

plugins: [isServer ? new VueSSRServerPlugin() : new VueSSRClientPlugin()],

},

chainWebpack: config => {

config.resolve.alias

.set('@', resolve('src'))

},

css: {

loaderOptions: {

// 设置 scss 公用变量文件

stylus: {

}

}

},

}

entry-client.js

import Vue from 'vue'

import 'es6-promise/auto'

import { createApp } from './app'

import ProgressBar from './components/ProgressBar.vue'

// global progress bar

const bar = Vue.prototype.$bar = new Vue(ProgressBar).$mount()

document.body.appendChild(bar.$el)

// a global mixin that calls `asyncData` when a route component's params change

Vue.mixin({

beforeRouteUpdate(to, from, next) {

const { asyncData } = this.$options

if (asyncData) {

asyncData({

store: this.$store,

route: to

}).then(next).catch(next)

} else {

next()

}

}

})

Vue.mixin({

beforeMount () {

const { asyncData } = this.$options

if (asyncData) {

// 将获取数据操作分配给 promise

// 以便在组件中,我们可以在数据准备就绪后

// 通过运行 `this.dataPromise.then(...)` 来执行其他任务

this.dataPromise = asyncData({

store: this.$store,

route: this.$route

})

}

}

})

const { app, router, store } = createApp()

// prime the store with server-initialized state.

// the state is determined during SSR and inlined in the page markup.

if (window.__INITIAL_STATE__) {

store.replaceState(window.__INITIAL_STATE__)

}

// wait until router has resolved all async before hooks

// and async components...

router.onReady(() => {

// Add router hook for handling asyncData.

// Doing it after initial route is resolved so that we don't double-fetch

// the data that we already have. Using router.beforeResolve() so that all

// async components are resolved.

router.beforeResolve((to, from, next) => {

const matched = router.getMatchedComponents(to)

const prevMatched = router.getMatchedComponents(from)

let diffed = false

const activated = matched.filter((c, i) => {

return diffed || (diffed = (prevMatched[i] !== c))

})

const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)

if (!asyncDataHooks.length) {

return next()

}

bar.start()

Promise.all(asyncDataHooks.map(hook => hook({ store, route: to })))

.then(() => {

bar.finish()

next()

})

.catch(next)

})

// actually mount to DOM

app.$mount('#app')

})

// service worker

// if ('https:' === location.protocol && navigator.serviceWorker) {

// navigator.serviceWorker.register('/service-worker.js')

// }

entry-server.js

import { createApp } from './app'

const isDev = process.env.NODE_ENV !== 'production'

// This exported function will be called by `bundleRenderer`.

// This is where we perform data-prefetching to determine the

// state of our application before actually rendering it.

// Since data fetching is async, this function is expected to

// return a Promise that resolves to the app instance.

export default context => {

return new Promise((resolve, reject) => {

const s = isDev && Date.now()

const { app, router, store } = createApp()

const { url } = context

const { fullPath } = router.resolve(url).route

if (fullPath !== url) {

return reject({ url: fullPath })

}

// set router's location

router.push(url)

// wait until router has resolved possible async hooks

router.onReady(() => {

const matchedComponents = router.getMatchedComponents()

// no matched routes

if (!matchedComponents.length) {

return reject({ code: 404 })

}

// Call fetchData hooks on components matched by the route.

// A preFetch hook dispatches a store action and returns a Promise,

// which is resolved when the action is complete and store state has been

// updated.

Promise.all(matchedComponents.map(({ asyncData }) => asyncData && asyncData({

store,

route: router.currentRoute

}))).then(() => {

isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)

// After all preFetch hooks are resolved, our store is now

// filled with the state needed to render the app.

// Expose the state on the render context, and let the request handler

// inline the state in the HTML response. This allows the client-side

// store to pick-up the server-side state without having to duplicate

// the initial data fetching on the client.

context.state = store.state

resolve(app)

}).catch(reject)

}, reject)

})

}

app.js

import Vue from 'vue'

import App from './App.vue'

import { createStore } from './store'

import { createRouter } from './router'

import { sync } from 'vuex-router-sync'

// import titleMixin from './util/title'

// import * as filters from './util/filters'

// mixin for handling title

// Vue.mixin(titleMixin)

// register global utility filters.

// Object.keys(filters).forEach(key => {

// Vue.filter(key, filters[key])

// })

// Expose a factory function that creates a fresh set of store, router,

// app instances on each call (which is called for each SSR request)

export function createApp () {

// create store and router instances

const store = createStore()

const router = createRouter()

// sync the router with the vuex store.

// this registers `store.state.route`

sync(store, router)

// create the app instance.

// here we inject the router, store and ssr context to all child components,

// making them available everywhere as `this.$router` and `this.$store`.

const app = new Vue({

router,

store,

render: h => h(App)

})

// expose the app, the router and the store.

// note we are not mounting the app here, since bootstrapping will be

// different depending on whether we are in a browser or on the server.

return { app, router, store }

}

server.js

const fs = require("fs");

const path = require("path");

const express = require('express')

const app = express()

// 第 2 步:获得一个createBundleRenderer

// const { createBundleRenderer } = require("vue-server-renderer");

// const serverBundle = require("./dist/server/vue-ssr-server-bundle.json");

// const clientManifest = require("./dist/client/vue-ssr-client-manifest.json");

// const renderer = createBundleRenderer(serverBundle, {

// runInNewContext: false,

// template: fs.readFileSync(path.resolve(__dirname, "./src/index.template.html"), "utf-8"),

// clientManifest,

// });

function renderToString(context) {

return new Promise((resolve, reject) => {

renderer.renderToString(context, (err, html) => {

err ? reject(err) : resolve(html);

});

});

}

app.use('/js', express.static('./dist/client/js'));

app.use('/css', express.static('./dist/client/css'));

app.get('/api/:id', (req, res) => {

res.header('Access-Control-Allow-Origin', '*');

res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');

res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');

const { id } = req.params

const result = {

code: 200,

data: id,

msg: '请求成功'

}

res.setHeader("Content-Type", "application/json;charset=utf-8")

res.setHeader("Server", "Sli97")

res.json(result);

res.end()

})

app.get('/test', (req, res) => {

console.log(req.query)

const { id } = req.query

console.log(id)

res.setHeader("Content-Type", "application/json;charset=utf-8")

res.setHeader("Server", "Sli97")

res.send("test :" + id)

})

// app.get('*', async (req, res) => {

// const context = { url: req.url, title: "Hello SSR", }

// // 将 context 数据渲染为 HTML

// const html = await renderToString(context).catch(err => {

// if (err.url) {

// res.redirect(err.url)

// } else if (err.code === 404) {

// res.status(404).send('404 | Page Not Found')

// } else {

// // Render Error Page or Redirect

// res.status(500).send('500 | Internal Server Error')

// console.error(`error during render : ${req.url}`)

// console.error(err.stack)

// }

// });

// res.send(html)

// })

/*服务启动*/

const port = 3000;

app.listen(port, () => {

console.log(`server started at localhost:${port}`);

});

package.json脚本命令

"scripts": {

"serve": "cross-env RUN_ENV=client vue-cli-service serve ",

"start": "npm run build:server && npm run build:client && npm run server",

"build:client": "cross-env RUN_ENV=client vue-cli-service build",

"build:server": "cross-env RUN_ENV=server vue-cli-service build",

"server": "node server",

"test": "echo \"Error: no test specified\" && exit 1"

},

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值