Skip to content

enhance global_defs#1469

Closed
alexlamsl wants to merge 1 commit intomishoo:masterfrom
alexlamsl:global_defs-obj
Closed

enhance global_defs#1469
alexlamsl wants to merge 1 commit intomishoo:masterfrom
alexlamsl:global_defs-obj

Conversation

@alexlamsl
Copy link
Copy Markdown
Collaborator

This is an alternative approach to #1467, incorporating the feature to substitute AST_Node when supplied via command-line (API also supported, but a bit messier).

This PR also has the advantage of not relying on #1425 thus not requiring unsafe to function for expanded property syntax, i.e. process.engine="V8" now works with default compress options.

Included (slightly modified) all the tests from #1467, added cli mocha tests for #1467 (comment)

/cc @kzc

@alexlamsl alexlamsl changed the title enhance global_defs [WIP] enhance global_defs Feb 7, 2017
console.log(++CONFIG.VALUE);
console.log(++CONFIG["VAL" + "UE"]);
console.log(++DEBUG[CONFIG.VALUE]);
CONFIG.VALUE.FOO = "bar";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This leads to a nonsensical replacement 42..FOO - could you replace it with something that works - be it another variable or an object literal or something?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed - now behaves the same way as #1467.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Works for me.

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Feb 7, 2017

If this PR incorporates the same tests as PR #1467 and addresses the same issues and does not rely on unsafe, then this PR would be preferable.

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Feb 7, 2017

I noticed that this works:

$ echo 'console.log(baz);' | bin/uglifyjs --define console.log=println -c
println(baz);

but the reverse does not:

