├── .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 |
2 |
3 |
{{article.title}}
4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
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 | };
--------------------------------------------------------------------------------