From 0f6670e7df1f4245dc17ca70bc73a44176027036 Mon Sep 17 00:00:00 2001 From: Hai Date: Thu, 2 Jul 2026 05:52:34 +0800 Subject: [PATCH] fix: invalidate provided-exports cache with lazy barrel --- .changeset/lazy-barrel-stale-exports-cache.md | 5 ++ lib/Compilation.js | 5 +- lib/FlagDependencyExportsPlugin.js | 63 ++++++++++++++++--- lib/LazyBarrel.js | 33 +++++++++- .../build-on-new-import/webpack.config.js | 5 ++ .../lazy-barrel/issue-21318/0/index.js | 5 ++ .../lazy-barrel/issue-21318/0/pkg/index.js | 5 ++ .../b.js => issue-21318/0/pkg/sub/index.js} | 0 .../lazy-barrel/issue-21318/1/index.js | 5 ++ .../lazy-barrel/issue-21318/webpack.config.js | 26 ++++++++ .../stale-provide-fs-cache/0/index.js | 11 ++++ .../stale-provide-fs-cache/0/pkg/index.js | 7 +++ .../stale-provide-fs-cache/0/pkg/sub/index.js | 1 + .../0/pkg/sub2/index.js | 1 + .../stale-provide-fs-cache/1/index.js | 16 +++++ .../stale-provide-fs-cache/2/index.js | 10 +++ .../stale-provide-fs-cache/webpack.config.js | 21 +++++++ .../0/index.js | 10 +++ .../0/pkg/index.js | 2 + .../0/pkg/sub/index.js | 1 + .../1/index.js | 10 +++ .../webpack.config.js | 26 ++++++++ .../0/index.js | 6 +- .../0/pkg/a.js | 0 .../0/pkg/index.js | 1 + .../stale-provide-unsafe-cache/0/pkg/sub/b.js | 1 + .../0/pkg/sub/c.js | 0 .../0/pkg/sub/index.js | 0 .../1/index.js | 0 .../2/index.js | 0 .../webpack.config.js | 5 ++ .../lazy-barrel/stale-provide/0/index.js | 11 ++++ .../lazy-barrel/stale-provide/0/pkg/index.js | 7 +++ .../stale-provide/0/pkg/sub/index.js | 1 + .../stale-provide/0/pkg/sub2/index.js | 1 + .../lazy-barrel/stale-provide/1/index.js | 16 +++++ .../lazy-barrel/stale-provide/2/index.js | 10 +++ .../stale-provide/webpack.config.js | 18 ++++++ 38 files changed, 327 insertions(+), 18 deletions(-) create mode 100644 .changeset/lazy-barrel-stale-exports-cache.md create mode 100644 test/watchCases/lazy-barrel/issue-21318/0/index.js create mode 100644 test/watchCases/lazy-barrel/issue-21318/0/pkg/index.js rename test/watchCases/lazy-barrel/{unsafe-cache/0/pkg/sub/b.js => issue-21318/0/pkg/sub/index.js} (100%) create mode 100644 test/watchCases/lazy-barrel/issue-21318/1/index.js create mode 100644 test/watchCases/lazy-barrel/issue-21318/webpack.config.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-fs-cache/0/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/sub/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/sub2/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-fs-cache/1/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-fs-cache/2/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-fs-cache/webpack.config.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/pkg/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/pkg/sub/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/1/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/webpack.config.js rename test/watchCases/lazy-barrel/{unsafe-cache => stale-provide-unsafe-cache}/0/index.js (78%) rename test/watchCases/lazy-barrel/{unsafe-cache => stale-provide-unsafe-cache}/0/pkg/a.js (100%) rename test/watchCases/lazy-barrel/{unsafe-cache => stale-provide-unsafe-cache}/0/pkg/index.js (65%) create mode 100644 test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/sub/b.js rename test/watchCases/lazy-barrel/{unsafe-cache => stale-provide-unsafe-cache}/0/pkg/sub/c.js (100%) rename test/watchCases/lazy-barrel/{unsafe-cache => stale-provide-unsafe-cache}/0/pkg/sub/index.js (100%) rename test/watchCases/lazy-barrel/{unsafe-cache => stale-provide-unsafe-cache}/1/index.js (100%) rename test/watchCases/lazy-barrel/{unsafe-cache => stale-provide-unsafe-cache}/2/index.js (100%) rename test/watchCases/lazy-barrel/{unsafe-cache => stale-provide-unsafe-cache}/webpack.config.js (78%) create mode 100644 test/watchCases/lazy-barrel/stale-provide/0/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide/0/pkg/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide/0/pkg/sub/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide/0/pkg/sub2/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide/1/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide/2/index.js create mode 100644 test/watchCases/lazy-barrel/stale-provide/webpack.config.js diff --git a/.changeset/lazy-barrel-stale-exports-cache.md b/.changeset/lazy-barrel-stale-exports-cache.md new file mode 100644 index 00000000000..35ea2167e3e --- /dev/null +++ b/.changeset/lazy-barrel-stale-exports-cache.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Key the provided-exports cache on a lazy barrel's still-lazy re-export targets. diff --git a/lib/Compilation.js b/lib/Compilation.js index 97cfae8e461..92e5156f4da 100644 --- a/lib/Compilation.js +++ b/lib/Compilation.js @@ -1394,10 +1394,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si this._unsafeCachePredicate = typeof unsafeCache === "function" ? unsafeCache : () => true; - /** - * @private - * @type {LazyBarrelController} - */ + /** @type {LazyBarrelController} */ this._lazyBarrelController = new LazyBarrelController(this); } diff --git a/lib/FlagDependencyExportsPlugin.js b/lib/FlagDependencyExportsPlugin.js index 2a61adcadf5..798e03b455f 100644 --- a/lib/FlagDependencyExportsPlugin.js +++ b/lib/FlagDependencyExportsPlugin.js @@ -7,6 +7,7 @@ const asyncLib = require("neo-async"); const Queue = require("./util/Queue"); +const createHash = require("./util/createHash"); /** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("./DependenciesBlock")} DependenciesBlock */ @@ -16,12 +17,32 @@ const Queue = require("./util/Queue"); /** @typedef {import("./ExportsInfo")} ExportsInfo */ /** @typedef {import("./ExportsInfo").ExportInfoName} ExportInfoName */ /** @typedef {import("./ExportsInfo").RestoreProvidedData} RestoreProvidedData */ +/** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */ +/** @typedef {import("./LazyBarrel")} LazyBarrelController */ /** @typedef {import("./Module")} Module */ /** @typedef {import("./Module").BuildInfo} BuildInfo */ const PLUGIN_NAME = "FlagDependencyExportsPlugin"; const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`; +/** @typedef {{ etag: string, data: RestoreProvidedData }} MemCacheEntry */ + +/** + * @param {Module} module the module + * @param {LazyBarrelController} lazyBarrelController the compilation's lazy barrel controller + * @param {HashFunction} hashFunction hash function for long keys + * @returns {string} the hash + */ +const getLazyRequestsHash = (module, lazyBarrelController, hashFunction) => { + const lazyKeys = lazyBarrelController.getLazyRequests(module); + if (!lazyKeys) return ""; + const joined = [...lazyKeys].join("|"); + if (joined.length < 100) return joined; + const hash = createHash(hashFunction); + hash.update(joined); + return /** @type {string} */ (hash.digest("hex")); +}; + class FlagDependencyExportsPlugin { /** * Applies the plugin by registering its hooks on the compiler. @@ -32,6 +53,8 @@ class FlagDependencyExportsPlugin { compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { const moduleGraph = compilation.moduleGraph; const cache = compilation.getCache(PLUGIN_NAME); + const lazyBarrelController = compilation._lazyBarrelController; + const hashFunction = compilation.outputOptions.hashFunction; compilation.hooks.finishModules.tapAsync( PLUGIN_NAME, (modules, callback) => { @@ -80,16 +103,28 @@ class FlagDependencyExportsPlugin { return callback(); } const memCache = moduleMemCaches && moduleMemCaches.get(module); - const memCacheValue = memCache && memCache.get(this); - if (memCacheValue !== undefined) { + /** @type {MemCacheEntry | undefined} */ + const memCacheEntry = memCache && memCache.get(this); + const hash = getLazyRequestsHash( + module, + lazyBarrelController, + hashFunction + ); + // validated by the still-lazy request keys: reference-change + // detection cannot invalidate the mem cache when the un-lazied + // connections are unsafe-cached (they are skipped when comparing + // references) + if (memCacheEntry !== undefined && memCacheEntry.etag === hash) { statRestoredFromMemCache++; - exportsInfo.restoreProvided(memCacheValue); + exportsInfo.restoreProvided(memCacheEntry.data); return callback(); } + const buildHash = /** @type {string} */ ( + /** @type {BuildInfo} */ (module.buildInfo).hash + ); cache.get( module.identifier(), - /** @type {BuildInfo} */ - (module.buildInfo).hash, + hash ? `${buildHash}|${hash}` : buildHash, (err, result) => { if (err) return callback(err); @@ -421,13 +456,25 @@ class FlagDependencyExportsPlugin { .getRestoreProvidedData(); const memCache = moduleMemCaches && moduleMemCaches.get(module); + const hash = getLazyRequestsHash( + module, + lazyBarrelController, + hashFunction + ); if (memCache) { - memCache.set(this, cachedData); + /** @type {MemCacheEntry} */ + const entry = { + etag: hash, + data: cachedData + }; + memCache.set(this, entry); } + const buildHash = /** @type {string} */ ( + /** @type {BuildInfo} */ (module.buildInfo).hash + ); cache.store( module.identifier(), - /** @type {BuildInfo} */ - (module.buildInfo).hash, + hash ? `${buildHash}|${hash}` : buildHash, cachedData, callback ); diff --git a/lib/LazyBarrel.js b/lib/LazyBarrel.js index b262f14d55f..eef84021c48 100644 --- a/lib/LazyBarrel.js +++ b/lib/LazyBarrel.js @@ -68,7 +68,7 @@ class LazyBarrelInfo { this._requestToDepGroup = new Map(); /** @type {Set | undefined} locally provided export names */ this._terminalIds = undefined; - /** @type {Set | undefined} request keys of star re-exports */ + /** @type {Set | undefined} requests of star re-exports */ this._fallbackRequests = undefined; } @@ -125,6 +125,14 @@ class LazyBarrelInfo { return this._requestToDepGroup.size === 0; } + /** + * Returns the requests of the still-lazy groups. + * @returns {Iterable} still-lazy requests + */ + getLazyRequests() { + return this._requestToDepGroup.keys(); + } + /** * Drops the export-name lookups once no group stays deferred; they only serve * to find still-deferred groups, so keeping them per module wastes memory. @@ -202,13 +210,32 @@ class LazyBarrelController { } /** - * Releases all per-module deferral state. Lazy barrel only acts while the - * module graph is built, so the bookkeeping is dead weight once make is done. + * Releases all per-module deferral state. The last consumer is + * `FlagDependencyExportsPlugin`'s cache key during `finishModules`, so the + * bookkeeping is dead weight once sealing starts. */ clear() { this._modules = new WeakMap(); } + /** + * requests of the re-export targets `module` still keeps lazy. + * `FlagDependencyExportsPlugin` folds them into its provided-exports cache + * key, so a barrel whose built targets differ from a cached run does not + * reuse that run's stale exports. + * @param {Module} module the module + * @returns {Iterable | undefined} still-lazy requests, if any + */ + getLazyRequests(module) { + const factoryMeta = module.factoryMeta; + if (factoryMeta === undefined || !factoryMeta.sideEffectFree) return; + const state = this._modules.get(module); + if (state === undefined) return; + const info = state.lazyBarrelInfo; + if (info === undefined || info.isEmpty()) return; + return info.getLazyRequests(); + } + /** * Classifies the re-export dependencies of a side-effect-free module (lazy barrel) * into deferrable groups and marks the deferred ones. diff --git a/test/watchCases/lazy-barrel/build-on-new-import/webpack.config.js b/test/watchCases/lazy-barrel/build-on-new-import/webpack.config.js index 101f260e069..a4bed93aa21 100644 --- a/test/watchCases/lazy-barrel/build-on-new-import/webpack.config.js +++ b/test/watchCases/lazy-barrel/build-on-new-import/webpack.config.js @@ -3,6 +3,11 @@ /** @type {import("../../../../").Configuration} */ module.exports = { module: { + parser: { + javascript: { + exportsPresence: "error" + } + }, rules: [ { test: /pkg/, diff --git a/test/watchCases/lazy-barrel/issue-21318/0/index.js b/test/watchCases/lazy-barrel/issue-21318/0/index.js new file mode 100644 index 00000000000..2cfd4bd040a --- /dev/null +++ b/test/watchCases/lazy-barrel/issue-21318/0/index.js @@ -0,0 +1,5 @@ +import { local } from "./pkg"; + +it("should mem-cache the barrel exports while its star target stays deferred", () => { + expect(local).toBe("local"); +}); diff --git a/test/watchCases/lazy-barrel/issue-21318/0/pkg/index.js b/test/watchCases/lazy-barrel/issue-21318/0/pkg/index.js new file mode 100644 index 00000000000..d772575e2f2 --- /dev/null +++ b/test/watchCases/lazy-barrel/issue-21318/0/pkg/index.js @@ -0,0 +1,5 @@ +import { b as internalB } from "./sub"; + +// consume the star target directly, like mobx-react consumes mobx-react-lite +export const local = internalB === "b" ? "local" : "wrong"; +export * from "./sub"; diff --git a/test/watchCases/lazy-barrel/unsafe-cache/0/pkg/sub/b.js b/test/watchCases/lazy-barrel/issue-21318/0/pkg/sub/index.js similarity index 100% rename from test/watchCases/lazy-barrel/unsafe-cache/0/pkg/sub/b.js rename to test/watchCases/lazy-barrel/issue-21318/0/pkg/sub/index.js diff --git a/test/watchCases/lazy-barrel/issue-21318/1/index.js b/test/watchCases/lazy-barrel/issue-21318/1/index.js new file mode 100644 index 00000000000..d3acc4a9096 --- /dev/null +++ b/test/watchCases/lazy-barrel/issue-21318/1/index.js @@ -0,0 +1,5 @@ +import { b } from "./pkg"; + +it("should provide a star re-export name newly imported with unsafe cache", () => { + expect(b).toBe("b"); +}); diff --git a/test/watchCases/lazy-barrel/issue-21318/webpack.config.js b/test/watchCases/lazy-barrel/issue-21318/webpack.config.js new file mode 100644 index 00000000000..9d9e9f48f24 --- /dev/null +++ b/test/watchCases/lazy-barrel/issue-21318/webpack.config.js @@ -0,0 +1,26 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + cache: { + type: "memory", + cacheUnaffected: true + }, + experiments: { + cacheUnaffected: true + }, + module: { + unsafeCache: true, + parser: { + javascript: { + exportsPresence: "error" + } + }, + rules: [ + { + test: /pkg/, + sideEffects: false + } + ] + } +}; diff --git a/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/index.js b/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/index.js new file mode 100644 index 00000000000..2c54ee088f6 --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/index.js @@ -0,0 +1,11 @@ +import { local, bProvided } from "./pkg"; + +// only the local export is used, so `export * from "./sub"` stays deferred and +// pkg gets cached without sub's exports +it("should not provide the unused star re-export name initially", () => { + expect(local).toBe("local"); + expect(bProvided).not.toBe(true); + expect( + STATS_JSON.modules.some((m) => m.name.endsWith("pkg/sub/index.js")) + ).toBe(false); +}); diff --git a/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/index.js b/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/index.js new file mode 100644 index 00000000000..1c647d40c6b --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/index.js @@ -0,0 +1,7 @@ +export const local = "local"; +export * from "./sub"; +export { c } from "./sub2"; + +// pkg's own exports info, re-read on each codegen (exports-info hash change) +export const bProvided = __webpack_exports_info__.b.provideInfo; +export const cProvided = __webpack_exports_info__.c.provideInfo; diff --git a/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/sub/index.js b/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/sub/index.js new file mode 100644 index 00000000000..59d1689930e --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/sub/index.js @@ -0,0 +1 @@ +export const b = "b"; diff --git a/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/sub2/index.js b/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/sub2/index.js new file mode 100644 index 00000000000..4c22ec6a2fd --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-fs-cache/0/pkg/sub2/index.js @@ -0,0 +1 @@ +export const c = 'c' diff --git a/test/watchCases/lazy-barrel/stale-provide-fs-cache/1/index.js b/test/watchCases/lazy-barrel/stale-provide-fs-cache/1/index.js new file mode 100644 index 00000000000..0b6f0cbdc9b --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-fs-cache/1/index.js @@ -0,0 +1,16 @@ +import { local, b, bProvided } from "./pkg"; + +// `b` comes through the previously deferred `export * from "./sub"`; pkg is +// restored from the FlagDependencyExportsPlugin cache by its unchanged hash, so +// it must be re-flagged to regain sub's exports (else `b` reads as not provided) +it("should provide a star re-export name newly imported on rebuild", () => { + expect(local).toBe("local"); + expect(b).toBe("b"); + expect(bProvided).toBe(true); + expect( + STATS_JSON.modules.some((m) => m.name.endsWith("pkg/sub/index.js")) + ).toBe(true); + expect( + STATS_JSON.modules.some((m) => m.name.endsWith("pkg/sub2/index.js")) + ).toBe(false); +}); diff --git a/test/watchCases/lazy-barrel/stale-provide-fs-cache/2/index.js b/test/watchCases/lazy-barrel/stale-provide-fs-cache/2/index.js new file mode 100644 index 00000000000..40865ea395e --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-fs-cache/2/index.js @@ -0,0 +1,10 @@ +import { local, c, cProvided } from "./pkg"; + +it("should provide a named re-export newly imported on rebuild", () => { + expect(local).toBe("local"); + expect(c).toBe("c"); + expect(cProvided).toBe(true); + expect( + STATS_JSON.modules.some((m) => m.name.endsWith("pkg/sub2/index.js")) + ).toBe(true); +}); diff --git a/test/watchCases/lazy-barrel/stale-provide-fs-cache/webpack.config.js b/test/watchCases/lazy-barrel/stale-provide-fs-cache/webpack.config.js new file mode 100644 index 00000000000..5132faee341 --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-fs-cache/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + cache: { + type: "filesystem" + }, + module: { + parser: { + javascript: { + exportsPresence: "error" + } + }, + rules: [ + { + test: /pkg/, + sideEffects: false + } + ] + } +}; diff --git a/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/index.js new file mode 100644 index 00000000000..e7e628cc830 --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/index.js @@ -0,0 +1,10 @@ +import { local } from "./pkg"; +// the direct import builds sub in this step, while pkg's `export * from "./sub"` +// stays deferred — so pkg's provided exports get mem-cached without sub's names +// and sub is "unchanged" (never flagged affected) on the next build +import { b as directB } from "./pkg/sub"; + +it("should mem-cache the barrel exports while its star target stays deferred", () => { + expect(local).toBe("local"); + expect(directB).toBe("b"); +}); diff --git a/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/pkg/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/pkg/index.js new file mode 100644 index 00000000000..146a201d5af --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/pkg/index.js @@ -0,0 +1,2 @@ +export const local = "local"; +export * from "./sub"; diff --git a/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/pkg/sub/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/pkg/sub/index.js new file mode 100644 index 00000000000..59d1689930e --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/0/pkg/sub/index.js @@ -0,0 +1 @@ +export const b = "b"; diff --git a/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/1/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/1/index.js new file mode 100644 index 00000000000..795984c54a0 --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/1/index.js @@ -0,0 +1,10 @@ +import { local, b } from "./pkg"; + +// `b` comes through the previously deferred `export * from "./sub"`; sub already +// existed unchanged, so the affected-modules propagation never resets the +// barrel's mem cache — it must still not restore the stale provided exports +// cached without sub's names +it("should provide a star re-export name newly imported on rebuild", () => { + expect(local).toBe("local"); + expect(b).toBe("b"); +}); diff --git a/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/webpack.config.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/webpack.config.js new file mode 100644 index 00000000000..9d9e9f48f24 --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache-unaffected/webpack.config.js @@ -0,0 +1,26 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + cache: { + type: "memory", + cacheUnaffected: true + }, + experiments: { + cacheUnaffected: true + }, + module: { + unsafeCache: true, + parser: { + javascript: { + exportsPresence: "error" + } + }, + rules: [ + { + test: /pkg/, + sideEffects: false + } + ] + } +}; diff --git a/test/watchCases/lazy-barrel/unsafe-cache/0/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/index.js similarity index 78% rename from test/watchCases/lazy-barrel/unsafe-cache/0/index.js rename to test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/index.js index d277b1a44fd..808d9b27c16 100644 --- a/test/watchCases/lazy-barrel/unsafe-cache/0/index.js +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/index.js @@ -1,14 +1,14 @@ -import { a } from "./pkg"; +import { local } from "./pkg"; const built = (file) => STATS_JSON.modules.some((m) => m.name.endsWith(file)); it("should provide the requested re-export with unsafe cache", () => { - expect(a).toBe("a"); + expect(local).toBe("local"); }); it("should not build the nested barrel or its targets initially", () => { - expect(built("pkg/a.js")).toBe(true); + expect(built("pkg/a.js")).toBe(false); expect(built("pkg/sub/index.js")).toBe(false); expect(built("pkg/sub/b.js")).toBe(false); expect(built("pkg/sub/c.js")).toBe(false); diff --git a/test/watchCases/lazy-barrel/unsafe-cache/0/pkg/a.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/a.js similarity index 100% rename from test/watchCases/lazy-barrel/unsafe-cache/0/pkg/a.js rename to test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/a.js diff --git a/test/watchCases/lazy-barrel/unsafe-cache/0/pkg/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/index.js similarity index 65% rename from test/watchCases/lazy-barrel/unsafe-cache/0/pkg/index.js rename to test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/index.js index 75342198769..423506d6155 100644 --- a/test/watchCases/lazy-barrel/unsafe-cache/0/pkg/index.js +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/index.js @@ -1,2 +1,3 @@ export { a } from "./a"; export { b, c } from "./sub"; +export const local = "local" diff --git a/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/sub/b.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/sub/b.js new file mode 100644 index 00000000000..59d1689930e --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/sub/b.js @@ -0,0 +1 @@ +export const b = "b"; diff --git a/test/watchCases/lazy-barrel/unsafe-cache/0/pkg/sub/c.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/sub/c.js similarity index 100% rename from test/watchCases/lazy-barrel/unsafe-cache/0/pkg/sub/c.js rename to test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/sub/c.js diff --git a/test/watchCases/lazy-barrel/unsafe-cache/0/pkg/sub/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/sub/index.js similarity index 100% rename from test/watchCases/lazy-barrel/unsafe-cache/0/pkg/sub/index.js rename to test/watchCases/lazy-barrel/stale-provide-unsafe-cache/0/pkg/sub/index.js diff --git a/test/watchCases/lazy-barrel/unsafe-cache/1/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/1/index.js similarity index 100% rename from test/watchCases/lazy-barrel/unsafe-cache/1/index.js rename to test/watchCases/lazy-barrel/stale-provide-unsafe-cache/1/index.js diff --git a/test/watchCases/lazy-barrel/unsafe-cache/2/index.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/2/index.js similarity index 100% rename from test/watchCases/lazy-barrel/unsafe-cache/2/index.js rename to test/watchCases/lazy-barrel/stale-provide-unsafe-cache/2/index.js diff --git a/test/watchCases/lazy-barrel/unsafe-cache/webpack.config.js b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/webpack.config.js similarity index 78% rename from test/watchCases/lazy-barrel/unsafe-cache/webpack.config.js rename to test/watchCases/lazy-barrel/stale-provide-unsafe-cache/webpack.config.js index aaefb613b20..82b420956ba 100644 --- a/test/watchCases/lazy-barrel/unsafe-cache/webpack.config.js +++ b/test/watchCases/lazy-barrel/stale-provide-unsafe-cache/webpack.config.js @@ -8,6 +8,11 @@ module.exports = { }, module: { unsafeCache: true, + parser: { + javascript: { + exportsPresence: "error" + } + }, rules: [ { test: /pkg/, diff --git a/test/watchCases/lazy-barrel/stale-provide/0/index.js b/test/watchCases/lazy-barrel/stale-provide/0/index.js new file mode 100644 index 00000000000..2c54ee088f6 --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide/0/index.js @@ -0,0 +1,11 @@ +import { local, bProvided } from "./pkg"; + +// only the local export is used, so `export * from "./sub"` stays deferred and +// pkg gets cached without sub's exports +it("should not provide the unused star re-export name initially", () => { + expect(local).toBe("local"); + expect(bProvided).not.toBe(true); + expect( + STATS_JSON.modules.some((m) => m.name.endsWith("pkg/sub/index.js")) + ).toBe(false); +}); diff --git a/test/watchCases/lazy-barrel/stale-provide/0/pkg/index.js b/test/watchCases/lazy-barrel/stale-provide/0/pkg/index.js new file mode 100644 index 00000000000..1c647d40c6b --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide/0/pkg/index.js @@ -0,0 +1,7 @@ +export const local = "local"; +export * from "./sub"; +export { c } from "./sub2"; + +// pkg's own exports info, re-read on each codegen (exports-info hash change) +export const bProvided = __webpack_exports_info__.b.provideInfo; +export const cProvided = __webpack_exports_info__.c.provideInfo; diff --git a/test/watchCases/lazy-barrel/stale-provide/0/pkg/sub/index.js b/test/watchCases/lazy-barrel/stale-provide/0/pkg/sub/index.js new file mode 100644 index 00000000000..59d1689930e --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide/0/pkg/sub/index.js @@ -0,0 +1 @@ +export const b = "b"; diff --git a/test/watchCases/lazy-barrel/stale-provide/0/pkg/sub2/index.js b/test/watchCases/lazy-barrel/stale-provide/0/pkg/sub2/index.js new file mode 100644 index 00000000000..4c22ec6a2fd --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide/0/pkg/sub2/index.js @@ -0,0 +1 @@ +export const c = 'c' diff --git a/test/watchCases/lazy-barrel/stale-provide/1/index.js b/test/watchCases/lazy-barrel/stale-provide/1/index.js new file mode 100644 index 00000000000..0b6f0cbdc9b --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide/1/index.js @@ -0,0 +1,16 @@ +import { local, b, bProvided } from "./pkg"; + +// `b` comes through the previously deferred `export * from "./sub"`; pkg is +// restored from the FlagDependencyExportsPlugin cache by its unchanged hash, so +// it must be re-flagged to regain sub's exports (else `b` reads as not provided) +it("should provide a star re-export name newly imported on rebuild", () => { + expect(local).toBe("local"); + expect(b).toBe("b"); + expect(bProvided).toBe(true); + expect( + STATS_JSON.modules.some((m) => m.name.endsWith("pkg/sub/index.js")) + ).toBe(true); + expect( + STATS_JSON.modules.some((m) => m.name.endsWith("pkg/sub2/index.js")) + ).toBe(false); +}); diff --git a/test/watchCases/lazy-barrel/stale-provide/2/index.js b/test/watchCases/lazy-barrel/stale-provide/2/index.js new file mode 100644 index 00000000000..40865ea395e --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide/2/index.js @@ -0,0 +1,10 @@ +import { local, c, cProvided } from "./pkg"; + +it("should provide a named re-export newly imported on rebuild", () => { + expect(local).toBe("local"); + expect(c).toBe("c"); + expect(cProvided).toBe(true); + expect( + STATS_JSON.modules.some((m) => m.name.endsWith("pkg/sub2/index.js")) + ).toBe(true); +}); diff --git a/test/watchCases/lazy-barrel/stale-provide/webpack.config.js b/test/watchCases/lazy-barrel/stale-provide/webpack.config.js new file mode 100644 index 00000000000..a4bed93aa21 --- /dev/null +++ b/test/watchCases/lazy-barrel/stale-provide/webpack.config.js @@ -0,0 +1,18 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + module: { + parser: { + javascript: { + exportsPresence: "error" + } + }, + rules: [ + { + test: /pkg/, + sideEffects: false + } + ] + } +};