webpack.server.config.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const base = require('./webpack.base.conf');
module.exports = merge(base,{
target:"node",
entry:"./src/entry-server.js",
output:{
filename:"bundle.server.js",
libraryTarget:"commonjs2"
},
plugins:[
]
})
webpack.client.config.js
const webpack = require("webpack");
const vueLoaderConfig = require('./vue-loader.conf');
const utils = require('./utils')
const path = require("path");
function resolve(dir){
return path.join(__dirname,"..",dir);
}
module.exports = {
entry: {
app: './src/entry-client.js'
},
output: {
path: path.resolve(__dirname,'../dist'),
filename: 'bundle.client.js',
publicPath: '/dist/'
},
plugins:[
// new VueSSRServerPlugin();
],
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}
entry-server.js
import { createApp } from './main';
export default context => {
return new Promise((resolve,reject) =>{
const { app,router,store } = createApp()
const url = context.url;
router.push(url);
router.onReady(()=>{
const matchedComponents = router.getMatchedComponents();
if(!matchedComponents.length){
reject({code:404})
}
let asyncDataPromiseFns = [];
matchedComponents.map( component => {
if(component.serverRequest){
asyncDataPromiseFns.push(component.serverRequest(store));
}
})
//遍历路由下所有的组件,如果有需要服务端渲染的请求,则进行请求
Promise.all(asyncDataPromiseFns).then(()=>{
context.state = store.state;
resolve(app);
}).catch(reject)
},reject)
})
}
entry-client.js
//客户端的入口文件
import { createApp } from './main';
const { app,router,store } = createApp();
if(window.__INITIAL_STATE__){
store.replaceState(window.__INITIAL_STATE__);
}
window.onload = function(){
app.$mount('#app');
}
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
// import router from './router'
import createRouter from "./router";
import createStore from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
export function createApp () {
const router = createRouter();
const store = createStore();
const app = new Vue({
router,
store,
// 根实例简单的渲染应用程序组件。
render: h => h(App)
})
return { app,router,store,App }
}
路由配置文件
import Vue from 'vue'
import Router from 'vue-router'
import home from '@/components/home'
import about from '@/components/about'
Vue.use(Router)
export default () => {
return new Router({
mode: 'history', //一定要是history模式
routes: [
{
path: '/',
name: 'home',
component: home
},
{
path: '/about',
name: 'about',
component: about
}
]
})
}
Vuex配置文件
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex);
export default function createStore(){
let store = new Vuex.Store({
state:{
homeInfo:''
},
actions:{
getHomeInfo({commit}){
return new Promise((resolve,reject)=>{
axios.get("api地址").then(res=>{
commit('setHomeInfo',res.data);
resolve();
}).catch(reject);
})
}
},
mutations:{
setHomeInfo(state,data){
state.homeInfo = data;
}
}
})
return store;
}
请求数据的页面
<template>
<div class="home">
<h1>这是首页</h1>
<div>{{homeInfo.message}}</div>
</div>
</template>
<script>
export default {
data() {
return {
}
},
serverRequest(store){
return store.dispatch('getHomeInfo')
},
computed:{
homeInfo(){
return this.$store.state.homeInfo;
}
}
}
</script>
package.json的scripts配置信息
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js",
"server": "webpack --config build/webpack.server.config.js",
"client": "webpack --config build/webpack.client.config.js"
}
服务器文件
const Vue = require('vue');
const Koa = require('koa');
const server = new Koa();
const path = require("path");
const koaStatic = require('koa-static');
const router = require('koa-router')();
//创建服务端渲染器
const renderer = require("vue-server-renderer").createRenderer();
const createApp = require('./dist/bundle.server.js')['default'];
//设置静态资源目录
server.use(koaStatic(path.resolve(__dirname,"./dist")))
const clientBundleFileUrl = "/bundle.client.js";
router.get('/text',async ctx=>{
ctx.body={
code:1,
message:"我是服务器返回的测试内容!"
}
})
router.get('*',async ctx=>{
const context = { url:ctx.request.url };
try{
const app = await createApp(context);
let state = JSON.stringify(context.state);
const resHtml = await renderer.renderToString(app);
ctx.body=`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Vue2.0 SSR渲染页面</title>
<script>window.__INITIAL_STATE__=${state}</script>
<script src="${clientBundleFileUrl}"></script>
</head>
<body>
${resHtml}
</body>
</html>
`;
}catch(err){
ctx.body="程序运行出错,请联系管理员进行处理!"
}
})
server.use(router.routes());
server.use(router.allowedMethods());
server.listen(3000,()=>{
console.log("服务已启动!");
});
利用到的插件
vue-server-renderer
网友评论