next-trace-entrypoints-plugin.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _path = _interopRequireDefault(require("path"));
  6. var _profilingPlugin = require("./profiling-plugin");
  7. var _isError = _interopRequireDefault(require("../../../lib/is-error"));
  8. var _nft = require("next/dist/compiled/@vercel/nft");
  9. var _constants = require("../../../shared/lib/constants");
  10. var _webpack = require("next/dist/compiled/webpack/webpack");
  11. var _webpackConfig = require("../../webpack-config");
  12. function _interopRequireDefault(obj) {
  13. return obj && obj.__esModule ? obj : {
  14. default: obj
  15. };
  16. }
  17. const PLUGIN_NAME = "TraceEntryPointsPlugin";
  18. const TRACE_IGNORES = [
  19. "**/*/next/dist/server/next.js",
  20. "**/*/next/dist/bin/next",
  21. ];
  22. function getModuleFromDependency(compilation, dep) {
  23. return compilation.moduleGraph.getModule(dep);
  24. }
  25. function getFilesMapFromReasons(fileList, reasons, ignoreFn) {
  26. // this uses the reasons tree to collect files specific to a
  27. // certain parent allowing us to not have to trace each parent
  28. // separately
  29. const parentFilesMap = new Map();
  30. function propagateToParents(parents, file, seen = new Set()) {
  31. for (const parent of parents || []){
  32. if (!seen.has(parent)) {
  33. seen.add(parent);
  34. let parentFiles = parentFilesMap.get(parent);
  35. if (!parentFiles) {
  36. parentFiles = new Set();
  37. parentFilesMap.set(parent, parentFiles);
  38. }
  39. if (!(ignoreFn == null ? void 0 : ignoreFn(file, parent))) {
  40. parentFiles.add(file);
  41. }
  42. const parentReason = reasons.get(parent);
  43. if (parentReason == null ? void 0 : parentReason.parents) {
  44. propagateToParents(parentReason.parents, file, seen);
  45. }
  46. }
  47. }
  48. }
  49. for (const file1 of fileList){
  50. const reason = reasons.get(file1);
  51. const isInitial = (reason == null ? void 0 : reason.type.length) === 1 && reason.type.includes("initial");
  52. if (!reason || !reason.parents || isInitial && reason.parents.size === 0) {
  53. continue;
  54. }
  55. propagateToParents(reason.parents, file1);
  56. }
  57. return parentFilesMap;
  58. }
  59. class TraceEntryPointsPlugin {
  60. constructor({ appDir , excludeFiles , esmExternals , staticImageImports , outputFileTracingRoot }){
  61. this.appDir = appDir;
  62. this.entryTraces = new Map();
  63. this.esmExternals = esmExternals;
  64. this.excludeFiles = excludeFiles || [];
  65. this.staticImageImports = staticImageImports;
  66. this.tracingRoot = outputFileTracingRoot || appDir;
  67. }
  68. // Here we output all traced assets and webpack chunks to a
  69. // ${page}.js.nft.json file
  70. async createTraceAssets(compilation, assets, span, readlink, stat) {
  71. const outputPath = compilation.outputOptions.path;
  72. await span.traceChild("create-trace-assets").traceAsyncFn(async ()=>{
  73. const entryFilesMap = new Map();
  74. const chunksToTrace = new Set();
  75. const isTraceable = (file)=>!file.endsWith(".wasm");
  76. for (const entrypoint of compilation.entrypoints.values()){
  77. const entryFiles = new Set();
  78. for (const chunk of entrypoint.getEntrypointChunk().getAllReferencedChunks()){
  79. for (const file of chunk.files){
  80. if (isTraceable(file)) {
  81. const filePath = _path.default.join(outputPath, file);
  82. chunksToTrace.add(filePath);
  83. entryFiles.add(filePath);
  84. }
  85. }
  86. for (const file2 of chunk.auxiliaryFiles){
  87. if (isTraceable(file2)) {
  88. const filePath = _path.default.join(outputPath, file2);
  89. chunksToTrace.add(filePath);
  90. entryFiles.add(filePath);
  91. }
  92. }
  93. }
  94. entryFilesMap.set(entrypoint, entryFiles);
  95. }
  96. const result = await (0, _nft).nodeFileTrace([
  97. ...chunksToTrace
  98. ], {
  99. base: this.tracingRoot,
  100. processCwd: this.appDir,
  101. readFile: async (path)=>{
  102. if (chunksToTrace.has(path)) {
  103. var ref;
  104. const source = (ref = assets[_path.default.relative(outputPath, path).replace(/\\/g, "/")]) == null ? void 0 : ref.source == null ? void 0 : ref.source();
  105. if (source) return source;
  106. }
  107. try {
  108. return await new Promise((resolve, reject)=>{
  109. compilation.inputFileSystem.readFile(path, (err, data)=>{
  110. if (err) return reject(err);
  111. resolve(data);
  112. });
  113. });
  114. } catch (e) {
  115. if ((0, _isError).default(e) && (e.code === "ENOENT" || e.code === "EISDIR")) {
  116. return null;
  117. }
  118. throw e;
  119. }
  120. },
  121. readlink,
  122. stat,
  123. ignore: [
  124. ...TRACE_IGNORES,
  125. ...this.excludeFiles
  126. ],
  127. mixedModules: true
  128. });
  129. const reasons = result.reasons;
  130. const fileList = result.fileList;
  131. result.esmFileList.forEach((file)=>fileList.add(file));
  132. const parentFilesMap = getFilesMapFromReasons(fileList, reasons);
  133. for (const [entrypoint1, entryFiles] of entryFilesMap){
  134. const traceOutputName = `../${entrypoint1.name}.js.nft.json`;
  135. const traceOutputPath = _path.default.dirname(_path.default.join(outputPath, traceOutputName));
  136. const allEntryFiles = new Set();
  137. entryFiles.forEach((file)=>{
  138. var ref;
  139. (ref = parentFilesMap.get(_path.default.relative(this.tracingRoot, file))) == null ? void 0 : ref.forEach((child)=>{
  140. allEntryFiles.add(_path.default.join(this.tracingRoot, child));
  141. });
  142. });
  143. // don't include the entry itself in the trace
  144. entryFiles.delete(_path.default.join(outputPath, `../${entrypoint1.name}.js`));
  145. assets[traceOutputName] = new _webpack.sources.RawSource(JSON.stringify({
  146. version: _constants.TRACE_OUTPUT_VERSION,
  147. files: [
  148. ...new Set([
  149. ...entryFiles,
  150. ...allEntryFiles,
  151. ...this.entryTraces.get(entrypoint1.name) || [],
  152. ]),
  153. ].map((file)=>{
  154. return _path.default.relative(traceOutputPath, file).replace(/\\/g, "/");
  155. })
  156. }));
  157. }
  158. });
  159. }
  160. tapfinishModules(compilation, traceEntrypointsPluginSpan, doResolve, readlink, stat) {
  161. compilation.hooks.finishModules.tapAsync(PLUGIN_NAME, async (_stats, callback)=>{
  162. const finishModulesSpan = traceEntrypointsPluginSpan.traceChild("finish-modules");
  163. await finishModulesSpan.traceAsyncFn(async ()=>{
  164. // we create entry -> module maps so that we can
  165. // look them up faster instead of having to iterate
  166. // over the compilation modules list
  167. const entryNameMap = new Map();
  168. const entryModMap = new Map();
  169. const additionalEntries = new Map();
  170. const depModMap = new Map();
  171. finishModulesSpan.traceChild("get-entries").traceFn(()=>{
  172. compilation.entries.forEach((entry, name)=>{
  173. if (name == null ? void 0 : name.replace(/\\/g, "/").startsWith("pages/")) {
  174. for (const dep of entry.dependencies){
  175. if (!dep) continue;
  176. const entryMod = getModuleFromDependency(compilation, dep);
  177. if (entryMod && entryMod.resource) {
  178. if (entryMod.resource.replace(/\\/g, "/").includes("pages/")) {
  179. entryNameMap.set(entryMod.resource, name);
  180. entryModMap.set(entryMod.resource, entryMod);
  181. } else {
  182. let curMap = additionalEntries.get(name);
  183. if (!curMap) {
  184. curMap = new Map();
  185. additionalEntries.set(name, curMap);
  186. }
  187. depModMap.set(entryMod.resource, entryMod);
  188. curMap.set(entryMod.resource, entryMod);
  189. }
  190. }
  191. }
  192. }
  193. });
  194. });
  195. const readFile = async (path)=>{
  196. const mod = depModMap.get(path) || entryModMap.get(path);
  197. // map the transpiled source when available to avoid
  198. // parse errors in node-file-trace
  199. const source = mod == null ? void 0 : mod.originalSource == null ? void 0 : mod.originalSource();
  200. if (source) {
  201. return source.buffer();
  202. }
  203. // we don't want to analyze non-transpiled
  204. // files here, that is done against webpack output
  205. return "";
  206. };
  207. const entryPaths = Array.from(entryModMap.keys());
  208. const collectDependencies = (mod)=>{
  209. if (!mod || !mod.dependencies) return;
  210. for (const dep of mod.dependencies){
  211. const depMod = getModuleFromDependency(compilation, dep);
  212. if ((depMod == null ? void 0 : depMod.resource) && !depModMap.get(depMod.resource)) {
  213. depModMap.set(depMod.resource, depMod);
  214. collectDependencies(depMod);
  215. }
  216. }
  217. };
  218. const entriesToTrace = [
  219. ...entryPaths
  220. ];
  221. entryPaths.forEach((entry)=>{
  222. collectDependencies(entryModMap.get(entry));
  223. const entryName = entryNameMap.get(entry);
  224. const curExtraEntries = additionalEntries.get(entryName);
  225. if (curExtraEntries) {
  226. entriesToTrace.push(...curExtraEntries.keys());
  227. }
  228. });
  229. let fileList;
  230. let reasons;
  231. await finishModulesSpan.traceChild("node-file-trace", {
  232. traceEntryCount: entriesToTrace.length + ""
  233. }).traceAsyncFn(async ()=>{
  234. const result = await (0, _nft).nodeFileTrace(entriesToTrace, {
  235. base: this.tracingRoot,
  236. processCwd: this.appDir,
  237. readFile,
  238. readlink,
  239. stat,
  240. resolve: doResolve ? async (id, parent, job, isCjs)=>{
  241. return doResolve(id, parent, job, !isCjs);
  242. } : undefined,
  243. ignore: [
  244. ...TRACE_IGNORES,
  245. ...this.excludeFiles,
  246. "**/node_modules/**",
  247. ],
  248. mixedModules: true
  249. });
  250. // @ts-ignore
  251. fileList = result.fileList;
  252. result.esmFileList.forEach((file)=>fileList.add(file));
  253. reasons = result.reasons;
  254. });
  255. await finishModulesSpan.traceChild("collect-traced-files").traceAsyncFn(()=>{
  256. const parentFilesMap = getFilesMapFromReasons(fileList, reasons, (file)=>{
  257. var ref;
  258. // if a file was imported and a loader handled it
  259. // we don't include it in the trace e.g.
  260. // static image imports, CSS imports
  261. file = _path.default.join(this.tracingRoot, file);
  262. const depMod = depModMap.get(file);
  263. const isAsset = (ref = reasons.get(_path.default.relative(this.tracingRoot, file))) == null ? void 0 : ref.type.includes("asset");
  264. return !isAsset && Array.isArray(depMod == null ? void 0 : depMod.loaders) && depMod.loaders.length > 0;
  265. });
  266. entryPaths.forEach((entry)=>{
  267. var ref;
  268. const entryName = entryNameMap.get(entry);
  269. const normalizedEntry = _path.default.relative(this.tracingRoot, entry);
  270. const curExtraEntries = additionalEntries.get(entryName);
  271. const finalDeps = new Set();
  272. (ref = parentFilesMap.get(normalizedEntry)) == null ? void 0 : ref.forEach((dep)=>{
  273. finalDeps.add(_path.default.join(this.tracingRoot, dep));
  274. });
  275. if (curExtraEntries) {
  276. for (const extraEntry of curExtraEntries.keys()){
  277. var ref1;
  278. const normalizedExtraEntry = _path.default.relative(this.tracingRoot, extraEntry);
  279. finalDeps.add(extraEntry);
  280. (ref1 = parentFilesMap.get(normalizedExtraEntry)) == null ? void 0 : ref1.forEach((dep)=>{
  281. finalDeps.add(_path.default.join(this.tracingRoot, dep));
  282. });
  283. }
  284. }
  285. this.entryTraces.set(entryName, finalDeps);
  286. });
  287. });
  288. }).then(()=>callback(), (err)=>callback(err));
  289. });
  290. }
  291. apply(compiler) {
  292. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation)=>{
  293. const readlink = async (path)=>{
  294. try {
  295. return await new Promise((resolve, reject)=>{
  296. compilation.inputFileSystem.readlink(path, (err, link)=>{
  297. if (err) return reject(err);
  298. resolve(link);
  299. });
  300. });
  301. } catch (e) {
  302. if ((0, _isError).default(e) && (e.code === "EINVAL" || e.code === "ENOENT" || e.code === "UNKNOWN")) {
  303. return null;
  304. }
  305. throw e;
  306. }
  307. };
  308. const stat = async (path)=>{
  309. try {
  310. return await new Promise((resolve, reject)=>{
  311. compilation.inputFileSystem.stat(path, (err, stats)=>{
  312. if (err) return reject(err);
  313. resolve(stats);
  314. });
  315. });
  316. } catch (e) {
  317. if ((0, _isError).default(e) && (e.code === "ENOENT" || e.code === "ENOTDIR")) {
  318. return null;
  319. }
  320. throw e;
  321. }
  322. };
  323. const compilationSpan = _profilingPlugin.spans.get(compilation) || _profilingPlugin.spans.get(compiler);
  324. const traceEntrypointsPluginSpan = compilationSpan.traceChild("next-trace-entrypoint-plugin");
  325. traceEntrypointsPluginSpan.traceFn(()=>{
  326. // @ts-ignore TODO: Remove ignore when webpack 5 is stable
  327. compilation.hooks.processAssets.tapAsync({
  328. name: PLUGIN_NAME,
  329. // @ts-ignore TODO: Remove ignore when webpack 5 is stable
  330. stage: _webpack.webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
  331. }, (assets, callback)=>{
  332. this.createTraceAssets(compilation, assets, traceEntrypointsPluginSpan, readlink, stat).then(()=>callback()).catch((err)=>callback(err));
  333. });
  334. let resolver = compilation.resolverFactory.get("normal");
  335. function getPkgName(name) {
  336. const segments = name.split("/");
  337. if (name[0] === "@" && segments.length > 1) return segments.length > 1 ? segments.slice(0, 2).join("/") : null;
  338. return segments.length ? segments[0] : null;
  339. }
  340. const getResolve = (options)=>{
  341. const curResolver = resolver.withOptions(options);
  342. return (parent, request, job)=>{
  343. return new Promise((resolve, reject)=>{
  344. const context = _path.default.dirname(parent);
  345. curResolver.resolve({}, context, request, {
  346. fileDependencies: compilation.fileDependencies,
  347. missingDependencies: compilation.missingDependencies,
  348. contextDependencies: compilation.contextDependencies
  349. }, async (err, result, resContext)=>{
  350. if (err) return reject(err);
  351. if (!result) {
  352. return reject(new Error("module not found"));
  353. }
  354. // webpack resolver doesn't strip loader query info
  355. // from the result so use path instead
  356. if (result.includes("?") || result.includes("!")) {
  357. result = (resContext == null ? void 0 : resContext.path) || result;
  358. }
  359. try {
  360. // we need to collect all parent package.json's used
  361. // as webpack's resolve doesn't expose this and parent
  362. // package.json could be needed for resolving e.g. stylis
  363. // stylis/package.json -> stylis/dist/umd/package.json
  364. if (result.includes("node_modules")) {
  365. let requestPath = result.replace(/\\/g, "/").replace(/\0/g, "");
  366. if (!_path.default.isAbsolute(request) && request.includes("/") && (resContext == null ? void 0 : resContext.descriptionFileRoot)) {
  367. var ref;
  368. requestPath = (resContext.descriptionFileRoot + request.slice(((ref = getPkgName(request)) == null ? void 0 : ref.length) || 0) + _path.default.sep + "package.json").replace(/\\/g, "/").replace(/\0/g, "");
  369. }
  370. const rootSeparatorIndex = requestPath.indexOf("/");
  371. let separatorIndex;
  372. while((separatorIndex = requestPath.lastIndexOf("/")) > rootSeparatorIndex){
  373. requestPath = requestPath.slice(0, separatorIndex);
  374. const curPackageJsonPath = `${requestPath}/package.json`;
  375. if (await job.isFile(curPackageJsonPath)) {
  376. await job.emitFile(await job.realpath(curPackageJsonPath), "resolve", parent);
  377. }
  378. }
  379. }
  380. } catch (_err) {
  381. // we failed to resolve the package.json boundary,
  382. // we don't block emitting the initial asset from this
  383. }
  384. resolve([
  385. result,
  386. options.dependencyType === "esm"
  387. ]);
  388. });
  389. });
  390. };
  391. };
  392. const CJS_RESOLVE_OPTIONS = {
  393. ..._webpackConfig.NODE_RESOLVE_OPTIONS,
  394. fullySpecified: undefined,
  395. modules: undefined,
  396. extensions: undefined
  397. };
  398. const BASE_CJS_RESOLVE_OPTIONS = {
  399. ...CJS_RESOLVE_OPTIONS,
  400. alias: false
  401. };
  402. const ESM_RESOLVE_OPTIONS = {
  403. ..._webpackConfig.NODE_ESM_RESOLVE_OPTIONS,
  404. fullySpecified: undefined,
  405. modules: undefined,
  406. extensions: undefined
  407. };
  408. const BASE_ESM_RESOLVE_OPTIONS = {
  409. ...ESM_RESOLVE_OPTIONS,
  410. alias: false
  411. };
  412. const doResolve = async (request, parent, job, isEsmRequested)=>{
  413. const context = _path.default.dirname(parent);
  414. // When in esm externals mode, and using import, we resolve with
  415. // ESM resolving options.
  416. const { res } = await (0, _webpackConfig).resolveExternal(this.appDir, this.esmExternals, context, request, isEsmRequested, (options)=>(_, resRequest)=>{
  417. return getResolve(options)(parent, resRequest, job);
  418. }, undefined, undefined, ESM_RESOLVE_OPTIONS, CJS_RESOLVE_OPTIONS, BASE_ESM_RESOLVE_OPTIONS, BASE_CJS_RESOLVE_OPTIONS);
  419. if (!res) {
  420. throw new Error(`failed to resolve ${request} from ${parent}`);
  421. }
  422. return res.replace(/\0/g, "");
  423. };
  424. this.tapfinishModules(compilation, traceEntrypointsPluginSpan, doResolve, readlink, stat);
  425. });
  426. });
  427. }
  428. }
  429. exports.TraceEntryPointsPlugin = TraceEntryPointsPlugin;
  430. //# sourceMappingURL=next-trace-entrypoints-plugin.js.map