Skip to content

Commit

Permalink
Merge pull request #606 from developit/fast-rest-parameters
Browse files Browse the repository at this point in the history
Fast rest parameters
  • Loading branch information
developit committed May 8, 2020
2 parents e082be1 + d56e4f0 commit 38d41ef
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 159 deletions.
26 changes: 23 additions & 3 deletions src/lib/babel-custom.js
@@ -1,6 +1,7 @@
import { createConfigItem } from '@babel/core';
import babelPlugin from 'rollup-plugin-babel';
import merge from 'lodash.merge';
import transformFastRest from './transform-fast-rest';
import { isTruthy } from '../utils';

const ESMODULES_TARGET = {
Expand All @@ -13,7 +14,9 @@ const mergeConfigItems = (type, ...configItemsToMerge) => {
configItemsToMerge.forEach(configItemToMerge => {
configItemToMerge.forEach(item => {
const itemToMergeWithIndex = mergedItems.findIndex(
mergedItem => mergedItem.file.resolved === item.file.resolved,
mergedItem =>
(mergedItem.name || mergedItem.file.resolved) ===
(item.name || item.file.resolved),
);

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

const createConfigItems = (type, items) => {
return items.map(({ name, ...options }) => {
return createConfigItem([require.resolve(name), options], { type });
return items.map(item => {
let { name, value, ...options } = item;
value = value || [require.resolve(name), options];
return createConfigItem(value, { type });
});
};

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

config(config, { customOptions }) {
const targets = customOptions.targets;
const isNodeTarget = targets && targets.node != null;

const defaultPlugins = createConfigItems(
'plugin',
[
Expand All @@ -80,6 +88,18 @@ export default () => {
externalHelpers: false,
minify: true,
},
!customOptions.modern &&
!isNodeTarget && {
value: [
transformFastRest,
{
// Use inline [].slice.call(arguments)
helper: false,
literal: true,
},
'transform-fast-rest',
],
},
{
name: '@babel/plugin-proposal-class-properties',
loose: true,
Expand Down
90 changes: 90 additions & 0 deletions src/lib/transform-fast-rest.js
@@ -0,0 +1,90 @@
/**
* @type {import('@babel/core')}
*/

/**
* Transform ...rest parameters to [].slice.call(arguments,offset).
* Demo: https://astexplorer.net/#/gist/70aaa0306db9a642171ef3e2f35df2e0/576c150f647e4936fa6960e0453a11cdc5d81f21
* Benchmark: https://jsperf.com/rest-arguments-babel-pr-9152/4
* @param {object} opts
* @param {babel.template} opts.template
* @param {babel.types} opts.types
* @returns {babel.PluginObj}
*/
export default function fastRestTransform({ template, types: t }) {
const slice = template`var IDENT = Array.prototype.slice;`;

const VISITOR = {
RestElement(path, state) {
if (path.parentKey !== 'params') return;

// Create a global _slice alias
let slice = state.get('slice');
if (!slice) {
slice = path.scope.generateUidIdentifier('slice');
state.set('slice', slice);
}

// _slice.call(arguments) or _slice.call(arguments, 1)
const args = [t.identifier('arguments')];
if (path.key) args.push(t.numericLiteral(path.key));
const sliced = t.callExpression(
t.memberExpression(t.clone(slice), t.identifier('call')),
args,
);

const ident = path.node.argument;
const binding = path.scope.getBinding(ident.name);

if (binding.referencePaths.length !== 0) {
// arguments access requires a non-Arrow function:
const func = path.parentPath;
if (t.isArrowFunctionExpression(func)) {
func.arrowFunctionToExpression();
}

if (binding.constant && binding.referencePaths.length === 1) {
// one usage, never assigned - replace usage inline
binding.referencePaths[0].replaceWith(sliced);
} else {
// unknown usage, create a binding
const decl = t.variableDeclaration('var', [
t.variableDeclarator(t.clone(ident), sliced),
]);
func.get('body').unshiftContainer('body', decl);
}
}

path.remove();
},
};

return {
name: 'transform-fast-rest',
visitor: {
Program(path, state) {
const childState = new Map();
const useHelper = state.opts.helper === true; // defaults to false

if (!useHelper) {
let inlineHelper;
if (state.opts.literal === false) {
inlineHelper = template.expression.ast`Array.prototype.slice`;
} else {
inlineHelper = template.expression.ast`[].slice`;
}
childState.set('slice', inlineHelper);
}

path.traverse(VISITOR, childState);

const name = childState.get('slice');
if (name && useHelper) {
const helper = slice({ IDENT: name });
t.addComment(helper.declarations[0].init, 'leading', '#__PURE__');
path.unshiftContainer('body', helper);
}
},
},
};
}

0 comments on commit 38d41ef

Please sign in to comment.