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

Babel Following Closure Compilation #26779

Merged
merged 21 commits into from Mar 25, 2020

Conversation

kristoferbaxter
Copy link
Contributor

@kristoferbaxter kristoferbaxter commented Feb 13, 2020

Introduces a Babel and Terser processing step post Closure Compiler invocation.

This allows us to write Babel plugins (like the one included) that modify the output of Closure generated code to take advantage of newer language features in the module build.

This PR also removes shorten-license which did not appear to work for ESM builds.

@amp-owners-bot
Copy link

amp-owners-bot bot commented Feb 13, 2020

Hey @jridgewell, these files were changed:

build-system/babel-plugins/babel-plugin-transform-fix-leading-comments/index.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/index.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/argument-function/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/arguments-identifier/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/arguments-identifier/options.json 
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/arguments-identifier/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/async-function/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/async-function/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/async-function/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/class-function-declaration-in-public-method/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/class-function-declaration-in-public-method/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/class-function-declaration-in-public-method/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/existing-arrow-function/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/existing-arrow-function/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/existing-arrow-function/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/existing-async-arrow-function/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/existing-async-arrow-function/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/existing-async-arrow-function/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/export-default-named-declaration/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/export-default-named-declaration/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/export-default-named-declaration/output.mjs
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/export-named-declaration/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/export-named-declaration/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/export-named-declaration/output.mjs
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/generator-function/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/generator-function/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/generator-function/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/multiple-body-statements/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/multiple-body-statements/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/multiple-body-statements/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/new-expression/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/new-expression/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/new-expression/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/no-return/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/no-return/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/no-return/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/single-return-statement/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/single-return-statement/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/single-return-statement/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/this-argument/input.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/this-argument/options.json
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/fixtures/transform-assertions/this-argument/output.js
build-system/babel-plugins/babel-plugin-transform-function-declarations/test/index.js
build-system/babel-plugins/babel-plugin-transform-minified-comments/index.js
build-system/babel-plugins/babel-plugin-transform-minified-comments/test/fixtures/transform/licenses-tabs/input.js
build-system/babel-plugins/babel-plugin-transform-minified-comments/test/fixtures/transform/licenses-tabs/options.json
build-system/babel-plugins/babel-plugin-transform-minified-comments/test/fixtures/transform/licenses-tabs/output.js
build-system/babel-plugins/babel-plugin-transform-minified-comments/test/fixtures/transform/licenses/input.js
build-system/babel-plugins/babel-plugin-transform-minified-comments/test/fixtures/transform/licenses/options.json
build-system/babel-plugins/babel-plugin-transform-minified-comments/test/fixtures/transform/licenses/output.js
build-system/babel-plugins/babel-plugin-transform-minified-comments/test/index.js

@kristoferbaxter kristoferbaxter marked this pull request as ready for review February 14, 2020 00:22
@kristoferbaxter kristoferbaxter changed the title Babel post Babel Following Closure Compilation Feb 14, 2020
@alanorozco
Copy link
Member

Are the numbers reflecting extension changes?

Diffing both I only see that v0 changes size:

-   ✔ dist/v0.mjs                                     64.38 kb   74.99 kb   228.51 kb  
+   ✔ dist/v0.mjs                                     63.85 kb   74.57 kb   231.03 kb  

@kristoferbaxter
Copy link
Contributor Author

Are the numbers reflecting extension changes?

Should be, I'll verify though since it's a surprise there was no change.

@samouri
Copy link
Member

samouri commented Feb 19, 2020

Does Closure Compiler not support generating two outputs, one for module and the other for nomodule? It feels strange to compile certain parts down and then after CC finishes, transform them back up to es6.

@kristoferbaxter
Copy link
Contributor Author

kristoferbaxter commented Mar 23, 2020

Closure Compiler doesn't support outputting with two different language_out versions from a single invocation.

Ideally we are not doing any transpilation with Closure, and relying on Babel for this work.

