Skip to content
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] Sourcemap support in the ui5-builder preload generation #282

Closed
wants to merge 15 commits into from

Conversation

nlunets
Copy link
Member

@nlunets nlunets commented Jul 4, 2019

  • Always go for the predefine rewrite in the library build
  • Change the order of the steps to have debug file created early enough
  • Sourcemap support for the rewrite and for the preload generation
  • Uglifier can take input sourcemap as well (if you have a custom transpile step...)

I still need to get around to fixing all the tests and usage but if you already wanted to have a look @codeworrior ;)

Thank you for your contribution! 🙌

To get it merged faster, kindly review the checklist below:

Pull Request Checklist

@CLAassistant
Copy link

CLAassistant commented Jul 4, 2019

CLA assistant check
All committers have signed the CLA.

@RandomByte RandomByte requested a review from devtomtom July 4, 2019 14:20
@RandomByte RandomByte added the enhancement New feature or request label Jul 4, 2019
@RandomByte RandomByte added this to In progress in UI5 Tooling - Tasks via automation Jul 4, 2019
Copy link
Member

@codeworrior codeworrior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you avoid writing out the *.map for the minified individual files? I missed that part...

@@ -25,6 +29,8 @@ class BundleWriter {
write(...str) {
for ( let i = 0; i < str.length; i++ ) {
this.buf += str[i];
this.lineOffset += str[i].split(NL).length - 1;
this.columnOffset += str[i].length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this just be the length of the last line if there's any NL in the str and be incremented, when there's no NL?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're correct, it works today because I just deal with minified, one line files ;)

// rewrite sap.ui.define to sap.ui.predefine
if ( defineCall.callee.type === Syntax.MemberExpression
&& defineCall.callee.property.type === Syntax.Identifier
&& defineCall.callee.property.name === "define" ) {
&& defineCall.callee.property.name === "define") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move back to original position , please

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you had prettier we wouldn't have this conversation ! (just teasing, yes I'll move it back)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So 'prettier' basically is a tool that keeps people from talking to each other, did I get that right?

@nlunets
Copy link
Member Author

nlunets commented Jul 4, 2019

How do you avoid writing out the *.map for the minified individual files? I missed that part...

I do not avoid creating the map for the individual minified files, I actually want that, but that whole part is weird anyway.
Why do we create uglified files if anyway we're not reusing them for the preload and doing it again ?
Also shouldn't we minify the whole preload rather than each file individually in the preload ?

@codeworrior
Copy link
Member

Having source maps also for each individual source will significantly increase our footprint for the DIST layer, I would assume (once we use the tooling also for DIST's production).

Why are we creating minified files if we are not reusing them for the bundles? And why don't we minify the bundle only after creating it? Well, there are many different reasons for that, but at the end this becomes a historically grown list of excuses:

  • "rewriting sap.ui.define to sap.ui.predefine" only for the preloads and "sorting by copyright" was easier that way in Maven
  • we anyhow had no multi-stage source map creation there which would be a precondition
  • minifying individual files payed out on mobile devices when running without preloads (h2). The memory kept by source code is a significant part of our heap (at least with dev tools open, we never measured without)
  • we got feedback that some developers prefer the ui5 debug mode over source maps (what's the argument here?)
  • reg. ui5-tooling, it seemed to be a nice quality of UI5 tasks when they're independent from each other (easier reconfiguration). What we envisioned instead of writing out all intermediate results was a kind of resource-related caching in the tooling. The first task that parses a file, can put the resulting AST in a cache and any later task requesting the same AST could take it from there (if the resource did not change in the meantime). Same for uglified code or for the results of the dependency analysis. Well, an idea, not reality...
  • for the import into the ABAP BSP, minimizing the line-based diff of preloads (between versions) was reported to be beneficial. This was again easier to ensure with a single stage minification, but not impossible without. Well, we anyhow lost that "smart line break" feature with the migration to uglify
  • we don't know a minifier that could handle mixed content as present in our preloads. So we anyhow would have to minify JSON / XML content first before embedding it into the preload (it's harder to judge the nature of a string literal in the final preload)

...

@nlunets
Copy link
Member Author

nlunets commented Jul 5, 2019

Hmmm, apart from the increased footprint I don't see any good reason to not include them (esp if you consider h2) but that's your decision in the end.

Regarding minifying twice it's not a big deal really but we could reuse the one that happened before (if it happened) as an optimisation.

You mentioned the abap stuff, won't that increase diff size create issue during transport ?

@codeworrior
Copy link
Member

Presumably, yes. But for apps, I'm not too much into the details. For the DIST layer, it doesn't play a role as DIST layer was imported into MIME repo (binaries, no diff) and with the universal bootstrap (redirect to cloud), on premise installations don't play the same role as before.

But transports created for apps IMO could suffer from that changed behavior. As long as Fiori apps are released with Maven, we won't notice. I'll ask Fiori and delivery colleagues next week.

The source maps topic, I'll discuss with Peter and Jens Ittel.

@nlunets
Copy link
Member Author

nlunets commented Jul 9, 2019

I've removed the automatic creation of map for the uglified files now and the test should all run.
I've checked some of the new output but you're the experts ;)

@coveralls
Copy link

coveralls commented Jul 9, 2019

Coverage Status

Coverage increased (+0.2%) to 90.527% when pulling 54edf28 on nlunets:sourcemap into 9aa6785 on SAP:master.

@nlunets
Copy link
Member Author

nlunets commented Sep 10, 2019

@codeworrior This has been rebased and should be fine for review "code wise".
I'll check separately that the sourcemap are actually correct ....

@nlunets nlunets force-pushed the sourcemap branch 3 times, most recently from 3c637e1 to 6842523 Compare September 20, 2019 13:12
Copy link
Member

@codeworrior codeworrior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the branch locally by building the Openui5 testsuite and debugging a few simple apps. From a functional point of view, the results look quite good.

However, I have a few comments / questions / suggestions:

  • some of the fixes you made meanwhile have been made in [FIX] Improve recognition of main module in case of bundles #341 as well. Some need to be removed as they differ from what has been done in [FIX] Improve recognition of main module in case of bundles #341

  • the added handling of library designtime bundles should be moved to a separate change (1 topic 1 change)

  • the further optimization of XML views IMO should not be done, see inline comment

  • building the source maps seems to significantly increase the build time. With the predecessor commit of this branch, a build --clean-dest --all took 3.07 min (avg. over 5 runs), with the sourcemaps it took 4.33 min. I wonder if it is possible (without too complicated code) to skip the sourcemap support and expose that as an option . @RandomByte , @matz3 : what do you think?

  • another question (which unfortunately makes my lack of understanding transparent :-) ): how essential is the escodegen step for the source map support? Would it continue to work when we provide terser with the AST instead of the code string? My impression is that terser has fixed the issues that uglifyes had with the Spider Monkey AST and newer (ES6) syntax.

@@ -42,7 +43,7 @@ const UI5BundleFormat = {
outW.write(`jQuery.sap.registerPreloadedModules(`);
outW.writeln(`{`);
if ( section.name ) {
outW.writeln(`"name":"${section.getSectionName()}",`);
outW.writeln(`"name":"${section.name}",`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you might have to rebase again. The same fix has been made in PR #341 where I also tried to close some code coverage gaps un the builder and resolver.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙈

@@ -73,7 +74,7 @@ const EVOBundleFormat = {
afterPreloads(outW, section) {
outW.write(`}`);
if ( section.name ) {
outW.write(`,"${section.getSectionName()}"`);
outW.write(`,"${section.name}"`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙈

@@ -288,7 +288,7 @@ class BundleBuilder {
const sequence = section.modules.slice();

this.beforeWriteFunctionPreloadSection(sequence);

this.outW.writeln(`//@ui5-bundle ${section.bundle.name}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also part of #341 . Not sure if the auto-merge can handle this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase can :)

column: mapping.originalColumn
},
source: mapping.originalLine != null ?
(path.join(path.relative(path.dirname(this._sourceMap._file), path.dirname(resource.name)), this.optimize ? ModuleName.getDebugName(path.basename(resource.name)) : path.basename(resource.name))).replace(/\\/g, "/") : null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the special handling for \ looks very suspicious to me. AFAIK, we do this nowhere.

And line length won't make @RandomByte happy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well i'm not sure how you deal with the path operation so far, but I couldn't find anything specific.
In my case I need to get relative file path for the sources, usually this would be done with path directly but since you have a vfs behind the scene all file paths are unix like (which in turn break the usage of path on windows).
I would love for a better alternative (which doesn't involve rewriting basic functionalities of node)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you have a vfs behind the scene all file paths are unix like (which in turn break the usage of path on windows)

When working with paths coming from or passed to our vfs, use path.posix. E.g. path.posix.relative.

In cases were you have a native path and want to use it with the vfs, we use the slash module:

let projectPath = this._project.path;
if (posix) {
projectPath = slash(projectPath);
p = path.posix;
}
return p.join(projectPath, this._project.resources.pathMappings["/"]);

@@ -440,6 +484,8 @@ class BundleBuilder {
// because whitespace of HTML <pre> should be preserved (should only happen rarely)
if (!xmlHtmlPrePattern.test(fileContent)) {
fileContent = pd.xmlmin(fileContent.toString(), false);
fileContent = fileContent.replace(/\n/g, "");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove these two replacement steps. They can't differentiate between whitespace between tags and whitespace inside e.g. string literals in expression bindings. It is hard to imagine how such unescaped whitespace could be used, but not modifying those parts of the view is a matter of principle.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this because it was actually difference with the current tool :) But I can remove it for now

// rewrite sap.ui.define to sap.ui.predefine
if ( defineCall.callee.type === Syntax.MemberExpression
&& defineCall.callee.property.type === Syntax.Identifier
&& defineCall.callee.property.name === "define" ) {
&& defineCall.callee.property.name === "define") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unbalanced. I understand that whitespace after/before parentheses is a matter of taste, but mixing both approaches does not qualify as a good compromise, IMHO :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one word: prettier

@@ -253,7 +253,7 @@ class BundleResolver {

// NODE-TODO long t0=System.nanoTime();
const resolvedSection = resolved.sections[index];

resolvedSection.name = section.name;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove, Now handled differently with PR #341 .

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙈

@@ -1,5 +1,6 @@
const terser = require("terser");
const copyrightCommentsPattern = /copyright|\(c\)(?:[0-9]+|\s+[0-9A-za-z])|released under|license|\u00a9/i;
// const EvoResource = require("@ui5/fs").Resource;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that's the whole problem with the current flow.
The preload files are created before the debug files are created and the source uglified, so in effect some of the job is done twice (uglify) and this also doesn't work well with external transpilation.

Ideally the flow should allow for

  • src (es6 / yolo)
  • transpilation (with source map to the original file)
  • uglify (with source map resolved to the original file)
  • bundling (with source map resolved to the original files)

The easiest way would be to consider

  • source
  • copy to dist as debug files (saved as is)
  • perform transformation(s) and bundling on the -dbg files

@@ -48,15 +49,18 @@ function getBundleDefinition(namespace) {
};
}
return {
name: `${namespace}/library-preload.js`,
defaultFileTypes: [".js", ".fragment.xml", ".view.xml", ".properties", ".json"],
name: `${namespace}/library-preload${isDesignTime ? ".designtime" : ""}.js`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we please address the handling of designtime bundles in a separate change. It totally makes sense, but I would suggest to follow the principle "1 topic - 1 change".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure I'll put that on the side

UI5 Tooling - Tasks automation moved this from In progress to Needs review Oct 2, 2019
@nlunets
Copy link
Member Author

nlunets commented Oct 3, 2019

another question (which unfortunately makes my lack of understanding transparent :-) ): how essential is the escodegen step for the source map support? Would it continue to work when we provide terser with the AST instead of the code string? My impression is that terser has fixed the issues that uglifyes had with the Spider Monkey AST and newer (ES6) syntax.

I think (but would need to check), that the overall process (even the bundling) could work on the AST objects directly, avoiding the back and forth from code to AST which may be a little be costly.
I'll make the changes you requested first and extract the design time stuff for now and will look at what we can do there.
However please also take a look at my comment on the task workflow as I think it's also relevant for all this.

@nlunets
Copy link
Member Author

nlunets commented Oct 23, 2019

Ok, while I fix the tests here are some details.

  • The code is now parsed once with terser to AST and minification is then done from the AST directly, avoiding the "useless" escodegen in the middle.
  • No more weird path !

@nlunets
Copy link
Member Author

nlunets commented Oct 23, 2019

@RandomByte @matz3 I doubt the error on the pipeline are normal, any way to rerun it ?

@@ -118,7 +118,7 @@ module.exports = function({resources, options}) {
const pool = new LocatorResourcePool();
const builder = new BundleBuilder(pool);

const bundleOptions = options.bundleOptions || {optimize: true};
const bundleOptions = options.bundleOptions || {optimize: true, usePredefineCalls: true};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codeworrior Am I correct in doing this or did I do too much here ? :)

@codeworrior
Copy link
Member

Basically, the creation of source maps looks good to me. The change of the order of tasks also makes sense to me, but that's something that I shouldn't decide alone. Next year, we'll do a review together in the team.

One thing that I noticed: only the createDebugFiles task has been moved, but not the uglify task. Was this intentional? As a side effect, uglification breaks the source maps for the bundles and removes the sourceMappingURL annotation. At least for sap-ui-core.js, I could observe this.

One minor thing: I expected some code that modifies the source map for the rewritten sap.ui.define call, but couldn't find anything. Is this handled by omitting the line and column info for the new AST nodes?

@codeworrior codeworrior requested review from codeworrior and removed request for devtomtom December 29, 2019 22:27
@nlunets
Copy link
Member Author

nlunets commented Jan 2, 2020

One thing that I noticed: only the createDebugFiles task has been moved, but not the uglify task. Was this intentional?

Probably not... although I would except uglification to ignore bundles

One minor thing: I expected some code that modifies the source map for the rewritten sap.ui.define call, but couldn't find anything. Is this handled by omitting the line and column info for the new AST nodes?

Source map voodoo, not needed to do anything

@RandomByte
Copy link
Member

fyi: We looked into this PR in more detail and also did a rebase in a separate sourcemap branch.

There have been changes to Terser in v5 and we might change other parts as well. Please don't put any work into rebasing or extending this PR yourself right now. We'll probably replace this PR with a new one once it's planned in our backlog. Thanks again for the work that went into this PR, it really helped us getting started with the topic.

RandomByte added a commit that referenced this pull request Feb 23, 2022
Resolves SAP/ui5-tooling#472
Supersedes #282
Based on SAP/ui5-tooling#583
JIRA: CPOUI5FOUNDATION-434

Co-authored-by: Matthias Osswald <mat.osswald@sap.com>
@RandomByte
Copy link
Member

Superseded by #695

We took over quite some code from this PR for the development of the Source Map support in UI5 Tooling 3.0. Thanks!

@RandomByte RandomByte closed this Apr 15, 2022
UI5 Tooling - Tasks automation moved this from Needs review to Done Apr 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Development

Successfully merging this pull request may close these issues.

None yet

5 participants