From 9799f73ada50513bc6cc0cc3fd9f8954765670d2 Mon Sep 17 00:00:00 2001 From: erwin mombay Date: Wed, 21 Oct 2020 17:07:59 -0700 Subject: [PATCH] add babel-plugin-transform-block-scoping (#30807) * add babel-plugin-transform-block-scoping A discussion in https://github.com/evanw/esbuild/issues/478 has shown that using let and class in same enclosing level has a signigicant performance impact on the Safari browser. To alleviate this problem we transform the let and class constructs into var when it is 'easily' possible. Co-authored-by: Justin Ridgewell Co-authored-by: erwin mombay * turn on mangle on terser * Comments and test cases Co-authored-by: Justin Ridgewell --- .../babel-config/post-closure-config.js | 1 + .../index.js | 121 ++++++++++++++++++ .../transform-class/input.js | 42 ++++++ .../transform-class/options.json | 3 + .../transform-class/output.js | 55 ++++++++ .../no-transform-const/input.js | 38 ++++++ .../no-transform-const/options.json | 3 + .../no-transform-const/output.js | 37 ++++++ .../transform-let/input.js | 43 +++++++ .../transform-let/options.json | 3 + .../transform-let/output.js | 44 +++++++ .../test/index.js | 19 +++ build-system/compile/post-closure-babel.js | 2 +- 13 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/index.js create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/input.js create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/options.json create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/output.js create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/input.js create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/options.json create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/output.js create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/input.js create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/options.json create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/output.js create mode 100644 build-system/babel-plugins/babel-plugin-transform-block-scoping/test/index.js diff --git a/build-system/babel-config/post-closure-config.js b/build-system/babel-config/post-closure-config.js index 4fcc105fd072..17444080cdca 100644 --- a/build-system/babel-config/post-closure-config.js +++ b/build-system/babel-config/post-closure-config.js @@ -32,6 +32,7 @@ function getPostClosureConfig() { './build-system/babel-plugins/babel-plugin-const-transformer', './build-system/babel-plugins/babel-plugin-transform-remove-directives', './build-system/babel-plugins/babel-plugin-transform-stringish-literals', + './build-system/babel-plugins/babel-plugin-transform-block-scoping', ]; return { compact: false, diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/index.js b/build-system/babel-plugins/babel-plugin-transform-block-scoping/index.js new file mode 100644 index 000000000000..3b6a89a4ec99 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/index.js @@ -0,0 +1,121 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Transforms let scoped variables into var variables, including class +// declarations. +// +// Let variables inside loops that are referenced inside a +// closure are left alone. +// +// Re: https://github.com/evanw/esbuild/issues/478 +// Re: https://gist.github.com/jridgewell/7a6468f61eecb467b8de265f04963286 +// +// Input: +// ``` +// let x = 0; +// () => { +// let y = 1; +// } +// +// for (let i = 0; i < 1; i++) { +// i; +// } +// +// for (let i = 0; i < 1; i++) { +// () => i; +// } +// +// class Foo {} +// ``` +// +// Output: +// ``` +// var x = 0; +// () => { +// var y = 1; +// } +// +// for (var i = 0; i < 1; i++) { +// i; +// } +// +// for (let i = 0; i < 1; i++) { +// () => i; +// } +// +// var Foo = class {} +// ``` +module.exports = function ({template, types: t}) { + return { + name: 'block-scoping', // not required + visitor: { + ClassDeclaration(path) { + const {node} = path; + const {id} = node; + if (!id) { + return; + } + + const {name} = id; + node.type = 'ClassExpression'; + node.id = null; + path.replaceWith(template.statement.ast`let ${name} = ${node}`); + }, + + VariableDeclaration(path) { + const {node, scope} = path; + if (node.kind !== 'let') { + return; + } + + // We're looking for the function scope that would inherit this var + // declaration, and whether there is a loop scope in between. + const parent = path.findParent((p) => { + return p.isFunction() || p.isProgram() || p.isLoop(); + }); + const bindings = Object.keys( + t.getBindingIdentifiers(node, false, true) + ); + + // If the let is contained in a loop scope, then there's the + // possibility that a reference will be closed over in a nested + // function. Each loop iteration will requires its own value, which + // means we have to use another function to emulate the behavior. + if (parent.isLoop()) { + for (const name of bindings) { + const references = scope.getBinding(name).referencePaths; + for (const ref of references) { + const p = ref.findParent((p) => { + return p === parent || p.isFunction(); + }); + + if (p.isFunction()) { + // The let variable is closed over. In this case, just leave + // the let alone instead of trying to transform it. + return; + } + } + } + } + + for (const name of bindings) { + scope.rename(name); + } + node.kind = 'var'; + }, + }, + }; +}; diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/input.js b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/input.js new file mode 100644 index 000000000000..911611306e64 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/input.js @@ -0,0 +1,42 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +class A { + method1() {} + method2() {} + static staticMethod1() {} +} + +{ + new A(); + class A { + method1() {} + method2() {} + static staticMethod1() {} + } + new A(); +} + +function hello() { + new A(); + class A { + method1() {} + method2() {} + static staticMethod1() {} + } + new A(); +} + +new A(); diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/options.json b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/options.json new file mode 100644 index 000000000000..a47282a6be9a --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../.."] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/output.js b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/output.js new file mode 100644 index 000000000000..6f6eeb3ec6db --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-class-declaration/transform-class/output.js @@ -0,0 +1,55 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +var _A = class { + method1() {} + + method2() {} + + static staticMethod1() {} + +}; + +{ + new _A2(); + + var _A2 = class { + method1() {} + + method2() {} + + static staticMethod1() {} + + }; + + new _A2(); +} + +function hello() { + new _A3(); + + var _A3 = class { + method1() {} + + method2() {} + + static staticMethod1() {} + + }; + + new _A3(); +} + +new _A(); diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/input.js b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/input.js new file mode 100644 index 000000000000..f80b4d4893e0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/input.js @@ -0,0 +1,38 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const a = 'abc'; + +{ + const a = 'xyz'; +} + +function test() { + const a = 1; + { + const a = 2; + const b = 2; + } +} + +const z = []; + +for (const i = 0; i < 10; i++) { + z.push(function() { + return function() { + console.log(i); + }; + }); +} diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/options.json b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/options.json new file mode 100644 index 000000000000..a47282a6be9a --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../.."] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/output.js b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/output.js new file mode 100644 index 000000000000..db44958fd3c7 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/no-transform-const/output.js @@ -0,0 +1,37 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const a = 'abc'; +{ + const a = 'xyz'; +} + +function test() { + const a = 1; + { + const a = 2; + const b = 2; + } +} + +const z = []; + +for (const i = 0; i < 10; i++) { + z.push(function () { + return function () { + console.log(i); + }; + }); +} diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/input.js b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/input.js new file mode 100644 index 000000000000..2a72fa28cdca --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/input.js @@ -0,0 +1,43 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +let a = 'abc'; +{ + let a = 'xyz'; + console.log(a); +} +console.log(a); + +function test() { + let a = 1; + { + let a = 2; + console.log(a); + } + console.log(a); +} + +let z = []; + +for (let i = 0; i < 10; i++) { + z.push(function() { + console.log(i); + }); + + let x = i; + z.push(function() { + console.log(x); + }); +} diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/options.json b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/options.json new file mode 100644 index 000000000000..a47282a6be9a --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["../../../.."] +} diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/output.js b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/output.js new file mode 100644 index 000000000000..9732c34fe3f0 --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/fixtures/transform-variable-declaration/transform-let/output.js @@ -0,0 +1,44 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +var _a = 'abc'; +{ + var _a2 = 'xyz'; + console.log(_a2); +} +console.log(_a); + +function test() { + var _a3 = 1; + { + var _a4 = 2; + console.log(_a4); + } + console.log(_a3); +} + +var _z = []; + +for (let i = 0; i < 10; i++) { + _z.push(function () { + console.log(i); + }); + + let x = i; + + _z.push(function () { + console.log(x); + }); +} diff --git a/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/index.js b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/index.js new file mode 100644 index 000000000000..bb2e50c56b6a --- /dev/null +++ b/build-system/babel-plugins/babel-plugin-transform-block-scoping/test/index.js @@ -0,0 +1,19 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const runner = require('@babel/helper-plugin-test-runner').default; + +runner(__dirname); diff --git a/build-system/compile/post-closure-babel.js b/build-system/compile/post-closure-babel.js index 541e2865acb8..6178265b0bf4 100644 --- a/build-system/compile/post-closure-babel.js +++ b/build-system/compile/post-closure-babel.js @@ -30,7 +30,7 @@ const {debug, CompilationLifecycles} = require('./debug-compilation-lifecycle'); */ async function terserMinify(code) { const minified = await terser.minify(code, { - mangle: false, + mangle: true, compress: { defaults: false, unused: true,