@kristoferbaxter
Copy link
Contributor Author

@rsimha Can you approve the eslintrc change?

Copy link
Contributor

@rsimha rsimha left a comment

Choose a reason for hiding this comment

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

Thanks for working on this, @kristoferbaxter. I've requested some changes to the compilation code. Also, I didn't see any changes to .eslintrc. Is it missing from this PR?

@@ -138,8 +141,18 @@ function getJsonConfigurationPlugin() {
* @param {!Object<string, boolean>} buildFlags
* @return {!Array<string|!Array<string|!Object>>}
*/
function plugins({isEsmBuild, isForTesting, isSinglePass, isChecktypes}) {
const applied = [...defaultPlugins(isEsmBuild || false)];
function plugins({
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI @rcebulko and @jridgewell, since this code is being refactored in #27161.

build-system/compile/compile.js Outdated Show resolved Hide resolved
build-system/tasks/helpers.js Show resolved Hide resolved
build-system/compile/compile.js Show resolved Hide resolved
build-system/compile/compile.js Outdated Show resolved Hide resolved
* @param {boolean} isEsmBuild
* @return {!Promise}
*/
function postClosureBabel(directory, isEsmBuild) {
Copy link
Contributor

@rsimha rsimha Mar 24, 2020

Choose a reason for hiding this comment

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

A couple questions, since it looks like we're now doing babel transforms before and after closure compilation:

  • Any chance the two can be consolidated at some point?
  • The pre-closure babel transforms run on all files at once. This currently prevents us from enabling lazy-building for minified builds. Would it be possible to do the pre-closure transforms per-file as well, and add them to the chain in the main compile function?

Obviously, both these items needn't be addressed in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any chance the two can be consolidated at some point?

Not likely, the transforms executed post closure are expected to optimize the output of Closure Compiled code for our specific usecase. There might be times where we need to retain information from a pre-closure scan/transform and use it afterwards too.

The pre-closure babel transforms run on all files at once. This currently prevents us from enabling lazy-building for minified builds. Would it be possible to do the pre-closure transforms per-file as well, and add them to the chain in the main compile function?

Sounds like an interesting experiment for a future PR. Don't have a compelling reason why this must be done all at once.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, I've talked to @erwinmombay about a plan to address pre-closure transforms in a new PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is being done in #27426.

@kristoferbaxter
Copy link
Contributor Author

@rsimha Sorry, it was .eslintignore.

Looking at the other comments now.

@@ -23,7 +23,7 @@ module.exports = function() {
Statement(path) {
const {node} = path;
const {trailingComments} = node;
if (!trailingComments) {
if (!trailingComments || trailingComments.length <= 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This transform was executing on empty array lengths, addressed since this PR also includes a new transform doing something very similar.

Copy link
Member

Choose a reason for hiding this comment

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

just for my own knowledge, can the comments property actually be null?

@erwinmombay
Copy link
Member

@kristoferbaxter read through it again and LGTM

Copy link
Contributor

@rsimha rsimha left a comment

Choose a reason for hiding this comment

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

LGTM after a few minor comments are addressed. Thanks!

build-system/compile/compile.js Show resolved Hide resolved
build-system/compile/post-closure-babel.js Outdated Show resolved Hide resolved
build-system/compile/shorten-license.js Show resolved Hide resolved
src/examiner/examiner.js Show resolved Hide resolved
@kristoferbaxter
Copy link
Contributor Author

kristoferbaxter commented Mar 25, 2020

@samouri or @choumx Can you take a look at the change for examiner.js. Your approval is required.

There is a review comment explaining the change.

Copy link

@dreamofabear dreamofabear left a comment

Choose a reason for hiding this comment

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

Whoa that's a large diff. :)

src/examiner/examiner.js changes LGTM.

Copy link
Contributor

@rsimha rsimha left a comment

Choose a reason for hiding this comment

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

Latest changes LGTM.

Paging @jridgewell who added strict mode directives a long time ago (#9575), for comment on whether there's any reason we can't remove all the use stricts in our code.

@dreamofabear
Copy link

Modules are strict by default so moving our infra source to ES6 would allow that. Though I think we'd still need transpilation to run browser tests on IE.

Somewhat related: AMP currently runs in sloppy mode (#19380), so we probably need to check that "strict by default" in module build doesn't break anything that relies on sloppy mode behavior.

@kristoferbaxter
Copy link
Contributor Author

If we convert the entire node source code to ESM, and load it with either .mjs extensions or {type:"modules"} then we would be strict by default.

There is also the option to invoke node with --use_strict, but that has broken several times in the past on LTS releases so it's not recommended.

@kristoferbaxter kristoferbaxter merged commit 6f90ee3 into ampproject:master Mar 25, 2020
Performance Improvements automation moved this from In progress to Done Mar 25, 2020
@kristoferbaxter kristoferbaxter deleted the babel-post branch March 25, 2020 19:44
const conf = require('./build.conf');
const fs = require('fs-extra');
const path = require('path');
const resorcery = require('@jridgewell/resorcery');
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be @ampproject/remapping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like the entire codebase is using your previously personal named scope version. Sounds like a good change to make wholesale.


return {
compressed: minified.code,
terserMap: minified.map,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: why not just return the minified object as is? code and map properties are pretty standard for every build library.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can, but the method consuming it was renaming the values anyway. Since it's only intended to be used within this module, I renamed the output to the expected form.

remapped.toString()
);

return next(null, file);
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we verify the sourcemaps coming out of this? Please verify by loading it into https://justin.ridgewell.name/source-map-visualization/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did verify before submitting the first PR. Will check again.

Statement(path) {
const {node} = path;
const {trailingComments} = node;
if (!trailingComments || trailingComments.length <= 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be looking at leadingComments, too?

...comment,
value: comment.value.replace(/\n +/g, `\n`),
}));
next.addComments('leading', formattedComments);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why put the comments onto a new node in this plugin? The babel-plugin-transform-fix-leading-comments should already be taking care of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Feel free to try within the codebase, the existing transform did not properly format the comments in a way that would work with terser.

isNewedInProgramScope: (t, path) => {
const {name} = path.get('id').node;
let isNewed = false;
path
Copy link
Contributor

Choose a reason for hiding this comment

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

We can use the binding directly to skip a full file traversal:

const binding = path.scope.getBinding(name);
const isNewed = binding.referencePaths.some(p => p.parentPath.isNewExpression());

});
return isNewed;
},
};
Copy link
Contributor

Choose a reason for hiding this comment

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

There are more cases we should be checking for. Essentially, if the binding is used in any way that is not a simple CallExpression, then it is not safe to convert.

const binding = path.scope.getBinding(name);
const isSafeToTransform = binding.referencePaths.every(p => p.parentPath.isCallExpression({ callee: p.node }));
});

This would subsume the isExported and isNewed checks.

},
};

function createVariableDeclaration(t, path) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Everything should be inside the module.exports function, so that you don't need to keep passing around the t variable. The function will only ever be invoked once, so it won't slow anything down.

return {
name: 'safe-arrows',
pre() {
this.bailoutCount = {...BAIL_OUT_CONDITIONS};
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we delete all of this? It's just doing extra work which makes it more difficult to understand the transformer.

console /*OK*/
.log(`Success for function ${path.get('id').node.name}.`);
}
path.replaceWith(createVariableDeclaration(t, path));
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not 100% safe. FunctionDeclarations are available to be used at any time in their enclosing scope, even before the program would reach the line that define the function. Replacing it with a variable declaration does not preserve that.

The only way to make this safe is to find the enclosing block, and insert this variable as the first statement of the body.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging this pull request may close these issues.

None yet

10 participants