From d0ca19157e490bbe19d0331fffb86e9d62f40421 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Fri, 8 May 2020 19:51:25 +0000 Subject: [PATCH] Add fast rest parameters transform --- src/lib/babel-custom.js | 26 ++++++++-- src/lib/transform-fast-rest.js | 90 ++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/lib/transform-fast-rest.js diff --git a/src/lib/babel-custom.js b/src/lib/babel-custom.js index d9af3ee..f593982 100644 --- a/src/lib/babel-custom.js +++ b/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 = { @@ -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) { @@ -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 }); }); }; @@ -59,6 +64,9 @@ export default () => { }, config(config, { customOptions }) { + const targets = customOptions.targets; + const isNodeTarget = targets && targets.node != null; + const defaultPlugins = createConfigItems( 'plugin', [ @@ -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, diff --git a/src/lib/transform-fast-rest.js b/src/lib/transform-fast-rest.js new file mode 100644 index 0000000..9b36758 --- /dev/null +++ b/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); + } + }, + }, + }; +}