项目地址: github.com/xxx-fe/mall…javascript
2019-9-11 更新css
vue,koa应用脚手架,3步创建应用页面html
支持多语言路由,多页应用,Mock,babel7,动态按需加载.前端
/server/router/app/index.js
const page = require('../controller/index');
module.exports = [
{path: '', ctrl: page.home},
{path: 'main', ctrl: page.home},
{path: 'api/list', ctrl: page.list, method: 'post'}
];
复制代码
/server/controller/index.js
const api = require('../api/index');
class page {
async home(ctx, _next) {
let locals = {
appId: 'home',
title: 'home-page'
};
//按需加载下必填,不然可忽略.
ctx.state.appKey = 'home/index';
//使用common通用视图
await ctx.render('pages/common', locals);
}
async list(ctx, _next) {
let locals = {
list: await api.getList(ctx)
};
ctx.body = locals;
}
}
module.exports = new page();
复制代码
/web/pages/app/home/index.vue
...
<script>
export default {
appId: 'home', //必填,入口根据此ID渲染
data () {
return {
list: ''
}
},
mounted(){
this.$http.post('/api/list').then(response => {
console.log(response);
this.list = response.data.list
}, response => {
console.log(response);
})
}
}
</script>
...
复制代码
样式
:scss.库管理
:npm框架
:vue2.模板引擎
:handlebars4.打包
:webpack4.图标
:iconfont,svg-sprite-loader.组件库
:element-ui.框架
:koa2, nodejs>=7.6.0.
├── build # 使用 vue-cli 2.9.3(有修改)
├── config # 使用 vue-cli 2.9.3(有修改)
├── server # 服务端(koa,nodejs)
│ ├── api # 接口
│ ├── controller # 控制器
│ ├── lib # 库
│ │ ├── context # 上下文(动态加载文件)
│ │ ├── middleware # 中间件
│ │ ├── utils # 通用方法
│ │ └── vendor # 第三方插件
│ ├── mock # 中台Mock
│ ├── router # 路由(动态加载文件)
│ ├── view # 视图
│ ├── server.js # 服务端入口
├── dist # 生产目录
├── public # 公共资源
├── web # 前端(vue,js,css...)
│ ├── components # 组件
│ ├── entry # 入口
│ ├── filters # 过滤
│ ├── global # 全局设置
│ ├── mixins # 混入
│ ├── mock # 前台Mock
│ ├── pages # 页面
│ │ └── /**/index.js # app入口
│ ├── styles # 样式
│ ├── webpack.entry.conf.js # 通用入口配置
│ ├── webpack.dev.conf.js # app入口配置
│ └── webpack.pord.conf.js # 其余配置
└── config.yml # 通用配置文件,整个脚手架不少功能都与它有关
复制代码
npm install # npm 安装
复制代码
npm run dev # 启动开发模式(dev)
npm run build # 构建项目
npm run prod # 启动生产模式(prod)
复制代码
/webpack.entry.conf.js
任何模式都有效,通用入口配置.vue
,处在不一样位置.在开发,生产模式webapck构建时自动合并引入webpack.entry.(不作其余属性合并).通常状况不做其余属性修改.java
module.exports ={
header: './web/entry/header.js', //全局头部通用文件(引用vue,全局样式...)
// footer: './web/entry/footer.js', //全局底部通用文件(好比统计数据...)
};
复制代码
header.js
:不支持删除,在生产模式时,紧接着插入manifest.js,vendor.js.node
footer.js
:支持删除.webpack
/webpack.dev.conf.js
开发模式有效,app入口配置.构建会合并全部属性.ios
全部的app入口都在webpack.dev.conf.js
配置. 默认是按需加载.git
module.exports ={
entry: {
'app': './web/pages/app/index.js',
'app2': './web/pages/app2/index.js',
},
//devtool: '#cheap-module-eval-source-map'
...
};
复制代码
合并后的实际入口
entry: {
'app': [
'./web/entry/header.js',
'./web/entry/footer.js' ,
'./web/pages/app/index.js' ,
'webpack-hot-client/client'
],
'app2': [
'./web/entry/header.js',
'./web/entry/footer.js' ,
'./web/pages/app/index.js' ,
'webpack-hot-client/client'
]
}
复制代码
webpack-hot-client/client(热加载)
: 开发模式时每一个入口自动加入.
/webpack.prod.conf.js
生产模式有效,其余配置.构建会合并全部属性.
module.exports ={
...
new ManifestPlugin({
publicPath: '/dist/'
})
...
};
复制代码
合并后的实际入口
entry: {
'app': ['./web/pages/app/index.js'],
'app2': ['./web/pages/app2/index.js'],
header: ['./web/entry/header.js'],
footer: ['./web/entry/footer.js']
}
复制代码
/web/pages/**/index.js
都是app. 这里,app
, app2
2个app,甚至更多,即多页应用.
app
, app2
,分别叫主app,其余app,还能够有另外app...等. 名字随你.
脚手架默认按需加载,能知足大多数app开发,app这个入口通常一个便可.
项目只保留1个app,多app需另建.
handlebars-layouts(模板引擎布局helpers)
/server/view/layout/**.hbs
以文件名注册为handlebars partial.
/server/view/pages/common.hbs
{{#extend "layout-default"}} # 使用layout-default布局 {{#content "head"}} {{{parseUrl 'app.css'}}} # app应用的css,直接引用 {{/content}} # 不须要新建,build时会抽取vue的style成独立的文件.不然生产模式看不到样式. {{#content "body"}} <div id="{{appId}}"></div> # 控制器带过来的ctx.appId {{{parseUrl 'app.js'}}} # app应用的js(相应webpack.entry) {{/content}} {{/extend}} 复制代码
解析url,handlebars自定义helpers.根据当前开发环境返回正确的url.
dev
ctx.state.appName='app'
{{{parseUrl 'app.css' 'app.js'}}}
复制代码
↓↓↓
<script web="app.js"></script>
复制代码
ctx.state.appName=''; 或不设置
↓↓↓
<link href="/dist/static/css/app.[chunkhash].css" type="text/css" rel="stylesheet">
<script web="app.js"></script>
复制代码
prod
<link href="/dist/static/css/app.[chunkhash].css" type="text/css" rel="stylesheet">
<script web="/dist/static/js/app.[chunkhash].js"></script>
复制代码
有这种场景
若是存在多个app如app1,app2.在控制器就须要设置ctx.state.appName ='app的名字'.不然读取样式会不正确.
默认按需加载.ctx.appKey填上相关vue的路径便可.
浏览器端, 整个app的传递信息(ctx.state封装),部分由 /config.yml
合成.
/server/view/layout/layout-default.hbs
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{title}}</title>
{{{mountState}}}
{{{parseUrl 'header.css' 'header.js' }}}
{{#block "head"}}
{{/block}}
</head>
<body>
{{#block "body"}}{{/block}}
</body>
</html>
复制代码
{{{mountState}}} //将ctx.state挂载到window.APPSTATE
复制代码
↓↓↓
<script type="text/javascript">
window.APPSTATE = {
locale:'zh',
publicServer:'',
appName:'app'
}
</script>
复制代码
查看页面源代码通常会看到以上代码.
/config.yml
...
# 是否使用模拟数据api(dev模式有效)
isMockAPI : true
# apiServer api服务器
apiServer : 'http://localhost:3334'
...
复制代码
isMockAPI:true
{{{parseUrl 'header.css' 'header.js'}}}
复制代码
↓↓↓
<script src="/public/vendor/mockjs/dist/mock-min.js"></script>
复制代码
/server/mock/api/list.json
如今请求 /api/list
isMockAPI:true
服务端返回 /server/mock/api/list.json
.
isMockAPI:false
服务端返回 http://localhost:3334/api/list
.
/web/mock/index.js
Mock.mock('/api/list', 'post', function () {
return Mock.mock({
"list|5-10": [{
'name': '@cname',
'imageUrl': 'http://placehold.it/300x150/f69/fff',
'description': '@cname'
}]
});
});
复制代码
优先级:前端Mock文件>后端Mock文件.不然报500.
/config.yml
...
#webpack构建路径(prod模式有效)
buildPath:
# name entry路径
# isIndexEntry 是否使用index.js做为webpack.entry.
# isIndexEntry = true
# './web/pages/app/index.js' --> /dist/static/js/app[chunkhash].js
# 使用index.js上一级目录名做为打包文件名(example.js).
# isIndexEntry = false
# './web/locale/zh.js' --> /dist/static/js/zh[chunkhash].js
# 使用当前文件做为打包文件名(zh.js).
-
name: './web/pages'
isIndexEntry : 'true'
-
name: './web/locale'
...
复制代码
通常状况每个应用都创建在 /web/pages/**/index.js
,以index.js
做为打包入口.
不然,若是有/web/pages/app/index.js
,/web/pages/app2/index.js
,/web/pages/app3/index.js
.就会最终构建出以排序最后的index.js
.
因此,/web/pages/**
,只要目录不重名,而且以index.js
做为入口.就不会冲突.
dev
从这些配置文件打包 /build/webpack.base.conf
, /webpack.entry.conf.js
, /webpack.dev.conf.js
主要从/webpack.dev.conf.js
配置打包开发须要的entry.
prod
从这些配置文件打包 /build/webpack.base.conf
, /webpack.entry.conf.js
, /webpack.prod.conf
, /web/pages/**/index.js
主要从/web/pages/**/index.js
打包全部js.
/config.yml
...
# 多语言路由前缀
locales: ['zh', 'en'[,.]]
# webpack构建路径(entry)
buildPath:
-
#多语言入口
name: './web/locale'
...
复制代码
缺一不可
/web/locale/zh.js
window.locale = {
'desc': 'vue koa 多页应用脚手架'
};
复制代码
/web/locale/en.js
window.locale = {
'desc': 'vue koa scaffold'
};
复制代码
多语言文件会在header.js
以前插入.
/web/utils/locale.js
/** * 获取locale对应的值 */
window.getLocale = function (key) {
if (window.locale) {
return window.locale[key] || '';
}
else {
return key;
}
};
复制代码
...
data() {
return {
list: '',
desc: getLocale('desc')
}
}
...
复制代码
路由则支持
如下路径的文件根据本来代码逻辑,自动引用全部js,无需手动引入.
/server/lib/context/**.js
/server/router/**.js
例如
/test/a.js
返回foo1方法. /test/b.js
返回foo2方法.
/test/index.js
//动态加载文件,无需手动引入
module.exports = {
foo1:function(){},
foo2:function(){}
};
复制代码
/test/index.js
//手动引入
let foo1 = require('./a');
let foo2 = require('./b');
module.exports = {
foo1: a,
foo2: b
};
复制代码
ctx.appKey = 'home/index'
复制代码
按需加载模式的页面路由.require.context根据appKey渲染vue页面.
let locals = {
appId: 'home',
...
};
await ctx.render('pages/common', locals);
复制代码
handlebar模版引擎根据appId返回页面.
ctx.appName = 'app'
复制代码
区分多入口app,避免读取样式不正确.一个app不须要设置.
ctx.logger.info(ctx.router);
复制代码
当前ctx所有匹配的路由.
ctx.axios(ctx, {
url: '/api/xxx',
method: 'post',
data: ctx.request.body
});
复制代码
引用封装 axios 的发起请求方法.
引用 log4js 的日志方法.
ctx.logger.info('示例');
复制代码
ctx.setState(ctx);
复制代码
设置ctx.state自定义通用属性.
路由根据 * /server/router/**/**.js
配置生成路由.
路由路径.
路由控制器.
路由方法.
{path: 'user/profile', ctrl: page.profile, method: 'post', isAuthenticated: true}
复制代码
路由是否须要权限. isAuthenticated:true, 重定向到登陆页面(自定义).
{path: 'captcha', ctrl: page.captcha, noContactToRoute: true} //普通验证码不须要权限验证
复制代码
不合并到ctx.router.
每一个请求都会通过/server/middleware/state-context.js
中间件.但只会匹配不带/api的页面路由.
noContactToRoute:true表示不通过这个中间件.由于state-context
中间件根据ctx.router判断.