UNPKG

web-dev-server

Version:

Node.js simple http server for common development or training purposes.

489 lines 25.2 kB
Object.defineProperty(exports, "__esModule", { value: true }); exports.Register = void 0; var tslib_1 = require("tslib"); var fs_1 = tslib_1.__importDefault(require("fs")); var path_1 = tslib_1.__importDefault(require("path")); var Record_1 = require("./Registers/Record"); var Register = /** @class */ (function () { /** * @summary Register constructor with stored server instance to call it back. * @param server */ function Register(server) { /** * @summary Store of cached application instances. * Keys are index script directories, values are `Record` types. */ this.store = new Map(); /** * @summary Store with keys as application instance index script directories full paths * and values as application instances dependent files full paths. */ this.dependencies = new Map(); /** * @summary Store with keys as watched filesystem directories and values as * dependent application instance index script directories full paths. */ this.watchedDirs = new Map(); this.watchHandleTimeouts = new Map(); this.server = server; } /** * @summary Set internal `Server` handling functionality. * @param errorsHandler */ Register.prototype.SetErrorsHandler = function (errorsHandler) { this.errorsHandler = errorsHandler; return this; }; /** * @summary Initialize filesystem change or rename handler for given * fullpath file to clear necessary require cache modules (only if necessary): */ Register.prototype.AddWatchHandlers = function (requiredByFullPath, cacheKeysToWatchFullPaths) { var e_1, _a, e_2, _b, e_3, _c; var alreadyDependentFiles, dependentIndexScriptsDirs, checkedDirectories, dependentDirFullPath, requiredByDirFullPath, watchedDirAlready, pos; pos = requiredByFullPath.lastIndexOf('/'); if (pos == -1) { requiredByDirFullPath = requiredByFullPath; } else { requiredByDirFullPath = requiredByFullPath.substr(0, pos); } // Add any other dependent files not already in dependencies: if (!this.dependencies.has(requiredByDirFullPath)) { alreadyDependentFiles = new Set(); try { for (var cacheKeysToWatchFullPaths_1 = tslib_1.__values(cacheKeysToWatchFullPaths), cacheKeysToWatchFullPaths_1_1 = cacheKeysToWatchFullPaths_1.next(); !cacheKeysToWatchFullPaths_1_1.done; cacheKeysToWatchFullPaths_1_1 = cacheKeysToWatchFullPaths_1.next()) { var newDependentFile = cacheKeysToWatchFullPaths_1_1.value; alreadyDependentFiles.add(newDependentFile); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (cacheKeysToWatchFullPaths_1_1 && !cacheKeysToWatchFullPaths_1_1.done && (_a = cacheKeysToWatchFullPaths_1.return)) _a.call(cacheKeysToWatchFullPaths_1); } finally { if (e_1) throw e_1.error; } } } else { alreadyDependentFiles = this.dependencies.get(requiredByDirFullPath); try { for (var cacheKeysToWatchFullPaths_2 = tslib_1.__values(cacheKeysToWatchFullPaths), cacheKeysToWatchFullPaths_2_1 = cacheKeysToWatchFullPaths_2.next(); !cacheKeysToWatchFullPaths_2_1.done; cacheKeysToWatchFullPaths_2_1 = cacheKeysToWatchFullPaths_2.next()) { var newDependentFile = cacheKeysToWatchFullPaths_2_1.value; if (!alreadyDependentFiles.has(newDependentFile)) alreadyDependentFiles.add(newDependentFile); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (cacheKeysToWatchFullPaths_2_1 && !cacheKeysToWatchFullPaths_2_1.done && (_b = cacheKeysToWatchFullPaths_2.return)) _b.call(cacheKeysToWatchFullPaths_2); } finally { if (e_2) throw e_2.error; } } } this.dependencies.set(requiredByDirFullPath, alreadyDependentFiles); // Check if directory with application script is already monitored: watchedDirAlready = this.hasWatchHandler(requiredByDirFullPath); if (watchedDirAlready == null) { // Add current application script directory between watched directories // and add dependency on it: this.addWatchHandler(requiredByDirFullPath, requiredByDirFullPath); } else { // Check if there is for this directory already a dependency // for currently called application index script: dependentIndexScriptsDirs = this.watchedDirs.get(watchedDirAlready); if (!dependentIndexScriptsDirs.has(requiredByDirFullPath)) { dependentIndexScriptsDirs.add(requiredByDirFullPath); this.watchedDirs.set(watchedDirAlready, dependentIndexScriptsDirs); } } // Check if all dependent file directories are already monitored: checkedDirectories = new Set(); try { for (var cacheKeysToWatchFullPaths_3 = tslib_1.__values(cacheKeysToWatchFullPaths), cacheKeysToWatchFullPaths_3_1 = cacheKeysToWatchFullPaths_3.next(); !cacheKeysToWatchFullPaths_3_1.done; cacheKeysToWatchFullPaths_3_1 = cacheKeysToWatchFullPaths_3.next()) { var dependentFileFullPath = cacheKeysToWatchFullPaths_3_1.value; pos = dependentFileFullPath.lastIndexOf('/'); if (pos == -1) { dependentDirFullPath = dependentFileFullPath; } else { dependentDirFullPath = dependentFileFullPath.substr(0, pos); } if (checkedDirectories.has(dependentDirFullPath)) continue; checkedDirectories.add(dependentDirFullPath); watchedDirAlready = this.hasWatchHandler(dependentDirFullPath); if (watchedDirAlready == null) { // Add current dependent script directory between watched directories // and add dependency to and application index script on it: this.addWatchHandler(dependentDirFullPath, requiredByDirFullPath); } else { // Check if there is for this directory already a dependency // for currently called application index script: dependentIndexScriptsDirs = this.watchedDirs.get(watchedDirAlready); if (!dependentIndexScriptsDirs.has(requiredByDirFullPath)) { dependentIndexScriptsDirs.add(requiredByDirFullPath); this.watchedDirs.set(watchedDirAlready, dependentIndexScriptsDirs); } } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (cacheKeysToWatchFullPaths_3_1 && !cacheKeysToWatchFullPaths_3_1.done && (_c = cacheKeysToWatchFullPaths_3.return)) _c.call(cacheKeysToWatchFullPaths_3); } finally { if (e_3) throw e_3.error; } } }; Register.prototype.addWatchHandler = function (dirFullPathToWatch, dependentIndexScriptDirFullPath) { var _this = this; var dependentIndexScriptDirs = new Set(); dependentIndexScriptDirs.add(dependentIndexScriptDirFullPath); this.watchedDirs.set(dirFullPathToWatch, dependentIndexScriptDirs); fs_1.default.watch(dirFullPathToWatch, { persistent: true, recursive: true }, function (eventType, fileName) { if (fileName.length > 3 && fileName.substr(-3).toLowerCase() == '.js') { // Delay cleaning for windows and do it fo all systems, because watching API is unstable: var changedFileFullPath = dirFullPathToWatch + '/' + fileName, prevWatchHandleTimeout, newWatchHandleTimeout; if (_this.watchHandleTimeouts.has(changedFileFullPath)) { prevWatchHandleTimeout = _this.watchHandleTimeouts.get(changedFileFullPath); clearTimeout(prevWatchHandleTimeout); _this.watchHandleTimeouts.delete(changedFileFullPath); } newWatchHandleTimeout = setTimeout(function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: clearTimeout(newWatchHandleTimeout); this.watchHandleTimeouts.delete(changedFileFullPath); return [4 /*yield*/, this.clearInstanceAndRequireCacheOnChange(dirFullPathToWatch, changedFileFullPath)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }, 50); _this.watchHandleTimeouts.set(changedFileFullPath, newWatchHandleTimeout); } }); }; /** * @summary Clear instance cache and require cache for all dependent index script directories. */ Register.prototype.clearInstanceAndRequireCacheOnChange = function (dirFullPathToWatch, changedFileFullPath) { return tslib_1.__awaiter(this, void 0, void 0, function () { var dependentIndexScriptDirs, promises, cacheRecord, _a, _b, dependentIndexScriptDir, allDone, allDoneBools; var e_4, _c; var _this = this; return tslib_1.__generator(this, function (_d) { switch (_d.label) { case 0: if (!this.watchedDirs.has(dirFullPathToWatch)) return [2 /*return*/, false]; dependentIndexScriptDirs = this.watchedDirs.get(dirFullPathToWatch), promises = []; try { for (_a = tslib_1.__values(dependentIndexScriptDirs.keys()), _b = _a.next(); !_b.done; _b = _a.next()) { dependentIndexScriptDir = _b.value; if (!this.store.has(dependentIndexScriptDir)) continue; cacheRecord = this.store.get(dependentIndexScriptDir); this.store.delete(dependentIndexScriptDir); (function (cacheRecordLocal) { promises.push(new Promise(function (resolve, reject) { return tslib_1.__awaiter(_this, void 0, void 0, function () { var e_5; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: if (!(cacheRecordLocal != null)) return [3 /*break*/, 5]; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, cacheRecordLocal.Instance.Stop(this.server)]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: e_5 = _a.sent(); this.errorsHandler.LogError(e_5, 500, null, null); return [3 /*break*/, 4]; case 4: this .ClearModuleInstance(dependentIndexScriptDir) .ClearModuleRequireCache(cacheRecordLocal); _a.label = 5; case 5: resolve(true); return [2 /*return*/]; } }); }); })); })(cacheRecord); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_4) throw e_4.error; } } promises.push(new Promise(function (resolve, reject) { return tslib_1.__awaiter(_this, void 0, void 0, function () { var fullPathResolved; return tslib_1.__generator(this, function (_a) { fullPathResolved = require.resolve(changedFileFullPath); if (typeof (require.cache[fullPathResolved]) != 'undefined') { delete require.cache[fullPathResolved]; if (this.server.IsDevelopment()) console.info('Module cache cleaned for: "' + changedFileFullPath + '"'); } resolve(true); return [2 /*return*/]; }); }); })); allDone = Promise.all(promises); return [4 /*yield*/, allDone]; case 1: allDoneBools = _d.sent(); return [2 /*return*/, allDoneBools.length == promises.length]; } }); }); }; /** * @summary Check if given directory full path has already any other * parent directory recursive watched or if the given directory itself has a watched. * @param dirFullPath * @return Already watched full path to cover this directory. */ Register.prototype.hasWatchHandler = function (dirFullPath) { var e_6, _a; var result = null; try { for (var _b = tslib_1.__values(this.watchedDirs.keys()), _c = _b.next(); !_c.done; _c = _b.next()) { var watchedParentDirFullPath = _c.value; if (dirFullPath.indexOf(watchedParentDirFullPath) === 0) { result = watchedParentDirFullPath; break; } } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_6) throw e_6.error; } } return result; }; /** * @summary Try to search in application scripts cache for * any application instance to handle given directory or virtual directory request. * @param pathsToFound */ Register.prototype.TryToFindParentDirectoryIndexScriptModule = function (pathsToFound) { var dirIndexScriptsModule = null, pathToFound = '', documentRoot = this.server.GetDocumentRoot(); for (var i = 0, l = pathsToFound.length; i < l; i += 1) { pathToFound = path_1.default.resolve(documentRoot + pathsToFound[i]).replace(/\\/g, '/'); if (this.store.has(pathToFound)) { dirIndexScriptsModule = this.store.get(pathToFound); break; } } return dirIndexScriptsModule; }; Register.prototype.GetIndexScriptModuleRecord = function (fullPath) { var indexScriptModuleRecord = null; if (this.store.has(fullPath)) indexScriptModuleRecord = this.store.get(fullPath); return indexScriptModuleRecord; }; /** * @summary Set new application instance cache record. * @param appInstance * @param indexScriptModTime * @param indexScriptFileName * @param dirFullPath */ Register.prototype.SetNewApplicationCacheRecord = function (appInstance, indexScriptModTime, indexScriptFileName, dirFullPath) { this.store.set(dirFullPath, new Record_1.Record(appInstance, indexScriptModTime, indexScriptFileName, dirFullPath)); return this; }; /** * @summary Get registered running apps count. */ Register.prototype.GetSize = function () { return this.store.size; }; /** * @summary Stop all running registered app instances. * @param cb */ Register.prototype.StopAll = function (cb) { var _this = this; var promises = []; if (this.store.size === 0) return cb(); this.store.forEach(function (record, indexScriptDirFullPath) { promises.push(new Promise(function (resolve, reject) { return tslib_1.__awaiter(_this, void 0, void 0, function () { var e_7; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: if (!(!record.Instance || !record.Instance.Stop)) return [3 /*break*/, 1]; this .ClearModuleInstance(indexScriptDirFullPath) .ClearModuleRequireCache(record); resolve(); return [3 /*break*/, 5]; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, record.Instance.Stop(this.server)]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: e_7 = _a.sent(); this.errorsHandler.LogError(e_7, 500, null, null); return [3 /*break*/, 4]; case 4: this .ClearModuleInstance(indexScriptDirFullPath) .ClearModuleRequireCache(record); resolve(); _a.label = 5; case 5: return [2 /*return*/]; } }); }); })); }); Promise.all(promises).then(function () { if (cb) cb(); }); }; /** * @summary Delete cached module from Node.JS require cache by full path. * @param indexScriptDirFullPath */ Register.prototype.ClearModuleInstanceAndModuleRequireCache = function (indexScriptDirFullPath) { if (this.store.has(indexScriptDirFullPath)) { var record = this.store.get(indexScriptDirFullPath); this.store.delete(indexScriptDirFullPath); this.ClearModuleRequireCache(record); } }; /** * @summary Delete cached application index script module instance. * @param indexScriptDirFullPath */ Register.prototype.ClearModuleInstance = function (indexScriptDirFullPath) { if (!this.store.has(indexScriptDirFullPath)) return this; this.store.delete(indexScriptDirFullPath); return this; }; /** * @summary Delete require cache for dependencies of application index script dir full path * delete require cache for index script file itself. * @param indexScriptDirFullPath * @param indexScriptFullPath */ Register.prototype.ClearModuleRequireCache = function (cacheRecord) { var e_8, _a, e_9, _b; var indexScriptDirFullPath = cacheRecord.DirectoryFullPath, indexScriptFullPath = indexScriptDirFullPath + '/' + cacheRecord.IndexScriptFileName, pathsToClear = new Map(); pathsToClear.set(indexScriptFullPath, require.resolve(indexScriptFullPath)); var dependentFiles; if (this.dependencies.has(indexScriptDirFullPath)) { dependentFiles = this.dependencies.get(indexScriptDirFullPath); try { for (var _c = tslib_1.__values(dependentFiles.keys()), _d = _c.next(); !_d.done; _d = _c.next()) { var dependentFileFullPath = _d.value; pathsToClear.set(dependentFileFullPath, require.resolve(dependentFileFullPath)); } } catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_8) throw e_8.error; } } } var isDevelopment = this.server.IsDevelopment(); try { for (var _e = tslib_1.__values(pathsToClear.entries()), _f = _e.next(); !_f.done; _f = _e.next()) { var _g = tslib_1.__read(_f.value, 2), fullPath = _g[0], fullPathResolved = _g[1]; if (typeof (require.cache[fullPathResolved]) != 'undefined') { delete require.cache[fullPathResolved]; if (isDevelopment) console.info('Module cache cleaned for: "' + fullPath + '"'); } } } catch (e_9_1) { e_9 = { error: e_9_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } finally { if (e_9) throw e_9.error; } } return this; }; /** * @summary Clear all require cache. */ Register.prototype.ClearAllRequireCache = function () { var requireCacheKeys = Object.keys(require.cache); for (var i = 0, l = requireCacheKeys.length; i < l; i++) delete require.cache[requireCacheKeys[i]]; return this; }; /** * @summary Get all required full paths as difference between application call and after application call. * @param cacheKeysBeforeRequire * @param cacheKeysAfterRequire * @param requiredBy * @param doNotIncludePaths */ Register.GetRequireCacheDifferenceKeys = function (cacheKeysBeforeRequire, cacheKeysAfterRequire, requiredBy, doNotIncludePaths) { var e_10, _a; var result = [], record, doNotInclude; for (var i = 0, l = cacheKeysAfterRequire.length; i < l; i += 1) { record = cacheKeysAfterRequire[i]; if (cacheKeysBeforeRequire.indexOf(record) == -1) { record = record.replace(/\\/g, '/'); if (record !== requiredBy) { doNotInclude = false; try { for (var doNotIncludePaths_1 = (e_10 = void 0, tslib_1.__values(doNotIncludePaths)), doNotIncludePaths_1_1 = doNotIncludePaths_1.next(); !doNotIncludePaths_1_1.done; doNotIncludePaths_1_1 = doNotIncludePaths_1.next()) { var doNotIncludePath = doNotIncludePaths_1_1.value; if (record.indexOf(doNotIncludePath) != -1) { doNotInclude = true; break; } } } catch (e_10_1) { e_10 = { error: e_10_1 }; } finally { try { if (doNotIncludePaths_1_1 && !doNotIncludePaths_1_1.done && (_a = doNotIncludePaths_1.return)) _a.call(doNotIncludePaths_1); } finally { if (e_10) throw e_10.error; } } if (!doNotInclude) result.push(record); } } } return result; }; return Register; }()); exports.Register = Register; //# sourceMappingURL=Register.js.map