server.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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.createServer = void 0;
  7. const path_1 = __importDefault(require("path"));
  8. const vite_1 = require("vite");
  9. const config_1 = require("./config");
  10. const markdownToVue_1 = require("./markdownToVue");
  11. const resolver_1 = require("./resolver");
  12. const fs_1 = require("fs");
  13. const debug = require('debug')('vitepress:serve');
  14. const debugHmr = require('debug')('vitepress:hmr');
  15. function createVitePressPlugin({ configPath, site: initialSiteData }) {
  16. return ({ app, root, watcher, resolver }) => {
  17. const markdownToVue = markdownToVue_1.createMarkdownToVueRenderFn(root);
  18. // hot reload .md files as .vue files
  19. watcher.on('change', async (file) => {
  20. if (file.endsWith('.md')) {
  21. debugHmr(`reloading ${file}`);
  22. const content = await vite_1.cachedRead(null, file);
  23. const timestamp = Date.now();
  24. const { pageData, vueSrc } = markdownToVue(content.toString(), file, timestamp,
  25. // do not inject pageData on HMR
  26. // it leads to vite to think <script> has changed and reloads the
  27. // component instead of re-rendering.
  28. // pageData needs separate HMR logic anyway (see below)
  29. false);
  30. // notify the client to update page data
  31. watcher.send({
  32. type: 'custom',
  33. id: 'vitepress:pageData',
  34. customData: {
  35. path: resolver.fileToRequest(file),
  36. pageData
  37. }
  38. });
  39. // reload the content component
  40. watcher.handleVueReload(file, timestamp, vueSrc);
  41. }
  42. });
  43. // hot reload handling for siteData
  44. // the data is stringified twice so it is sent to the client as a string
  45. // it is then parsed on the client via JSON.parse() which is faster than
  46. // parsing the object literal as JavaScript.
  47. let siteData = initialSiteData;
  48. let stringifiedData = JSON.stringify(JSON.stringify(initialSiteData));
  49. watcher.add(configPath);
  50. watcher.on('change', async (file) => {
  51. if (file === configPath) {
  52. const newData = await config_1.resolveSiteData(root);
  53. stringifiedData = JSON.stringify(JSON.stringify(newData));
  54. if (newData.base !== siteData.base) {
  55. console.warn(`[vitepress]: config.base has changed. Please restart the dev server.`);
  56. }
  57. siteData = newData;
  58. watcher.handleJSReload(resolver_1.SITE_DATA_REQUEST_PATH);
  59. }
  60. });
  61. // inject Koa middleware
  62. app.use(async (ctx, next) => {
  63. // serve siteData (which is a virtual file)
  64. if (ctx.path === resolver_1.SITE_DATA_REQUEST_PATH) {
  65. ctx.type = 'js';
  66. ctx.body = `export default ${stringifiedData}`;
  67. debug(ctx.url);
  68. return;
  69. }
  70. // handle .md -> vue transforms
  71. if (ctx.path.endsWith('.md')) {
  72. const file = resolver.requestToFile(ctx.path);
  73. if (!fs_1.existsSync(file)) {
  74. return next();
  75. }
  76. await vite_1.cachedRead(ctx, file);
  77. // let vite know this is supposed to be treated as vue file
  78. ctx.vue = true;
  79. const { vueSrc, pageData } = markdownToVue(ctx.body, file, ctx.lastModified.getTime(), false);
  80. ctx.body = vueSrc;
  81. debug(ctx.url, ctx.status);
  82. const pageDataWithLinks = {
  83. ...pageData,
  84. // TODO: this doesn't work with locales
  85. ...getNextAndPrev(siteData.themeConfig, ctx.path)
  86. };
  87. await next();
  88. // make sure this is the main <script> block
  89. if (!ctx.query.type) {
  90. // inject pageData to generated script
  91. ctx.body += `\nexport const __pageData = ${JSON.stringify(JSON.stringify(pageDataWithLinks))}`;
  92. }
  93. return;
  94. }
  95. await next();
  96. // serve our index.html after vite history fallback
  97. if (ctx.url.endsWith('.html')) {
  98. await vite_1.cachedRead(ctx, path_1.default.join(resolver_1.APP_PATH, 'index.html'));
  99. ctx.status = 200;
  100. }
  101. });
  102. };
  103. }
  104. function getNextAndPrev(themeConfig, pagePath) {
  105. if (!themeConfig.sidebar) {
  106. return;
  107. }
  108. const sidebar = themeConfig.sidebar;
  109. let candidates = [];
  110. Object.keys(sidebar).forEach((k) => {
  111. if (!pagePath.startsWith(k)) {
  112. return;
  113. }
  114. sidebar[k].forEach((sidebarItem) => {
  115. if (!sidebarItem.children) {
  116. return;
  117. }
  118. sidebarItem.children.forEach((candidate) => {
  119. candidates.push(candidate);
  120. });
  121. });
  122. });
  123. const path = pagePath.replace(/\.(md|html)$/, '');
  124. const currentLinkIndex = candidates.findIndex((v) => v.link === path);
  125. const nextAndPrev = {};
  126. if (themeConfig.nextLinks !== false &&
  127. currentLinkIndex > -1 &&
  128. currentLinkIndex < candidates.length - 1) {
  129. nextAndPrev.next = candidates[currentLinkIndex + 1];
  130. }
  131. if (themeConfig.prevLinks !== false && currentLinkIndex > 0) {
  132. nextAndPrev.next = candidates[currentLinkIndex - 1];
  133. }
  134. return nextAndPrev;
  135. }
  136. async function createServer(options = {}) {
  137. const config = await config_1.resolveConfig(options.root);
  138. return vite_1.createServer({
  139. ...options,
  140. configureServer: createVitePressPlugin(config),
  141. resolvers: [config.resolver]
  142. });
  143. }
  144. exports.createServer = createServer;
  145. //# sourceMappingURL=server.js.map