Skip to content

Commit

Permalink
Add "allowArrayLike" option to the destructuring and spread transforms
Browse files Browse the repository at this point in the history
When enabled, this option makes Babel treat array-like objects as if
they were iterables. This makes it possible to avoid a Symbol polyfill when
spreading or destructuring DOM lists in browsers without Symbol.iterator
support.
  • Loading branch information
nicolo-ribaudo committed Mar 20, 2020
1 parent 2e6f958 commit 71d0f21
Show file tree
Hide file tree
Showing 20 changed files with 110 additions and 5 deletions.
12 changes: 12 additions & 0 deletions packages/babel-helpers/src/helpers.js
Expand Up @@ -977,6 +977,18 @@ helpers.arrayWithHoles = helper("7.0.0-beta.0")`
}
`;

helpers.maybeArrayLike = helper("7.9.0")`
import arrayLikeToArray from "arrayLikeToArray";
export default function _maybeArrayLike(next, arr, i) {
if (arr && !Array.isArray(arr) && typeof arr.length === "number") {
var len = arr.length;
return arrayLikeToArray(arr, i !== void 0 && i < len ? i : len);
}
return next(arr, i);
}
`;

helpers.iterableToArray = helper("7.0.0-beta.0")`
export default function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
Expand Down
13 changes: 11 additions & 2 deletions packages/babel-plugin-transform-destructuring/src/index.js
Expand Up @@ -4,7 +4,11 @@ import { types as t } from "@babel/core";
export default declare((api, options) => {
api.assertVersion(7);

const { loose = false, useBuiltIns = false } = options;
const {
loose = false,
useBuiltIns = false,
allowArrayLike = false,
} = options;

if (typeof loose !== "boolean") {
throw new Error(`.loose must be a boolean or undefined`);
Expand Down Expand Up @@ -85,6 +89,7 @@ export default declare((api, options) => {
this.scope = opts.scope;
this.kind = opts.kind;
this.arrayOnlySpread = opts.arrayOnlySpread;
this.allowArrayLike = opts.allowArrayLike;
this.addHelper = opts.addHelper;
}

Expand Down Expand Up @@ -141,7 +146,7 @@ export default declare((api, options) => {
) {
return node;
} else {
return this.scope.toArray(node, count);
return this.scope.toArray(node, count, this.allowArrayLike);
}
}

Expand Down Expand Up @@ -523,6 +528,7 @@ export default declare((api, options) => {
scope: scope,
nodes: nodes,
arrayOnlySpread,
allowArrayLike,
addHelper: name => this.addHelper(name),
});

Expand All @@ -548,6 +554,7 @@ export default declare((api, options) => {
scope: scope,
nodes: nodes,
arrayOnlySpread,
allowArrayLike,
addHelper: name => this.addHelper(name),
});
destructuring.init(pattern, ref);
Expand All @@ -566,6 +573,7 @@ export default declare((api, options) => {
scope: scope,
nodes: nodes,
arrayOnlySpread,
allowArrayLike,
addHelper: name => this.addHelper(name),
});

Expand Down Expand Up @@ -624,6 +632,7 @@ export default declare((api, options) => {
scope: scope,
kind: node.kind,
arrayOnlySpread,
allowArrayLike,
addHelper: name => this.addHelper(name),
});

Expand Down
@@ -0,0 +1,6 @@
var o = { 0: "a", 2: "c", length: 3 };

var [...rest] = o;

expect(rest).toEqual(["a", undefined, "c"]);
expect(1 in rest).toBe(true); // Not holey
@@ -0,0 +1,6 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["transform-destructuring", { "allowArrayLike": true }]
]
}
@@ -0,0 +1,6 @@
var o = { 0: "a", 1: "b", 2: "c", length: 2 };

var [first, ...rest] = o;

expect(first).toBe("a");
expect(rest).toEqual(["b"]);
@@ -0,0 +1,6 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["transform-destructuring", { "allowArrayLike": true }]
]
}
@@ -0,0 +1,6 @@
var o = { 0: "a", 1: "b", 2: "c", length: 3 };

var [first, ...rest] = o;

expect(first).toBe("a");
expect(rest).toEqual(["b", "c"]);
@@ -0,0 +1 @@
var [first, ...rest] = o;
@@ -0,0 +1,6 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["transform-destructuring", { "allowArrayLike": true }]
]
}
@@ -0,0 +1,4 @@
var _o = o,
_o2 = babelHelpers.maybeArrayLike(babelHelpers.toArray, _o),
first = _o2[0],
rest = _o2.slice(1);
4 changes: 2 additions & 2 deletions packages/babel-plugin-transform-spread/src/index.js
Expand Up @@ -4,13 +4,13 @@ import { types as t } from "@babel/core";
export default declare((api, options) => {
api.assertVersion(7);

const { loose } = options;
const { loose, allowArrayLike } = options;

function getSpreadLiteral(spread, scope) {
if (loose && !t.isIdentifier(spread.argument, { name: "arguments" })) {
return spread.argument;
} else {
return scope.toArray(spread.argument, true);
return scope.toArray(spread.argument, true, allowArrayLike);
}
}

Expand Down
@@ -0,0 +1,6 @@
var p2 = { 0: "a", 2: "c", length: 3 };

var arr = [...p2, "d"];

expect(arr).toEqual(["a", undefined, "c", "d"]);
expect(1 in arr).toBe(true); // Not holey
@@ -0,0 +1,6 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["transform-spread", { "allowArrayLike": true }]
]
}
@@ -0,0 +1,5 @@
var p2 = { 0: "b", 1: "c", 2: "d", length: 2 };

var arr = ["a", ...p2, "e"];

expect(arr).toEqual(["a", "b", "c", "e"]);
@@ -0,0 +1,6 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["transform-spread", { "allowArrayLike": true }]
]
}
@@ -0,0 +1,5 @@
var p2 = { 0: "b", 1: "c", 2: "d", length: 3 };

var arr = ["a", ...p2, "e"];

expect(arr).toEqual(["a", "b", "c", "d", "e"]);
@@ -0,0 +1 @@
var arr = ["a", ...p2, "e"];
@@ -0,0 +1,6 @@
{
"plugins": [
["external-helpers", { "helperVersion": "7.100.0" }],
["transform-spread", { "allowArrayLike": true }]
]
}
@@ -0,0 +1 @@
var arr = ["a"].concat(babelHelpers.maybeArrayLike(babelHelpers.toConsumableArray, p2), ["e"]);
9 changes: 8 additions & 1 deletion packages/babel-traverse/src/scope/index.js
Expand Up @@ -493,7 +493,8 @@ export default class Scope {
console.log(sep);
}

toArray(node: Object, i?: number) {
// TODO: (Babel 8) Split i in two parameters, and use an object of flags
toArray(node: Object, i?: number | boolean, allowArrayLike?: boolean) {
if (t.isIdentifier(node)) {
const binding = this.getBinding(node.name);
if (binding && binding.constant && binding.path.isGenericType("Array")) {
Expand Down Expand Up @@ -536,6 +537,12 @@ export default class Scope {
// Used in array-rest to create an array
helperName = "toArray";
}

if (allowArrayLike) {
args.unshift(this.hub.addHelper(helperName));
helperName = "maybeArrayLike";
}

return t.callExpression(this.hub.addHelper(helperName), args);
}

Expand Down

0 comments on commit 71d0f21

Please sign in to comment.