UNPKG

thinknode

Version:

A fast, flexible and all-in-one web framework for node.js.

466 lines (396 loc) 13.3 kB
'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;