| 
									
										
										
										
											2023-02-16 14:11:36 +08:00
										 |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require("./helpers/warmup-webpack"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const path = require("path"); | 
					
						
							|  |  |  | const fs = require("graceful-fs"); | 
					
						
							|  |  |  | const rimraf = require("rimraf"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let fixtureCount = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe("Compiler (filesystem caching)", () => { | 
					
						
							|  |  |  | 	jest.setTimeout(5000); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const tempFixturePath = path.join( | 
					
						
							|  |  |  | 		__dirname, | 
					
						
							|  |  |  | 		"fixtures", | 
					
						
							|  |  |  | 		"temp-filesystem-cache-fixture" | 
					
						
							|  |  |  | 	); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function compile(entry, onSuccess, onError) { | 
					
						
							|  |  |  | 		const webpack = require(".."); | 
					
						
							|  |  |  | 		const options = webpack.config.getNormalizedWebpackOptions({}); | 
					
						
							|  |  |  | 		options.cache = { | 
					
						
							|  |  |  | 			type: "filesystem", | 
					
						
							|  |  |  | 			cacheDirectory: path.join(tempFixturePath, "cache") | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 		options.entry = entry; | 
					
						
							|  |  |  | 		options.context = path.join(__dirname, "fixtures"); | 
					
						
							|  |  |  | 		options.output.path = path.join(tempFixturePath, "dist"); | 
					
						
							|  |  |  | 		options.output.filename = "bundle.js"; | 
					
						
							|  |  |  | 		options.output.pathinfo = true; | 
					
						
							|  |  |  | 		options.module = { | 
					
						
							|  |  |  | 			rules: [ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					test: /\.svg$/, | 
					
						
							|  |  |  | 					type: "asset/resource", | 
					
						
							|  |  |  | 					use: { | 
					
						
							|  |  |  | 						loader: require.resolve("./fixtures/empty-svg-loader") | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			] | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 		const isBigIntSupported = typeof BigInt !== "undefined"; | 
					
						
							| 
									
										
										
										
											2023-05-30 22:33:47 +08:00
										 |  |  | 		const isErrorCaseSupported = | 
					
						
							|  |  |  | 			typeof new Error("test", { cause: new Error("cause") }).cause !== | 
					
						
							|  |  |  | 			"undefined"; | 
					
						
							| 
									
										
										
										
											2025-04-23 05:24:52 +08:00
										 |  |  | 		const isAggregateErrorSupported = typeof AggregateError !== "undefined"; | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 05:40:54 +08:00
										 |  |  | 		options.plugins = [ | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				apply(compiler) { | 
					
						
							|  |  |  | 					const name = "TestCachePlugin"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					compiler.hooks.thisCompilation.tap(name, compilation => { | 
					
						
							|  |  |  | 						compilation.hooks.processAssets.tapPromise( | 
					
						
							|  |  |  | 							{ | 
					
						
							|  |  |  | 								name, | 
					
						
							|  |  |  | 								stage: | 
					
						
							|  |  |  | 									compiler.webpack.Compilation | 
					
						
							|  |  |  | 										.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE | 
					
						
							|  |  |  | 							}, | 
					
						
							|  |  |  | 							async () => { | 
					
						
							|  |  |  | 								const cache = compilation.getCache(name); | 
					
						
							|  |  |  | 								const ident = "test.ext"; | 
					
						
							|  |  |  | 								const cacheItem = cache.getItemCache(ident, null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 								const result = await cacheItem.getPromise(ident); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 								if (result) { | 
					
						
							|  |  |  | 									expect(result.number).toEqual(42); | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 									expect(result.number1).toEqual(3.14); | 
					
						
							|  |  |  | 									expect(result.number2).toEqual(6.2); | 
					
						
							| 
									
										
										
										
											2023-05-30 05:40:54 +08:00
										 |  |  | 									expect(result.string).toEqual("string"); | 
					
						
							| 
									
										
										
										
											2023-05-30 22:33:47 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 									if (isErrorCaseSupported) { | 
					
						
							|  |  |  | 										expect(result.error.cause.message).toEqual("cause"); | 
					
						
							|  |  |  | 										expect(result.error1.cause.string).toBe("string"); | 
					
						
							|  |  |  | 										expect(result.error1.cause.number).toBe(42); | 
					
						
							|  |  |  | 									} | 
					
						
							| 
									
										
										
										
											2023-05-30 05:40:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-23 05:24:52 +08:00
										 |  |  | 									if (isAggregateErrorSupported) { | 
					
						
							|  |  |  | 										expect(result.aggregateError.errors).toEqual([ | 
					
						
							|  |  |  | 											new Error("first", { cause: "nested cause" }), | 
					
						
							|  |  |  | 											"second" | 
					
						
							|  |  |  | 										]); | 
					
						
							|  |  |  | 										expect(result.aggregateError.message).toEqual( | 
					
						
							|  |  |  | 											"aggregate error" | 
					
						
							|  |  |  | 										); | 
					
						
							|  |  |  | 										expect(result.aggregateError.cause.message).toBe("cause"); | 
					
						
							|  |  |  | 									} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 									if (isBigIntSupported) { | 
					
						
							| 
									
										
										
										
											2023-05-31 01:09:38 +08:00
										 |  |  | 										expect(result.bigint).toEqual(5n); | 
					
						
							|  |  |  | 										expect(result.bigint1).toEqual(124n); | 
					
						
							|  |  |  | 										expect(result.bigint2).toEqual(125n); | 
					
						
							|  |  |  | 										expect(result.bigint3).toEqual(12345678901234567890n); | 
					
						
							|  |  |  | 										expect(result.bigint4).toEqual(5n); | 
					
						
							|  |  |  | 										expect(result.bigint5).toEqual(1000000n); | 
					
						
							| 
									
										
										
										
											2023-05-31 03:50:19 +08:00
										 |  |  | 										expect(result.bigint6).toEqual(128n); | 
					
						
							|  |  |  | 										expect(result.bigint7).toEqual(2147483647n); | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 										expect(result.obj.foo).toBe(BigInt(-10)); | 
					
						
							|  |  |  | 										expect(Array.from(result.set)).toEqual([ | 
					
						
							|  |  |  | 											BigInt(1), | 
					
						
							|  |  |  | 											BigInt(2) | 
					
						
							|  |  |  | 										]); | 
					
						
							| 
									
										
										
										
											2023-05-31 01:09:38 +08:00
										 |  |  | 										expect(result.arr).toEqual([256n, 257n, 258n]); | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 									} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 05:40:54 +08:00
										 |  |  | 									return; | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 								const storeValue = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 								storeValue.number = 42; | 
					
						
							|  |  |  | 								storeValue.number1 = 3.14; | 
					
						
							|  |  |  | 								storeValue.number2 = 6.2; | 
					
						
							| 
									
										
										
										
											2023-05-30 22:33:47 +08:00
										 |  |  | 								storeValue.string = "string"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 								if (isErrorCaseSupported) { | 
					
						
							|  |  |  | 									storeValue.error = new Error("error", { | 
					
						
							|  |  |  | 										cause: new Error("cause") | 
					
						
							|  |  |  | 									}); | 
					
						
							|  |  |  | 									storeValue.error1 = new Error("error", { | 
					
						
							|  |  |  | 										cause: { string: "string", number: 42 } | 
					
						
							|  |  |  | 									}); | 
					
						
							|  |  |  | 								} | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-23 05:24:52 +08:00
										 |  |  | 								if (isAggregateErrorSupported) { | 
					
						
							|  |  |  | 									storeValue.aggregateError = new AggregateError( | 
					
						
							|  |  |  | 										[new Error("first", { cause: "nested cause" }), "second"], | 
					
						
							|  |  |  | 										"aggregate error", | 
					
						
							|  |  |  | 										{ cause: new Error("cause") } | 
					
						
							|  |  |  | 									); | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 								if (isBigIntSupported) { | 
					
						
							| 
									
										
										
										
											2023-05-31 01:09:38 +08:00
										 |  |  | 									storeValue.bigint = BigInt(5); | 
					
						
							|  |  |  | 									storeValue.bigint1 = BigInt(124); | 
					
						
							|  |  |  | 									storeValue.bigint2 = BigInt(125); | 
					
						
							|  |  |  | 									storeValue.bigint3 = 12345678901234567890n; | 
					
						
							|  |  |  | 									storeValue.bigint4 = 5n; | 
					
						
							|  |  |  | 									storeValue.bigint5 = 1000000n; | 
					
						
							| 
									
										
										
										
											2023-05-31 03:50:19 +08:00
										 |  |  | 									storeValue.bigint6 = 128n; | 
					
						
							|  |  |  | 									storeValue.bigint7 = 2147483647n; | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 									storeValue.obj = { foo: BigInt(-10) }; | 
					
						
							|  |  |  | 									storeValue.set = new Set([BigInt(1), BigInt(2)]); | 
					
						
							| 
									
										
										
										
											2023-05-31 01:09:38 +08:00
										 |  |  | 									storeValue.arr = [256n, 257n, 258n]; | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 								} | 
					
						
							| 
									
										
										
										
											2023-05-30 05:40:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 10:18:06 +08:00
										 |  |  | 								await cacheItem.storePromise(storeValue); | 
					
						
							| 
									
										
										
										
											2023-05-30 05:40:54 +08:00
										 |  |  | 							} | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-16 14:11:36 +08:00
										 |  |  | 		function runCompiler(onSuccess, onError) { | 
					
						
							|  |  |  | 			const c = webpack(options); | 
					
						
							|  |  |  | 			c.hooks.compilation.tap( | 
					
						
							|  |  |  | 				"CompilerCachingTest", | 
					
						
							|  |  |  | 				compilation => (compilation.bail = true) | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 			c.run((err, stats) => { | 
					
						
							|  |  |  | 				if (err) throw err; | 
					
						
							|  |  |  | 				expect(typeof stats).toBe("object"); | 
					
						
							|  |  |  | 				stats = stats.toJson({ | 
					
						
							|  |  |  | 					modules: true, | 
					
						
							|  |  |  | 					reasons: true | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 				expect(typeof stats).toBe("object"); | 
					
						
							|  |  |  | 				expect(stats).toHaveProperty("errors"); | 
					
						
							|  |  |  | 				expect(Array.isArray(stats.errors)).toBe(true); | 
					
						
							|  |  |  | 				if (stats.errors.length > 0) { | 
					
						
							|  |  |  | 					onError(new Error(JSON.stringify(stats.errors, null, 4))); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				c.close(() => { | 
					
						
							|  |  |  | 					onSuccess(stats); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		runCompiler(onSuccess, onError); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			runAgain: runCompiler | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-22 20:42:33 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2023-02-16 14:11:36 +08:00
										 |  |  | 	function cleanup() { | 
					
						
							|  |  |  | 		rimraf.sync(`${tempFixturePath}*`); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	beforeAll(cleanup); | 
					
						
							|  |  |  | 	afterAll(cleanup); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-22 20:42:33 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {{ rootPath: string, usesAssetFilepath: string, svgFilepath: string }} temp fixture paths | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2023-02-16 14:11:36 +08:00
										 |  |  | 	function createTempFixture() { | 
					
						
							|  |  |  | 		const fixturePath = `${tempFixturePath}-${fixtureCount}`; | 
					
						
							|  |  |  | 		const usesAssetFilepath = path.join(fixturePath, "uses-asset.js"); | 
					
						
							|  |  |  | 		const svgFilepath = path.join(fixturePath, "file.svg"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Remove previous copy if present
 | 
					
						
							|  |  |  | 		rimraf.sync(fixturePath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Copy over file since we"ll be modifying some of them
 | 
					
						
							|  |  |  | 		fs.mkdirSync(fixturePath); | 
					
						
							|  |  |  | 		fs.copyFileSync( | 
					
						
							|  |  |  | 			path.join(__dirname, "fixtures", "uses-asset.js"), | 
					
						
							|  |  |  | 			usesAssetFilepath | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		fs.copyFileSync(path.join(__dirname, "fixtures", "file.svg"), svgFilepath); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fixtureCount++; | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			rootPath: fixturePath, | 
					
						
							| 
									
										
										
										
											2025-04-22 19:09:25 +08:00
										 |  |  | 			usesAssetFilepath, | 
					
						
							|  |  |  | 			svgFilepath | 
					
						
							| 
									
										
										
										
											2023-02-16 14:11:36 +08:00
										 |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	it("should compile again when cached asset has changed but loader output remains the same", done => { | 
					
						
							|  |  |  | 		const tempFixture = createTempFixture(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const onError = error => done(error); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const helper = compile( | 
					
						
							|  |  |  | 			tempFixture.usesAssetFilepath, | 
					
						
							|  |  |  | 			stats => { | 
					
						
							|  |  |  | 				// Not cached the first time
 | 
					
						
							|  |  |  | 				expect(stats.assets[0].name).toBe("bundle.js"); | 
					
						
							|  |  |  | 				expect(stats.assets[0].emitted).toBe(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				expect(stats.assets[1].name).toMatch(/\w+\.svg$/); | 
					
						
							|  |  |  | 				expect(stats.assets[0].emitted).toBe(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				helper.runAgain(stats => { | 
					
						
							|  |  |  | 					// Cached the second run
 | 
					
						
							|  |  |  | 					expect(stats.assets[0].name).toBe("bundle.js"); | 
					
						
							|  |  |  | 					expect(stats.assets[0].emitted).toBe(false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					expect(stats.assets[1].name).toMatch(/\w+\.svg$/); | 
					
						
							|  |  |  | 					expect(stats.assets[0].emitted).toBe(false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					const svgContent = fs | 
					
						
							|  |  |  | 						.readFileSync(tempFixture.svgFilepath) | 
					
						
							|  |  |  | 						.toString() | 
					
						
							|  |  |  | 						.replace("icon-square-small", "icon-square-smaller"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					fs.writeFileSync(tempFixture.svgFilepath, svgContent); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					helper.runAgain(stats => { | 
					
						
							|  |  |  | 						// Still cached after file modification because loader always returns empty
 | 
					
						
							|  |  |  | 						expect(stats.assets[0].name).toBe("bundle.js"); | 
					
						
							|  |  |  | 						expect(stats.assets[0].emitted).toBe(false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						expect(stats.assets[1].name).toMatch(/\w+\.svg$/); | 
					
						
							|  |  |  | 						expect(stats.assets[0].emitted).toBe(false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						done(); | 
					
						
							|  |  |  | 					}, onError); | 
					
						
							|  |  |  | 				}, onError); | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			onError | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | }); |