├── .babelrc
├── .gitignore
├── README.md
├── doc
└── handlock.md
├── example
├── locker-check.html
├── locker-update.html
├── locker.html
└── recorder.html
├── lib
├── app.js
├── locker.js
└── recorder.js
├── package.json
├── script
├── cdn-uploader.js
└── deploy.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"],
3 | "plugins": ["transform-runtime"]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage/
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Dependency directory
23 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
24 | node_modules/
25 |
26 | # IDE config
27 | .idea
28 |
29 | # output
30 | output/
31 | output.tar.gz
32 |
33 | app/
34 | dist/
35 |
36 | runtime/
37 |
38 | .DS_Store
39 | sftp-config.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 手势密码设置和解锁的实现
2 |
3 | 这是[第三届360前端星计划](http://html5.360.cn/star)的[在线作业](doc/handlock.md)的参考实现。
4 |
5 | ## 在线例子
6 |
7 | [在线示例](http://handlock.test.h5jun.com/example/locker.html)
8 |
9 | 
10 |
11 | ## 安装和使用
12 |
13 | 直接在浏览器里引用:
14 |
15 | ```html
16 |
17 | ```
18 |
19 | ## API
20 |
21 | ### Locker
22 |
23 | 创建一个可以设置密码和验证密码的实例
24 |
25 | ```js
26 | var password = '11121323';
27 |
28 | var locker = new HandLock.Locker({
29 | container: document.querySelector('#handlock'),
30 | check: {
31 | checked: function(res){
32 | if(res.err){
33 | console.error(res.err); //密码错误或长度太短
34 | }else{
35 | console.log(`正确,密码是:${res.records}`);
36 | }
37 | },
38 | },
39 | update:{
40 | beforeRepeat: function(res){
41 | if(res.err){
42 | console.error(res.err); //密码长度太短
43 | }else{
44 | console.log(`密码初次输入完成,等待重复输入`);
45 | }
46 | },
47 | afterRepeat: function(res){
48 | if(res.err){
49 | console.error(res.err); //密码长度太短或者两次密码输入不一致
50 | }else{
51 | console.log(`密码更新完成,新密码是:${res.records}`);
52 | }
53 | },
54 | }
55 | });
56 |
57 | locker.check(password);
58 | ```
59 |
60 | ### 几种 err 状态
61 |
62 | - ERR_NOT_ENOUGH_POINTS 绘制的点数量不足,默认为最少4个点
63 | - ERR_PASSWORD_MISMATCH 密码不一致,check时密码不对或者update时两次输入密码不一致
64 | - ERR_USER_CANCELED 用户切换验证或设置操作时,取消当前的状态
65 |
66 | ### 可配置的参数
67 |
68 | ```js
69 | //recorder.js
70 | const defaultOptions = {
71 | container: null, //创建canvas的容器,如果不填,自动在 body 上创建覆盖全屏的层
72 | focusColor: '#e06555', //当前选中的圆的颜色
73 | fgColor: '#d6dae5', //未选中的圆的颜色
74 | bgColor: '#fff', //canvas背景颜色
75 | n: 3, //圆点的数量: n x n
76 | innerRadius: 10, //圆点的内半径
77 | outerRadius: 25, //圆点的外半径,focus 的时候显示
78 | touchRadius: 35, //判定touch事件的圆半径
79 | render: true, //自动渲染
80 | customStyle: false, //自定义样式
81 | minPoints: 4, //最小允许的点数
82 | };
83 | ```
84 |
85 | ```js
86 | //locker.js
87 | const defaultOptions = {
88 | update: {
89 | beforeRepeat: function(){}, //更新密码第一次输入后的事件
90 | afterRepeat: function(){} //更新密码重复输入后的事件
91 | },
92 | check: {
93 | checked: function(){} //校验密码之后的事件
94 | }
95 | }
96 | ```
97 |
98 | ### clearPath()
99 |
100 | 清除 canvas 上选中的圆。
101 |
102 | ### cancel()
103 |
104 | 取消当前状态,用于update/check状态切换。
105 |
106 | ## 修改和发布代码
107 |
108 | 下载仓库并安装依赖:
109 |
110 | ```bash
111 | npm install
112 | ```
113 |
114 | 启动服务:
115 |
116 | ```bash
117 | npm start
118 | ```
119 |
120 | 发布代码:
121 |
122 | ```bash
123 | npm run deploy
124 | ```
125 |
126 | ## License
127 |
128 | MIT
129 |
--------------------------------------------------------------------------------
/doc/handlock.md:
--------------------------------------------------------------------------------
1 | # 2017 前端星计划选拔作业
2 |
3 | 在移动端设备上,“手势密码”成为一个很常用的 UI 组件。
4 |
5 | 一个手势密码的界面大致如下:
6 |
7 | 
8 |
9 | 用户用手指按顺序依次划过 9 个原点中的若干个(必须不少于 4 个点),如果划过的点的数量和顺序与之前用户设置的相同,那么当用户的手指离开屏幕时,判定为密码输入正确,否则密码错误。
10 |
11 | 要求:实现一个移动网页,允许用户设置手势密码和验证手势密码。已设置的密码记录在本地 localStorage 中。
12 |
13 | 界面原型和操作流程如下:
14 |
15 | ### stat 1:设置密码
16 |
17 | 
18 |
19 | 用户选择设置密码,提示用户输入手势密码
20 |
21 | ### stat 2:密码长度太短
22 |
23 | 
24 |
25 | 如果不足 5 个点,提示用户密码太短
26 |
27 | ### stat 3:再次输入密码
28 |
29 | 
30 |
31 | 提示用户再次输入密码
32 |
33 | ### stat 4: 两次密码输入不一致
34 |
35 | 
36 |
37 | 如果用户输入的两次密码不一致,**提示并重置,重新开始设置密码**
38 |
39 | ### stat 5: 密码设置成功
40 |
41 | 
42 |
43 | 如果两次输入一致,**密码设置成功,更新 localStorage**
44 |
45 | ### stat 6: 验证密码 - 不正确
46 |
47 | 
48 |
49 | 切换单选框进入验证密码模式,将用户输入的密码与保存的密码相比较,如果不一致,则提示**输入密码不正确,重置为等待用户输入**。
50 |
51 | ### stat 7: 验证密码 - 正确
52 |
53 | 
54 |
55 | 如果用户输入的密码与 localStorage 中保存的密码一致,则提示**密码正确**。
56 |
57 | ---
58 |
59 | 请同学们按照上面的需求实现这个网页,在手机上可用。可以不用太考虑古老机器的兼容性,最新的 android 和 iPhone 可用即可。
60 |
61 | **要求:**
62 |
63 | 1. 独立思考,独立完成,严禁抄袭!如果发现抄袭导致代码雷同,抄袭者和被抄袭者将都不会入选,而且会取消以后参加 360 面试的资格。
64 |
65 | 1. 注意保持良好代码风格和注释,可以在 README 文档里写上自己的思路。
66 |
67 | 1. 请在截止日期内完成并提交。
--------------------------------------------------------------------------------
/example/locker-check.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo
7 |
26 |
27 |
28 |
29 |
30 |
31 |
43 |
44 |
--------------------------------------------------------------------------------
/example/locker-update.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo
7 |
26 |
27 |
28 |
29 |
30 |
31 |
54 |
55 |
--------------------------------------------------------------------------------
/example/locker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo
7 |
71 |
72 |
73 |
74 |
手势解锁
75 |
验证密码,请绘制密码图案
76 |
77 |
请连接至少4个点
78 |
79 |
80 |
81 |
82 |
83 |
84 |
154 |
155 |
--------------------------------------------------------------------------------
/example/recorder.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo
7 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
70 |
71 |
--------------------------------------------------------------------------------
/lib/app.js:
--------------------------------------------------------------------------------
1 | import Recorder from './recorder.js';
2 | import Locker from './locker.js';
3 |
4 | export {
5 | /** add your code here **/
6 | Recorder,
7 | Locker,
8 | }
9 |
--------------------------------------------------------------------------------
/lib/locker.js:
--------------------------------------------------------------------------------
1 | import Recorder from './recorder.js';
2 |
3 | const defaultOptions = {
4 | update: {
5 | beforeRepeat: function(){},
6 | afterRepeat: function(){}
7 | },
8 | check: {
9 | checked: function(){}
10 | }
11 | }
12 |
13 | export default class Locker extends Recorder{
14 | static get ERR_PASSWORD_MISMATCH(){
15 | return 'password mismatch!';
16 | }
17 | static get MODE_UPDATE(){
18 | return 'update';
19 | }
20 | static get MODE_CHECK(){
21 | return 'check';
22 | }
23 | constructor(options = {}) {
24 | options.update = Object.assign({}, defaultOptions.update, options.update);
25 | options.check = Object.assign({}, defaultOptions.check, options.check);
26 | super(options);
27 | }
28 | async update(){
29 | if(this.mode !== Locker.MODE_UPDATE){
30 | await this.cancel();
31 | this.mode = Locker.MODE_UPDATE;
32 | }
33 |
34 | let beforeRepeat = this.options.update.beforeRepeat,
35 | afterRepeat = this.options.update.afterRepeat;
36 |
37 | let first = await this.record();
38 |
39 | if(first.err && first.err.message === Locker.ERR_USER_CANCELED){
40 | return Promise.resolve(first);
41 | }
42 |
43 | if(first.err){
44 | this.update();
45 | beforeRepeat.call(this, first);
46 | return Promise.resolve(first);
47 | }
48 |
49 | beforeRepeat.call(this, first);
50 |
51 | let second = await this.record();
52 |
53 | if(second.err && second.err.message === Locker.ERR_USER_CANCELED){
54 | return Promise.resolve(second);
55 | }
56 |
57 | if(!second.err && first.records !== second.records){
58 | second.err = new Error(Locker.ERR_PASSWORD_MISMATCH);
59 | }
60 |
61 | this.update();
62 | afterRepeat.call(this, second);
63 | return Promise.resolve(second);
64 | }
65 | async check(password){
66 | if(this.mode !== Locker.MODE_CHECK){
67 | await this.cancel();
68 | this.mode = Locker.MODE_CHECK;
69 | }
70 |
71 | let checked = this.options.check.checked;
72 |
73 | let res = await this.record();
74 |
75 | if(res.err && res.err.message === Locker.ERR_USER_CANCELED){
76 | return Promise.resolve(res);
77 | }
78 |
79 | if(!res.err && password !== res.records){
80 | res.err = new Error(Locker.ERR_PASSWORD_MISMATCH)
81 | }
82 |
83 | checked.call(this, res);
84 | this.check(password);
85 | return Promise.resolve(res);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/recorder.js:
--------------------------------------------------------------------------------
1 | function getCanvasPoint(canvas, x, y){
2 | let rect = canvas.getBoundingClientRect();
3 | return {
4 | x: 2 * (x - rect.left), //canvas 显示大小缩放为实际大小的 50%。为了让图形在 Retina 屏上清晰
5 | y: 2 * (y - rect.top),
6 | };
7 | }
8 |
9 | function distance(p1, p2){
10 | let x = p2.x - p1.x, y = p2.y - p1.y;
11 | return Math.sqrt(x * x + y * y);
12 | }
13 |
14 | //画实心圆
15 | function drawSolidCircle(ctx, color, x, y, r){
16 | ctx.fillStyle = color;
17 | ctx.beginPath();
18 | ctx.arc(x, y, r, 0, Math.PI * 2, true);
19 | ctx.closePath();
20 | ctx.fill();
21 | }
22 |
23 | //画空心圆
24 | function drawHollowCircle(ctx, color, x, y, r){
25 | ctx.strokeStyle = color;
26 | ctx.beginPath();
27 | ctx.arc(x, y, r, 0, Math.PI * 2, true);
28 | ctx.closePath();
29 | ctx.stroke();
30 | }
31 |
32 | //画线段
33 | function drawLine(ctx, color, x1, y1, x2, y2){
34 | ctx.strokeStyle = color;
35 | ctx.beginPath();
36 | ctx.moveTo(x1, y1);
37 | ctx.lineTo(x2, y2);
38 | ctx.stroke();
39 | ctx.closePath();
40 | }
41 |
42 | const defaultOptions = {
43 | container: null, //创建canvas的容器,如果不填,自动在 body 上创建覆盖全屏的层
44 | focusColor: '#e06555', //当前选中的圆的颜色
45 | fgColor: '#d6dae5', //未选中的圆的颜色
46 | bgColor: '#fff', //canvas背景颜色
47 | n: 3, //圆点的数量: n x n
48 | innerRadius: 20, //圆点的内半径
49 | outerRadius: 50, //圆点的外半径,focus 的时候显示
50 | touchRadius: 70, //判定touch事件的圆半径
51 | render: true, //自动渲染
52 | customStyle: false, //自定义样式
53 | minPoints: 4, //最小允许的点数
54 | };
55 |
56 | export default class Recorder{
57 | static get ERR_NOT_ENOUGH_POINTS(){
58 | return 'not enough points';
59 | }
60 | static get ERR_USER_CANCELED(){
61 | return 'user canceled';
62 | }
63 | static get ERR_NO_TASK(){
64 | return 'no task';
65 | }
66 | constructor(options){
67 | options = Object.assign({}, defaultOptions, options);
68 |
69 | this.options = options;
70 | this.path = [];
71 |
72 | if(options.render){
73 | this.render();
74 | }
75 | }
76 | render(){
77 | if(this.circleCanvas) return false;
78 |
79 | let options = this.options;
80 | let container = options.container || document.createElement('div');
81 |
82 | if(!options.container && !options.customStyle){
83 | Object.assign(container.style, {
84 | position: 'absolute',
85 | top: 0,
86 | left: 0,
87 | width: '100%',
88 | height: '100%',
89 | lineHeight: '100%',
90 | overflow: 'hidden',
91 | backgroundColor: options.bgColor
92 | });
93 | document.body.appendChild(container);
94 | }
95 | this.container = container;
96 |
97 | let {width, height} = container.getBoundingClientRect();
98 |
99 | //画圆的 canvas,也是最外层监听事件的 canvas
100 | let circleCanvas = document.createElement('canvas');
101 |
102 | //2 倍大小,为了支持 retina 屏
103 | circleCanvas.width = circleCanvas.height = 2 * Math.min(width, height);
104 | if(!options.customStyle){
105 | Object.assign(circleCanvas.style, {
106 | position: 'absolute',
107 | top: '50%',
108 | left: '50%',
109 | transform: 'translate(-50%, -50%) scale(0.5)',
110 | });
111 | }
112 |
113 | //画固定线条的 canvas
114 | let lineCanvas = circleCanvas.cloneNode(true);
115 |
116 | //画不固定线条的 canvas
117 | let moveCanvas = circleCanvas.cloneNode(true);
118 |
119 | container.appendChild(lineCanvas);
120 | container.appendChild(moveCanvas);
121 | container.appendChild(circleCanvas);
122 |
123 | this.lineCanvas = lineCanvas;
124 | this.moveCanvas = moveCanvas;
125 | this.circleCanvas = circleCanvas;
126 |
127 | this.container.addEventListener('touchmove',
128 | evt => evt.preventDefault(), {passive: false});
129 |
130 | this.clearPath();
131 | return true;
132 | }
133 | clearPath(){
134 | if(!this.circleCanvas) this.render();
135 |
136 | let {circleCanvas, lineCanvas, moveCanvas} = this,
137 | circleCtx = circleCanvas.getContext('2d'),
138 | lineCtx = lineCanvas.getContext('2d'),
139 | moveCtx = moveCanvas.getContext('2d'),
140 | width = circleCanvas.width,
141 | {n, fgColor, innerRadius} = this.options;
142 |
143 | circleCtx.clearRect(0, 0, width, width);
144 | lineCtx.clearRect(0, 0, width, width);
145 | moveCtx.clearRect(0, 0, width, width);
146 |
147 | let range = Math.round(width / (n + 1));
148 |
149 | let circles = [];
150 |
151 | //drawCircleCenters
152 | for(let i = 1; i <= n; i++){
153 | for(let j = 1; j <= n; j++){
154 | let y = range * i, x = range * j;
155 | drawSolidCircle(circleCtx, fgColor, x, y, innerRadius);
156 | let circlePoint = {x, y};
157 | circlePoint.pos = [i, j];
158 | circles.push(circlePoint);
159 | }
160 | }
161 |
162 | this.circles = circles;
163 | }
164 | async cancel(){
165 | if(this.recordingTask){
166 | return this.recordingTask.cancel();
167 | }
168 | return Promise.resolve({err: new Error(Recorder.ERR_NO_TASK)});
169 | }
170 | async record(){
171 | if(this.recordingTask) return this.recordingTask.promise;
172 |
173 | let {circleCanvas, lineCanvas, moveCanvas, options} = this,
174 | circleCtx = circleCanvas.getContext('2d'),
175 | lineCtx = lineCanvas.getContext('2d'),
176 | moveCtx = moveCanvas.getContext('2d');
177 |
178 | circleCanvas.addEventListener('touchstart', ()=>{
179 | this.clearPath();
180 | });
181 |
182 | let records = [];
183 |
184 | let handler = evt => {
185 | let {clientX, clientY} = evt.changedTouches[0],
186 | {bgColor, focusColor, innerRadius, outerRadius, touchRadius} = options,
187 | touchPoint = getCanvasPoint(moveCanvas, clientX, clientY);
188 |
189 | for(let i = 0; i < this.circles.length; i++){
190 | let point = this.circles[i],
191 | x0 = point.x,
192 | y0 = point.y;
193 |
194 | if(distance(point, touchPoint) < touchRadius){
195 | drawSolidCircle(circleCtx, bgColor, x0, y0, outerRadius);
196 | drawSolidCircle(circleCtx, focusColor, x0, y0, innerRadius);
197 | drawHollowCircle(circleCtx, focusColor, x0, y0, outerRadius);
198 |
199 | if(records.length){
200 | let p2 = records[records.length - 1],
201 | x1 = p2.x,
202 | y1 = p2.y;
203 |
204 | drawLine(lineCtx, focusColor, x0, y0, x1, y1);
205 | }
206 |
207 | let circle = this.circles.splice(i, 1);
208 | records.push(circle[0]);
209 | break;
210 | }
211 | }
212 |
213 | if(records.length){
214 | let point = records[records.length - 1],
215 | x0 = point.x,
216 | y0 = point.y,
217 | x1 = touchPoint.x,
218 | y1 = touchPoint.y;
219 |
220 | moveCtx.clearRect(0, 0, moveCanvas.width, moveCanvas.height);
221 | drawLine(moveCtx, focusColor, x0, y0, x1, y1);
222 | }
223 | };
224 |
225 |
226 | circleCanvas.addEventListener('touchstart', handler);
227 | circleCanvas.addEventListener('touchmove', handler);
228 |
229 | let recordingTask = {};
230 | let promise = new Promise((resolve, reject) => {
231 | recordingTask.cancel = (res = {}) => {
232 | let promise = this.recordingTask.promise;
233 |
234 | res.err = res.err || new Error(Recorder.ERR_USER_CANCELED);
235 | circleCanvas.removeEventListener('touchstart', handler);
236 | circleCanvas.removeEventListener('touchmove', handler);
237 | document.removeEventListener('touchend', done);
238 | resolve(res);
239 | this.recordingTask = null;
240 |
241 | return promise;
242 | }
243 |
244 | let done = evt => {
245 | moveCtx.clearRect(0, 0, moveCanvas.width, moveCanvas.height);
246 | if(!records.length) return;
247 |
248 | circleCanvas.removeEventListener('touchstart', handler);
249 | circleCanvas.removeEventListener('touchmove', handler);
250 | document.removeEventListener('touchend', done);
251 |
252 | let err = null;
253 |
254 | if(records.length < options.minPoints){
255 | err = new Error(Recorder.ERR_NOT_ENOUGH_POINTS);
256 | }
257 |
258 | //这里可以选择一些复杂的编码方式,本例子用最简单的直接把坐标转成字符串
259 | let res = {err, records: records.map(o => o.pos.join('')).join('')};
260 |
261 | resolve(res);
262 | this.recordingTask = null;
263 | };
264 | document.addEventListener('touchend', done);
265 | });
266 |
267 | recordingTask.promise = promise;
268 |
269 | this.recordingTask = recordingTask;
270 |
271 | return promise;
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hand-lock",
3 | "version": "0.2.1",
4 | "description": "",
5 | "main": "index.js",
6 | "directories": {
7 | "example": "example"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "start": "webpack-dev-server --quiet & http-server example -c-1 -p 8081",
12 | "deploy": "rm -rf dist/* && ./script/deploy.js"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "MIT",
17 | "devDependencies": {
18 | "babel-core": "^6.24.0",
19 | "babel-loader": "^6.4.1",
20 | "babel-plugin-transform-runtime": "^6.23.0",
21 | "babel-preset-env": "^1.3.2",
22 | "http-server": "^0.9.0",
23 | "webpack": "^2.3.3",
24 | "webpack-dev-server": "^2.4.2"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/script/cdn-uploader.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | upload: function(file){
4 | try{
5 | let qcdn = require('@q/qcdn');
6 | return qcdn.upload(file, {
7 | https: true,
8 | keepName: true
9 | });
10 | }catch(ex){
11 | return Promise.reject('no cdn uploader specified!');
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/script/deploy.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const webpack = require('webpack');
4 | const fs = require('fs');
5 | const path = require('path');
6 |
7 | let webpackConf = require('../webpack.config.js');
8 |
9 | webpack(webpackConf({production: true}), function(err, stats){
10 | let cdnUploader = require('./cdn-uploader'),
11 | output = stats.compilation.compiler.options.output,
12 | file = path.resolve(output.path, output.filename);
13 |
14 | cdnUploader.upload(file).then(function(res){
15 | let readmeFile = path.resolve(__dirname, '..', 'README.md');
16 | let content = fs.readFileSync(readmeFile, 'utf-8');
17 | content = content.replace(/script src="(.*)"/igm, `script src="${res[file]}"`);
18 | fs.writeFileSync(readmeFile, content);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(env = {}){
2 |
3 | const webpack = require('webpack'),
4 | path = require('path'),
5 | fs = require('fs'),
6 | packageConf = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
7 |
8 | let version = packageConf.version,
9 | library = packageConf.name.replace(/(?:^|-)(\w)/g, (_, m) => m.toUpperCase()),
10 | proxyPort = 8081,
11 | plugins = [],
12 | jsLoaders = [];
13 |
14 | if(env.production){
15 | //compress js in production environment
16 | plugins.push(
17 | new webpack.optimize.UglifyJsPlugin({
18 | compress: {
19 | warnings: false,
20 | drop_console: false,
21 | }
22 | })
23 | );
24 | }
25 |
26 | if(fs.existsSync('./.babelrc')){
27 | //use babel
28 | let babelConf = JSON.parse(fs.readFileSync('.babelrc'));
29 | jsLoaders.push({
30 | loader: 'babel-loader',
31 | options: babelConf
32 | });
33 | }
34 |
35 | return {
36 | entry: './lib/app.js',
37 | output: {
38 | filename: env.production ? `${library}-${version}.min.js` : `${library}.js`,
39 | path: path.resolve(__dirname, 'dist'),
40 | publicPath: '/js/',
41 | library: `${library}`,
42 | libraryTarget: 'umd'
43 | },
44 |
45 | plugins: plugins,
46 |
47 | module: {
48 | rules : [{
49 | test: /\.js$/,
50 | exclude: /(node_modules|bower_components)/,
51 | use: jsLoaders
52 | }]
53 | },
54 |
55 | devServer: {
56 | proxy: {
57 | "*": `http://127.0.0.1:${proxyPort}`,
58 | }
59 | }
60 | };
61 | }
62 |
--------------------------------------------------------------------------------