├── .gitignore ├── Readme.md ├── index.ssr.html ├── entry-client.js ├── components └── Foo.vue ├── store └── index.js ├── App.vue ├── package.json ├── start.js └── entry-server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist 4 | .DS_Store 5 | npm-debug.log -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ### 文章 2 | 3 | [理解vue ssr原理,自己搭建简单的ssr框架](https://www.86886.wang/detail/5b8e6081f03d630ba8725892) 4 | 5 | ### 运行 6 | 7 | ```bash 8 | git clone https://github.com/wmui/vue-ssr-demo.git 9 | cd vue-ssr-demo 10 | npm install 11 | npm run start 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /index.ssr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 服务端渲染 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /entry-client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import createStore from './store/index.js'; 4 | 5 | function createApp() { 6 | const store = createStore(); 7 | const app = new Vue({ 8 | store, 9 | render: h => h(App) 10 | }); 11 | return {app, store} 12 | } 13 | 14 | const { app, store } = createApp(); 15 | 16 | if (window.__INITIAL_STATE__) { 17 | store.replaceState(window.__INITIAL_STATE__); 18 | } 19 | 20 | app.$mount('#app'); 21 | -------------------------------------------------------------------------------- /components/Foo.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import axios from 'axios'; 3 | import Vuex from 'vuex'; 4 | 5 | Vue.use(Vuex); 6 | 7 | export default function createStore() { 8 | return new Vuex.Store({ 9 | state: { 10 | article: {} 11 | }, 12 | actions: { 13 | async GET_ARTICLE({commit}) { 14 | const {data} = await axios.get('https://www.86886.wang/api/article/5b38d0098c98760acf25bfac') 15 | commit('SET_ARTICLE', data) 16 | } 17 | }, 18 | mutations: { 19 | SET_ARTICLE(state, data) { 20 | state.article = data.data 21 | // console.log(state.article) 22 | } 23 | } 24 | }) 25 | } -------------------------------------------------------------------------------- /App.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-ssr-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack --config build/webpack.client.conf.js && webpack --config build/webpack.server.conf.js && node start.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "^0.18.0", 13 | "babel-core": "^6.26.0", 14 | "babel-loader": "^7.1.2", 15 | "css-loader": "^0.28.7", 16 | "express": "^4.16.2", 17 | "html-webpack-plugin": "^2.30.1", 18 | "vue": "^2.5.13", 19 | "vue-loader": "^13.6.2", 20 | "vue-server-renderer": "^2.5.13", 21 | "vue-template-compiler": "^2.5.13", 22 | "vuex": "^3.0.1", 23 | "webpack": "^3.10.0", 24 | "webpack-merge": "^4.1.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const express = require('express'); 4 | const server = express(); 5 | server.use(express.static('dist')); 6 | 7 | const bundle = fs.readFileSync(path.resolve(__dirname, 'dist/server.js'), 'utf-8'); 8 | const renderer = require('vue-server-renderer').createBundleRenderer(bundle, { 9 | template: fs.readFileSync(path.resolve(__dirname, 'dist/index.ssr.html'), 'utf-8') // 服务端渲染数据 10 | }); 11 | 12 | 13 | server.get('*', (req, res) => { 14 | renderer.renderToString((err, html) => { 15 | // console.log(html) 16 | if (err) { 17 | console.error(err); 18 | res.status(500).end('服务器内部错误'); 19 | return; 20 | } 21 | res.end(html); 22 | }) 23 | }); 24 | 25 | 26 | server.listen(8010, () => { 27 | console.log('访问:http://127.0.0.1:8010'); 28 | }); 29 | -------------------------------------------------------------------------------- /entry-server.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import createStore from './store/index.js'; 4 | 5 | export default function(context) { 6 | const store = createStore(); 7 | let app = new Vue({ 8 | store, 9 | render: h => h(App) 10 | }); 11 | 12 | // 找到所有 asyncData 方法 13 | let components = App.components; 14 | let asyncDataArr = []; // promise集合 15 | for (let key in components) { 16 | if (!components.hasOwnProperty(key)) continue; 17 | let component = components[key]; 18 | if (component.asyncData) { 19 | asyncDataArr.push(component.asyncData({store})) // 把store传给asyncData 20 | } 21 | } 22 | // 所有请求并行执行 23 | return Promise.all(asyncDataArr).then(() => { 24 | // context.state 赋值成什么,window.__INITIAL_STATE__ 就是什么 25 | context.state = store.state; 26 | return app; 27 | }); 28 | }; --------------------------------------------------------------------------------