-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE] Minifier: Support input source maps #780
Conversation
@RandomByte instead of just relying on inline source maps, I would extend to logic a bit more and depend on the sourceMappingURL information, e.g. import {fileURLToPath} from "node:url";
import posixPath from "node:path/posix";
import os from "node:os";
import workerpool from "workerpool";
import Resource from "@ui5/fs/Resource";
import {getLogger} from "@ui5/logger";
const log = getLogger("builder:processors:minifier");
const debugFileRegex = /((?:\.view|\.fragment|\.controller|\.designtime|\.support)?\.js(?:\.map)?)$/;
const MIN_WORKERS = 2;
const MAX_WORKERS = 4;
const osCpus = os.cpus().length || 1;
const maxWorkers = Math.max(Math.min(osCpus - 1, MAX_WORKERS), MIN_WORKERS);
// Shared workerpool across all executions until the taskUtil cleanup is triggered
let pool;
function getPool(taskUtil) {
if (!pool) {
log.verbose(`Creating workerpool with up to ${maxWorkers} workers (available CPU cores: ${osCpus})`);
const workerPath = fileURLToPath(new URL("./minifierWorker.js", import.meta.url));
pool = workerpool.pool(workerPath, {
workerType: "auto",
maxWorkers
});
taskUtil.registerCleanupTask(() => {
log.verbose(`Terminating workerpool`);
const poolToBeTerminated = pool;
pool = null;
poolToBeTerminated.terminate();
});
}
return pool;
}
async function minifyInWorker(options, taskUtil) {
return getPool(taskUtil).exec("execMinification", [options]);
}
/**
* @public
* @module @ui5/builder/processors/minifier
*/
/**
* Result set
*
* @public
* @typedef {object} MinifierResult
* @property {@ui5/fs/Resource} resource Minified resource
* @property {@ui5/fs/Resource} dbgResource Debug (non-minified) variant
* @property {@ui5/fs/Resource} sourceMap Source Map
*/
/**
* Minifies the supplied resources.
*
* @public
* @function default
* @static
*
* @param {object} parameters Parameters
* @param {@ui5/fs/Resource[]} parameters.resources List of resources to be processed
* @param {@ui5/builder/tasks/TaskUtil|object} [parameters.taskUtil] TaskUtil instance.
* Required when using the <code>useWorkers</code> option
* @param {object} [parameters.options] Options
* @param {boolean} [parameters.options.addSourceMappingUrl=true]
* Whether to add a sourceMappingURL reference to the end of the minified resource
* @param {boolean} [parameters.options.useWorkers=false]
* Whether to offload the minification task onto separate CPU threads. This often speeds up the build process
* @returns {Promise<module:@ui5/builder/processors/minifier~MinifierResult[]>}
* Promise resolving with object of resource, dbgResource and sourceMap
*/
export default async function({resources, taskUtil, options: {addSourceMappingUrl = true, useWorkers = false} = {}, workspace}) {
let minify;
if (useWorkers) {
if (!taskUtil) {
// TaskUtil is required for worker support
throw new Error(`Minifier: Option 'useWorkers' requires a taskUtil instance to be provided`);
}
minify = minifyInWorker;
} else {
// Do not use workerpool
minify = (await import("./minifierWorker.js")).default;
}
return Promise.all(resources.map(async (resource) => {
const dbgPath = resource.getPath().replace(debugFileRegex, "-dbg$1");
const dbgResource = await resource.clone();
dbgResource.setPath(dbgPath);
const filename = posixPath.basename(resource.getPath());
const code = await resource.getString();
const sourceMapOptions = {
filename
};
if (addSourceMappingUrl) {
sourceMapOptions.url = filename + ".map";
}
const dbgFilename = posixPath.basename(dbgPath);
// find existing source map (either from sourceMappingURL or by naming convention)
const sourceMappingURL = code.match(/^\/\/# sourceMappingURL=(.*)$/m)?.[1] || filename + ".map";
if (sourceMappingURL.startsWith("data:")) {
sourceMapOptions.content = "inline";
} else {
const sourceMap = await workspace.byPath(posixPath.resolve(posixPath.dirname(resource.getPath()), sourceMappingURL));
if (sourceMap) {
sourceMapOptions.content = await sourceMap.getString();
// create the -dbg source map
const dbgSourceMapJSON = JSON.parse(sourceMapOptions.content);
dbgSourceMapJSON.file = dbgFilename;
const dbgSourceMapPath = sourceMap.getPath().replace(debugFileRegex, "-dbg$1");
const dbgSourceMapResource = new Resource({
path: dbgSourceMapPath,
string: JSON.stringify(dbgSourceMapJSON)
});
await workspace.write(dbgSourceMapResource);
}
}
const result = await minify({
filename,
dbgFilename,
code,
sourceMapOptions
}, taskUtil);
resource.setString(result.code);
const sourceMapResource = new Resource({
path: resource.getPath() + ".map",
string: result.map
});
return {resource, dbgResource, sourceMapResource};
}));
} I used this to verify the source maps handling in the |
Thanks Peter. Looks good! Maybe we can also reuse the code from our bundle/Builder module The main question for me is whether we should always search for source maps in the code or rather make this a |
How expensive is the |
@RandomByte @matz3 any chance to fix this before the vacation starts? 😉 |
Some things we need to consider when using exiting source maps: Existing source maps for resources that are modified by tasks preceding the minify tasks are likely to become inconsistent. For example if a resource is transpiled from TypeScript to JavaScript right at the beginning of the build, the This is not an issue for current TypeScript scenarios where typically To solve this aspect we would probably need to enhance the |
a1d50b5
to
9c31fda
Compare
Any chance for a release? 😄 |
lib/processors/minifier.js
Outdated
} | ||
} | ||
} else { | ||
const sourceMapFileCandidate = resourcePath + ".map"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We decided to remove this path, as it's not expected to have a Source Map file without a reference in the JavaScript file. Furthermore, if this is the case, it might be on purpose so that the existing Source Map is not used as input Source Map.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the records: according to the source map specs, a server can send an HTTP header to provide a reference to the source map for a resource it delivers. This is the only case where a source map would be needed without a reference in the code.
In the context of UI5, however, we're not aware of any scenario that makes use of this feature and therefore currently don't consider it as a use case for the tooling.
4575726
to
b50869d
Compare
Some benchmarks according to our documentation, comparing this PR to the current main branch: openui5-sample-app
sap.ui.core
OpenUI5 testsuite (including dependencies)
Internal TS Library
Specs: MacBook Pro M1 Max |
2e96a4c
to
44ff414
Compare
lib/tasks/minify.js
Outdated
@@ -19,32 +20,43 @@ import minifier from "../processors/minifier.js"; | |||
* @param {string} parameters.options.pattern Pattern to locate the files to be processed | |||
* @param {boolean} [parameters.options.omitSourceMapResources=false] Whether source map resources shall | |||
* be tagged as "OmitFromBuildResult" and no sourceMappingURL shall be added to the minified resource | |||
* @param {boolean} [parameters.options.useInputSourceMaps=false] Whether |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parameter description incomplete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
If a resource references a source map (or if we find a source map based on the resource name + ".map"): 1. Use that source map for the debug variant of the resource 2. Pass it to terser so it can update it based on the transformation, preserving the mapping to the initial source
…le path We expect source maps to be referenced in the respective source
…e been modified in previous tasks
* Always remove existing source map references from resource. Re-add reference to debug variant under certain conditions. * Add tests
…urce map entries This solves an issue where clicking on a line of generated bundle content in the dev tools still openes the module located above the expected one. We must always ensure that the first column is mapped.
Currently only fails because default of "useInputSourceMaps" option of minify task is set to "false".
630ac50
to
f25ea86
Compare
0c161e0
to
a34d522
Compare
What's the status on this one here? |
We've clarified all open points and are working on finalising this PR in the upcoming sprint. |
ui5-fs Resource change to ensure we don't use source maps of resources that have been modified by previous tasks, even when they exist in-memory only: SAP/ui5-fs#524 |
7312332
to
c713f15
Compare
Verified successful with all planned scenarios: This PR is ready from my side and only requires final review 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some minor stuff - hope I understood it correctly.
Co-authored-by: Günter Klatt <57760635+KlattG@users.noreply.github.com>
7b3e5f6
to
390fb81
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests for minify task should be enhanced
* Move integration tests into speparate file * Also tag the sourcemap associated with the debug resource as "IsDebugVariant"
Done |
If a resource references a source map (for example from an earlier transpilation):
Before this change, the minifier processor always created a new source map based on the minification of the resource. This change is active by default. However, if a resource is changed before the
minify
task is executed (for example by tasks likereplaceCopyright
), we assume that the referenced source map is potentially corrupt and therefore ignore it.The general logic is very similar to what we are already doing in
lbt/bundle/Builder.js
.Documentation: https://sap.github.io/ui5-tooling/stable/pages/Builder/#minify
Open points (also see comments below):
replaceVersion
orreplaceCopyright
), the corresponding source map is likely invalid and should not be used. It's better to have no map than an incorrect oneJIRA: CPOUI5FOUNDATION-467