UNPKG

31.1 kBJavaScriptView Raw
1/// <reference path="../typings/globals/node/index.d.ts" />
2'use strict';
3class 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
580module.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