├── .babelrc ├── .gitignore ├── .vscode └── launch.json ├── Icon ├── README.md ├── build ├── app.js ├── config │ └── config.js ├── controller │ ├── indexController.js │ └── initController.js ├── models │ └── indexmodel.js ├── public │ ├── css │ │ └── vendor.css │ └── scripts │ │ ├── common │ │ └── vendor.min.js │ │ ├── index.js │ │ └── tags.js ├── test │ └── server.js ├── views │ ├── 404.html │ ├── index.html │ ├── layout.html │ └── star.html └── widget │ ├── index.html │ └── star.html ├── config ├── webpack.dev.js └── webpack.prod.js ├── geckodriver ├── gulpfile.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── praise.php ├── readme img ├── 0.要求.png ├── 1.CDN预加载配置.png ├── 2.扩展星星组件,首页直出并用Pjax配置SPA.png ├── 3-1.localStorage缓存.png ├── 3-2.用localforage实现的缓存负载均衡ORM库.png ├── 3-3.离线资源配置manifest.png ├── 4.Prod上线版本动态分配CDN.png └── 5.lazyload并行加载静态资源.png ├── src ├── app-o.js ├── app.es ├── config │ ├── config.es │ └── config.js ├── controller │ ├── indexController.es │ ├── indexController.js │ ├── initController.es │ └── initController.js ├── models │ ├── indexmodel.es │ └── indexmodel.js ├── public │ ├── css │ │ └── index.css │ └── scripts │ │ ├── index-es.es │ │ ├── index-es.js │ │ ├── indexadd.js │ │ ├── star.es │ │ └── tags.es ├── test │ ├── e2e.js │ ├── index.spec.js │ ├── server.es │ └── server.js ├── views │ ├── index.js │ ├── star.js │ └── webAssetsHelp.js └── widget │ ├── 404.html │ ├── index.html │ ├── layout.html │ └── star.html └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets":[ 3 | "es2015", 4 | "stage-0" 5 | ], 6 | "plugins":[] 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略 node_modules/ 文件夹下的所有文件 2 | node_modules/ 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8080", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Icon : -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/Icon -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Performance 2 | 前端性能优化:动态CDN,CDN预加载,Pjax实现SPA,缓存负载均衡,ORM库,离线包manifest,lazyloadjs静态资源并行分发器 3 | 4 | 0.实现目标 5 |
6 | ![Image text](https://github.com/yllg/Performance/blob/master/readme%20img/0.%E8%A6%81%E6%B1%82.png) 7 |
8 | 1.CDN预加载配置 9 |
10 | ![Image text](https://github.com/yllg/Performance/blob/master/readme%20img/1.CDN%E9%A2%84%E5%8A%A0%E8%BD%BD%E9%85%8D%E7%BD%AE.png) 11 |
12 | 2.扩展星星组件,首页直出并用Pjax配置SPA 13 |
14 | ![Image text](https://github.com/yllg/Performance/blob/master/readme%20img/2.%E6%89%A9%E5%B1%95%E6%98%9F%E6%98%9F%E7%BB%84%E4%BB%B6%EF%BC%8C%E9%A6%96%E9%A1%B5%E7%9B%B4%E5%87%BA%E5%B9%B6%E7%94%A8Pjax%E9%85%8D%E7%BD%AESPA.png) 15 |
16 | 3-1.localStorage缓存 17 |
18 | ![Image text](https://github.com/yllg/Performance/blob/master/readme%20img/3-1.localStorage%E7%BC%93%E5%AD%98.png) 19 |
20 | 3-2.用localforage实现的缓存负载均衡ORM库 21 |
22 | ![Image text](https://github.com/yllg/Performance/blob/master/readme%20img/3-2.%E7%94%A8localforage%E5%AE%9E%E7%8E%B0%E7%9A%84%E7%BC%93%E5%AD%98%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1ORM%E5%BA%93.png) 23 |
24 | 3-3.离线资源配置manifest 25 |
26 | ![Image text](https://github.com/yllg/Performance/blob/master/readme%20img/3-3.%E7%A6%BB%E7%BA%BF%E8%B5%84%E6%BA%90%E9%85%8D%E7%BD%AEmanifest.png) 27 |
28 | 4.Prod上线版本动态分配CDN 29 |
30 | ![Image text](https://github.com/yllg/Performance/blob/master/readme%20img/4.Prod%E4%B8%8A%E7%BA%BF%E7%89%88%E6%9C%AC%E5%8A%A8%E6%80%81%E5%88%86%E9%85%8DCDN.png) 31 |
32 | 5.lazyload并行加载静态资源 33 |
34 | ![Image text](https://github.com/yllg/Performance/blob/master/readme%20img/5.lazyload%E5%B9%B6%E8%A1%8C%E5%8A%A0%E8%BD%BD%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90.png) 35 |
36 | -------------------------------------------------------------------------------- /build/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _koa = require('koa'); 8 | 9 | var _koa2 = _interopRequireDefault(_koa); 10 | 11 | var _koaSimpleRouter = require('koa-simple-router'); 12 | 13 | var _koaSimpleRouter2 = _interopRequireDefault(_koaSimpleRouter); 14 | 15 | var _initController = require('./controller/initController'); 16 | 17 | var _initController2 = _interopRequireDefault(_initController); 18 | 19 | var _koaSwig = require('koa-swig'); 20 | 21 | var _koaSwig2 = _interopRequireDefault(_koaSwig); 22 | 23 | var _co = require('co'); 24 | 25 | var _co2 = _interopRequireDefault(_co); 26 | 27 | var _koaStatic = require('koa-static'); 28 | 29 | var _koaStatic2 = _interopRequireDefault(_koaStatic); 30 | 31 | var _register = require('babel-core/register'); 32 | 33 | var _register2 = _interopRequireDefault(_register); 34 | 35 | var _babelPolyfill = require('babel-polyfill'); 36 | 37 | var _babelPolyfill2 = _interopRequireDefault(_babelPolyfill); 38 | 39 | var _config = require('./config/config'); 40 | 41 | var _config2 = _interopRequireDefault(_config); 42 | 43 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 44 | 45 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 46 | //引入路由,自己写的控制器 47 | 48 | //引入swig模板 49 | 50 | //引入静态资源配置 51 | 52 | //引入babel的另两个文件 53 | 54 | //引入配置文件 55 | 56 | 57 | var app = new _koa2.default(); 58 | 59 | //初始化路由设置 60 | _initController2.default.init(app, _koaSimpleRouter2.default); 61 | 62 | //模板引擎设置 63 | app.context.render = _co2.default.wrap((0, _koaSwig2.default)({ 64 | root: _config2.default.get('viewDir'), 65 | autoescape: true, 66 | cache: 'memory', // disable, set to false 67 | ext: 'html', 68 | writeBody: false 69 | })); 70 | 71 | //静态资源设置 72 | app.use((0, _koaStatic2.default)(_config2.default.get('staticDir'))); 73 | 74 | //路由容错处理 75 | // 404,跳到腾讯公益页面 76 | app.use(function () { 77 | var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(ctx) { 78 | return regeneratorRuntime.wrap(function _callee$(_context) { 79 | while (1) { 80 | switch (_context.prev = _context.next) { 81 | case 0: 82 | ctx.status = 404; 83 | _context.next = 3; 84 | return ctx.render('404.html'); 85 | 86 | case 3: 87 | ctx.body = _context.sent; 88 | 89 | case 4: 90 | case 'end': 91 | return _context.stop(); 92 | } 93 | } 94 | }, _callee, undefined); 95 | })); 96 | 97 | return function (_x) { 98 | return _ref.apply(this, arguments); 99 | }; 100 | }() 101 | // ctx.redirect("http://www.yidengxuetang.com/"); 102 | ); 103 | // 500,跳到一等学堂 104 | app.on('error', function (err, ctx) { 105 | // ctx.body = "😓 服务器开了小差......"; 106 | ctx.redirect("http://www.yidengxuetang.com/"); 107 | }); 108 | 109 | app.listen(_config2.default.get('port')); 110 | exports.default = app; -------------------------------------------------------------------------------- /build/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _path = require('path'); 8 | 9 | var _path2 = _interopRequireDefault(_path); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | var CONFIG = new Map(); 14 | CONFIG.set('port', 3000); 15 | CONFIG.set('staticDir', _path2.default.join(__dirname, '..')); 16 | CONFIG.set('viewDir', _path2.default.join(__dirname, '..', 'views')); 17 | exports.default = CONFIG; -------------------------------------------------------------------------------- /build/controller/indexController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _indexmodel = require('../models/indexmodel'); 8 | 9 | var _indexmodel2 = _interopRequireDefault(_indexmodel); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } //引入model层的方法 14 | 15 | 16 | //路由的方法 17 | var indexController = { 18 | index: function index() { 19 | var _this = this; 20 | 21 | return function () { 22 | var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(ctx, next) { 23 | return regeneratorRuntime.wrap(function _callee$(_context) { 24 | while (1) { 25 | switch (_context.prev = _context.next) { 26 | case 0: 27 | _context.next = 2; 28 | return ctx.render('index.html', { 29 | title: '首页' 30 | }); 31 | 32 | case 2: 33 | ctx.body = _context.sent; 34 | 35 | case 3: 36 | case 'end': 37 | return _context.stop(); 38 | } 39 | } 40 | }, _callee, _this); 41 | })); 42 | 43 | return function (_x, _x2) { 44 | return _ref.apply(this, arguments); 45 | }; 46 | }(); 47 | }, 48 | update: function update() { 49 | var _this2 = this; 50 | 51 | return function () { 52 | var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(ctx, next) { 53 | var indexM; 54 | return regeneratorRuntime.wrap(function _callee2$(_context2) { 55 | while (1) { 56 | switch (_context2.prev = _context2.next) { 57 | case 0: 58 | indexM = new _indexmodel2.default(ctx); 59 | _context2.next = 3; 60 | return indexM.updateNum(); 61 | 62 | case 3: 63 | ctx.body = _context2.sent; 64 | 65 | case 4: 66 | case 'end': 67 | return _context2.stop(); 68 | } 69 | } 70 | }, _callee2, _this2); 71 | })); 72 | 73 | return function (_x3, _x4) { 74 | return _ref2.apply(this, arguments); 75 | }; 76 | }(); 77 | }, 78 | star: function star() { 79 | var _this3 = this; 80 | 81 | return function () { 82 | var _ref3 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(ctx, next) { 83 | return regeneratorRuntime.wrap(function _callee3$(_context3) { 84 | while (1) { 85 | switch (_context3.prev = _context3.next) { 86 | case 0: 87 | if (!ctx.request.header['x-pjax']) { 88 | _context3.next = 4; 89 | break; 90 | } 91 | 92 | ctx.body = ""; 93 | _context3.next = 7; 94 | break; 95 | 96 | case 4: 97 | _context3.next = 6; 98 | return ctx.render('star.html', { 99 | title: '星星组件页' 100 | }); 101 | 102 | case 6: 103 | ctx.body = _context3.sent; 104 | 105 | case 7: 106 | case 'end': 107 | return _context3.stop(); 108 | } 109 | } 110 | }, _callee3, _this3); 111 | })); 112 | 113 | return function (_x5, _x6) { 114 | return _ref3.apply(this, arguments); 115 | }; 116 | }(); 117 | }, 118 | praise: function praise() { 119 | var _this4 = this; 120 | 121 | return function () { 122 | var _ref4 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(ctx, next) { 123 | return regeneratorRuntime.wrap(function _callee4$(_context4) { 124 | while (1) { 125 | switch (_context4.prev = _context4.next) { 126 | case 0: 127 | if (!ctx.request.header['x-pjax']) { 128 | _context4.next = 4; 129 | break; 130 | } 131 | 132 | ctx.body = ""; 133 | _context4.next = 7; 134 | break; 135 | 136 | case 4: 137 | _context4.next = 6; 138 | return ctx.render('index.html', { 139 | title: '首页' 140 | }); 141 | 142 | case 6: 143 | ctx.body = _context4.sent; 144 | 145 | case 7: 146 | case 'end': 147 | return _context4.stop(); 148 | } 149 | } 150 | }, _callee4, _this4); 151 | })); 152 | 153 | return function (_x7, _x8) { 154 | return _ref4.apply(this, arguments); 155 | }; 156 | }(); 157 | }, 158 | advertisement: function advertisement() { 159 | var _this5 = this; 160 | 161 | return function () { 162 | var _ref5 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee5(ctx, next) { 163 | return regeneratorRuntime.wrap(function _callee5$(_context5) { 164 | while (1) { 165 | switch (_context5.prev = _context5.next) { 166 | case 0: 167 | ctx.body = '
...大幅广告...
'; 168 | 169 | case 1: 170 | case 'end': 171 | return _context5.stop(); 172 | } 173 | } 174 | }, _callee5, _this5); 175 | })); 176 | 177 | return function (_x9, _x10) { 178 | return _ref5.apply(this, arguments); 179 | }; 180 | }(); 181 | } 182 | }; 183 | exports.default = indexController; -------------------------------------------------------------------------------- /build/controller/initController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _indexController = require('./indexController'); 8 | 9 | var _indexController2 = _interopRequireDefault(_indexController); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | var controllerInit = { 14 | init: function init(app, router) { 15 | app.use(router(function (_) { 16 | //使用indexController里面的index方法 17 | _.get('/', function (ctx, next) { 18 | ctx.redirect('/index/index'); 19 | }), _.get('/index', function (ctx, next) { 20 | ctx.redirect('/index/index'); 21 | }), _.get('/index/index', _indexController2.default.index()), _.get('/index/update', _indexController2.default.update()), _.get('/index/star', _indexController2.default.star()), _.get('/index/praise', _indexController2.default.praise()), _.get('/index/adv', _indexController2.default.advertisement()); 22 | })); 23 | } 24 | }; //注册路由 25 | exports.default = controllerInit; -------------------------------------------------------------------------------- /build/models/indexmodel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _requestPromise = require('request-promise'); 10 | 11 | var _requestPromise2 = _interopRequireDefault(_requestPromise); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | var indexModel = function () { 18 | function indexModel(ctx) { 19 | _classCallCheck(this, indexModel); 20 | 21 | this.ctx = ctx; 22 | } 23 | //model层调用php的接口 24 | 25 | 26 | _createClass(indexModel, [{ 27 | key: 'updateNum', 28 | value: function updateNum() { 29 | var options = { 30 | uri: 'http://localhost:8080/praise.php', 31 | method: 'GET' 32 | }; 33 | return new Promise(function (resolve, reject) { 34 | (0, _requestPromise2.default)(options).then(function (result) { 35 | var info = JSON.parse(result); 36 | if (info) { 37 | resolve({ data: info.result }); 38 | } else { 39 | reject({}); 40 | } 41 | }); 42 | }); 43 | } 44 | }]); 45 | 46 | return indexModel; 47 | }(); 48 | //导出给indexController里 49 | 50 | 51 | exports.default = indexModel; -------------------------------------------------------------------------------- /build/public/css/vendor.css: -------------------------------------------------------------------------------- 1 | body{background-image:linear-gradient(90deg,#cce6c9 0,#fff)}#wrap{left:50%;transform:translate(-50%);top:35%;width:220px}#PraiseButton,#wrap{position:absolute;height:130px}#PraiseButton{left:75px;width:125px;background:#ffcaa4;border-radius:60px 40px 40px 40px}#PraiseButton:after,#PraiseButton:before{display:block;content:"";position:absolute}#PraiseButton:before{top:35px;left:60px;-webkit-box-reflect:above 1px;box-reflect:above 1px}#PraiseButton:after,#PraiseButton:before{width:80px;height:30px;border-radius:25px 25px 25px 25px;background-image:linear-gradient(90deg,#ffcaa4 15px,#efdbcd 40px,#ffcaa4 100px)}#PraiseButton:after{top:66px;left:55px;-webkit-box-reflect:below 1px;box-reflect:below 1px}#Thumb{position:absolute;top:30px;width:90px;height:80px;background:#ffcaa4}#Thumb:before{display:block;content:"";position:absolute;width:110px;height:30px;top:-55px;left:73px;transform:rotate(-65deg);border-radius:25px 25px 235px 255px;background-image:linear-gradient(90deg,#ffcaa4 60px,#efdbcd 80px,#ffcaa4 100px)}.hide{display:none}.num{display:block!important;-webkit-animation:a .8s ease-in-out alternate;animation:a .8s ease-in-out alternate;-webkit-animation-iteration-count:1;font-size:40px;color:#f17;position:absolute;right:-10%;top:-45%}@-webkit-keyframes a{0%{opacity:0;-webkit-transform:translateY(-20px);text-shadow:0 0 10px #fff,0 0 20px #fff,0 0 30px #fff,0 0 40px #f17,0 0 70px #f17,0 0 80px #f17,0 0 100px #f17,0 0 150px #f17}to{opacity:1;-webkit-transform:translateY(0);text-shadow:0 0 5px #fff,0 0 10px #fff,0 0 15px #fff,0 0 30px #f17,0 0 35px #f17,0 0 40px #f17,0 0 50px #f17,0 0 75px #f17}}.star{margin:300px 50%;top:-30%;left:-8%;color:red;z-index:1;border-right:100px solid transparent;border-bottom:70px solid red;border-left:100px solid transparent;-moz-transform:rotate(35deg);-webkit-transform:rotate(35deg);-ms-transform:rotate(35deg);-o-transform:rotate(35deg)}.star,.star:before{position:absolute;display:block;width:0;height:0}.star:before{border-bottom:80px solid red;border-left:30px solid transparent;border-right:30px solid transparent;top:-45px;left:-65px;content:"";-webkit-transform:rotate(-35deg);-moz-transform:rotate(-35deg);-ms-transform:rotate(-35deg);-o-transform:rotate(-35deg)}.star1{position:relative;display:block;color:red;top:3px;left:-105px;width:0;height:0;border-right:100px solid transparent;border-bottom:70px solid red;border-left:100px solid transparent;-webkit-transform:rotate(-70deg);-moz-transform:rotate(-70deg);-ms-transform:rotate(-70deg);-o-transform:rotate(-70deg)} -------------------------------------------------------------------------------- /build/public/scripts/common/vendor.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(n){if(o[n])return o[n].exports;var r=o[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n=window.webpackJsonp;window.webpackJsonp=function(o,i,c){for(var u,a,f,l=0,s=[];l
+1',methods:{praise:function(){var i=this;s.clickAction();var t=i.querySelector("#animation");t.className="hide num",setTimeout(function(){t.className="hide"},800)}},events:{click:function(i){var t=this;if("Thumb"==i.target.id||"PraiseButton"==i.target.id){var a="";a&&clearTimeout(a),a=setTimeout(function(){t.praise()},500)}}}})},function(i,t,a){"use strict";var e=a(0),s=new e.Star;xtag.register("x-star",{content:'
+1',methods:{praise:function(){var i=this;s.clickAction();var t=i.querySelector("#animation");t.className="hide num",setTimeout(function(){t.className="hide"},800)}},events:{click:function(i){var t=this;if("star"==i.target.id){var a="";a&&clearTimeout(a),a=setTimeout(function(){t.praise()},500)}}}})}],[4]); -------------------------------------------------------------------------------- /build/test/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _supertest = require('supertest'); 4 | 5 | var _supertest2 = _interopRequireDefault(_supertest); 6 | 7 | var _appO = require('../app-o'); 8 | 9 | var _appO2 = _interopRequireDefault(_appO); 10 | 11 | function _interopRequireDefault(obj) { 12 | return obj && obj.__esModule ? obj : { default: obj }; 13 | } 14 | 15 | //获得监听的端口 16 | function request() { 17 | return (0, _supertest2.default)(_appO2.default.listen()); 18 | } 19 | 20 | //进行测试哦 21 | describe('测试路由', function () { 22 | it('点赞', function (done) { 23 | request().get('/index/update').expect(200).end(function (err, res) { 24 | if (res.data == 1) return done(err); 25 | done(); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /build/views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build/views/index.html: -------------------------------------------------------------------------------- 1 | {% extends './layout.html' %}{% block title %}KOA2{{title}}{% endblock %}{% block styles %}{% endblock %}{% block content %}{% include '../widget/index.html' %}{% endblock %}{% block script %}{% endblock %} -------------------------------------------------------------------------------- /build/views/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}My Site{% endblock %} 8 | {% block head %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% endblock %} {% block styles %}{% endblock %} 17 | 18 | 19 | 20 | 跳转到星星 21 | 跳转到大拇指点赞 22 |
{% block content %}{% endblock %}
23 | 43 | 44 | {% block script %}{% endblock %} 45 | 46 | -------------------------------------------------------------------------------- /build/views/star.html: -------------------------------------------------------------------------------- 1 | {% extends './layout.html' %}{% block title %}KOA2{{title}}{% endblock %}{% block styles %}{% endblock %}{% block content %}{% include '../widget/star.html' %}{% endblock %}{% block script %}{% endblock %} -------------------------------------------------------------------------------- /build/widget/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
JS与QA 三大测试: 6 |
单元测试:karma start 7 |
接口测试:npm run mochatest 8 |
e2e测试:npm run e2etest 9 |
10 |
nodejs KOA2: 11 |
重定向:根目录/跳转到/index/index 12 |
容错处理: 13 |
404跳到腾讯公益“找小孩”页面; 14 |
500跳到一灯官网; 15 |
16 |
工程化和持续构建: 17 |
运行gulp,打包编译后台文件,监视文件变化自动打包更新 18 |
运行webpack -w 默认用dev版打包文件,并监视前端文件变化重新打包 19 |
进入build目录运行node app.js 启动服务,默认3000端口 20 |
21 |
前端性能优化: 22 |
点击左上角两个a连接,可以实现跳转 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /build/widget/star.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const LiveReloadPlugin = require('webpack-livereload-plugin'); 4 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const Manifest = require('webpack-manifest'); 7 | 8 | module.exports = { 9 | entry: { 10 | index: [ 11 | path.join(__dirname, '../src/public/scripts/index-es.es'), 12 | path.join(__dirname, '../src/public/scripts/indexadd.js') 13 | ], 14 | tags: [ 15 | path.join(__dirname, '../src/public/scripts/tags.es'), 16 | path.join(__dirname, '../src/public/scripts/star.es') 17 | ] 18 | }, 19 | output: { 20 | filename: 'public/scripts/[name].js', 21 | path: path.join(__dirname, '../build') 22 | }, 23 | module: { 24 | rules: [{ 25 | test: /\.es$/, 26 | exclude: /(node_modules|bower_components)/, 27 | use: { 28 | loader: 'babel-loader', 29 | options: { 30 | presets: ['es2015', 'stage-0'] 31 | } 32 | } 33 | }, 34 | { 35 | test: /\.css$/, 36 | use: ExtractTextPlugin.extract({ 37 | fallback: "style-loader", 38 | use: "css-loader" 39 | }) 40 | } 41 | ] 42 | }, 43 | plugins: [ 44 | new webpack.DefinePlugin({ 45 | 'process.env': { 46 | NODE_ENV: 'dev' 47 | } 48 | }), 49 | new LiveReloadPlugin({ 50 | appendScriptTag: true 51 | }), 52 | new ExtractTextPlugin("public/css/[name].css"), 53 | new webpack.optimize.CommonsChunkPlugin({ 54 | name: 'vendor', 55 | filename: 'public/scripts/common/[name].min.js', 56 | }), 57 | new HtmlWebpackPlugin({ 58 | filename: './views/layout.html', 59 | template: 'src/widget/layout.html', 60 | inject: false 61 | }), 62 | new HtmlWebpackPlugin({ 63 | filename: './views/index.html', 64 | template: 'src/views/index.js', 65 | inject: false, 66 | chunks: ['vendor', 'index', 'tags'] 67 | }), 68 | new HtmlWebpackPlugin({ 69 | filename: './widget/index.html', 70 | template: 'src/widget/index.html', 71 | inject: false 72 | }), 73 | new HtmlWebpackPlugin({ 74 | filename: './views/star.html', 75 | template: 'src/views/star.js', 76 | inject: false, 77 | chunks: ['vendor', 'index', 'tags'] 78 | }), 79 | new HtmlWebpackPlugin({ 80 | filename: './widget/star.html', 81 | template: 'src/widget/star.html', 82 | inject: false 83 | }), 84 | new HtmlWebpackPlugin({ 85 | filename: './views/404.html', 86 | template: 'src/widget/404.html', 87 | inject: false 88 | }), 89 | new Manifest({ 90 | cache: [ 91 | '../public/css/vendor.css', 92 | '../public/scripts/common/vendor.min.js', 93 | '../public/scripts/tag.js', 94 | '../public/scripts/index.js', 95 | ], 96 | //Add time in comments. 97 | timestamp: true, 98 | // 生成的文件名字,选填 99 | // The generated file name, optional. 100 | filename: 'cache.manifest', 101 | // 注意*星号前面用空格隔开 102 | network: [ 103 | 'http://cdn.bootcss.com/ *', 104 | 'http://localhost:35729/livereload.js', 105 | 'http://localhost:3000/ *' 106 | ], 107 | // 注意中间用空格隔开 108 | // fallback: ['/ /404.html'], 109 | // manifest 文件中添加注释 110 | // Add notes to manifest file. 111 | headcomment: "koatesting", 112 | master: ['../views/layout.html'] 113 | }) 114 | ] 115 | }; -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const LiveReloadPlugin = require('webpack-livereload-plugin'); 4 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 5 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 6 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | 8 | module.exports = { 9 | entry: { 10 | index: [ 11 | path.join(__dirname, '../src/public/scripts/index-es.es'), 12 | path.join(__dirname, '../src/public/scripts/indexadd.js') 13 | ], 14 | tags: [ 15 | path.join(__dirname, '../src/public/scripts/tags.es'), 16 | path.join(__dirname, '../src/public/scripts/star.es') 17 | ] 18 | }, 19 | output: { 20 | filename: 'public/scripts/[name].js', 21 | publicPath: 'http://192.168.31.25:3000/', 22 | path: path.join(__dirname, '../build') 23 | }, 24 | module: { 25 | rules: [{ 26 | test: /\.es$/, 27 | exclude: /(node_modules|bower_components)/, 28 | use: { 29 | loader: 'babel-loader', 30 | options: { 31 | presets: ['es2015', 'stage-0'] 32 | } 33 | } 34 | }, 35 | { 36 | test: /\.css$/, 37 | use: ExtractTextPlugin.extract({ 38 | fallback: "style-loader", 39 | use: "css-loader" 40 | }) 41 | } 42 | ] 43 | }, 44 | plugins: [ 45 | new webpack.DefinePlugin({ 46 | 'process.env': { 47 | NODE_ENV: 'prod' 48 | } 49 | }), 50 | new LiveReloadPlugin({ 51 | appendScriptTag: true 52 | }), 53 | new ExtractTextPlugin("public/css/[name].css"), 54 | new webpack.optimize.UglifyJsPlugin({ 55 | compress:{ 56 | warnings:false, 57 | drop_console:false 58 | } 59 | }), 60 | new OptimizeCssAssetsPlugin({ 61 | assetNameRegExp: /.css$/g, 62 | cssProcessor: require('cssnano'), 63 | cssProcessorOptions: { discardComments: { removeAll: true } }, 64 | canPrint: true 65 | }), 66 | new webpack.optimize.CommonsChunkPlugin({ 67 | name: 'vendor', 68 | filename: 'public/scripts/common/[name].min.js', 69 | }), 70 | new HtmlWebpackPlugin({ 71 | filename: './views/layout.html', 72 | template: 'src/widget/layout.html', 73 | inject:false 74 | }), 75 | new HtmlWebpackPlugin({ 76 | filename: './views/index.html', 77 | template: 'src/views/index.js', 78 | inject:false, 79 | chunks:['vendor','index','tags'] 80 | }), 81 | new HtmlWebpackPlugin({ 82 | filename: './widget/index.html', 83 | template: 'src/widget/index.html' 84 | }), 85 | new HtmlWebpackPlugin({ 86 | filename: './views/star.html', 87 | template: 'src/views/star.js', 88 | inject: false, 89 | chunks: ['vendor', 'index', 'tags'] 90 | }), 91 | new HtmlWebpackPlugin({ 92 | filename: './widget/star.html', 93 | template: 'src/widget/star.html', 94 | inject: false 95 | }), 96 | new HtmlWebpackPlugin({ 97 | filename: './views/404.html', 98 | template: 'src/widget/404.html', 99 | inject:false 100 | }) 101 | ] 102 | }; -------------------------------------------------------------------------------- /geckodriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/geckodriver -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const babel = require('gulp-babel'); 3 | 4 | gulp.task('default', ['praise'], () => { 5 | gulp.watch(['src/**/*.es', '!src/public/**/*.es'], ['praise']) 6 | }); 7 | 8 | gulp.task('praise', () => { 9 | return gulp.src(['src/**/*.es', '!src/public/**/*.es']) 10 | .pipe(babel({ 11 | presets: ['es2015', 'stage-0'] 12 | })) 13 | .pipe(gulp.dest('./build')) 14 | }); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Nov 18 2017 19:05:58 GMT+0800 (CST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | "test/index.spec.js", 19 | "public/scripts/index.js" 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | ], 26 | 27 | 28 | // preprocess matching files before serving them to the browser 29 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 30 | preprocessors: { 31 | }, 32 | 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress' 36 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 37 | reporters: ['progress'], 38 | 39 | 40 | // web server port 41 | port: 9876, 42 | 43 | 44 | // enable / disable colors in the output (reporters and logs) 45 | colors: true, 46 | 47 | 48 | // level of logging 49 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 50 | logLevel: config.LOG_INFO, 51 | 52 | 53 | // enable / disable watching file and executing tests whenever any file changes 54 | autoWatch: true, 55 | 56 | 57 | // start these browsers 58 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 59 | browsers: ['PhantomJS'], 60 | 61 | 62 | // Continuous Integration mode 63 | // if true, Karma captures browsers, runs the tests and exits 64 | singleRun: false, 65 | 66 | // Concurrency level 67 | // how many browser should be started simultaneous 68 | concurrency: Infinity 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koatest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node app-o.js", 8 | "mochatest": "mocha test/server.es.js", 9 | "e2etest": "node test/e2e.js", 10 | "webpackdev": "better-npm-run webpack:dev", 11 | "webpackprod": "better-npm-run webpack:prod" 12 | }, 13 | "betterScripts": { 14 | "webpack:dev": { 15 | "command": "webpack --progress --colors", 16 | "env": { 17 | "NODE_ENV": "dev" 18 | } 19 | }, 20 | "webpack:prod": { 21 | "command": "webpack --progress --colors", 22 | "env": { 23 | "NODE_ENV": "prod" 24 | } 25 | } 26 | }, 27 | "author": "", 28 | "license": "ISC", 29 | "dependencies": { 30 | "koa": "^2.0.0-alpha.8", 31 | "koa-simple-router": "^0.2.0", 32 | "koa-static": "^4.0.2", 33 | "koa-swig": "^2.2.1", 34 | "path": "^0.12.7", 35 | "request": "^2.83.0", 36 | "request-promise": "^4.2.2" 37 | }, 38 | "devDependencies": { 39 | "babel": "^6.23.0", 40 | "babel-cli": "^6.26.0", 41 | "babel-loader": "^7.1.2", 42 | "babel-polyfill": "^6.26.0", 43 | "babel-preset-env": "^1.6.1", 44 | "babel-preset-es2015": "^6.24.1", 45 | "babel-preset-stage-0": "^6.24.1", 46 | "babel-register": "^6.26.0", 47 | "better-npm-run": "^0.1.0", 48 | "css-loader": "^0.28.7", 49 | "extract-text-webpack-plugin": "^3.0.2", 50 | "gulp": "^3.9.1", 51 | "gulp-babel": "^7.0.0", 52 | "html-webpack-plugin": "^2.30.1", 53 | "jasmine-core": "^2.8.0", 54 | "karma": "^1.7.1", 55 | "karma-chrome-launcher": "^2.2.0", 56 | "karma-jasmine": "^1.1.0", 57 | "mocha": "^4.0.1", 58 | "optimize-css-assets-webpack-plugin": "^3.2.0", 59 | "protractor": "^5.2.0", 60 | "selenium-standalone": "^6.11.0", 61 | "selenium-webdriver": "^3.6.0", 62 | "style-loader": "^0.19.0", 63 | "supertest": "^3.0.0", 64 | "webpack": "^3.9.1", 65 | "webpack-livereload-plugin": "^1.0.0", 66 | "webpack-manifest": "^1.0.8" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /praise.php: -------------------------------------------------------------------------------- 1 | servername = $servername; 14 | $this->username = $username; 15 | $this->password = $password; 16 | $this->dbname = $dbname; 17 | } 18 | 19 | //连接数据库 20 | public function getConnection(){ 21 | try { 22 | $dsn = "mysql:host=$this->servername;dbname=$this->dbname"; 23 | $this->con = new PDO($dsn, $this->username, $this->password); 24 | // echo "连接mysql数据库成功
"; 25 | } 26 | catch(PDOException $e){ 27 | echo $e->getMessage(); 28 | } 29 | } 30 | 31 | //更新数据库 32 | public function updateDate($sql){ 33 | //如果没有连接,让它连接上 34 | if($this->con == null){ 35 | $this->getConnection(); 36 | } 37 | header('content-type:application/json;charset=utf-8'); 38 | $res = $this->con->exec($sql); 39 | $arr = array('result'=>$res); 40 | echo json_encode($arr); 41 | $this->closeCon(); 42 | // echo "执行成功返回1,失败返回0;本次执行结果:".$res; 43 | } 44 | 45 | //关闭数据库连接 46 | public function closeCon(){ 47 | $this->conn = ''; 48 | } 49 | } 50 | 51 | 52 | //子类 53 | class realCon extends Conmysql{ 54 | //该类的构造方法 55 | public function __construct($servername,$username,$password,$dbname){ 56 | parent::__construct($servername,$username,$password,$dbname); 57 | } 58 | 59 | public function updateRealData(){ 60 | $sql = "UPDATE `test` SET `num`=`num`+1 WHERE `id`=1"; 61 | $this->updateDate($sql); 62 | } 63 | } 64 | 65 | //实例化 66 | $praiseCon = new realCon('localhost','root','','test'); 67 | $praiseCon->updateRealData(); 68 | 69 | 70 | ?> -------------------------------------------------------------------------------- /readme img/0.要求.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/readme img/0.要求.png -------------------------------------------------------------------------------- /readme img/1.CDN预加载配置.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/readme img/1.CDN预加载配置.png -------------------------------------------------------------------------------- /readme img/2.扩展星星组件,首页直出并用Pjax配置SPA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/readme img/2.扩展星星组件,首页直出并用Pjax配置SPA.png -------------------------------------------------------------------------------- /readme img/3-1.localStorage缓存.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/readme img/3-1.localStorage缓存.png -------------------------------------------------------------------------------- /readme img/3-2.用localforage实现的缓存负载均衡ORM库.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/readme img/3-2.用localforage实现的缓存负载均衡ORM库.png -------------------------------------------------------------------------------- /readme img/3-3.离线资源配置manifest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/readme img/3-3.离线资源配置manifest.png -------------------------------------------------------------------------------- /readme img/4.Prod上线版本动态分配CDN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/readme img/4.Prod上线版本动态分配CDN.png -------------------------------------------------------------------------------- /readme img/5.lazyload并行加载静态资源.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yllg/Performance/a25812fd1dbe0ee64d145a86eebd2cf0fcd010df/readme img/5.lazyload并行加载静态资源.png -------------------------------------------------------------------------------- /src/app-o.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _koa = require('koa'); 8 | 9 | var _koa2 = _interopRequireDefault(_koa); 10 | 11 | var _koaSimpleRouter = require('koa-simple-router'); 12 | 13 | var _koaSimpleRouter2 = _interopRequireDefault(_koaSimpleRouter); 14 | 15 | var _initController = require('./controller/initController'); 16 | 17 | var _initController2 = _interopRequireDefault(_initController); 18 | 19 | var _koaSwig = require('koa-swig'); 20 | 21 | var _koaSwig2 = _interopRequireDefault(_koaSwig); 22 | 23 | var _co = require('co'); 24 | 25 | var _co2 = _interopRequireDefault(_co); 26 | 27 | var _koaStatic = require('koa-static'); 28 | 29 | var _koaStatic2 = _interopRequireDefault(_koaStatic); 30 | 31 | var _register = require('babel-core/register'); 32 | 33 | var _register2 = _interopRequireDefault(_register); 34 | 35 | var _babelPolyfill = require('babel-polyfill'); 36 | 37 | var _babelPolyfill2 = _interopRequireDefault(_babelPolyfill); 38 | 39 | var _config = require('./config/config'); 40 | 41 | var _config2 = _interopRequireDefault(_config); 42 | 43 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 44 | 45 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 46 | //引入路由,自己写的控制器 47 | 48 | //引入swig模板 49 | 50 | //引入静态资源配置 51 | 52 | //引入babel的另两个文件 53 | 54 | //引入配置文件 55 | 56 | 57 | var app = new _koa2.default(); 58 | 59 | //初始化路由设置 60 | _initController2.default.init(app, _koaSimpleRouter2.default); 61 | 62 | //模板引擎设置 63 | app.context.render = _co2.default.wrap((0, _koaSwig2.default)({ 64 | root: _config2.default.get('viewDir'), 65 | autoescape: true, 66 | cache: 'memory', // disable, set to false 67 | ext: 'html', 68 | writeBody: false 69 | })); 70 | 71 | //静态资源设置 72 | app.use((0, _koaStatic2.default)(_config2.default.get('staticDir'))); 73 | 74 | //路由容错处理 75 | // 404,跳到腾讯公益页面 76 | app.use(function () { 77 | var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(ctx) { 78 | return regeneratorRuntime.wrap(function _callee$(_context) { 79 | while (1) { 80 | switch (_context.prev = _context.next) { 81 | case 0: 82 | ctx.status = 404; 83 | _context.next = 3; 84 | return ctx.render('404.html'); 85 | 86 | case 3: 87 | ctx.body = _context.sent; 88 | 89 | case 4: 90 | case 'end': 91 | return _context.stop(); 92 | } 93 | } 94 | }, _callee, undefined); 95 | })); 96 | 97 | return function (_x) { 98 | return _ref.apply(this, arguments); 99 | }; 100 | }() 101 | // ctx.redirect("http://www.yidengxuetang.com/"); 102 | ); 103 | // 500,跳到一等学堂 104 | app.on('error', function (err, ctx) { 105 | // ctx.body = "😓 服务器开了小差......"; 106 | ctx.redirect("http://www.yidengxuetang.com/"); 107 | }); 108 | 109 | app.listen(_config2.default.get('port')); 110 | exports.default = app; 111 | -------------------------------------------------------------------------------- /src/app.es: -------------------------------------------------------------------------------- 1 | import Koa from 'koa'; 2 | //引入路由,自己写的控制器 3 | import router from 'koa-simple-router'; 4 | import initController from './controller/initController'; 5 | //引入swig模板 6 | import render from 'koa-swig'; 7 | import co from 'co'; 8 | //引入静态资源配置 9 | import serve from 'koa-static'; 10 | //引入babel的另两个文件 11 | import babel_co from 'babel-core/register'; 12 | import babel_po from 'babel-polyfill'; 13 | //引入配置文件 14 | import CONFIG from './config/config'; 15 | 16 | const app = new Koa(); 17 | 18 | //初始化路由设置 19 | initController.init(app,router); 20 | 21 | //模板引擎设置 22 | app.context.render = co.wrap(render({ 23 | root:CONFIG.get('viewDir'), 24 | autoescape: true, 25 | cache: 'memory', // disable, set to false 26 | ext: 'html', 27 | writeBody: false 28 | })); 29 | 30 | //静态资源设置 31 | app.use(serve(CONFIG.get('staticDir'))); 32 | 33 | //路由容错处理 34 | // 404,跳到腾讯公益页面 35 | app.use(async (ctx) => { 36 | ctx.status = 404 37 | ctx.body = await ctx.render('404.html'); 38 | // ctx.redirect("http://www.yidengxuetang.com/"); 39 | }); 40 | // 500,跳到一等学堂 41 | app.on('error', (err, ctx) => { 42 | // ctx.body = "😓 服务器开了小差......"; 43 | ctx.redirect("http://www.yidengxuetang.com/"); 44 | }); 45 | 46 | app.listen(CONFIG.get('port')); 47 | export default app -------------------------------------------------------------------------------- /src/config/config.es: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | const CONFIG = new Map(); 3 | CONFIG.set('port',3000); 4 | CONFIG.set('staticDir',path.join(__dirname,'..')); 5 | CONFIG.set('viewDir',path.join(__dirname,'..','views')); 6 | export default CONFIG -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _path = require('path'); 8 | 9 | var _path2 = _interopRequireDefault(_path); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | var CONFIG = new Map(); 14 | CONFIG.set('port', 3000); 15 | CONFIG.set('staticDir', _path2.default.join(__dirname, '..', 'public')); 16 | CONFIG.set('viewDir', _path2.default.join(__dirname, '..', 'views')); 17 | exports.default = CONFIG; 18 | -------------------------------------------------------------------------------- /src/controller/indexController.es: -------------------------------------------------------------------------------- 1 | //引入model层的方法 2 | import indexModel from '../models/indexmodel' 3 | //路由的方法 4 | const indexController = { 5 | index(){ 6 | return async(ctx,next)=>{ 7 | ctx.body = await ctx.render('index.html',{ 8 | title: '首页' 9 | }); 10 | } 11 | }, 12 | update(){ 13 | return async(ctx,next)=>{ 14 | const indexM = new indexModel(ctx); 15 | ctx.body = await indexM.updateNum(); 16 | } 17 | }, 18 | star(){ 19 | return async(ctx,next)=>{ 20 | if(ctx.request.header['x-pjax']){ 21 | ctx.body = ""; 22 | }else{ 23 | ctx.body = await ctx.render('star.html',{ 24 | title: '星星组件页' 25 | }); 26 | } 27 | } 28 | }, 29 | praise(){ 30 | return async(ctx,next)=>{ 31 | if(ctx.request.header['x-pjax']){ 32 | ctx.body = ""; 33 | }else{ 34 | ctx.body = await ctx.render('index.html',{ 35 | title: '首页' 36 | }); 37 | } 38 | } 39 | }, 40 | advertisement(){ 41 | return async(ctx, next) => { 42 | ctx.body = '
...大幅广告...
' 43 | } 44 | } 45 | } 46 | export default indexController -------------------------------------------------------------------------------- /src/controller/indexController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _indexmodel = require('../models/indexmodel'); 8 | 9 | var _indexmodel2 = _interopRequireDefault(_indexmodel); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } //引入model层的方法 14 | 15 | 16 | //路由的方法 17 | var indexController = { 18 | index: function index() { 19 | var _this = this; 20 | 21 | return function () { 22 | var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(ctx, next) { 23 | return regeneratorRuntime.wrap(function _callee$(_context) { 24 | while (1) { 25 | switch (_context.prev = _context.next) { 26 | case 0: 27 | _context.next = 2; 28 | return ctx.render('index.html', { 29 | title: '大拇指点赞' 30 | }); 31 | 32 | case 2: 33 | ctx.body = _context.sent; 34 | 35 | case 3: 36 | case 'end': 37 | return _context.stop(); 38 | } 39 | } 40 | }, _callee, _this); 41 | })); 42 | 43 | return function (_x, _x2) { 44 | return _ref.apply(this, arguments); 45 | }; 46 | }(); 47 | }, 48 | update: function update() { 49 | var _this2 = this; 50 | 51 | return function () { 52 | var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(ctx, next) { 53 | var indexM; 54 | return regeneratorRuntime.wrap(function _callee2$(_context2) { 55 | while (1) { 56 | switch (_context2.prev = _context2.next) { 57 | case 0: 58 | indexM = new _indexmodel2.default(ctx); 59 | _context2.next = 3; 60 | return indexM.updateNum(); 61 | 62 | case 3: 63 | ctx.body = _context2.sent; 64 | 65 | case 4: 66 | case 'end': 67 | return _context2.stop(); 68 | } 69 | } 70 | }, _callee2, _this2); 71 | })); 72 | 73 | return function (_x3, _x4) { 74 | return _ref2.apply(this, arguments); 75 | }; 76 | }(); 77 | } 78 | }; 79 | exports.default = indexController; 80 | -------------------------------------------------------------------------------- /src/controller/initController.es: -------------------------------------------------------------------------------- 1 | //注册路由 2 | import index from './indexController' 3 | const controllerInit = { 4 | init(app,router){ 5 | app.use(router(_ => { 6 | //使用indexController里面的index方法 7 | _.get('/',(ctx,next) =>{ 8 | ctx.redirect('/index/index'); 9 | }), 10 | _.get('/index',(ctx,next) =>{ 11 | ctx.redirect('/index/index'); 12 | }), 13 | _.get('/index/index',index.index()), 14 | _.get('/index/update',index.update()), 15 | _.get('/index/star',index.star()), 16 | _.get('/index/praise',index.praise()), 17 | _.get('/index/adv',index.advertisement()) 18 | })) 19 | } 20 | } 21 | 22 | export default controllerInit -------------------------------------------------------------------------------- /src/controller/initController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _indexController = require('./indexController'); 8 | 9 | var _indexController2 = _interopRequireDefault(_indexController); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | var controllerInit = { 14 | init: function init(app, router) { 15 | app.use(router(function (_) { 16 | //使用indexController里面的index方法 17 | _.get('/', function (ctx, next) { 18 | ctx.redirect('/index/index'); 19 | }), _.get('/index/index', _indexController2.default.index()), _.get('/index/update', _indexController2.default.update()); 20 | })); 21 | } 22 | }; //注册路由 23 | exports.default = controllerInit; 24 | -------------------------------------------------------------------------------- /src/models/indexmodel.es: -------------------------------------------------------------------------------- 1 | 2 | import rpA from 'request-promise' 3 | class indexModel{ 4 | constructor(ctx){ 5 | this.ctx = ctx; 6 | } 7 | //model层调用php的接口 8 | updateNum(){ 9 | const options = { 10 | uri: 'http://localhost:8080/praise.php', 11 | method:'GET' 12 | } 13 | return new Promise((resolve,reject)=>{ 14 | rpA(options).then(function(result){ 15 | const info = JSON.parse(result); 16 | if(info){ 17 | resolve({data:info.result}); 18 | }else{ 19 | reject({}); 20 | } 21 | }) 22 | }) 23 | } 24 | } 25 | //导出给indexController里 26 | export default indexModel -------------------------------------------------------------------------------- /src/models/indexmodel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _requestPromise = require('request-promise'); 10 | 11 | var _requestPromise2 = _interopRequireDefault(_requestPromise); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | var indexModel = function () { 18 | function indexModel(ctx) { 19 | _classCallCheck(this, indexModel); 20 | 21 | this.ctx = ctx; 22 | } 23 | //model层调用php的接口 24 | 25 | 26 | _createClass(indexModel, [{ 27 | key: 'updateNum', 28 | value: function updateNum() { 29 | var options = { 30 | uri: 'http://localhost:8080/praise.php', 31 | method: 'GET' 32 | }; 33 | return new Promise(function (resolve, reject) { 34 | (0, _requestPromise2.default)(options).then(function (result) { 35 | var info = JSON.parse(result); 36 | if (info) { 37 | resolve({ data: info.result }); 38 | } else { 39 | reject({}); 40 | } 41 | }); 42 | }); 43 | } 44 | }]); 45 | 46 | return indexModel; 47 | }(); 48 | //导出给indexController里 49 | 50 | 51 | exports.default = indexModel; 52 | -------------------------------------------------------------------------------- /src/public/css/index.css: -------------------------------------------------------------------------------- 1 | 2 | /*背景色绿色渐变*/ 3 | body{ 4 | background-image: linear-gradient(to right, #cce6c9 0%, white 100%); 5 | } 6 | 7 | /*整个图案水平和垂直居中*/ 8 | #wrap { 9 | position: absolute; 10 | left: 50%; 11 | transform: translate(-50%); 12 | top: 35%; 13 | width: 220px; 14 | height: 130px; 15 | } 16 | 17 | /*手掌和四个手指*/ 18 | #PraiseButton { 19 | position:absolute; 20 | left:75px; 21 | width:125px; 22 | height:130px; 23 | background:#ffcaa4; 24 | border-radius:60px 40px 40px 40px; 25 | } 26 | 27 | 28 | #PraiseButton:before, #PraiseButton:after { 29 | display:block; 30 | content:''; 31 | position:absolute; 32 | } 33 | 34 | #PraiseButton:before { 35 | width:80px; 36 | height:30px; 37 | top:35px; 38 | left:60px; 39 | border-radius:25px 25px 25px 25px; 40 | background-image:linear-gradient(to right, #ffcaa4 15px, #efdbcd 40px, #ffcaa4 100px); 41 | -webkit-box-reflect: above 1px; 42 | box-reflect: above 1px; 43 | } 44 | 45 | #PraiseButton:after { 46 | width:80px; 47 | height:30px; 48 | top:66px; 49 | left:55px; 50 | border-radius:25px 25px 25px 25px; 51 | background-image:linear-gradient(to right, #ffcaa4 15px, #efdbcd 40px, #ffcaa4 100px); 52 | -webkit-box-reflect: below 1px; 53 | box-reflect: below 1px; 54 | } 55 | 56 | 57 | /*手腕和大拇指*/ 58 | #Thumb { 59 | position:absolute; 60 | top:30px; 61 | width:90px; 62 | height:80px; 63 | background:#ffcaa4; 64 | } 65 | 66 | 67 | #Thumb:before { 68 | display:block; 69 | content:''; 70 | position:absolute; 71 | width:110px; 72 | height:30px; 73 | top:-55px; 74 | left:73px; 75 | transform: rotateZ(-65deg); 76 | border-radius:25px 25px 235px 255px; 77 | background-image:linear-gradient(to right, #ffcaa4 60px, #efdbcd 80px, #ffcaa4 100px); 78 | /*border:1px solid #000;*/ 79 | } 80 | 81 | 82 | /*+1的弹出效果*/ 83 | .hide { 84 | display: none; 85 | } 86 | 87 | /*动态添加和删除num类名,增加这个动画效果哦*/ 88 | .num { 89 | display: block !important; 90 | -webkit-animation: pop 0.8s ease-in-out alternate; 91 | animation: pop 0.8s ease-in-out alternate; 92 | /*定义动画播放的次数*/ 93 | -webkit-animation-iteration-count:1; 94 | 95 | font-size: 40px; 96 | color: #ff1177; 97 | position: absolute; 98 | right: -10%; 99 | top: -45%; 100 | } 101 | 102 | @-webkit-keyframes pop { 103 | from{ 104 | opacity: 0; 105 | -webkit-transform: translateY(-20px); 106 | text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 107 | 0 0 40px #ff1177, 0 0 70px #ff1177, 0 0 80px #ff1177, 0 0 100px #ff1177, 0 0 150px #ff1177; 108 | } 109 | to{ 110 | opacity: 1; 111 | -webkit-transform: translateY(0px); 112 | text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 113 | 0 0 30px #ff1177, 0 0 35px #ff1177, 0 0 40px #ff1177, 0 0 50px #ff1177, 0 0 75px #ff1177; 114 | } 115 | } 116 | 117 | .star{ 118 | margin: 300px 50%; 119 | position: absolute; 120 | top: -30%; 121 | left: -8%; 122 | display: block; 123 | color: red; 124 | width: 0px; 125 | height: 0px; 126 | z-index: 999; 127 | border-right: 100px solid transparent; 128 | border-bottom: 70px solid red; 129 | border-left: 100px solid transparent; 130 | -moz-transform: rotate(35deg); 131 | -webkit-transform: rotate(35deg); 132 | -ms-transform: rotate(35deg); 133 | -o-transform: rotate(35deg); 134 | } 135 | .star:before { 136 | border-bottom: 80px solid red; 137 | border-left: 30px solid transparent; 138 | border-right: 30px solid transparent; 139 | position: absolute; 140 | height: 0; 141 | width: 0; 142 | top: -45px; 143 | left: -65px; 144 | display: block; 145 | content: ""; 146 | -webkit-transform: rotate(-35deg); 147 | -moz-transform: rotate(-35deg); 148 | -ms-transform: rotate(-35deg); 149 | -o-transform: rotate(-35deg); 150 | } 151 | 152 | .star1 { 153 | position: relative; 154 | display: block; 155 | color: red; 156 | top: 3px; 157 | left: -105px; 158 | width: 0px; 159 | height: 0px; 160 | border-right: 100px solid transparent; 161 | border-bottom: 70px solid red; 162 | border-left: 100px solid transparent; 163 | -webkit-transform: rotate(-70deg); 164 | -moz-transform: rotate(-70deg); 165 | -ms-transform: rotate(-70deg); 166 | -o-transform: rotate(-70deg); 167 | } 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /src/public/scripts/index-es.es: -------------------------------------------------------------------------------- 1 | 2 | //只留下父类发送请求的方法,子类,点击事件,稀释事件全都放到tags组件中 3 | 4 | import css from "../css/index.css" 5 | 6 | class PraiseButton { 7 | constructor(){ 8 | 9 | } 10 | clickAction(){ 11 | axios.get('/index/update') 12 | .then(function (response) { 13 | console.log(response); 14 | }) 15 | .catch(function (error) { 16 | console.log(error); 17 | }); 18 | } 19 | } 20 | 21 | //Thumb和star都继承PraiseButton 22 | class Thumb extends PraiseButton{ 23 | constructor(){ 24 | super(); 25 | } 26 | } 27 | class Star extends PraiseButton{ 28 | constructor(){ 29 | super(); 30 | } 31 | } 32 | export {Thumb,Star} 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/public/scripts/index-es.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Thumb = exports.PraiseButton = undefined; 7 | 8 | var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; 9 | 10 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 11 | //父类,可以无限次点击 12 | 13 | require('./index.js'); 14 | 15 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 16 | 17 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | var PraiseButton = exports.PraiseButton = function () { 22 | function PraiseButton() { 23 | var ele = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : $('#PraiseButton'); 24 | var num = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 25 | 26 | _classCallCheck(this, PraiseButton); 27 | 28 | this.ele = ele; 29 | this.num = num; 30 | this.tId = 0; 31 | } 32 | 33 | _createClass(PraiseButton, [{ 34 | key: 'click', 35 | value: function click() { 36 | var _this = this; 37 | 38 | this.ele.on('click', function () { 39 | _this.throttle(_this.handleClick.bind(_this)); 40 | }.bind(this)); 41 | } 42 | }, { 43 | key: 'handleClick', 44 | value: function handleClick() { 45 | clearTimeout(this.tId); 46 | this.num = add(this.num); 47 | this.pop(); 48 | axios.get('/index/update').then(function (response) { 49 | console.log(response); 50 | }).catch(function (error) { 51 | console.log(error); 52 | }); 53 | console.log('\u7236\u7C7B\u88AB\u70B9\u4E86' + this.num + ' \u6B21\uFF01\u53EF\u65E0\u9650\u70B9\u7236\u7C7B\uFF0C\u4E0D\u7F6E\u7070\u8272\uFF01\uFF01'); 54 | console.log('快速点击多次会被稀释成一次!'); 55 | } 56 | }, { 57 | key: 'throttle', 58 | value: function throttle(method, context) { 59 | clearTimeout(this.tId); 60 | this.tId = setTimeout(function () { 61 | method.call(context); 62 | }, 500); 63 | } 64 | }, { 65 | key: 'pop', 66 | value: function pop() { 67 | $('#animation').addClass('num'); 68 | setTimeout(function () { 69 | $('#animation').removeClass('num'); 70 | }, 800); 71 | } 72 | }]); 73 | 74 | return PraiseButton; 75 | }(); 76 | 77 | //子类,点击十次置灰;再点击从1开始 78 | 79 | 80 | var Thumb = exports.Thumb = function (_PraiseButton) { 81 | _inherits(Thumb, _PraiseButton); 82 | 83 | function Thumb() { 84 | var ele = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : $('#Thumb'); 85 | var num = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 86 | 87 | _classCallCheck(this, Thumb); 88 | 89 | return _possibleConstructorReturn(this, (Thumb.__proto__ || Object.getPrototypeOf(Thumb)).call(this, ele, num)); 90 | } 91 | 92 | _createClass(Thumb, [{ 93 | key: 'click', 94 | value: function click() { 95 | var _this3 = this; 96 | 97 | this.ele.on('click', function () { 98 | _get(Thumb.prototype.__proto__ || Object.getPrototypeOf(Thumb.prototype), 'throttle', _this3).call(_this3, _this3.handleClick.bind(_this3)); 99 | }.bind(this)); 100 | } 101 | }, { 102 | key: 'handleClick', 103 | value: function handleClick() { 104 | if (this.num < 9) { 105 | this.num = add(this.num); 106 | _get(Thumb.prototype.__proto__ || Object.getPrototypeOf(Thumb.prototype), 'pop', this).call(this); 107 | } else if (this.num === 9) { 108 | $('#PraiseButton').css('-webkit-filter', 'grayscale(1)'); 109 | $('#Thumb').css('-webkit-filter', 'grayscale(1)'); 110 | this.num = add(this.num); 111 | _get(Thumb.prototype.__proto__ || Object.getPrototypeOf(Thumb.prototype), 'pop', this).call(this); 112 | } else { 113 | this.num = 1; 114 | $('#PraiseButton').css('-webkit-filter', 'grayscale(0)'); 115 | $('#Thumb').css('-webkit-filter', 'grayscale(0)'); 116 | _get(Thumb.prototype.__proto__ || Object.getPrototypeOf(Thumb.prototype), 'pop', this).call(this); 117 | } 118 | axios.get('/index/update').then(function (response) { 119 | console.log(response); 120 | }).catch(function (error) { 121 | console.log(error); 122 | }); 123 | console.log('\u5B50\u7C7B\u88AB\u70B9\u4E86' + this.num + ' \u6B21\uFF01\u7B2C10\u6B21\u53D8\u7070\u8272\uFF0C\u7136\u540E\u4ECE\u5934\u8BA1\u6570\uFF01\uFF01'); 124 | console.log('快速点击多次会被稀释成一次!'); 125 | } 126 | }]); 127 | 128 | return Thumb; 129 | }(PraiseButton); 130 | -------------------------------------------------------------------------------- /src/public/scripts/indexadd.js: -------------------------------------------------------------------------------- 1 | window.add = function add(num){ 2 | return num+1; 3 | } -------------------------------------------------------------------------------- /src/public/scripts/star.es: -------------------------------------------------------------------------------- 1 | import {Star} from './index-es.es' 2 | const f = new Star(); 3 | 4 | xtag.register('x-star', { 5 | content: '
' + 6 | '
' + 7 | '
' + 8 | '+1' , 9 | 10 | methods: { 11 | praise: function(){ 12 | let _this = this; 13 | f.clickAction(); 14 | let animation = _this.querySelector("#animation"); 15 | animation.className = "hide num"; 16 | setTimeout(function(){ 17 | animation.className = "hide"; 18 | },800) 19 | } 20 | }, 21 | events: { 22 | click: function(e){ 23 | let _this = this; 24 | if(e.target.id == "star"){ 25 | let t =""; 26 | if(t){ 27 | clearTimeout(t); 28 | } 29 | t = setTimeout(function(){ 30 | _this.praise(); 31 | },500) 32 | } 33 | } 34 | } 35 | }); 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/public/scripts/tags.es: -------------------------------------------------------------------------------- 1 | import {Thumb} from './index-es.es' 2 | const f = new Thumb(); 3 | 4 | xtag.register('x-praise', { 5 | content: '
' + 6 | '
' + 7 | '
' + 8 | '+1' + 9 | '
' , 10 | 11 | methods: { 12 | praise: function(){ 13 | let _this = this; 14 | f.clickAction(); 15 | let animation = _this.querySelector("#animation"); 16 | animation.className = "hide num"; 17 | setTimeout(function(){ 18 | animation.className = "hide"; 19 | },800) 20 | } 21 | }, 22 | events: { 23 | click: function(e){ 24 | let _this = this; 25 | if(e.target.id == "Thumb"|| e.target.id == "PraiseButton"){ 26 | let t =""; 27 | if(t){ 28 | clearTimeout(t); 29 | } 30 | t = setTimeout(function(){ 31 | _this.praise(); 32 | },500) 33 | } 34 | } 35 | } 36 | }); 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/test/e2e.js: -------------------------------------------------------------------------------- 1 | const {Builder, By, Key, until} = require('selenium-webdriver'); 2 | 3 | let driver = new Builder() 4 | .forBrowser('firefox') 5 | .build(); 6 | 7 | driver.get('http://localhost:3000/index/index'); 8 | driver.findElement(By.id('Thumb')).click(); 9 | const _animation = driver.findElement(By.id('animation')); 10 | driver.wait(_animation.isDisplayed(), 10000); 11 | // driver.quit(); -------------------------------------------------------------------------------- /src/test/index.spec.js: -------------------------------------------------------------------------------- 1 | describe('测试加1函数是否正确',function(){ 2 | it('测试add(1)是否等于2',function(){ 3 | expect(add(1)).toBe(2); 4 | }) 5 | }) -------------------------------------------------------------------------------- /src/test/server.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _supertest = require('supertest'); 4 | 5 | var _supertest2 = _interopRequireDefault(_supertest); 6 | 7 | var _appO = require('../app-o'); 8 | 9 | var _appO2 = _interopRequireDefault(_appO); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | //获得监听的端口 14 | function request() { 15 | return (0, _supertest2.default)(_appO2.default.listen()); 16 | } 17 | 18 | //进行测试哦 19 | describe('测试路由', function () { 20 | it('点赞', function (done) { 21 | request().get('/index/update').expect(200).end(function (err, res) { 22 | if (res.data == 1) return done(err); 23 | done(); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/test/server.js: -------------------------------------------------------------------------------- 1 | import requestsuper from 'supertest'; 2 | import app from '../app-o'; 3 | 4 | //获得监听的端口 5 | function request(){ 6 | return requestsuper(app.listen()); 7 | } 8 | 9 | //进行测试哦 10 | describe('测试路由', function() { 11 | it('点赞', function(done) { 12 | request() 13 | .get('/index/update') 14 | .expect(200) 15 | .end(function(err,res){ 16 | if(res.data ==1 ) return done(err); 17 | done(); 18 | }) 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/views/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (templateParams) { 2 | var _cssList = ['vendor']; 3 | var webAssetsHelp = require('./webAssetsHelp.js')(templateParams, _cssList); 4 | 5 | var _html = "{% extends './layout.html' %}" + 6 | "{% block title %}KOA2{{title}}{% endblock %}" + 7 | "{% block styles %}" + 8 | webAssetsHelp.styles + 9 | "{% endblock %}" + 10 | "{% block content %}{% include '../widget/index.html' %}{% endblock %}" + 11 | "{% block script %}" + 12 | "" + 34 | 35 | // webAssetsHelp.scripts + 36 | "{% endblock %}"; 37 | return _html; 38 | } -------------------------------------------------------------------------------- /src/views/star.js: -------------------------------------------------------------------------------- 1 | module.exports = function (templateParams) { 2 | var _cssList = ['vendor']; 3 | var webAssetsHelp = require('./webAssetsHelp.js')(templateParams, _cssList); 4 | 5 | var _html = "{% extends './layout.html' %}" + 6 | "{% block title %}KOA2{{title}}{% endblock %}" + 7 | "{% block styles %}" + 8 | webAssetsHelp.styles + 9 | "{% endblock %}" + 10 | "{% block content %}{% include '../widget/star.html' %}{% endblock %}" + 11 | "{% block script %}" + 12 | webAssetsHelp.scripts + 13 | "{% endblock %}"; 14 | return _html; 15 | } -------------------------------------------------------------------------------- /src/views/webAssetsHelp.js: -------------------------------------------------------------------------------- 1 | /* 2 | *@Description 通过HtmlWebpackPlugin自定义处理静态资源 3 | *@Author yuanzhijia@yidengxuetang.com 4 | *@Date 2016-05-05 5 | */ 6 | module.exports = function(templateParams, cssList) { 7 | console.log('入口文件', templateParams.htmlWebpackPlugin.files); 8 | var _files = templateParams.htmlWebpackPlugin.files; 9 | console.log('文件', _files); 10 | var _regChunk = templateParams.htmlWebpackPlugin.options.chunks; 11 | var _regCss = cssList; 12 | var _scripts = ""; 13 | var _scriptsShow = []; 14 | var _styles = ""; 15 | for (var i = 0; i < _regChunk.length; i++) { 16 | _scripts += ""; 17 | _scriptsShow.push("'" + _files.chunks[_regChunk[i]]['entry'] + "'") 18 | } 19 | for (var k = 0; k < _regCss.length; k++) { 20 | var _cssitem = _regCss[k], 21 | _cssitems = new RegExp("^" + _cssitem), 22 | _cssiteme = new RegExp(".css$"); 23 | (_files.css).map(function(filename) { 24 | var _filearr = filename.split('/'), 25 | filrdata = _filearr[_filearr.length - 1]; 26 | if (_cssitems.test(filrdata) && _cssiteme.test(filrdata)) { 27 | _styles += ''; 28 | } 29 | }); 30 | } 31 | return { 32 | scripts: _scripts, 33 | styles: _styles, 34 | scriptsShow: _scriptsShow 35 | } 36 | } -------------------------------------------------------------------------------- /src/widget/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/widget/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
JS与QA 三大测试: 6 |
单元测试:karma start 7 |
接口测试:npm run mochatest 8 |
e2e测试:npm run e2etest 9 |
10 |
nodejs KOA2: 11 |
重定向:根目录/跳转到/index/index 12 |
容错处理: 13 |
404跳到腾讯公益“找小孩”页面; 14 |
500跳到一灯官网; 15 |
16 |
工程化和持续构建: 17 |
运行gulp,打包编译后台文件,监视文件变化自动打包更新 18 |
运行webpack -w 默认用dev版打包文件,并监视前端文件变化重新打包 19 |
进入build目录运行node app.js 启动服务,默认3000端口 20 |
21 |
前端性能优化: 22 |
点击左上角两个a连接,可以实现跳转 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/widget/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}My Site{% endblock %} 8 | {% block head %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% endblock %} {% block styles %}{% endblock %} 17 | 18 | 19 | 20 | 跳转到星星 21 | 跳转到大拇指点赞 22 |
{% block content %}{% endblock %}
23 | 43 | 44 | {% block script %}{% endblock %} 45 | 46 | -------------------------------------------------------------------------------- /src/widget/star.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var DevWebpack = require('./config/webpack.dev'); 2 | var ProdWebpack = require('./config/webpack.prod'); 3 | switch ( process.env.NODE_ENV ) { 4 | case 'dev': 5 | module.exports = DevWebpack; 6 | break; 7 | case 'prod': 8 | module.exports = ProdWebpack; 9 | break; 10 | default: 11 | module.exports = ProdWebpack; 12 | } --------------------------------------------------------------------------------