Skip to content

Commit 38d41ef

Browse files
authored
Merge pull request #606 from developit/fast-rest-parameters
Fast rest parameters
2 parents e082be1 + d56e4f0 commit 38d41ef

File tree

3 files changed

+239
-159
lines changed

3 files changed

+239
-159
lines changed

src/lib/babel-custom.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createConfigItem } from '@babel/core';
22
import babelPlugin from 'rollup-plugin-babel';
33
import merge from 'lodash.merge';
4+
import transformFastRest from './transform-fast-rest';
45
import { isTruthy } from '../utils';
56

67
const ESMODULES_TARGET = {
@@ -13,7 +14,9 @@ const mergeConfigItems = (type, ...configItemsToMerge) => {
1314
configItemsToMerge.forEach(configItemToMerge => {
1415
configItemToMerge.forEach(item => {
1516
const itemToMergeWithIndex = mergedItems.findIndex(
16-
mergedItem => mergedItem.file.resolved === item.file.resolved,
17+
mergedItem =>
18+
(mergedItem.name || mergedItem.file.resolved) ===
19+
(item.name || item.file.resolved),
1720
);
1821

1922
if (itemToMergeWithIndex === -1) {
@@ -37,8 +40,10 @@ const mergeConfigItems = (type, ...configItemsToMerge) => {
3740
};
3841

3942
const createConfigItems = (type, items) => {
40-
return items.map(({ name, ...options }) => {
41-
return createConfigItem([require.resolve(name), options], { type });
43+
return items.map(item => {
44+
let { name, value, ...options } = item;
45+
value = value || [require.resolve(name), options];
46+
return createConfigItem(value, { type });
4247
});
4348
};
4449

@@ -59,6 +64,9 @@ export default () => {
5964
},
6065

6166
config(config, { customOptions }) {
67+
const targets = customOptions.targets;
68+
const isNodeTarget = targets && targets.node != null;
69+
6270
const defaultPlugins = createConfigItems(
6371
'plugin',
6472
[
@@ -80,6 +88,18 @@ export default () => {
8088
externalHelpers: false,
8189
minify: true,
8290
},
91+
!customOptions.modern &&
92+
!isNodeTarget && {
93+
value: [
94+
transformFastRest,
95+
{
96+
// Use inline [].slice.call(arguments)
97+
helper: false,
98+
literal: true,
99+
},
100+
'transform-fast-rest',
101+
],
102+
},
83103
{
84104
name: '@babel/plugin-proposal-class-properties',
85105
loose: true,

src/lib/transform-fast-rest.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* @type {import('@babel/core')}
3+
*/
4+
5+
/**
6+
* Transform ...rest parameters to [].slice.call(arguments,offset).
7+
* Demo: https://astexplorer.net/#/gist/70aaa0306db9a642171ef3e2f35df2e0/576c150f647e4936fa6960e0453a11cdc5d81f21
8+
* Benchmark: https://jsperf.com/rest-arguments-babel-pr-9152/4
9+
* @param {object} opts
10+
* @param {babel.template} opts.template
11+
* @param {babel.types} opts.types
12+
* @returns {babel.PluginObj}
13+
*/
14+
export default function fastRestTransform({ template, types: t }) {
15+
const slice = template`var IDENT = Array.prototype.slice;`;
16+
17+
const VISITOR = {
18+
RestElement(path, state) {
19+
if (path.parentKey !== 'params') return;
20+
21+
// Create a global _slice alias
22+
let slice = state.get('slice');
23+
if (!slice) {
24+
slice = path.scope.generateUidIdentifier('slice');
25+
state.set('slice', slice);
26+
}
27+
28+
// _slice.call(arguments) or _slice.call(arguments, 1)
29+
const args = [t.identifier('arguments')];
30+
if (path.key) args.push(t.numericLiteral(path.key));
31+
const sliced = t.callExpression(
32+
t.memberExpression(t.clone(slice), t.identifier('call')),
33+
args,
34+
);
35+
36+
const ident = path.node.argument;
37+
const binding = path.scope.getBinding(ident.name);
38+
39+
if (binding.referencePaths.length !== 0) {
40+
// arguments access requires a non-Arrow function:
41+
const func = path.parentPath;
42+
if (t.isArrowFunctionExpression(func)) {
43+
func.arrowFunctionToExpression();
44+
}
45+
46+
if (binding.constant && binding.referencePaths.length === 1) {
47+
// one usage, never assigned - replace usage inline
48+
binding.referencePaths[0].replaceWith(sliced);
49+
} else {
50+
// unknown usage, create a binding
51+
const decl = t.variableDeclaration('var', [
52+
t.variableDeclarator(t.clone(ident), sliced),
53+
]);
54+
func.get('body').unshiftContainer('body', decl);
55+
}
56+
}
57+
58+
path.remove();
59+
},
60+
};
61+
62+
return {
63+
name: 'transform-fast-rest',
64+
visitor: {
65+
Program(path, state) {
66+
const childState = new Map();
67+
const useHelper = state.opts.helper === true; // defaults to false
68+
69+
if (!useHelper) {
70+
let inlineHelper;
71+
if (state.opts.literal === false) {
72+
inlineHelper = template.expression.ast`Array.prototype.slice`;
73+
} else {
74+
inlineHelper = template.expression.ast`[].slice`;
75+
}
76+
childState.set('slice', inlineHelper);
77+
}
78+
79+
path.traverse(VISITOR, childState);
80+
81+
const name = childState.get('slice');
82+
if (name && useHelper) {
83+
const helper = slice({ IDENT: name });
84+
t.addComment(helper.declarations[0].init, 'leading', '#__PURE__');
85+
path.unshiftContainer('body', helper);
86+
}
87+
},
88+
},
89+
};
90+
}

0 commit comments

Comments
 (0)