thinknode
Version:
A fast, flexible and all-in-one web framework for node.js.
466 lines (396 loc) • 13.3 kB
JavaScript
'use strict';
exports.__esModule = true;
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
*
*
* @param {any} p
* @returns
*/
var isFile = function isFile(p) {
if (!_fs2.default.existsSync(p)) {
return false;
}
var stats = _fs2.default.statSync(p);
return stats.isFile();
};
/**
*
*
* @param {any} p
* @param {any} mode
* @returns
*/
var chmod = function chmod(p, mode) {
mode = mode || '0777';
if (!_fs2.default.existsSync(p)) {
return true;
}
return _fs2.default.chmodSync(p, mode);
};
/**
*
*
* @param {any} p
* @param {any} mode
*/
var mkDir = function mkDir(p, mode) {
mode = mode || '0777';
if (_fs2.default.existsSync(p)) {
chmod(p, mode);
return true;
}
var pp = _path2.default.dirname(p);
if (_fs2.default.existsSync(pp)) {
_fs2.default.mkdirSync(p, mode);
} else {
mkDir(pp, mode);
mkDir(p, mode);
}
return true;
};
/**
*
*
* @param {any} dir
* @param {any} prefix
* @param {any} filter
* @returns
*/
var getFiles = function getFiles(dir, prefix, filter) {
dir = _path2.default.normalize(dir);
if (!_fs2.default.existsSync(dir)) {
return [];
}
if (filter === true) {
filter = function filter(item) {
return item[0] !== '.';
};
}
prefix = prefix || '';
var files = _fs2.default.readdirSync(dir);
var result = [];
files.forEach(function (item) {
var stat = _fs2.default.statSync(dir + _path2.default.sep + item);
if (stat.isFile()) {
if (!filter || filter(item)) {
result.push(prefix + item);
}
} else if (stat.isDirectory()) {
if (!filter || filter(item, true)) {
var cFiles = getFiles(dir + _path2.default.sep + item, prefix + item + _path2.default.sep, filter);
result = result.concat(cFiles);
}
}
});
return result;
};
/**
*
*
* @export
* @class
*/
var _class = function () {
/**
*
* @param {*} args
*/
/**
*
*
*/
function _class() {
(0, _classCallCheck3.default)(this, _class);
this.compiledMtime = {};
this.compiledErrorFiles = [];
this.allowFileExt = ['.js', '.ts'];
this.init.apply(this, arguments);
}
/**
*
*
* @param {any} srcPath
* @param {any} outPath
* @param {any} [options={}]
* @param {any} callback
*/
/**
*
*
*/
/**
*
*
*/
_class.prototype.init = function init(srcPath, outPath) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var callback = arguments[3];
this.srcPath = _path2.default.normalize(srcPath);
this.outPath = _path2.default.normalize(outPath);
this.options = options;
this.callback = callback;
};
/**
*
*
* @param {any} file
* @param {any} onlyCopy
* @returns
*/
_class.prototype.compileFile = function compileFile(file, onlyCopy) {
var filePath = '' + this.srcPath + _path2.default.sep + file;
var content = _fs2.default.readFileSync(filePath, 'utf8');
//when get file content empty, maybe file is locked
if (!content) {
return true;
}
//only copy file content
if (onlyCopy) {
var saveFilepath = '' + this.outPath + _path2.default.sep + file;
mkDir(_path2.default.dirname(saveFilepath));
_fs2.default.writeFileSync(saveFilepath, content);
return true;
}
try {
if (this.options.type === 'ts') {
this.compileByTypeScript(content, file);
} else {
this.compileByBabel(content, file);
}
return true;
} catch (e) {
console.log('compile file ' + file + ' error');
console.log(e.stack);
}
return false;
};
/**
*
*
* @param {any} file
* @returns
*/
_class.prototype.getRelationPath = function getRelationPath(file) {
//use dirname to resolve file path in source-map-support
//so must use dirname in here
var pPath = _path2.default.dirname('' + this.outPath + _path2.default.sep + file);
return _path2.default.relative(pPath, '' + this.srcPath + _path2.default.sep + file);
};
/**
*
*
* @param {any} content
* @param {any} file
*/
_class.prototype.compileByTypeScript = function compileByTypeScript(content, file) {
var ts = require('typescript');
var diagnostics = [];
var output = ts.transpileModule(content, {
compilerOptions: {
module: 'commonjs',
target: 'es6',
experimentalDecorators: true,
emitDecoratorMetadata: true,
allowSyntheticDefaultImports: true,
sourceMap: true
},
fileName: file,
reportDiagnostics: !!diagnostics
});
ts.addRange(diagnostics, output.diagnostics);
//has error
if (diagnostics.length) {
var firstDiagnostics = diagnostics[0];
var _firstDiagnostics$fil = firstDiagnostics.file.getLineAndCharacterOfPosition(firstDiagnostics.start),
line = _firstDiagnostics$fil.line,
character = _firstDiagnostics$fil.character;
var message = ts.flattenDiagnosticMessageText(firstDiagnostics.messageText, '\n');
throw new Error(message + ' on Line ' + (line + 1) + ', Character ' + character);
}
console.log('[TypeScript] Compile file ' + file);
file = this.replaceExtName(file, '.js');
var sourceMap = JSON.parse(output.sourceMapText);
sourceMap.sources[0] = this.getRelationPath(file);
sourceMap.sourcesContent = [content];
//file value must be equal sources values
sourceMap.file = sourceMap.sources[0];
delete sourceMap.sourceRoot;
this.compileByBabel(output.outputText, file, sourceMap);
};
/**
*
*
* @param {any} content
* @param {any} file
* @param {any} orginSourceMap
*/
_class.prototype.compileByBabel = function compileByBabel(content, file, orginSourceMap) {
var relativePath = this.getRelationPath(file);
//babel not export default property
//so can not use `import babel from 'babel-core'`
var babel = require('babel-core');
var data = babel.transform(content, {
filename: file,
presets: [].concat(this.options.presets || [['es2015', { 'loose': true }], 'stage-2']),
plugins: [].concat(this.options.plugins || ['transform-runtime']),
sourceMaps: true,
sourceFileName: relativePath
});
console.log('[Babel] Compile file ' + file);
mkDir(_path2.default.dirname('' + this.outPath + _path2.default.sep + file));
var basename = _path2.default.basename(file);
var prefix = '//# sourceMappingURL=';
if (data.code.indexOf(prefix) === -1) {
data.code = data.code + '\n' + prefix + basename + '.map';
}
_fs2.default.writeFileSync('' + this.outPath + _path2.default.sep + file, data.code);
var sourceMap = data.map;
//file value must be equal sources values
sourceMap.file = sourceMap.sources[0];
if (orginSourceMap) {
sourceMap = this.mergeSourceMap(orginSourceMap, sourceMap);
}
_fs2.default.writeFileSync('' + this.outPath + _path2.default.sep + file + '.map', (0, _stringify2.default)(sourceMap, undefined, 4));
};
/**
*
*
* @param {any} orginSourceMap
* @param {any} sourceMap
* @returns
*/
_class.prototype.mergeSourceMap = function mergeSourceMap(orginSourceMap, sourceMap) {
var _require = require('source-map'),
SourceMapGenerator = _require.SourceMapGenerator,
SourceMapConsumer = _require.SourceMapConsumer;
sourceMap.file = sourceMap.file.replace(/\\/g, '/');
sourceMap.sources = sourceMap.sources.map(function (filePath) {
return filePath.replace(/\\/g, '/');
});
var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(sourceMap));
generator.applySourceMap(new SourceMapConsumer(orginSourceMap));
sourceMap = JSON.parse(generator.toString());
return sourceMap;
};
/**
* src file is deleted, but app file also exist
* then delete app file
* @param {any} srcFiles
* @param {any} appFiles
* @returns
*/
_class.prototype.getSrcDeletedFiles = function getSrcDeletedFiles(srcFiles, appFiles) {
var _this = this;
var srcFilesWithoutExt = srcFiles.map(function (item) {
return _this.replaceExtName(item);
});
return appFiles.filter(function (file) {
var extname = _path2.default.extname(file);
if (_this.allowFileExt.indexOf(extname) === -1) {
return null;
}
var fileWithoutExt = _this.replaceExtName(file);
//src file not exist
if (srcFilesWithoutExt.indexOf(fileWithoutExt) === -1) {
var filepath = _this.outPath + _path2.default.sep + file;
if (isFile(filepath)) {
_fs2.default.unlinkSync(filepath);
}
return true;
}
return null;
}).map(function (file) {
return _this.outPath + _path2.default.sep + file;
});
};
/**
*
*
* @param {any} filepath
* @param {string} [extname='']
* @returns
*/
_class.prototype.replaceExtName = function replaceExtName(filepath) {
var extname = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
return filepath.replace(/\.\w+$/, extname);
};
/**
*
*
* @param {any} once
*/
_class.prototype.compile = function compile(once) {
var _this2 = this;
var files = getFiles(this.srcPath, '', true);
var appFiles = getFiles(this.outPath, '', true);
var changedFiles = this.getSrcDeletedFiles(files, appFiles);
files.forEach(function (file) {
var extname = _path2.default.extname(file);
//if is not js file, only copy
if (_this2.allowFileExt.indexOf(extname) === -1) {
_this2.compileFile(file, true);
return;
}
var mTime = _fs2.default.statSync('' + _this2.srcPath + _path2.default.sep + file).mtime.getTime();
var outFile = '' + _this2.outPath + _path2.default.sep + file;
//change extname to .js.
//in typescript, file extname is .ts
outFile = _this2.replaceExtName(outFile, '.js');
if (isFile(outFile)) {
var outmTime = _fs2.default.statSync(outFile).mtime.getTime();
//if compiled file mtime is after than source file, return
if (outmTime >= mTime) {
return;
}
}
if (!_this2.compiledMtime[file] || mTime > _this2.compiledMtime[file]) {
var ret = _this2.compileFile(file);
if (ret) {
changedFiles.push(outFile);
}
_this2.compiledMtime[file] = mTime;
var index = _this2.compiledErrorFiles.indexOf(file);
if (ret) {
if (index > -1) {
_this2.compiledErrorFiles.splice(index, 1);
}
} else if (ret === false) {
if (index === -1) {
_this2.compiledErrorFiles.push(file);
}
}
}
});
//notify auto reload service to clear file cache
if (changedFiles.length && this.callback) {
this.callback(changedFiles);
}
if (!once) {
setTimeout(this.compile.bind(this), 100);
}
};
/**
*
*
* @static
* @param {any} srcPath
* @param {any} outPath
* @param {any} [options={}]
*/
_class.run = function run(srcPath, outPath) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var instance = new this(srcPath, outPath, options);
instance.compile(true);
};
return _class;
}();
exports.default = _class;