項目地址: 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判斷.