diff --git a/packages/builder/lib/tasks/minify.js b/packages/builder/lib/tasks/minify.js index 439f53fc9a0..dc51c3c8eb0 100644 --- a/packages/builder/lib/tasks/minify.js +++ b/packages/builder/lib/tasks/minify.js @@ -36,7 +36,11 @@ export default async function({ resources = await Promise.all( changedProjectResourcePaths // Filtering out non-JS resources such as .map files - // FIXME: The changed resources should rather be matched against the provided pattern + // FIXME: A changed input source map (.js.map) does not re-minify its owning .js here, + // so the produced -dbg.js.map goes stale. Matching changed paths against "pattern" + // would not fix this: the task would need to learn the .map -> .js relation while + // processing changedProjectResourcePaths. That is likely a larger rework rather than a + // local fix (see the failing minify source-map staleness tests in @ui5/project). .filter((resourcePath) => resourcePath.endsWith(".js")) .map((resource) => workspace.byPath(resource)) ); diff --git a/packages/project/test/lib/build/BuildServer.integration.js b/packages/project/test/lib/build/BuildServer.integration.js index a8953ecfe11..dff935f0115 100644 --- a/packages/project/test/lib/build/BuildServer.integration.js +++ b/packages/project/test/lib/build/BuildServer.integration.js @@ -1355,6 +1355,77 @@ test.serial.failing( "Served resource no longer reflects the stale control value v1"); }); +// Served counterpart of the ProjectBuilder minify source-map staleness test (see +// ProjectBuilder.caching.integration.js for the full mechanism). Minify reads a resource's input source +// map via fsInterface and embeds its content into the `-dbg.js.map` output. That read goes through the +// monitored workspace's byPath, so changing ONLY the `.js.map` (not the referencing `.js`) invalidates +// minify's cache and re-runs it in delta mode with the `.js.map` as the sole changed path. But minify +// keeps only changed `.js` paths, so the unchanged `.js` is filtered out, the task writes nothing, and +// the previously served `-dbg.js.map` is carried forward STALE. +// +// This asserts the desired behavior (the changed input map is reflected in the served debug map without +// a server restart) and is marked test.failing because the delta path does not yet achieve it. See the +// minify FIXME for why a fix needs the `.map` -> `.js` relation, not a local pattern tweak. +test.serial.failing( + "Serve application.a, changing only an input source map read via fs by minify invalidates the debug source map", + async (t) => { + const fixtureTester = t.context.fixtureTester = await FixtureTester.create(t, "application.a"); + + await fixtureTester.serveProject(); + + const dbgSourceMapResourcePath = "/thirdparty/scriptWithSourceMap-dbg.js.map"; + const jsMapFilePath = `${fixtureTester.fixturePath}/webapp/thirdparty/scriptWithSourceMap.js.map`; + + // #1 request (fills the cache): the produced debug source map embeds the input source map's + // content, so it reflects the original marker. + const first = await fixtureTester.requestResource({resource: dbgSourceMapResourcePath}); + const firstContent = await first.getString(); + t.true(firstContent.includes("This is a script with a source map."), + "Initial debug source map reflects the original input source map content"); + + // Change ONLY the input source map — NOT the referencing scriptWithSourceMap.js. The minify task + // read this map via fsInterface, so it is a tracked input and this change invalidates minify's + // cache. But the owning .js is unchanged, so the differential minify path has no .js to reprocess. + const jsMapContent = await fs.readFile(jsMapFilePath, {encoding: "utf8"}); + await fs.writeFile( + jsMapFilePath, + jsMapContent.replace( + "This is a script with a source map.", + "This is a CHANGED script with a source map." + ) + ); + await fixtureTester.fireWatcherEvent("update", jsMapFilePath); + + // #2 request: the served debug source map must reflect the changed input source map content. + // The minify task is expected to re-execute here (its cache is invalidated because the changed + // .js.map is a tracked input) — proving the staleness is a differential-execution defect, not a + // missed invalidation. + const second = await fixtureTester.requestResource({ + resource: dbgSourceMapResourcePath, + assertions: { + projects: { + "application.a": { + skippedTasks: [ + "escapeNonAsciiCharacters", + // replaceCopyright is skipped because no copyright is configured in the project + "replaceCopyright", + "replaceVersion", + "enhanceManifest", + "generateFlexChangesBundle", + "generateVersionInfo" + // "minify" is NOT skipped: it re-runs in differential mode for the changed .js.map + ] + } + } + } + }); + const secondContent = await second.getString(); + t.true(secondContent.includes("This is a CHANGED script with a source map."), + "Served debug source map reflects the changed input source map without a server restart"); + t.false(secondContent.includes("This is a script with a source map."), + "Served debug source map no longer reflects the stale input source map content"); + }); + function getFixturePath(fixtureName) { return fileURLToPath(new URL(`../../fixtures/${fixtureName}`, import.meta.url)); } diff --git a/packages/project/test/lib/build/ProjectBuilder.caching.integration.js b/packages/project/test/lib/build/ProjectBuilder.caching.integration.js index d1f18ef21c7..afad3d0d54d 100644 --- a/packages/project/test/lib/build/ProjectBuilder.caching.integration.js +++ b/packages/project/test/lib/build/ProjectBuilder.caching.integration.js @@ -199,6 +199,78 @@ test.serial("Build application.a project multiple times", async (t) => { }); }); +// Minify reads a resource's input source map (the `//# sourceMappingURL=` target) via fsInterface and +// embeds its content almost verbatim into the `-dbg.js.map` output, so that debug map is a direct +// function of the input map. The read is a tracked input, so changing ONLY the `.js.map` (not the `.js` +// that references it) invalidates minify's cache and re-runs it in delta mode with the `.js.map` as the +// sole changed path. But minify keeps only changed `.js` paths and reads input maps only as a side +// effect of processing their owning `.js`; the unchanged `.js` is filtered out, so the task writes +// nothing and the previously produced `-dbg.js.map` is carried forward STALE. +// +// This asserts the desired behavior (the changed input map is reflected in the built debug map) and is +// marked test.failing because the delta path does not yet achieve it. See BuildServer.integration.js for +// the same scenario over the served build, and the minify FIXME for why a fix needs the `.map` -> `.js` +// relation, not a local pattern tweak. +test.serial.failing( + "Build application.a, changing only an input source map read via fs by minify invalidates the debug source map", + async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + const dbgSourceMapDestPath = `${destPath}/thirdparty/scriptWithSourceMap-dbg.js.map`; + const jsMapFilePath = + `${fixtureTester.fixturePath}/webapp/thirdparty/scriptWithSourceMap.js.map`; + + // #1 build (fills the cache): the produced debug source map embeds the input source map's + // content, so it reflects the original marker. + await fixtureTester.buildProject({ + config: {destPath, cleanDest: false}, + }); + const firstContent = await fs.readFile(dbgSourceMapDestPath, {encoding: "utf8"}); + t.true(firstContent.includes("This is a script with a source map."), + "Initial debug source map reflects the original input source map content"); + + // Change ONLY the input source map — NOT the referencing scriptWithSourceMap.js. The minify task + // read this map via fsInterface, so it is a tracked input and this change invalidates minify's + // cache. But the owning .js is unchanged, so the differential minify path has no .js to reprocess. + const jsMapContent = await fs.readFile(jsMapFilePath, {encoding: "utf8"}); + await fs.writeFile( + jsMapFilePath, + jsMapContent.replace( + "This is a script with a source map.", + "This is a CHANGED script with a source map." + ) + ); + + // #2 build (with cache, with changes): the built debug source map must reflect the changed input + // source map content. The minify task is expected to re-execute here (its cache is invalidated + // because the changed .js.map is a tracked input) — proving the staleness is a differential- + // execution defect, not a missed invalidation. + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": { + skippedTasks: [ + "enhanceManifest", + "escapeNonAsciiCharacters", + "generateFlexChangesBundle", + "generateVersionInfo", + // replaceCopyright is skipped because no copyright is configured in the project + "replaceCopyright" + // "minify" is NOT skipped: it re-runs in differential mode for the changed .js.map + ] + } + } + } + }); + const secondContent = await fs.readFile(dbgSourceMapDestPath, {encoding: "utf8"}); + t.true(secondContent.includes("This is a CHANGED script with a source map."), + "Built debug source map reflects the changed input source map"); + t.false(secondContent.includes("This is a script with a source map."), + "Built debug source map no longer reflects the stale input source map content"); + }); + test.serial("Build library.d project multiple times", async (t) => { const fixtureTester = new FixtureTester(t, "library.d"); const destPath = fixtureTester.destPath;