简介
Egg
+ Vue
工程化解决方案
参考链接:Egg + Vue 工程化解决方案
安装
方式一:easywebpack-cli 脚手架初始化项目
- 安装脚手架:
npm i easywebpack-cli -g
- 测试是否安装成功:
easywebpack -V
或者easy -V
- 初始化:
easywebpack init
- 安装依赖:
npm i
备注:用了几次都没有成功初始化
方式二:Github 仓库代码初始化项目
git clone https://github.com/hubcarl/egg-vue-webpack-boilerplate.git
- 安装依赖:
npm i
方式三:vscode 插件初始化项目
- 打开
vscode
- 点击左侧插件按钮
- 搜索插件:
vscode-easy-plugin
- 安装插件
- 使用插件初始化
项目介绍
- 目录结构基本上跟
egg.js
一样,遵循egg.js
规范。参考:egg入门笔记 - 前端工程目录:
app/web/
目录 - 页面入口目录:
app/web/page/
目录
渲染模式
egg-view-vue-ssr
目前egg-view-vue-ssr
支持服务端渲染模式和前端渲染模式两种渲染模式
服务端渲染:
- 编写的
Vue
组件在Node
服务端直接编译成HTML
,然后直接输入给浏览器- 使用
render
方法或者renderToHtml
方法实现服务器渲染- 与客户端渲染相比:支持
SEO
、更快的首屏渲染- 与传统的模板引擎相比:更好的组件化、前后端模板共用
- 数据驱动,更快的开发效率
- 适用于:有一定交互性,对SEO、首屏速度有要求的业务
// controller/home.js
module.exports = app => {
return class HomeController extends app.Controller {
async index() {
const { ctx } = this;
await ctx.render('home/home.js', Model.getPage(1, 10));
}
async index2() {
const { ctx } = this;
const html = await ctx.renderToHtml('home/home.js', Model.getPage(1, 10));
// 这里可以处理对渲染后的 HTML 进行处理
ctx.body = html;
}
};
};
客户端渲染:
Node
端只会根据html
、head
、body
节点信息的layout
文件输出骨架内容,页面的实际内容交给浏览器去渲染- 使用
renderClient
方法实现客户端渲染- 使用客户端渲染时,需要有
layout
骨架文件- 默认
renderClient
会对layout HTML
模板进行Vue
编译;如果不想对layout HTML
进行Vue
编译,可以在第三个参数中传入{ viewEngine: null }
- 使用
renderClient
的第三个扩展参数配置layout
,优先级高于全局layout
配置
const Model = require('../../mocks/article/list');
module.exports = app => {
return class HomeController extends app.Controller {
async client() {
const { ctx } = this;
const locals = {
seo: { keyword: 'Egg,Vue,SSR', description: 'Egg Vue SSR Development'},
data: Model.getPage(1, 10)
};
// 如果不想对 layout HTML 进行 Vue 编译,传入第三个参数 { viewEngine: null }
// await ctx.renderClient('home/home.js', locals, { viewEngine: null });
await ctx.renderClient('home/home.js', locals);
}
async client2() {
const { ctx } = this;
// 个性化配置 layout 文件
await ctx.renderClient('home/home.js', {}, { layout: path.join(app.baseDir, 'app/web/page/home/layout.html') });
}
};
};
egg-view-vue-ssr在项目中的使用
- 添加插件在
config/plugin.js
文件中
exports.vuessr = {
enable: true,
package: 'egg-view-vue-ssr'
};
- 插件配置在
config/default.js
文件中
// ...
module.exports = app => {
// ...
exports.vuessr = {
// layout 文件位置。当使用 renderClient 实现客户端渲染时需要这个 layout 文件
// 这只是一个简单的骨架文件,具体内容由前端进行渲染
layout: path.join(app.baseDir, 'app/web/view/layout.html'),
renderOptions: {
// ctx.renderClient('home/home.js', locals);
// 这里的 home/home.js 就是基于这个目录 即:app/view/home/home.js
// app/view/ 目录下的文件会根据 app/web/page/ 目录下文件生成,即对应的 app/web/page/home/home.vue
basedir: path.join(app.baseDir, 'app/view')
},
// 渲染时会自动将 这些 css、js 依赖注入到 layout 中
injectRes:[
{
url: 'https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.0.2/css/swiper.min.css'
},
{
url: 'https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.0.2/js/swiper.min.js'
}
]
};
// ...
}
多页面服务器端渲染
比如首页
和test
页面
- 配置路由:修改
app/router.js
文件
'use strict';
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.index.index.index);
router.get('/test', controller.index.index.test);
// ...
};
- 配置控制器:修改
app/controller/index/index.js
文件
'usestrict';
const egg = require('egg');
module.exports = class IndexController extends egg.Controller {
async index() {
const { ctx, service} = this
const result = service.article.getArtilceList();
await ctx.render('index/index.js', result);
}
async test() {
const { ctx, service} = this
const result = service.article.getArtilceList();
await ctx.render('test/index.js', result);
}
};
- 添加首页前端文件:添加
app/web/page/index/index.vue
文件
<template>
<layout title="test" description="vue server side render" keywords="egg, vue, webpack, server side render">
home
</layout>
</template>
<script type="text/babel">
export default {
mounted() {
console.log('mounted: this.list', this.list)
}
}
</script>
<style>
/* @import "index.css"; */
</style>
- 添加
test
页面前端文件:添加app/web/page/test/index.vue
文件
<template>
<layout>
test
</layout>
</template>
备注
- 服务器端渲染会直接将
vue
文件解析成html
代码渲染- 需要有一个骨架,这里使用的是
layout
组件。整体步骤如下
- 执行渲染首先会进入到
app/web/framework/entry/template.js
文件中
import Layout from 'component/layout/index';
import plugin from 'framework/plugin';
export default function(Vue) {
// 添加国际化,挂载 $request 等
Vue.use(plugin);
// 引入 Layout 并注册成一个组件
// 在页面中就可以使用 Layout 这个组件了
Vue.component(Layout.name, Layout);
}
- 在
app/web/framework/entry/template.js
文件中注册了一个Layout
组件
import Vue from 'vue';
import MainLayout from './main.vue';
import '../../../asset/css/bootstrap.css';
import '../../../asset/css/blog.css';
import createLayout from '../layout';
// 这里使用 createLayout 方法渲染出来一个骨架
export default createLayout('Layout', { MainLayout }, '<div id="app" data-server-rendered="true"><MainLayout><div slot="main"><slot></slot></div></MainLayout></div>');
多页面客户端渲染
-
将控制器中的
render
方法或者renderToHtml
方法修改成renderClient
方法即可 -
客户端渲染会使用
app/web/page/home/layout.html
作为骨架文件进行渲染,所以不需要再使用layout
组件(当然使用也不会报错)
<!-- app/web/page/test/index.vue -->
<template>
<div>
test
</div>
</template>
单页面服务器端渲染
比如后台admin
- 配置路由:修改
app/router.js
文件
// ...
module.exports = app => {
const { router, controller } = app;
// ...
router.post('/admin/api/article/list', controller.admin.admin.list); // 后台的接口
router.post('/admin/api/article/add', controller.admin.admin.add);
router.get('/admin/api/article/del/:id', controller.admin.admin.del);
router.get('/admin/api/article/:id', controller.admin.admin.detail);
router.get('/admin(/.+)?', controller.admin.admin.home); // 后台对应的前端路由
// ...
};
- 修改
app/controller/admin/admin.js
文件
'use strict';
const egg = require('egg');
module.exports = class AdminController extends egg.Controller {
async home(ctx) {
const url = ctx.url.replace(/\/admin/, '');
await ctx.render('admin/home/home.js', { ctx, url, title: 'easy-admin' });
}
async list(ctx) {
this.ctx.body = ctx.service.article.getArtilceList(ctx.request.body);
}
async add(ctx) {
ctx.body = this.service.article.saveArticle(ctx.request.body);
}
async del(ctx) {
const { id } = ctx.params;
ctx.body = this.service.article.deleteArticle(id);
}
async detail(ctx) {
const id = ctx.query.id;
ctx.body = {};
}
};
- 在
app/web/page/admin/home/
目录下添加home.vue
文件
<template>
<AdminLayout :title="title">
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</AdminLayout>
</template>
<script type="text/babel">
import Vue from 'vue';
import ElementUI from 'element-ui';
import VueI18n from 'vue-i18n';
import 'element-ui/lib/theme-chalk/index.css';
import createI18n from 'framework/i18n/admin';
import store from './store/app';
import router from './router';
import AdminLayout from 'component/layout/admin';
Vue.use(VueI18n);
Vue.use(ElementUI);
export default {
router,
store,
components: {
AdminLayout,
},
// data() {
// return {
// title: this.$store.state.title
// }
// },
computed: {
title() {
return this.$store.state.title;
}
},
hook :{
render(context, options) {
const i18n = createI18n(context.state.locale);
options.i18n = i18n;
}
},
mounted() {},
};
</script>
备注:
app/web/page/admin/home/
目录就相当于单页面应用中的src/
目录- 在该目录下新建
router/
、store/
、view/
、component/
目录等- 控制器中使用的是
render
方法进行服务器端渲染。所以需要一个layout
。这里是app/web/component/layout/admin/index.js
发表评论