$ echo 'println(baz);' | bin/uglifyjs --define println=console.log -c
undefined:8001
                && (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
                               ^

TypeError: Cannot read property 'uses_with' of undefined

It's it's too difficult to fix we can drop the arbitrary node replacement feature altogether and just stick to replacing constants and literals.

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Feb 7, 2017

@alexlamsl By the way, these PRs are really impressive. Just tell me when you think I'm asking for an unreasonable feature.

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

@kzc as long as you are willing to test and point out areas of bugs / improvements, I'm happy to give it a go 😉

While making fixes to #1467, I noticed there is a fundamental difference between C={D:1} and C.D=1, i.e. the former express an absolute knowledge on C whereas the latter is only sure C has one defined property with a specific value.

So this is a reboot of the efforts, separating #1425 (best-effort basis) with global_defs (absolute substitution). Of course in some cases both features would be required to give a final optimal result.

Let me try and address the two issues you've mentioned above, and if successful, I'll close out #1467.

options = {
global_defs: { DEBUG: 0 }
}
input: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add this to the beginning of input:

const DEBUG = true;

It should also generate a warning.

Could you also add OTHER: 1 to global_defs and the following line to the test:

var OTHER;

To ensure it also produces a warning.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It would generate a warning, plus killing all the positive cases below (because they are no longer AST_SymolRef.undeclared() 😅

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'll pick two OTHER variables as you suggested then.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You could name it something else. Just curious what happens to variable declarations that are set and not set.

Copy link
Copy Markdown
Contributor

@kzc kzc Feb 8, 2017

Choose a reason for hiding this comment

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

Having const DEBUG = false; at the top of the file and overriding it with --define DEBUG=true (or vice versa) would be a common use case. It would work well with -c toplevel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overriding the definition would be ideal if you don't mind the extra work.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Okay, I'll go with the warning option then.

That's fine too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Overriding the definition would be ideal

Should we be limiting this override to top-level-defined variables or at any scopes?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So this is an interesting case to consider:

global_defs: { "config.DEBUG" : 1 }

var config = { engine: "voom!" };
if (config.DEBUG) {
  log();
}

Should we override the value and/or raise a warning, or something else altogether?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've added those tests to this PR to reflect the current behaviour. Please leave comments on what you expect each case to be and we'll go from there?

x = DEBUG;
}
expect: {
const ENV = 3;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

overriding const ENV should warn.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

See if d7e9eb9 works?

}
expect: {
const ENV = 3;
var FOO = 4;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

overriding var FOO should warn.

One could argue in this specific case if it is set to the same value (4) it shouldn't - but that's too much work.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

See if d7e9eb9 works?

}
expect: {
const FOO = { BAR: 0 };
console.log(FOO.BAR);
Copy link
Copy Markdown
Contributor

@kzc kzc Feb 8, 2017

Choose a reason for hiding this comment

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

This is a tricky case. But since FOO is known to be global I think FOO.BAR should be changed to 0.

Had FOO been a local variable or parameter, then it would have been left as is.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So do you mean no changes to current behaviour is required for this case?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If FOO is global - which it is in this test case - then FOO.BAR should be changed to 0.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah I see - the FOO.BAR ➡️ 0 optimisation goes under #1425. I'll add unsafe here to enable it then.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done 28c4d97

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Interesting side effect. That wasn't my intention. Even if FOO was declared as const FOO = null; I was thinking that since FOO is global then FOO.BAR is fair game to be replaced with the global_defs setting.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm slightly confused now - FOO.BAR is defined as "moo" In global_defs, so do you mean you want that instead of 0?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

PTAL at e88acb4 and see if that matches what you mean?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the new test and behavior is correct.

}

(function (def){
AST_Node.DEFMETHOD("resolve_defines", function(compressor) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To speed this up when global_defs are not defined:

        if (!compressor.option("global_defs")) return null;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done 00d93f3

lib/compress.js Outdated
});
def(AST_SymbolRef, function(compressor, suffix){
if (!this.undeclared()) return;
var name = this.name + suffix;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

var name;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done 85c3c60

lib/compress.js Outdated
if (!this.undeclared()) return;
var name = this.name + suffix;
var defines = compressor.option("global_defs");
if (defines && HOP(defines, name)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  •        if (defines && HOP(defines, (name = this.name + suffix))) {
    

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done 85c3c60

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

alexlamsl commented Feb 8, 2017

@kzc you may have discovered a bug / missing feature in #1469 (comment)

$ echo 'const a={b:0};console.log(a.b)' | bin/uglifyjs -c unsafe
const a={b:0};console.log(0);

$ echo 'const a=0;console.log(a)' | bin/uglifyjs -c
const a=0;console.log(a);

$ echo 'const a=0;console.log(a)' | bin/uglifyjs -c unsafe
const a=0;console.log(a);

}
input: {
function f(CONFIG) {
return CONFIG.VALUE;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add comment: // CONFIG not global - do not replace

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done 889e879

}
function g() {
var CONFIG = { VALUE: 1 };
return CONFIG.VALUE;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add comment: // CONFIG not global - do not replace

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done 889e879

}
input: {
function f(CONFIG) {
return CONFIG.VALUE;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add comment: // CONFIG not global - do not replace

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done 889e879

}
function g() {
var CONFIG = { VALUE: 1 };
return CONFIG.VALUE;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add comment: // CONFIG not global - do not replace

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done 889e879

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

alexlamsl commented Feb 8, 2017

@kzc I've updated test/compress/issue-208.js in 7495875 to clarify the value of ENV - is that what you expect it to be?

(If it were to be overridden by global_defs, it would have been 10 as opposed to 30.)

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

(Travis CI timed out.)

@alexlamsl alexlamsl closed this Feb 8, 2017
@alexlamsl alexlamsl reopened this Feb 8, 2017
@kzc
Copy link
Copy Markdown
Contributor

kzc commented Feb 8, 2017

I think we have keep backwards compatibility with -c --define:

$ uglifyjs -V
uglify-js 2.7.5
$ echo 'f(ENV * 10);' | uglifyjs -c --define ENV=1
f(10);
$ echo 'const ENV = 3; f(ENV * 10);' | uglifyjs -c --define ENV=1
const ENV=3;f(30);

But this seems inconsistent to me. ENV is global in both cases, but only in the former case is it replaced.

So if we are to keep backwards compatibility then we can't have a global const DEBUG = false; overridden by -c --define. That's unfortunate because that would be a real use case.

What's the logic governing the current behavior in master?

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

Both 2.7.5 and master are governed by this rule, which means a variable will never trigger global_defs if defined (local or global).

So this PR current is backward compatible with 2.7.5, with the addition of a new warning emitted on this case. I suppose if we ever go to 3.x then we can change this behaviour?

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

FYI, global_defs is introduced in dde5b22, and the only change to that line since then was #208.

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Feb 8, 2017

So this PR current is backward compatible with 2.7.5, with the addition of a new warning emitted on this case. I suppose if we ever go to 3.x then we can change this behaviour?

It's probably prudent to retain this behavior in 2.x and revisit the logic in 3.x.

If the variable is declared in 2.x, then no substitutions can take place at all - correct? Hmm. I wonder if any code in the wild would break since using the --define would have no effect, and as such would probably not be used.

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

If the variable is declared in 2.x, then no substitutions can take place at all - correct?

Except for the expanded syntax, e.g. --define CONFIG.DEBUG=false - in this case any CONFIG.DEBUG will get overridden even if a global variable CONFIG is defined.

Hmm. I wonder if any code in the wild would break

I'm wondering about that too - the 2.7.5 behaviour is so limited it's either not used, or the results aren't optimal. But hey, may be we should bet on the safe side, release the current form into the wild, then wait and see if there are request for enhancements on the Issue Tracker?

@kzc
Copy link
Copy Markdown
Contributor

kzc commented Feb 8, 2017

I tend to agree. A case could easily be made to allow --define overrides for declared globals. We don't have to wait for another user to request it since it's clearly a useful scenario.

@avdg, @rvanvelzen Do you have an opinion on this?

@alexlamsl
Copy link
Copy Markdown
Collaborator Author

Squashed all the previous commits, and added one to demonstrate the potential breaking change we've discussed.

FWIW, I'd vote towards including the change as it seems to be the expected behaviour, but I'm fine with it either way.

@alexlamsl alexlamsl changed the title [WIP] enhance global_defs enhance global_defs Feb 9, 2017
@kzc
Copy link
Copy Markdown
Contributor

kzc commented Feb 9, 2017

LGTM

@avdg
Copy link
Copy Markdown
Contributor

avdg commented Feb 9, 2017

LGTM unless there are coverage holes (which is a hard question anyway now especially without cover analysis).

But there are many tests in already :-)

This was referenced Feb 9, 2017
- support arrays, objects & AST_Node
- support `"a.b":1` on both cli & API
- emit warning if variable is modified
- override top-level variables

fixes mishoo#1416
closes mishoo#1198
alexlamsl added a commit to alexlamsl/UglifyJS that referenced this pull request Feb 18, 2017
- support arrays, objects & AST_Node
- support `"a.b":1` on both cli & API
- emit warning if variable is modified
- override top-level variables

fixes mishoo#1416
closes mishoo#1198
closes mishoo#1469
@alexlamsl alexlamsl closed this in e275148 Feb 23, 2017
@alexlamsl alexlamsl deleted the global_defs-obj branch February 24, 2017 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants