/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
test/Compiler-filesystem-caching.test.js
432 строки
12 KB
Alexander Akait
fix(css): name inlined CSS source map sources and honor hidden devtools (#21558)
30 июл 2026, 16:44
Не верифицирован
30 июл 2026, 16:44
029fd0f
Код
Авторство
О чём код?
"use strict"; require("./helpers/warmup-webpack"); const path = require("path"); const fs = require("graceful-fs"); const rimraf = /** @type {{ sync: (path: string) => void }} */ ( require("rimraf") ); const expectNoDeprecations = require("./helpers/expectNoDeprecations"); let fixtureCount = 0; describe("Compiler (filesystem caching)", () => { expectNoDeprecations(); const tempFixturePath = path.join( __dirname, "fixtures", "temp-filesystem-cache-fixture" ); function compile( /** @type {string} */ entry, /** @type {(stats: import("../types").StatsCompilation) => void} */ onSuccess, /** @type {(err: Error) => void} */ onError ) { const webpack = require(".."); const options = webpack.config.getNormalizedWebpackOptions({}); options.cache = { type: "filesystem", cacheDirectory: path.join(tempFixturePath, "cache") }; options.entry = /** @type {import("../types").EntryNormalized} */ ( /** @type {unknown} */ (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 = /** @type {import("../types").WebpackOptionsNormalized["module"]} */ ( /** @type {unknown} */ ({ rules: [ { test: /\.svg$/, type: "asset/resource", use: { loader: require.resolve("./fixtures/empty-svg-loader") } } ] }) ); const isBigIntSupported = typeof BigInt !== "undefined"; const isErrorCaseSupported = typeof new Error("test", { cause: new Error("cause") }).cause !== "undefined"; const isAggregateErrorSupported = typeof AggregateError !== "undefined"; 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 cacheItem = /** @type {{ getPromise(id: string): Promise<unknown>, storePromise(v: unknown): Promise<void> }} */ ( /** @type {unknown} */ (cacheItem_) ); const result_ = await cacheItem.getPromise(ident); const result = /** @type {Record<string, Record<string, Record<string, unknown>> & Iterable<unknown>>} */ ( result_ ); if (result) { expect(result.number).toBe(42); expect(result.number1).toBe(3.14); expect(result.number2).toBe(6.2); expect(result.string).toBe("string"); if (isErrorCaseSupported) { expect(result.error.cause.message).toBe("cause"); expect(result.error1.cause.string).toBe("string"); expect(result.error1.cause.number).toBe(42); } if (isAggregateErrorSupported) { expect(result.aggregateError.errors).toEqual([ new Error("first", { cause: "nested cause" }), "second" ]); expect(result.aggregateError.message).toBe( "aggregate error" ); expect(result.aggregateError.cause.message).toBe("cause"); } if (isBigIntSupported) { expect(result.bigint).toBe(BigInt(5)); expect(result.bigint1).toBe(BigInt(124)); expect(result.bigint2).toBe(BigInt(125)); expect(result.bigint3).toBe(BigInt("12345678901234567890")); expect(result.bigint4).toBe(BigInt(5)); expect(result.bigint5).toBe(BigInt(1000000)); expect(result.bigint6).toBe(BigInt(128)); expect(result.bigint7).toBe(BigInt(2147483647)); expect(result.obj.foo).toBe(BigInt(-10)); expect([...result.set]).toEqual([BigInt(1), BigInt(2)]); expect(result.arr).toEqual([ BigInt(256), BigInt(257), BigInt(258) ]); } return; } const storeValue = {}; storeValue.number = 42; storeValue.number1 = 3.14; storeValue.number2 = 6.2; storeValue.string = "string"; if (isErrorCaseSupported) { storeValue.error = new Error("error", { cause: new Error("cause") }); storeValue.error1 = new Error("error", { cause: { string: "string", number: 42 } }); } if (isAggregateErrorSupported) { storeValue.aggregateError = new AggregateError( [new Error("first", { cause: "nested cause" }), "second"], "aggregate error", { cause: new Error("cause") } ); } if (isBigIntSupported) { storeValue.bigint = BigInt(5); storeValue.bigint1 = BigInt(124); storeValue.bigint2 = BigInt(125); storeValue.bigint3 = BigInt("12345678901234567890"); storeValue.bigint4 = BigInt(5); storeValue.bigint5 = BigInt(1000000); storeValue.bigint6 = BigInt(128); storeValue.bigint7 = BigInt(2147483647); storeValue.obj = { foo: BigInt(-10) }; storeValue.set = new Set([BigInt(1), BigInt(2)]); storeValue.arr = [BigInt(256), BigInt(257), BigInt(258)]; } await cacheItem.storePromise(storeValue); } ); }); } } ]; function runCompiler( /** @type {(stats: import("../types").StatsCompilation) => void} */ onSuccess, /** @type {(err: Error) => void} */ onError ) { const c = webpack( /** @type {import("../types").Configuration} */ ( /** @type {unknown} */ (options) ) ); c.hooks.compilation.tap( "CompilerCachingTest", (compilation) => (compilation.bail = true) ); c.run((err, stats_) => { if (err) throw err; expect(typeof stats_).toBe("object"); const stats = /** @type {import("../types").Stats} */ (stats_).toJson({ modules: true, reasons: true }); expect(typeof stats).toBe("object"); expect(stats).toHaveProperty("errors"); expect(Array.isArray(stats.errors)).toBe(true); if (/** @type {unknown[]} */ (stats.errors).length > 0) { onError(new Error(JSON.stringify(stats.errors, null, 4))); } c.close(() => { onSuccess(stats); }); }); } runCompiler(onSuccess, onError); return { runAgain: runCompiler }; } /** * @returns {void} */ function cleanup() { rimraf.sync(`${tempFixturePath}*`); } beforeAll(cleanup); afterAll(cleanup); /** * @returns {{ rootPath: string, usesAssetFilepath: string, svgFilepath: string }} temp fixture paths */ 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, usesAssetFilepath, svgFilepath }; } it("should compile again when cached asset has changed but loader output remains the same", (done) => { const tempFixture = createTempFixture(); const onError = (/** @type {Error} */ error) => done(error); const helper = compile( tempFixture.usesAssetFilepath, (stats) => { const assets = /** @type {import("../types").StatsAsset[]} */ ( stats.assets ); // Not cached the first time expect(assets[0].name).toBe("bundle.js"); expect(assets[0].emitted).toBe(true); expect(assets[1].name).toMatch(/\w+\.svg$/); expect(assets[0].emitted).toBe(true); helper.runAgain((stats) => { const assets = /** @type {import("../types").StatsAsset[]} */ ( stats.assets ); // Cached the second run expect(assets[0].name).toBe("bundle.js"); expect(assets[0].emitted).toBe(false); expect(assets[1].name).toMatch(/\w+\.svg$/); expect(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) => { const assets = /** @type {import("../types").StatsAsset[]} */ ( stats.assets ); // Still cached after file modification because loader always returns empty expect(assets[0].name).toBe("bundle.js"); expect(assets[0].emitted).toBe(false); expect(assets[1].name).toMatch(/\w+\.svg$/); expect(assets[0].emitted).toBe(false); done(); }, onError); }, onError); }, onError ); }); }); describe("Compiler (filesystem caching, css devtool)", () => { expectNoDeprecations(); const cachePath = path.join( __dirname, "fixtures", "temp-css-devtool-cache-fixture" ); beforeEach(() => { rimraf.sync(cachePath); }); afterEach(() => { rimraf.sync(cachePath); }); /** * @param {import("../declarations/WebpackOptions").DevTool} devtool devtool to build with * @param {string=} devtoolModuleFilenameTemplate template to build with * @returns {Promise<string>} the emitted bundle */ function build(devtool, devtoolModuleFilenameTemplate) { const webpack = require(".."); return new Promise((resolve, reject) => { const compiler = webpack({ context: path.join(__dirname, "fixtures", "css-devtool-cache"), entry: "./index.js", mode: "development", devtool, target: "web", experiments: { css: true }, cache: { type: "filesystem", cacheDirectory: cachePath }, module: { rules: [ { test: /\.css$/, type: "css/auto", parser: { exportType: "text" } } ] }, output: { path: path.join(cachePath, "dist"), filename: "bundle.js", devtoolModuleFilenameTemplate } }); compiler.run((err, stats) => { if (err) return reject(err); if (/** @type {import("../types").Stats} */ (stats).hasErrors()) { return reject( new Error( /** @type {import("../types").Stats} */ (stats).toString() ) ); } compiler.close(() => { resolve( fs .readFileSync(path.join(cachePath, "dist", "bundle.js")) .toString() ); }); }); }); } const INLINE_MAP_REGEXP = /sourceMappingURL=data:application\/json/; it("should not reuse an inlined css map for a hidden devtool", async () => { expect(await build("source-map")).toMatch(INLINE_MAP_REGEXP); expect(await build("hidden-source-map")).not.toMatch(INLINE_MAP_REGEXP); }); it("should not reuse a hidden result for a non-hidden devtool", async () => { expect(await build("hidden-source-map")).not.toMatch(INLINE_MAP_REGEXP); expect(await build("source-map")).toMatch(INLINE_MAP_REGEXP); }); it("should not reuse inlined css map names for another template", async () => { await build("source-map"); const bundle = await build( "source-map", "webpack://custom/[resource-path]" ); const match = /sourceMappingURL=data:application\/json;charset=utf-8;base64,([\w+/=]+)/.exec( bundle ); const map = JSON.parse( Buffer.from(/** @type {RegExpExecArray} */ (match)[1], "base64").toString( "utf8" ) ); expect(map.sources).toContain("webpack://custom/./style.css"); }); it("should not reuse sources of an inlined css map for nosources", async () => { await build("source-map"); const bundle = await build("nosources-source-map"); const match = /sourceMappingURL=data:application\/json;charset=utf-8;base64,([\w+/=]+)/.exec( bundle ); const map = JSON.parse( Buffer.from(/** @type {RegExpExecArray} */ (match)[1], "base64").toString( "utf8" ) ); expect(map.sourcesContent).toBeUndefined(); }); });