| 
									
										
										
										
											2022-01-19 21:21:10 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const { WebpackError } = require(".."); | 
					
						
							|  |  |  | const { getUsedModuleIdsAndModules } = require("./IdHelpers"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {import("../Compiler")} Compiler */ | 
					
						
							|  |  |  | /** @typedef {import("../Module")} Module */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const plugin = "SyncModuleIdsPlugin"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SyncModuleIdsPlugin { | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {Object} options options | 
					
						
							|  |  |  | 	 * @param {string} options.path path to file | 
					
						
							|  |  |  | 	 * @param {string=} options.context context for module names | 
					
						
							|  |  |  | 	 * @param {function(Module): boolean} options.test selector for modules | 
					
						
							|  |  |  | 	 * @param {"read" | "create" | "merge" | "update"=} options.mode operation mode (defaults to merge) | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	constructor({ path, context, test, mode }) { | 
					
						
							|  |  |  | 		this._path = path; | 
					
						
							|  |  |  | 		this._context = context; | 
					
						
							|  |  |  | 		this._test = test || (() => true); | 
					
						
							|  |  |  | 		const readAndWrite = !mode || mode === "merge" || mode === "update"; | 
					
						
							|  |  |  | 		this._read = readAndWrite || mode === "read"; | 
					
						
							|  |  |  | 		this._write = readAndWrite || mode === "create"; | 
					
						
							|  |  |  | 		this._prune = mode === "update"; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * Apply the plugin | 
					
						
							|  |  |  | 	 * @param {Compiler} compiler the compiler instance | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	apply(compiler) { | 
					
						
							|  |  |  | 		/** @type {Map<string, string | number>} */ | 
					
						
							|  |  |  | 		let data; | 
					
						
							|  |  |  | 		let dataChanged = false; | 
					
						
							|  |  |  | 		if (this._read) { | 
					
						
							|  |  |  | 			compiler.hooks.readRecords.tapAsync(plugin, callback => { | 
					
						
							|  |  |  | 				const fs = compiler.intermediateFileSystem; | 
					
						
							|  |  |  | 				fs.readFile(this._path, (err, buffer) => { | 
					
						
							|  |  |  | 					if (err) { | 
					
						
							|  |  |  | 						if (err.code !== "ENOENT") { | 
					
						
							|  |  |  | 							return callback(err); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						return callback(); | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2023-05-27 01:21:35 +08:00
										 |  |  | 					const json = JSON.parse(/** @type {Buffer} */ (buffer).toString()); | 
					
						
							| 
									
										
										
										
											2022-01-19 21:21:10 +08:00
										 |  |  | 					data = new Map(); | 
					
						
							|  |  |  | 					for (const key of Object.keys(json)) { | 
					
						
							|  |  |  | 						data.set(key, json[key]); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					dataChanged = false; | 
					
						
							|  |  |  | 					return callback(); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (this._write) { | 
					
						
							|  |  |  | 			compiler.hooks.emitRecords.tapAsync(plugin, callback => { | 
					
						
							|  |  |  | 				if (!data || !dataChanged) return callback(); | 
					
						
							| 
									
										
										
										
											2023-06-13 01:24:59 +08:00
										 |  |  | 				/** @type {Object<string, string | number>} */ | 
					
						
							| 
									
										
										
										
											2022-01-19 21:21:10 +08:00
										 |  |  | 				const json = {}; | 
					
						
							|  |  |  | 				const sorted = Array.from(data).sort(([a], [b]) => (a < b ? -1 : 1)); | 
					
						
							|  |  |  | 				for (const [key, value] of sorted) { | 
					
						
							|  |  |  | 					json[key] = value; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				const fs = compiler.intermediateFileSystem; | 
					
						
							|  |  |  | 				fs.writeFile(this._path, JSON.stringify(json), callback); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		compiler.hooks.thisCompilation.tap(plugin, compilation => { | 
					
						
							|  |  |  | 			const associatedObjectForCache = compiler.root; | 
					
						
							|  |  |  | 			const context = this._context || compiler.context; | 
					
						
							|  |  |  | 			if (this._read) { | 
					
						
							|  |  |  | 				compilation.hooks.reviveModules.tap(plugin, (_1, _2) => { | 
					
						
							|  |  |  | 					if (!data) return; | 
					
						
							|  |  |  | 					const { chunkGraph } = compilation; | 
					
						
							|  |  |  | 					const [usedIds, modules] = getUsedModuleIdsAndModules( | 
					
						
							|  |  |  | 						compilation, | 
					
						
							|  |  |  | 						this._test | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 					for (const module of modules) { | 
					
						
							|  |  |  | 						const name = module.libIdent({ | 
					
						
							|  |  |  | 							context, | 
					
						
							|  |  |  | 							associatedObjectForCache | 
					
						
							|  |  |  | 						}); | 
					
						
							|  |  |  | 						if (!name) continue; | 
					
						
							|  |  |  | 						const id = data.get(name); | 
					
						
							|  |  |  | 						const idAsString = `${id}`; | 
					
						
							|  |  |  | 						if (usedIds.has(idAsString)) { | 
					
						
							|  |  |  | 							const err = new WebpackError( | 
					
						
							|  |  |  | 								`SyncModuleIdsPlugin: Unable to restore id '${id}' from '${this._path}' as it's already used.` | 
					
						
							|  |  |  | 							); | 
					
						
							|  |  |  | 							err.module = module; | 
					
						
							|  |  |  | 							compilation.errors.push(err); | 
					
						
							|  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2023-05-27 01:21:35 +08:00
										 |  |  | 						chunkGraph.setModuleId(module, /** @type {string | number} */ (id)); | 
					
						
							| 
									
										
										
										
											2022-01-19 21:21:10 +08:00
										 |  |  | 						usedIds.add(idAsString); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (this._write) { | 
					
						
							|  |  |  | 				compilation.hooks.recordModules.tap(plugin, modules => { | 
					
						
							|  |  |  | 					const { chunkGraph } = compilation; | 
					
						
							|  |  |  | 					let oldData = data; | 
					
						
							|  |  |  | 					if (!oldData) { | 
					
						
							|  |  |  | 						oldData = data = new Map(); | 
					
						
							|  |  |  | 					} else if (this._prune) { | 
					
						
							|  |  |  | 						data = new Map(); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					for (const module of modules) { | 
					
						
							|  |  |  | 						if (this._test(module)) { | 
					
						
							|  |  |  | 							const name = module.libIdent({ | 
					
						
							|  |  |  | 								context, | 
					
						
							|  |  |  | 								associatedObjectForCache | 
					
						
							|  |  |  | 							}); | 
					
						
							|  |  |  | 							if (!name) continue; | 
					
						
							|  |  |  | 							const id = chunkGraph.getModuleId(module); | 
					
						
							|  |  |  | 							if (id === null) continue; | 
					
						
							|  |  |  | 							const oldId = oldData.get(name); | 
					
						
							|  |  |  | 							if (oldId !== id) { | 
					
						
							|  |  |  | 								dataChanged = true; | 
					
						
							|  |  |  | 							} else if (data === oldData) { | 
					
						
							|  |  |  | 								continue; | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 							data.set(name, id); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if (data.size !== oldData.size) dataChanged = true; | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = SyncModuleIdsPlugin; |