Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/builder/lib/tasks/minify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
);
Expand Down
71 changes: 71 additions & 0 deletions packages/project/test/lib/build/BuildServer.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading