得不到总是最好 ----
在开发的thinkjs的过程中,会发现thinkjs 在开发过程中总是能够实时编译文件进入app目录,不用开发者重新reload .可以让node web开发可以和php一样方便调试.相对于纯express 或者koa来说 thinkjs 提供了更佳的开发环境.(当然express和koa本身意图也不在次).对于thinkjs 的实时编译 ,也引起我的一点兴趣.
1.think-watcher 通过setTimeout 循环检查文件,返回文件改动列表
通过查看think-watcher 源码可以得知 think-watcher 只是一个方法集
源码链接:https://github.com/thinkjs/think-watcher/blob/master/index.js
const helper = require('think-helper');
const assert = require('assert');
const fs = require('fs');
const debug = require('debug')('think-watcher');
const path = require('path');
/**
* default options
* @type {Object}
*/
const defaultOptions = {
allowExts: ['js', 'es', 'ts'],
filter: (fileInfo, options) => {
const seps = fileInfo.file.split(path.sep);
// filter hidden file
const flag = seps.some(item => {
return item[0] === '.';
});
if (flag) {
return false;
}
const ext = path.extname(fileInfo.file).slice(1);
return options.allowExts.indexOf(ext) !== -1;
}
};
/**
* watcher class
*/
class Watcher {
/**
* constructor
* @param {Object} options watch options
* @param {Function} cb callback when files changed
*/
constructor(options, cb) {
assert(helper.isFunction(cb), 'callback must be a function');
options = this.buildOptions(options);
debug(`srcPath: ${options.srcPath}`);
debug(`diffPath: ${options.diffPath}`);
this.options = options;
this.cb = cb;
this.lastMtime = {};
}
/**
* build Options
* @param {Object} options
*/
buildOptions(options = {}) {
if (helper.isString(options)) {
options = {srcPath: options};
}
let srcPath = options.srcPath;
assert(srcPath, 'srcPath can not be blank');
if (!helper.isArray(srcPath)) {
srcPath = [srcPath];
}
let diffPath = options.diffPath || [];
if (!helper.isArray(diffPath)) {
diffPath = [diffPath];
}
options.srcPath = srcPath;
options.diffPath = diffPath;
if (!options.filter) {
options.filter = defaultOptions.filter;
}
if (!options.allowExts) {
options.allowExts = defaultOptions.allowExts;
}
return options;
}
/**
* get changed files
*/
getChangedFiles() {
const changedFiles = [];
const options = this.options;
options.srcPath.forEach((srcPath, index) => {
assert(path.isAbsolute(srcPath), 'srcPath must be an absolute path');
const diffPath = options.diffPath[index];
const srcFiles = helper.getdirFiles(srcPath).filter(file => {
return options.filter({path: srcPath, file}, options);
});
let diffFiles = [];
if (diffPath) {
diffFiles = helper.getdirFiles(diffPath).filter(file => {
return options.filter({path: diffPath, file}, options);
});
this.removeDeletedFiles(srcFiles, diffFiles, diffPath);
}
srcFiles.forEach(file => {
const mtime = fs.statSync(path.join(srcPath, file)).mtime.getTime();
if (diffPath) {
let diffFile = '';
diffFiles.some(dfile => {
if (this.removeFileExtName(dfile) === this.removeFileExtName(file)) {
diffFile = dfile;
return true;
}
});
const diffFilePath = path.join(diffPath, diffFile);
// compiled file exist
if (diffFile && helper.isFile(diffFilePath)) {
const diffmtime = fs.statSync(diffFilePath).mtime.getTime();
// if compiled file mtime is after than source file, return
if (diffmtime > mtime) {
return;
}
}
}
if (!this.lastMtime[file] || mtime > this.lastMtime[file]) {
this.lastMtime[file] = mtime;
changedFiles.push({path: srcPath, file});
}
});
});
return changedFiles;
}
/**
* remove files in diffPath when is deleted in srcPath
* @param {Array} srcFiles
* @param {Array} diffFiles
* @param {String} diffPath
*/
removeDeletedFiles(srcFiles, diffFiles, diffPath) {
const srcFilesWithoutExt = srcFiles.map(file => {
return this.removeFileExtName(file);
});
diffFiles.forEach(file => {
const fileWithoutExt = this.removeFileExtName(file);
if (srcFilesWithoutExt.indexOf(fileWithoutExt) === -1) {
const filepath = path.join(diffPath, file);
if (helper.isFile(filepath)) {
fs.unlinkSync(filepath);
}
}
});
}
/**
* remove file extname
* @param {String} file
*/
removeFileExtName(file) {
return file.replace(/\.\w+$/, '');
}
/**
* watch files change
*/
watch() {
const detectFiles = () => {
const changedFiles = this.getChangedFiles();
if (changedFiles.length) {
changedFiles.forEach(item => {
debug(`file changed: path=${item.path}, file=${item.file}`);
this.cb(item);
});
}
setTimeout(detectFiles, this.options.interval || 100);
};
detectFiles();
}
}
我们可以看到 watch方法中 汇总了各类文件变化情况 在1毫秒之类不断扫描并把执行回调函数
2.startWatcher中实例化
/**
* start watcher
*/
startWatcher() {
const Watcher = this.options.watcher;
if (!Watcher) return;
const instance = new Watcher({
srcPath: path.join(this.options.ROOT_PATH, 'src'),
diffPath: this.options.APP_PATH
}, fileInfo => this._watcherCallBack(fileInfo));
instance.watch();
}
实例化时 传入 开发路径和编译路径 还有回调函数
执行watch方法 开始定时扫描
3.回调函数_watcherCallBack
_watcherCallBack(fileInfo) {
let transpiler = this.options.transpiler;
if (transpiler) {
if (!helper.isArray(transpiler)) {
transpiler = [transpiler];
}
const ret = transpiler[0]({
srcPath: fileInfo.path,
outPath: this.options.APP_PATH,
file: fileInfo.file,
options: transpiler[1]
});
if (helper.isError(ret)) {
console.error(ret.stack);
this.notifier(ret);
return false;
}
if (think.logger) {
think.logger.info(`transpile file ${fileInfo.file} success`);
}
}
// reload all workers
if (this.masterInstance) {
this.masterInstance.forceReloadWorkers();
}
}
其实thinkjs 处理方式还是比较低级.
..前面传入了所有的文件改变的列表 其实最终还是重新启动了所有work进程....
扩展:希望明后两天可以整合一下think-watcher和express... thinkjs 不错 只是文档太栏,也不如express轻便,如果express能够提供一个更加舒适的开发环境 是不是会更好呢?
又水了一篇文章....哈哈...