reboost
Version:
A super fast dev server for rapid web development
168 lines (167 loc) • 8.24 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFileHandler = void 0;
const tslib_1 = require("tslib");
const md5_file_1 = (0, tslib_1.__importDefault)(require("md5-file"));
const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
const fs_1 = (0, tslib_1.__importDefault)(require("fs"));
const path_1 = (0, tslib_1.__importDefault)(require("path"));
const crypto_1 = (0, tslib_1.__importDefault)(require("crypto"));
const utils_1 = require("./utils");
const transformer_1 = require("./transformer");
const watcher_1 = require("./watcher");
const sourceMapCommentRE = /^[ \t]*\/\/#\s*sourceMappingURL=.+(?![\s\S]*\/\/#\s*sourceMappingURL=.+)/m;
const fixSourceMap = (code, sourceMapPath) => {
// Remove other source maps
return `${code.replace(sourceMapCommentRE, '')}\n//# sourceMappingURL=/raw?q=${encodeURIComponent(sourceMapPath)}`;
};
const createFileHandler = (instance) => {
let initialized = false;
let filesDir;
let transformer;
let watcher;
let currentPlugins;
const { config, cache } = instance;
const memoizedFiles = new Map();
const noop = () => { };
const eTagBase = `
mtime: "@mtime"
plugins: "@plugins"
dependentsMtime: "@dependentsMtime"
`.split('\n').map((s) => s.trim()).join('\n').trim();
const getETag = (filePath) => {
const mtime = Math.floor(fs_1.default.statSync(filePath).mtimeMs) + '';
let dependentsMtime = '';
const deps = cache.cacheInfo[cache.cacheIDs[filePath]].dependencies;
if (deps) {
dependentsMtime = Object.keys(deps).sort().map((dependency) => {
try {
return Math.floor(fs_1.default.statSync(dependency).mtimeMs);
}
catch (e) {
return '';
}
}).join('-');
}
const eTagStr = eTagBase
.replace('@mtime', mtime)
.replace('@plugins', currentPlugins)
.replace('@dependentsMtime', dependentsMtime);
const eTag = crypto_1.default.createHash('md5').update(eTagStr).digest('hex');
return eTag;
};
return async (ctx) => {
if (!initialized) {
filesDir = cache.getFilesDir();
transformer = (0, transformer_1.createTransformer)(instance);
watcher = (0, watcher_1.createWatcher)(instance);
currentPlugins = cache.getCurrentPlugins();
(0, utils_1.ensureDir)(config.cacheDir);
(0, utils_1.ensureDir)(filesDir);
fs_1.default.writeFile(path_1.default.join(config.cacheDir, './.gitignore'), '/**/*', noop);
initialized = true;
}
const filePath = ctx.query.q;
let startTime;
let transformedCode;
if (instance.isLogEnabled('responseTime'))
startTime = process.hrtime();
if (fs_1.default.existsSync(filePath)) {
const mtime = Math.floor(fs_1.default.statSync(filePath).mtimeMs);
const makeNewCache = async () => {
const cacheID = (config.debugMode ? path_1.default.basename(filePath) + '-' : '') + (0, utils_1.uniqueID)(16);
const { code, map, dependencies, error } = await transformer.transformFile(filePath);
transformedCode = code;
if (map) {
const sourceMapPath = cache.sourceMapPath(cacheID);
transformedCode = fixSourceMap(transformedCode, sourceMapPath);
fs_1.default.writeFile(sourceMapPath, map, noop);
}
if (!error) {
fs_1.default.writeFileSync(cache.cacheFilePath(cacheID), transformedCode);
cache.cacheIDs[filePath] = cacheID;
cache.cacheInfo[cacheID] = {
hash: await (0, md5_file_1.default)(filePath),
mtime,
plugins: currentPlugins
};
await cache.updateDependencies(filePath, dependencies, true);
cache.saveData();
if (config.cacheOnMemory)
memoizedFiles.set(filePath, transformedCode);
ctx.set('ETag', getETag(filePath));
}
watcher.setDependencies(filePath, dependencies);
};
if (cache.cacheIDs[filePath]) {
const cacheID = cache.cacheIDs[filePath];
const cacheInfo = cache.cacheInfo[cacheID];
const cacheFilePath = cache.cacheFilePath(cacheID);
let hash;
try {
if (cacheInfo.plugins !== currentPlugins ||
await cache.hasDependenciesChanged(filePath) ||
((cacheInfo.mtime !== mtime) &&
(cacheInfo.hash !== (hash = await (0, md5_file_1.default)(filePath))))) {
const { code, map, dependencies, error } = await transformer.transformFile(filePath);
transformedCode = code;
if (map) {
const sourceMapPath = cache.sourceMapPath(cacheID);
transformedCode = fixSourceMap(transformedCode, sourceMapPath);
fs_1.default.writeFile(sourceMapPath, map, noop);
}
if (!error) {
fs_1.default.writeFileSync(cacheFilePath, transformedCode);
cacheInfo.hash = hash || await (0, md5_file_1.default)(filePath);
cacheInfo.mtime = mtime;
cacheInfo.plugins = currentPlugins;
await cache.updateDependencies(filePath, dependencies);
cache.saveData();
if (config.cacheOnMemory)
memoizedFiles.set(filePath, transformedCode);
ctx.set('ETag', getETag(filePath));
}
watcher.setDependencies(filePath, dependencies);
}
else {
if (ctx.get('If-None-Match') === getETag(filePath)) {
ctx.status = 304;
}
else {
transformedCode = config.cacheOnMemory && memoizedFiles.get(filePath) || fs_1.default.readFileSync(cacheFilePath, 'utf8');
if (cacheInfo.mtime !== mtime) {
cacheInfo.mtime = mtime;
cache.saveData();
}
if (config.cacheOnMemory)
memoizedFiles.set(filePath, transformedCode);
}
watcher.setDependencies(filePath, Object.keys(cacheInfo.dependencies || {}));
}
}
catch (e) {
if (e.message.includes('ENOENT')) {
await makeNewCache();
}
console.error(e);
}
}
else {
await makeNewCache();
}
}
else {
const message = `[reboost] The requested file does not exist: ${filePath}.`;
transformedCode = `console.error(${JSON.stringify(message)})`;
}
if (transformedCode) {
ctx.type = 'text/javascript';
ctx.body = transformedCode;
}
if (instance.isLogEnabled('responseTime')) {
const endTime = process.hrtime(startTime);
instance.log('responseTime', chalk_1.default.cyan(`Response time - ${(0, utils_1.toPosix)(path_1.default.relative(config.rootDir, filePath))}:`), (0, utils_1.getReadableHRTime)(endTime));
}
};
};
exports.createFileHandler = createFileHandler;