post-process.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.postProcessHTML = void 0;
  6. var _constants = require("../shared/lib/constants");
  7. var _nonNullable = require("../lib/non-nullable");
  8. let optimizeAmp;
  9. let getFontDefinitionFromManifest;
  10. let parse;
  11. if (process.env.NEXT_RUNTIME !== "edge") {
  12. optimizeAmp = require("./optimize-amp").default;
  13. getFontDefinitionFromManifest = require("./font-utils").getFontDefinitionFromManifest;
  14. parse = require("next/dist/compiled/node-html-parser").parse;
  15. }
  16. const middlewareRegistry = [];
  17. function registerPostProcessor(name, middleware, condition) {
  18. middlewareRegistry.push({
  19. name,
  20. middleware,
  21. condition: condition || null
  22. });
  23. }
  24. async function processHTML(html, data, options) {
  25. // Don't parse unless there's at least one processor middleware
  26. if (!middlewareRegistry[0]) {
  27. return html;
  28. }
  29. const root = parse(html);
  30. let document = html;
  31. // Calls the middleware, with some instrumentation and logging
  32. async function callMiddleWare(middleware) {
  33. // let timer = Date.now()
  34. const inspectData = middleware.inspect(root, data);
  35. document = await middleware.mutate(document, inspectData, data);
  36. // timer = Date.now() - timer
  37. // if (timer > MIDDLEWARE_TIME_BUDGET) {
  38. // TODO: Identify a correct upper limit for the postprocess step
  39. // and add a warning to disable the optimization
  40. // }
  41. return;
  42. }
  43. for(let i = 0; i < middlewareRegistry.length; i++){
  44. let middleware = middlewareRegistry[i];
  45. if (!middleware.condition || middleware.condition(options)) {
  46. await callMiddleWare(middlewareRegistry[i].middleware);
  47. }
  48. }
  49. return document;
  50. }
  51. class FontOptimizerMiddleware {
  52. inspect(originalDom, options) {
  53. if (!options.getFontDefinition) {
  54. return;
  55. }
  56. const fontDefinitions = [];
  57. // collecting all the requested font definitions
  58. originalDom.querySelectorAll("link").filter((tag)=>tag.getAttribute("rel") === "stylesheet" && tag.hasAttribute("data-href") && _constants.OPTIMIZED_FONT_PROVIDERS.some(({ url })=>{
  59. const dataHref = tag.getAttribute("data-href");
  60. return dataHref ? dataHref.startsWith(url) : false;
  61. })).forEach((element)=>{
  62. const url = element.getAttribute("data-href");
  63. const nonce = element.getAttribute("nonce");
  64. if (url) {
  65. fontDefinitions.push([
  66. url,
  67. nonce
  68. ]);
  69. }
  70. });
  71. return fontDefinitions;
  72. }
  73. mutate = async (markup, fontDefinitions, options)=>{
  74. let result = markup;
  75. let preconnectUrls = new Set();
  76. if (!options.getFontDefinition) {
  77. return markup;
  78. }
  79. fontDefinitions.forEach((fontDef)=>{
  80. const [url, nonce] = fontDef;
  81. const fallBackLinkTag = `<link rel="stylesheet" href="${url}"/>`;
  82. if (result.indexOf(`<style data-href="${url}">`) > -1 || result.indexOf(fallBackLinkTag) > -1) {
  83. // The font is already optimized and probably the response is cached
  84. return;
  85. }
  86. const fontContent = options.getFontDefinition ? options.getFontDefinition(url) : null;
  87. if (!fontContent) {
  88. /**
  89. * In case of unreachable font definitions, fallback to default link tag.
  90. */ result = result.replace("</head>", `${fallBackLinkTag}</head>`);
  91. } else {
  92. const nonceStr = nonce ? ` nonce="${nonce}"` : "";
  93. result = result.replace("</head>", `<style data-href="${url}"${nonceStr}>${fontContent}</style></head>`);
  94. // Remove inert font tag
  95. const escapedUrl = url.replace(/&/g, "&amp;").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  96. const fontRegex = new RegExp(`<link[^>]*data-href="${escapedUrl}"[^>]*/>`);
  97. result = result.replace(fontRegex, "");
  98. const provider = _constants.OPTIMIZED_FONT_PROVIDERS.find((p)=>url.startsWith(p.url));
  99. if (provider) {
  100. preconnectUrls.add(provider.preconnect);
  101. }
  102. }
  103. });
  104. let preconnectTag = "";
  105. preconnectUrls.forEach((url)=>{
  106. preconnectTag += `<link rel="preconnect" href="${url}" crossorigin />`;
  107. });
  108. result = result.replace('<meta name="next-font-preconnect"/>', preconnectTag);
  109. return result;
  110. };
  111. }
  112. async function postProcessHTML(pathname, content, renderOpts, { inAmpMode , hybridAmp }) {
  113. const postProcessors = [
  114. process.env.NEXT_RUNTIME !== "edge" && inAmpMode ? async (html)=>{
  115. html = await optimizeAmp(html, renderOpts.ampOptimizerConfig);
  116. if (!renderOpts.ampSkipValidation && renderOpts.ampValidator) {
  117. await renderOpts.ampValidator(html, pathname);
  118. }
  119. return html;
  120. } : null,
  121. process.env.NEXT_RUNTIME !== "edge" && renderOpts.optimizeFonts ? async (html)=>{
  122. const getFontDefinition = (url)=>{
  123. if (renderOpts.fontManifest) {
  124. return getFontDefinitionFromManifest(url, renderOpts.fontManifest);
  125. }
  126. return "";
  127. };
  128. return await processHTML(html, {
  129. getFontDefinition
  130. }, {
  131. optimizeFonts: renderOpts.optimizeFonts
  132. });
  133. } : null,
  134. process.env.NEXT_RUNTIME !== "edge" && renderOpts.optimizeCss ? async (html)=>{
  135. // eslint-disable-next-line import/no-extraneous-dependencies
  136. const Critters = require("critters");
  137. const cssOptimizer = new Critters({
  138. ssrMode: true,
  139. reduceInlineStyles: false,
  140. path: renderOpts.distDir,
  141. publicPath: `${renderOpts.assetPrefix}/_next/`,
  142. preload: "media",
  143. fonts: false,
  144. ...renderOpts.optimizeCss
  145. });
  146. return await cssOptimizer.process(html);
  147. } : null,
  148. inAmpMode || hybridAmp ? async (html)=>{
  149. return html.replace(/&amp;amp=1/g, "&amp=1");
  150. } : null,
  151. ].filter(_nonNullable.nonNullable);
  152. for (const postProcessor of postProcessors){
  153. if (postProcessor) {
  154. content = await postProcessor(content);
  155. }
  156. }
  157. return content;
  158. }
  159. // Initialization
  160. registerPostProcessor("Inline-Fonts", new FontOptimizerMiddleware(), // Using process.env because passing Experimental flag through loader is not possible.
  161. // @ts-ignore
  162. (options)=>options.optimizeFonts || process.env.__NEXT_OPTIMIZE_FONTS);
  163. exports.postProcessHTML = postProcessHTML;
  164. //# sourceMappingURL=post-process.js.map