1 | /// <reference path="../typings/globals/node/index.d.ts" />
|
2 | ;
|
3 | class Watch{
|
4 | constructor(srcPath,options){
|
5 | const _ts = this;
|
6 |
|
7 | let m = _ts.m = {
|
8 | path:require('path'),
|
9 | chokidar:require('chokidar'),
|
10 | fs:require('fs-extra'),
|
11 | autoRefresh:require('../lib/autoRefresh'), //自动刷新
|
12 | openurl:require('openurl'), //打开前台页面
|
13 | tip:require('../lib/tip'), //文字提示
|
14 | pathInfo:require('../lib/getPathInfo'), //判断文件或目录是否存在
|
15 | Compile:require('../lib/compile'), //编译文件
|
16 | isFwsDir:require('../lib/isFwsDir'), //判断是否为fws项目目录
|
17 | getDirFilesPath:require('../lib/getDirFilesPath'), //获取目录文件数据
|
18 | isData:require('../lib/isData'), //判断是否为页面数据文件
|
19 | isSprite:require('../lib/isSprite'), //判断是否为精灵图数据
|
20 | getFileInfo:require('../lib/getFileInfo'), //获取指定文件的相关信息
|
21 | getLocalIp:require('../lib/getLocalIp'), //获取本机ip地址
|
22 | isFilter:require('../lib/isFilter'), //判断是否为需要忽略的文件
|
23 | getCompileFn:require('../lib/getCompileFn'), //根据文件类型来获取编译方法
|
24 | getDistPath:require('../lib/getDistPath'), //获取输出路径
|
25 | updateImgData:require('../lib/updateImgData'), //更新sass图片数据文件
|
26 | updateImg:require('../lib/updateImg')
|
27 | },
|
28 | config = _ts.config = {},
|
29 | option = _ts.option = options;
|
30 |
|
31 | config.src = fws.srcPath = typeof srcPath === 'string' ? m.path.join(fws.cmdPath,srcPath,'src'+m.path.sep) : fws.srcPath;
|
32 | config.dev = fws.devPath = m.path.join(config.src,'..','dev'+m.path.sep);
|
33 | config.dist = fws.distPath = m.path.join(config.src,'..','dist'+m.path.sep);
|
34 |
|
35 | }
|
36 | init(){
|
37 | const _ts = this,
|
38 | m = _ts.m,
|
39 | config = _ts.config;
|
40 |
|
41 | let tasks = _ts.taskList(),
|
42 | f = async ()=>{
|
43 | for(let i=0,len=tasks.length; i<len; i++){
|
44 | let task = await tasks[i]();
|
45 | if(task.status === 'success'){
|
46 | m.tip.success(task.msg);
|
47 | };
|
48 | };
|
49 | return '已经启动文件监听服务';
|
50 | };
|
51 |
|
52 | f().then(v => {
|
53 | m.tip.highlight('========================================');
|
54 | m.tip.highlight(v);
|
55 | m.tip.highlight('========================================');
|
56 | }).catch(e => {
|
57 | m.tip.error(e.msg);
|
58 | console.log('error',e);
|
59 | });
|
60 |
|
61 | }
|
62 | taskList(){
|
63 | const _ts = this,
|
64 | m = _ts.m,
|
65 | config = _ts.config,
|
66 | option = _ts.option,
|
67 | tsOption = _ts.option;
|
68 | let tasks = [],
|
69 | isInitCompile,
|
70 | projectDir = m.path.join(config.src,'..');
|
71 |
|
72 | //检查项目是否为一个fws项目
|
73 | tasks.push(()=>{
|
74 | return new Promise((resolve,reject)=>{
|
75 | if(m.isFwsDir(projectDir)){
|
76 | resolve({
|
77 | status:'success',
|
78 | msg:'检查目录为有效的 【FWS】 项目'
|
79 | });
|
80 | }else{
|
81 | reject({
|
82 | status:'error',
|
83 | msg:`${projectDir} 不是有效的fws项目目录`
|
84 | });
|
85 | };
|
86 | });
|
87 | });
|
88 |
|
89 | //开启http服务
|
90 | //var listenPort;
|
91 | let vsCodeDebugConfigDirPath = m.path.join(fws.srcPath,'../','.vscode/'),
|
92 | vsCodeDebugConfigPath = m.path.join(vsCodeDebugConfigDirPath,'launch.json'),
|
93 | vsCodeDebugConfigTplPath = m.path.join(fws.tplPath,'json','launchTpl.json'),
|
94 | vsCodeDebugConfigTpl = (()=>{
|
95 | if(m.pathInfo(vsCodeDebugConfigPath).type === 'file'){
|
96 | return JSON.parse(m.fs.readFileSync(vsCodeDebugConfigPath).toString());
|
97 | }else{
|
98 | return JSON.parse(m.fs.readFileSync(vsCodeDebugConfigTplPath).toString());
|
99 | };
|
100 | })();
|
101 |
|
102 | if(option.server){
|
103 | tasks.push(()=>{
|
104 | return new Promise((resolve,reject)=>{
|
105 | _ts.server = new m.autoRefresh();
|
106 | _ts.server.init().then(v => {
|
107 | //保存端口号
|
108 | global.fws.listenPort = v.data.listenPort;
|
109 | global.fws.localIp = m.getLocalIp();
|
110 |
|
111 | let url = `${global.fws.localIp}:${global.fws.listenPort}`;
|
112 | //vscode调试配置添加本服务url,以便启动访问
|
113 | vsCodeDebugConfigTpl.configurations.forEach(item => {
|
114 | if(item.name === 'FWS Web'){
|
115 | item.url = `http://${url}/dev/`;
|
116 | };
|
117 | });
|
118 |
|
119 | v.msg = v.msg + ` ${url}`;
|
120 | resolve(v);
|
121 | }).catch(e => {
|
122 | reject(e);
|
123 | });
|
124 | });
|
125 | });
|
126 | };
|
127 |
|
128 | // 创建调试文件
|
129 | tasks.push(()=>{
|
130 | return new Promise((resolve,reject)=>{
|
131 | try {
|
132 | // vscode调试配置添加本服务url,以便启动访问
|
133 | vsCodeDebugConfigTpl.configurations.forEach(item => {
|
134 | if(item.name === 'FWS Mocha'){
|
135 | item.program = m.path.join(fws.fwsPath,'node_modules','mocha','bin','_mocha');
|
136 | };
|
137 | });
|
138 |
|
139 | // 目录不存在则创建
|
140 | if(m.pathInfo(vsCodeDebugConfigDirPath).type !== 'dir'){
|
141 | m.fs.mkdirSync(m.path.join(vsCodeDebugConfigPath,'../'));
|
142 | };
|
143 | // 写入调试配置文件
|
144 | m.fs.writeFileSync(vsCodeDebugConfigPath,JSON.stringify(vsCodeDebugConfigTpl,null,4));
|
145 |
|
146 | resolve({
|
147 | status:'success',
|
148 | msg:'创建 vsCode 调试配置文件',
|
149 | data:{
|
150 | data:vsCodeDebugConfigTpl,
|
151 | path:vsCodeDebugConfigPath
|
152 | }
|
153 | });
|
154 | } catch (error) {
|
155 | reject({
|
156 | status:'error',
|
157 | msg:'创建 vsCode 调试配置文件失败'
|
158 | });
|
159 | };
|
160 | });
|
161 | });
|
162 |
|
163 | //如果有开启快速模式,将不会预先编译项目
|
164 | if(!option.fast){
|
165 | //将初始化项目任务添加到任务列表
|
166 | let initCompileTasks = require('../lib/initCompile_dev')({
|
167 | src:fws.srcPath,
|
168 | dist:fws.devPath
|
169 | });
|
170 |
|
171 | tasks.push(...initCompileTasks);
|
172 | };
|
173 |
|
174 | //开启浏览服务
|
175 | if(option.server && option.browse){
|
176 | tasks.push(()=>{
|
177 | return new Promise((resolve,reject)=>{
|
178 | try {
|
179 | if(fws.listenPort){
|
180 | m.openurl.open('http://'+fws.localIp+':'+fws.listenPort);
|
181 | resolve({
|
182 | status:'success',
|
183 | msg:'浏览项目'
|
184 | });
|
185 | }else{
|
186 | reject({
|
187 | status:'error',
|
188 | msg:'获取不到端口号',
|
189 | info:listenPort
|
190 | });
|
191 | };
|
192 | } catch (error) {
|
193 | reject({
|
194 | status:'error',
|
195 | msg:'启动浏览器失败',
|
196 | info:error
|
197 | });
|
198 | };
|
199 | });
|
200 | });
|
201 | };
|
202 |
|
203 | //监听文件
|
204 | tasks.push(()=>{
|
205 | return new Promise((resolve,reject)=>{
|
206 | try {
|
207 | let w = m.chokidar.watch(config.src,{persistent:true}),
|
208 | data = {};
|
209 |
|
210 | w.on('all',(stats,filePath)=>{
|
211 | //是否为需要过滤的文件
|
212 | let isFilter = m.isFilter(filePath),
|
213 | taskList = [];
|
214 |
|
215 | if(!isFilter){
|
216 | //是否为精灵图
|
217 | let isSprite = m.isSprite(filePath),
|
218 | isData = m.isData(filePath),
|
219 | fileInfo = m.getFileInfo(filePath),
|
220 | fileType = fileInfo.type,
|
221 | fileName = fileInfo.name,
|
222 | isPublic = fileInfo.isPublic,
|
223 | isVue = fileType === '.vue',
|
224 | isPug = fileType === '.jade' || fileType === '.pug',
|
225 |
|
226 | //是否为图片目录内图片(排除精灵图)
|
227 | isImgDirImgs = (()=>{
|
228 | let isImgDir = filePath.indexOf(m.path.join(fws.srcPath,'images',m.path.sep)) === 0,
|
229 | isImg = ['jpg','jpeg','png','gif'].some((item)=>{
|
230 | return '.'+item === fileType;
|
231 | });
|
232 |
|
233 | return isImgDir && isImg && !isSprite;
|
234 | })(),
|
235 | key = isSprite ? '_sprite' : isImgDirImgs ? '_img' : fileType,
|
236 | temp,
|
237 | compileFn = ()=>{
|
238 | let compile = m.getCompileFn(key),
|
239 | option = {
|
240 | debug:true
|
241 | };
|
242 | if(isSprite){
|
243 | //如果是精灵图,编译该精灵图对应的目录
|
244 | let srcDir = option.srcDir = m.path.dirname(filePath);
|
245 |
|
246 | option.distSpreiteDir = m.path.resolve(srcDir.replace(config.src,config.dev),'..');
|
247 | option.distScssDir = m.path.join(config.src,'css','_fws','sprite');
|
248 | taskList.push(()=>{
|
249 | return new compile(option);
|
250 | });
|
251 | }else if(isData){
|
252 | compile = m.getCompileFn('.pug');
|
253 | if(isPublic){
|
254 | //如果是数据公共文件,则编译所有的jade|pug文件
|
255 | let files = [],
|
256 | pugFiles = data['.pug'],
|
257 | jadeFiles = data['.jade'];
|
258 |
|
259 | //将pug和jade的文件添加到文件列表
|
260 | if(pugFiles){
|
261 | for(let i in pugFiles){
|
262 | files.push(i);
|
263 | };
|
264 | };
|
265 |
|
266 | if(jadeFiles){
|
267 | for(let i in jadeFiles){
|
268 | files.push(i);
|
269 | };
|
270 | };
|
271 |
|
272 | files.forEach((item,index)=>{
|
273 | option.src = item;
|
274 | option.dist = m.getDistPath(item,true);
|
275 |
|
276 | //根据jade|pug文件路径得到相对应的数据文件路径
|
277 | let srcInfo = m.getFileInfo(item),
|
278 | dataPath = item.replace(
|
279 | config.src,
|
280 | m.path.join(config.src,'data'+m.path.sep)
|
281 | );
|
282 | dataPath = m.path.join(
|
283 | m.path.dirname(dataPath),
|
284 | srcInfo.name+'.js'
|
285 | );
|
286 |
|
287 | //检查对应的文件是否存在,如果存在则引入文件
|
288 | if(m.pathInfo(dataPath).extension === '.js'){
|
289 | option.data = fws.require(dataPath);
|
290 | };
|
291 |
|
292 | taskList.push(()=>{
|
293 | return new compile(option);
|
294 | });
|
295 | });
|
296 | }else{
|
297 | //非公共的数据文件,内里只编译与之相对应的jade|pug文件
|
298 | let files = [],
|
299 |
|
300 | //将与之对应的jade|pug文件路径添加到文件列表
|
301 | dirPath = filePath.replace(
|
302 | m.path.join(config.src,'data'+m.path.sep),
|
303 | config.src
|
304 | );
|
305 | files.push(m.path.join(
|
306 | m.path.dirname(dirPath),
|
307 | fileName+'.jade'
|
308 | ));
|
309 | files.push(m.path.join(
|
310 | m.path.dirname(dirPath),
|
311 | fileName+'.pug'
|
312 | ));
|
313 |
|
314 | //循环文件列表,并检查文件是否有效,如果有效则将编译任务添加到任务列表
|
315 | files.forEach((item,index)=>{
|
316 | if(m.pathInfo(item).extension){
|
317 | option.src = item;
|
318 | option.dist = m.getDistPath(item,true);
|
319 | option.data = fws.require(filePath);
|
320 |
|
321 | taskList.push(()=>{
|
322 | return new compile(option);
|
323 | });
|
324 | };
|
325 | });
|
326 | };
|
327 | }else if(isPublic && data[key]){
|
328 | //如果公共文件,且有同类型的文件则编译同类型所有文件
|
329 | for(let i in data[key]){
|
330 | // option.src = i;
|
331 | // option.dist = m.getDistPath(i,true);
|
332 |
|
333 | let fileInfo = m.getFileInfo(i),
|
334 | fileType = fileInfo.type,
|
335 | pugData;
|
336 | //如果文件是pug或jade扩展名,则尝试获取页面的数据
|
337 | if(fileType === '.pug' || fileType === '.jade'){
|
338 | let dataPath = i.replace(
|
339 | config.src,
|
340 | m.path.join(config.src,'data'+m.path.sep)
|
341 | );
|
342 | dataPath = m.path.join(
|
343 | m.path.dirname(dataPath),
|
344 | fileInfo.name + '.js'
|
345 | );
|
346 |
|
347 | //检查对应的文件是否存在,如果存在则引入文件
|
348 | if(m.pathInfo(dataPath).extension === '.js'){
|
349 | pugData = fws.require(dataPath);
|
350 | };
|
351 | };
|
352 |
|
353 | taskList.push(()=>{
|
354 | //编译选项
|
355 | let op = {
|
356 | src:i,
|
357 | dist:m.getDistPath(i,true),
|
358 | debug:true
|
359 | };
|
360 |
|
361 | //pug或jade数据有的话,需要增加该项
|
362 | if(pugData){
|
363 | op.data = pugData;
|
364 | };
|
365 |
|
366 | return new compile(op);
|
367 | });
|
368 | };
|
369 |
|
370 | }else if(isVue){
|
371 | let esList = [],
|
372 | isEs = (type)=>{
|
373 | return type === '.es' || type === '.es6' || type === '.ts';
|
374 | };
|
375 |
|
376 | //遍历项目目录文件,如果是es、es6、ts文件则添加到文件编译列表
|
377 | for(let i in data){
|
378 | if(isEs(i)){
|
379 | for(let ii in data[i]){
|
380 | esList.push(ii);
|
381 | };
|
382 | };
|
383 | };
|
384 |
|
385 | //遍历所有的文件列表
|
386 | esList.forEach((item)=>{
|
387 | option.src = item;
|
388 | option.dist = m.getDistPath(item,true);
|
389 |
|
390 | //重新得到文件编译方法
|
391 | let fileInfo = m.getFileInfo(item);
|
392 | compile = m.getCompileFn(fileInfo.type);
|
393 |
|
394 | taskList.push(()=>{
|
395 | return new compile(option);
|
396 | });
|
397 | });
|
398 | }else{
|
399 | //只编译自身即可
|
400 | option.src = filePath;
|
401 | option.dist = m.getDistPath(filePath,true);
|
402 |
|
403 | if(isPug){
|
404 | //根据jade|pug文件路径得到相对应的数据文件路径
|
405 | let dataPath = filePath.replace(
|
406 | config.src,
|
407 | m.path.join(config.src,'data'+m.path.sep)
|
408 | );
|
409 | dataPath = m.path.join(
|
410 | m.path.dirname(dataPath),
|
411 | fileInfo.name + '.js'
|
412 | );
|
413 |
|
414 | //检查对应的文件是否存在,如果存在则引入文件
|
415 | if(m.pathInfo(dataPath).extension === '.js'){
|
416 | option.data = fws.require(dataPath);
|
417 | };
|
418 | };
|
419 |
|
420 | //如果是图片需要同步
|
421 | if(isImgDirImgs){
|
422 | taskList.push(()=>{
|
423 | let cp = m.getCompileFn('copy');
|
424 | return new cp(option);
|
425 | });
|
426 | };
|
427 |
|
428 | //使用与文件对应的方法进行处理
|
429 | taskList.push(()=>{
|
430 | return new compile(option);
|
431 | });
|
432 | };
|
433 | };
|
434 |
|
435 | switch (stats) {
|
436 | //文件添加,如果文件为非公共文件,则将文件保存到数据中
|
437 | case 'add':
|
438 | if(data[key] === undefined){
|
439 | data[key] = {};
|
440 | };
|
441 | if(!isPublic && !isData){
|
442 | data[key][filePath] = null;
|
443 | };
|
444 |
|
445 | //如果初始化状态已经完成,则添加的文件也会进行编译处理
|
446 | if(isInitCompile){
|
447 | compileFn();
|
448 | };
|
449 |
|
450 | //500ms内wacth无新增加文件响应将初始化状态设置为完成
|
451 | if(!isInitCompile){
|
452 | clearTimeout(temp);
|
453 | temp = setTimeout(()=>{
|
454 | isInitCompile = true;
|
455 | },500);
|
456 | };
|
457 | break;
|
458 | //文件修改
|
459 | case 'change':
|
460 | compileFn();
|
461 | break;
|
462 |
|
463 | //文件删除
|
464 | case 'unlink':
|
465 | try {
|
466 | //图片目录,非精灵图删除更新图片base64数据
|
467 | if(isImgDirImgs){
|
468 | if(fws.ImgsData === undefined){
|
469 | let srcDirFiles = _ts.m.getDirFilesPath({
|
470 | srcDir:fws.srcPath
|
471 | });
|
472 |
|
473 | taskList.push(()=>{
|
474 | return new Promise((resolve,reject)=>{
|
475 | new _ts.m.updateImg({
|
476 | srcDirFiles:srcDirFiles
|
477 | }).then(v => {
|
478 | resolve(v);
|
479 | }).catch(e => {
|
480 | reject(e);
|
481 | });
|
482 | })
|
483 | });
|
484 | }else{
|
485 | let key = filePath.replace(fws.srcPath,'../').replace(/\\/g,'/');
|
486 | if(fws.ImgsData[key]){
|
487 | delete fws.ImgsData[key];
|
488 | taskList.push(()=>{
|
489 | return new Promise((resolve,reject)=>{
|
490 | m.updateImgData(fws.ImgsData).then(v => {
|
491 | resolve(v);
|
492 | }).catch(e => {
|
493 | reject(e);
|
494 | });
|
495 | });
|
496 | });
|
497 | };
|
498 | };
|
499 | };
|
500 |
|
501 | //精灵图删除即时更新数据
|
502 | if(isSprite){
|
503 | compileFn();
|
504 | };
|
505 |
|
506 | delete data[key][filePath];
|
507 | } catch (error) {
|
508 | console.log(error);
|
509 | };
|
510 | break;
|
511 | };
|
512 |
|
513 | //如果有可执行的任务
|
514 | if(taskList.length){
|
515 | let f = async ()=>{
|
516 | let data = [];
|
517 | for(let i=0,len=taskList.length; i<len; i++){
|
518 | let subTask = await taskList[i]();
|
519 | data.push(subTask);
|
520 | if(subTask instanceof Array){
|
521 | subTask.forEach((item,index)=>{
|
522 | if(item.status === 'success'){
|
523 | m.tip.success(item.msg);
|
524 | };
|
525 | })
|
526 | };
|
527 | if(subTask.status === 'success'){
|
528 | m.tip.success(subTask.msg);
|
529 | };
|
530 | };
|
531 | return {
|
532 | status:'success',
|
533 | msg:'文件监听编译完成',
|
534 | data:data
|
535 | };
|
536 | };
|
537 |
|
538 | f().then(v => {
|
539 | //编译完成,如果有开启server则需要往前台提供刷新服务
|
540 |
|
541 | if(tsOption.server){
|
542 | v.data.forEach((item,index)=>{
|
543 | _ts.server.io.broadcast('refresh',{
|
544 | status:'success',
|
545 | path:item.distPath
|
546 | });
|
547 | });
|
548 | };
|
549 |
|
550 | }).catch(e => {
|
551 | //编译遇到出错
|
552 | m.tip.error(e.msg);
|
553 | console.log(e.info);
|
554 | });
|
555 | };
|
556 | };
|
557 | });
|
558 |
|
559 | resolve({
|
560 | status:'success',
|
561 | msg:'开启文件监听服务'
|
562 | });
|
563 | } catch (error) {
|
564 | reject({
|
565 | status:'error',
|
566 | msg:'',
|
567 | info:error
|
568 | });
|
569 | };
|
570 | });
|
571 | });
|
572 |
|
573 | return tasks;
|
574 | }
|
575 |
|
576 |
|
577 | };
|
578 |
|
579 |
|
580 | module.exports = {
|
581 | regTask:{
|
582 | command:'[name]',
|
583 | description:'项目监听编译',
|
584 | option:[
|
585 | ['-b, --browse','浏览器访问项目'],
|
586 | ['-s, --server','开启http服务'],
|
587 | ['-f, --fast','快速模式,将不会预先编译项目']
|
588 | ],
|
589 | help:()=>{
|
590 | console.log('');
|
591 | console.log(' 补充说明:');
|
592 | console.log(' ------------------------------------------------------------');
|
593 | console.log(' 暂无');
|
594 | },
|
595 | action:Watch
|
596 | }
|
597 | }; |
\ | No newline at end of file |