fsUtils.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.watchFileIfOutOfRoot = exports.lookupFile = exports.readBody = exports.cachedRead = void 0;
  7. const path_1 = __importDefault(require("path"));
  8. const fs_extra_1 = __importDefault(require("fs-extra"));
  9. const lru_cache_1 = __importDefault(require("lru-cache"));
  10. const stream_1 = require("stream");
  11. const serverPluginServeStatic_1 = require("../server/serverPluginServeStatic");
  12. const mime_types_1 = __importDefault(require("mime-types"));
  13. const getETag = require('etag');
  14. const fsReadCache = new lru_cache_1.default({
  15. max: 10000
  16. });
  17. /**
  18. * Read a file with in-memory cache.
  19. * Also sets appropriate headers and body on the Koa context.
  20. * This is exposed on middleware context as `ctx.read` with the `ctx` already
  21. * bound, so it can be used as `ctx.read(file)`.
  22. */
  23. async function cachedRead(ctx, file) {
  24. const lastModified = fs_extra_1.default.statSync(file).mtimeMs;
  25. const cached = fsReadCache.get(file);
  26. if (ctx) {
  27. ctx.set('Cache-Control', 'no-cache');
  28. ctx.type = mime_types_1.default.lookup(path_1.default.extname(file)) || 'application/octet-stream';
  29. }
  30. if (cached && cached.lastModified === lastModified) {
  31. if (ctx) {
  32. // a private marker in case the user ticks "disable cache" during dev
  33. ctx.__notModified = true;
  34. ctx.etag = cached.etag;
  35. ctx.lastModified = new Date(cached.lastModified);
  36. if (ctx.get('If-None-Match') === ctx.etag && serverPluginServeStatic_1.seenUrls.has(ctx.url)) {
  37. ctx.status = 304;
  38. }
  39. serverPluginServeStatic_1.seenUrls.add(ctx.url);
  40. ctx.body = cached.content;
  41. }
  42. return cached.content;
  43. }
  44. // #395 some file is an binary file, eg. font
  45. let content = await fs_extra_1.default.readFile(file);
  46. // Populate the "sourcesContent" array and resolve relative paths in the
  47. // "sources" array, so the debugger can trace back to the original source.
  48. if (file.endsWith('.map')) {
  49. const map = JSON.parse(content.toString('utf8'));
  50. if (!map.sourcesContent || !map.sources.every(path_1.default.isAbsolute)) {
  51. const sourcesContent = map.sourcesContent || [];
  52. map.sources = await Promise.all(map.sources.map(async (source, i) => {
  53. const originalPath = path_1.default.resolve(path_1.default.dirname(file), source);
  54. if (!sourcesContent[i]) {
  55. const originalCode = await cachedRead(null, originalPath);
  56. sourcesContent[i] = originalCode.toString('utf8');
  57. }
  58. return originalPath;
  59. }));
  60. map.sourcesContent = sourcesContent;
  61. content = Buffer.from(JSON.stringify(map));
  62. }
  63. }
  64. const etag = getETag(content);
  65. fsReadCache.set(file, {
  66. content,
  67. etag,
  68. lastModified
  69. });
  70. if (ctx) {
  71. ctx.etag = etag;
  72. ctx.lastModified = new Date(lastModified);
  73. ctx.body = content;
  74. ctx.status = 200;
  75. // watch the file if it's out of root.
  76. const { root, watcher } = ctx;
  77. watchFileIfOutOfRoot(watcher, root, file);
  78. }
  79. return content;
  80. }
  81. exports.cachedRead = cachedRead;
  82. /**
  83. * Read already set body on a Koa context and normalize it into a string.
  84. * Useful in post-processing middlewares.
  85. */
  86. async function readBody(stream) {
  87. if (stream instanceof stream_1.Readable) {
  88. return new Promise((resolve, reject) => {
  89. let res = '';
  90. stream
  91. .on('data', (chunk) => (res += chunk))
  92. .on('error', reject)
  93. .on('end', () => {
  94. resolve(res);
  95. });
  96. });
  97. }
  98. else {
  99. return !stream || typeof stream === 'string' ? stream : stream.toString();
  100. }
  101. }
  102. exports.readBody = readBody;
  103. function lookupFile(dir, formats, pathOnly = false) {
  104. for (const format of formats) {
  105. const fullPath = path_1.default.join(dir, format);
  106. if (fs_extra_1.default.existsSync(fullPath) && fs_extra_1.default.statSync(fullPath).isFile()) {
  107. return pathOnly ? fullPath : fs_extra_1.default.readFileSync(fullPath, 'utf-8');
  108. }
  109. }
  110. const parentDir = path_1.default.dirname(dir);
  111. if (parentDir !== dir) {
  112. return lookupFile(parentDir, formats, pathOnly);
  113. }
  114. }
  115. exports.lookupFile = lookupFile;
  116. /**
  117. * Files under root are watched by default, but with user aliases we can still
  118. * serve files out of root. Add such files to the watcher (if not node_modules)
  119. */
  120. function watchFileIfOutOfRoot(watcher, root, file) {
  121. if (!file.startsWith(root) && !/node_modules/.test(file)) {
  122. watcher.add(file);
  123. }
  124. }
  125. exports.watchFileIfOutOfRoot = watchFileIfOutOfRoot;
  126. //# sourceMappingURL=fsUtils.js.map