Skip to content

Commit

Permalink
add babel-plugin-transform-block-scoping (ampproject#30807)
Browse files Browse the repository at this point in the history
* add babel-plugin-transform-block-scoping

A discussion in evanw/esbuild#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 <jridgewell@google.com>
Co-authored-by: erwin mombay <erwinm@google.com>

* turn on mangle on terser

* Comments and test cases

Co-authored-by: Justin Ridgewell <jridgewell@google.com>
  • Loading branch information
2 people authored and ed-bird committed Dec 10, 2020
1 parent 1d50112 commit 0698e95
Show file tree
Hide file tree
Showing 13 changed files with 410 additions and 1 deletion.
1 change: 1 addition & 0 deletions build-system/babel-config/post-closure-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
},
},
};
};
Original file line number Diff line number Diff line change
@@ -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();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["../../../.."]
}
Original file line number Diff line number Diff line change
@@ -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();
Original file line number Diff line number Diff line change
@@ -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);
};
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["../../../.."]
}
Original file line number Diff line number Diff line change
@@ -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);
};
});
}
Original file line number Diff line number Diff line change
@@ -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);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["../../../.."]
}
Loading

0 comments on commit 0698e95

Please sign in to comment.