flight-client-entry-plugin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.injectedClientEntries = void 0;
  6. var _querystring = require("querystring");
  7. var _path = _interopRequireDefault(require("path"));
  8. var _webpack = require("next/dist/compiled/webpack/webpack");
  9. var _onDemandEntryHandler = require("../../../server/dev/on-demand-entry-handler");
  10. var _constants = require("../../../lib/constants");
  11. var _constants1 = require("../../../shared/lib/constants");
  12. var _utils = require("../loaders/utils");
  13. function _interopRequireDefault(obj) {
  14. return obj && obj.__esModule ? obj : {
  15. default: obj
  16. };
  17. }
  18. const PLUGIN_NAME = "ClientEntryPlugin";
  19. const injectedClientEntries = new Map();
  20. exports.injectedClientEntries = injectedClientEntries;
  21. // TODO-APP: ensure .scss / .sass also works.
  22. const regexCSS = /\.css$/;
  23. // TODO-APP: move CSS manifest generation to the flight manifest plugin.
  24. const flightCSSManifest = {};
  25. class FlightClientEntryPlugin {
  26. constructor(options){
  27. this.dev = options.dev;
  28. this.isEdgeServer = options.isEdgeServer;
  29. }
  30. apply(compiler) {
  31. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory })=>{
  32. compilation.dependencyFactories.set(_webpack.webpack.dependencies.ModuleDependency, normalModuleFactory);
  33. compilation.dependencyTemplates.set(_webpack.webpack.dependencies.ModuleDependency, new _webpack.webpack.dependencies.NullDependency.Template());
  34. });
  35. compiler.hooks.finishMake.tapPromise(PLUGIN_NAME, (compilation)=>{
  36. return this.createClientEndpoints(compiler, compilation);
  37. });
  38. }
  39. async createClientEndpoints(compiler, compilation) {
  40. const promises = [];
  41. // For each SC server compilation entry, we need to create its corresponding
  42. // client component entry.
  43. for (const [name, entry] of compilation.entries.entries()){
  44. var ref;
  45. // Check if the page entry is a server component or not.
  46. const entryDependency = (ref = entry.dependencies) == null ? void 0 : ref[0];
  47. // Ensure only next-app-loader entries are handled.
  48. if (!entryDependency || !entryDependency.request) continue;
  49. const request = entryDependency.request;
  50. if (!request.startsWith("next-edge-ssr-loader?") && !request.startsWith("next-app-loader?")) continue;
  51. let entryModule = compilation.moduleGraph.getResolvedModule(entryDependency);
  52. if (request.startsWith("next-edge-ssr-loader?")) {
  53. entryModule.dependencies.forEach((dependency)=>{
  54. const modRequest = dependency.request;
  55. if (modRequest == null ? void 0 : modRequest.includes("next-app-loader")) {
  56. entryModule = compilation.moduleGraph.getResolvedModule(dependency);
  57. }
  58. });
  59. }
  60. const internalClientComponentEntryImports = new Set();
  61. for (const connection of compilation.moduleGraph.getOutgoingConnections(entryModule)){
  62. const layoutOrPageDependency = connection.dependency;
  63. const layoutOrPageRequest = connection.dependency.request;
  64. const [clientComponentImports, cssImports] = this.collectClientComponentsAndCSSForDependency({
  65. layoutOrPageRequest,
  66. compilation,
  67. dependency: layoutOrPageDependency
  68. });
  69. Object.assign(flightCSSManifest, cssImports);
  70. const isAbsoluteRequest = layoutOrPageRequest[0] === "/";
  71. // Next.js internals are put into a separate entry.
  72. if (!isAbsoluteRequest) {
  73. clientComponentImports.forEach((value)=>internalClientComponentEntryImports.add(value));
  74. continue;
  75. }
  76. const relativeRequest = isAbsoluteRequest ? _path.default.relative(compilation.options.context, layoutOrPageRequest) : layoutOrPageRequest;
  77. // Replace file suffix as `.js` will be added.
  78. const bundlePath = relativeRequest.replace(/\.(js|ts)x?$/, "");
  79. promises.push(this.injectClientEntryAndSSRModules({
  80. compiler,
  81. compilation,
  82. entryName: name,
  83. clientComponentImports,
  84. bundlePath
  85. }));
  86. }
  87. // Create internal app
  88. promises.push(this.injectClientEntryAndSSRModules({
  89. compiler,
  90. compilation,
  91. entryName: name,
  92. clientComponentImports: [
  93. ...internalClientComponentEntryImports
  94. ],
  95. bundlePath: "app-internals"
  96. }));
  97. }
  98. compilation.hooks.processAssets.tap({
  99. name: PLUGIN_NAME,
  100. // Have to be in the optimize stage to run after updating the CSS
  101. // asset hash via extract mini css plugin.
  102. stage: _webpack.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH
  103. }, (assets)=>{
  104. const manifest = JSON.stringify(flightCSSManifest);
  105. assets[_constants1.FLIGHT_SERVER_CSS_MANIFEST + ".json"] = new _webpack.sources.RawSource(manifest);
  106. assets[_constants1.FLIGHT_SERVER_CSS_MANIFEST + ".js"] = new _webpack.sources.RawSource("self.__RSC_CSS_MANIFEST=" + manifest);
  107. });
  108. const res = await Promise.all(promises);
  109. // Invalidate in development to trigger recompilation
  110. const invalidator = (0, _onDemandEntryHandler).getInvalidator();
  111. // Check if any of the entry injections need an invalidation
  112. if (invalidator && res.includes(true)) {
  113. invalidator.invalidate([
  114. _constants1.COMPILER_NAMES.client
  115. ]);
  116. }
  117. }
  118. collectClientComponentsAndCSSForDependency({ layoutOrPageRequest , compilation , dependency }) {
  119. /**
  120. * Keep track of checked modules to avoid infinite loops with recursive imports.
  121. */ const visitedBySegment = {};
  122. const clientComponentImports = [];
  123. const serverCSSImports = {};
  124. const filterClientComponents = (dependencyToFilter, inClientComponentBoundary)=>{
  125. var ref;
  126. const mod = compilation.moduleGraph.getResolvedModule(dependencyToFilter);
  127. if (!mod) return;
  128. // Keep client imports as simple
  129. // native or installed js module: -> raw request, e.g. next/head
  130. // client js or css: -> user request
  131. const rawRequest = mod.rawRequest;
  132. // Request could be undefined or ''
  133. if (!rawRequest) return;
  134. const modRequest = !rawRequest.endsWith(".css") && !rawRequest.startsWith(".") && !rawRequest.startsWith("/") && !rawRequest.startsWith(_constants.APP_DIR_ALIAS) ? rawRequest : (ref = mod.resourceResolveData) == null ? void 0 : ref.path;
  135. // Ensure module is not walked again if it's already been visited
  136. if (!visitedBySegment[layoutOrPageRequest]) {
  137. visitedBySegment[layoutOrPageRequest] = new Set();
  138. }
  139. if (!modRequest || visitedBySegment[layoutOrPageRequest].has(modRequest)) {
  140. return;
  141. }
  142. visitedBySegment[layoutOrPageRequest].add(modRequest);
  143. const isCSS = regexCSS.test(modRequest);
  144. const isClientComponent = (0, _utils).isClientComponentModule(mod);
  145. if (isCSS) {
  146. serverCSSImports[layoutOrPageRequest] = serverCSSImports[layoutOrPageRequest] || [];
  147. serverCSSImports[layoutOrPageRequest].push(modRequest);
  148. }
  149. // Check if request is for css file.
  150. if (!inClientComponentBoundary && isClientComponent || isCSS) {
  151. clientComponentImports.push(modRequest);
  152. return;
  153. }
  154. compilation.moduleGraph.getOutgoingConnections(mod).forEach((connection)=>{
  155. filterClientComponents(connection.dependency, inClientComponentBoundary || isClientComponent);
  156. });
  157. };
  158. // Traverse the module graph to find all client components.
  159. filterClientComponents(dependency, false);
  160. return [
  161. clientComponentImports,
  162. serverCSSImports
  163. ];
  164. }
  165. async injectClientEntryAndSSRModules({ compiler , compilation , entryName , clientComponentImports , bundlePath }) {
  166. let shouldInvalidate = false;
  167. const loaderOptions = {
  168. modules: clientComponentImports,
  169. server: false
  170. };
  171. const clientLoader = `next-flight-client-entry-loader?${(0, _querystring).stringify(loaderOptions)}!`;
  172. const clientSSRLoader = `next-flight-client-entry-loader?${(0, _querystring).stringify({
  173. ...loaderOptions,
  174. server: true
  175. })}!`;
  176. // Add for the client compilation
  177. // Inject the entry to the client compiler.
  178. if (this.dev) {
  179. const pageKey = _constants1.COMPILER_NAMES.client + bundlePath;
  180. if (!_onDemandEntryHandler.entries[pageKey]) {
  181. _onDemandEntryHandler.entries[pageKey] = {
  182. type: _onDemandEntryHandler.EntryTypes.CHILD_ENTRY,
  183. parentEntries: new Set([
  184. entryName
  185. ]),
  186. bundlePath,
  187. request: clientLoader,
  188. dispose: false,
  189. lastActiveTime: Date.now()
  190. };
  191. shouldInvalidate = true;
  192. } else {
  193. const entryData = _onDemandEntryHandler.entries[pageKey];
  194. // New version of the client loader
  195. if (entryData.request !== clientLoader) {
  196. entryData.request = clientLoader;
  197. shouldInvalidate = true;
  198. }
  199. if (entryData.type === _onDemandEntryHandler.EntryTypes.CHILD_ENTRY) {
  200. entryData.parentEntries.add(entryName);
  201. }
  202. }
  203. } else {
  204. injectedClientEntries.set(bundlePath, clientLoader);
  205. }
  206. // Inject the entry to the server compiler (__sc_client__).
  207. const clientComponentEntryDep = _webpack.webpack.EntryPlugin.createDependency(clientSSRLoader, {
  208. name: bundlePath
  209. });
  210. // Add the dependency to the server compiler.
  211. await this.addEntry(compilation, // Reuse compilation context.
  212. compiler.context, clientComponentEntryDep, {
  213. // By using the same entry name
  214. name: entryName,
  215. // Layer should be undefined for the SSR modules
  216. // This ensures the client components are
  217. layer: undefined
  218. });
  219. return shouldInvalidate;
  220. }
  221. // TODO-APP: make sure dependsOn is added for layouts/pages
  222. addEntry(compilation, context, entry /* Dependency */ , options /* EntryOptions */ ) /* Promise<module> */ {
  223. return new Promise((resolve, reject)=>{
  224. compilation.entries.get(options.name).includeDependencies.push(entry);
  225. compilation.hooks.addEntry.call(entry, options);
  226. compilation.addModuleTree({
  227. context,
  228. dependency: entry,
  229. contextInfo: {
  230. issuerLayer: options.layer
  231. }
  232. }, (err, module)=>{
  233. if (err) {
  234. compilation.hooks.failedEntry.call(entry, options, err);
  235. return reject(err);
  236. }
  237. compilation.hooks.succeedEntry.call(entry, options, module);
  238. return resolve(module);
  239. });
  240. });
  241. }
  242. }
  243. exports.FlightClientEntryPlugin = FlightClientEntryPlugin;
  244. //# sourceMappingURL=flight-client-entry-plugin.js.map